[cfe] Update inferred types through listeners

This CL changes the way inferred types are updated in the AST. Instead
of updating the AST directly, builders register a listener with the
OmittedTypeBuilder, that will be called when the inferred type has been
computed. This prepares for propagating inferred types to macro types
that are references to omitted types.

Change-Id: Ica5e1ed259b552d5de24000235f114ab1b9eb682
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/246642
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/front_end/lib/src/fasta/builder/formal_parameter_builder.dart b/pkg/front_end/lib/src/fasta/builder/formal_parameter_builder.dart
index fe77f2d..1e2c3e9 100644
--- a/pkg/front_end/lib/src/fasta/builder/formal_parameter_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/formal_parameter_builder.dart
@@ -55,7 +55,7 @@
 /// A builder for a formal parameter, i.e. a parameter on a method or
 /// constructor.
 class FormalParameterBuilder extends ModifierBuilderImpl
-    implements VariableBuilder, ParameterBuilder {
+    implements VariableBuilder, ParameterBuilder, InferredTypeListener {
   static const String noNameSentinel = 'no name sentinel';
 
   /// List of metadata builders for the metadata declared on this parameter.
@@ -98,7 +98,9 @@
       this.name, LibraryBuilder? compilationUnit, int charOffset,
       {Uri? fileUri, this.isExtensionThis: false})
       : this.fileUri = fileUri ?? compilationUnit?.fileUri,
-        super(compilationUnit, charOffset);
+        super(compilationUnit, charOffset) {
+    type.registerInferredTypeListener(this);
+  }
 
   @override
   String get debugName => "FormalParameterBuilder";
@@ -166,6 +168,13 @@
   }
 
   @override
+  void onInferredType(DartType type) {
+    if (variable != null) {
+      variable!.type = type;
+    }
+  }
+
+  @override
   ParameterBuilder clone(
       List<NamedTypeBuilder> newTypes,
       SourceLibraryBuilder contextLibrary,
@@ -213,9 +222,9 @@
   void finalizeInitializingFormal(ClassBuilder classBuilder) {
     Builder? fieldBuilder = classBuilder.lookupLocalMember(name);
     if (fieldBuilder is SourceFieldBuilder) {
-      variable!.type = fieldBuilder.inferType();
+      type.registerInferredType(fieldBuilder.inferType());
     } else {
-      variable!.type = const DynamicType();
+      type.registerInferredType(const DynamicType());
     }
   }
 
diff --git a/pkg/front_end/lib/src/fasta/builder/omitted_type_builder.dart b/pkg/front_end/lib/src/fasta/builder/omitted_type_builder.dart
index 3cfc546..5ffdf21 100644
--- a/pkg/front_end/lib/src/fasta/builder/omitted_type_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/omitted_type_builder.dart
@@ -11,8 +11,6 @@
 import 'type_builder.dart';
 
 class OmittedTypeBuilder extends TypeBuilder {
-  const OmittedTypeBuilder();
-
   @override
   DartType build(LibraryBuilder library, TypeUse typeUse) {
     throw new UnsupportedError('$runtimeType.build');
@@ -67,4 +65,46 @@
   TypeBuilder withNullabilityBuilder(NullabilityBuilder nullabilityBuilder) {
     return this;
   }
+
+  bool get hasType => _type != null;
+
+  DartType? _type;
+
+  DartType get type => _type!;
+
+  List<InferredTypeListener>? _listeners;
+
+  @override
+  void registerInferredTypeListener(InferredTypeListener onType) {
+    if (hasType) {
+      onType.onInferredType(type);
+    } else {
+      (_listeners ??= []).add(onType);
+    }
+  }
+
+  void _registerType(DartType type) {
+    // TODO(johnniwinther): Avoid multiple registration from enums and
+    //  duplicated fields.
+    if (_type == null) {
+      _type = type;
+      List<InferredTypeListener>? listeners = _listeners;
+      if (listeners != null) {
+        _listeners = null;
+        for (InferredTypeListener listener in listeners) {
+          listener.onInferredType(type);
+        }
+      }
+    }
+  }
+
+  @override
+  void registerInferredType(DartType type) {
+    _registerType(type);
+  }
+}
+
+/// Listener for the late computation of an inferred type.
+abstract class InferredTypeListener {
+  void onInferredType(DartType type);
 }
diff --git a/pkg/front_end/lib/src/fasta/builder/type_builder.dart b/pkg/front_end/lib/src/fasta/builder/type_builder.dart
index 4582b5c..b694a5f 100644
--- a/pkg/front_end/lib/src/fasta/builder/type_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/type_builder.dart
@@ -10,6 +10,7 @@
 import 'library_builder.dart';
 import 'named_type_builder.dart';
 import 'nullability_builder.dart';
+import 'omitted_type_builder.dart';
 import 'type_declaration_builder.dart';
 import 'type_variable_builder.dart';
 
@@ -325,4 +326,10 @@
   TypeBuilder withNullabilityBuilder(NullabilityBuilder nullabilityBuilder);
 
   bool get isVoidType;
+
+  void registerInferredType(DartType type) {
+    throw new UnsupportedError("${runtimeType}.registerInferredType");
+  }
+
+  void registerInferredTypeListener(InferredTypeListener listener) {}
 }
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index c75580a..dfc641f 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -1754,7 +1754,7 @@
                     /* metadata = */ null,
                     FormalParameterKind.requiredPositional,
                     /* modifiers = */ 0,
-                    const OmittedTypeBuilder(),
+                    libraryBuilder.addInferableType(),
                     formal.name!,
                     libraryBuilder,
                     formal.fileOffset,
@@ -4279,7 +4279,7 @@
       }
     }
     TypeBuilder type = formals.toFunctionType(
-        returnType ?? const OmittedTypeBuilder(),
+        returnType ?? libraryBuilder.addInferableType(),
         libraryBuilder.nullableBuilderIfTrue(questionMark != null),
         typeVariables);
     exitLocalScope();
