Skip to content

Commit 014cca1

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Migration: add support for invocation of generic functions.
Change-Id: I56dbedad452e24aaf57ec1d622ce776089e28de7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107449 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]>
1 parent 9640ae8 commit 014cca1

4 files changed

Lines changed: 133 additions & 22 deletions

File tree

pkg/nnbd_migration/lib/src/decorated_type.dart

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,33 @@ class DecoratedType {
170170
}
171171
}
172172

173+
/// If this type is a function type, returns its generic formal parameters.
174+
/// Otherwise returns `null`.
175+
List<TypeParameterElement> get typeFormals {
176+
var type = this.type;
177+
if (type is FunctionType) {
178+
return type.typeFormals;
179+
} else {
180+
return null;
181+
}
182+
}
183+
184+
/// Converts one function type into another by substituting the given
185+
/// [argumentTypes] for the function's generic parameters.
186+
DecoratedType instantiate(List<DecoratedType> argumentTypes) {
187+
var type = this.type as FunctionType;
188+
var typeFormals = type.typeFormals;
189+
List<DartType> undecoratedArgumentTypes = [];
190+
Map<TypeParameterElement, DecoratedType> substitution = {};
191+
for (int i = 0; i < argumentTypes.length; i++) {
192+
var argumentType = argumentTypes[i];
193+
undecoratedArgumentTypes.add(argumentType.type);
194+
substitution[typeFormals[i]] = argumentType;
195+
}
196+
return _substituteFunctionAfterFormals(
197+
type.instantiate(undecoratedArgumentTypes), substitution);
198+
}
199+
173200
/// Apply the given [substitution] to this type.
174201
///
175202
/// [undecoratedResult] is the result of the substitution, as determined by
@@ -204,11 +231,14 @@ class DecoratedType {
204231
}
205232
return '$name$args$trailing';
206233
} else if (type is FunctionType) {
207-
assert(type.typeFormals.isEmpty); // TODO(paulberry)
234+
String formals = '';
235+
if (type.typeFormals.isNotEmpty) {
236+
formals = '<${type.typeFormals.join(', ')}>';
237+
}
208238
assert(type.namedParameterTypes.isEmpty &&
209239
namedParameters.isEmpty); // TODO(paulberry)
210240
var args = positionalParameters.map((p) => p.toString()).join(', ');
211-
return '$returnType Function($args)$trailing';
241+
return '$returnType Function$formals($args)$trailing';
212242
} else if (type is DynamicTypeImpl) {
213243
return 'dynamic';
214244
} else {
@@ -230,21 +260,7 @@ class DecoratedType {
230260
var type = this.type;
231261
if (type is FunctionType && undecoratedResult is FunctionType) {
232262
assert(type.typeFormals.isEmpty); // TODO(paulberry)
233-
var newPositionalParameters = <DecoratedType>[];
234-
for (int i = 0; i < positionalParameters.length; i++) {
235-
var numRequiredParameters =
236-
undecoratedResult.normalParameterTypes.length;
237-
var undecoratedParameterType = i < numRequiredParameters
238-
? undecoratedResult.normalParameterTypes[i]
239-
: undecoratedResult
240-
.optionalParameterTypes[i - numRequiredParameters];
241-
newPositionalParameters.add(positionalParameters[i]
242-
._substitute(substitution, undecoratedParameterType));
243-
}
244-
return DecoratedType(undecoratedResult, node,
245-
returnType: returnType._substitute(
246-
substitution, undecoratedResult.returnType),
247-
positionalParameters: newPositionalParameters);
263+
return _substituteFunctionAfterFormals(undecoratedResult, substitution);
248264
} else if (type is InterfaceType && undecoratedResult is InterfaceType) {
249265
List<DecoratedType> newTypeArguments = [];
250266
for (int i = 0; i < typeArguments.length; i++) {
@@ -266,6 +282,27 @@ class DecoratedType {
266282
}
267283
throw '$type.substitute($substitution)'; // TODO(paulberry)
268284
}
285+
286+
/// Performs the logic that is common to substitution and function type
287+
/// instantiation. Namely, a decorated type is formed whose undecorated type
288+
/// is [undecoratedResult], and whose return type, positional parameters, and
289+
/// named parameters are formed by performing the given [substitution].
290+
DecoratedType _substituteFunctionAfterFormals(FunctionType undecoratedResult,
291+
Map<TypeParameterElement, DecoratedType> substitution) {
292+
var newPositionalParameters = <DecoratedType>[];
293+
for (int i = 0; i < positionalParameters.length; i++) {
294+
var numRequiredParameters = undecoratedResult.normalParameterTypes.length;
295+
var undecoratedParameterType = i < numRequiredParameters
296+
? undecoratedResult.normalParameterTypes[i]
297+
: undecoratedResult.optionalParameterTypes[i - numRequiredParameters];
298+
newPositionalParameters.add(positionalParameters[i]
299+
._substitute(substitution, undecoratedParameterType));
300+
}
301+
return DecoratedType(undecoratedResult, node,
302+
returnType:
303+
returnType._substitute(substitution, undecoratedResult.returnType),
304+
positionalParameters: newPositionalParameters);
305+
}
269306
}
270307

271308
/// A [DecoratedType] based on a type annotation appearing explicitly in the

pkg/nnbd_migration/lib/src/edge_builder.dart

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,8 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType> {
472472
// TODO(brianwilkerson)
473473
_unimplemented(node, 'Instance creation expression with type arguments');
474474
}
475-
_handleInvocationArguments(node.argumentList, calleeType);
475+
_handleInvocationArguments(
476+
node.argumentList, node.constructorName.type.typeArguments, calleeType);
476477
return calleeType.returnType;
477478
}
478479

@@ -563,8 +564,8 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType> {
563564
}
564565
var calleeType = getOrComputeElementType(callee, targetType: targetType);
565566
// TODO(paulberry): substitute if necessary
566-
_handleInvocationArguments(node.argumentList, calleeType);
567-
var expressionType = calleeType.returnType;
567+
var expressionType = _handleInvocationArguments(
568+
node.argumentList, node.typeArguments, calleeType);
568569
if (isConditional) {
569570
expressionType = expressionType.withNode(
570571
NullabilityNode.forLUB(targetType.node, expressionType.node));
@@ -978,8 +979,21 @@ $stackTrace''');
978979

979980
/// Creates the necessary constraint(s) for an [argumentList] when invoking an
980981
/// executable element whose type is [calleeType].
981-
void _handleInvocationArguments(
982-
ArgumentList argumentList, DecoratedType calleeType) {
982+
///
983+
/// Returns the decorated return type of the invocation, after any necessary
984+
/// substitutions.
985+
DecoratedType _handleInvocationArguments(ArgumentList argumentList,
986+
TypeArgumentList typeArguments, DecoratedType calleeType) {
987+
var typeFormals = calleeType.typeFormals;
988+
if (typeFormals.isNotEmpty) {
989+
if (typeArguments != null) {
990+
calleeType = calleeType.instantiate(typeArguments.arguments
991+
.map((t) => _variables.decoratedTypeAnnotation(_source, t))
992+
.toList());
993+
} else {
994+
_unimplemented(argumentList, 'Inferred type parameters in invocation');
995+
}
996+
}
983997
var arguments = argumentList.arguments;
984998
int i = 0;
985999
var suppliedNamedParameters = Set<String>();
@@ -1007,6 +1021,7 @@ $stackTrace''');
10071021
entry.value.node.recordNamedParameterNotSupplied(_guards, _graph,
10081022
NamedParameterNotSuppliedOrigin(_source, argumentList.offset));
10091023
}
1024+
return calleeType.returnType;
10101025
}
10111026

