[CFE] Fix using part as entry with experimental invalidation

Change-Id: I813aecd06aa2e7c4cc6b1aadff50a77268096ca6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241743
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
diff --git a/pkg/front_end/lib/src/fasta/incremental_compiler.dart b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
index ac929fb..4586498 100644
--- a/pkg/front_end/lib/src/fasta/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
@@ -288,9 +288,11 @@
     if (_resetTicker) {
       _ticker.reset();
     }
-    entryPoints ??= context.options.inputs;
+    List<Uri>? entryPointsSavedForLaterOverwrite = entryPoints;
     return context
         .runInContext<IncrementalCompilerResult>((CompilerContext c) async {
+      List<Uri> entryPoints =
+          entryPointsSavedForLaterOverwrite ?? context.options.inputs;
       if (_computeDeltaRunOnce && _initializedForExpressionCompilationOnly) {
         throw new StateError("Initialized for expression compilation: "
             "cannot do another general compile.");
@@ -311,23 +313,7 @@
       Set<Uri?> invalidatedUris = this._invalidatedUris.toSet();
       _invalidateNotKeptUserBuilders(invalidatedUris);
       ReusageResult? reusedResult = _computeReusedLibraries(
-          lastGoodKernelTarget,
-          _userBuilders,
-          invalidatedUris,
-          uriTranslator,
-          entryPoints!);
-
-      // Use the reused libraries to re-write entry-points.
-      if (reusedResult.arePartsUsedAsEntryPoints()) {
-        for (int i = 0; i < entryPoints!.length; i++) {
-          Uri entryPoint = entryPoints![i];
-          Uri? redirect =
-              reusedResult.getLibraryUriForPartUsedAsEntryPoint(entryPoint);
-          if (redirect != null) {
-            entryPoints![i] = redirect;
-          }
-        }
-      }
+          lastGoodKernelTarget, _userBuilders, invalidatedUris, uriTranslator);
 
       // Experimental invalidation initialization (e.g. figure out if we can).
       _benchmarker
@@ -338,6 +324,10 @@
           experimentalInvalidation?.missingSources.length ?? 0);
 
       _benchmarker
+          ?.enterPhase(BenchmarkPhases.incremental_rewriteEntryPointsIfPart);
+      _rewriteEntryPointsIfPart(entryPoints, reusedResult);
+
+      _benchmarker
           ?.enterPhase(BenchmarkPhases.incremental_invalidatePrecompiledMacros);
       await _invalidatePrecompiledMacros(
           c.options, reusedResult.notReusedLibraries);
@@ -375,11 +365,11 @@
       while (true) {
         _benchmarker?.enterPhase(BenchmarkPhases.incremental_setupInLoop);
         currentKernelTarget = _setupNewKernelTarget(c, uriTranslator, hierarchy,
-            reusedLibraries, experimentalInvalidation, entryPoints!.first);
+            reusedLibraries, experimentalInvalidation, entryPoints.first);
         Map<LibraryBuilder, List<LibraryBuilder>>? rebuildBodiesMap =
             _experimentalInvalidationCreateRebuildBodiesBuilders(
                 currentKernelTarget, experimentalInvalidation, uriTranslator);
-        entryPoints = currentKernelTarget.setEntryPoints(entryPoints!);
+        entryPoints = currentKernelTarget.setEntryPoints(entryPoints);
 
         // TODO(johnniwinther,jensj): Ensure that the internal state of the
         // incremental compiler is consistent across 1 or more macro
@@ -477,7 +467,7 @@
               currentKernelTarget,
               data.component != null || fullComponent,
               compiledLibraries,
-              entryPoints!,
+              entryPoints,
               reusedLibraries,
               hierarchy,
               uriTranslator,
@@ -540,6 +530,21 @@
     });
   }
 
+  void _rewriteEntryPointsIfPart(
+      List<Uri> entryPoints, ReusageResult reusedResult) {
+    for (int i = 0; i < entryPoints.length; i++) {
+      Uri entryPoint = entryPoints[i];
+      LibraryBuilder? parent = reusedResult.partUriToParent[entryPoint];
+      if (parent == null) continue;
+      // TODO(jensj): .contains on a list is O(n).
+      // It will only be done for each entry point that's a part though, i.e.
+      // most likely very rarely.
+      if (reusedResult.reusedLibraries.contains(parent)) {
+        entryPoints[i] = parent.importUri;
+      }
+    }
+  }
+
   /// Convert every SourceLibraryBuilder to a DillLibraryBuilder.
   /// As we always do this, this will only be the new ones.
   ///
