Skip to content

Commit 88ae929

Browse files
stereotype441Commit Queue
authored andcommitted
Analyzer: resolve type to bound in FunctionExpressionInvocationResolver.
Consider the following code: void f<T extends void Function(int)>(List<T> x) { x.first(0); } While analyzing the expression `x.first(0)`, the analyzer has to do two things: - Convert the AST representation from a MethodInvocation (which is what was initially parsed) to a FunctionExpressionInvocation targeting a PrefixedIdentifier. This reflects the fact that the invocation isn't a method invocation after all; it's a function call invocation applied to a property get. - Convert the static type of `x.first` from `T` to its bound, `void Function(int)`, in order to type check the invocation. This is done using the `TypeSystemImpl.resolveToBound` method. Previously, `TypeSystemImpl.resolveToBound` was called as part of converting the AST representation, and the resolved bound (`void Function(int)` in this example) was stored as the static type of the FunctionExpressionInvocation target. This led to some minor inaccuracies in the AST representation (since the type returned by `TypeSystemImpl.resolveToBound` is *not* the correct type of the FunctionExpressionInvocation target). With this change, the call to `TypeSystemImpl.resolveToBound` happens during resolution of the FunctionExpressionInvocation instead, allowing the target of the FunctionExpressionInvocation to retain its correct static type. I'm in the middle of a larger arc of work trying to introduce a new, simpler mechanism for flow analysis to be told about the static types of property gets, and part of that arc of work will involve introducing a temporary check to verify that the old and new mechanisms see the same static types. Fixing this incorrect type will allow the temporary check to pass. This change also has the side effect of fixing #56907 (Analyzer fails to propery type check invocations of complex expressions whose type is a type parameter). This bug was happening in circumstances where a FunctionExpressionInvocation arises directly from parsing (rather than being created from a MethodInvocation), and the static type of the target is a type variable. Previously, the analyzer was failing to convert the static type to its bound when analyzing the FunctionExpressionInvocation, so it was failing to type check the invocation. Moving the call to `TypeSystemImpl.resolveToBound` into FunctionExpressionInvocation resolution ensures that the type is properly resolved to its bound in _all_ circumstances where a FunctionExpressionInvocation occurs. Fixes #56907. Bug: #56907 Change-Id: Id648d5289a6cabe95b8410abd19898acb97fc5e7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/390661 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]>
1 parent 37b7db0 commit 88ae929

File tree

6 files changed

+171
-4
lines changed

6 files changed

