[cfe] Add OffsetMap

This adds OffsetMap to avoid using names (or other means) to
connect objects/builders created in the OutlineBuilder with the
DietListener. The OffsetMap uses offsets, through Tokens or
Identifiers, as the key for the created objects.

Change-Id: I704d8f8374402463ea741e36ed15b279acb85535
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/366942
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/fasta/builder_graph.dart b/pkg/front_end/lib/src/fasta/builder_graph.dart
index fdae514..b36f06d 100644
--- a/pkg/front_end/lib/src/fasta/builder_graph.dart
+++ b/pkg/front_end/lib/src/fasta/builder_graph.dart
@@ -16,7 +16,7 @@
 
 import 'dill/dill_library_builder.dart' show DillLibraryBuilder;
 
-import 'source/source_library_builder.dart' show SourceLibraryBuilder;
+import 'source/source_library_builder.dart' show Part, SourceLibraryBuilder;
 
 import 'incremental_compiler.dart' show getPartUri;
 
@@ -52,8 +52,8 @@
           neighbors.add(uri);
         }
       }
-      for (LibraryBuilder part in library.parts) {
-        Uri uri = part.importUri;
+      for (Part part in library.parts) {
+        Uri uri = part.builder.importUri;
         if (builders.containsKey(uri)) {
           neighbors.add(uri);
         }
diff --git a/pkg/front_end/lib/src/fasta/export.dart b/pkg/front_end/lib/src/fasta/export.dart
index 3eb8ee8..9c0234a 100644
--- a/pkg/front_end/lib/src/fasta/export.dart
+++ b/pkg/front_end/lib/src/fasta/export.dart
@@ -4,6 +4,8 @@
 
 library fasta.export;
 
+import 'package:kernel/ast.dart';
+
 import 'builder/builder.dart';
 import 'builder/library_builder.dart';
 import 'combinator.dart' show CombinatorBuilder;
@@ -23,6 +25,11 @@
 
   Uri get fileUri => exporter.fileUri;
 
+  /// The [LibraryDependency] node corresponding to this import.
+  ///
+  /// This set in [SourceLibraryBuilder.addDependencies].
+  late final LibraryDependency libraryDependency;
+
   bool addToExportScope(String name, Builder member) {
     if (combinators != null) {
       for (CombinatorBuilder combinator in combinators!) {
diff --git a/pkg/front_end/lib/src/fasta/import.dart b/pkg/front_end/lib/src/fasta/import.dart
index 310033c..dfb8cfe 100644
--- a/pkg/front_end/lib/src/fasta/import.dart
+++ b/pkg/front_end/lib/src/fasta/import.dart
@@ -46,6 +46,11 @@
   // this field is set.
   final String? nativeImportPath;
 
+  /// The [LibraryDependency] node corresponding to this import.
+  ///
+  /// This set in [SourceLibraryBuilder.addDependencies].
+  LibraryDependency? libraryDependency;
+
   Import(
       this.importer,
       this.imported,
diff --git a/pkg/front_end/lib/src/fasta/incremental_compiler.dart b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
index 38a698d..7b9c4d2 100644
--- a/pkg/front_end/lib/src/fasta/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
@@ -105,7 +105,7 @@
 import 'source/source_class_builder.dart' show SourceClassBuilder;
 import 'source/source_extension_builder.dart';
 import 'source/source_library_builder.dart'
-    show ImplicitLanguageVersion, SourceLibraryBuilder;
+    show ImplicitLanguageVersion, Part, SourceLibraryBuilder;
 import 'source/source_loader.dart';
 import 'ticker.dart' show Ticker;
 import 'uri_translator.dart' show UriTranslator;
@@ -2180,12 +2180,12 @@
       }
       if (libraryBuilder is SourceLibraryBuilder) {
         // TODO(jensj): This shouldn't be possible anymore.
-        for (LibraryBuilder part in libraryBuilder.parts) {
-          partUriToParent[part.importUri] = libraryBuilder;
-          partUriToParent[part.fileUri] = libraryBuilder;
-          if (isInvalidated(part.importUri, part.fileUri)) {
-            invalidatedImportUris.add(part.importUri);
-            builders[part.importUri] = part;
+        for (Part part in libraryBuilder.parts) {
+          partUriToParent[part.builder.importUri] = libraryBuilder;
+          partUriToParent[part.builder.fileUri] = libraryBuilder;
+          if (isInvalidated(part.builder.importUri, part.builder.fileUri)) {
+            invalidatedImportUris.add(part.builder.importUri);
+            builders[part.builder.importUri] = part.builder;
           }
         }
       } else if (libraryBuilder is DillLibraryBuilder) {
diff --git a/pkg/front_end/lib/src/fasta/source/diet_listener.dart b/pkg/front_end/lib/src/fasta/source/diet_listener.dart
index 2c843a6..15b3bc3 100644
--- a/pkg/front_end/lib/src/fasta/source/diet_listener.dart
+++ b/pkg/front_end/lib/src/fasta/source/diet_listener.dart
@@ -11,8 +11,7 @@
         DeclarationKind,
         IdentifierContext,
         MemberKind,
-        Parser,
-        optional;
+        Parser;
 import 'package:_fe_analyzer_shared/src/parser/quote.dart' show unescapeString;
 import 'package:_fe_analyzer_shared/src/parser/stack_listener.dart'
     show FixedNullableList, NullValues, ParserRecovery;
@@ -26,12 +25,7 @@
 import '../builder/declaration_builders.dart';
 import '../builder/modifier_builder.dart';
 import '../codes/fasta_codes.dart'
-    show
-        Code,
-        LocatedMessage,
-        Message,
-        messageExpectedBlockToSkip,
-        templateInternalProblemNotFound;
+    show Code, LocatedMessage, Message, messageExpectedBlockToSkip;
 import '../constant_context.dart' show ConstantContext;
 import '../crash.dart' show Crash;
 import '../identifiers.dart'
@@ -40,18 +34,16 @@
 import '../kernel/benchmarker.dart' show BenchmarkSubdivides, Benchmarker;
 import '../kernel/body_builder.dart' show BodyBuilder, FormalParameters;
 import '../kernel/body_builder_context.dart';
-import '../operator.dart';
-import '../problems.dart' show DebugAbort, internalProblem, unexpected;
+import '../problems.dart' show DebugAbort;
 import '../scope.dart';
 import '../source/value_kinds.dart';
 import '../type_inference/type_inference_engine.dart'
     show InferenceDataForTesting, TypeInferenceEngine;
 import '../type_inference/type_inferrer.dart' show TypeInferrer;
 import 'diet_parser.dart';
-import 'source_class_builder.dart';
+import 'offset_map.dart';
 import 'source_constructor_builder.dart';
 import 'source_enum_builder.dart';
-import 'source_extension_type_declaration_builder.dart';
 import 'source_field_builder.dart';
 import 'source_function_builder.dart';
 import 'source_library_builder.dart' show SourceLibraryBuilder;
@@ -69,9 +61,6 @@
 
   final TypeInferenceEngine typeInferenceEngine;
 
-  int importExportDirectiveIndex = 0;
-  int partDirectiveIndex = 0;
-
   DeclarationBuilder? _currentDeclaration;
   ClassBuilder? _currentClass;
   bool _inRedirectingFactory = false;
@@ -83,14 +72,16 @@
   Scope memberScope;
 
   @override
-  Uri uri;
+  final Uri uri;
 
   final Benchmarker? _benchmarker;
 
+  final OffsetMap _offsetMap;
+
   DietListener(SourceLibraryBuilder library, this.hierarchy, this.coreTypes,
-      this.typeInferenceEngine)
+      this.typeInferenceEngine, this._offsetMap)
       : libraryBuilder = library,
-        uri = library.fileUri,
+        uri = _offsetMap.uri,
         memberScope = library.scope,
         enableNative =
             library.loader.target.backendTarget.enableNative(library.importUri),
@@ -355,9 +346,8 @@
     if (name is ParserRecovery) return;
 
     Identifier identifier = name as Identifier;
-    final BodyBuilder listener = createFunctionListener(
-        lookupBuilder(getOrSet, identifier.name, identifier.nameOffset)
-            as SourceFunctionBuilderImpl);
+    final BodyBuilder listener =
+        createFunctionListener(_offsetMap.lookupProcedure(identifier));
     buildFunctionBody(listener, bodyToken, metadata, MemberKind.TopLevelMethod);
   }
 
@@ -554,9 +544,8 @@
         unescapeString(importUriToken.lexeme, importUriToken, this);
     if (importUri.startsWith("dart-ext:")) return;
 
-    Library libraryNode = libraryBuilder.library;
     LibraryDependency? dependency =
-        libraryNode.dependencies[importExportDirectiveIndex++];
+        _offsetMap.lookupImport(importKeyword).libraryDependency;
     parseMetadata(libraryBuilder.bodyBuilderContext, libraryBuilder, metadata,
         dependency);
   }
@@ -571,9 +560,8 @@
     debugEvent("Export");
 
     Token? metadata = pop() as Token?;
-    Library libraryNode = libraryBuilder.library;
     LibraryDependency dependency =
-        libraryNode.dependencies[importExportDirectiveIndex++];
+        _offsetMap.lookupExport(exportKeyword).libraryDependency;
     parseMetadata(libraryBuilder.bodyBuilderContext, libraryBuilder, metadata,
         dependency);
   }
@@ -583,16 +571,9 @@
     debugEvent("Part");
 
     Token? metadata = pop() as Token?;
-    Library libraryNode = libraryBuilder.library;
-    if (libraryNode.parts.length > partDirectiveIndex) {
-      // If partDirectiveIndex >= libraryNode.parts.length we are in a case of
-      // on part having other parts. An error has already been issued.
-      // Don't try to parse metadata into other parts that have nothing to do
-      // with the one this keyword is talking about.
-      LibraryPart part = libraryNode.parts[partDirectiveIndex++];
-      parseMetadata(
-          libraryBuilder.bodyBuilderContext, libraryBuilder, metadata, part);
-    }
+    LibraryPart part = _offsetMap.lookupPart(partKeyword);
+    parseMetadata(
+        libraryBuilder.bodyBuilderContext, libraryBuilder, metadata, part);
   }
 
   @override
@@ -635,9 +616,7 @@
     if (name is ParserRecovery || currentClassIsParserRecovery) return;
 
     Identifier identifier = name as Identifier;
-    SourceFunctionBuilderImpl builder = lookupConstructor(
-            _getConstructorName(identifier), identifier.qualifierOffset)
-        as SourceFunctionBuilderImpl;
+    SourceFunctionBuilder builder = _offsetMap.lookupConstructor(identifier);
     if (_inRedirectingFactory) {
       buildRedirectingFactoryMethod(
           bodyToken, builder, MemberKind.Factory, metadata);
@@ -755,18 +734,9 @@
 
     SourceFunctionBuilder builder;
     if (isConstructor) {
-      builder = lookupConstructor(
-              _getConstructorName(identifier), identifier.qualifierOffset)
-          as SourceFunctionBuilder;
+      builder = _offsetMap.lookupConstructor(identifier);
     } else {
-      String name = identifier.name;
-      // TODO(johnniwinther): Find a uniform way to compute this.
-      bool hasNoFormals = identical(beginParam.next, beginParam.endGroup);
-      if (Operator.subtract == identifier.operator && hasNoFormals) {
-        name = Operator.unaryMinus.text;
-      }
-      Builder? memberBuilder =
-          lookupBuilder(getOrSet, name, identifier.nameOffset);
+      Builder? memberBuilder = _offsetMap.lookupProcedure(identifier);
       if (currentClass?.isEnum == true &&
           memberBuilder is SourceFieldBuilder &&
           memberBuilder.name == "values") {
@@ -857,8 +827,8 @@
         inferenceDataForTesting: builder.dataForTesting?.inferenceData);
   }
 
-  void buildRedirectingFactoryMethod(Token token,
-      SourceFunctionBuilderImpl builder, MemberKind kind, Token? metadata) {
+  void buildRedirectingFactoryMethod(Token token, SourceFunctionBuilder builder,
+      MemberKind kind, Token? metadata) {
     _benchmarker?.beginSubdivide(
         BenchmarkSubdivides.diet_listener_buildRedirectingFactoryMethod);
     final BodyBuilder listener = createFunctionListener(builder);
@@ -896,9 +866,7 @@
     if (names == null || currentClassIsParserRecovery) return;
 
     Identifier first = names.first!;
-    SourceFieldBuilder declaration =
-        lookupBuilder(/*getOrSet*/ null, first.name, first.nameOffset)
-            as SourceFieldBuilder;
+    SourceFieldBuilder declaration = _offsetMap.lookupField(first);
     // TODO(paulberry): don't re-parse the field if we've already parsed it
     // for type inference.
     _parseFields(
@@ -949,11 +917,9 @@
       return;
     }
     if (name is Identifier) {
-      currentDeclaration =
-          lookupBuilder(/*getOrSet*/ null, name.name, name.nameOffset)
-              as DeclarationBuilder;
+      currentDeclaration = _offsetMap.lookupNamedDeclaration(name);
     } else {
-      currentDeclaration = lookupUnnamedExtensionBuilder(beginToken);
+      currentDeclaration = _offsetMap.lookupUnnamedDeclaration(beginToken);
     }
     memberScope = currentDeclaration!.scope;
   }
@@ -1023,7 +989,8 @@
   void beginExtensionTypeDeclaration(
       Token? augmentToken, Token extensionKeyword, Token nameToken) {
     debugEvent("beginExtensionTypeDeclaration");
-    push(new SimpleIdentifier(nameToken));
+    Identifier identifier = new SimpleIdentifier(nameToken);
+    push(identifier);
     push(extensionKeyword);
 
     // The current declaration is set in [beginClassOrMixinOrExtensionBody] but
@@ -1032,9 +999,7 @@
     assert(currentDeclaration == null);
     assert(memberScope == libraryBuilder.scope);
 
-    currentDeclaration =
-        lookupBuilder(/*getOrSet*/ null, nameToken.lexeme, nameToken.charOffset)
-            as DeclarationBuilder;
+    currentDeclaration = _offsetMap.lookupNamedDeclaration(identifier);
     memberScope = currentDeclaration!.scope;
   }
 
@@ -1052,16 +1017,12 @@
     ]));
     debugEvent("endPrimaryConstructor");
     Token formalsToken = pop() as Token; // Pop formals begin token.
-    int charOffset = formalsToken.charOffset;
-    String constructorName = '';
     if (hasConstructorName) {
       // TODO(johnniwinther): Handle [ParserRecovery].
-      Identifier identifier = pop() as Identifier;
-      constructorName = identifier.name;
-      charOffset = identifier.nameOffset;
+      pop() as Identifier;
     }
     SourceFunctionBuilder builder =
-        lookupConstructor(constructorName, charOffset) as SourceFunctionBuilder;
+        _offsetMap.lookupPrimaryConstructor(beginToken);
     if (!builder.isConst) {
       buildPrimaryConstructor(createFunctionListener(builder), formalsToken);
     }
@@ -1104,9 +1065,7 @@
     }
 
     Identifier identifier = name as Identifier;
-    currentDeclaration =
-        lookupBuilder(/*getOrSet*/ null, identifier.name, identifier.nameOffset)
-            as DeclarationBuilder;
+    currentDeclaration = _offsetMap.lookupNamedDeclaration(identifier);
     memberScope = currentDeclaration!.scope;
   }
 
