Migration: Add support for generic function types.

Fixes #37212.

Change-Id: I3dfe0afa32564b9e4762f3410f5c44ea42d6d004
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105467
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/nnbd_migration/lib/src/decorated_type.dart b/pkg/nnbd_migration/lib/src/decorated_type.dart
index 8e42f80..a740c1c 100644
--- a/pkg/nnbd_migration/lib/src/decorated_type.dart
+++ b/pkg/nnbd_migration/lib/src/decorated_type.dart
@@ -164,8 +164,15 @@
 
   DecoratedTypeAnnotation(
       DartType type, NullabilityNode nullabilityNode, this._offset,
-      {List<DecoratedType> typeArguments = const []})
-      : super(type, nullabilityNode, typeArguments: typeArguments);
+      {List<DecoratedType> typeArguments = const [],
+      DecoratedType returnType,
+      List<DecoratedType> positionalParameters = const [],
+      Map<String, DecoratedType> namedParameters = const {}})
+      : super(type, nullabilityNode,
+            typeArguments: typeArguments,
+            returnType: returnType,
+            positionalParameters: positionalParameters,
+            namedParameters: namedParameters);
 
   @override
   bool get isEmpty => !node.isNullable;
diff --git a/pkg/nnbd_migration/lib/src/node_builder.dart b/pkg/nnbd_migration/lib/src/node_builder.dart
index 316ab10..e7c5778 100644
--- a/pkg/nnbd_migration/lib/src/node_builder.dart
+++ b/pkg/nnbd_migration/lib/src/node_builder.dart
@@ -130,7 +130,6 @@
   @override
   DecoratedType visitTypeAnnotation(TypeAnnotation node) {
     assert(node != null); // TODO(paulberry)
-    assert(node is NamedType); // TODO(paulberry)
     var type = node.type;
     if (type.isVoid || type.isDynamic) {
       var nullabilityNode = NullabilityNode.forTypeAnnotation(node.end);
@@ -141,9 +140,10 @@
           potentialModification: false);
       return decoratedType;
     }
-    assert(
-        type is InterfaceType || type is TypeParameterType); // TODO(paulberry)
     var typeArguments = const <DecoratedType>[];
+    DecoratedType returnType;
+    var positionalParameters = const <DecoratedType>[];
+    var namedParameters = const <String, DecoratedType>{};
     if (type is InterfaceType && type.typeParameters.isNotEmpty) {
       if (node is TypeName) {
         assert(node.typeArguments != null);
@@ -153,10 +153,30 @@
         assert(false); // TODO(paulberry): is this possible?
       }
     }
+    if (node is GenericFunctionType) {
+      returnType = decorateType(node.returnType, node);
+      if (node.typeParameters != null) {
+        throw UnimplementedError('TODO(paulberry)');
+      }
+      positionalParameters = <DecoratedType>[];
+      namedParameters = <String, DecoratedType>{};
+    }
     var decoratedType = DecoratedTypeAnnotation(
         type, NullabilityNode.forTypeAnnotation(node.end), node.end,
-        typeArguments: typeArguments);
+        typeArguments: typeArguments,
+        returnType: returnType,
+        positionalParameters: positionalParameters,
+        namedParameters: namedParameters);
     _variables.recordDecoratedTypeAnnotation(_source, node, decoratedType);
+    if (node is GenericFunctionType) {
+      var previousFunctionType = _currentFunctionType;
+      try {
+        _currentFunctionType = decoratedType;
+        node.parameters.accept(this);
+      } finally {
+        _currentFunctionType = previousFunctionType;
+      }
+    }
     switch (_classifyComment(node.endToken.next.precedingComments)) {
       case _NullabilityComment.bang:
         _graph.connect(decoratedType.node, NullabilityNode.never, hard: true);
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 8a15fbf..ca633bf 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -920,6 +920,20 @@
     await _checkSingleFileChanges(content, expected);
   }
 
+  test_parameter_genericFunctionType() async {
+    var content = '''
+int f(int x, int Function(int i) g) {
+  return g(x);
+}
+''';
+    var expected = '''
+int f(int x, int Function(int i) g) {
+  return g(x);
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   test_single_file_multiple_changes() async {
     var content = '''
 int f() => null;
diff --git a/pkg/nnbd_migration/test/migration_visitor_test.dart b/pkg/nnbd_migration/test/migration_visitor_test.dart
index 04e624f..27969b5 100644
--- a/pkg/nnbd_migration/test/migration_visitor_test.dart
+++ b/pkg/nnbd_migration/test/migration_visitor_test.dart
@@ -1227,6 +1227,13 @@
     }
   }
 
+  /// Gets the [DecoratedType] associated with the generic function type
+  /// annotation whose text is [text].
+  DecoratedType decoratedGenericFunctionTypeAnnotation(String text) {
+    return _variables.decoratedTypeAnnotation(
+        testSource, findNode.genericFunctionType(text));
+  }
+
   /// Gets the [DecoratedType] associated with the type annotation whose text
   /// is [text].
   DecoratedType decoratedTypeAnnotation(String text) {
@@ -1287,6 +1294,51 @@
         same(decoratedType));
   }
 
+  test_genericFunctionType_namedParameterType() async {
+    await analyze('''
+void f(void Function({int y}) x) {}
+''');
+    var decoratedType =
+        decoratedGenericFunctionTypeAnnotation('void Function({int y})');
+    expect(decoratedFunctionType('f').positionalParameters[0],
+        same(decoratedType));
+    expect(decoratedType.node, TypeMatcher<NullabilityNodeMutable>());
+    var decoratedIntType = decoratedTypeAnnotation('int');
+    expect(decoratedType.namedParameters['y'], same(decoratedIntType));
+    expect(decoratedIntType.node, isNotNull);
+    expect(decoratedIntType.node, isNot(NullabilityNode.never));
+  }
+
+  test_genericFunctionType_returnType() async {
+    await analyze('''
+void f(int Function() x) {}
+''');
+    var decoratedType =
+        decoratedGenericFunctionTypeAnnotation('int Function()');
+    expect(decoratedFunctionType('f').positionalParameters[0],
+        same(decoratedType));
+    expect(decoratedType.node, TypeMatcher<NullabilityNodeMutable>());
+    var decoratedIntType = decoratedTypeAnnotation('int');
+    expect(decoratedType.returnType, same(decoratedIntType));
+    expect(decoratedIntType.node, isNotNull);
+    expect(decoratedIntType.node, isNot(NullabilityNode.never));
+  }
+
+  test_genericFunctionType_unnamedParameterType() async {
+    await analyze('''
+void f(void Function(int) x) {}
+''');
+    var decoratedType =
+        decoratedGenericFunctionTypeAnnotation('void Function(int)');
+    expect(decoratedFunctionType('f').positionalParameters[0],
+        same(decoratedType));
+    expect(decoratedType.node, TypeMatcher<NullabilityNodeMutable>());
+    var decoratedIntType = decoratedTypeAnnotation('int');
+    expect(decoratedType.positionalParameters[0], same(decoratedIntType));
+    expect(decoratedIntType.node, isNotNull);
+    expect(decoratedIntType.node, isNot(NullabilityNode.never));
+  }
+
   test_interfaceType_typeParameter() async {
     await analyze('''
 void f(List<int> x) {}