Skip to content

Commit f6a6e57

Browse files
scheglovcommit-bot@chromium.org
authored andcommitted
Issue 44223. Improve diagnostics for type arguments violating type parameter bounds.
Bug: #44223 Change-Id: Ia3ffddbc7d2a1be8455f642c5799d21051eefb1d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/184524 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]>
1 parent 896d72a commit f6a6e57

File tree

5 files changed

+74
-45
lines changed

5 files changed

+74
-45
lines changed

pkg/analyzer/lib/src/dart/element/generic_inferrer.dart

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ class GenericInferrer {
140140
if (considerExtendsClause && bound != null) {
141141
extendsClause = _TypeConstraint.fromExtends(
142142
typeParam,
143+
bound,
143144
Substitution.fromPairs(typeFormals, inferredTypes)
144145
.substituteType(bound),
145146
isNonNullableByDefault: isNonNullableByDefault,
@@ -164,38 +165,38 @@ class GenericInferrer {
164165
// Check the inferred types against all of the constraints.
165166
var knownTypes = <TypeParameterElement, DartType>{};
166167
for (int i = 0; i < typeFormals.length; i++) {
167-
TypeParameterElement typeParam = typeFormals[i];
168-
var constraints = _constraints[typeParam]!;
169-
170-
var typeParamBound = typeParam.bound;
171-
if (typeParamBound != null) {
172-
typeParamBound = Substitution.fromPairs(typeFormals, inferredTypes)
173-
.substituteType(typeParamBound);
174-
typeParamBound = _toLegacyElementIfOptOut(typeParamBound);
175-
} else {
176-
typeParamBound = typeProvider.dynamicType;
177-
}
168+
TypeParameterElement parameter = typeFormals[i];
169+
var constraints = _constraints[parameter]!;
178170

179171
var inferred = inferredTypes[i];
180172
bool success =
181173
constraints.every((c) => c.isSatisfiedBy(_typeSystem, inferred));
182-
if (success && !typeParamBound.isDynamic) {
183-
// If everything else succeeded, check the `extends` constraint.
184-
var extendsConstraint = _TypeConstraint.fromExtends(
185-
typeParam,
186-
typeParamBound,
187-
isNonNullableByDefault: isNonNullableByDefault,
188-
);
189-
constraints.add(extendsConstraint);
190-
success = extendsConstraint.isSatisfiedBy(_typeSystem, inferred);
174+
175+
// If everything else succeeded, check the `extends` constraint.
176+
if (success) {
177+
var parameterBoundRaw = parameter.bound;
178+
if (parameterBoundRaw != null) {
179+
var parameterBound =
180+
Substitution.fromPairs(typeFormals, inferredTypes)
181+
.substituteType(parameterBoundRaw);
182+
parameterBound = _toLegacyElementIfOptOut(parameterBound);
183+
var extendsConstraint = _TypeConstraint.fromExtends(
184+
parameter,
185+
parameterBoundRaw,
186+
parameterBound,
187+
isNonNullableByDefault: isNonNullableByDefault,
188+
);
189+
constraints.add(extendsConstraint);
190+
success = extendsConstraint.isSatisfiedBy(_typeSystem, inferred);
191+
}
191192
}
192193

193194
if (!success) {
194195
if (failAtError) return null;
195196
errorReporter?.reportErrorForNode(
196197
CompileTimeErrorCode.COULD_NOT_INFER,
197198
errorNode!,
198-
[typeParam.name, _formatError(typeParam, inferred, constraints)]);
199+
[parameter.name, _formatError(parameter, inferred, constraints)]);
199200

200201
// Heuristic: even if we failed, keep the erroneous type.
201202
// It should satisfy at least some of the constraints (e.g. the return
@@ -209,7 +210,7 @@ class GenericInferrer {
209210
var typeFormalsStr = typeFormals.map(_elementStr).join(', ');
210211
errorReporter?.reportErrorForNode(
211212
CompileTimeErrorCode.COULD_NOT_INFER, errorNode!, [
212-
typeParam.name,
213+
parameter.name,
213214
' Inferred candidate type ${_typeStr(inferred)} has type parameters'
214215
' [$typeFormalsStr], but a function with'
215216
' type parameters cannot be used as a type argument.'
@@ -223,7 +224,7 @@ class GenericInferrer {
223224
}
224225

225226
if (UnknownInferredType.isKnown(inferred)) {
226-
knownTypes[typeParam] = inferred;
227+
knownTypes[parameter] = inferred;
227228
} else if (_typeSystem.strictInference) {
228229
// [typeParam] could not be inferred. A result will still be returned
229230
// by [infer], with [typeParam] filled in as its bounds. This is
@@ -538,11 +539,12 @@ class _TypeConstraint extends _TypeRange {
538539
: super(upper: upper, lower: lower);
539540

540541
_TypeConstraint.fromExtends(
541-
TypeParameterElement element, DartType extendsType,
542+
TypeParameterElement element, DartType boundType, DartType extendsType,
542543
{required bool isNonNullableByDefault})
543544
: this(
544545
_TypeConstraintFromExtendsClause(
545546
element,
547+
boundType,
546548
extendsType,
547549
isNonNullableByDefault: isNonNullableByDefault,
548550
),
@@ -601,17 +603,31 @@ class _TypeConstraintFromArgument extends _TypeConstraintOrigin {
601603

602604
class _TypeConstraintFromExtendsClause extends _TypeConstraintOrigin {
603605
final TypeParameterElement typeParam;
606+
607+
/// The declared bound of [typeParam], not `null`, because we create
608+
/// this clause only when it is not `null`.
609+
///
610+
/// For example `Iterable<T>` for `<T, E extends Iterable<T>>`.
611+
final DartType boundType;
612+
613+
/// [boundType] in which type parameters are substituted with inferred
614+
/// type arguments.
615+
///
616+
/// For example `Iterable<int>` if `T` inferred to `int`.
604617
final DartType extendsType;
605618

606-
_TypeConstraintFromExtendsClause(this.typeParam, this.extendsType,
619+
_TypeConstraintFromExtendsClause(
620+
this.typeParam, this.boundType, this.extendsType,
607621
{required bool isNonNullableByDefault})
608622
: super(isNonNullableByDefault: isNonNullableByDefault);
609623

610624
@override
611625
List<String> formatError() {
626+
var boundStr = _typeStr(boundType);
627+
var extendsStr = _typeStr(extendsType);
612628
return [
613629
"Type parameter '${typeParam.name}'",
614-
"declared to extend '${_typeStr(extendsType)}'."
630+
"is declared to extend '$boundStr' producing '$extendsStr'."
615631
];
616632
}
617633
}

pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,15 +223,16 @@ class ExtensionMemberResolver {
223223
) {
224224
if (typeArgumentList != null) {
225225
for (var i = 0; i < typeArgumentTypes.length; i++) {
226-
var argType = typeArgumentTypes[i];
227-
var boundType = typeParameters[i].bound;
228-
if (boundType != null) {
229-
boundType = substitution.substituteType(boundType);
230-
if (!_typeSystem.isSubtypeOf(argType, boundType)) {
226+
var argument = typeArgumentTypes[i];
227+
var parameter = typeParameters[i];
228+
var parameterBound = parameter.bound;
229+
if (parameterBound != null) {
230+
parameterBound = substitution.substituteType(parameterBound);
231+
if (!_typeSystem.isSubtypeOf(argument, parameterBound)) {
231232
_errorReporter.reportErrorForNode(
232233
CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS,
233234
typeArgumentList.arguments[i],
234-
[argType, boundType],
235+
[argument, parameter.name, parameterBound],
235236
);
236237
}
237238
}

pkg/analyzer/lib/src/error/codes.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10801,7 +10801,8 @@ class CompileTimeErrorCode extends AnalyzerErrorCode {
1080110801
* Parameters:
1080210802
* 0: the name of the type used in the instance creation that should be
1080310803
* limited by the bound as specified in the class declaration
10804-
* 1: the name of the bounding type
10804+
* 1: the name of the type parameter
10805+
* 2: the substituted bound of the type parameter
1080510806
*/
1080610807
// #### Description
1080710808
//
@@ -10830,9 +10831,11 @@ class CompileTimeErrorCode extends AnalyzerErrorCode {
1083010831
// ```
1083110832
static const CompileTimeErrorCode TYPE_ARGUMENT_NOT_MATCHING_BOUNDS =
1083210833
CompileTimeErrorCode(
10833-
'TYPE_ARGUMENT_NOT_MATCHING_BOUNDS', "'{0}' doesn't extend '{1}'.",
10834-
correction: "Try using a type that is or is a subclass of '{1}'.",
10835-
hasPublishedDocs: true);
10834+
'TYPE_ARGUMENT_NOT_MATCHING_BOUNDS',
10835+
"'{0}' doesn't conform to the bound '{2}' of the type parameter '{1}'.",
10836+
correction: "Try using a type that is or is a subclass of '{2}'.",
10837+
hasPublishedDocs: true,
10838+
);
1083610839

1083710840
/**
1083810841
* No parameters.

pkg/analyzer/lib/src/error/type_arguments_verifier.dart

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ class TypeArgumentsVerifier {
234234
if (!_typeSystem.isSubtypeOf(typeArgument, bound)) {
235235
issues ??= <_TypeArgumentIssue>[];
236236
issues.add(
237-
_TypeArgumentIssue(i, typeParameter, typeArgument),
237+
_TypeArgumentIssue(i, typeParameter, bound, typeArgument),
238238
);
239239
}
240240
}
@@ -250,7 +250,7 @@ class TypeArgumentsVerifier {
250250
_errorReporter.reportErrorForNode(
251251
CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS,
252252
_typeArgumentErrorNode(typeName, issue.index),
253-
[issue.argument, issue.parameter],
253+
[issue.argument, issue.parameter.name, issue.parameterBound],
254254
);
255255
}
256256
return;
@@ -284,7 +284,7 @@ class TypeArgumentsVerifier {
284284
_errorReporter.reportErrorForNode(
285285
CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS,
286286
_typeArgumentErrorNode(typeName, i),
287-
[typeArgument, bound],
287+
[typeArgument, typeParameter.name, bound],
288288
);
289289
}
290290
}
@@ -361,7 +361,8 @@ class TypeArgumentsVerifier {
361361
continue;
362362
}
363363

364-
var rawBound = fnTypeParams[i].bound;
364+
var fnTypeParam = fnTypeParams[i];
365+
var rawBound = fnTypeParam.bound;
365366
if (rawBound == null) {
366367
continue;
367368
}
@@ -372,7 +373,7 @@ class TypeArgumentsVerifier {
372373
_errorReporter.reportErrorForNode(
373374
CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS,
374375
typeArgumentList[i],
375-
[argType, bound]);
376+
[argType, fnTypeParam.name, bound]);
376377
}
377378
}
378379
}
@@ -457,14 +458,22 @@ class _TypeArgumentIssue {
457458
/// The type parameter with the bound that was violated.
458459
final TypeParameterElement parameter;
459460

460-
/// The type argument that violated the bound.
461+
/// The substituted bound of the [parameter].
462+
final DartType parameterBound;
463+
464+
/// The type argument that violated the [parameterBound].
461465
final DartType argument;
462466

463-
_TypeArgumentIssue(this.index, this.parameter, this.argument);
467+
_TypeArgumentIssue(
468+
this.index,
469+
this.parameter,
470+
this.parameterBound,
471+
this.argument,
472+
);
464473

465474
@override
466475
String toString() {
467476
return 'TypeArgumentIssue(index=$index, parameter=$parameter, '
468-
'argument=$argument)';
477+
'parameterBound=$parameterBound, argument=$argument)';
469478
}
470479
}

pkg/analyzer/test/generated/strong_mode_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1252,7 +1252,7 @@ test() {
12521252
Couldn't infer type parameter 'T'.
12531253
12541254
Tried to infer 'String' for 'T' which doesn't work:
1255-
Type parameter 'T' declared to extend 'num'.
1255+
Type parameter 'T' is declared to extend 'num' producing 'num'.
12561256
The type 'String' was inferred from:
12571257
Return type declared as 'T Function(T)'
12581258
used where 'String Function(String)' is required.

0 commit comments

Comments
 (0)