10121027
DecoratedType _handlePropertyAccess(

pkg/nnbd_migration/test/api_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,29 @@ void test(C<int> c) {
492492
await _checkSingleFileChanges(content, expected);
493493
}
494494

495+
test_data_flow_generic_contravariant_inward_function() async {
496+
var content = '''
497+
T f<T>(T t) => t;
498+
int g(int x) => f<int>(x);
499+
void h() {
500+
g(null);
501+
}
502+
''';
503+
504+
// As with the generic class case (see
505+
// [test_data_flow_generic_contravariant_inward_function]), we favor adding
506+
// nullability at the call site, so that other uses of `f` don't necessarily
507+
// see a nullable return value.
508+
var expected = '''
509+
T f<T>(T t) => t;
510+
int? g(int? x) => f<int?>(x);
511+
void h() {
512+
g(null);
513+
}
514+
''';
515+
await _checkSingleFileChanges(content, expected);
516+
}
517+
495518
test_data_flow_generic_contravariant_inward_using_core_class() async {
496519
var content = '''
497520
void f(List<int> x, int i) {

pkg/nnbd_migration/test/edge_builder_test.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,25 @@ void f(List<int> x, int i) {
12641264
assertEdge(nullable_i, nullable_list_t_or_nullable_t, hard: true));
12651265
}
12661266

1267+
test_methodInvocation_parameter_contravariant_function() async {
1268+
await analyze('''
1269+
void f<T>(T t) {}
1270+
void g(int i) {
1271+
f<int>(i/*check*/);
1272+
}
1273+
''');
1274+
var nullable_i = decoratedTypeAnnotation('int i').node;
1275+
var nullable_f_t = decoratedTypeAnnotation('int>').node;
1276+
var nullable_t = decoratedTypeAnnotation('T t').node;
1277+
var check_i = checkExpression('i/*check*/');
1278+
var nullable_f_t_or_nullable_t =
1279+
check_i.edges.single.destinationNode as NullabilityNodeForSubstitution;
1280+
expect(nullable_f_t_or_nullable_t.innerNode, same(nullable_f_t));
1281+
expect(nullable_f_t_or_nullable_t.outerNode, same(nullable_t));
1282+
assertNullCheck(check_i,
1283+
assertEdge(nullable_i, nullable_f_t_or_nullable_t, hard: true));
1284+
}
1285+
12671286
test_methodInvocation_parameter_generic() async {
12681287
await analyze('''
12691288
class C<T> {}
@@ -1327,6 +1346,23 @@ bool f(C c) => c.m();
13271346
hard: false);
13281347
}
13291348

1349+
test_methodInvocation_return_type_generic_function() async {
1350+
await analyze('''
1351+
T f<T>(T t) => t;
1352+
int g() => (f<int>(1));
1353+
''');
1354+
var check_i = checkExpression('(f<int>(1))');
1355+
var nullable_f_t = decoratedTypeAnnotation('int>').node;
1356+
var nullable_f_t_or_nullable_t =
1357+
check_i.edges.single.primarySource as NullabilityNodeForSubstitution;
1358+
var nullable_t = decoratedTypeAnnotation('T f').node;
1359+
expect(nullable_f_t_or_nullable_t.innerNode, same(nullable_f_t));
1360+
expect(nullable_f_t_or_nullable_t.outerNode, same(nullable_t));
1361+
var nullable_return = decoratedTypeAnnotation('int g').node;
1362+
assertNullCheck(check_i,
1363+
assertEdge(nullable_f_t_or_nullable_t, nullable_return, hard: false));
1364+
}
1365+
13301366
test_methodInvocation_return_type_null_aware() async {
13311367
await analyze('''
13321368
class C {

0 commit comments

Comments
 (0)