[cfe] Add _ImplicitType to handle field and enum element inference

Change-Id: I00bb0817c6504de0f970d58abfaac294c16209e9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/405003
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/fragment/enum_element.dart b/pkg/front_end/lib/src/fragment/enum_element.dart
index 9303fa0..3f9301c 100644
--- a/pkg/front_end/lib/src/fragment/enum_element.dart
+++ b/pkg/front_end/lib/src/fragment/enum_element.dart
@@ -20,12 +20,22 @@
   final Uri fileUri;
 
   final ConstructorReferenceBuilder? constructorReferenceBuilder;
-  Token? argumentsBeginToken;
+  Token? _argumentsBeginToken;
 
   SourcePropertyBuilder? _builder;
 
   Field? _field;
-  late DartType _type = new InferredType.fromEnumElementInitializer(this);
+
+  late DartType _type = new InferredType(
+      libraryBuilder: builder.libraryBuilder,
+      typeBuilder: type,
+      inferType: inferType,
+      computeType: _computeType,
+      fileUri: fileUri,
+      name: name,
+      nameOffset: nameOffset,
+      nameLength: name.length,
+      token: argumentsBeginToken);
 
   late final int elementIndex;
 
@@ -38,7 +48,8 @@
       required this.nameOffset,
       required this.fileUri,
       required this.constructorReferenceBuilder,
-      required this.argumentsBeginToken});
+      required Token? argumentsBeginToken})
+      : _argumentsBeginToken = argumentsBeginToken;
 
   @override
   SourcePropertyBuilder get builder {
@@ -53,6 +64,29 @@
     type.registerInferredTypeListener(this);
   }
 
+  /// Returns the token for begin of the constructor arguments of this enum
+  /// element, if any.
+  ///
+  /// This can only be called once and will hand over the responsibility of
+  /// the token to the caller.
+  Token? get argumentsBeginToken {
+    Token? token = _argumentsBeginToken;
+    _argumentsBeginToken = null;
+    return token;
+  }
+
+  DartType _computeType(ClassHierarchyBase hierarchy, Token? token) {
+    SourceLibraryBuilder libraryBuilder = builder.libraryBuilder;
+    SourceEnumBuilder sourceEnumBuilder =
+        builder.declarationBuilder as SourceEnumBuilder;
+    _buildElement(
+        sourceEnumBuilder,
+        sourceEnumBuilder.selfType.build(libraryBuilder, TypeUse.enumSelfType),
+        libraryBuilder.loader.coreTypes,
+        token);
+    return fieldType;
+  }
+
   @override
   bool get isEnumElement => true;
 
@@ -125,8 +159,8 @@
     f(member: _field!, kind: BuiltMemberKind.Field);
   }
 
-  void buildElement(SourceEnumBuilder sourceEnumBuilder, DartType selfType,
-      CoreTypes coreTypes) {
+  void _buildElement(SourceEnumBuilder sourceEnumBuilder, DartType selfType,
+      CoreTypes coreTypes, Token? token) {
     SourceLibraryBuilder libraryBuilder = sourceEnumBuilder.libraryBuilder;
     DartType inferredFieldType = selfType;
 
@@ -174,8 +208,8 @@
               fileUri);
       bodyBuilder.constantContext = ConstantContext.inferred;
 
-      if (argumentsBeginToken != null) {
-        arguments = bodyBuilder.parseArguments(argumentsBeginToken!);
+      if (token != null) {
+        arguments = bodyBuilder.parseArguments(token);
         // We pass `true` for [allowFurtherDelays] here because the members of
         // the enums are built before the inference, and the resolution of the
         // redirecting factories can't be completed at this moment and
@@ -185,7 +219,6 @@
 
         arguments.positional.insertAll(0, enumSyntheticArguments);
         arguments.argumentsOriginalOrder?.insertAll(0, enumSyntheticArguments);
-        argumentsBeginToken = null;
       } else {
         arguments = new ArgumentsImpl(enumSyntheticArguments);
       }
diff --git a/pkg/front_end/lib/src/fragment/field.dart b/pkg/front_end/lib/src/fragment/field.dart
index b4c46ca..5b9f2a0 100644
--- a/pkg/front_end/lib/src/fragment/field.dart
+++ b/pkg/front_end/lib/src/fragment/field.dart
@@ -66,6 +66,10 @@
     }
   }
 
