Record InvocationExpression.typeArgumentTypes and use it directly, without need to restore type arguments.

Also remove FunctionTypeImpl.recoverTypeArguments because Analyzer
does not need it anymore.

This CL used to replace GenericFunctionTypeElement based FunctionType(s)
with synthetic FunctionType(s). But this is not enough step, because
ideally we want to do this for all FunctionType(s) in invocations. And
this is too big change that affects many parts of Analyzer to do it now.

R=brianwilkerson@google.com, paulberry@google.com

Change-Id: I180eca03ed249abfafe8d6faa94050a30969f125
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/98984
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/dart/ast/ast.dart b/pkg/analyzer/lib/dart/ast/ast.dart
index f233ef1..8747ace 100644
--- a/pkg/analyzer/lib/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/dart/ast/ast.dart
@@ -3471,6 +3471,15 @@
   /// Return the type arguments to be applied to the method being invoked, or
   /// `null` if no type arguments were provided.
   TypeArgumentList get typeArguments;
+
+  /// Return the actual type arguments of the invocation, either explicitly
+  /// specified in [typeArguments], or inferred.
+  ///
+  /// If the AST has been resolved, never returns `null`, returns an empty list
+  /// if the [function] does not have type parameters.
+  ///
+  /// Return `null` if the AST structure has not been resolved.
+  List<DartType> get typeArgumentTypes;
 }
 
 /// An is expression.
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart
index bbc3d44..1eee9ae 100644
--- a/pkg/analyzer/lib/src/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -6324,6 +6324,9 @@
   TypeArgumentListImpl _typeArguments;
 
   @override
+  List<DartType> typeArgumentTypes;
+
+  @override
   DartType staticInvokeType;
 
   /// Initialize a newly created invocation.
