[CFE] Incremental compiler and part as entry

This CL fixes the incremental compiler in the case where it gets a
part as an entry point.

Change-Id: I79f1735e0056f259500e2ef52d9b23047f2b812f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/178999
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Johnni Winther <johnniwinther@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 b3acb30..e629ce0 100644
--- a/pkg/front_end/lib/src/fasta/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
@@ -230,7 +230,19 @@
       Set<Uri> invalidatedUris = this.invalidatedUris.toSet();
       invalidateNotKeptUserBuilders(invalidatedUris);
       ReusageResult reusedResult =
-          computeReusedLibraries(invalidatedUris, uriTranslator);
+          computeReusedLibraries(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;
+          }
+        }
+      }
 
       // Experimental invalidation initialization (e.g. figure out if we can).
       ExperimentalInvalidation experimentalInvalidation =
@@ -1440,18 +1452,26 @@
   /// any saved component problems for such builders.
   List<Library> computeTransitiveClosure(
       List<Library> inputLibraries,
-      List<Uri> entries,
+      List<Uri> entryPoints,
       List<LibraryBuilder> reusedLibraries,
       ClassHierarchy hierarchy,
       UriTranslator uriTranslator,
       Map<Uri, Source> uriToSource,
       [List<Library> inputLibrariesFiltered]) {
     List<Library> result = <Library>[];
+    Map<Uri, Uri> partUriToLibraryImportUri = <Uri, Uri>{};
     Map<Uri, Library> libraryMap = <Uri, Library>{};
     Map<Uri, Library> potentiallyReferencedLibraries = <Uri, Library>{};
     Map<Uri, Library> potentiallyReferencedInputLibraries = <Uri, Library>{};
     for (Library library in inputLibraries) {
       libraryMap[library.importUri] = library;
+      if (library.parts.isNotEmpty) {
+        for (int partIndex = 0; partIndex < library.parts.length; partIndex++) {
+          LibraryPart part = library.parts[partIndex];
+          Uri partUri = getPartUri(library.importUri, part);
+          partUriToLibraryImportUri[partUri] = library.importUri;
+        }
+      }
       if (library.importUri.scheme == "dart") {
         result.add(library);
         inputLibrariesFiltered?.add(library);
@@ -1460,9 +1480,6 @@
         potentiallyReferencedInputLibraries[library.importUri] = library;
       }
     }
-
-    List<Uri> worklist = <Uri>[];
-    worklist.addAll(entries);
     for (LibraryBuilder libraryBuilder in reusedLibraries) {
       if (libraryBuilder.importUri.scheme == "dart" &&
           !libraryBuilder.isSynthetic) {
@@ -1473,6 +1490,19 @@
       libraryMap[libraryBuilder.importUri] = lib;
     }
 
+    List<Uri> worklist = <Uri>[];
+    for (Uri entry in entryPoints) {
+      if (libraryMap.containsKey(entry)) {
+        worklist.add(entry);
+      } else {
+        // If the entry is a part redirect to the "main" entry.
+        Uri partTranslation = partUriToLibraryImportUri[entry];
+        if (partTranslation != null) {
+          worklist.add(partTranslation);
+        }
+      }
+    }
+
     LibraryGraph graph = new LibraryGraph(libraryMap);
     Set<Uri> partsUsed = new Set<Uri>();
     while (worklist.isNotEmpty && potentiallyReferencedLibraries.isNotEmpty) {
@@ -1928,8 +1958,8 @@
   }
 
   /// Internal method.
-  ReusageResult computeReusedLibraries(
-      Set<Uri> invalidatedUris, UriTranslator uriTranslator) {
+  ReusageResult computeReusedLibraries(Set<Uri> invalidatedUris,
+      UriTranslator uriTranslator, List<Uri> entryPoints) {
     Set<Uri> seenUris = new Set<Uri>();
     List<LibraryBuilder> reusedLibraries = <LibraryBuilder>[];
     for (int i = 0; i < platformBuilders.length; i++) {
@@ -1938,7 +1968,7 @@
       reusedLibraries.add(builder);
     }
     if (userCode == null && userBuilders == null) {
-      return new ReusageResult(const {}, const {}, false, reusedLibraries);
+      return new ReusageResult.reusedLibrariesOnly(reusedLibraries);
     }
     bool invalidatedBecauseOfPackageUpdate = false;
     Set<LibraryBuilder> directlyInvalidated = new Set<LibraryBuilder>();
@@ -1946,6 +1976,7 @@
 
     // Maps all non-platform LibraryBuilders from their import URI.
     Map<Uri, LibraryBuilder> builders = <Uri, LibraryBuilder>{};
+    Map<Uri, LibraryBuilder> partUriToParent = <Uri, LibraryBuilder>{};
 
     // Invalidated URIs translated back to their import URI (package:, dart:,
     // etc.).
@@ -1989,7 +2020,10 @@
         invalidatedImportUris.add(uri);
       }
       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;
@@ -2000,6 +2034,8 @@
           Uri partUri = getPartUri(libraryBuilder.importUri, part);
           Uri fileUri = getPartFileUri(
               libraryBuilder.library.fileUri, part, uriTranslator);
+          partUriToParent[partUri] = libraryBuilder;
+          partUriToParent[fileUri] = libraryBuilder;
 
           if (isInvalidated(partUri, fileUri)) {
             invalidatedImportUris.add(partUri);
@@ -2078,8 +2114,21 @@
       reusedLibraries.add(builder);
     }
 
-    return new ReusageResult(notReusedLibraries, directlyInvalidated,
-        invalidatedBecauseOfPackageUpdate, reusedLibraries);
+    ReusageResult result = new ReusageResult(
+        notReusedLibraries,
+        directlyInvalidated,
+        invalidatedBecauseOfPackageUpdate,
+        reusedLibraries);
+
+    for (Uri entryPoint in entryPoints) {
+      LibraryBuilder parent = partUriToParent[entryPoint];
+      if (reusedLibraries.contains(parent)) {
+        result.registerLibraryUriForPartUsedAsEntryPoint(
+            entryPoint, parent.importUri);
+      }
+    }
+
+    return result;
   }
 
   @override
@@ -2153,13 +2202,32 @@
   final Set<LibraryBuilder> directlyInvalidated;
   final bool invalidatedBecauseOfPackageUpdate;
   final List<LibraryBuilder> reusedLibraries;
+  final Map<Uri, Uri> _reusedLibrariesPartsToParentForEntryPoints;
+
+  ReusageResult.reusedLibrariesOnly(this.reusedLibraries)
+      : notReusedLibraries = const {},
+        directlyInvalidated = const {},
+        invalidatedBecauseOfPackageUpdate = false,
+        _reusedLibrariesPartsToParentForEntryPoints = const {};
 
   ReusageResult(this.notReusedLibraries, this.directlyInvalidated,
       this.invalidatedBecauseOfPackageUpdate, this.reusedLibraries)
-      : assert(notReusedLibraries != null),
+      : _reusedLibrariesPartsToParentForEntryPoints = {},
+        assert(notReusedLibraries != null),
         assert(directlyInvalidated != null),
         assert(invalidatedBecauseOfPackageUpdate != null),
         assert(reusedLibraries != null);
+
+  void registerLibraryUriForPartUsedAsEntryPoint(
+      Uri entryPoint, Uri importUri) {
+    _reusedLibrariesPartsToParentForEntryPoints[entryPoint] = importUri;
+  }
+
+  bool arePartsUsedAsEntryPoints() =>
+      _reusedLibrariesPartsToParentForEntryPoints.isNotEmpty;
+
+  Uri getLibraryUriForPartUsedAsEntryPoint(Uri entryPoint) =>
+      _reusedLibrariesPartsToParentForEntryPoints[entryPoint];
 }
 
 class ExperimentalInvalidation {
diff --git a/pkg/front_end/test/incremental_load_from_dill_suite.dart b/pkg/front_end/test/incremental_load_from_dill_suite.dart
index 70a5360..84c74bc 100644
--- a/pkg/front_end/test/incremental_load_from_dill_suite.dart
+++ b/pkg/front_end/test/incremental_load_from_dill_suite.dart
@@ -831,18 +831,20 @@
       }
 
       if (!noFullComponent) {
-        List<Library> entryLib = component.libraries
-            .where((Library lib) =>
-                entries.contains(lib.importUri) ||
-                entries.contains(lib.fileUri))
-            .toList();
-        if (entryLib.length != entries.length) {
-          return new Result<TestData>(
-              data,
-              UnexpectedEntryToLibraryCount,
-              "Expected the entries to become libraries. "
-              "Got ${entryLib.length} libraries for the expected "
-              "${entries.length} entries.");
+        if (world["checkEntries"] != false) {
+          List<Library> entryLib = component.libraries
+              .where((Library lib) =>
+                  entries.contains(lib.importUri) ||
+                  entries.contains(lib.fileUri))
+              .toList();
+          if (entryLib.length != entries.length) {
+            return new Result<TestData>(
+                data,
+                UnexpectedEntryToLibraryCount,
+                "Expected the entries to become libraries. "
+                "Got ${entryLib.length} libraries for the expected "
+                "${entries.length} entries.");
+          }
         }
       }
       if (compiler.initializedFromDill != expectInitializeFromDill) {
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml
new file mode 100644
index 0000000..aba97dff
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml
@@ -0,0 +1,32 @@
+# Copyright (c) 2021, 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: main.dart
+    checkEntries: false
+    sources:
+      main.dart: |
+        part of 'lib.dart';
+        partMethod() {}
+      lib.dart: |
+        part 'main.dart';
+        main() {}
+    expectedLibraryCount: 1
+
+  - entry: main.dart
+    checkEntries: false
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - main.dart
+    expectedLibraryCount: 1
+
+  - entry: main.dart
+    checkEntries: false
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - lib.dart
+    expectedLibraryCount: 1
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml.world.1.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml.world.1.expect
new file mode 100644
index 0000000..1d3c1ac
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml.world.1.expect
@@ -0,0 +1,7 @@
+main = lib::main;
+library from "org-dartlang-test:///lib.dart" as lib {
+
+  part main.dart;
+  static method main() → dynamic {}
+  static method /* from org-dartlang-test:///main.dart */ partMethod() → dynamic {}
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml.world.2.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml.world.2.expect
new file mode 100644
index 0000000..1d3c1ac
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml.world.2.expect
@@ -0,0 +1,7 @@
+main = lib::main;
+library from "org-dartlang-test:///lib.dart" as lib {
+
+  part main.dart;
+  static method main() → dynamic {}
+  static method /* from org-dartlang-test:///main.dart */ partMethod() → dynamic {}
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml.world.3.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml.world.3.expect
new file mode 100644
index 0000000..1d3c1ac
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_entry.yaml.world.3.expect
@@ -0,0 +1,7 @@
+main = lib::main;
+library from "org-dartlang-test:///lib.dart" as lib {
+
+  part main.dart;
+  static method main() → dynamic {}
+  static method /* from org-dartlang-test:///main.dart */ partMethod() → dynamic {}
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml
new file mode 100644
index 0000000..e599398
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml
@@ -0,0 +1,34 @@
+# Copyright (c) 2021, 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: package:foo/main.dart
+    checkEntries: false
+    sources:
+      .packages: |
+        foo:.
+      main.dart: |
+        part of 'lib.dart';
+        partMethod() {}
+      lib.dart: |
+        part 'main.dart';
+        main() {}
+    expectedLibraryCount: 1
+
+  - entry: package:foo/main.dart
+    checkEntries: false
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - main.dart
+    expectedLibraryCount: 1
+
+  - entry: package:foo/main.dart
+    checkEntries: false
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - lib.dart
+    expectedLibraryCount: 1
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml.world.1.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml.world.1.expect
new file mode 100644
index 0000000..c9c3c86
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml.world.1.expect
@@ -0,0 +1,7 @@
+main = lib::main;
+library from "package:foo/lib.dart" as lib {
+
+  part main.dart;
+  static method main() → dynamic {}
+  static method /* from org-dartlang-test:///main.dart */ partMethod() → dynamic {}
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml.world.2.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml.world.2.expect
new file mode 100644
index 0000000..c9c3c86
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml.world.2.expect
@@ -0,0 +1,7 @@
+main = lib::main;
+library from "package:foo/lib.dart" as lib {
+
+  part main.dart;
+  static method main() → dynamic {}
+  static method /* from org-dartlang-test:///main.dart */ partMethod() → dynamic {}
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml.world.3.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml.world.3.expect
new file mode 100644
index 0000000..c9c3c86
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry.yaml.world.3.expect
@@ -0,0 +1,7 @@
+main = lib::main;
+library from "package:foo/lib.dart" as lib {
+
+  part main.dart;
+  static method main() → dynamic {}
+  static method /* from org-dartlang-test:///main.dart */ partMethod() → dynamic {}
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml
new file mode 100644
index 0000000..3d91b09
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml
@@ -0,0 +1,37 @@
+# Copyright (c) 2021, 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: main.dart
+    warnings: true
+    checkEntries: false
+    sources:
+      .packages: |
+        foo:.
+      main.dart: |
+        part of 'lib.dart';
+        partMethod() {}
+      lib.dart: |
+        part 'main.dart';
+        main() {}
+    expectedLibraryCount: 1
+
+  - entry: main.dart
+    warnings: true
+    checkEntries: false
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - main.dart
+    expectedLibraryCount: 1
+
+  - entry: main.dart
+    warnings: true
+    checkEntries: false
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - lib.dart
+    expectedLibraryCount: 1
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml.world.1.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml.world.1.expect
new file mode 100644
index 0000000..e826847
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml.world.1.expect
@@ -0,0 +1,12 @@
+main = lib::main;
+//
+// Problems in component:
+//
+// org-dartlang-test:///main.dart: Warning: Interpreting this as package URI, 'package:foo/main.dart'.
+//
+library from "package:foo/lib.dart" as lib {
+
+  part main.dart;
+  static method main() → dynamic {}
+  static method /* from org-dartlang-test:///main.dart */ partMethod() → dynamic {}
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml.world.2.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml.world.2.expect
new file mode 100644
index 0000000..e826847
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml.world.2.expect
@@ -0,0 +1,12 @@
+main = lib::main;
+//
+// Problems in component:
+//
+// org-dartlang-test:///main.dart: Warning: Interpreting this as package URI, 'package:foo/main.dart'.
+//
+library from "package:foo/lib.dart" as lib {
+
+  part main.dart;
+  static method main() → dynamic {}
+  static method /* from org-dartlang-test:///main.dart */ partMethod() → dynamic {}
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml.world.3.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml.world.3.expect
new file mode 100644
index 0000000..e826847
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/part_as_package_entry_2.yaml.world.3.expect
@@ -0,0 +1,12 @@
+main = lib::main;
+//
+// Problems in component:
+//
+// org-dartlang-test:///main.dart: Warning: Interpreting this as package URI, 'package:foo/main.dart'.
+//
+library from "package:foo/lib.dart" as lib {
+
+  part main.dart;
+  static method main() → dynamic {}
+  static method /* from org-dartlang-test:///main.dart */ partMethod() → dynamic {}
+}