+  /// Returns the token for the initializer of this field, if any.
+  ///
+  /// This can only be called once and will hand over the responsibility of
+  /// the token to the caller.
   Token? get initializerToken {
     Token? result = _initializerToken;
     // Ensure that we don't hold onto the token.
@@ -73,7 +77,12 @@
     return result;
   }
 
-  // Coverage-ignore(suite): Not run.
+  /// Returns the token for the initializer of this field, if any. This is the
+  /// same as [initializerToken] but is used to signal that the initializer
+  /// needs to be computed for outline expressions.
+  ///
+  /// This can only be called once and will hand over the responsibility of
+  /// the token to the caller.
   Token? get constInitializerToken {
     Token? result = _constInitializerToken;
     // Ensure that we don't hold onto the token.
@@ -173,13 +182,63 @@
       } else {
         // A field with no type and initializer or an instance field without
         // type and initializer need to have the type inferred.
-        _encoding.type =
-            new InferredType.fromFieldFragmentInitializer(this, token);
+        _encoding.type = new InferredType(
+            libraryBuilder: libraryBuilder,
+            typeBuilder: type,
+            inferType: inferType,
+            computeType: _computeInferredType,
+            fileUri: fileUri,
+            name: name,
+            nameOffset: nameOffset,
+            nameLength: name.length,
+            token: token);
         type.registerInferable(this);
       }
     }
   }
 
+  DartType _computeInferredType(
+      ClassHierarchyBase classHierarchy, Token? token) {
+    DartType? inferredType;
+    SourceLibraryBuilder libraryBuilder = builder.libraryBuilder;
+    DeclarationBuilder? declarationBuilder = builder.declarationBuilder;
+    if (token != null) {
+      InterfaceType? enclosingClassThisType = declarationBuilder
+              is SourceClassBuilder
+          ? libraryBuilder.loader.typeInferenceEngine.coreTypes
+              .thisInterfaceType(
+                  declarationBuilder.cls, libraryBuilder.library.nonNullable)
+          : null;
+      TypeInferrer typeInferrer =
+          libraryBuilder.loader.typeInferenceEngine.createTopLevelTypeInferrer(
+              fileUri,
+              enclosingClassThisType,
+              libraryBuilder,
+              builder
+                  .dataForTesting
+                  // Coverage-ignore(suite): Not run.
+                  ?.inferenceData);
+      BodyBuilderContext bodyBuilderContext = createBodyBuilderContext();
+      BodyBuilder bodyBuilder = libraryBuilder.loader.createBodyBuilderForField(
+          libraryBuilder,
+          bodyBuilderContext,
+          declarationBuilder?.scope ?? libraryBuilder.scope,
+          typeInferrer,
+          fileUri);
+      bodyBuilder.constantContext =
+          modifiers.isConst ? ConstantContext.inferred : ConstantContext.none;
+      bodyBuilder.inFieldInitializer = true;
+      bodyBuilder.inLateFieldInitializer = modifiers.isLate;
+      Expression initializer = bodyBuilder.parseFieldInitializer(token);
+
+      inferredType =
+          typeInferrer.inferImplicitFieldType(bodyBuilder, initializer);
+    } else {
+      inferredType = const DynamicType();
+    }
+    return inferredType;
+  }
+
   @override
   bool get isEnumElement => false;
 
@@ -240,13 +299,13 @@
     // For modular compilation we need to include initializers of all const
     // fields and all non-static final fields in classes with const constructors
     // into the outline.
+    Token? token = constInitializerToken;
     if ((modifiers.isConst ||
             (isFinal &&
                 isClassInstanceMember &&
                 (declarationBuilder as SourceClassBuilder)
                     .declaresConstConstructor)) &&
-        _constInitializerToken != null) {
-      Token initializerToken = _constInitializerToken!;
+        token != null) {
       LookupScope scope = declarationBuilder?.scope ?? libraryBuilder.scope;
       BodyBuilder bodyBuilder = libraryBuilder.loader
           .createBodyBuilderForOutlineExpression(
@@ -255,18 +314,17 @@
           ? ConstantContext.inferred
           : ConstantContext.required;
       Expression initializer = bodyBuilder.typeInferrer
-          .inferFieldInitializer(bodyBuilder, fieldType,
-              bodyBuilder.parseFieldInitializer(initializerToken))
+          .inferFieldInitializer(
+              bodyBuilder, fieldType, bodyBuilder.parseFieldInitializer(token))
           .expression;
       buildBody(classHierarchy.coreTypes, initializer);
       bodyBuilder.performBacklogComputations();
       if (computeSharedExpressionForTesting) {
         // Coverage-ignore-block(suite): Not run.
         _initializerExpression = parseFieldInitializer(libraryBuilder.loader,
-            initializerToken, libraryBuilder.importUri, fileUri, scope);
+            token, libraryBuilder.importUri, fileUri, scope);
       }
     }
