[cfe] Use NNBD-updated least closure in constraint gathering

Closes https://github.com/dart-lang/sdk/issues/47797

Change-Id: I58544e185fc691630d56960d54f0711f4a91b60a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241746
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart b/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart
index 4d13bfa..7c3f33b 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart
@@ -9,7 +9,12 @@
 import 'package:kernel/core_types.dart' show CoreTypes;
 
 import 'package:kernel/type_algebra.dart'
-    show FreshTypeParameters, Substitution, getFreshTypeParameters, substitute;
+    show
+        FreshTypeParameters,
+        NullabilityAwareFreeTypeVariableEliminator,
+        Substitution,
+        getFreshTypeParameters,
+        substitute;
 
 import 'package:kernel/type_environment.dart';
 
@@ -416,14 +421,17 @@
 
     if (!isEmptyContext(returnContextType)) {
       if (isConst) {
-        returnContextType = new TypeVariableEliminator(
-                clientLibrary.isNonNullableByDefault
-                    ? const NeverType.nonNullable()
-                    : const NullType(),
-                clientLibrary.isNonNullableByDefault
-                    ? objectNullableRawType
-                    : objectLegacyRawType)
-            .substituteType(returnContextType!);
+        if (clientLibrary.isNonNullableByDefault) {
+          returnContextType = new NullabilityAwareFreeTypeVariableEliminator(
+                  bottomType: const NeverType.nonNullable(),
+                  topType: objectNullableRawType,
+                  topFunctionType: functionRawType(Nullability.nonNullable))
+              .eliminateToLeast(returnContextType!);
+        } else {
+          returnContextType =
+              new TypeVariableEliminator(const NullType(), objectLegacyRawType)
+                  .substituteType(returnContextType!);
+        }
       }
       gatherer.tryConstrainUpper(declaredReturnType!, returnContextType!);
     }
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart b/pkg/front_end/testcases/nnbd/issue47795.dart
new file mode 100644
index 0000000..c08ad85
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, 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.
+
+class C<X> {
+  void m1({List<void Function(X)> xs = const []}) {}
+  void m2({List<void Function<Y extends List<X>>(Y)> xs = const []}) {}
+}
+
+void main() => C().m1();
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.strong.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.strong.expect
new file mode 100644
index 0000000..e9d2bf6
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.strong.expect
@@ -0,0 +1,18 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::C<self::C::X%>
+    : super core::Object::•()
+    ;
+  method m1({core::List<(self::C::X%) → void> xs = #C1}) → void {}
+  method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = #C2}) → void {}
+}
+static method main() → void
+  return new self::C::•<dynamic>().{self::C::m1}(){({xs: core::List<(dynamic) → void>}) → void};
+
+constants  {
+  #C1 = <(core::Object?) → void>[]
+  #C2 = <Never>[]
+}
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.strong.transformed.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.strong.transformed.expect
new file mode 100644
index 0000000..e9d2bf6
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.strong.transformed.expect
@@ -0,0 +1,18 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::C<self::C::X%>
+    : super core::Object::•()
+    ;
+  method m1({core::List<(self::C::X%) → void> xs = #C1}) → void {}
+  method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = #C2}) → void {}
+}
+static method main() → void
+  return new self::C::•<dynamic>().{self::C::m1}(){({xs: core::List<(dynamic) → void>}) → void};
+
+constants  {
+  #C1 = <(core::Object?) → void>[]
+  #C2 = <Never>[]
+}
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline.expect
new file mode 100644
index 0000000..05de177
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline.expect
@@ -0,0 +1,6 @@
+class C<X> {
+  void m1({List<void Function(X)> xs = const []}) {}
+  void m2({List<void Function<Y extends List<X>>(Y)> xs = const []}) {}
+}
+
+void main() => C().m1();
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..05de177
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline_modelled.expect
@@ -0,0 +1,6 @@
+class C<X> {
+  void m1({List<void Function(X)> xs = const []}) {}
+  void m2({List<void Function<Y extends List<X>>(Y)> xs = const []}) {}
+}
+
+void main() => C().m1();
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.weak.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.expect
new file mode 100644
index 0000000..e7b237c
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.expect
@@ -0,0 +1,18 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::C<self::C::X%>
+    : super core::Object::•()
+    ;
+  method m1({core::List<(self::C::X%) → void> xs = #C1}) → void {}
+  method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = #C2}) → void {}
+}
+static method main() → void
+  return new self::C::•<dynamic>().{self::C::m1}(){({xs: core::List<(dynamic) → void>}) → void};
+
+constants  {
+  #C1 = <(core::Object?) →* void>[]
+  #C2 = <Never*>[]
+}
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.weak.modular.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.modular.expect
new file mode 100644
index 0000000..e7b237c
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.modular.expect
@@ -0,0 +1,18 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::C<self::C::X%>
+    : super core::Object::•()
+    ;
+  method m1({core::List<(self::C::X%) → void> xs = #C1}) → void {}
+  method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = #C2}) → void {}
+}
+static method main() → void
+  return new self::C::•<dynamic>().{self::C::m1}(){({xs: core::List<(dynamic) → void>}) → void};
+
+constants  {
+  #C1 = <(core::Object?) →* void>[]
+  #C2 = <Never*>[]
+}
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.weak.outline.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.outline.expect
new file mode 100644
index 0000000..1952228
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.outline.expect
@@ -0,0 +1,20 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::C<self::C::X%>
+    ;
+  method m1({core::List<(self::C::X%) → void> xs = const <(core::Object?) → void>[]}) → void
+    ;
+  method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = const <Never>[]}) → void
+    ;
+}
+static method main() → void
+  ;
+
+
+Extra constant evaluation status:
+Evaluated: ListLiteral @ org-dartlang-testcase:///issue47795.dart:6:40 -> ListConstant(const <void Function(Object?)*>[])
+Evaluated: ListLiteral @ org-dartlang-testcase:///issue47795.dart:7:59 -> ListConstant(const <Never*>[])
+Extra constant evaluation: evaluated: 2, effectively constant: 2
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.weak.transformed.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.transformed.expect
new file mode 100644
index 0000000..e7b237c
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.transformed.expect
@@ -0,0 +1,18 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::C<self::C::X%>
+    : super core::Object::•()
+    ;
+  method m1({core::List<(self::C::X%) → void> xs = #C1}) → void {}
+  method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = #C2}) → void {}
+}
+static method main() → void
+  return new self::C::•<dynamic>().{self::C::m1}(){({xs: core::List<(dynamic) → void>}) → void};
+
+constants  {
+  #C1 = <(core::Object?) →* void>[]
+  #C2 = <Never*>[]
+}
diff --git a/pkg/kernel/lib/type_algebra.dart b/pkg/kernel/lib/type_algebra.dart
index fe6e356..a7d73a0 100644
--- a/pkg/kernel/lib/type_algebra.dart
+++ b/pkg/kernel/lib/type_algebra.dart
@@ -98,8 +98,10 @@
 ///
 /// Returns `true` if [type] contains a [TypeParameterType] that doesn't refer
 /// to an enclosing generic [FunctionType] within [type].
-bool containsFreeTypeVariables(DartType type) {
-  return new _FreeTypeVariableVisitor().visit(type);
+bool containsFreeTypeVariables(DartType type,
+    {Set<TypeParameter>? boundVariables}) {
+  return new _FreeTypeVariableVisitor(boundVariables: boundVariables)
+      .visit(type);
 }
 
 /// Generates a fresh copy of the given type parameters, with their bounds
@@ -402,6 +404,12 @@
 
   @override
   TypeParameter freshTypeParameter(TypeParameter node) {
+    assert(
+        !substitution.containsKey(node),
+        "Function type variables cannot be substituted while still attached "
+        "to the function. Perform substitution on "
+        "`FunctionType.withoutTypeParameters` instead.");
+
     TypeParameter fresh = new TypeParameter(node.name)..flags = node.flags;
     TypeParameterType typeParameterType = substitution[node] =
         new TypeParameterType.forAlphaRenaming(node, fresh);
@@ -661,13 +669,6 @@
     // any uses, but does not tell if the resulting function type is distinct.
     // Our own use counter will get incremented if something from our
     // environment has been used inside the function.
-    assert(
-        node.typeParameters.every((TypeParameter parameter) =>
-            lookup(parameter, true) == null &&
-            lookup(parameter, false) == null),
-        "Function type variables cannot be substituted while still attached "
-        "to the function. Perform substitution on "
-        "`FunctionType.withoutTypeParameters` instead.");
     _TypeSubstitutor inner =
         node.typeParameters.isEmpty ? this : newInnerEnvironment();
     int before = this.useCounter;
@@ -919,9 +920,10 @@
 }
 
 class _FreeTypeVariableVisitor implements DartTypeVisitor<bool> {
-  final Set<TypeParameter> variables = new Set<TypeParameter>();
+  final Set<TypeParameter> boundVariables;
 
-  _FreeTypeVariableVisitor();
+  _FreeTypeVariableVisitor({Set<TypeParameter>? boundVariables})
+      : this.boundVariables = boundVariables ?? <TypeParameter>{};
 
   bool visit(DartType node) => node.accept(this);
 
@@ -967,22 +969,22 @@
 
   @override
   bool visitFunctionType(FunctionType node) {
-    variables.addAll(node.typeParameters);
+    boundVariables.addAll(node.typeParameters);
     bool result = node.typeParameters.any(handleTypeParameter) ||
         node.positionalParameters.any(visit) ||
         node.namedParameters.any(visitNamedType) ||
         visit(node.returnType);
-    variables.removeAll(node.typeParameters);
+    boundVariables.removeAll(node.typeParameters);
     return result;
   }
 
   @override
   bool visitTypeParameterType(TypeParameterType node) {
-    return !variables.contains(node.parameter);
+    return !boundVariables.contains(node.parameter);
   }
 
   bool handleTypeParameter(TypeParameter node) {
-    assert(variables.contains(node));
+    assert(boundVariables.contains(node));
     if (node.bound.accept(this)) return true;
     // ignore: unnecessary_null_comparison
     if (node.defaultType == null) return false;
@@ -1168,27 +1170,18 @@
   DartType visitVoidType(VoidType node, CoreTypes coreTypes) => node;
 }
 
-/// Eliminates specified free type parameters in a type.
-///
-/// The algorithm for elimination of type variables is described in
-/// https://github.com/dart-lang/language/pull/957
-class NullabilityAwareTypeVariableEliminator extends ReplacementVisitor {
+abstract class NullabilityAwareTypeVariableEliminatorBase
+    extends ReplacementVisitor {
   final DartType bottomType;
   final DartType topType;
   final DartType topFunctionType;
-  final Set<TypeParameter> eliminationTargets;
   late bool _isLeastClosure;
-  final bool Function(DartType type, bool Function(DartType type) recursor)?
-      unhandledTypeHandler;
 
-  NullabilityAwareTypeVariableEliminator(
-      {required this.eliminationTargets,
-      required this.bottomType,
+  NullabilityAwareTypeVariableEliminatorBase(
+      {required this.bottomType,
       required this.topType,
-      required this.topFunctionType,
-      this.unhandledTypeHandler})
-      // ignore: unnecessary_null_comparison
-      : assert(eliminationTargets != null),
+      required this.topFunctionType})
+      :
         // ignore: unnecessary_null_comparison
         assert(bottomType != null),
         // ignore: unnecessary_null_comparison
@@ -1196,13 +1189,17 @@
         // ignore: unnecessary_null_comparison
         assert(topFunctionType != null);
 
-  /// Returns a subtype of [type] for all values of [eliminationTargets].
+  bool containsTypeVariablesToEliminate(DartType type);
+
+  bool isTypeVariableToEliminate(TypeParameter typeParameter);
+
+  /// Returns a subtype of [type] for all variables to be eliminated.
   DartType eliminateToLeast(DartType type) {
     _isLeastClosure = true;
     return type.accept1(this, Variance.covariant) ?? type;
   }
 
-  /// Returns a supertype of [type] for all values of [eliminationTargets].
+  /// Returns a supertype of [type] for all variables to be eliminated.
   DartType eliminateToGreatest(DartType type) {
     _isLeastClosure = false;
     return type.accept1(this, Variance.covariant) ?? type;
@@ -1234,8 +1231,7 @@
     //  - The greatest closure of `S` with respect to `L` is `Function`
     if (node.typeParameters.isNotEmpty) {
       for (TypeParameter typeParameter in node.typeParameters) {
-        if (containsTypeVariable(typeParameter.bound, eliminationTargets,
-            unhandledTypeHandler: unhandledTypeHandler)) {
+        if (containsTypeVariablesToEliminate(typeParameter.bound)) {
           return getFunctionReplacement(variance);
         }
       }
@@ -1245,13 +1241,114 @@
 
   @override
   DartType? visitTypeParameterType(TypeParameterType node, int variance) {
-    if (eliminationTargets.contains(node.parameter)) {
+    if (isTypeVariableToEliminate(node.parameter)) {
       return getTypeParameterReplacement(variance);
     }
     return super.visitTypeParameterType(node, variance);
   }
 }
 
+/// Eliminates specified free type parameters in a type.
+///
+/// Use this class when only a specific subset of unbound variables in a type
+/// should be substituted with one of [bottomType], [topType], or
+/// [topFunctionType].  For example, running a
+/// `NullabilityAwareTypeVariableEliminatorBase({T}, Never, Object?,
+/// Function).eliminateToLeast` on type `T Function<S>(S s, R r)` will return
+/// `Never Function<S>(S s, R r)`.
+///
+/// The algorithm for elimination of type variables is described in
+/// https://github.com/dart-lang/language/pull/957
+class NullabilityAwareTypeVariableEliminator
+    extends NullabilityAwareTypeVariableEliminatorBase {
+  final Set<TypeParameter> eliminationTargets;
+  final bool Function(DartType type, bool Function(DartType type) recursor)?
+      unhandledTypeHandler;
+
+  NullabilityAwareTypeVariableEliminator(
+      {required this.eliminationTargets,
+      required DartType bottomType,
+      required DartType topType,
+      required DartType topFunctionType,
+      this.unhandledTypeHandler})
+      // ignore: unnecessary_null_comparison
+      : assert(eliminationTargets != null),
+        // ignore: unnecessary_null_comparison
+        assert(bottomType != null),
+        // ignore: unnecessary_null_comparison
+        assert(topType != null),
+        // ignore: unnecessary_null_comparison
+        assert(topFunctionType != null),
+        super(
+            bottomType: bottomType,
+            topType: topType,
+            topFunctionType: topFunctionType);
+
+  @override
+  bool containsTypeVariablesToEliminate(DartType type) {
+    return containsTypeVariable(type, eliminationTargets,
+        unhandledTypeHandler: unhandledTypeHandler);
+  }
+
+  @override
+  bool isTypeVariableToEliminate(TypeParameter typeParameter) {
+    return eliminationTargets.contains(typeParameter);
+  }
+}
+
+/// Eliminates all free type parameters in a type.
+///
+/// Use this class when all unbound variables in a type should be substituted
+/// with one of [bottomType], [topType], or [topFunctionType].  For example,
+/// running a `NullabilityAwareFreeTypeVariableEliminator(Never, Object?,
+/// Function).eliminateToLeast` on type `T Function<S>(S s, R r)` will return
+/// `Never Function<S>(S s, Object? r)`.
+///
+/// The algorithm for elimination of type variables is described in
+/// https://github.com/dart-lang/language/pull/957
+class NullabilityAwareFreeTypeVariableEliminator
+    extends NullabilityAwareTypeVariableEliminatorBase {
+  Set<TypeParameter> _boundVariables = <TypeParameter>{};
+
+  NullabilityAwareFreeTypeVariableEliminator(
+      {required DartType bottomType,
+      required DartType topType,
+      required DartType topFunctionType})
+      :
+        // ignore: unnecessary_null_comparison
+        assert(bottomType != null),
+        // ignore: unnecessary_null_comparison
+        assert(topType != null),
+        // ignore: unnecessary_null_comparison
+        assert(topFunctionType != null),
+        super(
+            bottomType: bottomType,
+            topType: topType,
+            topFunctionType: topFunctionType);
+
+  @override
+  DartType? visitFunctionType(FunctionType node, int variance) {
+    if (node.typeParameters.isNotEmpty) {
+      _boundVariables.addAll(node.typeParameters);
+      DartType? result = super.visitFunctionType(node, variance);
+      _boundVariables.removeAll(node.typeParameters);
+      return result;
+    } else {
+      return super.visitFunctionType(node, variance);
+    }
+  }
+
+  @override
+  bool containsTypeVariablesToEliminate(DartType type) {
+    return containsFreeTypeVariables(type, boundVariables: _boundVariables);
+  }
+
+  @override
+  bool isTypeVariableToEliminate(TypeParameter typeParameter) {
+    return !_boundVariables.contains(typeParameter);
+  }
+}
+
 /// Computes [type] as if declared without nullability markers.
 ///
 /// For example, int? and int* are considered applications of the nullable and