[analyzer][cfe] Share inference-using-bounds routines

Part of https://github.com/dart-lang/sdk/issues/54902

Change-Id: I12282c2492ed9f1220b47fcf8b0d73c93bbfc432
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/401660
Reviewed-by: Erik Ernst <eernst@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart
index 09e3103..d4eaf4d 100644
--- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart
@@ -5,6 +5,7 @@
 import '../flow_analysis/flow_analysis_operations.dart';
 import '../types/shared_type.dart';
 import 'nullability_suffix.dart';
+import 'type_constraint.dart';
 
 /// Callback API used by the shared type analyzer to query and manipulate the
 /// client's representation of variables and types.
@@ -698,6 +699,40 @@
   /// Returns [type] suffixed with the [suffix].
   TypeStructure withNullabilitySuffixInternal(
       TypeStructure type, NullabilitySuffix suffix);
+
+  TypeConstraintGenerator<
+          TypeStructure,
+          SharedNamedFunctionParameterStructure<TypeStructure>,
+          Variable,
+          TypeParameterStructure,
+          TypeDeclarationType,
+          TypeDeclaration,
+          Object>
+      createTypeConstraintGenerator(
+          {required TypeConstraintGenerationDataForTesting<TypeStructure,
+                  TypeParameterStructure, Variable, Object>?
+              typeConstraintGenerationDataForTesting,
+          required List<TypeParameterStructure> typeParametersToInfer,
+          required TypeAnalyzerOperations<TypeStructure, Variable,
+                  TypeParameterStructure, TypeDeclarationType, TypeDeclaration>
+              typeAnalyzerOperations,
+          required bool inferenceUsingBoundsIsEnabled});
+
+  MergedTypeConstraint<TypeStructure, TypeParameterStructure, Variable,
+          TypeDeclarationType, TypeDeclaration>
+      mergeInConstraintsFromBound(
+          {required TypeParameterStructure typeParameterToInfer,
+          required List<TypeParameterStructure> typeParametersToInfer,
+          required TypeStructure lower,
+          required Map<
+                  TypeParameterStructure,
+                  MergedTypeConstraint<TypeStructure, TypeParameterStructure,
+                      Variable, TypeDeclarationType, TypeDeclaration>>
+              inferencePhaseConstraints,
+          required TypeConstraintGenerationDataForTesting<TypeStructure,
+                  TypeParameterStructure, Variable, Object>?
+              dataForTesting,
+          required bool inferenceUsingBoundsIsEnabled});
 }
 
 mixin TypeAnalyzerOperationsMixin<
@@ -893,6 +928,96 @@
       SharedTypeView<TypeStructure> type) {
     return new SharedTypeSchemaView(type.unwrapTypeView());
   }
+
+  @override
+  MergedTypeConstraint<TypeStructure, TypeParameterStructure, Variable,
+          TypeDeclarationType, TypeDeclaration>
+      mergeInConstraintsFromBound(
+          {required TypeParameterStructure typeParameterToInfer,
+          required List<TypeParameterStructure> typeParametersToInfer,
+          required TypeStructure lower,
+          required Map<
+                  TypeParameterStructure,
+                  MergedTypeConstraint<TypeStructure, TypeParameterStructure,
+                      Variable, TypeDeclarationType, TypeDeclaration>>
+              inferencePhaseConstraints,
+          required TypeConstraintGenerationDataForTesting<TypeStructure,
+                  TypeParameterStructure, Variable, Object>?
+              dataForTesting,
+          required bool inferenceUsingBoundsIsEnabled}) {
+    // The type parameter's bound may refer to itself (or other type
+    // parameters), so we might have to create an additional constraint.
+    // Consider this example from
+    // https://github.com/dart-lang/language/issues/3009:
+    //
+    //     class A<X extends A<X>> {}
+    //     class B extends A<B> {}
+    //     class C extends B {}
+    //     void f<X extends A<X>>(X x) {}
+    //     void main() {
+    //       f(C()); // should infer f<B>(C()).
+    //     }
+    //
+    // In order for `f(C())` to be inferred as `f<B>(C())`, we need to
+    // generate the constraint `X <: B`. To do this, we first take the lower
+    // constraint we've accumulated so far (which, in this example, is `C`,
+    // due to the presence of the actual argument `C()`), and use subtype
+    // constraint generation to match it against the explicit bound (which
+    // is `A<X>`; hence we perform `C <# A<X>`). If this produces any
+    // constraints (i.e. `X <: B` in this example), then they are added to
+    // the set of constraints just before choosing the final type.
+
+    TypeStructure typeParameterToInferBound = typeParameterToInfer.bound!;
+
+    // TODO(cstefantsova): Pass [dataForTesting] when
+    // [InferenceDataForTesting] is merged with [TypeInferenceResultForTesting].
+    TypeConstraintGenerator<
+            TypeStructure,
+            SharedNamedFunctionParameterStructure<TypeStructure>,
+            Variable,
+            TypeParameterStructure,
+            TypeDeclarationType,
+            TypeDeclaration,
+            Object> typeConstraintGatherer =
+        createTypeConstraintGenerator(
+            typeConstraintGenerationDataForTesting: null,
+            typeParametersToInfer: typeParametersToInfer,
+            typeAnalyzerOperations: this,
+            inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled);
+    typeConstraintGatherer.performSubtypeConstraintGenerationInternal(
+        lower, typeParameterToInferBound,
+        leftSchema: true, astNodeForTesting: null);
+    Map<
+            TypeParameterStructure,
+            MergedTypeConstraint<
+                TypeStructure,
+                TypeParameterStructure,
+                Variable,
+                TypeDeclarationType,
+                TypeDeclaration>> constraintsPerTypeVariable =
+        typeConstraintGatherer.computeConstraints();
+    for (TypeParameterStructure typeParameter
+        in constraintsPerTypeVariable.keys) {
+      MergedTypeConstraint<TypeStructure, TypeParameterStructure, Variable,
+              TypeDeclarationType, TypeDeclaration> constraint =
+          constraintsPerTypeVariable[typeParameter]!;
+      constraint.origin = new TypeConstraintFromExtendsClause(
+          typeParameterName: typeParameterToInfer.displayName,
+          boundType: new SharedTypeView(typeParameterToInferBound),
+          extendsType: new SharedTypeView(typeParameterToInferBound));
+      if (!constraint.isEmpty(this)) {
+        MergedTypeConstraint? constraintForParameter =
+            inferencePhaseConstraints[typeParameter];
+        if (constraintForParameter == null) {
+          inferencePhaseConstraints[typeParameter] = constraint;
+        } else {
+          constraintForParameter.mergeInTypeSchemaUpper(constraint.upper, this);
+          constraintForParameter.mergeInTypeSchemaLower(constraint.lower, this);
+        }
+      }
+    }
+    return constraintsPerTypeVariable[typeParameterToInfer]!;
+  }
 }
 
 /// Abstract interface of a type constraint generator.
@@ -1832,6 +1957,12 @@
 
     return false;
   }
+
+  /// Returns the set of type constraints that was gathered.
+  Map<
+      TypeParameterStructure,
+      MergedTypeConstraint<TypeStructure, TypeParameterStructure, Variable,
+          TypeDeclarationType, TypeDeclaration>> computeConstraints();
 }
 
 mixin TypeConstraintGeneratorMixin<
diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_constraint.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_constraint.dart
index 58beeed..7a8a79d 100644
--- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_constraint.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_constraint.dart
@@ -365,3 +365,41 @@
     return <String>[];
   }
 }
+
+/// Data structure maintaining intermediate type inference results, such as
+/// type constraints, for testing purposes.  Under normal execution, no
+/// instance of this class should be created.
+class TypeConstraintGenerationDataForTesting<
+    TypeStructure extends SharedTypeStructure<TypeStructure>,
+    TypeParameterStructure extends SharedTypeParameterStructure<TypeStructure>,
+    Variable extends Object,
+    AstNode extends Object> {
+  /// Map from nodes requiring type inference to the generated type constraints
+  /// for the node.
+  final Map<
+      AstNode,
+      List<
+          GeneratedTypeConstraint<TypeStructure, TypeParameterStructure,
+              Variable>>> generatedTypeConstraints = {};
+
+  /// Merges [other] into the receiver, combining the constraints.
+  ///
+  /// The method reuses data structures from [other] whenever possible, to
+  /// avoid extra memory allocations. This process is destructive to [other]
+  /// because the changes made to the reused structures will be visible to
+  /// [other].
+  void mergeIn(
+      TypeConstraintGenerationDataForTesting<TypeStructure,
+              TypeParameterStructure, Variable, AstNode>
+          other) {
+    for (AstNode node in other.generatedTypeConstraints.keys) {
+      List<GeneratedTypeConstraint>? constraints =
+          generatedTypeConstraints[node];
+      if (constraints != null) {
+        constraints.addAll(other.generatedTypeConstraints[node]!);
+      } else {
+        generatedTypeConstraints[node] = other.generatedTypeConstraints[node]!;
+      }
+    }
+  }
+}
diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart
index 264acd1..e9b5c31 100644
--- a/pkg/_fe_analyzer_shared/test/mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart
@@ -28,11 +28,14 @@
     as shared;
 import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
     hide MapPatternEntry, RecordPatternField;
