Skip to content

Commit 64211b6

Browse files
srawlinscommit-bot@chromium.org
authored andcommitted
Verify that function reference type arguments match bounds
Bug: #46233 Change-Id: Id83a1fbbb058ed04c4433a380ed08799b047c11e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/204520 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 35b9419 commit 64211b6

File tree

4 files changed

+100
-54
lines changed

4 files changed

+100
-54
lines changed

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,6 @@ class FunctionReferenceResolver {
167167
var invokeType = rawType.instantiate(typeArguments);
168168
node.typeArgumentTypes = typeArguments;
169169
node.staticType = invokeType;
170-
171-
// TODO(srawlins): Verify that type arguments conform to bounds. This will
172-
// probably be done later, not in this resolution phase.
173170
}
174171

175172
void _resolveFunctionReferenceMethod({

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

Lines changed: 69 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,22 @@ class TypeArgumentsVerifier {
3333
_libraryElement.typeSystem as TypeSystemImpl;
3434

3535
void checkFunctionExpressionInvocation(FunctionExpressionInvocation node) {
36-
_checkTypeArguments(node);
36+
_checkTypeArguments(
37+
node.typeArguments?.arguments,
38+
node.function.staticType,
39+
node.staticInvokeType,
40+
);
3741
_checkForImplicitDynamicInvoke(node);
3842
}
3943

44+
void checkFunctionReference(FunctionReference node) {
45+
_checkTypeArguments(
46+
node.typeArguments?.arguments,
47+
node.function.staticType,
48+
node.staticType,
49+
);
50+
}
51+
4052
void checkListLiteral(ListLiteral node) {
4153
var typeArguments = node.typeArguments;
4254
if (typeArguments != null) {
@@ -68,7 +80,11 @@ class TypeArgumentsVerifier {
6880
}
6981

7082
void checkMethodInvocation(MethodInvocation node) {
71-
_checkTypeArguments(node);
83+
_checkTypeArguments(
84+
node.typeArguments?.arguments,
85+
node.function.staticType,
86+
node.staticInvokeType,
87+
);
7288
_checkForImplicitDynamicInvoke(node);
7389
}
7490

@@ -324,63 +340,65 @@ class TypeArgumentsVerifier {
324340
}
325341
}
326342

327-
/// Verify that the given [typeArguments] are all within their bounds, as
328-
/// defined by the given [element].
329-
void _checkTypeArguments(InvocationExpression node) {
330-
var typeArgumentList = node.typeArguments?.arguments;
343+
/// Verify that each type argument in [typeArgumentList] is within its bounds,
344+
/// as defined by [genericType].
345+
void _checkTypeArguments(
346+
List<TypeAnnotation>? typeArgumentList,
347+
DartType? genericType,
348+
DartType? instantiatedType,
349+
) {
331350
if (typeArgumentList == null) {
332351
return;
333352
}
334353

335-
var genericType = node.function.staticType;
336-
var instantiatedType = node.staticInvokeType;
337-
if (genericType is FunctionType && instantiatedType is FunctionType) {
338-
var fnTypeParams = genericType.typeFormals;
339-
var typeArgs = typeArgumentList.map((t) => t.typeOrThrow).toList();
340-
341-
// If the amount mismatches, clean up the lists to be substitutable. The
342-
// mismatch in size is reported elsewhere, but we must successfully
343-
// perform substitution to validate bounds on mismatched lists.
344-
final providedLength = math.min(typeArgs.length, fnTypeParams.length);
345-
fnTypeParams = fnTypeParams.sublist(0, providedLength);
346-
typeArgs = typeArgs.sublist(0, providedLength);
347-
348-
for (int i = 0; i < providedLength; i++) {
349-
// Check the `extends` clause for the type parameter, if any.
350-
//
351-
// Also substitute to handle cases like this:
352-
//
353-
// <TFrom, TTo extends TFrom>
354-
// <TFrom, TTo extends Iterable<TFrom>>
355-
// <T extends Cloneable<T>>
356-
//
357-
DartType argType = typeArgs[i];
358-
359-
if (argType is FunctionType && argType.typeFormals.isNotEmpty) {
360-
if (!_libraryElement.featureSet.isEnabled(Feature.generic_metadata)) {
361-
_errorReporter.reportErrorForNode(
362-
CompileTimeErrorCode
363-
.GENERIC_FUNCTION_TYPE_CANNOT_BE_TYPE_ARGUMENT,
364-
typeArgumentList[i],
365-
);
366-
continue;
367-
}
368-
}
354+
if (genericType is! FunctionType || instantiatedType is! FunctionType) {
355+
return;
356+
}
369357

370-
var fnTypeParam = fnTypeParams[i];
371-
var rawBound = fnTypeParam.bound;
372-
if (rawBound == null) {
358+
var fnTypeParams = genericType.typeFormals;
359+
var typeArgs = typeArgumentList.map((t) => t.typeOrThrow).toList();
360+
361+
// If the amount mismatches, clean up the lists to be substitutable. The
362+
// mismatch in size is reported elsewhere, but we must successfully
363+
// perform substitution to validate bounds on mismatched lists.
364+
var providedLength = math.min(typeArgs.length, fnTypeParams.length);
365+
fnTypeParams = fnTypeParams.sublist(0, providedLength);
366+
typeArgs = typeArgs.sublist(0, providedLength);
367+
368+
for (int i = 0; i < providedLength; i++) {
369+
// Check the `extends` clause for the type parameter, if any.
370+
//
371+
// Also substitute to handle cases like this:
372+
//
373+
// <TFrom, TTo extends TFrom>
374+
// <TFrom, TTo extends Iterable<TFrom>>
375+
// <T extends Cloneable<T>>
376+
//
377+
DartType argType = typeArgs[i];
378+
379+
if (argType is FunctionType && argType.typeFormals.isNotEmpty) {
380+
if (!_libraryElement.featureSet.isEnabled(Feature.generic_metadata)) {
381+
_errorReporter.reportErrorForNode(
382+
CompileTimeErrorCode.GENERIC_FUNCTION_TYPE_CANNOT_BE_TYPE_ARGUMENT,
383+
typeArgumentList[i],
384+
);
373385
continue;
374386
}
387+
}
375388

376-
var substitution = Substitution.fromPairs(fnTypeParams, typeArgs);
377-
var bound = substitution.substituteType(rawBound);
378-
if (!_typeSystem.isSubtypeOf(argType, bound)) {
379-
_errorReporter.reportErrorForNode(
380-
CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS,
381-
typeArgumentList[i],
382-
[argType, fnTypeParam.name, bound]);
383-
}
389+
var fnTypeParam = fnTypeParams[i];
390+
var rawBound = fnTypeParam.bound;
391+
if (rawBound == null) {
392+
continue;
393+
}
394+
395+
var substitution = Substitution.fromPairs(fnTypeParams, typeArgs);
396+
var bound = substitution.substituteType(rawBound);
397+
if (!_typeSystem.isSubtypeOf(argType, bound)) {
398+
_errorReporter.reportErrorForNode(
399+
CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS,
400+
typeArgumentList[i],
401+
[argType, fnTypeParam.name, bound]);
384402
}
385403
}
386404
}

pkg/analyzer/lib/src/generated/error_verifier.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,11 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
756756
super.visitFunctionExpressionInvocation(node);
757757
}
758758

759+
@override
760+
void visitFunctionReference(FunctionReference node) {
761+
_typeArgumentsVerifier.checkFunctionReference(node);
762+
}
763+
759764
@override
760765
void visitFunctionTypeAlias(FunctionTypeAlias node) {
761766
_checkForBuiltInIdentifierAsName(

pkg/analyzer/test/src/diagnostics/type_argument_not_matching_bounds_test.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,32 @@ main() {
451451
''');
452452
}
453453

454+
test_functionReference() async {
455+
await assertErrorsInCode('''
456+
void bar(void Function<T extends num>(T a) foo) {
457+
foo<String>;
458+
}
459+
''', [
460+
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 56, 6),
461+
]);
462+
}
463+
464+
test_functionReference_matching() async {
465+
await assertNoErrorsInCode('''
466+
void bar(void Function<T extends num>(T a) foo) {
467+
foo<int>;
468+
}
469+
''');
470+
}
471+
472+
test_functionReference_regularBounded() async {
473+
await assertNoErrorsInCode('''
474+
void bar(void Function<T>(T a) foo) {
475+
foo<String>;
476+
}
477+
''');
478+
}
479+
454480
test_genericFunctionTypeArgument_invariant() async {
455481
await assertErrorsInCode(r'''
456482
typedef F = T Function<T>(T);

0 commit comments

Comments
 (0)