-    _constInitializerToken = null;
   }
 
   @override
diff --git a/pkg/front_end/lib/src/fragment/fragment.dart b/pkg/front_end/lib/src/fragment/fragment.dart
index 4eda6be..41515c6 100644
--- a/pkg/front_end/lib/src/fragment/fragment.dart
+++ b/pkg/front_end/lib/src/fragment/fragment.dart
@@ -58,6 +58,7 @@
 import '../source/type_parameter_scope_builder.dart';
 import '../type_inference/inference_results.dart';
 import '../type_inference/type_inference_engine.dart';
+import '../type_inference/type_inferrer.dart';
 import '../type_inference/type_schema.dart';
 
 part 'class.dart';
diff --git a/pkg/front_end/lib/src/kernel/implicit_field_type.dart b/pkg/front_end/lib/src/kernel/implicit_field_type.dart
index 08f6420..c0ccb6a 100644
--- a/pkg/front_end/lib/src/kernel/implicit_field_type.dart
+++ b/pkg/front_end/lib/src/kernel/implicit_field_type.dart
@@ -10,13 +10,9 @@
 
 import '../base/constant_context.dart';
 import '../base/problems.dart' show unsupported;
-import '../builder/declaration_builders.dart';
 import '../builder/inferable_type_builder.dart';
 import '../builder/type_builder.dart';
 import '../codes/cfe_codes.dart';
-import '../fragment/fragment.dart';
-import '../source/source_class_builder.dart';
-import '../source/source_enum_builder.dart';
 import '../source/source_field_builder.dart';
 import '../source/source_library_builder.dart';
 import '../type_inference/type_inferrer.dart';
@@ -33,13 +29,16 @@
           SourceFieldBuilder fieldBuilder, Token? initializerToken) =
       _ImplicitFieldTypeRoot;
 
-  factory InferredType.fromFieldFragmentInitializer(
-          FieldFragment fieldFragment, Token? initializerToken) =
-      _ImplicitFieldFragmentTypeRoot;
-
-  factory InferredType.fromEnumElementInitializer(
-          EnumElementFragment enumElementFragment) =
-      _ImplicitEnumElementFragmentType;
+  factory InferredType(
+      {required SourceLibraryBuilder libraryBuilder,
+      required TypeBuilder typeBuilder,
+      required InferTypeFunction inferType,
+      required ComputeTypeFunction computeType,
+      required Uri fileUri,
+      required String name,
+      required int nameOffset,
+      required int nameLength,
+      required Token? token}) = _ImplicitType;
 
   factory InferredType.fromInferableTypeUse(InferableTypeUse inferableTypeUse) =
       _InferredTypeUse;
@@ -196,174 +195,99 @@
   String toString() => 'ImplicitFieldType(${toStringInternal()})';
 }
 
