[cfe] Enable alternative invalidation strategy in incremental macro test

This adds support for changes to macro classes with the alternative
invalidation strategy. Because macro classes can modify code in any
library where they are applied, changes to libraries with macro classes,
or to libraries that they depend upon, are handled as a signature
change that require full recompilation of the dependencies.

A more precise approach would be to invalidate only the libraries that
apply the changed macros. This data is not currently available during
incremental compilation.

Change-Id: I23347e332b95acfe232f4a04a023677780a64d60
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/235681
Reviewed-by: Jens Johansen <jensj@google.com>
Commit-Queue: 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 cd882d6..0f02e2e 100644
--- a/pkg/front_end/lib/src/fasta/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
@@ -284,7 +284,6 @@
 
       // Figure out what to keep and what to throw away.
       Set<Uri?> invalidatedUris = this._invalidatedUris.toSet();
-      _invalidatePrecompiledMacros(c.options, invalidatedUris);
       _invalidateNotKeptUserBuilders(invalidatedUris);
       ReusageResult? reusedResult = _computeReusedLibraries(
           lastGoodKernelTarget,
@@ -311,6 +310,8 @@
       recorderForTesting?.recordRebuildBodiesCount(
           experimentalInvalidation?.missingSources.length ?? 0);
 
+      _invalidatePrecompiledMacros(c.options, reusedResult.notReusedLibraries);
+
       // Cleanup: After (potentially) removing builders we have stuff to cleanup
       // to not leak, and we might need to re-create the dill target.
       _cleanupRemovedBuilders(
@@ -1098,6 +1099,21 @@
     if (reusedResult.directlyInvalidated.isEmpty) return null;
     if (reusedResult.invalidatedBecauseOfPackageUpdate) return null;
 
+    if (enableMacros) {
+      /// TODO(johnniwinther): Add a [hasMacro] property to [LibraryBuilder].
+      for (LibraryBuilder builder in reusedResult.notReusedLibraries) {
+        Iterator<Builder> iterator = builder.iterator;
+        while (iterator.moveNext()) {
+          Builder childBuilder = iterator.current;
+          if (childBuilder is ClassBuilder && childBuilder.isMacro) {
+            // Changes to a library with macro classes can affect any class that
+            // depends on it.
+            return null;
+          }
+        }
+      }
+    }
+
     // Figure out if the file(s) have changed outline, or we can just
     // rebuild the bodies.
     for (LibraryBuilder builder in reusedResult.directlyInvalidated) {
@@ -1398,16 +1414,19 @@
     }
   }
 
-  void _invalidatePrecompiledMacros(
-      ProcessedOptions processedOptions, Set<Uri?> invalidatedUris) {
-    if (invalidatedUris.isEmpty) {
+  /// Removes the precompiled macros whose libraries cannot be reused.
+  void _invalidatePrecompiledMacros(ProcessedOptions processedOptions,
+      Set<LibraryBuilder> notReusedLibraries) {
+    if (notReusedLibraries.isEmpty) {
       return;
     }
     CompilerOptions compilerOptions = processedOptions.rawOptionsForTesting;
     Map<Uri, Uri>? precompiledMacroUris = compilerOptions.precompiledMacroUris;
     if (precompiledMacroUris != null) {
+      Set<Uri> importUris =
+          notReusedLibraries.map((library) => library.importUri).toSet();
       for (Uri macroLibraryUri in precompiledMacroUris.keys.toList()) {
-        if (invalidatedUris.contains(macroLibraryUri)) {
+        if (importUris.contains(macroLibraryUri)) {
           precompiledMacroUris.remove(macroLibraryUri);
         }
       }
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro.0.dart b/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro.0.dart
index 73a3a2d..a65202e 100644
--- a/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro.0.dart
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro.0.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
+import 'macro_dependency.dart';
 
 macro
 
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro.1.dart b/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro.1.dart
index 0129964..5cfce9f 100644
--- a/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro.1.dart
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro.1.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
+import 'macro_dependency.dart';
 
 macro
 
@@ -13,8 +14,6 @@
   @override
   FutureOr<void> buildDeclarationsForClass(ClassDeclaration clazz,
       ClassMemberDeclarationBuilder builder) async {
-    builder.declareInClass(new DeclarationCode.fromString('''
-  void method() {}
-'''));
+    builder.declareInClass(new DeclarationCode.fromString(generateBody()));
   }
 }
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro_dependency.0.dart b/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro_dependency.0.dart
new file mode 100644
index 0000000..2e1634f
--- /dev/null
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro_dependency.0.dart
@@ -0,0 +1,9 @@
+// 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 file.
+
+String generateBody() {
+  return '''
+  void method() {}
+''';
+}
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro_dependency.2.dart b/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro_dependency.2.dart
new file mode 100644
index 0000000..6861d3d
--- /dev/null
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/macro_dependency.2.dart
@@ -0,0 +1,11 @@
+// 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 file.
+
+String generateBody() {
+  return '''
+  void method() {
+    print('method');
+  }
+''';
+}
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.0.dart b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.0.dart
index 9e7e582..26d9e54 100644
--- a/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.0.dart
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.0.dart
@@ -3,6 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'macro.dart';
+import 'main_lib.dart';
 
 @MethodMacro()
-class Class {}
+class Class {
+  void existingMethod() {
+    existingMethodDependency();
+  }
+}
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.0.dart.expect b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.0.dart.expect
index 3b13cdd..977e49e 100644
--- a/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.0.dart.expect
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.0.dart.expect
@@ -2,23 +2,34 @@
 import self as self;
 import "macro.dart" as mac;
 import "dart:core" as core;
+import "main_lib.dart" as mai;
 
 import "org-dartlang-test:///a/b/c/user_macro/macro.dart";
+import "org-dartlang-test:///a/b/c/user_macro/main_lib.dart";
 
 @#C1
 class Class extends core::Object {
   synthetic constructor •() → self::Class
     : super core::Object::•()
     ;
+  method existingMethod() → void {
+    mai::existingMethodDependency();
+  }
 }
 
 library /*isNonNullableByDefault*/;
+import self as mai;
+
+static method existingMethodDependency() → void {}
+
+library /*isNonNullableByDefault*/;
 import self as mac;
 import "dart:core" as core;
 import "package:_fe_analyzer_shared/src/macros/api.dart" as api;
 
 import "dart:async";
 import "package:_fe_analyzer_shared/src/macros/api.dart";
+import "org-dartlang-test:///a/b/c/user_macro/macro_dependency.dart";
 
 macro class MethodMacro extends core::Object implements api::ClassDeclarationsMacro /*hasConstConstructor*/  {
   const constructor •() → mac::MethodMacro
@@ -28,6 +39,14 @@
   method buildDeclarationsForClass(api::ClassDeclaration clazz, api::ClassMemberDeclarationBuilder builder) → FutureOr<void> async {}
 }
 
+library /*isNonNullableByDefault*/;
+import self as self2;
+import "dart:core" as core;
+
+static method generateBody() → core::String {
+  return "  void method() {}\n";
+}
+
 constants  {
   #C1 = mac::MethodMacro {}
   #C2 = dart.core::_Override {}
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.1.dart.expect b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.1.dart.expect
index 9a9c376..88b94c0 100644
--- a/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.1.dart.expect
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.1.dart.expect
@@ -2,8 +2,10 @@
 import self as self;
 import "macro.dart" as mac;
 import "dart:core" as core;
+import "main_lib.dart" as mai;
 
 import "org-dartlang-test:///a/b/c/user_macro/macro.dart";
+import "org-dartlang-test:///a/b/c/user_macro/main_lib.dart";
 
 @#C1
 class Class extends core::Object {
@@ -11,15 +13,20 @@
     : super core::Object::•()
     ;
   method /* from org-dartlang-augmentation:/a/b/c/user_macro/main.dart-0 */ method() → void {}
+  method existingMethod() → void {
+    mai::existingMethodDependency();
+  }
 }
 
 library /*isNonNullableByDefault*/;
 import self as mac;
 import "dart:core" as core;
 import "package:_fe_analyzer_shared/src/macros/api.dart" as api;
+import "macro_dependency.dart" as mac2;
 
 import "dart:async";
 import "package:_fe_analyzer_shared/src/macros/api.dart";
+import "org-dartlang-test:///a/b/c/user_macro/macro_dependency.dart";
 
 macro class MethodMacro extends core::Object implements api::ClassDeclarationsMacro /*hasConstConstructor*/  {
   const constructor •() → mac::MethodMacro
@@ -27,7 +34,7 @@
     ;
   @#C2
   method buildDeclarationsForClass(api::ClassDeclaration clazz, api::ClassMemberDeclarationBuilder builder) → FutureOr<void> async {
-    builder.{api::ClassMemberDeclarationBuilder::declareInClass}(new api::DeclarationCode::fromString("  void method() {}\n")){(api::DeclarationCode) → void};
+    builder.{api::ClassMemberDeclarationBuilder::declareInClass}(new api::DeclarationCode::fromString(mac2::generateBody())){(api::DeclarationCode) → void};
   }
 }
 
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.2.dart.expect b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.2.dart.expect
new file mode 100644
index 0000000..39bdd89
--- /dev/null
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.2.dart.expect
@@ -0,0 +1,54 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "macro.dart" as mac;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-test:///a/b/c/user_macro/macro.dart";
+import "org-dartlang-test:///a/b/c/user_macro/main_lib.dart";
+
+@#C1
+class Class extends core::Object {
+  synthetic constructor •() → self::Class
+    : super core::Object::•()
+    ;
+  method /* from org-dartlang-augmentation:/a/b/c/user_macro/main.dart-0 */ method() → void {
+    core::print("method");
+  }
+  method existingMethod() → void {
+    mai::existingMethodDependency();
+  }
+}
+
+library /*isNonNullableByDefault*/;
+import self as mac;
+import "dart:core" as core;
+import "package:_fe_analyzer_shared/src/macros/api.dart" as api;
+import "macro_dependency.dart" as mac2;
+
+import "dart:async";
+import "package:_fe_analyzer_shared/src/macros/api.dart";
+import "org-dartlang-test:///a/b/c/user_macro/macro_dependency.dart";
+
+macro class MethodMacro extends core::Object implements api::ClassDeclarationsMacro /*hasConstConstructor*/  {
+  const constructor •() → mac::MethodMacro
+    : super core::Object::•()
+    ;
+  @#C2
+  method buildDeclarationsForClass(api::ClassDeclaration clazz, api::ClassMemberDeclarationBuilder builder) → FutureOr<void> async {
+    builder.{api::ClassMemberDeclarationBuilder::declareInClass}(new api::DeclarationCode::fromString(mac2::generateBody())){(api::DeclarationCode) → void};
+  }
+}
+
+library /*isNonNullableByDefault*/;
+import self as mac2;
+import "dart:core" as core;
+
+static method generateBody() → core::String {
+  return "  void method() {\n    print('method');\n  }\n";
+}
+
+constants  {
+  #C1 = mac::MethodMacro {}
+  #C2 = dart.core::_Override {}
+}
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.3.dart.expect b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.3.dart.expect
new file mode 100644
index 0000000..9e26179
--- /dev/null
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main.3.dart.expect
@@ -0,0 +1,8 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+static method existingMethodDependency() → void {
+  core::print("existingMethodDependency");
+}
+
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/main_lib.0.dart b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main_lib.0.dart
new file mode 100644
index 0000000..0dc9b98
--- /dev/null
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main_lib.0.dart
@@ -0,0 +1,5 @@
+// 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 file.
+
+void existingMethodDependency() {}
diff --git a/pkg/front_end/test/macros/incremental/data/tests/user_macro/main_lib.3.dart b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main_lib.3.dart
new file mode 100644
index 0000000..d574b8d
--- /dev/null
+++ b/pkg/front_end/test/macros/incremental/data/tests/user_macro/main_lib.3.dart
@@ -0,0 +1,7 @@
+// 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 file.
+
+void existingMethodDependency() {
+  print('existingMethodDependency');
+}
diff --git a/pkg/front_end/test/macros/incremental/incremental_macro_test.dart b/pkg/front_end/test/macros/incremental/incremental_macro_test.dart
index e6fd95e..7fb1af7 100644
--- a/pkg/front_end/test/macros/incremental/incremental_macro_test.dart
+++ b/pkg/front_end/test/macros/incremental/incremental_macro_test.dart
@@ -90,7 +90,10 @@
   CompilerOptions compilerOptions = new CompilerOptions()
     ..sdkRoot = computePlatformBinariesLocation(forceBuildDir: true)
     ..packagesFileUri = Platform.script.resolve('data/package_config.json')
-    ..explicitExperimentalFlags = {ExperimentalFlag.macros: true}
+    ..explicitExperimentalFlags = {
+      ExperimentalFlag.macros: true,
+      ExperimentalFlag.alternativeInvalidationStrategy: true,
+    }
     ..macroSerializer = macroSerializer
     ..precompiledMacroUris = {}
     ..macroExecutorProvider = () async {