@@ -1156,6 +1161,9 @@
   /// Figure out if we can (and was asked to) do experimental invalidation.
   /// Note that this returns (future or) [null] if we're not doing experimental
   /// invalidation.
+  ///
+  /// Note that - when doing experimental invalidation - [reusedResult] is
+  /// updated.
   Future<ExperimentalInvalidation?> _initializeExperimentalInvalidation(
       ReusageResult reusedResult, CompilerContext c) async {
     Set<LibraryBuilder>? rebuildBodies;
@@ -1263,7 +1271,6 @@
               // to rebuild only the body of.
               // TODO(jensj): We can probably add this to the rebuildBodies
               // list and just rebuild that library too.
-              // print("Usage of mixin in ${lib.importUri}");
               return null;
             }
           }
@@ -2020,8 +2027,7 @@
       IncrementalKernelTarget? lastGoodKernelTarget,
       Map<Uri, LibraryBuilder>? _userBuilders,
       Set<Uri?> invalidatedUris,
-      UriTranslator uriTranslator,
-      List<Uri> entryPoints) {
+      UriTranslator uriTranslator) {
     Set<Uri> seenUris = new Set<Uri>();
     List<LibraryBuilder> reusedLibraries = <LibraryBuilder>[];
     for (int i = 0; i < _platformBuilders!.length; i++) {
@@ -2179,26 +2185,8 @@
       if (!seenUris.add(builder.importUri)) continue;
       reusedLibraries.add(builder);
     }
-
-    ReusageResult result = new ReusageResult(
-        notReusedLibraries,
-        directlyInvalidated,
-        invalidatedBecauseOfPackageUpdate,
-        reusedLibraries);
-
-    for (Uri entryPoint in entryPoints) {
-      LibraryBuilder? parent = partUriToParent[entryPoint];
-      if (parent == null) continue;
-      // TODO(jensj): .contains on a list is O(n).
-      // It will only be done for each entry point that's a part though, i.e.
-      // most likely very rarely.
-      if (reusedLibraries.contains(parent)) {
-        result.registerLibraryUriForPartUsedAsEntryPoint(
-            entryPoint, parent.importUri);
-      }
-    }
-
-    return result;
+    return new ReusageResult(notReusedLibraries, directlyInvalidated,
+        invalidatedBecauseOfPackageUpdate, reusedLibraries, partUriToParent);
   }
 
   @override
@@ -2312,17 +2300,21 @@
   final Set<LibraryBuilder> directlyInvalidated;
   final bool invalidatedBecauseOfPackageUpdate;
   final List<LibraryBuilder> reusedLibraries;
-  final Map<Uri, Uri> _reusedLibrariesPartsToParentForEntryPoints;
+  final Map<Uri?, LibraryBuilder> partUriToParent;
 
   ReusageResult.reusedLibrariesOnly(this.reusedLibraries)
       : notReusedLibraries = const {},
         directlyInvalidated = const {},
         invalidatedBecauseOfPackageUpdate = false,
-        _reusedLibrariesPartsToParentForEntryPoints = const {};
+        partUriToParent = const {};
 
-  ReusageResult(this.notReusedLibraries, this.directlyInvalidated,
-      this.invalidatedBecauseOfPackageUpdate, this.reusedLibraries)
-      : _reusedLibrariesPartsToParentForEntryPoints = {},
+  ReusageResult(
+      this.notReusedLibraries,
+      this.directlyInvalidated,
+      this.invalidatedBecauseOfPackageUpdate,
+      this.reusedLibraries,
+      this.partUriToParent)
+      :
         // ignore: unnecessary_null_comparison
         assert(notReusedLibraries != null),
         // ignore: unnecessary_null_comparison
@@ -2330,18 +2322,9 @@
         // ignore: unnecessary_null_comparison
         assert(invalidatedBecauseOfPackageUpdate != null),
         // ignore: unnecessary_null_comparison