@@ -1278,70 +1237,6 @@
     bodyBuilder.checkEmpty(token.charOffset);
   }
 
-  Builder? lookupBuilder(Token? getOrSet, String name, int charOffset) {
-    // TODO(ahe): Can I move this to Scope or ScopeBuilder?
-    Builder? declaration;
-    DeclarationBuilder? currentDeclaration = this.currentDeclaration;
-    if (currentDeclaration != null) {
-      if (uri != currentDeclaration.fileUri) {
-        unexpected("$uri", "${currentDeclaration.fileUri}",
-            currentDeclaration.charOffset, currentDeclaration.fileUri);
-      }
-
-      if (getOrSet != null && optional("set", getOrSet)) {
-        declaration =
-            currentDeclaration.scope.lookupLocalMember(name, setter: true);
-      } else {
-        declaration =
-            currentDeclaration.scope.lookupLocalMember(name, setter: false);
-      }
-    } else if (getOrSet != null && optional("set", getOrSet)) {
-      declaration = libraryBuilder.scope.lookupLocalMember(name, setter: true);
-    } else {
-      declaration = libraryBuilder.scope.lookupLocalMember(name, setter: false);
-    }
-    declaration = handleDuplicatedName(declaration, charOffset);
-    checkBuilder(declaration, name, charOffset);
-    return declaration;
-  }
-
-  ExtensionBuilder? lookupUnnamedExtensionBuilder(Token extensionToken) {
-    return libraryBuilder.scope
-        .lookupLocalUnnamedExtension(uri, extensionToken.charOffset);
-  }
-
-  String _getConstructorName(Identifier nameOrQualified) {
-    String suffix;
-    if (nameOrQualified is QualifiedName) {
-      suffix = nameOrQualified.name;
-    } else {
-      suffix = nameOrQualified.name == currentDeclaration!.name
-          ? ""
-          : nameOrQualified.name;
-    }
-    return suffix;
-  }
-
-  Builder? lookupConstructor(String constructorName, int charOffset) {
-    assert(currentDeclaration != null);
-    assert(currentDeclaration is SourceClassBuilder ||
-        currentDeclaration is SourceExtensionTypeDeclarationBuilder);
-    Builder? declaration;
-    if (libraryFeatures.constructorTearoffs.isEnabled) {
-      constructorName = constructorName == "new" ? "" : constructorName;
-    }
-    declaration =
-        currentDeclaration!.constructorScope.lookupLocalMember(constructorName);
-    declaration = handleDuplicatedName(declaration, charOffset);
-    checkBuilder(
-        declaration,
-        constructorName.isEmpty
-            ? currentDeclaration!.name
-            : '${currentDeclaration!.name}.$constructorName',
-        charOffset);
-    return declaration;
-  }
-
   Builder? handleDuplicatedName(Builder? declaration, int charOffset) {
     if (declaration == null) {
       return null;
@@ -1376,7 +1271,7 @@
     }
   }
 
