Issue 47364. Add context messages to TYPE_ARGUMENT_NOT_MATCHING_BOUNDS.

We include two pieces of data:
1. If the named type was raw, the instantated type.
2. If the type was not regular bounded, also the inverted type.

Bug: https://github.com/dart-lang/sdk/issues/47364
Change-Id: Idc8fce998f755e68bee0ada2caa715b59101cc84
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/215484
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2021-10-04 22:23:00 +00:00 committed by commit-bot@chromium.org
parent 6e987219c4
commit 2b36a3945d
6 changed files with 137 additions and 45 deletions

View file

@ -8,12 +8,14 @@ import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_schema.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
import 'package:analyzer/src/generated/resolver.dart';
@ -265,18 +267,21 @@ class TypeArgumentsVerifier {
/// Verify that the type arguments in the given [namedType] are all within
/// their bounds.
void _checkForTypeArgumentNotMatchingBounds(NamedType namedType) {
var type = namedType.type;
final type = namedType.type;
if (type == null) {
return;
}
List<TypeParameterElement> typeParameters;
List<DartType> typeArguments;
var alias = type.alias;
final List<TypeParameterElement> typeParameters;
final String elementName;
final List<DartType> typeArguments;
final alias = type.alias;
if (alias != null) {
elementName = alias.element.name;
typeParameters = alias.element.typeParameters;
typeArguments = alias.typeArguments;
} else if (type is InterfaceType) {
elementName = type.element.name;
typeParameters = type.element.typeParameters;
typeArguments = type.typeArguments;
} else {
@ -289,7 +294,7 @@ class TypeArgumentsVerifier {
// Check for regular-bounded.
List<_TypeArgumentIssue>? issues;
var substitution = Substitution.fromPairs(typeParameters, typeArguments);
final substitution = Substitution.fromPairs(typeParameters, typeArguments);
for (var i = 0; i < typeArguments.length; i++) {
var typeParameter = typeParameters[i];
var typeArgument = typeArguments[i];
@ -325,6 +330,49 @@ class TypeArgumentsVerifier {
return;
}
List<DiagnosticMessage>? buildContextMessages({
List<DartType>? invertedTypeArguments,
}) {
final messages = <DiagnosticMessage>[];
void addMessage(String message) {
messages.add(
DiagnosticMessageImpl(
filePath: _errorReporter.source.fullName,
length: namedType.length,
message: message,
offset: namedType.offset,
url: null,
),
);
}
String typeArgumentsToString(List<DartType> typeArguments) {
return typeArguments
.map((e) => e.getDisplayString(withNullability: true))
.join(', ');
}
if (namedType.typeArguments == null) {
var typeStr = '$elementName<${typeArgumentsToString(typeArguments)}>';
addMessage(
"The raw type was instantiated as '$typeStr', "
"and is not regular-bounded.",
);
}
if (invertedTypeArguments != null) {
var invertedTypeStr =
'$elementName<${typeArgumentsToString(invertedTypeArguments)}>';
addMessage(
"The inverted type '$invertedTypeStr' is also not regular-bounded, "
"so the type is not well-bounded.",
);
}
return messages.isNotEmpty ? messages : null;
}
// If not allowed to be super-bounded, report issues.
if (!_shouldAllowSuperBoundedTypes(namedType)) {
for (var issue in issues) {
@ -332,27 +380,32 @@ class TypeArgumentsVerifier {
CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS,
_typeArgumentErrorNode(namedType, issue.index),
[issue.argument, issue.parameter.name, issue.parameterBound],
buildContextMessages(),
);
}
return;
}
// Prepare type arguments for checking for super-bounded.
type = _typeSystem.replaceTopAndBottom(type);
alias = type.alias;
if (alias != null) {
typeArguments = alias.typeArguments;
} else if (type is InterfaceType) {
typeArguments = type.typeArguments;
final invertedType = _typeSystem.replaceTopAndBottom(type);
final List<DartType> invertedTypeArguments;
final invertedAlias = invertedType.alias;
if (invertedAlias != null) {
invertedTypeArguments = invertedAlias.typeArguments;
} else if (invertedType is InterfaceType) {
invertedTypeArguments = invertedType.typeArguments;
} else {
return;
}
// Check for super-bounded.
substitution = Substitution.fromPairs(typeParameters, typeArguments);
for (var i = 0; i < typeArguments.length; i++) {
final invertedSubstitution = Substitution.fromPairs(
typeParameters,
invertedTypeArguments,
);
for (var i = 0; i < invertedTypeArguments.length; i++) {
var typeParameter = typeParameters[i];
var typeArgument = typeArguments[i];
var typeArgument = invertedTypeArguments[i];
var bound = typeParameter.bound;
if (bound == null) {
@ -360,13 +413,16 @@ class TypeArgumentsVerifier {
}
bound = _libraryElement.toLegacyTypeIfOptOut(bound);
bound = substitution.substituteType(bound);
bound = invertedSubstitution.substituteType(bound);
if (!_typeSystem.isSubtypeOf(typeArgument, bound)) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS,
_typeArgumentErrorNode(namedType, i),
[typeArgument, typeParameter.name, bound],
buildContextMessages(
invertedTypeArguments: invertedTypeArguments,
),
);
}
}

View file

@ -3508,8 +3508,10 @@ void test() {
class C<T0 extends List<T1>, T1 extends List<T0>> {}
class D extends C {}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 69, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 69, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 69, 1,
contextMessages: [message('/home/test/lib/test.dart', 69, 1)]),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 69, 1,
contextMessages: [message('/home/test/lib/test.dart', 69, 1)]),
]);
}
@ -3523,8 +3525,9 @@ void test() {
}
''', [
error(HintCode.UNUSED_LOCAL_VARIABLE, 73, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 81, 1),
error(CompileTimeErrorCode.COULD_NOT_INFER, 81, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 81, 1,
contextMessages: [message('/home/test/lib/test.dart', 81, 1)]),
]);
_assertLocalVarType('c', 'C<List<dynamic>, List<List<dynamic>>>');
}
@ -3560,7 +3563,11 @@ typedef T F<T>(T x);
class C<T extends F<T>> {}
C c;
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 48, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 48, 1,
contextMessages: [
message('/home/test/lib/test.dart', 48, 1),
message('/home/test/lib/test.dart', 48, 1)
]),
]);
_assertTopVarType('c', 'C<dynamic Function(dynamic)>');
}

View file

@ -562,7 +562,8 @@ A<String> Function() bar() {
return A.foo;
}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 41, 6),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 41, 6,
contextMessages: [message('/home/test/lib/test.dart', 39, 9)]),
]);
var classElement = findElement.class_('A');

View file

@ -75,7 +75,8 @@ var t = C<int, int>;
class C<T extends num> {}
var t = C<String>;
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 36, 6),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 36, 6,
contextMessages: [message('/home/test/lib/test.dart', 34, 9)]),
]);
var typeLiteral = findNode.typeLiteral('C<String>;');
@ -142,7 +143,8 @@ class C<T> {}
typedef CA<T extends num> = C<T>;
var t = CA<String>;
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 59, 6),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 59, 6,
contextMessages: [message('/home/test/lib/test.dart', 56, 10)]),
]);
var typeLiteral = findNode.typeLiteral('CA<String>;');
@ -374,7 +376,8 @@ var t = Fn<int, String>;
typedef Fn<T extends num> = void Function(T);
var t = Fn<String>;
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 57, 6),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 57, 6,
contextMessages: [message('/home/test/lib/test.dart', 54, 10)]),
]);
var typeLiteral = findNode.typeLiteral('Fn<String>;');

View file

@ -33,10 +33,12 @@ main() {
P._();
}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 154, 1,
contextMessages: [message('/home/test/lib/test.dart', 154, 1)]),
error(CompileTimeErrorCode.COULD_NOT_INFER, 154, 3),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 154, 1,
contextMessages: [message('/home/test/lib/test.dart', 154, 1)]),
error(CompileTimeErrorCode.COULD_NOT_INFER, 154, 3),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 154, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 154, 1),
]);
}
@ -58,8 +60,9 @@ main() {
}
''', [
error(HintCode.UNUSED_LOCAL_VARIABLE, 120, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 124, 1),
error(CompileTimeErrorCode.COULD_NOT_INFER, 124, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 124, 1,
contextMessages: [message('/home/test/lib/test.dart', 124, 1)]),
]);
}