+import 'package:_fe_analyzer_shared/src/type_inference/type_constraint.dart';
 import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.dart';
 import 'package:_fe_analyzer_shared/src/type_inference/variable_bindings.dart';
 import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
 import 'package:test/test.dart';
 
+import 'type_inference/type_constraint_gatherer_test.dart';
+
 import 'mini_ir.dart';
 import 'mini_types.dart';
 
@@ -3225,6 +3228,21 @@
   Type withNullabilitySuffixInternal(Type type, NullabilitySuffix modifier) {
     return type.withNullability(modifier);
   }
+
+  @override
+  TypeConstraintGenerator<Type, NamedFunctionParameter, Var, TypeParameter,
+          Type, String, Node>
+      createTypeConstraintGenerator(
+          {required TypeConstraintGenerationDataForTesting?
+              typeConstraintGenerationDataForTesting,
+          required List<TypeParameter> typeParametersToInfer,
+          required TypeAnalyzerOperations<Type, Var, TypeParameter, Type,
+                  String>
+              typeAnalyzerOperations,
+          required bool inferenceUsingBoundsIsEnabled}) {
+    return TypeConstraintGatherer(
+        {for (var typeParameter in typeParametersToInfer) typeParameter.name});
+  }
 }
 
 /// Representation of an expression or statement in the pseudo-Dart language
diff --git a/pkg/_fe_analyzer_shared/test/type_inference/type_constraint_gatherer_test.dart b/pkg/_fe_analyzer_shared/test/type_inference/type_constraint_gatherer_test.dart
index a9ab92d..57d600a 100644
--- a/pkg/_fe_analyzer_shared/test/type_inference/type_constraint_gatherer_test.dart
+++ b/pkg/_fe_analyzer_shared/test/type_inference/type_constraint_gatherer_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.dart';
+import 'package:_fe_analyzer_shared/src/type_inference/type_constraint.dart';
 import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
 import 'package:checks/checks.dart';
 import 'package:test/scaffolding.dart';