-  void checkBuilder(Builder? declaration, String name, int charOffset) {
+  /*void checkBuilder(Builder? declaration, String name, int charOffset) {
     if (declaration == null) {
       internalProblem(
           templateInternalProblemNotFound.withArguments(name), charOffset, uri);
@@ -1385,7 +1280,7 @@
       unexpected("$uri", "${declaration.fileUri}", declaration.charOffset,
           declaration.fileUri);
     }
-  }
+  }*/
 
   @override
   void addProblem(Message message, int charOffset, int length,
diff --git a/pkg/front_end/lib/src/fasta/source/offset_map.dart b/pkg/front_end/lib/src/fasta/source/offset_map.dart
new file mode 100644
index 0000000..363c056
--- /dev/null
+++ b/pkg/front_end/lib/src/fasta/source/offset_map.dart
@@ -0,0 +1,150 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:_fe_analyzer_shared/src/scanner/token.dart';
+import 'package:kernel/ast.dart';
+import '../builder/builder.dart';
+import '../builder/declaration_builders.dart';
+import '../codes/fasta_codes.dart';
+import '../export.dart';
+import '../identifiers.dart';
+import '../import.dart';
+import '../problems.dart';
+import 'source_field_builder.dart';
+import 'source_function_builder.dart';
+
+/// Map from offsets of directives and declarations to the objects the define.
+///
+/// This is used to connect parsing of the [OutlineBuilder], where the objects
+/// are created, with the [DietListener], where the objects are looked up.
+class OffsetMap {
+  final Uri uri;
+  final Map<int, DeclarationBuilder> _declarations = {};
+  final Map<int, SourceFieldBuilder> _fields = {};
+  final Map<int, SourceFunctionBuilder> _constructors = {};
+  final Map<int, SourceFunctionBuilder> _procedures = {};
+  final Map<int, LibraryPart> _parts = {};
+  final Map<int, Import> _imports = {};
+  final Map<int, Export> _exports = {};
+
+  OffsetMap(this.uri);
+
+  void registerImport(Token importKeyword, Import import) {
+    assert(importKeyword.lexeme == 'import',
+        "Invalid token for import: $importKeyword.");
+    _imports[importKeyword.charOffset] = import;
+  }
+
+  Import lookupImport(Token importKeyword) {
+    assert(importKeyword.lexeme == 'import',
+        "Invalid token for import: $importKeyword.");
+    return _checkDirective(_imports[importKeyword.charOffset], '<import>',
+        importKeyword.charOffset);
+  }
+
+  void registerExport(Token exportKeyword, Export export) {
+    assert(exportKeyword.lexeme == 'export',
+        "Invalid token for export: $exportKeyword.");
+    _exports[exportKeyword.charOffset] = export;
+  }
+
+  Export lookupExport(Token exportKeyword) {
+    assert(exportKeyword.lexeme == 'export',
+        "Invalid token for export: $exportKeyword.");
+    return _checkDirective(_exports[exportKeyword.charOffset], '<export>',
+        exportKeyword.charOffset);
+  }
+
+  void registerPart(Token partKeyword, LibraryPart part) {
+    assert(
+        partKeyword.lexeme == 'part', "Invalid token for part: $partKeyword.");
+    _parts[partKeyword.charOffset] = part;
+  }
+
+  LibraryPart lookupPart(Token partKeyword) {
+    assert(
+        partKeyword.lexeme == 'part', "Invalid token for part: $partKeyword.");
+    return _checkDirective(
+        _parts[partKeyword.charOffset], '<part>', partKeyword.charOffset);
+  }
+
+  void registerNamedDeclaration(
+      Identifier identifier, DeclarationBuilder builder) {
+    _declarations[identifier.nameOffset] = builder;
+  }
+
+  DeclarationBuilder lookupNamedDeclaration(Identifier identifier) {
+    return _checkBuilder(_declarations[identifier.nameOffset], identifier.name,
+        identifier.nameOffset);
+  }
+
+  void registerUnnamedDeclaration(
+      Token beginToken, DeclarationBuilder builder) {
+    _declarations[beginToken.charOffset] = builder;
+  }
+
+  DeclarationBuilder lookupUnnamedDeclaration(Token beginToken) {
+    return _checkBuilder(_declarations[beginToken.charOffset],
+        '<unnamed-declaration>', beginToken.charOffset);
+  }
+
+  void registerField(Identifier identifier, SourceFieldBuilder builder) {
+    _fields[identifier.nameOffset] = builder;
+  }
+
+  SourceFieldBuilder lookupField(Identifier identifier) {
+    return _checkBuilder(
+        _fields[identifier.nameOffset], identifier.name, identifier.nameOffset);
+  }
+
+  void registerPrimaryConstructor(
+      Token beginToken, SourceFunctionBuilder builder) {
+    _constructors[beginToken.charOffset] = builder;
+  }
+
+  SourceFunctionBuilder lookupPrimaryConstructor(Token beginToken) {
+    return _checkBuilder(_constructors[beginToken.charOffset],
+        '<primary-constructor>', beginToken.charOffset);
+  }
+
+  void registerConstructor(
+      Identifier identifier, SourceFunctionBuilder builder) {
+    _constructors[identifier.nameOffset] = builder;
+  }
+
+  SourceFunctionBuilder lookupConstructor(Identifier identifier) {
+    return _checkBuilder(_constructors[identifier.nameOffset], identifier.name,
+        identifier.nameOffset);
+  }
+
+  void registerProcedure(Identifier identifier, SourceFunctionBuilder builder) {
+    _procedures[identifier.nameOffset] = builder;
+  }
+
+  SourceFunctionBuilder lookupProcedure(Identifier identifier) {
+    return _checkBuilder(_procedures[identifier.nameOffset], identifier.name,
+        identifier.nameOffset);
+  }
+
+  T _checkDirective<T>(T? directive, String name, int charOffset) {
+    if (directive == null) {
+      internalProblem(
+          templateInternalProblemNotFound.withArguments(name), charOffset, uri);
+    }
+    return directive;
+  }
+
+  T _checkBuilder<T extends Builder>(
+      T? declaration, String name, int charOffset) {
+    if (declaration == null) {
+      internalProblem(
+          templateInternalProblemNotFound.withArguments(name), charOffset, uri);
+    }
+    if (uri != declaration.fileUri) {
+      unexpected("$uri", "${declaration.fileUri}", declaration.charOffset,
+          declaration.fileUri);
+    }
+    return declaration;
+  }
+}
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 8d2d77e..ff08984 100644
--- a/pkg/front_end/lib/src/fasta/source/outline_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
@@ -70,6 +70,7 @@
         staticMask;
 import '../operator.dart' show Operator;
 import '../problems.dart' show unhandled;
+import 'offset_map.dart';
 import 'source_enum_builder.dart';
 import 'source_library_builder.dart'
     show
@@ -537,10 +538,11 @@
   bool get inFunctionType =>
       _structuralParameterDepthLevel > 0 || _insideOfFormalParameterType;
 
-  OutlineBuilder(SourceLibraryBuilder library)
-      : libraryBuilder = library,
-        enableNative =
-            library.loader.target.backendTarget.enableNative(library.importUri);
+  OffsetMap _offsetMap;
+
+  OutlineBuilder(this.libraryBuilder, this._offsetMap)
+      : enableNative = libraryBuilder.loader.target.backendTarget
+            .enableNative(libraryBuilder.importUri);
 
   DeclarationContext get declarationContext => _declarationContext.head;
 
@@ -678,8 +680,8 @@
     int uriOffset = popCharOffset();
     String uri = pop() as String;
     List<MetadataBuilder>? metadata = pop() as List<MetadataBuilder>?;
-    libraryBuilder.addExport(metadata, uri, configurations, combinators,
-        exportKeyword.charOffset, uriOffset);
+    libraryBuilder.addExport(_offsetMap, exportKeyword, metadata, uri,
+        configurations, combinators, exportKeyword.charOffset, uriOffset);
     checkEmpty(exportKeyword.charOffset);
   }
 
@@ -733,6 +735,8 @@
     }
     bool isAugmentationImport = augmentToken != null;
     libraryBuilder.addImport(
+        offsetMap: _offsetMap,
+        importKeyword: importKeyword,
         metadata: metadata,
         isAugmentationImport: isAugmentationImport,
         uri: uri,
@@ -811,7 +815,7 @@
     int charOffset = popCharOffset();
     String uri = pop() as String;
     List<MetadataBuilder>? metadata = pop() as List<MetadataBuilder>?;
-    libraryBuilder.addPart(metadata, uri, charOffset);
+    libraryBuilder.addPart(_offsetMap, partKeyword, metadata, uri, charOffset);
     checkEmpty(partKeyword.charOffset);
   }
 
@@ -1365,9 +1369,10 @@
         modifiers |= abstractMask;
       }
       libraryBuilder.addClass(
+          _offsetMap,
           metadata,
           modifiers,
-          identifier.name,
+          identifier,
           typeVariables,
           supertype,
           mixinApplication,
@@ -1458,9 +1463,10 @@
       }
 
       libraryBuilder.addMixinDeclaration(
+          _offsetMap,
           metadata,
           mixinDeclarationMask,
-          identifier.name,
+          identifier,
           typeVariables,
           supertypeConstraints,
           interfaces,
@@ -1496,8 +1502,9 @@
     List<NominalVariableBuilder>? typeVariables =
         pop() as List<NominalVariableBuilder>?;
     int offset = nameToken?.charOffset ?? extensionKeyword.charOffset;
-    push(nameToken?.lexeme ?? NullValues.Name);
-    push(offset);
+    push(nameToken != null
+        ? new SimpleIdentifier(nameToken)
+        : NullValues.Identifier);
     push(typeVariables ?? NullValues.NominalVariables);
     libraryBuilder.currentTypeParameterScopeBuilder
         .markAsExtensionDeclaration(nameToken?.lexeme, offset, typeVariables);
@@ -1509,8 +1516,7 @@
     assert(checkState(extensionKeyword, [
       unionOfKinds([ValueKinds.ParserRecovery, ValueKinds.TypeBuilder]),
       ValueKinds.NominalVariableListOrNull,
-      ValueKinds.Integer,
-      ValueKinds.NameOrNull,
+      ValueKinds.IdentifierOrNull,
       ValueKinds.MetadataListOrNull
     ]));
     debugEvent("endExtensionDeclaration");
@@ -1523,11 +1529,8 @@
     }
     List<NominalVariableBuilder>? typeVariables =
         pop(NullValues.NominalVariables) as List<NominalVariableBuilder>?;
-    int nameOffset = popCharOffset();
-    String? name = pop(NullValues.Name) as String?;
-    if (name == null) {
-      nameOffset = extensionKeyword.charOffset;
-    }
+    Identifier? name = pop(NullValues.Identifier) as Identifier?;
+    int nameOffset = name?.nameOffset ?? extensionKeyword.charOffset;
     List<MetadataBuilder>? metadata =
         pop(NullValues.Metadata) as List<MetadataBuilder>?;
     checkEmpty(extensionKeyword.charOffset);