-class _ImplicitFieldFragmentTypeRoot extends InferredType {
-  final FieldFragment _fieldFragment;
+/// Signature for function called to trigger the inference of the type of
+/// [_ImplicitType], if it hasn't already been computed.
+typedef InferTypeFunction = DartType Function(ClassHierarchyBase hierarchy);
 
-  Token? initializerToken;
+/// Signature for function called to compute the type for [_ImplicitType]
+typedef ComputeTypeFunction = DartType Function(
+    ClassHierarchyBase hierarchy, Token? token);
+
+/// [InferredType] implementation that infers the type of [_typeBuilder] using
+/// [_computeType] and [_token].
+class _ImplicitType extends InferredType {
+  final SourceLibraryBuilder _libraryBuilder;
+  final TypeBuilder _typeBuilder;
+  final InferTypeFunction _inferType;
+  final ComputeTypeFunction _computeType;
+  final Uri _fileUri;
+  final String _name;
+  final int _nameOffset;
+  final int _nameLength;
+  Token? _token;
+
   bool isStarted = false;
 
-  _ImplicitFieldFragmentTypeRoot(this._fieldFragment, this.initializerToken)
-      : super._();
+  _ImplicitType(
+      {required SourceLibraryBuilder libraryBuilder,
+      required TypeBuilder typeBuilder,
+      required InferTypeFunction inferType,
+      required ComputeTypeFunction computeType,
+      required Uri fileUri,
+      required String name,
+      required int nameOffset,
+      required int nameLength,
+      required Token? token})
+      : _libraryBuilder = libraryBuilder,
+        _typeBuilder = typeBuilder,
+        _inferType = inferType,
+        _computeType = computeType,
+        _fileUri = fileUri,
+        _name = name,
+        _nameOffset = nameOffset,
+        _nameLength = nameLength,
+        _token = token,
+        super._();
 
   @override
   // Coverage-ignore(suite): Not run.
-  Uri get fileUri => _fieldFragment.fileUri;
+  Uri get fileUri => _fileUri;
 
   @override
   // Coverage-ignore(suite): Not run.
-  int get charOffset => _fieldFragment.nameOffset;
+  int get charOffset => _nameOffset;
 
   @override
   DartType inferType(ClassHierarchyBase hierarchy) {
-    return _fieldFragment.inferType(hierarchy);
+    return _inferType(hierarchy);
   }
 
   @override
   DartType computeType(ClassHierarchyBase hierarchy) {
     if (isStarted) {
-      _fieldFragment.builder.libraryBuilder.addProblem(
-          templateCantInferTypeDueToCircularity
-              .withArguments(_fieldFragment.name),
-          _fieldFragment.nameOffset,
-          _fieldFragment.name.length,
-          _fieldFragment.fileUri);
+      _libraryBuilder.addProblem(
+          templateCantInferTypeDueToCircularity.withArguments(_name),
+          _nameOffset,
+          _nameLength,
+          _fileUri);
       DartType type = const InvalidType();
-      _fieldFragment.type.registerInferredType(type);
+      _typeBuilder.registerInferredType(type);
       return type;
     }
     isStarted = true;
-    DartType? inferredType;
-    SourceLibraryBuilder libraryBuilder = _fieldFragment.builder.libraryBuilder;
-    DeclarationBuilder? declarationBuilder =
-        _fieldFragment.builder.declarationBuilder;
-    if (initializerToken != null) {
-      InterfaceType? enclosingClassThisType = declarationBuilder
-              is SourceClassBuilder
-          ? libraryBuilder.loader.typeInferenceEngine.coreTypes
-              .thisInterfaceType(
-                  declarationBuilder.cls, libraryBuilder.library.nonNullable)
-          : null;
-      TypeInferrer typeInferrer =
-          libraryBuilder.loader.typeInferenceEngine.createTopLevelTypeInferrer(
-              _fieldFragment.fileUri,
-              enclosingClassThisType,
-              libraryBuilder,
-              _fieldFragment
-                  .builder
-                  .dataForTesting
-                  // Coverage-ignore(suite): Not run.
-                  ?.inferenceData);
-      BodyBuilderContext bodyBuilderContext =
-          _fieldFragment.createBodyBuilderContext();
-      BodyBuilder bodyBuilder = libraryBuilder.loader.createBodyBuilderForField(
-          libraryBuilder,
-          bodyBuilderContext,
-          declarationBuilder?.scope ?? libraryBuilder.scope,
-          typeInferrer,
-          _fieldFragment.fileUri);
-      bodyBuilder.constantContext = _fieldFragment.modifiers.isConst
-          ? ConstantContext.inferred
-          : ConstantContext.none;
-      bodyBuilder.inFieldInitializer = true;
-      bodyBuilder.inLateFieldInitializer = _fieldFragment.modifiers.isLate;
-      Expression initializer =
-          bodyBuilder.parseFieldInitializer(initializerToken!);
-      initializerToken = null;
-
-      inferredType =
-          typeInferrer.inferImplicitFieldType(bodyBuilder, initializer);
-    } else {
-      inferredType = const DynamicType();
-    }
-    return inferredType;
+    Token? token = _token;
+    _token = null;
+    return _computeType(hierarchy, token);
   }
 
   @override
   // Coverage-ignore(suite): Not run.
   void toTextInternal(AstPrinter printer) {
-    printer.write('<implicit-field-type:$_fieldFragment>');
+    printer.write('<implicit-type:$_name>');
   }
 
   @override
   // Coverage-ignore(suite): Not run.
   bool equals(Object other, Assumptions? assumptions) {
     if (identical(this, other)) return true;
-    return other is _ImplicitFieldFragmentTypeRoot &&
-        _fieldFragment == other._fieldFragment;
+    return other is _ImplicitType && _typeBuilder == other._typeBuilder;
   }
 
   @override
-  int get hashCode => _fieldFragment.hashCode;
+  int get hashCode => _typeBuilder.hashCode;
 
   @override
-  String toString() => 'ImplicitFieldType(${toStringInternal()})';
-}
-
-class _ImplicitEnumElementFragmentType extends InferredType {
-  final EnumElementFragment _enumElementFragment;
-
-  bool isStarted = false;
-
-  _ImplicitEnumElementFragmentType(this._enumElementFragment) : super._();
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  Uri get fileUri => _enumElementFragment.fileUri;
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  int get charOffset => _enumElementFragment.nameOffset;
-
-  @override
-  DartType inferType(ClassHierarchyBase hierarchy) {
-    return _enumElementFragment.inferType(hierarchy);
-  }
-
-  @override
-  DartType computeType(ClassHierarchyBase hierarchy) {
-    if (isStarted) {
-      // Coverage-ignore-block(suite): Not run.
-      _enumElementFragment.builder.libraryBuilder.addProblem(
-          templateCantInferTypeDueToCircularity
-              .withArguments(_enumElementFragment.name),
-          _enumElementFragment.nameOffset,
-          _enumElementFragment.name.length,
-          _enumElementFragment.fileUri);
-      DartType type = const InvalidType();
-      _enumElementFragment.type.registerInferredType(type);
-      return type;
-    }
-    isStarted = true;
-    SourceLibraryBuilder libraryBuilder =
-        _enumElementFragment.builder.libraryBuilder;
-    SourceEnumBuilder sourceEnumBuilder =
-        _enumElementFragment.builder.declarationBuilder as SourceEnumBuilder;
-    _enumElementFragment.buildElement(
-        sourceEnumBuilder,
-        sourceEnumBuilder.selfType.build(libraryBuilder, TypeUse.enumSelfType),
-        libraryBuilder.loader.coreTypes);
-    return _enumElementFragment.fieldType;
-  }
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  void toTextInternal(AstPrinter printer) {
-    printer.write('<implicit-field-type:$_enumElementFragment>');
-  }
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  bool equals(Object other, Assumptions? assumptions) {
-    if (identical(this, other)) return true;
-    return other is _ImplicitEnumElementFragmentType &&
-        _enumElementFragment == other._enumElementFragment;
-  }
-
-  @override
-  int get hashCode => _enumElementFragment.hashCode;
-
-  @override
-  String toString() => 'ImplicitFieldType(${toStringInternal()})';
+  String toString() => '_ImplicitType(${toStringInternal()})';
 }
 
 class _InferredTypeUse extends InferredType {
diff --git a/pkg/front_end/test/coverage_suite_expected.dart b/pkg/front_end/test/coverage_suite_expected.dart
index b5b6fc4..7962768 100644
--- a/pkg/front_end/test/coverage_suite_expected.dart
+++ b/pkg/front_end/test/coverage_suite_expected.dart
@@ -490,7 +490,7 @@
   ),
   // 100.0%.
   "package:front_end/src/fragment/enum_element.dart": (
-    hitCount: 224,
+    hitCount: 242,
     missCount: 0,
   ),
   // 100.0%.
@@ -510,7 +510,7 @@
   ),
   // 100.0%.
   "package:front_end/src/fragment/field.dart": (
-    hitCount: 259,
+    hitCount: 302,
     missCount: 0,
   ),
   // 100.0%.
@@ -700,7 +700,7 @@
   ),
   // 100.0%.
   "package:front_end/src/kernel/implicit_field_type.dart": (
-    hitCount: 93,
+    hitCount: 25,
     missCount: 0,
   ),
   // 100.0%.
@@ -925,7 +925,7 @@
   ),
   // 100.0%.
   "package:front_end/src/source/source_enum_builder.dart": (
-    hitCount: 335,
+    hitCount: 331,
     missCount: 0,
   ),
   // 100.0%.
@@ -961,7 +961,7 @@
   ),
   // 100.0%.
   "package:front_end/src/source/source_loader.dart": (
-    hitCount: 1868,
+    hitCount: 1855,
     missCount: 0,
   ),
   // 100.0%.