@@ -4486,7 +4486,7 @@
           null,
           kind,
           modifiers,
-          type ?? const OmittedTypeBuilder(),
+          type ?? libraryBuilder.addInferableType(),
           name?.name ?? '',
           libraryBuilder,
           offsetForToken(nameToken),
@@ -4563,7 +4563,7 @@
       reportErrorIfNullableType(question);
     }
     TypeBuilder type = formals.toFunctionType(
-        returnType ?? const OmittedTypeBuilder(),
+        returnType ?? libraryBuilder.addInferableType(),
         libraryBuilder.nullableBuilderIfTrue(question != null),
         typeVariables);
     exitLocalScope();
diff --git a/pkg/front_end/lib/src/fasta/kernel/hierarchy/members_node.dart b/pkg/front_end/lib/src/fasta/kernel/hierarchy/members_node.dart
index 3b0f111..1c689cc 100644
--- a/pkg/front_end/lib/src/fasta/kernel/hierarchy/members_node.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/hierarchy/members_node.dart
@@ -191,7 +191,7 @@
 
       if (declaredMember.returnType is OmittedTypeBuilder) {
         inferredReturnType ??= const DynamicType();
-        declaredFunction.returnType = inferredReturnType;
+        declaredMember.returnType.registerInferredType(inferredReturnType);
       }
       if (declaredMember.formals != null) {
         for (FormalParameterBuilder declaredParameter
@@ -200,7 +200,7 @@
             DartType inferredParameterType =
                 inferredParameterTypes[declaredParameter] ??
                     const DynamicType();
-            declaredParameter.variable!.type = inferredParameterType;
+            declaredParameter.type.registerInferredType(inferredParameterType);
           }
         }
       }
@@ -299,8 +299,8 @@
         inferFrom(overriddenSetters, forSetter: true);
       }
 
-      declaredMember.procedure.function.returnType =
-          inferredType ?? const DynamicType();
+      declaredMember.returnType
+          .registerInferredType(inferredType ?? const DynamicType());
     }
   }
 
@@ -364,7 +364,7 @@
         inferFrom(overriddenGetters, forSetter: false);
       }
 
-      parameter.variable!.type = inferredType ?? const DynamicType();
+      parameter.type.registerInferredType(inferredType ?? const DynamicType());
     }
   }
 
@@ -483,7 +483,7 @@
         inferredType = combinedMemberSignatureType;
       }
 