View file

@ -129,7 +129,8 @@ class C {
C(G<B> this.f) {}
}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 71, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 71, 1,
contextMessages: [message('/home/test/lib/test.dart', 69, 4)]),
]);
}
@ -140,7 +141,8 @@ class B {}
class G<E extends A> {}
G<B> f() => throw 0;
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 48, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 48, 1,
contextMessages: [message('/home/test/lib/test.dart', 46, 4)]),
]);
}
@ -151,7 +153,8 @@ class B {}
class G<E extends A> {}
typedef G<B> f();
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 56, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 56, 1,
contextMessages: [message('/home/test/lib/test.dart', 54, 4)]),
]);
}
@ -162,7 +165,8 @@ class B {}
class G<E extends A> {}
f(G<B> h()) {}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 50, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 50, 1,
contextMessages: [message('/home/test/lib/test.dart', 48, 4)]),
]);
}
@ -184,7 +188,8 @@ class B {}
class G<E extends A> {}
var b = 1 is G<B>;
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 61, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 61, 1,
contextMessages: [message('/home/test/lib/test.dart', 59, 4)]),
]);
}
@ -252,7 +257,8 @@ class C {
G<B> m() => throw 0;
}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 60, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 60, 1,
contextMessages: [message('/home/test/lib/test.dart', 58, 4)]),
]);
}
@ -297,7 +303,8 @@ class Bar<T extends Foo<T>> {}
class Baz extends Bar {}
void main() {}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 65, 3),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 65, 3,
contextMessages: [message('/home/test/lib/test.dart', 65, 3)]),
]);
// Instantiate-to-bounds should have instantiated "Bar" to "Bar<Foo>".
assertType(result.unit.declaredElement!.getType('Baz')!.supertype,
@ -311,7 +318,8 @@ class B {}
typedef F<T extends A>();
F<B> fff = (throw 42);
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 50, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 50, 1,
contextMessages: [message('/home/test/lib/test.dart', 48, 4)]),
]);
}
@ -350,7 +358,8 @@ class B {}
class G<E extends A> {}
f(G<B> g) {}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 50, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 50, 1,
contextMessages: [message('/home/test/lib/test.dart', 48, 4)]),
]);
}
@ -376,7 +385,8 @@ class C<E> {}
class D<E extends A> {}
C<D<B>> c = (throw 0);
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 64, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 64, 1,
contextMessages: [message('/home/test/lib/test.dart', 62, 4)]),
]);
}
@ -388,7 +398,8 @@ class C {}
class G<E extends A> {}
class D<F extends G<B>> {}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 77, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 77, 1,
contextMessages: [message('/home/test/lib/test.dart', 75, 4)]),
]);
}
@ -399,7 +410,8 @@ class B {}
class G<E extends A> {}
G<B> g = (throw 0);
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 48, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 48, 1,
contextMessages: [message('/home/test/lib/test.dart', 46, 4)]),
]);
}
@ -487,7 +499,8 @@ typedef FB<T extends F> = S Function<S extends T>(S);
class CB<T extends F> {}
void f(CB<FB<F>> a) {}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 119, 5),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 119, 5,
contextMessages: [message('/home/test/lib/test.dart', 116, 9)]),
]);
}
@ -613,7 +626,8 @@ typedef X<T> = A;
class A<T extends A<T>> {}
typedef X<T> = A;
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 42, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 42, 1,
contextMessages: [message('/home/test/lib/test.dart', 42, 1)]),
]);
}
@ -623,7 +637,8 @@ class A {}
typedef X<T extends A> = Map<int, T>;
void f(X<String> a) {}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 58, 6),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 58, 6,
contextMessages: [message('/home/test/lib/test.dart', 56, 9)]),
]);
}
@ -642,7 +657,11 @@ typedef A<X> = X Function(X);
typedef G<X extends A<X>> = void Function<Y extends X>();
foo(G g) {}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 92, 1),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 92, 1,
contextMessages: [
message('/home/test/lib/test.dart', 92, 1),
message('/home/test/lib/test.dart', 92, 1)
]),
]);
}
@ -698,7 +717,8 @@ A get foo => throw 0;
class C<T extends int> {}
var t = C<String>;
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 36, 6),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 36, 6,
contextMessages: [message('/home/test/lib/test.dart', 34, 9)]),
]);
}
@ -707,7 +727,8 @@ var t = C<String>;
typedef Cb<T extends int> = void Function();
var t = Cb<String>;
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 56, 6),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 56, 6,
contextMessages: [message('/home/test/lib/test.dart', 53, 10)]),
]);
}
@ -717,7 +738,8 @@ class C {}
typedef D<T extends int> = C;
var t = D<String>;
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 51, 6),
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 51, 6,
contextMessages: [message('/home/test/lib/test.dart', 49, 9)]),
]);
}
}