Skip horizontal inference when unncessary due to explicit closure parameter types.

The purpose of horizontal inference is to allow the types of closure
parameters to be inferred based on the static type of other arguments
in the same invocation.  When the closure parameter in question
already has an explicit type, there is no benefit, and there are
potential drawbacks (because horizontal inference could infer too
narrow a type).

This change includes the explicitness/implicitness of closure
parameter types in the dependency analysis for
https://github.com/dart-lang/language/issues/731 (improved inference
for fold etc.) so that we won't do horizontal inference when it's not
needed.

Change-Id: I33781877685867a8fcb40de54fc055f6348c21b2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/240505
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart b/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart
index 918eaec..fe15873 100644
--- a/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart
@@ -18,6 +18,23 @@
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/resolver.dart';
 
+Set<Object> _computeExplicitlyTypedParameterSet(
+    FunctionExpression functionExpression) {
+  List<FormalParameter> parameters =
+      functionExpression.parameters?.parameters ?? const [];
+  Set<Object> result = {};
+  for (var formalParameter in parameters) {
+    int unnamedParameterIndex = 0;
+    var key = formalParameter.isNamed
+        ? formalParameter.identifier?.name ?? ''
+        : unnamedParameterIndex++;
+    if (formalParameter.isExplicitlyTyped) {
+      result.add(key);
+    }
+  }
+  return result;
+}
+
 /// Given an iterable of parameters, computes a map whose keys are either the
 /// parameter name (for named parameters) or the zero-based integer index (for
 /// unnamed parameters), and whose values are the parameters themselves.
@@ -642,9 +659,13 @@
       _DeferredParamInfo paramInfo) {
     var type = paramInfo.parameter?.type;
     if (type is FunctionType) {
+      var parameterMap = _computeParameterMap(type.parameters);
+      var explicitlyTypedParameters =
+          _computeExplicitlyTypedParameterSet(paramInfo.value);
       Set<TypeParameterElement> result = {};
-      for (var parameter in type.parameters) {
-        result.addAll(_typeSystem.getFreeParameters(parameter.type,
+      for (var entry in parameterMap.entries) {
+        if (explicitlyTypedParameters.contains(entry.key)) continue;
+        result.addAll(_typeSystem.getFreeParameters(entry.value.type,
                 candidates: _typeVariables) ??
             const []);
       }
diff --git a/pkg/analyzer/test/src/dart/resolution/type_inference/inference_update_1_test.dart b/pkg/analyzer/test/src/dart/resolution/type_inference/inference_update_1_test.dart
index 8acef31..81d22f9 100644
--- a/pkg/analyzer/test/src/dart/resolution/type_inference/inference_update_1_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/type_inference/inference_update_1_test.dart
@@ -205,6 +205,39 @@
         _isEnabled ? 'int' : 'Object?');
   }
 
+  test_horizontal_inference_unnecessary_due_to_explicit_parameter_type() async {
+    // In this example, there is no need for horizontal type inference because
+    // the type of `x` is explicit.
+    await assertErrorsInCode('''
+test(List<int> list) {
+  var a = list.fold(null, (int? x, y) => (x ?? 0) + y);
+}
+''', [
+      error(HintCode.UNUSED_LOCAL_VARIABLE, 29, 1),
+    ]);
+    assertType(findElement.localVar('a').type, 'int?');
+    assertType(findElement.parameter('x').type, 'int?');
+    assertType(findElement.parameter('y').type, 'int');
+    expect(findNode.binary('+ y').staticElement!.enclosingElement.name, 'num');
+  }
+
+  test_horizontal_inference_unnecessary_due_to_explicit_parameter_type_named() async {
+    // In this example, there is no need for horizontal type inference because
+    // the type of `x` is explicit.
+    await assertErrorsInCode('''
+T f<T>(T a, T Function({required T x, required int y}) b) => throw '';
+test() {
+  var a = f(null, ({int? x, required y}) => (x ?? 0) + y);
+}
+''', [
+      error(HintCode.UNUSED_LOCAL_VARIABLE, 86, 1),
+    ]);
+    assertType(findElement.localVar('a').type, 'int?');
+    assertType(findElement.parameter('x').type, 'int?');
+    assertType(findElement.parameter('y').type, 'int');
+    expect(findNode.binary('+ y').staticElement!.enclosingElement.name, 'num');
+  }
+
   test_horizontal_inference_unnecessary_due_to_no_dependency() async {
     // In this example, there is no dependency between the two parameters of
     // `f`, so there should be no horizontal type inference between inferring
diff --git a/tests/language/inference_update_1/horizontal_inference_disabled_test.dart b/tests/language/inference_update_1/horizontal_inference_disabled_test.dart
index 7aa4b24..c63166b 100644
--- a/tests/language/inference_update_1/horizontal_inference_disabled_test.dart
+++ b/tests/language/inference_update_1/horizontal_inference_disabled_test.dart
@@ -48,4 +48,15 @@
   f(() => 0, null).expectStaticType<Exactly<int?>>();
 }
 
+testUnnecessaryDueToExplicitParameterType(List<int> list) {
+  var a = list.fold(null, (int? x, y) => (x ?? 0) + y);
+  a.expectStaticType<Exactly<int?>>();
+}
+
+testUnnecessaryDueToExplicitParameterTypeNamed(
+    T Function<T>(T, T Function({required T x, required int y})) f) {
+  var a = f(null, ({int? x, required y}) => (x ?? 0) + y);
+  a.expectStaticType<Exactly<int?>>();
+}
+
 main() {}
diff --git a/tests/language/inference_update_1/horizontal_inference_enabled_test.dart b/tests/language/inference_update_1/horizontal_inference_enabled_test.dart
index 51de587..6191c06 100644
--- a/tests/language/inference_update_1/horizontal_inference_enabled_test.dart
+++ b/tests/language/inference_update_1/horizontal_inference_enabled_test.dart
@@ -111,4 +111,15 @@
   f(() => 0, null).expectStaticType<Exactly<int?>>();
 }
 
+testUnnecessaryDueToExplicitParameterType(List<int> list) {
+  var a = list.fold(null, (int? x, y) => (x ?? 0) + y);
+  a.expectStaticType<Exactly<int?>>();
+}
+
+testUnnecessaryDueToExplicitParameterTypeNamed(
+    T Function<T>(T, T Function({required T x, required int y})) f) {
+  var a = f(null, ({int? x, required y}) => (x ?? 0) + y);
+  a.expectStaticType<Exactly<int?>>();
+}
+
 main() {}