@@ -1535,6 +1538,8 @@
         ? extensionKeyword.charOffset
         : metadata.first.charOffset;
     libraryBuilder.addExtensionDeclaration(
+        _offsetMap,
+        beginToken,
         metadata,
         // TODO(johnniwinther): Support modifiers on extensions?
         0,
@@ -1559,8 +1564,7 @@
         pop() as List<NominalVariableBuilder>?;
     String name = nameToken.lexeme;
     int offset = nameToken.charOffset;
-    push(name);
-    push(offset);
+    push(new SimpleIdentifier(nameToken));
     push(typeVariables ?? NullValues.NominalVariables);
     libraryBuilder.currentTypeParameterScopeBuilder
         .markAsExtensionTypeDeclaration(name, offset, typeVariables);
@@ -1574,8 +1578,7 @@
     assert(checkState(extensionKeyword, [
       ValueKinds.TypeBuilderListOrNull,
       ValueKinds.NominalVariableListOrNull,
-      ValueKinds.Integer,
-      ValueKinds.Name,
+      ValueKinds.Identifier,
       ValueKinds.MetadataListOrNull,
     ]));
     reportIfNotEnabled(libraryFeatures.inlineClass, typeKeyword.charOffset,
@@ -1585,8 +1588,7 @@
         pop(NullValues.TypeBuilderList) as List<TypeBuilder>?;
     List<NominalVariableBuilder>? typeVariables =
         pop(NullValues.NominalVariables) as List<NominalVariableBuilder>?;
-    int nameOffset = popCharOffset();
-    String name = pop() as String;
+    Identifier identifier = pop() as Identifier;
     List<MetadataBuilder>? metadata =
         pop(NullValues.Metadata) as List<MetadataBuilder>?;
     checkEmpty(extensionKeyword.charOffset);
@@ -1597,14 +1599,14 @@
         ? extensionKeyword.charOffset
         : metadata.first.charOffset;
     libraryBuilder.addExtensionTypeDeclaration(
+        _offsetMap,
         metadata,
         // TODO(johnniwinther): Support modifiers on extension types?
         0,
-        name,
+        identifier,
         typeVariables,
         interfaces,
         startOffset,
-        nameOffset,
         endToken.charOffset);
 
     libraryBuilder.endIndexedContainer();
@@ -1731,6 +1733,8 @@
     scopeBuilder.resolveNamedTypes(typeVariables, libraryBuilder);
 
     libraryBuilder.addPrimaryConstructor(
+        offsetMap: _offsetMap,
+        beginToken: beginToken,
         constructorName: constructorName == "new" ? "" : constructorName,
         charOffset: charOffset,
         formals: formals,
@@ -1814,9 +1818,11 @@
       final int startCharOffset =
           metadata == null ? beginToken.charOffset : metadata.first.charOffset;
       libraryBuilder.addProcedure(
+          _offsetMap,
           metadata,
           modifiers,
           returnType,
+          identifier,
           identifier.name,
           typeVariables,
           formals,
@@ -2343,9 +2349,10 @@
       final int startCharOffset =
           metadata == null ? beginToken.charOffset : metadata.first.charOffset;
       libraryBuilder.addConstructor(
+          _offsetMap,
           metadata,
           modifiers,
-          name,
+          identifier,
           constructorName,
           typeVariables,
           formals,
@@ -2369,9 +2376,11 @@
       bool isExtensionTypeMember =
           methodKind == _MethodKind.extensionTypeMethod;
       libraryBuilder.addProcedure(
+          _offsetMap,
           metadata,
           modifiers,
           returnType,
+          identifier,
           name,
           typeVariables,
           formals,
@@ -3063,14 +3072,14 @@
       }
 
       libraryBuilder.addEnum(
+          _offsetMap,
           metadata,
-          identifier.name,
+          identifier,
           typeVariables,
           mixinBuilder,
           interfaces,
           enumConstantInfos,
           startCharOffset,
-          identifier.nameOffset,
           endCharOffset);
     } else {
       libraryBuilder
@@ -3369,6 +3378,7 @@
     List<MetadataBuilder>? metadata =
         pop(NullValues.Metadata) as List<MetadataBuilder>?;
     checkEmpty(typedefKeyword.charOffset);
+
     libraryBuilder.addFunctionTypeAlias(metadata, identifier.name,
         typeVariables, aliasedType, identifier.nameOffset);
     popDeclarationContext(DeclarationContext.Typedef);
@@ -3472,8 +3482,8 @@
     List<MetadataBuilder>? metadata = pop() as List<MetadataBuilder>?;
     checkEmpty(beginToken.charOffset);
     if (fieldInfos != null) {
-      libraryBuilder.addFields(
-          metadata, modifiers, /* isTopLevel = */ true, type, fieldInfos);
+      libraryBuilder.addFields(_offsetMap, metadata, modifiers,
+          /* isTopLevel = */ true, type, fieldInfos);
     }
     popDeclarationContext();
   }
@@ -3535,8 +3545,8 @@
     }
     List<MetadataBuilder>? metadata = pop() as List<MetadataBuilder>?;
     if (fieldInfos != null) {
-      libraryBuilder.addFields(
-          metadata, modifiers, /* isTopLevel = */ false, type, fieldInfos);
+      libraryBuilder.addFields(_offsetMap, metadata, modifiers,
+          /* isTopLevel = */ false, type, fieldInfos);
     }
     popDeclarationContext();
   }
@@ -3810,6 +3820,7 @@
           TypeParameterScopeKind.factoryMethod, "<syntax-error>");
     } else {
       libraryBuilder.addFactoryMethod(
+        _offsetMap,
         metadata,
         modifiers,
         name,
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 1672f47..7e60e542 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
@@ -105,6 +105,7 @@
 import '../util/helpers.dart';
 import 'class_declaration.dart';
 import 'name_scheme.dart';
+import 'offset_map.dart';
 import 'source_class_builder.dart' show SourceClassBuilder;
 import 'source_constructor_builder.dart';
 import 'source_enum_builder.dart';
@@ -124,15 +125,18 @@
   @override
   final SourceLoader loader;
 
+  /// Map used to find objects created in the [OutlineBuilder] from within
+  /// the [DietListener].
+  ///
+  /// This is meant to be written once and read once.
+  OffsetMap? _offsetMap;
+
   final TypeParameterScopeBuilder _libraryTypeParameterScopeBuilder;
 
   final List<ConstructorReferenceBuilder> constructorReferences =
       <ConstructorReferenceBuilder>[];
 
-  final List<LibraryBuilder> parts = <LibraryBuilder>[];
-
-  // Can I use library.parts instead? See SourceLibraryBuilder.addPart.
-  final List<int> partOffsets = <int>[];
+  final List<Part> parts = [];
 
   final List<Import> imports = <Import>[];
 
@@ -353,6 +357,24 @@
         "'${importUri}'.");
   }
 
+  /// Returns the map of objects created in the [OutlineBuilder].
+  ///
+  /// This should only be called once.
+  OffsetMap get offsetMap {
+    assert(_offsetMap != null, "No OffsetMap for $this");
+    OffsetMap map = _offsetMap!;
+    _offsetMap = null;
+    return map;
+  }
+
+  /// Registers the map of objects created in the [OutlineBuilder].
+  ///
+  /// This should only be called once.
+  void set offsetMap(OffsetMap value) {
+    assert(_offsetMap == null, "OffsetMap has already been set for $this");
+    _offsetMap = value;
+  }
+
   MergedLibraryScope get mergedScope {
     return _mergedScope ??=
         isAugmenting ? origin.mergedScope : new MergedLibraryScope(this);
@@ -740,6 +762,8 @@
   }
 
   void addExport(
+      OffsetMap offsetMap,
+      Token exportKeyword,
       List<MetadataBuilder>? metadata,
       String uri,
       List<Configuration>? configurations,
@@ -760,11 +784,15 @@
         resolve(this.importUri, uri, uriOffset), charOffset,
         accessor: this);
     exportedLibrary.addExporter(this, combinators, charOffset);
-    exports.add(new Export(this, exportedLibrary, combinators, charOffset));
+    Export export = new Export(this, exportedLibrary, combinators, charOffset);
+    exports.add(export);
+    offsetMap.registerExport(exportKeyword, export);
   }
 
   void addImport(
-      {required List<MetadataBuilder>? metadata,
+      {OffsetMap? offsetMap,
+      Token? importKeyword,
+      required List<MetadataBuilder>? metadata,
       required bool isAugmentationImport,
       required String uri,
       required List<Configuration>? configurations,
@@ -810,7 +838,7 @@
           referencesFromIndex: isAugmentationImport ? indexedLibrary : null);
     }
 
-    imports.add(new Import(
+    Import import = new Import(
         this,
         builder,
         isAugmentationImport,
@@ -821,10 +849,13 @@
         charOffset,
         prefixCharOffset,
         importIndex,
-        nativeImportPath: nativePath));
+        nativeImportPath: nativePath);
+    imports.add(import);
+    offsetMap?.registerImport(importKeyword!, import);
   }
 