diff --git a/pkg/analyzer/lib/src/dart/element/type.dart b/pkg/analyzer/lib/src/dart/element/type.dart
index e1b62e2..b55b9e6 100644
--- a/pkg/analyzer/lib/src/dart/element/type.dart
+++ b/pkg/analyzer/lib/src/dart/element/type.dart
@@ -947,43 +947,6 @@
   }
 
   /**
-   * Given a generic function type [g] and an instantiated function type [f],
-   * find a list of type arguments TArgs such that `g<TArgs> == f`,
-   * and return TArgs.
-   *
-   * This function must be called with type [f] that was instantiated from [g].
-   *
-   * If [g] is not generic, returns an empty list.
-   */
-  static Iterable<DartType> recoverTypeArguments(
-      FunctionType g, FunctionType f) {
-    // TODO(jmesserly): perhaps a better design here would be: instead of
-    // recording staticInvokeType on InvocationExpression, we could record the
-    // instantiated type arguments, that way we wouldn't need to recover them.
-    //
-    // For now though, this is a pretty quick operation.
-    if (g.typeFormals.isEmpty) {
-      assert(g == f);
-      return const <DartType>[];
-    }
-    assert(f.typeFormals.isEmpty);
-    assert(g.typeFormals.length <= f.typeArguments.length);
-
-    // Instantiation in Analyzer works like this:
-    // Given:
-    //     {U/T} <S> T -> S
-    // Where {U/T} represents the typeArguments (U) and typeParameters (T) list,
-    // and <S> represents the typeFormals.
-    //
-    // Now instantiate([V]), and the result should be:
-    //     {U/T, V/S} T -> S.
-    //
-    // Therefore, we can recover the typeArguments from our instantiated
-    // function.
-    return f.typeArguments.skip(f.typeArguments.length - g.typeFormals.length);
-  }
-
-  /**
    * Compares two function types [t] and [s] to see if their corresponding
    * parameter types match [parameterRelation], return types match
    * [returnRelation], and type parameter bounds match [boundsRelation].
diff --git a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
index 844dfd9..23844eb 100644
--- a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
@@ -38,6 +38,9 @@
   /// The object keeping track of which elements have had their types promoted.
   final TypePromotionManager _promoteManager;
 
+  /// The invocation being resolved.
+  MethodInvocationImpl _invocation;
+
   /// The [Name] object of the invocation being resolved by [resolve].
   Name _currentName;
 
@@ -52,6 +55,8 @@
   Scope get nameScope => _resolver.nameScope;
 
   void resolve(MethodInvocation node) {
+    _invocation = node;
+
     SimpleIdentifier nameNode = node.methodName;
     String name = nameNode.name;
     _currentName = Name(_definingLibraryUri, name);
@@ -167,10 +172,17 @@
 
     if (typeFormals.isNotEmpty) {
       if (arguments == null) {
-        return _resolver.typeSystem.instantiateToBounds(invokeType);
+        var typeArguments =
+            _resolver.typeSystem.instantiateTypeFormalsToBounds(typeFormals);
+        _invocation.typeArgumentTypes = typeArguments;
+        return invokeType.instantiate(typeArguments);
       } else {
-        return invokeType.instantiate(arguments.map((n) => n.type).toList());
+        var typeArguments = arguments.map((n) => n.type).toList();
+        _invocation.typeArgumentTypes = typeArguments;
+        return invokeType.instantiate(typeArguments);
       }
+    } else {
+      _invocation.typeArgumentTypes = const <DartType>[];
     }
 
     return invokeType;
@@ -442,6 +454,7 @@
         nameNode.staticElement = loadLibraryFunction;
         node.staticInvokeType = loadLibraryFunction?.type;
         node.staticType = loadLibraryFunction?.returnType;
+        _setExplicitTypeArgumentTypes();
         return;
       }
     }
@@ -547,6 +560,20 @@
     }
     node.staticInvokeType = _dynamicType;
     node.staticType = _dynamicType;
+    _setExplicitTypeArgumentTypes();
+  }
+
+  /// Set explicitly specified type argument types, or empty if not specified.
+  /// Inference is done in type analyzer, so inferred type arguments might be
+  /// set later.
+  void _setExplicitTypeArgumentTypes() {
+    var typeArgumentList = _invocation.typeArguments;
+    if (typeArgumentList != null) {
+      var arguments = typeArgumentList.arguments;
+      _invocation.typeArgumentTypes = arguments.map((n) => n.type).toList();
+    } else {
+      _invocation.typeArgumentTypes = [];
+    }
   }
 
   void _setResolution(MethodInvocation node, DartType type) {
@@ -573,6 +600,7 @@
         node.typeArguments,
         node.methodName,
       );
+      instantiatedType = _toSyntheticFunctionType(instantiatedType);
       node.staticInvokeType = instantiatedType;
       node.staticType = instantiatedType.returnType;
       // TODO(scheglov) too much magic
@@ -619,4 +647,30 @@
     }
     return false;
   }
+
+  /// As an experiment for using synthetic [FunctionType]s, we replace some
+  /// function types with the equivalent synthetic function type instance.
+  /// The assumption that we try to prove is that only the set of parameters,
+  /// with their names, types and kinds is important, but the element that
+  /// encloses them is not (`null` for synthetic function types).
+  static FunctionType _toSyntheticFunctionType(FunctionType type) {
+//    if (type.element is GenericFunctionTypeElement) {
+//      var synthetic = FunctionTypeImpl.synthetic(
+//        type.returnType,
+//        type.typeFormals.map((e) {
+//          return TypeParameterElementImpl.synthetic(e.name)..bound = e.bound;
+//        }).toList(),
+//        type.parameters.map((p) {
+//          return ParameterElementImpl.synthetic(
+//            p.name,
+//            p.type,
+//            // ignore: deprecated_member_use_from_same_package
+//            p.parameterKind,
+//          );
+//        }).toList(),
+//      );
+//      return synthetic;
+//    }
+    return type;
+  }
 }
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 8eb5579..97ecdd6 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -3479,8 +3479,7 @@
     if (invokeType is FunctionType &&
         declaredType is FunctionType &&
         declaredType.typeFormals.isNotEmpty) {
-      Iterable<DartType> typeArgs =
-          FunctionTypeImpl.recoverTypeArguments(declaredType, invokeType);
+      List<DartType> typeArgs = node.typeArgumentTypes;
       if (typeArgs.any((t) => t.isDynamic)) {
         // Issue an error depending on what we're trying to call.
         Expression function = node.function;
diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
index eb5a7a8..b110a681 100644
--- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
+++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
@@ -1514,6 +1514,7 @@
     if (context is FunctionType &&
         type is FunctionType &&
         ts is Dart2TypeSystem) {
+      // TODO(scheglov) Also store type arguments for identifiers.
       return ts.inferFunctionTypeInstantiation(context, type,
           errorReporter: _resolver.errorReporter, errorNode: node);
     }
@@ -1577,12 +1578,31 @@
           argTypes.add(argumentList.arguments[i].staticType);
         }
       }
-      return ts.inferGenericFunctionOrType(
+      var typeArgs = ts.inferGenericFunctionOrType2<FunctionType>(
           fnType, params, argTypes, InferenceContext.getContext(node),
           isConst: isConst,
           errorReporter: _resolver.errorReporter,
           errorNode: errorNode);
+      if (node is InvocationExpressionImpl) {
+        node.typeArgumentTypes = typeArgs;
+      }
+      if (typeArgs != null) {
+        return fnType.instantiate(typeArgs);
+      }
+      return fnType;
     }
+
+    // There is currently no other place where we set type arguments
+    // for FunctionExpressionInvocation(s), so set it here, if not inferred.
+    if (node is FunctionExpressionInvocationImpl) {
+      if (typeArguments != null) {
+        var typeArgs = typeArguments.arguments.map((n) => n.type).toList();
+        node.typeArgumentTypes = typeArgs;
+      } else {
+        node.typeArgumentTypes = const <DartType>[];
+      }
+    }
+
     return null;
   }
 
diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart
index f1ed12b..64b747e 100644
--- a/pkg/analyzer/lib/src/generated/type_system.dart
+++ b/pkg/analyzer/lib/src/generated/type_system.dart
@@ -194,8 +194,12 @@
     inferrer.constrainGenericFunctionInContext(fnType, contextType);
 
     // Infer and instantiate the resulting type.
-    return inferrer.infer(fnType, fnType.typeFormals,
-        errorReporter: errorReporter, errorNode: errorNode);
+    var inferredTypes = inferrer.infer(
+      fnType.typeFormals,
+      errorReporter: errorReporter,
+      errorNode: errorNode,
+    );
+    return fnType.instantiate(inferredTypes);
   }
 
   /// Infers a generic type, function, method, or list/map literal
@@ -228,10 +232,53 @@
       AstNode errorNode,
       bool downwards: false,
       bool isConst: false}) {
+    var inferredTypes = inferGenericFunctionOrType2(
+      genericType,
+      parameters,
+      argumentTypes,
+      returnContextType,
+      errorReporter: errorReporter,
+      errorNode: errorNode,
+      downwards: downwards,
+      isConst: isConst,
+    );
+    return genericType.instantiate(inferredTypes);
+  }
+
+  /// Infers type arguments for a generic type, function, method, or
+  /// list/map literal, using the downward context type as well as the
+  /// argument types if available.
+  ///
+  /// For example, given a function type with generic type parameters, this
+  /// infers the type parameters from the actual argument types, and returns the
+  /// instantiated function type.
+  ///
+  /// Concretely, given a function type with parameter types P0, P1, ... Pn,
+  /// result type R, and generic type parameters T0, T1, ... Tm, use the
+  /// argument types A0, A1, ... An to solve for the type parameters.
+  ///
+  /// For each parameter Pi, we want to ensure that Ai <: Pi. We can do this by
+  /// running the subtype algorithm, and when we reach a type parameter Tj,
+  /// recording the lower or upper bound it must satisfy. At the end, all
+  /// constraints can be combined to determine the type.
+  ///
+  /// All constraints on each type parameter Tj are tracked, as well as where
+  /// they originated, so we can issue an error message tracing back to the
+  /// argument values, type parameter "extends" clause, or the return type
+  /// context.
+  List<DartType> inferGenericFunctionOrType2<T extends ParameterizedType>(
+      T genericType,
+      List<ParameterElement> parameters,
+      List<DartType> argumentTypes,
+      DartType returnContextType,
+      {ErrorReporter errorReporter,
+      AstNode errorNode,
+      bool downwards: false,
+      bool isConst: false}) {
     // TODO(jmesserly): expose typeFormals on ParameterizedType.
     List<TypeParameterElement> typeFormals = typeFormalsAsElements(genericType);
     if (typeFormals.isEmpty) {
-      return genericType;
+      return null;
     }
 
     // Create a TypeSystem that will allow certain type parameters to be
@@ -258,7 +305,7 @@
           genericType: genericType);
     }
 
-    return inferrer.infer(genericType, typeFormals,
+    return inferrer.infer(typeFormals,
         errorReporter: errorReporter,
         errorNode: errorNode,
         downwardsInferPhase: downwards);
@@ -298,7 +345,7 @@
       Map<TypeParameterType, DartType> knownTypes}) {
     int count = typeFormals.length;
     if (count == 0) {
-      return null;
+      return const <DartType>[];
     }
 
     Set<TypeParameterType> all = new Set<TypeParameterType>();
@@ -1158,16 +1205,16 @@
     tryMatchSubtypeOf(declaredType, contextType, origin, covariant: true);
   }
 
-  /// Given the constraints that were given by calling [isSubtypeOf], find the
-  /// instantiation of the generic function that satisfies these constraints.
+  /// Given the constraints that were given by calling [constrainArgument] and
+  /// [constrainReturnType], find the type arguments for the [typeFormals] that
+  /// satisfies these constraints.
   ///
   /// If [downwardsInferPhase] is set, we are in the first pass of inference,
   /// pushing context types down. At that point we are allowed to push down
   /// `?` to precisely represent an unknown type. If [downwardsInferPhase] is
   /// false, we are on our final inference pass, have all available information
   /// including argument types, and must not conclude `?` for any type formal.
-  T infer<T extends ParameterizedType>(
-      T genericType, List<TypeParameterElement> typeFormals,
+  List<DartType> infer(List<TypeParameterElement> typeFormals,
       {bool considerExtendsClause: true,
       ErrorReporter errorReporter,
       AstNode errorNode,
@@ -1201,7 +1248,7 @@
     // If the downwards infer phase has failed, we'll catch this in the upwards
     // phase later on.
     if (downwardsInferPhase) {
-      return genericType.instantiate(inferredTypes) as T;
+      return inferredTypes;
     }
 
     // Check the inferred types against all of the constraints.
@@ -1260,8 +1307,8 @@
 
     // Use instantiate to bounds to finish things off.
     var hasError = new List<bool>.filled(fnTypeParams.length, false);
-    var result = _typeSystem.instantiateToBounds(genericType,
-        hasError: hasError, knownTypes: knownTypes) as T;
+    var result = _typeSystem.instantiateTypeFormalsToBounds(typeFormals,
+        hasError: hasError, knownTypes: knownTypes);
 
     // Report any errors from instantiateToBounds.
     for (int i = 0; i < hasError.length; i++) {
@@ -1279,6 +1326,7 @@
         ]);
       }
     }
+
     return result;
   }
 
@@ -1991,8 +2039,13 @@
       inferrer.constrainReturnType(srcs[i], dests[i]);
       inferrer.constrainReturnType(dests[i], srcs[i]);
     }
-    var result = inferrer.infer(mixinElement.type, typeParameters,
-        considerExtendsClause: false);
+
+    var inferredTypes = inferrer.infer(
+      typeParameters,
+      considerExtendsClause: false,
+    );
+    var result = mixinElement.type.instantiate(inferredTypes);
+
     for (int i = 0; i < srcs.length; i++) {
       if (!srcs[i]
           .substitute2(result.typeArguments, mixinElement.type.typeArguments)
diff --git a/pkg/analyzer/lib/src/test_utilities/find_node.dart b/pkg/analyzer/lib/src/test_utilities/find_node.dart
index ced02d0..ce217cf 100644
--- a/pkg/analyzer/lib/src/test_utilities/find_node.dart
+++ b/pkg/analyzer/lib/src/test_utilities/find_node.dart
@@ -107,6 +107,10 @@
     return _node(search, (n) => n is FunctionExpression);
   }
 
+  FunctionExpressionInvocation functionExpressionInvocation(String search) {
+    return _node(search, (n) => n is FunctionExpressionInvocation);
+  }
+
   FunctionTypeAlias functionTypeAlias(String search) {
     return _node(search, (n) => n is FunctionTypeAlias);
   }
diff --git a/pkg/analyzer/test/src/dart/resolution/function_expression_invocation_test.dart b/pkg/analyzer/test/src/dart/resolution/function_expression_invocation_test.dart
new file mode 100644
index 0000000..7bfebe1
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/resolution/function_expression_invocation_test.dart
@@ -0,0 +1,57 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'driver_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(FunctionExpressionInvocationTest);
+  });
+}
+
+@reflectiveTest
+class FunctionExpressionInvocationTest extends DriverResolutionTest {
+  test_dynamic_withoutTypeArguments() async {
+    await assertNoErrorsInCode(r'''
+main() {
+  (main as dynamic)(0);
+}
+''');
+
+    var invocation = findNode.functionExpressionInvocation('(0)');
+    assertTypeDynamic(invocation);
+    assertInvokeTypeDynamic(invocation);
+    assertTypeArgumentTypes(invocation, []);
+  }
+
+  test_dynamic_withTypeArguments() async {
+    await assertNoErrorsInCode(r'''
+main() {
+  (main as dynamic)<bool, int>(0);
+}
+''');
+
+    var invocation = findNode.functionExpressionInvocation('(0)');
+    assertTypeDynamic(invocation);
+    assertInvokeTypeDynamic(invocation);
+    assertTypeArgumentTypes(invocation, ['bool', 'int']);
+  }
+
+  test_generic() async {
+    await assertNoErrorsInCode(r'''
+main() {
+  (f)(0);
+}
+
+bool f<T>(T a) => true;
+''');
+
+    var invocation = findNode.functionExpressionInvocation('(0)');
+    assertType(invocation, 'bool');
+    assertInvokeType(invocation, '(int) → bool');
+    assertTypeArgumentTypes(invocation, ['int']);
+  }
+}
diff --git a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
index 307d438..0f402b5 100644
--- a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
@@ -629,7 +629,10 @@
       StaticTypeWarningCode.UNDEFINED_METHOD,
     ]);
 
-    _assertUnresolvedMethodInvocation('foo<int>();');
+    _assertUnresolvedMethodInvocation(
+      'foo<int>();',
+      expectedTypeArguments: ['int'],
+    );
     assertTypeName(findNode.typeName('int>'), intElement, 'int');
   }
 
@@ -1018,6 +1021,7 @@
       findNode.methodInvocation('foo<int>()'),
       findElement.topFunction('foo'),
       '() → Map<num, dynamic>',
+      expectedTypeArguments: ['num', 'dynamic'],
     );
     assertTypeName(findNode.typeName('int>'), intElement, 'int');
   }
@@ -1132,6 +1136,7 @@
       invocation,
       import.topFunction('foo'),
       '(int, int) → int',
+      expectedTypeArguments: ['int'],
     );
     assertImportPrefix(invocation.target, import.prefix);
   }
@@ -1159,6 +1164,7 @@
       import.topGetter('foo'),
       '(int, int) → int',
       expectedMethodNameType: '() → <T>(T, T) → T',
+      expectedTypeArguments: ['int'],
     );
     assertImportPrefix(invocation.target, import.prefix);
   }
@@ -1231,6 +1237,7 @@
       '(int) → void',
       expectedMethodNameType: '(int) → void',
     );
+    assertTypeArgumentTypes(invocation, []);
   }
 
   test_hasReceiver_instance_method_generic() async {
@@ -1254,7 +1261,9 @@
       findElement.method('foo'),
       '(int) → int',
       expectedMethodNameType: '(int) → int',
+      expectedTypeArguments: ['int'],
     );
+    assertTypeArgumentTypes(invocation, ['int']);
   }
 
   test_hasReceiver_instance_method_issue30552() async {
@@ -1721,9 +1730,77 @@
     );
   }
 
+  test_typeArgumentTypes_generic_inferred() async {
+    await assertNoErrorsInCode(r'''
+U foo<T, U>(T a) => null;
+
+main() {
+  bool v = foo(0);
+}
+''');
+
+    var invocation = findNode.methodInvocation('foo(0)');
+    assertTypeArgumentTypes(invocation, ['int', 'bool']);
+  }
+
+  test_typeArgumentTypes_generic_instantiateToBounds() async {
+    await assertNoErrorsInCode(r'''
+void foo<T extends num>() {}
+
+main() {
+  foo();
+}
+''');
+
+    var invocation = findNode.methodInvocation('foo();');
+    assertTypeArgumentTypes(invocation, ['num']);
+  }
+
+  test_typeArgumentTypes_generic_typeArguments_notBounds() async {
+    addTestFile(r'''
+void foo<T extends num>() {}
+
+main() {
+  foo<bool>();
+}
+''');
+    await resolveTestFile();
+
+    var invocation = findNode.methodInvocation('foo<bool>();');
+    assertTypeArgumentTypes(invocation, ['bool']);
+  }
+
+  test_typeArgumentTypes_generic_typeArguments_wrongNumber() async {
+    addTestFile(r'''
+void foo<T>() {}
+
+main() {
+  foo<int, double>();
+}
+''');
+    await resolveTestFile();
+
+    var invocation = findNode.methodInvocation('foo<int, double>();');
+    assertTypeArgumentTypes(invocation, ['dynamic']);
+  }
+
+  test_typeArgumentTypes_notGeneric() async {
+    await assertNoErrorsInCode(r'''
+void foo(int a) {}
+
+main() {
+  foo(0);
+}
+''');
+
+    var invocation = findNode.methodInvocation('foo(0)');
+    assertTypeArgumentTypes(invocation, []);
+  }
+
   void _assertInvalidInvocation(String search, Element expectedElement,
       {String expectedMethodNameType,
       String expectedNameType,
+      List<String> expectedTypeArguments: const <String>[],
       bool dynamicNameType: false}) {
     var invocation = findNode.methodInvocation(search);
     if (dynamicNameType) {
@@ -1737,12 +1814,21 @@
       expectedMethodNameType: expectedMethodNameType,
       expectedNameType: expectedNameType,
       expectedType: 'dynamic',
+      expectedTypeArguments: expectedTypeArguments,
     );
+    assertTypeArgumentTypes(invocation, expectedTypeArguments);
   }
 
-  void _assertUnresolvedMethodInvocation(String search) {
+  void _assertUnresolvedMethodInvocation(
+    String search, {
+    List<String> expectedTypeArguments: const <String>[],
+  }) {
     // TODO(scheglov) clean up
-    _assertInvalidInvocation(search, null);
+    _assertInvalidInvocation(
+      search,
+      null,
+      expectedTypeArguments: expectedTypeArguments,
+    );
 //    var invocation = findNode.methodInvocation(search);
 //    assertTypeDynamic(invocation.methodName);
 //    // TODO(scheglov) I think `invokeType` should be `null`.
diff --git a/pkg/analyzer/test/src/dart/resolution/resolution.dart b/pkg/analyzer/test/src/dart/resolution/resolution.dart
index bad87c1..fadcc32 100644
--- a/pkg/analyzer/test/src/dart/resolution/resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/resolution.dart
@@ -279,11 +279,15 @@
     expect(actual.baseElement, same(expectedBase));
   }
 
-  void assertMethodInvocation(MethodInvocation invocation,
-      Element expectedElement, String expectedInvokeType,
-      {String expectedMethodNameType,
-      String expectedNameType,
-      String expectedType}) {
+  void assertMethodInvocation(
+    MethodInvocation invocation,
+    Element expectedElement,
+    String expectedInvokeType, {
+    String expectedMethodNameType,
+    String expectedNameType,
+    String expectedType,
+    List<String> expectedTypeArguments: const <String>[],
+  }) {
     MethodInvocationImpl invocationImpl = invocation;
 
     // TODO(scheglov) Check for Member.
@@ -305,6 +309,8 @@
 //    }
 //    assertType(invocation.methodName, expectedNameType);
 
+    assertTypeArgumentTypes(invocation, expectedTypeArguments);
+
     assertInvokeType(invocation, expectedInvokeType);
 
     expectedType ??= _extractReturnType(expectedInvokeType);
@@ -371,6 +377,14 @@
         expected);
   }
 
+  void assertTypeArgumentTypes(
+    InvocationExpression node,
+    List<String> expected,
+  ) {
+    var actual = node.typeArgumentTypes.map((t) => '$t').toList();
+    expect(actual, expected);
+  }
+
   void assertTypeDynamic(Expression expression) {
     DartType actual = expression.staticType;
     expect(actual, isDynamicType);
diff --git a/pkg/analyzer/test/src/dart/resolution/test_all.dart b/pkg/analyzer/test/src/dart/resolution/test_all.dart
index 8c03439..ebd712a 100644
--- a/pkg/analyzer/test/src/dart/resolution/test_all.dart
+++ b/pkg/analyzer/test/src/dart/resolution/test_all.dart
@@ -15,6 +15,8 @@
 import 'flow_analysis_test.dart' as flow_analysis;
 import 'for_element_test.dart' as for_element;
 import 'for_in_test.dart' as for_in;
+import 'function_expression_invocation_test.dart'
+    as function_expression_invocation;
 import 'generic_type_alias_test.dart' as generic_type_alias;
 import 'import_prefix_test.dart' as import_prefix;
 import 'instance_creation_test.dart' as instance_creation;
@@ -43,6 +45,7 @@
     flow_analysis.main();
     for_element.main();
     for_in.main();
+    function_expression_invocation.main();
     generic_type_alias.main();
     import_prefix.main();
     instance_creation.main();
diff --git a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
index 39f7c88f..2c72273 100644
--- a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
@@ -13,6 +13,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/error/error.dart';
+import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/ast/token.dart' show StringToken;
 import 'package:analyzer/src/dart/ast/utilities.dart' show UIAsCodeVisitorMixin;
 import 'package:analyzer/src/dart/element/element.dart';
@@ -3961,23 +3962,30 @@
       return null;
     }
     return _emitFunctionTypeArguments(
-        function.staticType, node.staticInvokeType, node.typeArguments);
+        node, function.staticType, node.staticInvokeType, node.typeArguments);
   }
 
   /// If `g` is a generic function type, and `f` is an instantiation of it,
   /// then this will return the type arguments to apply, otherwise null.
-  List<JS.Expression> _emitFunctionTypeArguments(DartType g, DartType f,
+  List<JS.Expression> _emitFunctionTypeArguments(
+      AstNode node, DartType g, DartType f,
       [TypeArgumentList typeArgs]) {
+    if (node is InvocationExpression) {
+      if (g is! FunctionType && typeArgs == null) {
+        return null;
+      }
+      var typeArguments = node.typeArgumentTypes;
+      return typeArguments.map(_emitType).toList(growable: false);
+    }
+
     if (g is FunctionType &&
         g.typeFormals.isNotEmpty &&
         f is FunctionType &&
         f.typeFormals.isEmpty) {
-      return _recoverTypeArguments(g, f).map(_emitType).toList(growable: false);
-    } else if (typeArgs != null) {
-      // Dynamic calls may have type arguments, even though the function types
-      // are not known.
-      return _visitExpressionList(typeArgs.arguments);
+      var typeArguments = _recoverTypeArguments(g, f);
+      return typeArguments.map(_emitType).toList(growable: false);
     }
+
     return null;
   }
 
@@ -5177,9 +5185,12 @@
       return ast.propertyAccess(newTarget, node.propertyName);
     } else {
       var invoke = node as MethodInvocation;
-      return ast.methodInvoke(newTarget, invoke.methodName,
+      var newNode = ast.methodInvoke(newTarget, invoke.methodName,
           invoke.typeArguments, invoke.argumentList.arguments)
         ..staticInvokeType = invoke.staticInvokeType;
+      (newNode as MethodInvocationImpl).typeArgumentTypes =
+          invoke.typeArgumentTypes;
+      return newNode;
     }
   }
 
@@ -5193,7 +5204,7 @@
 
     // TODO(jmesserly): handle explicitly passed type args.
     if (type == null) return null;
-    return _emitFunctionTypeArguments(type, instantiated);
+    return _emitFunctionTypeArguments(null, type, instantiated);
   }
 
   /// Shared code for [PrefixedIdentifier] and [PropertyAccess].
diff --git a/pkg/dev_compiler/lib/src/analyzer/reify_coercions.dart b/pkg/dev_compiler/lib/src/analyzer/reify_coercions.dart
index a071fcb..9d2ad1d 100644
--- a/pkg/dev_compiler/lib/src/analyzer/reify_coercions.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/reify_coercions.dart
@@ -6,7 +6,11 @@
 import 'package:analyzer/dart/ast/standard_ast_factory.dart';
 import 'package:analyzer/dart/ast/visitor.dart' show GeneralizingAstVisitor;
 import 'package:analyzer/dart/element/type.dart' show DartType;
-import 'package:analyzer/src/dart/ast/ast.dart' show FunctionBodyImpl;
+import 'package:analyzer/src/dart/ast/ast.dart'
+    show
+        FunctionBodyImpl,
+        FunctionExpressionInvocationImpl,
+        MethodInvocationImpl;
 import 'package:analyzer/src/dart/ast/utilities.dart'
     show AstCloner, NodeReplacer;
 import 'package:analyzer/src/generated/parser.dart' show ResolutionCopier;
@@ -185,6 +189,22 @@
     return clone;
   }
 
+  @override
+  FunctionExpressionInvocation visitFunctionExpressionInvocation(
+      FunctionExpressionInvocation node) {
+    var clone = super.visitFunctionExpressionInvocation(node);
+    (clone as FunctionExpressionInvocationImpl).typeArgumentTypes =
+        node.typeArgumentTypes;
+    return clone;
+  }
+
+  @override
+  MethodInvocation visitMethodInvocation(MethodInvocation node) {
+    var clone = super.visitMethodInvocation(node);
+    (clone as MethodInvocationImpl).typeArgumentTypes = node.typeArgumentTypes;
+    return clone;
+  }
+
   // TODO(jmesserly): workaround for
   // https://github.com/dart-lang/sdk/issues/26368
   @override