-      fieldBuilder.fieldType = inferredType;
+      fieldBuilder.type.registerInferredType(inferredType);
     }
   }
 
diff --git a/pkg/front_end/lib/src/fasta/kernel/implicit_field_type.dart b/pkg/front_end/lib/src/fasta/kernel/implicit_field_type.dart
index 8294c53..7fc4b44c 100644
--- a/pkg/front_end/lib/src/fasta/kernel/implicit_field_type.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/implicit_field_type.dart
@@ -113,7 +113,9 @@
           fieldBuilder.charOffset,
           fieldBuilder.name.length,
           fieldBuilder.fileUri);
-      return fieldBuilder.fieldType = const InvalidType();
+      DartType type = const InvalidType();
+      fieldBuilder.type.registerInferredType(type);
+      return type;
     }
     isStarted = true;
     DartType? inferredType;
diff --git a/pkg/front_end/lib/src/fasta/kernel/utils.dart b/pkg/front_end/lib/src/fasta/kernel/utils.dart
index c1a644f..393198a 100644
--- a/pkg/front_end/lib/src/fasta/kernel/utils.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/utils.dart
@@ -234,7 +234,7 @@
     new FixedTypeBuilder(dummyDartType, dummyUri, -1);
 final FormalParameterBuilder dummyFormalParameterBuilder =
     new FormalParameterBuilder(null, FormalParameterKind.requiredPositional, 0,
-        const OmittedTypeBuilder(), '', null, -1,
+        new OmittedTypeBuilder(), '', null, -1,
         fileUri: dummyUri);
 final TypeVariableBuilder dummyTypeVariableBuilder = new TypeVariableBuilder(
     TypeVariableBuilder.noNameSentinel, null, -1, null,
diff --git a/pkg/front_end/lib/src/fasta/source/outline_builder.dart b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
index 81871e0..3e826a0 100644
--- a/pkg/front_end/lib/src/fasta/source/outline_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
@@ -32,7 +32,6 @@
 import '../builder/mixin_application_builder.dart';
 import '../builder/named_type_builder.dart';
 import '../builder/nullability_builder.dart';
-import '../builder/omitted_type_builder.dart';
 import '../builder/type_builder.dart';
 import '../builder/type_variable_builder.dart';
 import '../combinator.dart' show CombinatorBuilder;
@@ -1926,7 +1925,6 @@
         libraryBuilder.addConstructor(
             metadata,
             modifiers,
-            returnType,
             name,
             constructorName,
             typeVariables,
@@ -2261,7 +2259,7 @@
           metadata,
           kind,
           modifiers,
-          type ?? const OmittedTypeBuilder(),
+          type ?? libraryBuilder.addInferableType(),
           name == null ? FormalParameterBuilder.noNameSentinel : name as String,
           thisKeyword != null,
           superKeyword != null,
@@ -2584,7 +2582,7 @@
     List<TypeVariableBuilder>? typeVariables =
         pop() as List<TypeVariableBuilder>?;
     push(libraryBuilder.addFunctionType(
-        returnType ?? const OmittedTypeBuilder(),
+        returnType ?? libraryBuilder.addInferableType(),
         typeVariables,
         formals,
         libraryBuilder.nullableBuilderIfTrue(questionMark != null),
@@ -2605,7 +2603,7 @@
       reportErrorIfNullableType(question);
     }
     push(libraryBuilder.addFunctionType(
-        returnType ?? const OmittedTypeBuilder(),
+        returnType ?? libraryBuilder.addInferableType(),
         typeVariables,
         formals,
         libraryBuilder.nullableBuilderIfTrue(question != null),
@@ -2644,7 +2642,7 @@
           hasMembers: false);
       // TODO(cstefantsova): Make sure that RHS of typedefs can't have '?'.
       aliasedType = libraryBuilder.addFunctionType(
-          returnType ?? const OmittedTypeBuilder(),
+          returnType ?? libraryBuilder.addInferableType(),
           null,
           formals,
           const NullabilityBuilder.omitted(),
diff --git a/pkg/front_end/lib/src/fasta/source/source_constructor_builder.dart b/pkg/front_end/lib/src/fasta/source/source_constructor_builder.dart
index 4b94f07..aaca7a6 100644
--- a/pkg/front_end/lib/src/fasta/source/source_constructor_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_constructor_builder.dart
@@ -67,6 +67,9 @@
 
 class DeclaredSourceConstructorBuilder extends SourceFunctionBuilderImpl
     implements SourceConstructorBuilder {
+  @override
+  final OmittedTypeBuilder returnType;
+
   final Constructor _constructor;
   final Procedure? _constructorTearOff;
 
@@ -107,7 +110,7 @@
   DeclaredSourceConstructorBuilder(
       List<MetadataBuilder>? metadata,
       int modifiers,
-      TypeBuilder returnType,
+      this.returnType,
       String name,
       List<TypeVariableBuilder>? typeVariables,
       this.formals,
@@ -137,7 +140,7 @@
             forAbstractClassOrEnum: forAbstractClassOrEnum),
         _hasSuperInitializingFormals =
             formals?.any((formal) => formal.isSuperInitializingFormal) ?? false,
-        super(metadata, modifiers, returnType, name, typeVariables, formals,
+        super(metadata, modifiers, name, typeVariables, formals,
             compilationUnit, charOffset, nativeMethodName);
 
   @override
@@ -427,7 +430,7 @@
           if (substitution.isNotEmpty && type != null) {
             type = substitute(type, substitution);
           }
-          formal.variable!.type = type ?? const DynamicType();
+          formal.type.registerInferredType(type ?? const DynamicType());
         }
         formal.variable!.hasDeclaredInitializer = formal.hasDeclaredInitializer;
       }
@@ -521,8 +524,9 @@
           new TypeParameterType.withDefaultNullabilityForLibrary(
               typeParameter, libraryBuilder.library));
     }
-    function.returnType = new InterfaceType(
+    DartType type = new InterfaceType(
         enclosingClass, libraryBuilder.nonNullable, typeParameterTypes);
+    returnType.registerInferredType(type);
   }
 
   @override
diff --git a/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart b/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart
index 4da1575..b92e7c8 100644
--- a/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart
@@ -21,7 +21,6 @@
 import '../builder/metadata_builder.dart';
 import '../builder/named_type_builder.dart';
 import '../builder/nullability_builder.dart';
-import '../builder/omitted_type_builder.dart';
 import '../builder/procedure_builder.dart';
 import '../builder/type_builder.dart';
 import '../builder/type_declaration_builder.dart';
@@ -46,7 +45,6 @@
 import '../kernel/constructor_tearoff_lowering.dart';
 import '../kernel/expression_generator_helper.dart';
 import '../kernel/kernel_helper.dart';
-import '../kernel/implicit_field_type.dart';
 import '../kernel/internal_ast.dart';
 
 import '../modifier.dart' show constMask, hasInitializerMask, staticMask;
@@ -310,7 +308,7 @@
           new DeclaredSourceConstructorBuilder(
               /* metadata = */ null,
               constMask,
-              /* returnType = */ const OmittedTypeBuilder(),
+              /* returnType = */ libraryBuilder.addInferableType(),
               "",
               /* typeParameters = */ null,
               <FormalParameterBuilder>[
@@ -467,7 +465,7 @@
         }
         SourceFieldBuilder fieldBuilder = new SourceFieldBuilder(
             metadata,
-            const OmittedTypeBuilder(),
+            libraryBuilder.addInferableType(),
             name,
             constMask | staticMask | hasInitializerMask,
             /* isTopLevel = */ false,
@@ -477,10 +475,8 @@
             staticFieldNameScheme,
             fieldReference: fieldReference,
             fieldGetterReference: getterReference,
-            fieldSetterReference: setterReference);
-        fieldBuilder.fieldType = new ImplicitFieldType(
-            fieldBuilder, enumConstantInfo.argumentsBeginToken);
-        libraryBuilder.registerImplicitlyTypedField(fieldBuilder);
+            fieldSetterReference: setterReference,
+            initializerToken: enumConstantInfo.argumentsBeginToken);
         members[name] = fieldBuilder..next = existing;
         elementBuilders.add(fieldBuilder);
       }
@@ -795,8 +791,8 @@
             typeArgument: rawType(libraryBuilder.nonNullable), isConst: true));
 
     for (SourceFieldBuilder elementBuilder in elementBuilders) {
-      elementBuilder.fieldType =
-          buildElement(elementBuilder, classHierarchy.coreTypes);
+      elementBuilder.type.registerInferredType(
+          buildElement(elementBuilder, classHierarchy.coreTypes));
     }
     delayedActionPerformers.addAll(_delayedActionPerformers);
     _delayedActionPerformers.clear();