-  void addPart(List<MetadataBuilder>? metadata, String uri, int charOffset) {
+  void addPart(OffsetMap offsetMap, Token partKeyword,
+      List<MetadataBuilder>? metadata, String uri, int charOffset) {
     Uri resolvedUri = resolve(this.importUri, uri, charOffset, isPart: true);
     // To support absolute paths from within packages in the part uri, we try to
     // translate the file uri from the resolved import uri before resolving
@@ -833,17 +864,18 @@
         resolve(fileUri, uri, charOffset);
     // TODO(johnniwinther): Add a LibraryPartBuilder instead of using
     // [LibraryBuilder] to represent both libraries and parts.
-    parts.add(loader.read(resolvedUri, charOffset,
+    LibraryBuilder builder = loader.read(resolvedUri, charOffset,
         origin: isAugmenting ? origin : null,
         fileUri: newFileUri,
         accessor: this,
-        isPatch: isAugmenting));
-    partOffsets.add(charOffset);
+        isPatch: isAugmenting);
+    parts.add(new Part(charOffset, builder));
 
     // TODO(ahe): [metadata] should be stored, evaluated, and added to [part].
     LibraryPart part = new LibraryPart(<Expression>[], uri)
       ..fileOffset = charOffset;
     library.addPart(part);
+    offsetMap.registerPart(partKeyword, part);
   }
 
   void addPartOf(List<MetadataBuilder>? metadata, String? name, String? uri,
@@ -868,8 +900,13 @@
     _scriptTokenOffset ??= charOffset;
   }
 
-  void addFields(List<MetadataBuilder>? metadata, int modifiers,
-      bool isTopLevel, TypeBuilder? type, List<FieldInfo> fieldInfos) {
+  void addFields(
+      OffsetMap offsetMap,
+      List<MetadataBuilder>? metadata,
+      int modifiers,
+      bool isTopLevel,
+      TypeBuilder? type,
+      List<FieldInfo> fieldInfos) {
     for (FieldInfo info in fieldInfos) {
       bool isConst = modifiers & constMask != 0;
       bool isFinal = modifiers & finalMask != 0;
@@ -886,22 +923,24 @@
         new Token.eof(startToken.previous!.offset).setNext(startToken);
       }
       bool hasInitializer = info.initializerToken != null;
-      addField(
-          metadata,
-          modifiers,
-          isTopLevel,
-          type ?? addInferableType(),
-          info.identifier.name,
-          info.identifier.nameOffset,
-          info.charEndOffset,
-          startToken,
-          hasInitializer,
-          constInitializerToken:
-              potentiallyNeedInitializerInOutline ? startToken : null);
+      offsetMap.registerField(
+          info.identifier,
+          addField(
+              metadata,
+              modifiers,
+              isTopLevel,
+              type ?? addInferableType(),
+              info.identifier.name,
+              info.identifier.nameOffset,
+              info.charEndOffset,
+              startToken,
+              hasInitializer,
+              constInitializerToken:
+                  potentiallyNeedInitializerInOutline ? startToken : null));
     }
   }
 
-  Builder? addBuilder(String name, Builder declaration, int charOffset,
+  Builder addBuilder(String name, Builder declaration, int charOffset,
       {Reference? getterReference, Reference? setterReference}) {
     // TODO(ahe): Set the parent correctly here. Could then change the
     // implementation of MemberBuilder.isTopLevel to test explicitly for a
@@ -948,7 +987,7 @@
             : currentTypeParameterScopeBuilder.members!);
     Builder? existing = members[name];
 
-    if (existing == declaration) return existing;
+    if (existing == declaration) return declaration;
 
     if (declaration.next != null && declaration.next != existing) {
       unexpected(
@@ -1171,13 +1210,13 @@
       List<LocatedMessage> context = <LocatedMessage>[
         messagePartInPartLibraryContext.withLocation(library.fileUri, -1, 1),
       ];
-      for (int offset in partOffsets) {
-        addProblem(messagePartInPart, offset, noLength, fileUri,
+      for (Part part in parts) {
+        addProblem(messagePartInPart, part.offset, noLength, fileUri,
             context: context);
       }
-      for (LibraryBuilder part in parts) {
+      for (Part part in parts) {
         // Mark this part as used so we don't report it as orphaned.
-        usedParts!.add(part.importUri);
+        usedParts!.add(part.builder.importUri);
       }
     }
     parts.clear();
@@ -1206,32 +1245,43 @@
 
   void includeParts(Set<Uri> usedParts) {
     Set<Uri> seenParts = new Set<Uri>();
-    for (int i = 0; i < parts.length; i++) {
-      LibraryBuilder part = parts[i];
-      int partOffset = partOffsets[i];
-      if (part == this) {
+    int index = 0;
+    while (index < parts.length) {
+      Part part = parts[index];
+      bool keepPart = true;
+      // TODO(johnniwinther): Use [part.offset] in messages.
+      if (part.builder == this) {
         addProblem(messagePartOfSelf, -1, noLength, fileUri);
-      } else if (seenParts.add(part.fileUri)) {
-        if (part.partOfLibrary != null) {
-          addProblem(messagePartOfTwoLibraries, -1, noLength, part.fileUri,
+        keepPart = false;
+      } else if (seenParts.add(part.builder.fileUri)) {
+        if (part.builder.partOfLibrary != null) {
+          addProblem(
+              messagePartOfTwoLibraries, -1, noLength, part.builder.fileUri,
               context: [
                 messagePartOfTwoLibrariesContext.withLocation(
-                    part.partOfLibrary!.fileUri, -1, noLength),
+                    part.builder.partOfLibrary!.fileUri, -1, noLength),
                 messagePartOfTwoLibrariesContext.withLocation(
                     this.fileUri, -1, noLength)
               ]);
+          keepPart = false;
         } else {
-          usedParts.add(part.importUri);
-          includePart(part, usedParts, partOffset);
+          usedParts.add(part.builder.importUri);
+          keepPart = _includePart(part.builder, usedParts, part.offset);
         }
       } else {
-        addProblem(templatePartTwice.withArguments(part.fileUri), -1, noLength,
-            fileUri);
+        addProblem(templatePartTwice.withArguments(part.builder.fileUri), -1,
+            noLength, fileUri);
+        keepPart = false;
+      }
+      if (keepPart) {
+        index++;
+      } else {
+        parts.removeAt(index);
       }
     }
   }
 
-  bool includePart(LibraryBuilder part, Set<Uri> usedParts, int partOffset) {
+  bool _includePart(LibraryBuilder part, Set<Uri> usedParts, int partOffset) {
     if (part is SourceLibraryBuilder) {
       if (part.partOfUri != null) {
         if (uriIsValid(part.partOfUri!) && part.partOfUri != importUri) {
@@ -1902,9 +1952,10 @@
   }
 
   void addClass(
+      OffsetMap offsetMap,
       List<MetadataBuilder>? metadata,
       int modifiers,
-      String className,
+      Identifier identifier,
       List<NominalVariableBuilder>? typeVariables,
       TypeBuilder? supertype,
       MixinApplicationBuilder? mixins,
@@ -1921,10 +1972,11 @@
       required bool isAugmentation,
       required bool isMixinClass}) {
     _addClass(
+        offsetMap,
         TypeParameterScopeKind.classDeclaration,
         metadata,
         modifiers,
-        className,
+        identifier,
         typeVariables,
         supertype,
         mixins,
@@ -1943,9 +1995,10 @@
   }
 
   void addMixinDeclaration(
+      OffsetMap offsetMap,
       List<MetadataBuilder>? metadata,
       int modifiers,
-      String className,
+      Identifier identifier,
       List<NominalVariableBuilder>? typeVariables,
       List<TypeBuilder>? supertypeConstraints,
       List<TypeBuilder>? interfaces,
@@ -1967,10 +2020,11 @@
       }
     }
     _addClass(
+        offsetMap,
         TypeParameterScopeKind.mixinDeclaration,
         metadata,
         modifiers,
-        className,
+        identifier,
         typeVariables,
         supertype,
         mixinApplication,
@@ -1989,10 +2043,11 @@
   }
 
   void _addClass(
+      OffsetMap offsetMap,
       TypeParameterScopeKind kind,
       List<MetadataBuilder>? metadata,
       int modifiers,
-      String className,
+      Identifier identifier,
       List<NominalVariableBuilder>? typeVariables,
       TypeBuilder? supertype,
       MixinApplicationBuilder? mixins,
@@ -2008,6 +2063,7 @@
       required bool isFinal,
       required bool isAugmentation,
       required bool isMixinClass}) {
+    String className = identifier.name;
     // Nested declaration began in `OutlineBuilder.beginClassDeclaration`.
     TypeParameterScopeBuilder declaration =
         endNestedDeclaration(kind, className)
@@ -2106,6 +2162,7 @@
     setters.forEach(setParentAndCheckConflicts);
     addBuilder(className, classBuilder, nameOffset,
         getterReference: _indexedContainer?.reference);
+    offsetMap.registerNamedDeclaration(identifier, classBuilder);
   }
 
   Map<String, NominalVariableBuilder>? checkTypeVariables(
@@ -2279,14 +2336,17 @@
   }
 
   void addExtensionDeclaration(
+      OffsetMap offsetMap,
+      Token beginToken,
       List<MetadataBuilder>? metadata,
       int modifiers,
-      String? name,
+      Identifier? identifier,
       List<NominalVariableBuilder>? typeVariables,
       TypeBuilder type,
       int startOffset,
       int nameOffset,
       int endOffset) {
+    String? name = identifier?.name;
     // Nested declaration began in
     // `OutlineBuilder.beginExtensionDeclarationPrelude`.
     TypeParameterScopeBuilder declaration =
@@ -2355,17 +2415,23 @@
     setters.forEach(setParentAndCheckConflicts);
     addBuilder(extensionBuilder.name, extensionBuilder, nameOffset,
         getterReference: referenceFrom?.reference);
+    if (identifier != null) {
+      offsetMap.registerNamedDeclaration(identifier, extensionBuilder);
+    } else {
+      offsetMap.registerUnnamedDeclaration(beginToken, extensionBuilder);
+    }
   }
 
   void addExtensionTypeDeclaration(
+      OffsetMap offsetMap,
       List<MetadataBuilder>? metadata,
       int modifiers,
-      String name,
+      Identifier identifier,
       List<NominalVariableBuilder>? typeVariables,
       List<TypeBuilder>? interfaces,
       int startOffset,
-      int nameOffset,
       int endOffset) {
+    String name = identifier.name;
     // Nested declaration began in `OutlineBuilder.beginExtensionDeclaration`.
     TypeParameterScopeBuilder declaration = endNestedDeclaration(
         TypeParameterScopeKind.extensionTypeDeclaration, name)
@@ -2414,7 +2480,7 @@
             this,
             new List<ConstructorReferenceBuilder>.of(constructorReferences),
             startOffset,
-            nameOffset,
+            identifier.nameOffset,
             endOffset,
             indexedContainer,
             representationFieldBuilder);
@@ -2449,8 +2515,10 @@
     constructors.forEach(setParentAndCheckConflicts);
     setters.forEach(setParentAndCheckConflicts);
     addBuilder(extensionTypeDeclarationBuilder.name,
-        extensionTypeDeclarationBuilder, nameOffset,
+        extensionTypeDeclarationBuilder, identifier.nameOffset,
         getterReference: indexedContainer?.reference);
+    offsetMap.registerNamedDeclaration(
+        identifier, extensionTypeDeclarationBuilder);
   }
 
   TypeBuilder? _applyMixins(
@@ -2986,15 +3054,16 @@
   }
 
   void addPrimaryConstructor(
-      {required String constructorName,
+      {required OffsetMap offsetMap,
+      required Token beginToken,
+      required String constructorName,
       required List<NominalVariableBuilder>? typeVariables,
       required List<FormalParameterBuilder>? formals,
       required int charOffset,
       required bool isConst}) {
-    addConstructor(
+    SourceFunctionBuilder builder = _addConstructor(
         null,
         isConst ? constMask : 0,
-        null,
         constructorName,
         typeVariables,
         formals,
@@ -3004,12 +3073,43 @@
         /* charEndOffset = */ charOffset,
         /* nativeMethodName = */ null,
         forAbstractClassOrMixin: false);
+    offsetMap.registerPrimaryConstructor(beginToken, builder);
   }
 
   void addConstructor(
+      OffsetMap offsetMap,
       List<MetadataBuilder>? metadata,
       int modifiers,
-      final Object? name,
+      Identifier identifier,
+      String constructorName,
+      List<NominalVariableBuilder>? typeVariables,
+      List<FormalParameterBuilder>? formals,
+      int startCharOffset,
+      int charOffset,
+      int charOpenParenOffset,
+      int charEndOffset,
+      String? nativeMethodName,
+      {Token? beginInitializers,
+      required bool forAbstractClassOrMixin}) {
+    SourceFunctionBuilder builder = _addConstructor(
+        metadata,
+        modifiers,
+        constructorName,
+        typeVariables,
+        formals,
+        startCharOffset,
+        charOffset,
+        charOpenParenOffset,
+        charEndOffset,
+        nativeMethodName,
+        beginInitializers: beginInitializers,
+        forAbstractClassOrMixin: forAbstractClassOrMixin);
+    offsetMap.registerConstructor(identifier, builder);
+  }
+
+  SourceFunctionBuilder _addConstructor(
+      List<MetadataBuilder>? metadata,
+      int modifiers,
       String constructorName,
       List<NominalVariableBuilder>? typeVariables,
       List<FormalParameterBuilder>? formals,
@@ -3103,12 +3203,15 @@
       constructorBuilder.beginInitializers =
           beginInitializers ?? new Token.eof(-1);
     }
+    return constructorBuilder;
   }
 
   void addProcedure(
+      OffsetMap offsetMap,
       List<MetadataBuilder>? metadata,
       int modifiers,
       TypeBuilder? returnType,
+      Identifier identifier,
       String name,
       List<NominalVariableBuilder>? typeVariables,
       List<FormalParameterBuilder>? formals,
@@ -3197,9 +3300,11 @@
     if (nativeMethodName != null) {
       addNativeMethod(procedureBuilder);
     }
+    offsetMap.registerProcedure(identifier, procedureBuilder);
   }
 
   void addFactoryMethod(
+      OffsetMap offsetMap,
       List<MetadataBuilder>? metadata,
       int modifiers,
       Identifier identifier,
@@ -3352,18 +3457,22 @@
     if (nativeMethodName != null) {
       addNativeMethod(procedureBuilder);
     }
+    offsetMap.registerConstructor(identifier, procedureBuilder);
   }
 
   void addEnum(
+      OffsetMap offsetMap,
       List<MetadataBuilder>? metadata,
-      String name,
+      Identifier identifier,
       List<NominalVariableBuilder>? typeVariables,
       MixinApplicationBuilder? supertypeBuilder,
       List<TypeBuilder>? interfaceBuilders,
       List<EnumConstantInfo?>? enumConstantInfos,
       int startCharOffset,
-      int charOffset,
       int charEndOffset) {
+    String name = identifier.name;
+    int charOffset = identifier.nameOffset;
+
     IndexedClass? referencesFromIndexedClass;
     if (indexedLibrary != null) {
       referencesFromIndexedClass = indexedLibrary!.lookupIndexedClass(name);
@@ -3448,6 +3557,8 @@
     setters.forEach(setParentAndCheckConflicts);
     addBuilder(name, enumBuilder, charOffset,
         getterReference: referencesFromIndexedClass?.cls.reference);
+
+    offsetMap.registerNamedDeclaration(identifier, enumBuilder);
   }
 
   void addFunctionTypeAlias(
@@ -3736,29 +3847,35 @@
           continue;
         }
 
+        LibraryDependency libraryDependency;
         if (import.deferred && import.prefixBuilder?.dependency != null) {
-          library.addDependency(import.prefixBuilder!.dependency!);
+          libraryDependency = import.prefixBuilder!.dependency!;
         } else {
           LibraryBuilder imported = import.imported!.origin;
           Library targetLibrary = imported.library;
-          library.addDependency(new LibraryDependency.import(targetLibrary,
+          libraryDependency = new LibraryDependency.import(targetLibrary,
               name: import.prefix,
               combinators: toKernelCombinators(import.combinators))
-            ..fileOffset = import.charOffset);
+            ..fileOffset = import.charOffset;
         }
+        library.addDependency(libraryDependency);
+        import.libraryDependency = libraryDependency;
       } else {
         // Add export
         Export export = exports[exportIndex++];
-        library.addDependency(new LibraryDependency.export(
+        LibraryDependency libraryDependency = new LibraryDependency.export(
             export.exported.library,
             combinators: toKernelCombinators(export.combinators))
-          ..fileOffset = export.charOffset);
+          ..fileOffset = export.charOffset;
+        library.addDependency(libraryDependency);
+        export.libraryDependency = libraryDependency;
       }
     }
 
-    for (LibraryBuilder part in parts) {
-      if (part is SourceLibraryBuilder) {
-        part.addDependencies(library, seen);
+    for (Part part in parts) {
+      LibraryBuilder builder = part.builder;
+      if (builder is SourceLibraryBuilder) {
+        builder.addDependencies(library, seen);
       }
     }
   }
@@ -6187,3 +6304,18 @@
   @override
   String get name => _iterator?.name ?? (throw new StateError('No element'));
 }
+
+class Part {
+  final int offset;
+  final LibraryBuilder builder;
+
+  Part(this.offset, this.builder);
+
+  OffsetMap get offsetMap {
+    if (builder is SourceLibraryBuilder) {
+      return (builder as SourceLibraryBuilder).offsetMap;
+    }
+    assert(false, "No offset map for $builder.");
+    return new OffsetMap(builder.fileUri);
+  }
+}
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 7e8fae6..7f42382 100644
--- a/pkg/front_end/lib/src/fasta/source/source_loader.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_loader.dart
@@ -81,6 +81,7 @@
 import 'diet_listener.dart' show DietListener;
 import 'diet_parser.dart' show DietParser, useImplicitCreationExpressionInCfe;
 import 'name_scheme.dart';
+import 'offset_map.dart';
 import 'outline_builder.dart' show OutlineBuilder;
 import 'source_class_builder.dart' show SourceClassBuilder;
 import 'source_constructor_builder.dart';
@@ -92,6 +93,7 @@
         InvalidLanguageVersion,
         LanguageVersion,
         LibraryAccess,
+        Part,
         SourceLibraryBuilder;
 import 'source_procedure_builder.dart';
 import 'stack_listener_impl.dart' show offsetForToken;
@@ -1122,10 +1124,12 @@
 
   Future<Null> buildOutline(SourceLibraryBuilder library) async {
     Token tokens = await tokenize(library);
-    OutlineBuilder listener = new OutlineBuilder(library);
+    OffsetMap offsetMap = new OffsetMap(library.fileUri);
+    OutlineBuilder listener = new OutlineBuilder(library, offsetMap);
     new ClassMemberParser(listener,
             allowPatterns: library.libraryFeatures.patterns.isEnabled)
         .parseUnit(tokens);
+    library.offsetMap = offsetMap;
   }
 
   /// Builds all the method bodies found in the given [library].
@@ -1172,18 +1176,18 @@
       }
     }
 
-    DietListener listener = createDietListener(library);
+    DietListener listener = createDietListener(library, library.offsetMap);
     DietParser parser = new DietParser(listener,
         allowPatterns: library.libraryFeatures.patterns.isEnabled);
     parser.parseUnit(tokens);
-    for (LibraryBuilder part in library.parts) {
-      if (part.partOfLibrary != library) {
-        // Part was included in multiple libraries. Skip it here.
-        continue;
-      }
-      Token tokens = await tokenize(part as SourceLibraryBuilder,
+    for (Part part in library.parts) {
+      assert(part.builder.partOfLibrary == library,
+          "Part ${part.builder} is not part of ${library}.");
+      Token tokens = await tokenize(part.builder as SourceLibraryBuilder,
           suppressLexicalErrors: true);
-      listener.uri = part.fileUri;
+      DietListener listener = createDietListener(library, part.offsetMap);
+      DietParser parser = new DietParser(listener,
+          allowPatterns: library.libraryFeatures.patterns.isEnabled);
       parser.parseUnit(tokens);
     }
   }
@@ -1195,7 +1199,11 @@
       FunctionNode parameters,
       VariableDeclaration? extensionThis) async {
     Token token = await tokenize(libraryBuilder, suppressLexicalErrors: false);
-    DietListener dietListener = createDietListener(libraryBuilder);
+    DietListener dietListener = createDietListener(
+        libraryBuilder,
+        // Expression compilation doesn't build an outline, and thus doesn't
+        // support members from source, so we provide an empty [DeclarationMap].
+        new OffsetMap(libraryBuilder.fileUri));
 
     Builder parent = libraryBuilder;
     if (enclosingClassOrExtension != null) {
@@ -1271,8 +1279,10 @@
         parameters);
   }
 
-  DietListener createDietListener(SourceLibraryBuilder library) {
-    return new DietListener(library, hierarchy, coreTypes, typeInferenceEngine);
+  DietListener createDietListener(
+      SourceLibraryBuilder library, OffsetMap offsetMap) {
+    return new DietListener(
+        library, hierarchy, coreTypes, typeInferenceEngine, offsetMap);
   }
 
   void resolveParts() {
diff --git a/pkg/front_end/test/compiler_test_helper.dart b/pkg/front_end/test/compiler_test_helper.dart
index e6be3e5..6c28995 100644
--- a/pkg/front_end/test/compiler_test_helper.dart
+++ b/pkg/front_end/test/compiler_test_helper.dart
@@ -19,10 +19,10 @@
 import 'package:front_end/src/fasta/kernel/kernel_target.dart';
 import 'package:front_end/src/fasta/scope.dart';
 import 'package:front_end/src/fasta/source/diet_listener.dart';
+import 'package:front_end/src/fasta/source/offset_map.dart';
 import 'package:front_end/src/fasta/source/source_library_builder.dart';
 import 'package:front_end/src/fasta/source/source_loader.dart';
 import 'package:front_end/src/fasta/ticker.dart';
-import 'package:front_end/src/fasta/type_inference/type_inference_engine.dart';
 import 'package:front_end/src/fasta/type_inference/type_inferrer.dart';
 import 'package:front_end/src/fasta/uri_translator.dart';
 import 'package:kernel/class_hierarchy.dart';
@@ -214,9 +214,10 @@
       : super(fileSystem, includeComments, target);
 
   @override
-  DietListener createDietListener(SourceLibraryBuilder library) {
-    return new DietListenerTest(
-        library, hierarchy, coreTypes, typeInferenceEngine, bodyBuilderCreator);
+  DietListener createDietListener(
+      SourceLibraryBuilder library, OffsetMap offsetMap) {
+    return new DietListenerTest(library, hierarchy, coreTypes,
+        typeInferenceEngine, offsetMap, bodyBuilderCreator);
   }
 
   @override
@@ -246,13 +247,8 @@
 class DietListenerTest extends DietListener {
   final BodyBuilderCreator bodyBuilderCreator;
 
-  DietListenerTest(
-      SourceLibraryBuilder library,
-      ClassHierarchy hierarchy,
-      CoreTypes coreTypes,
-      TypeInferenceEngine typeInferenceEngine,
-      this.bodyBuilderCreator)
-      : super(library, hierarchy, coreTypes, typeInferenceEngine);
+  DietListenerTest(super.library, super.hierarchy, super.coreTypes,
+      super.typeInferenceEngine, super.offsetMap, this.bodyBuilderCreator);
 
   @override
   BodyBuilder createListenerInternal(
diff --git a/pkg/front_end/test/coverage_suite.dart b/pkg/front_end/test/coverage_suite.dart
index 0788206..6dc69d8 100644
--- a/pkg/front_end/test/coverage_suite.dart
+++ b/pkg/front_end/test/coverage_suite.dart
@@ -97,7 +97,7 @@
       78.42227378190255,
   "package:front_end/src/fasta/builder/void_type_declaration_builder.dart":
       100.0,
-  "package:front_end/src/fasta/builder_graph.dart": 55.769230769230774,
+  "package:front_end/src/fasta/builder_graph.dart": 54.0,
   "package:front_end/src/fasta/codes/fasta_codes_cfe_generated.dart":
       73.0892742453436,
   "package:front_end/src/fasta/codes/type_labeler.dart": 83.68336025848141,
@@ -205,10 +205,10 @@
   "package:front_end/src/fasta/modifier.dart": 100.0,
   "package:front_end/src/fasta/operator.dart": 100.0,
   "package:front_end/src/fasta/problems.dart": 0.0,
-  "package:front_end/src/fasta/scope.dart": 80.14256619144604,
+  "package:front_end/src/fasta/scope.dart": 79.0,
   "package:front_end/src/fasta/source/class_declaration.dart":
       80.29556650246306,
-  "package:front_end/src/fasta/source/diet_listener.dart": 91.22807017543859,
+  "package:front_end/src/fasta/source/diet_listener.dart": 89.0,
   "package:front_end/src/fasta/source/diet_parser.dart": 100.0,
   "package:front_end/src/fasta/source/name_scheme.dart": 93.19148936170212,
   "package:front_end/src/fasta/source/outline_builder.dart": 91.54411764705883,
diff --git a/pkg/front_end/testcases/general/part_of_multiple.dart b/pkg/front_end/testcases/general/part_of_multiple.dart
new file mode 100644
index 0000000..5e76378
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_of_multiple.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'part_of_multiple_lib1.dart';
+import 'part_of_multiple_lib2.dart';
diff --git a/pkg/front_end/testcases/general/part_of_multiple.dart.strong.expect b/pkg/front_end/testcases/general/part_of_multiple.dart.strong.expect
new file mode 100644
index 0000000..0a4a719
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_of_multiple.dart.strong.expect
@@ -0,0 +1,24 @@
+library;
+import self as self;
+
+import "org-dartlang-testcase:///part_of_multiple_lib1.dart";
+import "org-dartlang-testcase:///part_of_multiple_lib2.dart";
+
+
+library part_of_multiple;
+import self as self2;
+
+part part_of_multiple_part.dart;
+
+library part_of_multiple;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_of_multiple_part.dart: Error: A file can't be part of more than one library.
+// Try moving the shared declarations into the libraries, or into a new library.
+// pkg/front_end/testcases/general/part_of_multiple_lib1.dart: Context: Used as a part in this library.
+// pkg/front_end/testcases/general/part_of_multiple_lib2.dart: Context: Used as a part in this library.
+//
+import self as self3;
+
+part part_of_multiple_part.dart;
diff --git a/pkg/front_end/testcases/general/part_of_multiple.dart.strong.modular.expect b/pkg/front_end/testcases/general/part_of_multiple.dart.strong.modular.expect
new file mode 100644
index 0000000..0a4a719
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_of_multiple.dart.strong.modular.expect
@@ -0,0 +1,24 @@
+library;
+import self as self;
+
+import "org-dartlang-testcase:///part_of_multiple_lib1.dart";
+import "org-dartlang-testcase:///part_of_multiple_lib2.dart";
+
+
+library part_of_multiple;
+import self as self2;
+
+part part_of_multiple_part.dart;
+
+library part_of_multiple;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_of_multiple_part.dart: Error: A file can't be part of more than one library.
+// Try moving the shared declarations into the libraries, or into a new library.
+// pkg/front_end/testcases/general/part_of_multiple_lib1.dart: Context: Used as a part in this library.
+// pkg/front_end/testcases/general/part_of_multiple_lib2.dart: Context: Used as a part in this library.
+//
+import self as self3;
+
+part part_of_multiple_part.dart;
diff --git a/pkg/front_end/testcases/general/part_of_multiple.dart.strong.outline.expect b/pkg/front_end/testcases/general/part_of_multiple.dart.strong.outline.expect
new file mode 100644
index 0000000..0a4a719
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_of_multiple.dart.strong.outline.expect
@@ -0,0 +1,24 @@
+library;
+import self as self;
+
+import "org-dartlang-testcase:///part_of_multiple_lib1.dart";
+import "org-dartlang-testcase:///part_of_multiple_lib2.dart";
+
+
+library part_of_multiple;
+import self as self2;
+
+part part_of_multiple_part.dart;
+
+library part_of_multiple;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_of_multiple_part.dart: Error: A file can't be part of more than one library.
+// Try moving the shared declarations into the libraries, or into a new library.
+// pkg/front_end/testcases/general/part_of_multiple_lib1.dart: Context: Used as a part in this library.
+// pkg/front_end/testcases/general/part_of_multiple_lib2.dart: Context: Used as a part in this library.
+//
+import self as self3;
+
+part part_of_multiple_part.dart;
diff --git a/pkg/front_end/testcases/general/part_of_multiple.dart.strong.transformed.expect b/pkg/front_end/testcases/general/part_of_multiple.dart.strong.transformed.expect
new file mode 100644
index 0000000..0a4a719
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_of_multiple.dart.strong.transformed.expect
@@ -0,0 +1,24 @@
+library;
+import self as self;
+
+import "org-dartlang-testcase:///part_of_multiple_lib1.dart";
+import "org-dartlang-testcase:///part_of_multiple_lib2.dart";
+
+
+library part_of_multiple;
+import self as self2;
+
+part part_of_multiple_part.dart;
+
+library part_of_multiple;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_of_multiple_part.dart: Error: A file can't be part of more than one library.
+// Try moving the shared declarations into the libraries, or into a new library.
+// pkg/front_end/testcases/general/part_of_multiple_lib1.dart: Context: Used as a part in this library.
+// pkg/front_end/testcases/general/part_of_multiple_lib2.dart: Context: Used as a part in this library.
+//
+import self as self3;
+
+part part_of_multiple_part.dart;
diff --git a/pkg/front_end/testcases/general/part_of_multiple.dart.textual_outline.expect b/pkg/front_end/testcases/general/part_of_multiple.dart.textual_outline.expect
new file mode 100644
index 0000000..0370ff6
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_of_multiple.dart.textual_outline.expect
@@ -0,0 +1,3 @@
+import 'part_of_multiple_lib1.dart';
+
+import 'part_of_multiple_lib2.dart';
diff --git a/pkg/front_end/testcases/general/part_of_multiple.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/part_of_multiple.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..03c393e
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_of_multiple.dart.textual_outline_modelled.expect
@@ -0,0 +1,2 @@
+import 'part_of_multiple_lib1.dart';
+import 'part_of_multiple_lib2.dart';
diff --git a/pkg/front_end/testcases/general/part_of_multiple_lib1.dart b/pkg/front_end/testcases/general/part_of_multiple_lib1.dart
new file mode 100644
index 0000000..4322291
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_of_multiple_lib1.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library part_of_multiple;
+
+part 'part_of_multiple_part.dart';
diff --git a/pkg/front_end/testcases/general/part_of_multiple_lib2.dart b/pkg/front_end/testcases/general/part_of_multiple_lib2.dart
new file mode 100644
index 0000000..4322291
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_of_multiple_lib2.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library part_of_multiple;
+
+part 'part_of_multiple_part.dart';
diff --git a/pkg/front_end/testcases/general/part_of_multiple_part.dart b/pkg/front_end/testcases/general/part_of_multiple_part.dart
new file mode 100644
index 0000000..8d76ba4
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_of_multiple_part.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of part_of_multiple;
diff --git a/pkg/front_end/testcases/general/part_self.dart b/pkg/front_end/testcases/general/part_self.dart
new file mode 100644
index 0000000..dc0f39a
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_self.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part 'part_self.dart';
diff --git a/pkg/front_end/testcases/general/part_self.dart.strong.expect b/pkg/front_end/testcases/general/part_self.dart.strong.expect
new file mode 100644
index 0000000..d591432
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_self.dart.strong.expect
@@ -0,0 +1,9 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_self.dart: Error: A file can't be a part of itself.
+//
+import self as self;
+
+part part_self.dart;
diff --git a/pkg/front_end/testcases/general/part_self.dart.strong.modular.expect b/pkg/front_end/testcases/general/part_self.dart.strong.modular.expect
new file mode 100644
index 0000000..d591432
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_self.dart.strong.modular.expect
@@ -0,0 +1,9 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_self.dart: Error: A file can't be a part of itself.
+//
+import self as self;
+
+part part_self.dart;
diff --git a/pkg/front_end/testcases/general/part_self.dart.strong.outline.expect b/pkg/front_end/testcases/general/part_self.dart.strong.outline.expect
new file mode 100644
index 0000000..d591432
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_self.dart.strong.outline.expect
@@ -0,0 +1,9 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_self.dart: Error: A file can't be a part of itself.
+//
+import self as self;
+
+part part_self.dart;
diff --git a/pkg/front_end/testcases/general/part_self.dart.strong.transformed.expect b/pkg/front_end/testcases/general/part_self.dart.strong.transformed.expect
new file mode 100644
index 0000000..d591432
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_self.dart.strong.transformed.expect
@@ -0,0 +1,9 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_self.dart: Error: A file can't be a part of itself.
+//
+import self as self;
+
+part part_self.dart;
diff --git a/pkg/front_end/testcases/general/part_self.dart.textual_outline.expect b/pkg/front_end/testcases/general/part_self.dart.textual_outline.expect
new file mode 100644
index 0000000..347608e
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_self.dart.textual_outline.expect
@@ -0,0 +1 @@
+part 'part_self.dart';
diff --git a/pkg/front_end/testcases/general/part_self.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/part_self.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..347608e
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_self.dart.textual_outline_modelled.expect
@@ -0,0 +1 @@
+part 'part_self.dart';
diff --git a/pkg/front_end/testcases/general/part_twice.dart b/pkg/front_end/testcases/general/part_twice.dart
new file mode 100644
index 0000000..8ce116f
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_twice.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part 'part_twice_part.dart';
+part 'part_twice_part.dart';
diff --git a/pkg/front_end/testcases/general/part_twice.dart.strong.expect b/pkg/front_end/testcases/general/part_twice.dart.strong.expect
new file mode 100644
index 0000000..1402ce5
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_twice.dart.strong.expect
@@ -0,0 +1,10 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_twice.dart: Error: Can't use 'pkg/front_end/testcases/general/part_twice_part.dart' as a part more than once.
+//
+import self as self;
+
+part part_twice_part.dart;
+part part_twice_part.dart;
diff --git a/pkg/front_end/testcases/general/part_twice.dart.strong.modular.expect b/pkg/front_end/testcases/general/part_twice.dart.strong.modular.expect
new file mode 100644
index 0000000..1402ce5
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_twice.dart.strong.modular.expect
@@ -0,0 +1,10 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_twice.dart: Error: Can't use 'pkg/front_end/testcases/general/part_twice_part.dart' as a part more than once.
+//
+import self as self;
+
+part part_twice_part.dart;
+part part_twice_part.dart;
diff --git a/pkg/front_end/testcases/general/part_twice.dart.strong.outline.expect b/pkg/front_end/testcases/general/part_twice.dart.strong.outline.expect
new file mode 100644
index 0000000..1402ce5
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_twice.dart.strong.outline.expect
@@ -0,0 +1,10 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_twice.dart: Error: Can't use 'pkg/front_end/testcases/general/part_twice_part.dart' as a part more than once.
+//
+import self as self;
+
+part part_twice_part.dart;
+part part_twice_part.dart;
diff --git a/pkg/front_end/testcases/general/part_twice.dart.strong.transformed.expect b/pkg/front_end/testcases/general/part_twice.dart.strong.transformed.expect
new file mode 100644
index 0000000..1402ce5
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_twice.dart.strong.transformed.expect
@@ -0,0 +1,10 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/part_twice.dart: Error: Can't use 'pkg/front_end/testcases/general/part_twice_part.dart' as a part more than once.
+//
+import self as self;
+
+part part_twice_part.dart;
+part part_twice_part.dart;
diff --git a/pkg/front_end/testcases/general/part_twice.dart.textual_outline.expect b/pkg/front_end/testcases/general/part_twice.dart.textual_outline.expect
new file mode 100644
index 0000000..1819cdd6
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_twice.dart.textual_outline.expect
@@ -0,0 +1,3 @@
+part 'part_twice_part.dart';
+
+part 'part_twice_part.dart';
diff --git a/pkg/front_end/testcases/general/part_twice.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/part_twice.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..1819cdd6
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_twice.dart.textual_outline_modelled.expect
@@ -0,0 +1,3 @@
+part 'part_twice_part.dart';
+
+part 'part_twice_part.dart';
diff --git a/pkg/front_end/testcases/general/part_twice_part.dart b/pkg/front_end/testcases/general/part_twice_part.dart
new file mode 100644
index 0000000..93a8918
--- /dev/null
+++ b/pkg/front_end/testcases/general/part_twice_part.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of 'part_twice.dart';
diff --git a/pkg/front_end/testcases/macros/multiple_imports.dart.strong.expect b/pkg/front_end/testcases/macros/multiple_imports.dart.strong.expect
index b7fd881..b6f612e 100644
--- a/pkg/front_end/testcases/macros/multiple_imports.dart.strong.expect
+++ b/pkg/front_end/testcases/macros/multiple_imports.dart.strong.expect
@@ -5,12 +5,12 @@
 import "dart:convert" as con;
 
 @#C2
-@#C4
 import "dart:math";
-@#C6
-@#C8
+@#C4
 import "dart:convert";
+@#C6
 import "dart:async";
+@#C8
 import "org-dartlang-testcase:///multiple_imports.dart";
 
 class Const extends core::Object /*hasConstConstructor*/  { // from org-dartlang-testcase:///multiple_imports_lib.dart
@@ -30,9 +30,9 @@
 constants  {
   #C1 = 0
   #C2 = self::Const {field:#C1}
-  #C3 = 2
+  #C3 = 1
   #C4 = self::Const {field:#C3}
-  #C5 = 1
+  #C5 = 2
   #C6 = self::Const {field:#C5}
   #C7 = 3
   #C8 = self::Const {field:#C7}
diff --git a/pkg/front_end/testcases/macros/multiple_imports.dart.strong.modular.expect b/pkg/front_end/testcases/macros/multiple_imports.dart.strong.modular.expect
index b7fd881..b6f612e 100644
--- a/pkg/front_end/testcases/macros/multiple_imports.dart.strong.modular.expect
+++ b/pkg/front_end/testcases/macros/multiple_imports.dart.strong.modular.expect
@@ -5,12 +5,12 @@
 import "dart:convert" as con;
 
 @#C2
-@#C4
 import "dart:math";
-@#C6
-@#C8
+@#C4
 import "dart:convert";
+@#C6
 import "dart:async";
+@#C8
 import "org-dartlang-testcase:///multiple_imports.dart";
 
 class Const extends core::Object /*hasConstConstructor*/  { // from org-dartlang-testcase:///multiple_imports_lib.dart
@@ -30,9 +30,9 @@
 constants  {
   #C1 = 0
   #C2 = self::Const {field:#C1}
-  #C3 = 2
+  #C3 = 1
   #C4 = self::Const {field:#C3}
-  #C5 = 1
+  #C5 = 2
   #C6 = self::Const {field:#C5}
   #C7 = 3
   #C8 = self::Const {field:#C7}
diff --git a/pkg/front_end/testcases/macros/multiple_imports.dart.strong.transformed.expect b/pkg/front_end/testcases/macros/multiple_imports.dart.strong.transformed.expect
index b7fd881..b6f612e 100644
--- a/pkg/front_end/testcases/macros/multiple_imports.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/macros/multiple_imports.dart.strong.transformed.expect
@@ -5,12 +5,12 @@
 import "dart:convert" as con;
 
 @#C2
-@#C4
 import "dart:math";
-@#C6
-@#C8
+@#C4
 import "dart:convert";
+@#C6
 import "dart:async";
+@#C8
 import "org-dartlang-testcase:///multiple_imports.dart";
 
 class Const extends core::Object /*hasConstConstructor*/  { // from org-dartlang-testcase:///multiple_imports_lib.dart
@@ -30,9 +30,9 @@
 constants  {
   #C1 = 0
   #C2 = self::Const {field:#C1}
-  #C3 = 2
+  #C3 = 1
   #C4 = self::Const {field:#C3}
-  #C5 = 1
+  #C5 = 2
   #C6 = self::Const {field:#C5}
   #C7 = 3
   #C8 = self::Const {field:#C7}