-        assert(reusedLibraries != null);
-
-  void registerLibraryUriForPartUsedAsEntryPoint(
-      Uri entryPoint, Uri importUri) {
-    _reusedLibrariesPartsToParentForEntryPoints[entryPoint] = importUri;
-  }
-
-  bool arePartsUsedAsEntryPoints() =>
-      _reusedLibrariesPartsToParentForEntryPoints.isNotEmpty;
-
-  Uri? getLibraryUriForPartUsedAsEntryPoint(Uri entryPoint) =>
-      _reusedLibrariesPartsToParentForEntryPoints[entryPoint];
+        assert(reusedLibraries != null),
+        // ignore: unnecessary_null_comparison
+        assert(partUriToParent != null);
 }
 
 class ExperimentalInvalidation {
diff --git a/pkg/front_end/lib/src/fasta/kernel/benchmarker.dart b/pkg/front_end/lib/src/fasta/kernel/benchmarker.dart
index 5966303..8008f364 100644
--- a/pkg/front_end/lib/src/fasta/kernel/benchmarker.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/benchmarker.dart
@@ -200,6 +200,7 @@
   incremental_ensurePlatform,
   incremental_invalidate,
   incremental_experimentalInvalidation,
+  incremental_rewriteEntryPointsIfPart,
   incremental_invalidatePrecompiledMacros,
   incremental_cleanup,
   incremental_loadEnsureLoadedComponents,
diff --git a/pkg/front_end/testcases/incremental/part_as_entry_2.yaml b/pkg/front_end/testcases/incremental/part_as_entry_2.yaml
new file mode 100644
index 0000000..0def2e8
--- /dev/null
+++ b/pkg/front_end/testcases/incremental/part_as_entry_2.yaml
@@ -0,0 +1,42 @@
+# Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE.md file.
+
+type: newworld
+worlds:
+  - entry: entry.dart
+    experiments: alternative-invalidation-strategy
+    checkEntries: false
+    sources:
+      entry.dart: |
+        part of "lib.dart";
+      lib.dart: |
+        import 'lib1.dart';
+        export 'lib1.dart';
+        part "entry.dart";
+      lib1.dart: |
+        import 'lib.dart';
+        main() {
+          print("Hello, World!");
+        }
+    expectedLibraryCount: 2
+
+  - entry: entry.dart
+    experiments: alternative-invalidation-strategy
+    checkEntries: false
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - lib.dart
+    expectedLibraryCount: 2
+    expectsRebuildBodiesOnly: true
+
+  - entry: entry.dart
+    experiments: alternative-invalidation-strategy
+    checkEntries: false
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - lib1.dart
+    expectedLibraryCount: 2
+    expectsRebuildBodiesOnly: true
diff --git a/pkg/front_end/testcases/incremental/part_as_entry_2.yaml.world.1.expect b/pkg/front_end/testcases/incremental/part_as_entry_2.yaml.world.1.expect
new file mode 100644
index 0000000..066a54b
--- /dev/null
+++ b/pkg/front_end/testcases/incremental/part_as_entry_2.yaml.world.1.expect
@@ -0,0 +1,17 @@
+main = lib1::main;
+library from "org-dartlang-test:///lib.dart" as lib {
+additionalExports = (lib1::main)
+
+  import "org-dartlang-test:///lib1.dart";
+  export "org-dartlang-test:///lib1.dart";
+
+  part entry.dart;
+}
+library from "org-dartlang-test:///lib1.dart" as lib1 {
+
+  import "org-dartlang-test:///lib.dart";
+
+  static method main() → dynamic {
+    dart.core::print("Hello, World!");
+  }
+}
diff --git a/pkg/front_end/testcases/incremental/part_as_entry_2.yaml.world.2.expect b/pkg/front_end/testcases/incremental/part_as_entry_2.yaml.world.2.expect
new file mode 100644
index 0000000..066a54b
--- /dev/null
+++ b/pkg/front_end/testcases/incremental/part_as_entry_2.yaml.world.2.expect
@@ -0,0 +1,17 @@
+main = lib1::main;
+library from "org-dartlang-test:///lib.dart" as lib {
+additionalExports = (lib1::main)
+
+  import "org-dartlang-test:///lib1.dart";
+  export "org-dartlang-test:///lib1.dart";
+
+  part entry.dart;
+}
+library from "org-dartlang-test:///lib1.dart" as lib1 {
+
+  import "org-dartlang-test:///lib.dart";
+
+  static method main() → dynamic {
+    dart.core::print("Hello, World!");
+  }
+}
diff --git a/pkg/front_end/testcases/incremental/part_as_entry_2.yaml.world.3.expect b/pkg/front_end/testcases/incremental/part_as_entry_2.yaml.world.3.expect
new file mode 100644
index 0000000..066a54b
--- /dev/null
+++ b/pkg/front_end/testcases/incremental/part_as_entry_2.yaml.world.3.expect
@@ -0,0 +1,17 @@
+main = lib1::main;
+library from "org-dartlang-test:///lib.dart" as lib {
+additionalExports = (lib1::main)
+
+  import "org-dartlang-test:///lib1.dart";
+  export "org-dartlang-test:///lib1.dart";
+
+  part entry.dart;
+}
+library from "org-dartlang-test:///lib1.dart" as lib1 {
+
+  import "org-dartlang-test:///lib.dart";
+
+  static method main() → dynamic {
+    dart.core::print("Hello, World!");
+  }
+}