diff --git a/pkg/front_end/lib/src/fasta/source/source_factory_builder.dart b/pkg/front_end/lib/src/fasta/source/source_factory_builder.dart
index 7ba09c8..0b88f34 100644
--- a/pkg/front_end/lib/src/fasta/source/source_factory_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_factory_builder.dart
@@ -43,6 +43,9 @@
   @override
   final bool isExtensionInstanceMember = false;
 
+  @override
+  final TypeBuilder returnType;
+
   final Procedure _procedureInternal;
   final Procedure? _factoryTearOff;
 
@@ -53,7 +56,7 @@
   SourceFactoryBuilder(
       List<MetadataBuilder>? metadata,
       int modifiers,
-      TypeBuilder returnType,
+      this.returnType,
       String name,
       List<TypeVariableBuilder> typeVariables,
       List<FormalParameterBuilder>? formals,
@@ -79,8 +82,8 @@
           ..isNonNullableByDefault = libraryBuilder.isNonNullableByDefault,
         _factoryTearOff = createFactoryTearOffProcedure(name, libraryBuilder,
             libraryBuilder.fileUri, charOffset, tearOffReference),
-        super(metadata, modifiers, returnType, name, typeVariables, formals,
-            libraryBuilder, charOffset, nativeMethodName) {
+        super(metadata, modifiers, name, typeVariables, formals, libraryBuilder,
+            charOffset, nativeMethodName) {
     this.asyncModifier = asyncModifier;
   }
 
diff --git a/pkg/front_end/lib/src/fasta/source/source_field_builder.dart b/pkg/front_end/lib/src/fasta/source/source_field_builder.dart
index f9ca277..3b6f323 100644
--- a/pkg/front_end/lib/src/fasta/source/source_field_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_field_builder.dart
@@ -41,7 +41,7 @@
 import 'source_member_builder.dart';
 
 class SourceFieldBuilder extends SourceMemberBuilderImpl
-    implements FieldBuilder {
+    implements FieldBuilder, InferredTypeListener {
   @override
   final String name;
 
@@ -91,10 +91,13 @@
       Reference? lateIsSetSetterReference,
       Reference? lateGetterReference,
       Reference? lateSetterReference,
+      Token? initializerToken,
       Token? constInitializerToken,
       this.isSynthesized = false})
       : _constInitializerToken = constInitializerToken,
         super(libraryBuilder, charOffset) {
+    type.registerInferredTypeListener(this);
+
     bool isInstanceMember = fieldNameScheme.isInstanceMember;
 
     Uri fileUri = libraryBuilder.fileUri;
@@ -259,6 +262,19 @@
           getterReference: fieldGetterReference,
           setterReference: fieldSetterReference);
     }
+
+    if (type is OmittedTypeBuilder) {
+      if (!hasInitializer && isStatic) {
+        // A static field without type and initializer will always be inferred
+        // to have type `dynamic`.
+        type.registerInferredType(const DynamicType());
+      } else {
+        // A field with no type and initializer or an instance field without
+        // type and initializer need to have the type inferred.
+        fieldType = new ImplicitFieldType(this, initializerToken);
+        libraryBuilder.registerImplicitlyTypedField(this);
+      }
+    }
   }
 
   bool get isLateLowered => _fieldEncoding.isLateLowering;
