Migration: fix handling of `.call` on a function-typed expression.
Change-Id: Ibdee57d455215cd974ea34c8aeab578bd801f87b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/170361
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index d10f7f4..35f98a8 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -1236,17 +1236,27 @@
targetType = _thisOrSuper(node);
}
DecoratedType expressionType;
- if (callee == null) {
+ DecoratedType calleeType;
+ if (targetType != null &&
+ targetType.type is FunctionType &&
+ node.methodName.name == 'call') {
+ // If `X` has a function type, then in the expression `X.call()`, the
+ // function being called is `X` itself, so the callee type is simply the
+ // type of `X`.
+ calleeType = targetType;
+ } else if (callee != null) {
+ calleeType = getOrComputeElementType(callee, targetType: targetType);
+ if (callee is PropertyAccessorElement) {
+ calleeType = calleeType.returnType;
+ }
+ }
+ if (calleeType == null) {
// Dynamic dispatch. The return type is `dynamic`.
// TODO(paulberry): would it be better to assume a return type of `Never`
// so that we don't unnecessarily propagate nullabilities everywhere?
_dispatch(node.argumentList);
expressionType = _makeNullableDynamicType(node);
} else {
- var calleeType = getOrComputeElementType(callee, targetType: targetType);
- if (callee is PropertyAccessorElement) {
- calleeType = calleeType.returnType;
- }
expressionType = _handleInvocationArguments(
node,
node.argumentList.arguments,
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index b0ba46d..a444f76 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -5231,6 +5231,24 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_null_aware_call_followed_by_if_null() async {
+ var content = '''
+typedef MapGetter = Map<String, String> Function();
+void f(Map<String, String> m) {}
+void g(MapGetter/*?*/ mapGetter) {
+ f(mapGetter?.call() ?? {});
+}
+''';
+ var expected = '''
+typedef MapGetter = Map<String, String> Function();
+void f(Map<String, String> m) {}
+void g(MapGetter? mapGetter) {
+ f(mapGetter?.call() ?? {});
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_null_aware_getter_invocation() async {
var content = '''
bool f(int i) => i?.isEven;
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index 0497d63..712cd2a 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -4692,6 +4692,29 @@
expect(hasNullCheckHint(findNode.methodInvocation('c.m2')), isTrue);
}
+ Future<void> test_methodInvocation_call_functionTyped() async {
+ await analyze('''
+void f(void Function(int x) callback, int y) => callback.call(y);
+''');
+ assertEdge(decoratedTypeAnnotation('int y').node,
+ decoratedTypeAnnotation('int x').node,
+ hard: true);
+ }
+
+ Future<void> test_methodInvocation_call_interfaceTyped() async {
+ // Make sure that we don't try to treat all methods called `call` as though
+ // the underlying type is a function type.
+ await analyze('''
+abstract class C {
+ void call(int x);
+}
+void f(C c, int y) => c.call(y);
+''');
+ assertEdge(decoratedTypeAnnotation('int y').node,
+ decoratedTypeAnnotation('int x').node,
+ hard: true);
+ }
+
Future<void> test_methodInvocation_dynamic() async {
await analyze('''
class C {