+171
-4
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:analyzer/error/listener.dart';
88
import 'package:analyzer/src/dart/ast/ast.dart';
99
import 'package:analyzer/src/dart/ast/extensions.dart';
1010
import 'package:analyzer/src/dart/element/type.dart';
11+
import 'package:analyzer/src/dart/element/type_system.dart';
1112
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
1213
import 'package:analyzer/src/dart/resolver/invocation_inferrer.dart';
1314
import 'package:analyzer/src/dart/resolver/type_property_resolver.dart';
@@ -32,6 +33,8 @@ class FunctionExpressionInvocationResolver {
3233
NullableDereferenceVerifier get _nullableDereferenceVerifier =>
3334
_resolver.nullableDereferenceVerifier;
3435

36+
TypeSystemImpl get _typeSystem => _resolver.typeSystem;
37+
3538
void resolve(FunctionExpressionInvocationImpl node,
3639
List<WhyNotPromotedGetter> whyNotPromotedList,
3740
{required DartType contextType}) {
@@ -50,6 +53,7 @@ class FunctionExpressionInvocationResolver {
5053
return;
5154
}
5255

56+
receiverType = _typeSystem.resolveToBound(receiverType);
5357
if (receiverType is FunctionType) {
5458
_nullableDereferenceVerifier.expression(
5559
CompileTimeErrorCode.UNCHECKED_INVOCATION_OF_NULLABLE_VALUE,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,7 @@ class MethodInvocationResolver with ScopeHelpers {
949949
FunctionExpressionInvocationImpl _rewriteAsFunctionExpressionInvocation(
950950
MethodInvocationImpl node, DartType getterReturnType,
951951
{bool isSuperAccess = false}) {
952-
var targetType = _typeSystem.resolveToBound(getterReturnType);
952+
var targetType = getterReturnType;
953953

954954
ExpressionImpl functionExpression;
955955
var target = node.target;

pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2049,8 +2049,7 @@ FunctionExpressionInvocation
20492049
token: foo
20502050
staticElement: <testLibraryFragment>::@class::C::@getter::foo
20512051
element: <testLibraryFragment>::@class::C::@getter::foo#element
2052-
staticType: double Function(int)
2053-
alias: <testLibraryFragment>::@typeAlias::MyFunction
2052+
staticType: T
20542053
argumentList: ArgumentList
20552054
leftParenthesis: (
20562055
arguments
@@ -8120,6 +8119,70 @@ FunctionExpressionInvocation
81208119
''');
81218120
}
81228121

8122+
test_rewrite_with_target() async {
8123+
await assertNoErrorsInCode(r'''
8124+
test<T extends Function>(List<T> x) {
8125+
x.first();
8126+
}
8127+
''');
8128+
8129+
var node = findNode.functionExpressionInvocation('x.first()');
8130+
assertResolvedNodeText(node, r'''
8131+
FunctionExpressionInvocation
8132+
function: PropertyAccess
8133+
target: SimpleIdentifier
8134+
token: x
8135+
staticElement: <testLibraryFragment>::@function::test::@parameter::x
8136+
element: <testLibraryFragment>::@function::test::@parameter::x#element
8137+
staticType: List<T>
8138+
operator: .
8139+
propertyName: SimpleIdentifier
8140+
token: first
8141+
staticElement: GetterMember
8142+
base: dart:core::<fragment>::@class::Iterable::@getter::first
8143+
substitution: {E: T, E: T}
8144+
element: dart:core::<fragment>::@class::Iterable::@getter::first#element
8145+
staticType: T
8146+
staticType: T
8147+
argumentList: ArgumentList
8148+
leftParenthesis: (
8149+
rightParenthesis: )
8150+
staticElement: <null>
8151+
element: <null>
8152+
staticInvokeType: dynamic
8153+
staticType: dynamic
8154+
''');
8155+
}
8156+
8157+
test_rewrite_without_target() async {
8158+
await assertNoErrorsInCode(r'''
8159+
extension E<T extends Function> on List<T> {
8160+
test() {
8161+
first();
8162+
}
8163+
}
8164+
''');
8165+
8166+
var node = findNode.functionExpressionInvocation('first()');
8167+
assertResolvedNodeText(node, r'''
8168+
FunctionExpressionInvocation
8169+
function: SimpleIdentifier
8170+
token: first
8171+
staticElement: GetterMember
8172+
base: dart:core::<fragment>::@class::Iterable::@getter::first
8173+
substitution: {E: T, E: T}
8174+
element: dart:core::<fragment>::@class::Iterable::@getter::first#element
8175+
staticType: T
8176+
argumentList: ArgumentList
8177+
leftParenthesis: (
8178+
rightParenthesis: )
8179+
staticElement: <null>
8180+
element: <null>
8181+
staticInvokeType: dynamic
8182+
staticType: dynamic
8183+
''');
8184+
}
8185+
81238186
test_superQualifier_identifier_methodOfMixin_inEnum() async {
81248187
await assertNoErrorsInCode(r'''
81258188
mixin M {

pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ m<T extends Function>() {
208208
''');
209209
// Do not assert no test errors. Deliberately invokes nullable type.
210210
var invocation = findNode.functionExpressionInvocation('first()');
211-
assertType(invocation.function, 'Function?');
211+
assertType(invocation.function, 'T?');
212212
}
213213

214214
test_mixin_hierarchy() async {

pkg/analyzer/test/src/dart/resolution/type_inference/function_expression_test.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import 'package:analyzer/src/error/codes.dart';
66
import 'package:test_reflective_loader/test_reflective_loader.dart';
77

88
import '../context_collection_resolution.dart';
9+
import '../node_text_expectations.dart';
910

1011
main() {
1112
defineReflectiveSuite(() {
1213
defineReflectiveTests(FunctionExpressionTest);
14+
defineReflectiveTests(UpdateNodeTextExpectations);
1315
});
1416
}
1517

@@ -862,6 +864,80 @@ var v = () sync* {
862864
_assertReturnType('() sync* {', 'Iterable<int>');
863865
}
864866

867+
test_targetBoundedByFunctionType_argumentTypeMismatch() async {
868+
await assertErrorsInCode(r'''
869+
int test<T extends int Function(int)>(T Function() createT) {
870+
return createT()('');
871+
}
872+
''', [
873+
error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 81, 2),
874+
]);
875+
876+
var node = findNode.functionExpressionInvocation("('')");
877+
assertResolvedNodeText(node, r'''FunctionExpressionInvocation
878+
function: FunctionExpressionInvocation
879+
function: SimpleIdentifier
880+
token: createT
881+
staticElement: <testLibraryFragment>::@function::test::@parameter::createT
882+
element: <testLibraryFragment>::@function::test::@parameter::createT#element
883+
staticType: T Function()
884+
argumentList: ArgumentList
885+
leftParenthesis: (
886+
rightParenthesis: )
887+
staticElement: <null>
888+
element: <null>
889+
staticInvokeType: T Function()
890+
staticType: T
891+
argumentList: ArgumentList
892+
leftParenthesis: (
893+
arguments
894+
SimpleStringLiteral
895+
literal: ''
896+
rightParenthesis: )
897+
staticElement: <null>
898+
element: <null>
899+
staticInvokeType: int Function(int)
900+
staticType: int
901+
''');
902+
}
903+
904+
test_targetBoundedByFunctionType_ok() async {
905+
await assertNoErrorsInCode(r'''
906+
int test<T extends int Function(int)>(T Function() createT) {
907+
return createT()(0);
908+
}
909+
''');
910+
911+
var node = findNode.functionExpressionInvocation('(0)');
912+
assertResolvedNodeText(node, r'''FunctionExpressionInvocation
913+
function: FunctionExpressionInvocation
914+
function: SimpleIdentifier
915+
token: createT
916+
staticElement: <testLibraryFragment>::@function::test::@parameter::createT
917+
element: <testLibraryFragment>::@function::test::@parameter::createT#element
918+
staticType: T Function()
919+
argumentList: ArgumentList
920+
leftParenthesis: (
921+
rightParenthesis: )
922+
staticElement: <null>
923+
element: <null>
924+
staticInvokeType: T Function()
925+
staticType: T
926+
argumentList: ArgumentList
927+
leftParenthesis: (
928+
arguments
929+
IntegerLiteral
930+
literal: 0
931+
parameter: root::@parameter::
932+
staticType: int
933+
rightParenthesis: )
934+
staticElement: <null>
935+
element: <null>
936+
staticInvokeType: int Function(int)
937+
staticType: int
938+
''');
939+
}
940+
865941
void _assertReturnType(String search, String expected) {
866942
var element = findNode.functionExpression(search).declaredElement!;
867943
assertType(element.returnType, expected);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// This test verifies that if the target of a function expression invocation has
6+
// a static type which is a type parameter, the type parameter is resolved to
7+
// its bound in order to check argument types.
8+
9+
int testSimpleTarget<T extends int Function(int)>(T Function() createT) {
10+
var tValue = createT();
11+
return tValue('');
12+
// ^^
13+
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
14+
// [cfe] The argument type 'String' can't be assigned to the parameter type 'int'.
15+
}
16+
17+
int testComplexTarget<T extends int Function(int)>(T Function() createT) {
18+
return createT()('');
19+
// ^^
20+
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
21+
// [cfe] The argument type 'String' can't be assigned to the parameter type 'int'.
22+
}
23+
24+
main() {}

0 commit comments

Comments
 (0)