@@ -25,7 +26,7 @@
 
   group('performSubtypeConstraintGenerationForFunctionTypes:', () {
     test('Matching functions with no parameters', () {
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
               Type('void Function()'), Type('void Function()'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -35,7 +36,7 @@
 
     group('Matching functions with positional parameters:', () {
       test('None optional', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function(int, String)'), Type('void Function(T, U)'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -44,7 +45,7 @@
       });
 
       test('Some optional on LHS', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function(int, [String])'),
                 Type('void Function(T, U)'),
@@ -57,7 +58,7 @@
 
     group('Non-matching functions with positional parameters:', () {
       test('Non-matching due to return types', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('int Function(int)'), Type('String Function(int)'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -66,7 +67,7 @@
       });
 
       test('Non-matching due to parameter types', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function(int)'), Type('void Function(String)'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -75,7 +76,7 @@
       });
 
       test('Non-matching due to optional parameters on RHS', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function()'), Type('void Function([int])'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -84,7 +85,7 @@
       });
 
       test('Non-matching due to more parameters being required on LHS', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function(int)'), Type('void Function([int])'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -95,7 +96,7 @@
 
     group('Matching functions with named parameters:', () {
       test('None optional', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function({required int x, required String y})'),
                 Type('void Function({required T x, required U y})'),
@@ -106,7 +107,7 @@
       });
 
       test('Some optional on LHS', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function({required int x, String y})'),
                 Type('void Function({required T x, required U y})'),
@@ -117,7 +118,7 @@
       });
 
       test('Optional named parameter on LHS', () {
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function(int, {String x})'),
                 Type('void Function(T)'),
@@ -128,7 +129,7 @@
       });
 
       test('Extra optional named parameter on LHS', () {
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function({String x, int y})'),
                 Type('void Function({T y})'),
@@ -141,7 +142,7 @@
 
     group('Non-matching functions with named parameters:', () {
       test('Non-matching due to return types', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('int Function({int x})'), Type('String Function({int x})'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -150,7 +151,7 @@
       });
 
       test('Non-matching due to named parameter types', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function({int x})'),
                 Type('void Function({String x})'),
@@ -161,7 +162,7 @@
       });
 
       test('Non-matching due to required named parameter on LHS', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function({required int x})'),
                 Type('void Function()'),
@@ -172,7 +173,7 @@
       });
 
       test('Non-matching due to optional named parameter on RHS', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function()'), Type('void Function({int x})'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -182,7 +183,7 @@
 
       test('Non-matching due to named parameter on RHS, with decoys on LHS',
           () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function({int x, int y})'),
                 Type('void Function({int z})'),
@@ -194,7 +195,7 @@
     });
 
     test('Matching functions with named and positional parameters', () {
-      var tcg = _TypeConstraintGatherer({'T', 'U'});
+      var tcg = TypeConstraintGatherer({'T', 'U'});
       check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
               Type('void Function(int, {String y})'),
               Type('void Function(T, {U y})'),
@@ -208,7 +209,7 @@
       test(
           'Non-matching due to LHS not accepting optional positional parameter',
           () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function(int, {String x})'),
                 Type('void Function(int, [String])'),
@@ -219,7 +220,7 @@
       });
 
       test('Non-matching due to positional parameter length mismatch', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('void Function(int, {String x})'),
                 Type('void Function(int, String)'),
@@ -233,7 +234,7 @@
 
   group('performSubtypeConstraintGenerationForRecordTypes:', () {
     test('Matching empty records', () {
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForRecordTypes(
               Type('()'), Type('()'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -243,7 +244,7 @@
 
     group('Matching records:', () {
       test('Without named parameters', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('(int, String)'), Type('(T, U)'),
                 leftSchema: true, astNodeForTesting: Node.placeholder()))
@@ -252,7 +253,7 @@
       });
 
       test('With named parameters', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('(int, {String foo})'), Type('(T, {U foo})'),
                 leftSchema: true, astNodeForTesting: Node.placeholder()))
@@ -263,7 +264,7 @@
 
     group('Non-matching records without named parameters:', () {
       test('Non-matching due to positional types', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('(int,)'), Type('(String,)'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -272,7 +273,7 @@
       });
 
       test('Non-matching due to parameter numbers', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('()'), Type('(int,)'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -281,7 +282,7 @@
       });
 
       test('Non-matching due to more parameters on LHS', () {
-        var tcg = _TypeConstraintGatherer({});
+        var tcg = TypeConstraintGatherer({});
         check(tcg.performSubtypeConstraintGenerationForFunctionTypes(
                 Type('(int,)'), Type('()'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -292,7 +293,7 @@
 
     group('Matching records with named parameters:', () {
       test('No type parameter occurrences', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('({int x, String y})'), Type('({int x, String y})'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -301,7 +302,7 @@
       });
 
       test('Type parameters in RHS', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('({int x, String y})'), Type('({T x, U y})'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -310,7 +311,7 @@
       });
 
       test('Type parameters in LHS', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('({T x, U y})'), Type('({int x, String y})'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -321,7 +322,7 @@
 
     group('Matching records with named parameters:', () {
       test('No type parameter occurrences', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('({int x, String y})'), Type('({int x, String y})'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -330,7 +331,7 @@
       });
 
       test('Type parameters in RHS', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('({int x, String y})'), Type('({T x, U y})'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -339,7 +340,7 @@
       });
 
       test('Type parameters in LHS', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('({T x, U y})'), Type('({int x, String y})'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -350,7 +351,7 @@
 
     group('Non-matching records with named parameters:', () {
       test('Non-matching due to positional parameter numbers', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('(num, num, {T x, U y})'),
                 Type('(num, {int x, String y})'),
@@ -361,7 +362,7 @@
       });
 
       test('Non-matching due to named parameter numbers', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('({T x, U y, num z})'), Type('({int x, String y})'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -370,7 +371,7 @@
       });
 
       test('Non-matching due to named parameter names', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForRecordTypes(
                 Type('(num, {T x, U y})'), Type('(num, {int x, String x2})'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -383,7 +384,7 @@
   group('performSubtypeConstraintGenerationForFutureOr:', () {
     test('FutureOr matches FutureOr with constraints based on arguments', () {
       // `FutureOr<T> <# FutureOr<int>` reduces to `T <# int`
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
               Type('FutureOr<T>'), Type('FutureOr<int>'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -394,7 +395,7 @@
     test('FutureOr does not match FutureOr because arguments fail to match',
         () {
       // `FutureOr<int> <# FutureOr<String>` reduces to `int <# String`
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
               Type('FutureOr<int>'), Type('FutureOr<String>'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -410,7 +411,7 @@
       //   producing `Future<int> <: T`
       // In cases where both branches produce a constraint, the "Future" branch
       // is favored.
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
               Type('Future<int>'), Type('FutureOr<T>'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -426,7 +427,7 @@
       //   producing `Future<_> <: T`
       // In cases where only one branch produces a constraint, that branch is
       // favored.
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
               Type('Future<_>'), Type('FutureOr<T>'),
               leftSchema: true, astNodeForTesting: Node.placeholder()))
@@ -442,7 +443,7 @@
       //   producing `T <: int`
       // In cases where both branches produce a constraint, the "Future" branch
       // is favored.
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
               Type('T'), Type('FutureOr<int>'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -451,7 +452,7 @@
     });
 
     test('Testing FutureOr as the lower bound of the constraint', () {
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForLeftFutureOr(
               Type('FutureOr<T>'), Type('dynamic'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -463,7 +464,7 @@
       // `FutureOr<P0> <# Q` if `Future<P0> <# Q` and `P0 <# Q`. This test case
       // verifies that if `Future<P0> <# Q` matches but `P0 <# Q` does not, then
       // the match fails.
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForLeftFutureOr(
               Type('FutureOr<(T,)>'), Type('Future<(int,)>'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -472,7 +473,7 @@
     });
 
     test('Testing nested FutureOr as the lower bound of the constraint', () {
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForLeftFutureOr(
               Type('FutureOr<FutureOr<T>>'), Type('FutureOr<dynamic>'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -483,7 +484,7 @@
     test('Future matches FutureOr with no constraints', () {
       // `Future<int> <# FutureOr<int>` matches (taking the "Future" branch of
       // the FutureOr) without generating any constraints.
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
               Type('Future<int>'), Type('FutureOr<int>'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -495,7 +496,7 @@
       // `List<T> <# FutureOr<List<int>>` could only match by taking the
       // "non-Future" branch of the FutureOr, so the constraint `T <: int` is
       // produced.
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
               Type('List<T>'), Type('FutureOr<List<int>>'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -505,7 +506,7 @@
 
     group('Nullable FutureOr on RHS:', () {
       test('Does not match, according to spec', () {
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
                 Type('FutureOr<T>'), Type('FutureOr<int>?'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -514,7 +515,7 @@
       });
 
       test('Matches, according to CFE discrepancy', () {
-        var tcg = _TypeConstraintGatherer({'T'},
+        var tcg = TypeConstraintGatherer({'T'},
             enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr: true);
         check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
                 Type('FutureOr<T>'), Type('FutureOr<int>?'),
@@ -526,7 +527,7 @@
 
     group('Nullable FutureOr on LHS:', () {
       test('Does not match, according to spec', () {
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
                 Type('FutureOr<T>?'), Type('FutureOr<int>'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -535,7 +536,7 @@
       });
 
       test('Matches, according to CFE discrepancy', () {
-        var tcg = _TypeConstraintGatherer({'T'},
+        var tcg = TypeConstraintGatherer({'T'},
             enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr: true);
         check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
                 Type('FutureOr<T>?'), Type('FutureOr<int>'),
@@ -549,7 +550,7 @@
   group('performSubtypeConstraintGenerationForLeftNullableType:', () {
     test('Nullable matches nullable with constraints based on base types', () {
       // `T? <# int?` reduces to `T <# int?`
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForLeftNullableType(
               Type('T?'), Type('Null'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -560,7 +561,7 @@
     test('Nullable does not match Nullable because base types fail to match',
         () {
       // `int? <# String?` reduces to `int <# String`
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForLeftNullableType(
               Type('int?'), Type('String?'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -570,7 +571,7 @@
 
     test('Nullable does not match non-nullable', () {
       // `(int, T)? <# (int, String)` does not match
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForLeftNullableType(
               Type('(int, T)?'), Type('(int, String)'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -579,7 +580,7 @@
     });
 
     test('Both LHS and RHS nullable, matching', () {
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForRightNullableType(
               Type('T?'), Type('int?'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -588,7 +589,7 @@
     });
 
     test('Both LHS and RHS nullable, not matching', () {
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForRightNullableType(
               Type('(T, int)?'), Type('(int, String)?'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -606,7 +607,7 @@
       //   producing `Null <: T`
       // In cases where both branches produce a constraint, the "non-Null"
       // branch is favored.
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForRightNullableType(
               Type('Null'), Type('T?'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -622,7 +623,7 @@
       //   producing `T <: int`
       // In cases where both branches produce a constraint, the "non-Null"
       // branch is favored.
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationForRightNullableType(
               Type('T'), Type('int?'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -633,7 +634,7 @@
     test('Null matches Nullable with no constraints', () {
       // `Null <# int?` matches (taking the "Null" branch of
       // the Nullable) without generating any constraints.
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForRightNullableType(
               Type('Null'), Type('int?'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -642,7 +643,7 @@
     });
 
     test('Dynamic matches Object?', () {
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForRightNullableType(
               Type('dynamic'), Type('Object?'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -651,7 +652,7 @@
     });
 
     test('void matches Object?', () {
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForRightNullableType(
               Type('void'), Type('Object?'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -660,7 +661,7 @@
     });
 
     test('LHS not nullable, matches with no constraints', () {
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForRightNullableType(
               Type('int'), Type('int?'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -672,7 +673,7 @@
   group('performSubtypeConstraintGenerationForTypeDeclarationTypes', () {
     group('Same base type on both sides:', () {
       test('Covariant, matching', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
                 Type('Map<T, U>'), Type('Map<int, String>'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -681,7 +682,7 @@
       });
 
       test('Covariant, not matching', () {
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
                 Type('Map<T, int>'), Type('Map<int, String>'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -690,7 +691,7 @@
       });
 
       test('Contravariant, matching', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         tcg.typeAnalyzerOperations.addVariance(
             'Contravariant', [Variance.contravariant, Variance.contravariant]);
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
@@ -701,7 +702,7 @@
       });
 
       test('Contravariant, not matching', () {
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         tcg.typeAnalyzerOperations.addVariance(
             'Contravariant', [Variance.contravariant, Variance.contravariant]);
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
@@ -714,7 +715,7 @@
       });
 
       test('Invariant, matching', () {
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         tcg.typeAnalyzerOperations
             .addVariance('Invariant', [Variance.invariant, Variance.invariant]);
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
@@ -726,7 +727,7 @@
       });
 
       test('Invariant, not matching', () {
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         tcg.typeAnalyzerOperations
             .addVariance('Invariant', [Variance.invariant, Variance.invariant]);
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
@@ -739,7 +740,7 @@
       test('Unrelated, matchable', () {
         // When the variance is "unrelated", type inference doesn't even try to
         // match up the type parameters; they are always considered to match.
-        var tcg = _TypeConstraintGatherer({'T', 'U'});
+        var tcg = TypeConstraintGatherer({'T', 'U'});
         tcg.typeAnalyzerOperations
             .addVariance('Unrelated', [Variance.unrelated, Variance.unrelated]);
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
@@ -752,7 +753,7 @@
       test('Unrelated, not matchable', () {
         // When the variance is "unrelated", type inference doesn't even try to
         // match up the type parameters; they are always considered to match.
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         tcg.typeAnalyzerOperations
             .addVariance('Unrelated', [Variance.unrelated, Variance.unrelated]);
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
@@ -765,7 +766,7 @@
 
     group('Related types on both sides:', () {
       test('No change in type args', () {
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
                 Type('List<T>'), Type('Iterable<int>'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -774,7 +775,7 @@
       });
 
       test('Change in type args', () {
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
                 Type('MyListOfInt'), Type('List<T>'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -787,7 +788,7 @@
         // performSubtypeConstraintGenerationForTypeDeclarationTypes considers
         // it not to match (this is handled by other parts of the subtyping
         // algorithm)
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
                 Type('List<T>?'), Type('Iterable<int>'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -800,7 +801,7 @@
         // performSubtypeConstraintGenerationForTypeDeclarationTypes considers
         // it not to match (this is handled by other parts of the subtyping
         // algorithm)
-        var tcg = _TypeConstraintGatherer({'T'});
+        var tcg = TypeConstraintGatherer({'T'});
         check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
                 Type('List<T>'), Type('Iterable<int>?'),
                 leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -810,7 +811,7 @@
     });
 
     test('Non-interface type on LHS', () {
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
               Type('void Function()'), Type('int'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -819,7 +820,7 @@
     });
 
     test('Non-interface type on RHS', () {
-      var tcg = _TypeConstraintGatherer({});
+      var tcg = TypeConstraintGatherer({});
       check(tcg.performSubtypeConstraintGenerationForTypeDeclarationTypes(
               Type('int'), Type('void Function()'),
               leftSchema: false, astNodeForTesting: Node.placeholder()))
@@ -830,7 +831,7 @@
 
   group('matchTypeParameterBoundInternal', () {
     test('Non-promoted parameter on LHS', () {
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationInternal(
               TypeParameterType(TypeRegistry.addTypeParameter('X')
                 ..explicitBound = Type('Future<String>')),
@@ -842,7 +843,7 @@
     });
 
     test('Promoted parameter on LHS', () {
-      var tcg = _TypeConstraintGatherer({'T'});
+      var tcg = TypeConstraintGatherer({'T'});
       check(tcg.performSubtypeConstraintGenerationInternal(
               TypeParameterType(
                   TypeRegistry.addTypeParameter('X')
@@ -857,7 +858,7 @@
   });
 }
 
-class _TypeConstraintGatherer extends TypeConstraintGenerator<Type,
+class TypeConstraintGatherer extends TypeConstraintGenerator<Type,
         NamedFunctionParameter, Var, TypeParameter, Type, String, Node>
     with
         TypeConstraintGeneratorMixin<Type, NamedFunctionParameter, Var,
@@ -873,7 +874,7 @@
 
   final _constraints = <String>[];
 
-  _TypeConstraintGatherer(Set<String> typeVariablesBeingConstrained,
+  TypeConstraintGatherer(Set<String> typeVariablesBeingConstrained,
       {this.enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr = false})
       : super(inferenceUsingBoundsIsEnabled: false) {
     for (var typeVariableName in typeVariablesBeingConstrained) {
@@ -949,4 +950,12 @@
     // TODO(paulberry): implement instantiateFunctionTypesAndProvideEliminator
     throw UnimplementedError();
   }
+
+  @override
+  Map<TypeParameter,
+          MergedTypeConstraint<Type, TypeParameter, Var, Type, String>>
+      computeConstraints() {
+    // TODO(cstefantsova): implement computeConstraints
+    throw UnimplementedError();
+  }
 }
diff --git a/pkg/analyzer/lib/src/dart/analysis/testing_data.dart b/pkg/analyzer/lib/src/dart/analysis/testing_data.dart
index 706e67d..fb93ab9 100644
--- a/pkg/analyzer/lib/src/dart/analysis/testing_data.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/testing_data.dart
@@ -39,10 +39,14 @@
   void recordTypeConstraintGenerationDataForTesting(
       Uri uri, TypeConstraintGenerationDataForTesting result) {
     TypeConstraintGenerationDataForTesting? existing =
+        // ignore: analyzer_use_new_elements
         uriToTypeConstraintGenerationData[uri];
+    // ignore: analyzer_use_new_elements
     if (existing != null) {
+      // ignore: analyzer_use_new_elements
       existing.mergeIn(result);
     } else {
+      // ignore: analyzer_use_new_elements
       uriToTypeConstraintGenerationData[uri] = result;
     }
   }
diff --git a/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart b/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart
index d97a017..aa57418 100644
--- a/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart
+++ b/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart
@@ -6,20 +6,12 @@
 
 import 'package:_fe_analyzer_shared/src/type_inference/shared_inference_log.dart';
 import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
-import 'package:analyzer/dart/ast/ast.dart'
-    show
-        Annotation,
-        AsExpression,
-        AstNode,
-        ConstructorName,
-        Expression,
-        InvocationExpression,
-        SimpleIdentifier;
 import 'package:analyzer/dart/ast/syntactic_entity.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/element2.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/error/listener.dart' show ErrorReporter;
+import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/ast/extensions.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/type.dart';
@@ -154,7 +146,7 @@
   /// type is a subtype of the [parameterType].
   void constrainArgument(
       DartType argumentType, DartType parameterType, String parameterName,
-      {InterfaceElement? genericClass, required AstNode? nodeForTesting}) {
+      {InterfaceElement? genericClass, required AstNodeImpl? nodeForTesting}) {
     var origin = TypeConstraintFromArgument(
       argumentType: SharedTypeView(argumentType),
       parameterType: SharedTypeView(parameterType),
@@ -175,7 +167,7 @@
       {InterfaceElement? genericClass,
       required List<ParameterElement> parameters,
       required List<DartType> argumentTypes,
-      required AstNode? nodeForTesting}) {
+      required AstNodeImpl? nodeForTesting}) {
     for (int i = 0; i < argumentTypes.length; i++) {
       // Try to pass each argument to each parameter, recording any type
       // parameter bounds that were implied by this assignment.
@@ -193,7 +185,7 @@
   /// [contextType].
   void constrainGenericFunctionInContext(
       FunctionType fnType, DartType contextType,
-      {required AstNode? nodeForTesting}) {
+      {required AstNodeImpl? nodeForTesting}) {
     var origin = TypeConstraintFromFunctionContext(
         functionType: fnType, contextType: contextType);
 
@@ -217,7 +209,7 @@
   /// Apply a return type constraint, which asserts that the [declaredType]
   /// is a subtype of the [contextType].
   void constrainReturnType(DartType declaredType, DartType contextType,
-      {required AstNode? nodeForTesting}) {
+      {required AstNodeImpl? nodeForTesting}) {
     var origin = TypeConstraintFromReturnType(
         declaredType: declaredType, contextType: contextType);
     inferenceLogWriter?.enterConstraintGeneration(
@@ -426,13 +418,10 @@
   /// If [isContravariant] is `true`, then we are solving for a contravariant
   /// type parameter which means we choose the upper bound rather than the
   /// lower bound for normally covariant type parameters.
-  DartType _chooseTypeFromConstraints(
-      Iterable<MergedTypeConstraint> constraints,
-      {bool toKnownType = false,
-      required bool isContravariant}) {
-    var (:lower, :upper) =
-        _computeLowerAndUpperBoundsOfConstraints(constraints);
-
+  DartType _chooseTypeFromConstraint(MergedTypeConstraint constraint,
+      {bool toKnownType = false, required bool isContravariant}) {
+    DartType upper = constraint.upper.unwrapTypeSchemaView();
+    DartType lower = constraint.lower.unwrapTypeSchemaView();
     // Prefer the known bound, if any.
     // Otherwise take whatever bound has partial information, e.g. `Iterable<?>`
     //
@@ -476,7 +465,7 @@
         _typeFormals.length, UnknownInferredType.instance);
     var inferencePhaseConstraints = {
       for (var typeParameter in _constraints.keys)
-        typeParameter: [...?_constraints[typeParameter]]
+        typeParameter: _squashConstraints(_constraints[typeParameter]!)
     };
     for (int i = 0; i < _typeFormals.length; i++) {
       // TODO(kallentu): : Clean up TypeParameterElementImpl casting once
@@ -495,24 +484,24 @@
         );
       }
 
-      var constraints = inferencePhaseConstraints[typeParam]!;
+      var constraint = inferencePhaseConstraints[typeParam]!;
       var previouslyInferredType = _typesInferredSoFar[typeParam];
       if (previouslyInferredType != null) {
         inferredTypes[i] = previouslyInferredType;
       } else if (preliminary) {
         var inferredType = _inferTypeParameterFromContext(
-            constraints, extendsClause,
+            constraint, extendsClause,
             isContravariant: typeParam.variance.isContravariant,
             typeParameterToInfer: typeParam,
             inferencePhaseConstraints: inferencePhaseConstraints);
+
         inferredTypes[i] = inferredType;
         if (typeParam.isLegacyCovariant &&
             UnknownInferredType.isKnown(inferredType)) {
           _typesInferredSoFar[typeParam] = inferredType;
         }
       } else {
-        inferredTypes[i] = _inferTypeParameterFromAll(
-            constraints, extendsClause,
+        inferredTypes[i] = _inferTypeParameterFromAll(constraint, extendsClause,
             isContravariant: typeParam.variance.isContravariant,
             typeParameterToInfer: typeParam,
             inferencePhaseConstraints: inferencePhaseConstraints);
@@ -522,32 +511,6 @@
     return inferredTypes;
   }
 
-  ({DartType lower, DartType upper}) _computeLowerAndUpperBoundsOfConstraints(
-      Iterable<MergedTypeConstraint> constraints) {
-    DartType lower = UnknownInferredType.instance;
-    DartType upper = UnknownInferredType.instance;
-    for (var constraint in constraints) {
-      // Given constraints:
-      //
-      //     L1 <: T <: U1
-      //     L2 <: T <: U2
-      //
-      // These can be combined to produce:
-      //
-      //     LUB(L1, L2) <: T <: GLB(U1, U2).
-      //
-      // This can then be done for all constraints in sequence.
-      //
-      // This resulting constraint may be unsatisfiable; in that case inference
-      // will fail.
-      upper = _typeSystem.greatestLowerBound(
-          upper, constraint.upper.unwrapTypeSchemaView());
-      lower = _typeSystem.leastUpperBound(
-          lower, constraint.lower.unwrapTypeSchemaView());
-    }
-    return (lower: lower, upper: upper);
-  }
-
   void _demoteTypes(List<DartType> types) {
     for (var i = 0; i < types.length; i++) {
       types[i] = _typeSystem.demoteType(types[i]);
@@ -593,52 +556,52 @@
         'Consider passing explicit type argument(s) to the generic.\n\n';
   }
 
-  DartType _inferTypeParameterFromAll(List<MergedTypeConstraint> constraints,
-      MergedTypeConstraint? extendsClause,
+  DartType _inferTypeParameterFromAll(
+      MergedTypeConstraint constraint, MergedTypeConstraint? extendsClause,
       {required bool isContravariant,
       required TypeParameterElementImpl2 typeParameterToInfer,
-      required Map<TypeParameterElementImpl2, List<MergedTypeConstraint>>
+      required Map<TypeParameterElementImpl2, MergedTypeConstraint>
           inferencePhaseConstraints}) {
     if (extendsClause != null) {
-      var (:lower, upper: _) =
-          _computeLowerAndUpperBoundsOfConstraints(constraints);
-
       MergedTypeConstraint? boundConstraint;
       if (inferenceUsingBoundsIsEnabled) {
-        if (!identical(lower, UnknownInferredType.instance)) {
-          boundConstraint = _mergeInConstraintsFromBound(
+        if (!identical(constraint.lower.unwrapTypeSchemaView(),
+            UnknownInferredType.instance)) {
+          boundConstraint = _typeSystemOperations.mergeInConstraintsFromBound(
               typeParameterToInfer: typeParameterToInfer,
-              lower: lower,
-              inferencePhaseConstraints: inferencePhaseConstraints);
+              typeParametersToInfer: _typeFormals,
+              lower: constraint.lower.unwrapTypeSchemaView(),
+              inferencePhaseConstraints: inferencePhaseConstraints,
+              dataForTesting: dataForTesting,
+              inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled);
         }
       }
 
-      constraints = [
-        ...constraints,
+      constraint = _squashConstraints([
+        constraint,
         extendsClause,
         if (boundConstraint != null &&
             !boundConstraint.isEmpty(_typeSystemOperations))
           boundConstraint
-      ];
+      ]);
     }
 
-    var choice = _chooseTypeFromConstraints(constraints,
+    var choice = _chooseTypeFromConstraint(constraint,
         toKnownType: true, isContravariant: isContravariant);
     return choice;
   }
 
   DartType _inferTypeParameterFromContext(
-      Iterable<MergedTypeConstraint> constraints,
-      MergedTypeConstraint? extendsClause,
+      MergedTypeConstraint constraint, MergedTypeConstraint? extendsClause,
       {required bool isContravariant,
       required TypeParameterElementImpl2 typeParameterToInfer,
-      required Map<TypeParameterElementImpl2, List<MergedTypeConstraint>>
+      required Map<TypeParameterElementImpl2, MergedTypeConstraint>
           inferencePhaseConstraints}) {
     // Both bits of the bound information should be available at the same time.
     assert(extendsClause == null || typeParameterToInfer.bound != null);
 
-    DartType t = _chooseTypeFromConstraints(constraints,
-        isContravariant: isContravariant);
+    DartType t =
+        _chooseTypeFromConstraint(constraint, isContravariant: isContravariant);
     if (UnknownInferredType.isUnknown(t)) {
       return t;
     }
@@ -651,83 +614,33 @@
     //
     // If we consider the `T extends num` we conclude `<num>`, which works.
     if (extendsClause != null) {
-      var (:lower, upper: _) =
-          _computeLowerAndUpperBoundsOfConstraints(constraints);
-
       MergedTypeConstraint? boundConstraint;
       if (inferenceUsingBoundsIsEnabled) {
-        if (!identical(lower, UnknownInferredType.instance)) {
-          boundConstraint = _mergeInConstraintsFromBound(
+        if (!identical(constraint.lower.unwrapTypeSchemaView(),
+            UnknownInferredType.instance)) {
+          boundConstraint = _typeSystemOperations.mergeInConstraintsFromBound(
               typeParameterToInfer: typeParameterToInfer,
-              lower: lower,
-              inferencePhaseConstraints: inferencePhaseConstraints);
+              typeParametersToInfer: _typeFormals,
+              lower: constraint.lower.unwrapTypeSchemaView(),
+              inferencePhaseConstraints: inferencePhaseConstraints,
+              dataForTesting: dataForTesting,
+              inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled);
         }
       }
 
-      constraints = [
-        ...constraints,
+      constraint = _squashConstraints([
+        constraint,
         extendsClause,
         if (boundConstraint != null &&
             !boundConstraint.isEmpty(_typeSystemOperations))
           boundConstraint
-      ];
-      return _chooseTypeFromConstraints(constraints,
+      ]);
+      return _chooseTypeFromConstraint(constraint,
           isContravariant: isContravariant);
     }
     return t;
   }
 
-  MergedTypeConstraint _mergeInConstraintsFromBound(
-      {required TypeParameterElementImpl2 typeParameterToInfer,
-      required DartType lower,
-      required Map<TypeParameterElementImpl2, List<MergedTypeConstraint>>
-          inferencePhaseConstraints}) {
-    // The type parameter's bound may refer to itself (or other type
-    // parameters), so we might have to create an additional constraint.
-    // Consider this example from
-    // https://github.com/dart-lang/language/issues/3009:
-    //
-    //     class A<X extends A<X>> {}
-    //     class B extends A<B> {}
-    //     class C extends B {}
-    //     void f<X extends A<X>>(X x) {}
-    //     void main() {
-    //       f(C()); // should infer f<B>(C()).
-    //     }
-    //
-    // In order for `f(C())` to be inferred as `f<B>(C())`, we need to
-    // generate the constraint `X <: B`. To do this, we first take the lower
-    // constraint we've accumulated so far (which, in this example, is `C`,
-    // due to the presence of the actual argument `C()`), and use subtype
-    // constraint generation to match it against the explicit bound (which
-    // is `A<X>`; hence we perform `C <# A<X>`). If this produces any
-    // constraints (i.e. `X <: B` in this example), then they are added to
-    // the set of constraints just before choosing the final type.
-
-    DartType typeParameterToInferBound = typeParameterToInfer.bound!;
-    TypeConstraintGatherer typeConstraintGatherer = TypeConstraintGatherer(
-        typeSystemOperations: _typeSystemOperations,
-        typeParameters: _typeFormals,
-        inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled,
-        dataForTesting: null);
-    typeConstraintGatherer.performSubtypeConstraintGenerationInternal(
-        lower, typeParameterToInferBound,
-        leftSchema: true, astNodeForTesting: null);
-    var constraintsPerTypeVariable =
-        typeConstraintGatherer.computeConstraints();
-    for (var typeParameter in constraintsPerTypeVariable.keys) {
-      var constraint = constraintsPerTypeVariable[typeParameter]!;
-      constraint.origin = TypeConstraintFromExtendsClause(
-          typeParameterName: typeParameterToInfer.name3,
-          boundType: SharedTypeView(typeParameterToInferBound),
-          extendsType: SharedTypeView(typeParameterToInferBound));
-      if (!constraint.isEmpty(_typeSystemOperations)) {
-        (inferencePhaseConstraints[typeParameter] ??= []).add(constraint);
-      }
-    }
-    return constraintsPerTypeVariable[typeParameterToInfer]!;
-  }
-
   /// Reports an inference failure on [errorEntity] according to its type.
   void _reportInferenceFailure({
     ErrorReporter? errorReporter,
@@ -809,13 +722,44 @@
     }
   }
 
+  MergedTypeConstraint _squashConstraints(
+      Iterable<MergedTypeConstraint> constraints) {
+    DartType lower = UnknownInferredType.instance;
+    DartType upper = UnknownInferredType.instance;
+    TypeConstraintOrigin origin = UnknownTypeConstraintOrigin();
+
+    for (var constraint in constraints) {
+      // Given constraints:
+      //
+      //     L1 <: T <: U1
+      //     L2 <: T <: U2
+      //
+      // These can be combined to produce:
+      //
+      //     LUB(L1, L2) <: T <: GLB(U1, U2).
+      //
+      // This can then be done for all constraints in sequence.
+      //
+      // This resulting constraint may be unsatisfiable; in that case inference
+      // will fail.
+      upper = _typeSystem.greatestLowerBound(
+          upper, constraint.upper.unwrapTypeSchemaView());
+      lower = _typeSystem.leastUpperBound(
+          lower, constraint.lower.unwrapTypeSchemaView());
+    }
+    return MergedTypeConstraint(
+        lower: SharedTypeSchemaView(lower),
+        upper: SharedTypeSchemaView(upper),
+        origin: origin);
+  }
+
   /// Tries to make [t1] a subtype of [t2] and accumulate constraints as needed.
   ///
   /// The return value indicates whether the match was successful.  If it was
   /// unsuccessful, any constraints that were accumulated during the match
   /// attempt have been rewound.
   bool _tryMatchSubtypeOf(DartType t1, DartType t2, TypeConstraintOrigin origin,
-      {required bool covariant, required AstNode? nodeForTesting}) {
+      {required bool covariant, required AstNodeImpl? nodeForTesting}) {
     var gatherer = TypeConstraintGatherer(
         typeParameters: _typeParameters,
         typeSystemOperations: _typeSystemOperations,
diff --git a/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart b/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
index 2d4829e..7dc445d 100644
--- a/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
+++ b/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
@@ -13,6 +13,7 @@
     show
         GeneratedTypeConstraint,
         MergedTypeConstraint,
+        TypeConstraintGenerationDataForTesting,
         TypeConstraintFromArgument,
         TypeConstraintFromExtendsClause,
         TypeConstraintFromFunctionContext,
@@ -74,6 +75,10 @@
     InterfaceTypeImpl,
     InterfaceElementImpl2>;
 
+typedef TypeConstraintGenerationDataForTesting
+    = shared.TypeConstraintGenerationDataForTesting<DartType,
+        TypeParameterElementImpl2, PromotableElementImpl2, AstNodeImpl>;
+
 /// Instance of [shared.TypeConstraintOrigin] specific to the Analyzer.
 typedef TypeConstraintOrigin = shared.TypeConstraintOrigin<
     DartType,
@@ -99,7 +104,7 @@
         TypeParameterElementImpl2,
         InterfaceTypeImpl,
         InterfaceElementImpl2,
-        AstNode>
+        AstNodeImpl>
     with
         shared.TypeConstraintGeneratorMixin<
             DartType,
@@ -108,7 +113,7 @@
             TypeParameterElementImpl2,
             InterfaceTypeImpl,
             InterfaceElementImpl2,
-            AstNode> {
+            AstNodeImpl> {
   @override
   final Set<TypeParameterElementImpl2> typeParametersToConstrain =
       Set.identity();
@@ -142,7 +147,7 @@
   @override
   void addLowerConstraintForParameter(
       TypeParameterElementImpl2 element, DartType lower,
-      {required AstNode? astNodeForTesting}) {
+      {required AstNodeImpl? astNodeForTesting}) {
     GeneratedTypeConstraint generatedTypeConstraint =
         GeneratedTypeConstraint.lower(element, SharedTypeSchemaView(lower));
     _constraints.add(generatedTypeConstraint);
@@ -155,7 +160,7 @@
   @override
   void addUpperConstraintForParameter(
       TypeParameterElementImpl2 element, DartType upper,
-      {required AstNode? astNodeForTesting}) {
+      {required AstNodeImpl? astNodeForTesting}) {
     GeneratedTypeConstraint generatedTypeConstraint =
         GeneratedTypeConstraint.upper(element, SharedTypeSchemaView(upper));
     _constraints.add(generatedTypeConstraint);
@@ -165,7 +170,7 @@
     }
   }
 
-  /// Returns the set of type constraints that was gathered.
+  @override
   Map<TypeParameterElementImpl2, MergedTypeConstraint> computeConstraints() {
     var result = <TypeParameterElementImpl2, MergedTypeConstraint>{};
     for (var parameter in typeParametersToConstrain) {
@@ -190,7 +195,7 @@
   void eliminateTypeParametersInGeneratedConstraints(
       covariant List<TypeParameterElementImpl2> eliminator,
       shared.TypeConstraintGeneratorState eliminationStartState,
-      {required AstNode? astNodeForTesting}) {
+      {required AstNodeImpl? astNodeForTesting}) {
     var constraints = _constraints.sublist(eliminationStartState.count);
     _constraints.length = eliminationStartState.count;
     for (var constraint in constraints) {
@@ -269,31 +274,3 @@
     _constraints.length = state.count;
   }
 }
-
-/// Data structure maintaining intermediate type inference results, such as
-/// type constraints, for testing purposes.  Under normal execution, no
-/// instance of this class should be created.
-class TypeConstraintGenerationDataForTesting {
-  /// Map from nodes requiring type inference to the generated type constraints
-  /// for the node.
-  final Map<AstNode, List<GeneratedTypeConstraint>> generatedTypeConstraints =
-      {};
-
-  /// Merges [other] into the receiver, combining the constraints.
-  ///
-  /// The method reuses data structures from [other] whenever possible, to
-  /// avoid extra memory allocations. This process is destructive to [other]
-  /// because the changes made to the reused structures will be visible to
-  /// [other].
-  void mergeIn(TypeConstraintGenerationDataForTesting other) {
-    for (AstNode node in other.generatedTypeConstraints.keys) {
-      List<GeneratedTypeConstraint>? constraints =
-          generatedTypeConstraints[node];
-      if (constraints != null) {
-        constraints.addAll(other.generatedTypeConstraints[node]!);
-      } else {
-        generatedTypeConstraints[node] = other.generatedTypeConstraints[node]!;
-      }
-    }
-  }
-}
diff --git a/pkg/analyzer/lib/src/dart/element/type_system.dart b/pkg/analyzer/lib/src/dart/element/type_system.dart
index fa47fa8..0bd6189 100644
--- a/pkg/analyzer/lib/src/dart/element/type_system.dart
+++ b/pkg/analyzer/lib/src/dart/element/type_system.dart
@@ -13,6 +13,7 @@
 import 'package:analyzer/dart/element/type_provider.dart';
 import 'package:analyzer/dart/element/type_system.dart';
 import 'package:analyzer/error/listener.dart' show ErrorReporter;
+import 'package:analyzer/src/dart/ast/ast.dart' show AstNodeImpl;
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/extensions.dart';
 import 'package:analyzer/src/dart/element/generic_inferrer.dart';
@@ -644,7 +645,7 @@
     required bool strictInference,
     required bool strictCasts,
     required TypeConstraintGenerationDataForTesting? dataForTesting,
-    required AstNode? nodeForTesting,
+    required AstNodeImpl? nodeForTesting,
   }) {
     if (contextType.typeFormals.isNotEmpty || fnType.typeFormals.isEmpty) {
       return const <DartType>[];
@@ -1687,7 +1688,7 @@
     required bool strictCasts,
     required TypeSystemOperations typeSystemOperations,
     required TypeConstraintGenerationDataForTesting? dataForTesting,
-    required AstNode? nodeForTesting,
+    required AstNodeImpl? nodeForTesting,
   }) {
     // Create a GenericInferrer that will allow certain type parameters to be
     // inferred. It will optimistically assume these type parameters can be
diff --git a/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
index f1e34f7..06a31d0 100644
--- a/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
@@ -337,7 +337,7 @@
   List<DartType>? _inferTypeArguments(
       ExtensionOverride node, DartType receiverType,
       {required TypeConstraintGenerationDataForTesting? dataForTesting,
-      required AstNode? nodeForTesting}) {
+      required AstNodeImpl? nodeForTesting}) {
     var element = node.element2;
     var typeParameters = element.typeParameters2;
     var typeArguments = node.typeArguments;
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
index b116388..8a09f40 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
@@ -22,6 +22,7 @@
 import 'package:analyzer/src/dart/ast/extensions.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/type.dart';
+import 'package:analyzer/src/dart/element/type_constraint_gatherer.dart';
 import 'package:analyzer/src/dart/element/type_schema.dart';
 import 'package:analyzer/src/dart/element/type_system.dart' show TypeSystemImpl;
 import 'package:analyzer/src/generated/inference_log.dart';
@@ -166,7 +167,8 @@
   /// the top level declaration. This is used to compute assigned variables
   /// information within the body or initializer. If `null`, the entire [node]
   /// will be visited.
-  void bodyOrInitializer_enter(AstNode node, FormalParameterList? parameters,
+  void bodyOrInitializer_enter(
+      AstNodeImpl node, FormalParameterList? parameters,
       {void Function(AstVisitor<Object?> visitor)? visit}) {
     inferenceLogWriter?.enterBodyOrInitializer(node);
     assert(flow == null);
@@ -349,7 +351,8 @@
 
   /// Computes the [AssignedVariables] map for the given [node].
   static AssignedVariables<AstNodeImpl, PromotableElementImpl2>
-      computeAssignedVariables(AstNode node, FormalParameterList? parameters,
+      computeAssignedVariables(
+          AstNodeImpl node, FormalParameterList? parameters,
           {bool retainDataForTesting = false,
           void Function(AstVisitor<Object?> visitor)? visit}) {
     AssignedVariables<AstNodeImpl, PromotableElementImpl2> assignedVariables =
@@ -508,6 +511,28 @@
   }
 
   @override
+  TypeConstraintGenerator<
+          DartType,
+          FormalParameterElementOrMember,
+          PromotableElementImpl2,
+          TypeParameterElementImpl2,
+          InterfaceTypeImpl,
+          InterfaceElementImpl2,
+          AstNodeImpl>
+      createTypeConstraintGenerator(
+          {required covariant TypeConstraintGenerationDataForTesting?
+              typeConstraintGenerationDataForTesting,
+          required List<TypeParameterElementImpl2> typeParametersToInfer,
+          required covariant TypeSystemOperations typeAnalyzerOperations,
+          required bool inferenceUsingBoundsIsEnabled}) {
+    return TypeConstraintGatherer(
+        typeParameters: typeParametersToInfer,
+        inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled,
+        typeSystemOperations: typeAnalyzerOperations,
+        dataForTesting: typeConstraintGenerationDataForTesting);
+  }
+
+  @override
   SharedTypeView<DartType> extensionTypeErasure(SharedTypeView<DartType> type) {
     return SharedTypeView(type.unwrapTypeView().extensionTypeErasure);
   }
diff --git a/pkg/analyzer/lib/src/dart/resolver/invocation_inference_helper.dart b/pkg/analyzer/lib/src/dart/resolver/invocation_inference_helper.dart
index 25d8a7c..3497b07 100644
--- a/pkg/analyzer/lib/src/dart/resolver/invocation_inference_helper.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/invocation_inference_helper.dart
@@ -120,8 +120,8 @@
   /// Given an uninstantiated generic function type, referenced by the
   /// [identifier] in the tear-off [expression], try to infer the instantiated
   /// generic function type from the surrounding context.
-  DartType inferTearOff(Expression expression, SimpleIdentifierImpl identifier,
-      DartType tearOffType,
+  DartType inferTearOff(ExpressionImpl expression,
+      SimpleIdentifierImpl identifier, DartType tearOffType,
       {required DartType contextType}) {
     if (contextType is FunctionType && tearOffType is FunctionType) {
       var typeArguments = _typeSystem.inferFunctionTypeInstantiation(
diff --git a/pkg/analyzer/lib/src/dart/resolver/named_type_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/named_type_resolver.dart
index 1a5b52f..45feb8b 100644
--- a/pkg/analyzer/lib/src/dart/resolver/named_type_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/named_type_resolver.dart
@@ -171,7 +171,7 @@
   /// [enclosingClass].
   InterfaceType _inferRedirectedConstructor(InterfaceElement element,
       {required TypeConstraintGenerationDataForTesting? dataForTesting,
-      required AstNode? nodeForTesting}) {
+      required AstNodeImpl? nodeForTesting}) {
     if (element == enclosingClass) {
       return element.thisType;
     } else {
@@ -200,7 +200,7 @@
     }
   }
 
-  DartType _instantiateElement(NamedType node, Element element,
+  DartType _instantiateElement(NamedTypeImpl node, Element element,
       {required TypeConstraintGenerationDataForTesting? dataForTesting}) {
     var nullability = _getNullability(node);
 
diff --git a/pkg/analyzer/lib/src/dart/resolver/typed_literal_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/typed_literal_resolver.dart
index a7a1f545..71cc13e 100644
--- a/pkg/analyzer/lib/src/dart/resolver/typed_literal_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/typed_literal_resolver.dart
@@ -425,7 +425,7 @@
     }
   }
 
-  GenericInferrer _inferListTypeDownwards(ListLiteral node,
+  GenericInferrer _inferListTypeDownwards(ListLiteralImpl node,
       {required DartType contextType}) {
     var element = _typeProvider.listElement2;
     var typeParameters = element.typeParameters2;
@@ -493,7 +493,7 @@
   }
 
   GenericInferrer _inferMapTypeDownwards(
-      SetOrMapLiteral node, DartType contextType) {
+      SetOrMapLiteralImpl node, DartType contextType) {
     var element = _typeProvider.mapElement2;
     inferenceLogWriter?.enterGenericInference(
         // TODO(paulberry): make this cast unnecessary by changing
@@ -601,7 +601,7 @@
   }
 
   GenericInferrer _inferSetTypeDownwards(
-      SetOrMapLiteral node, DartType contextType) {
+      SetOrMapLiteralImpl node, DartType contextType) {
     var element = _typeProvider.setElement2;
     inferenceLogWriter?.enterGenericInference(
         // TODO(paulberry): make this cast unnecessary by changing
@@ -737,7 +737,7 @@
   DartType _toMapType(
       GenericInferrer? inferrer,
       _LiteralResolution literalResolution,
-      SetOrMapLiteral node,
+      SetOrMapLiteralImpl node,
       List<_InferredCollectionElementTypeInformation> inferredTypes) {
     inferenceLogWriter?.assertGenericInferenceState(
         inProgress: inferrer != null);
@@ -787,7 +787,7 @@
   DartType _toSetType(
       GenericInferrer? inferrer,
       _LiteralResolution literalResolution,
-      SetOrMapLiteral node,
+      SetOrMapLiteralImpl node,
       List<_InferredCollectionElementTypeInformation> inferredTypes) {
     inferenceLogWriter?.assertGenericInferenceState(
         inProgress: inferrer != null);
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index d167b5d..5532133 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -2763,7 +2763,7 @@
   }
 
   @override
-  void visitFormalParameterList(FormalParameterList node) {
+  void visitFormalParameterList(covariant FormalParameterListImpl node) {
     // Formal parameter lists can contain default values, which in turn contain
     // expressions, so we need flow analysis to be available to process those
     // expressions.
@@ -4059,7 +4059,7 @@
     required AstNode errorNode,
     required DartType declaredType,
     required DartType contextType,
-    required AstNode? nodeForTesting,
+    required AstNodeImpl? nodeForTesting,
   }) {
     inferenceLogWriter?.enterGenericInference(typeParameters, declaredType);
     var inferrer = GenericInferrer(
diff --git a/pkg/analyzer/lib/src/summary2/ast_resolver.dart b/pkg/analyzer/lib/src/summary2/ast_resolver.dart
index dcdf5fe..c66daf5 100644
--- a/pkg/analyzer/lib/src/summary2/ast_resolver.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_resolver.dart
@@ -107,7 +107,7 @@
     node = getNode();
     node.accept(_scopeResolverVisitor);
     _prepareEnclosingDeclarations();
-    _flowAnalysis.bodyOrInitializer_enter(node.parent!, null);
+    _flowAnalysis.bodyOrInitializer_enter(node.parent as AstNodeImpl, null);
     _resolverVisitor.analyzeExpression(node, SharedTypeSchemaView(contextType));
     _resolverVisitor.popRewrite();
     _resolverVisitor.checkIdle();
diff --git a/pkg/front_end/lib/src/type_inference/type_constraint_gatherer.dart b/pkg/front_end/lib/src/type_inference/type_constraint_gatherer.dart
index 26b4b0f..a809b1c 100644
--- a/pkg/front_end/lib/src/type_inference/type_constraint_gatherer.dart
+++ b/pkg/front_end/lib/src/type_inference/type_constraint_gatherer.dart
@@ -186,7 +186,7 @@
     return _environment.getTypeArgumentsAsInstanceOf(type, typeDeclaration);
   }
 
-  /// Returns the set of type constraints that was gathered.
+  @override
   Map<StructuralParameter, MergedTypeConstraint> computeConstraints() {
     Map<StructuralParameter, MergedTypeConstraint> result = {};
     for (StructuralParameter parameter in typeParametersToConstrain) {
diff --git a/pkg/front_end/lib/src/type_inference/type_inference_engine.dart b/pkg/front_end/lib/src/type_inference/type_inference_engine.dart
index 5149be2..872f68d 100644
--- a/pkg/front_end/lib/src/type_inference/type_inference_engine.dart
+++ b/pkg/front_end/lib/src/type_inference/type_inference_engine.dart
@@ -7,6 +7,8 @@
 import 'package:_fe_analyzer_shared/src/type_inference/nullability_suffix.dart';
 import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.dart'
     hide Variance;
+import 'package:_fe_analyzer_shared/src/type_inference/type_constraint.dart'
+    as shared;
 import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
 import 'package:kernel/ast.dart';
 import 'package:kernel/class_hierarchy.dart'
@@ -28,11 +30,11 @@
 import '../source/source_library_builder.dart'
     show FieldNonPromotabilityInfo, SourceLibraryBuilder;
 import 'factor_type.dart';
+import 'type_constraint_gatherer.dart';
 import 'type_inferrer.dart';
 import 'type_schema.dart';
 import 'type_schema_elimination.dart' as type_schema_elimination;
-import 'type_schema_environment.dart'
-    show GeneratedTypeConstraint, TypeSchemaEnvironment;
+import 'type_schema_environment.dart' show TypeSchemaEnvironment;
 
 /// Visitor to check whether a given type mentions any of a class's type
 /// parameters in a non-covariant fashion.
@@ -392,7 +394,10 @@
   }
 }
 
-class InferenceDataForTesting {
+// TODO(cstefantsova): Merge with [TypeInferenceResultForTesting].
+class InferenceDataForTesting
+    extends shared.TypeConstraintGenerationDataForTesting<DartType,
+        StructuralParameter, VariableDeclaration, TreeNode> {
   final FlowAnalysisResult flowAnalysisResult = new FlowAnalysisResult();
 
   final TypeInferenceResultForTesting typeInferenceResult =
@@ -1010,12 +1015,31 @@
   bool isNullableInternal(DartType type) {
     return type.nullability == Nullability.nullable;
   }
+
+  @override
+  TypeConstraintGenerator<DartType, NamedType, VariableDeclaration,
+          StructuralParameter, TypeDeclarationType, TypeDeclaration, TreeNode>
+      createTypeConstraintGenerator(
+          {required covariant TypeInferenceResultForTesting?
+              typeConstraintGenerationDataForTesting,
+          required List<StructuralParameter> typeParametersToInfer,
+          required covariant OperationsCfe typeAnalyzerOperations,
+          required bool inferenceUsingBoundsIsEnabled}) {
+    // TODO(cstefantsova): Pass [typeConstraintGenerationDataForTesting] when
+    // [InferenceDataForTesting] is merged with [TypeInferenceResultForTesting].
+    return new TypeConstraintGatherer(
+        typeAnalyzerOperations.typeEnvironment as TypeSchemaEnvironment,
+        typeParametersToInfer,
+        typeOperations: typeAnalyzerOperations,
+        inferenceResultForTesting: null,
+        inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled);
+  }
 }
 
 /// Type inference results used for testing.
-class TypeInferenceResultForTesting {
+class TypeInferenceResultForTesting
+    extends shared.TypeConstraintGenerationDataForTesting<DartType,
+        StructuralParameter, VariableDeclaration, TreeNode> {
   final Map<TreeNode, List<DartType>> inferredTypeArguments = {};
-  final Map<TreeNode, List<GeneratedTypeConstraint>> generatedTypeConstraints =
-      {};
   final Map<TreeNode, DartType> inferredVariableTypes = {};
 }
diff --git a/pkg/front_end/lib/src/type_inference/type_schema_environment.dart b/pkg/front_end/lib/src/type_inference/type_schema_environment.dart
index 94d5bf1..ad068325 100644
--- a/pkg/front_end/lib/src/type_inference/type_schema_environment.dart
+++ b/pkg/front_end/lib/src/type_inference/type_schema_environment.dart
@@ -187,6 +187,7 @@
       List<DartType>? previouslyInferredTypes,
       {bool preliminary = false,
       required OperationsCfe operations,
+      required InferenceDataForTesting? dataForTesting,
       required bool inferenceUsingBoundsIsEnabled}) {
     List<DartType> inferredTypes =
         previouslyInferredTypes?.toList(growable: false) ??
@@ -197,53 +198,10 @@
 
       DartType typeParamBound = typeParam.bound;
       DartType? extendsConstraint;
-      Map<StructuralParameter, MergedTypeConstraint>? constraintsFromBound;
       if (!hasOmittedBound(typeParam)) {
         extendsConstraint = new FunctionTypeInstantiator.fromIterables(
                 typeParametersToInfer, inferredTypes)
             .substitute(typeParamBound);
-
-        MergedTypeConstraint? mergedTypeConstraint = constraints[typeParam];
-        if (inferenceUsingBoundsIsEnabled) {
-          // The type parameter's bound may refer to itself (or other type
-          // parameters), so we might have to create an additional constraint.
-          // Consider this example from
-          // https://github.com/dart-lang/language/issues/3009:
-          //
-          //     class A<X extends A<X>> {}
-          //     class B extends A<B> {}
-          //     class C extends B {}
-          //     void f<X extends A<X>>(X x) {}
-          //     void main() {
-          //       f(C()); // should infer f<B>(C()).
-          //     }
-          //
-          // In order for `f(C())` to be inferred as `f<B>(C())`, we need to
-          // generate the constraint `X <: B`. To do this, we first take the
-          // lower constraint we've accumulated so far (which, in this example,
-          // is `C`, due to the presence of the actual argument `C()`), and use
-          // subtype constraint generation to match it against the explicit
-          // bound (which is `A<X>`; hence we perform `C <# A<X>`). If this
-          // produces any constraints (i.e. `X <: B` in this example), then they
-          // are added to the set of constraints just before choosing the final
-          // type.
-
-          if (mergedTypeConstraint != null &&
-              mergedTypeConstraint.lower
-                  is! SharedUnknownTypeSchemaView<DartType>) {
-            TypeConstraintGatherer extendsConstraintGatherer =
-                new TypeConstraintGatherer(this, typeParametersToInfer,
-                    typeOperations: operations,
-                    inferenceUsingBoundsIsEnabled:
-                        inferenceUsingBoundsIsEnabled,
-                    inferenceResultForTesting: null);
-            extendsConstraintGatherer.tryConstrainLower(typeParamBound,
-                mergedTypeConstraint.lower.unwrapTypeSchemaView(),
-                treeNodeForTesting: null);
-            constraintsFromBound =
-                extendsConstraintGatherer.computeConstraints();
-          }
-        }
       }
 
       MergedTypeConstraint constraint = constraints[typeParam]!;
@@ -252,10 +210,11 @@
             previouslyInferredTypes?[i], constraint, extendsConstraint,
             isLegacyCovariant: typeParam.isLegacyCovariant,
             operations: operations,
-            constraintsFromBound: constraintsFromBound,
             constraints: constraints,
             typeParameterToInfer: typeParam,
-            typeParametersToInfer: typeParametersToInfer);
+            typeParametersToInfer: typeParametersToInfer,
+            dataForTesting: dataForTesting,
+            inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled);
       } else {
         inferredTypes[i] = _inferTypeParameterFromAll(
             previouslyInferredTypes?[i], constraint, extendsConstraint,
@@ -263,10 +222,11 @@
                 typeParam.variance == shared.Variance.contravariant,
             isLegacyCovariant: typeParam.isLegacyCovariant,
             operations: operations,
-            constraintsFromBound: constraintsFromBound,
             constraints: constraints,
             typeParameterToInfer: typeParam,
-            typeParametersToInfer: typeParametersToInfer);
+            typeParametersToInfer: typeParametersToInfer,
+            dataForTesting: dataForTesting,
+            inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled);
       }
     }
 
@@ -490,6 +450,7 @@
         previouslyInferredTypes,
         preliminary: preliminary,
         operations: gatherer.typeOperations,
+        dataForTesting: dataForTesting,
         inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled);
 
     for (int i = 0; i < inferredTypes.length; i++) {
@@ -503,11 +464,11 @@
       {bool isContravariant = false,
       bool isLegacyCovariant = true,
       required OperationsCfe operations,
-      required Map<StructuralParameter, MergedTypeConstraint>?
-          constraintsFromBound,
       required Map<StructuralParameter, MergedTypeConstraint> constraints,
       required StructuralParameter typeParameterToInfer,
-      required List<StructuralParameter> typeParametersToInfer}) {
+      required List<StructuralParameter> typeParametersToInfer,
+      required InferenceDataForTesting? dataForTesting,
+      required bool inferenceUsingBoundsIsEnabled}) {
     // See if we already fixed this type in a previous inference step.
     // If so, then we aren't allowed to change it unless [isLegacyCovariant] is
     // false.
@@ -517,13 +478,21 @@
       return typeFromPreviousInference;
     }
 
-    if (constraintsFromBound != null) {
-      _mergeInConstraintsFromBound(
-          constraints: constraints,
-          constraintsFromBound: constraintsFromBound,
-          typeParametersToInfer: typeParametersToInfer,
-          operations: operations);
-      constraint = constraints[typeParameterToInfer]!;
+    if (inferenceUsingBoundsIsEnabled &&
+        constraint.lower is! SharedUnknownTypeSchemaView<DartType> &&
+        !hasOmittedBound(typeParameterToInfer)) {
+      // Coverage-ignore-block(suite): Not run.
+      MergedTypeConstraint constraintFromBound =
+          operations.mergeInConstraintsFromBound(
+              typeParameterToInfer: typeParameterToInfer,
+              typeParametersToInfer: typeParametersToInfer,
+              lower: constraint.lower.unwrapTypeSchemaView(),
+              inferencePhaseConstraints: constraints,
+              dataForTesting: dataForTesting,
+              inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled);
+
+      constraint.mergeInTypeSchemaUpper(constraintFromBound.upper, operations);
+      constraint.mergeInTypeSchemaLower(constraintFromBound.lower, operations);
     }
 
     if (extendsConstraint != null) {
@@ -538,33 +507,15 @@
         isContravariant: isContravariant);
   }
 
-  void _mergeInConstraintsFromBound(
-      {required Map<StructuralParameter, MergedTypeConstraint>
-          constraintsFromBound,
-      required List<StructuralParameter> typeParametersToInfer,
-      required Map<StructuralParameter, MergedTypeConstraint> constraints,
-      required OperationsCfe operations}) {
-    for (StructuralParameter typeParamToUpdate in typeParametersToInfer) {
-      MergedTypeConstraint? constraintFromBoundForTypeParam =
-          constraintsFromBound[typeParamToUpdate];
-      if (constraintFromBoundForTypeParam != null) {
-        constraints[typeParamToUpdate]?.mergeInTypeSchemaUpper(
-            constraintFromBoundForTypeParam.upper, operations);
-        constraints[typeParamToUpdate]?.mergeInTypeSchemaLower(
-            constraintFromBoundForTypeParam.lower, operations);
-      }
-    }
-  }
-
   DartType _inferTypeParameterFromContext(DartType? typeFromPreviousInference,
       MergedTypeConstraint constraint, DartType? extendsConstraint,
       {bool isLegacyCovariant = true,
       required OperationsCfe operations,
-      required Map<StructuralParameter, MergedTypeConstraint>?
-          constraintsFromBound,
       required Map<StructuralParameter, MergedTypeConstraint> constraints,
       required List<StructuralParameter> typeParametersToInfer,
-      required StructuralParameter typeParameterToInfer}) {
+      required StructuralParameter typeParameterToInfer,
+      required InferenceDataForTesting? dataForTesting,
+      required bool inferenceUsingBoundsIsEnabled}) {
     // See if we already fixed this type in a previous inference step.
     // If so, then we aren't allowed to change it unless [isLegacyCovariant] is
     // false.
@@ -588,14 +539,21 @@
     //
     // If we consider the `T extends num` we conclude `<num>`, which works.
 
-    if (constraintsFromBound != null) {
+    if (inferenceUsingBoundsIsEnabled &&
+        constraint.lower is! SharedUnknownTypeSchemaView<DartType> &&
+        !hasOmittedBound(typeParameterToInfer)) {
       // Coverage-ignore-block(suite): Not run.
-      _mergeInConstraintsFromBound(
-          constraintsFromBound: constraintsFromBound,
-          typeParametersToInfer: typeParametersToInfer,
-          constraints: constraints,
-          operations: operations);
-      constraint = constraints[typeParameterToInfer]!;
+      MergedTypeConstraint constraintFromBound =
+          operations.mergeInConstraintsFromBound(
+              typeParameterToInfer: typeParameterToInfer,
+              typeParametersToInfer: typeParametersToInfer,
+              lower: constraint.lower.unwrapTypeSchemaView(),
+              inferencePhaseConstraints: constraints,
+              dataForTesting: dataForTesting,
+              inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled);
+
+      constraint.mergeInTypeSchemaUpper(constraintFromBound.upper, operations);
+      constraint.mergeInTypeSchemaLower(constraintFromBound.lower, operations);
     }
 
     if (extendsConstraint != null) {
diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt
index 4f6203e..863a5b0 100644
--- a/pkg/front_end/test/spell_checking_list_common.txt
+++ b/pkg/front_end/test/spell_checking_list_common.txt
@@ -105,6 +105,7 @@
 allocated
 allocating
 allocation
+allocations
 allow
 allowed
 allowing
diff --git a/pkg/front_end/test/type_inference/type_schema_environment_test_base.dart b/pkg/front_end/test/type_inference/type_schema_environment_test_base.dart
index 6af484d..b63208c 100644
--- a/pkg/front_end/test/type_inference/type_schema_environment_test_base.dart
+++ b/pkg/front_end/test/type_inference/type_schema_environment_test_base.dart
@@ -230,6 +230,7 @@
           inferredTypeNodes,
           preliminary: downwardsInferPhase,
           inferenceUsingBoundsIsEnabled: true,
+          dataForTesting: null,
           operations: _operations);
 
       expect(inferredTypeNodes.single, expectedTypeNode);