@@ -326,11 +342,6 @@
         isSynthetic: isSynthetic);
   }
 
-  bool get isEligibleForInference {
-    return type is OmittedTypeBuilder &&
-        (hasInitializer || isClassInstanceMember);
-  }
-
   @override
   bool get isAssignable {
     if (isConst) return false;
@@ -462,7 +473,7 @@
       if (!libraryBuilder.isNonNullableByDefault) {
         inferredType = legacyErasure(inferredType);
       }
-      fieldType = implicitFieldType.checkInferred(inferredType);
+      type.registerInferredType(implicitFieldType.checkInferred(inferredType));
 
       IncludesTypeParametersNonCovariantly? needsCheckVisitor;
       if (parent is ClassBuilder) {
@@ -485,6 +496,11 @@
     return fieldType;
   }
 
+  @override
+  void onInferredType(DartType type) {
+    fieldType = type;
+  }
+
   DartType get builtType => fieldType;
 
   List<ClassMember>? _localMembers;
diff --git a/pkg/front_end/lib/src/fasta/source/source_function_builder.dart b/pkg/front_end/lib/src/fasta/source/source_function_builder.dart
index 3b199e8..b91c160 100644
--- a/pkg/front_end/lib/src/fasta/source/source_function_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_function_builder.dart
@@ -127,7 +127,7 @@
 
 /// Common base class for constructor and procedure builders.
 abstract class SourceFunctionBuilderImpl extends SourceMemberBuilderImpl
-    implements SourceFunctionBuilder {
+    implements SourceFunctionBuilder, InferredTypeListener {
   @override
   final List<MetadataBuilder>? metadata;
 
@@ -135,9 +135,6 @@
   final int modifiers;
 
   @override
-  final TypeBuilder returnType;
-
-  @override
   final String name;
 
   @override
@@ -158,7 +155,6 @@
   SourceFunctionBuilderImpl(
       this.metadata,
       this.modifiers,
-      this.returnType,
       this.name,
       this.typeVariables,
       this.formals,
@@ -166,6 +162,7 @@
       int charOffset,
       this.nativeMethodName)
       : super(compilationUnit, charOffset) {
+    returnType.registerInferredTypeListener(this);
     if (formals != null) {
       for (int i = 0; i < formals!.length; i++) {
         formals![i].parent = this;
@@ -430,6 +427,11 @@
     return _extensionTypeParameters;
   }
 
+  @override
+  void onInferredType(DartType type) {
+    function.returnType = type;
+  }
+
   bool _hasBuiltOutlineExpressions = false;
 
   @override
diff --git a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
index 760a0cc..8dff630 100644
--- a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
@@ -65,7 +65,6 @@
 import '../import.dart' show Import;
 import '../kernel/constructor_tearoff_lowering.dart';
 import '../kernel/hierarchy/members_builder.dart';
-import '../kernel/implicit_field_type.dart';
 import '../kernel/internal_ast.dart';
 import '../kernel/kernel_helper.dart';
 import '../kernel/load_library_builder.dart';
@@ -791,7 +790,7 @@
           metadata,
           modifiers,
           isTopLevel,
-          type ?? const OmittedTypeBuilder(),
+          type ?? addInferableType(),
           info.name,
           info.charOffset,
           info.charEndOffset,
@@ -1585,6 +1584,11 @@
     //addBuilder("Null", new NullTypeBuilder(const NullType(), this, -1), -1);
   }
 
+  OmittedTypeBuilder addInferableType() {
+    // TODO(johnniwinther): Register inferable types in a backlog.
+    return new OmittedTypeBuilder();
+  }
+
   NamedTypeBuilder addNamedType(
       Object name,
       NullabilityBuilder nullabilityBuilder,
@@ -2501,31 +2505,16 @@
         lateIsSetSetterReference: lateIsSetSetterReference,
         lateGetterReference: lateGetterReference,
         lateSetterReference: lateSetterReference,
+        initializerToken: initializerToken,
         constInitializerToken: constInitializerToken);
     addBuilder(name, fieldBuilder, charOffset,
         getterReference: fieldGetterReference,
         setterReference: fieldSetterReference);
-    if (type is OmittedTypeBuilder && fieldBuilder.next == null) {
-      // Only the first one (the last one in the linked list of next pointers)
-      // are added to the tree, had parent pointers and can infer correctly.
-      if (initializerToken == null && fieldBuilder.isStatic) {
-        // A static field without type and initializer will always be inferred
-        // to have type `dynamic`.
-        fieldBuilder.fieldType = const DynamicType();
-      } else {
-        // A field with no type and initializer or an instance field without
-        // type and initializer need to have the type inferred.
-        fieldBuilder.fieldType =
-            new ImplicitFieldType(fieldBuilder, initializerToken);
-        registerImplicitlyTypedField(fieldBuilder);
-      }
-    }
   }
 
   void addConstructor(
       List<MetadataBuilder>? metadata,
       int modifiers,
-      TypeBuilder? returnType,
       final Object? name,
       String constructorName,
       List<TypeVariableBuilder>? typeVariables,
@@ -2551,7 +2540,7 @@
         new DeclaredSourceConstructorBuilder(
             metadata,
             modifiers & ~abstractMask,
-            returnType ?? const OmittedTypeBuilder(),
+            addInferableType(),
             constructorName,
             typeVariables,
             formals,
@@ -2658,7 +2647,7 @@
     SourceProcedureBuilder procedureBuilder = new SourceProcedureBuilder(
         metadata,
         modifiers,
-        returnType ?? const OmittedTypeBuilder(),
+        returnType ?? addInferableType(),
         name,
         typeVariables,
         formals,
diff --git a/pkg/front_end/lib/src/fasta/source/source_loader.dart b/pkg/front_end/lib/src/fasta/source/source_loader.dart
index 3748d5e3..3787878 100644
--- a/pkg/front_end/lib/src/fasta/source/source_loader.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_loader.dart
@@ -52,7 +52,6 @@
 import '../builder/member_builder.dart';
 import '../builder/modifier_builder.dart';
 import '../builder/named_type_builder.dart';
-import '../builder/omitted_type_builder.dart';
 import '../builder/prefix_builder.dart';
 import '../builder/procedure_builder.dart';
 import '../builder/type_alias_builder.dart';
@@ -1208,7 +1207,7 @@
     ProcedureBuilder builder = new SourceProcedureBuilder(
         /* metadata = */ null,
         /* modifier flags = */ 0,
-        const OmittedTypeBuilder(),
+        libraryBuilder.addInferableType(),
         "debugExpr",
         /* type variables = */ null,
         /* formals = */ null,
diff --git a/pkg/front_end/lib/src/fasta/source/source_procedure_builder.dart b/pkg/front_end/lib/src/fasta/source/source_procedure_builder.dart
index e24939d..e513652 100644
--- a/pkg/front_end/lib/src/fasta/source/source_procedure_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_procedure_builder.dart
@@ -11,7 +11,6 @@
 import '../builder/formal_parameter_builder.dart';
 import '../builder/member_builder.dart';
 import '../builder/metadata_builder.dart';
-import '../builder/omitted_type_builder.dart';
 import '../builder/procedure_builder.dart';
 import '../builder/type_builder.dart';
 import '../builder/type_variable_builder.dart';
@@ -35,6 +34,9 @@
   @override
   final bool isExtensionInstanceMember;
 
+  @override
+  final TypeBuilder returnType;
+
   late Procedure _procedure;
 
   final Reference? _tearOffReference;
@@ -61,7 +63,7 @@
   SourceProcedureBuilder(
       List<MetadataBuilder>? metadata,
       int modifiers,
-      TypeBuilder returnType,
+      this.returnType,
       String name,
       List<TypeVariableBuilder>? typeVariables,
       List<FormalParameterBuilder>? formals,
@@ -85,8 +87,8 @@
         assert(isInstanceMember != null),
         assert(kind != ProcedureKind.Factory),
         this.isExtensionInstanceMember = isInstanceMember && isExtensionMember,
-        super(metadata, modifiers, returnType, name, typeVariables, formals,
-            libraryBuilder, charOffset, nativeMethodName) {
+        super(metadata, modifiers, name, typeVariables, formals, libraryBuilder,
+            charOffset, nativeMethodName) {
     _procedure = new Procedure(
         nameScheme.getProcedureName(kind, name),
         isExtensionInstanceMember ? ProcedureKind.Method : kind,
@@ -131,18 +133,6 @@
     function.dartAsyncMarker = actualAsyncModifier;
   }
 
-  bool get isEligibleForTopLevelInference {
-    if (isDeclarationInstanceMember) {
-      if (returnType is OmittedTypeBuilder) return true;
-      if (formals != null) {
-        for (FormalParameterBuilder formal in formals!) {
-          if (formal.type is OmittedTypeBuilder) return true;
-        }
-      }
-    }
-    return false;
-  }
-
   bool get isExtensionMethod {
     return parent is ExtensionBuilder;
   }
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index e3d8120..37bfea3 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -634,6 +634,7 @@
 inequivalence
 inequivalences
 inequivalent
+inferable
 influence
 informative
 infos
diff --git a/pkg/front_end/testcases/general/issue38961.dart.weak.expect b/pkg/front_end/testcases/general/issue38961.dart.weak.expect
index 0b9c61a..5a863b3 100644
--- a/pkg/front_end/testcases/general/issue38961.dart.weak.expect
+++ b/pkg/front_end/testcases/general/issue38961.dart.weak.expect
@@ -9,14 +9,14 @@
 //   dynamic x = this;
 //           ^
 //
-// pkg/front_end/testcases/general/issue38961.dart:6:15: Error: Can't access 'this' in a field initializer.
-//   dynamic x = this;
-//               ^^^^
-//
 // pkg/front_end/testcases/general/issue38961.dart:7:11: Error: Can't access 'this' in a field initializer.
 //   var x = this;
 //           ^^^^
 //
+// pkg/front_end/testcases/general/issue38961.dart:6:15: Error: Can't access 'this' in a field initializer.
+//   dynamic x = this;
+//               ^^^^
+//
 import self as self;
 import "dart:core" as core;
 
diff --git a/pkg/front_end/testcases/general/issue38961.dart.weak.modular.expect b/pkg/front_end/testcases/general/issue38961.dart.weak.modular.expect
index 0b9c61a..5a863b3 100644
--- a/pkg/front_end/testcases/general/issue38961.dart.weak.modular.expect
+++ b/pkg/front_end/testcases/general/issue38961.dart.weak.modular.expect
@@ -9,14 +9,14 @@
 //   dynamic x = this;
 //           ^
 //
-// pkg/front_end/testcases/general/issue38961.dart:6:15: Error: Can't access 'this' in a field initializer.
-//   dynamic x = this;
-//               ^^^^
-//
 // pkg/front_end/testcases/general/issue38961.dart:7:11: Error: Can't access 'this' in a field initializer.
 //   var x = this;
 //           ^^^^
 //
+// pkg/front_end/testcases/general/issue38961.dart:6:15: Error: Can't access 'this' in a field initializer.
+//   dynamic x = this;
+//               ^^^^
+//
 import self as self;
 import "dart:core" as core;
 
diff --git a/pkg/front_end/testcases/general/issue38961.dart.weak.outline.expect b/pkg/front_end/testcases/general/issue38961.dart.weak.outline.expect
index 6b1474b..4ce64d9 100644
--- a/pkg/front_end/testcases/general/issue38961.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/general/issue38961.dart.weak.outline.expect
@@ -9,6 +9,10 @@
 //   dynamic x = this;
 //           ^
 //
+// pkg/front_end/testcases/general/issue38961.dart:7:11: Error: Can't access 'this' in a field initializer.
+//   var x = this;
+//           ^^^^
+//
 import self as self;
 import "dart:core" as core;
 
diff --git a/pkg/front_end/testcases/general/issue38961.dart.weak.transformed.expect b/pkg/front_end/testcases/general/issue38961.dart.weak.transformed.expect
index 0b9c61a..5a863b3 100644
--- a/pkg/front_end/testcases/general/issue38961.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/general/issue38961.dart.weak.transformed.expect
@@ -9,14 +9,14 @@
 //   dynamic x = this;
 //           ^
 //
-// pkg/front_end/testcases/general/issue38961.dart:6:15: Error: Can't access 'this' in a field initializer.
-//   dynamic x = this;
-//               ^^^^
-//
 // pkg/front_end/testcases/general/issue38961.dart:7:11: Error: Can't access 'this' in a field initializer.
 //   var x = this;
 //           ^^^^
 //
+// pkg/front_end/testcases/general/issue38961.dart:6:15: Error: Can't access 'this' in a field initializer.
+//   dynamic x = this;
+//               ^^^^
+//
 import self as self;
 import "dart:core" as core;