Use implementation (full content) based signatures for results that might be affected by macros.
Change-Id: I5106930ac904a99e674cba7e3ed1ecd29b104d1d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241861
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index e2a6383..618627c 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -331,7 +331,7 @@
String get transitiveSignature {
var librarySignatureBuilder = ApiSignature()
..addString(uriStr)
- ..addString(libraryCycle.transitiveSignature);
+ ..addString(libraryCycle.apiSignature);
return librarySignatureBuilder.toHex();
}
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_context.dart b/pkg/analyzer/lib/src/dart/analysis/library_context.dart
index f19c23c..3d44aa2 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_context.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_context.dart
@@ -160,7 +160,7 @@
}
}
- var resolutionKey = '${cycle.transitiveSignature}.linked_bundle';
+ var resolutionKey = '${cycle.apiSignature}.linked_bundle';
var resolutionBytes = byteStore.get(resolutionKey);
if (resolutionBytes == null) {
@@ -244,7 +244,7 @@
final macroKernelBuilder = this.macroKernelBuilder;
if (macroKernelBuilder != null && macroLibraries.isNotEmpty) {
- var macroKernelKey = '${cycle.transitiveSignature}.macro_kernel';
+ var macroKernelKey = '${cycle.implSignature}.macro_kernel';
var macroKernelBytes = byteStore.get(macroKernelKey);
if (macroKernelBytes == null) {
macroKernelBytes = await macroKernelBuilder.build(
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
index 1a4ed51..1457c6b 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
@@ -28,18 +28,46 @@
/// The cycles that use this cycle, used to [invalidate] transitively.
final List<LibraryCycle> _directUsers = [];
- /// The transitive signature of this cycle.
+ /// The transitive API signature of this cycle.
///
/// It is based on the API signatures of all files of the [libraries], and
- /// transitive signatures of the cycles that the [libraries] reference
- /// directly. So, indirectly it is based on the transitive closure of all
- /// files that [libraries] reference (but we don't compute these files).
- String transitiveSignature;
+ /// API signatures of the cycles that the [libraries] reference directly.
+ /// So, indirectly it is based on API signatures of the transitive closure
+ /// of all files that [libraries] reference.
+ String apiSignature;
+
+ /// The transitive implementation signature of this cycle.
+ ///
+ /// It is based on the full code signatures of all files of the [libraries],
+ /// and full code signatures of the cycles that the [libraries] reference
+ /// directly. So, indirectly it is based on full code signatures of the
+ /// transitive closure of all files that [libraries] reference.
+ ///
+ /// Usually, when a library is imported we need its [apiSignature], because
+ /// its API is all we can see from outside. But if the library contains
+ /// a macro, and we use it, we run full code of the macro defining library,
+ /// potentially executing every method body of the transitive closure of
+ /// the libraries imported by the macro defining library. So, the resulting
+ /// library (that imports a macro defining library) API signature must
+ /// include [implSignature] of the macro defining library.
+ String implSignature;
+
+ late final bool hasMacroClass = () {
+ for (final library in libraries) {
+ for (final file in library.libraryFiles) {
+ if (file.unlinked2.macroClasses.isNotEmpty) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }();
LibraryCycle({
required this.libraries,
required this.directDependencies,
- required this.transitiveSignature,
+ required this.apiSignature,
+ required this.implSignature,
}) {
for (var directDependency in directDependencies) {
directDependency._directUsers.add(this);
@@ -99,8 +127,10 @@
@override
void evaluateScc(List<_LibraryNode> scc) {
- var signature = ApiSignature();
- signature.addUint32List(_salt);
+ var apiSignature = ApiSignature();
+ var implSignature = ApiSignature();
+ apiSignature.addUint32List(_salt);
+ implSignature.addUint32List(_salt);
// Sort libraries to produce stable signatures.
scc.sort((first, second) {
@@ -115,7 +145,8 @@
var file = node.file;
_appendDirectlyReferenced(
directDependencies,
- signature,
+ apiSignature,
+ implSignature,
file.directReferencedLibraries.whereNotNull().toList(),
);
}
@@ -125,13 +156,22 @@
for (var node in scc) {
libraries.add(node.file);
- signature.addLanguageVersion(node.file.packageLanguageVersion);
- signature.addString(node.file.uriStr);
+ apiSignature.addLanguageVersion(node.file.packageLanguageVersion);
+ apiSignature.addString(node.file.uriStr);
- signature.addInt(node.file.libraryFiles.length);
+ implSignature.addLanguageVersion(node.file.packageLanguageVersion);
+ implSignature.addString(node.file.uriStr);
+
+ apiSignature.addInt(node.file.libraryFiles.length);
for (var file in node.file.libraryFiles) {
- signature.addBool(file.exists);
- signature.addBytes(file.apiSignature);
+ apiSignature.addBool(file.exists);
+ apiSignature.addBytes(file.apiSignature);
+ }
+
+ implSignature.addInt(node.file.libraryFiles.length);
+ for (var file in node.file.libraryFiles) {
+ implSignature.addBool(file.exists);
+ implSignature.addString(file.contentHash);
}
}
@@ -139,7 +179,8 @@
var cycle = LibraryCycle(
libraries: libraries,
directDependencies: directDependencies,
- transitiveSignature: signature.toHex(),
+ apiSignature: apiSignature.toHex(),
+ implSignature: implSignature.toHex(),
);
// Set the instance into the libraries.
@@ -154,10 +195,12 @@
void _appendDirectlyReferenced(
Set<LibraryCycle> directDependencies,
- ApiSignature signature,
+ ApiSignature apiSignature,
+ ApiSignature implSignature,
List<FileState> directlyReferenced,
) {
- signature.addInt(directlyReferenced.length);
+ apiSignature.addInt(directlyReferenced.length);
+ implSignature.addInt(directlyReferenced.length);
for (var referencedLibrary in directlyReferenced) {
var referencedCycle = referencedLibrary.internal_libraryCycle;
@@ -165,7 +208,12 @@
if (referencedCycle == null) continue;
if (directDependencies.add(referencedCycle)) {
- signature.addString(referencedCycle.transitiveSignature);
+ apiSignature.addString(
+ referencedCycle.hasMacroClass
+ ? referencedCycle.implSignature
+ : referencedCycle.apiSignature,
+ );
+ implSignature.addString(referencedCycle.implSignature);
}
}
}
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
index 28a1626..648a942 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
@@ -5,11 +5,15 @@
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
+import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/error/codes.dart';
+import 'package:analyzer/src/summary2/kernel_compilation_service.dart';
+import 'package:analyzer/src/summary2/macro.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import '../../summary/macros_environment.dart';
import '../resolution/context_collection_resolution.dart';
main() {
@@ -20,6 +24,11 @@
@reflectiveTest
class AnalysisDriverCachingTest extends PubPackageResolutionTest {
+ @override
+ MacroKernelBuilder? get macroKernelBuilder {
+ return FrontEndServerMacroKernelBuilder();
+ }
+
String get testFilePathPlatform => convertPath(testFilePath);
List<Set<String>> get _linkedCycles {
@@ -27,6 +36,24 @@
return driver.test.libraryContextTestView.linkedCycles;
}
+ @override
+ void setUp() {
+ super.setUp();
+
+ writeTestPackageConfig(
+ PackageConfigFileBuilder(),
+ macrosEnvironment: MacrosEnvironment.instance,
+ );
+ }
+
+ @override
+ Future<void> tearDown() async {
+ await super.tearDown();
+ KernelCompilationService.disposeDelayed(
+ const Duration(milliseconds: 100),
+ );
+ }
+
test_analysisOptions_strictCasts() async {
useEmptyByteStore();
@@ -340,6 +367,180 @@
_assertNoLinkedCycles();
}
+ test_macro_libraryElement_changeMacroCode() async {
+ File newFileWithFixedNameMacro(String className) {
+ return newFile('$testPackageLibPath/my_macro.dart', '''
+import 'dart:async';
+import 'package:_fe_analyzer_shared/src/macros/api.dart';
+
+macro class MyMacro implements ClassTypesMacro {
+ const MyMacro();
+
+ FutureOr<void> buildTypesForClass(clazz, builder) {
+ builder.declareType(
+ '$className',
+ DeclarationCode.fromString('class $className {}'),
+ );
+ }
+}
+''');
+ }
+
+ final macroFile = newFileWithFixedNameMacro('MacroA');
+
+ var a = newFile('$testPackageLibPath/a.dart', r'''
+import 'my_macro.dart';
+
+@MyMacro()
+class A {}
+''');
+
+ var b = newFile('$testPackageLibPath/b.dart', r'''
+export 'a.dart';
+''');
+
+ final analysisContext = contextFor(macroFile.path);
+
+ Future<LibraryElement> getLibrary(String uriStr) async {
+ final result = await analysisContext.currentSession
+ .getLibraryByUri(uriStr) as LibraryElementResult;
+ return result.element;
+ }
+
+ // This macro generates `MacroA`, but not `MacroB`.
+ {
+ final libraryA = await getLibrary('package:test/a.dart');
+ expect(libraryA.getType('MacroA'), isNotNull);
+ expect(libraryA.getType('MacroB'), isNull);
+ // This propagates transitively.
+ final libraryB = await getLibrary('package:test/b.dart');
+ expect(libraryB.exportNamespace.get('MacroA'), isNotNull);
+ expect(libraryB.exportNamespace.get('MacroB'), isNull);
+ }
+
+ _assertContainsLinkedCycle({a.path});
+ _assertContainsLinkedCycle({b.path}, andClear: true);
+
+ // The macro will generate `MacroB`.
+ newFileWithFixedNameMacro('MacroB');
+
+ // Notify about changes.
+ analysisContext.changeFile(macroFile.path);
+ await analysisContext.applyPendingFileChanges();
+
+ // This macro generates `MacroB`, but not `MacroA`.
+ {
+ final libraryA = await getLibrary('package:test/a.dart');
+ expect(libraryA.getType('MacroA'), isNull);
+ expect(libraryA.getType('MacroB'), isNotNull);
+ // This propagates transitively.
+ final libraryB = await getLibrary('package:test/b.dart');
+ expect(libraryB.exportNamespace.get('MacroA'), isNull);
+ expect(libraryB.exportNamespace.get('MacroB'), isNotNull);
+ }
+
+ _assertContainsLinkedCycle({a.path});
+ _assertContainsLinkedCycle({b.path}, andClear: true);
+ }
+
+ test_macro_resolvedUnit_changeCodeUsedByMacro() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+String getClassName() => 'MacroA';
+''');
+
+ newFile('$testPackageLibPath/my_macro.dart', r'''
+import 'dart:async';
+import 'package:_fe_analyzer_shared/src/macros/api.dart';
+import 'a.dart';
+
+macro class MyMacro implements ClassTypesMacro {
+ const MyMacro();
+
+ FutureOr<void> buildTypesForClass(clazz, builder) {
+ final className = getClassName();
+ builder.declareType(
+ '$className',
+ DeclarationCode.fromString('class $className {}'),
+ );
+ }
+}
+''');
+
+ // The macro will generate `MacroA`, so no errors.
+ await assertNoErrorsInCode('''
+import 'my_macro.dart';
+
+@MyMacro()
+class A {}
+
+void f(MacroA a) {}
+''');
+
+ // The macro will generate `MacroB`.
+ newFile(a.path, r'''
+String getClassName() => 'MacroB';
+''');
+
+ // Notify about changes.
+ var analysisContext = contextFor(a.path);
+ analysisContext.changeFile(a.path);
+ await analysisContext.applyPendingFileChanges();
+
+ // Resolve the test file, it still references `MacroA`, but the macro
+ // generates `MacroB` now, so we have an error.
+ await resolveTestFile();
+ assertErrorsInResult([
+ error(CompileTimeErrorCode.UNDEFINED_CLASS, 55, 6),
+ ]);
+ }
+
+ test_macro_resolvedUnit_changeMacroCode() async {
+ File newFileWithFixedNameMacro(String className) {
+ return newFile('$testPackageLibPath/my_macro.dart', '''
+import 'dart:async';
+import 'package:_fe_analyzer_shared/src/macros/api.dart';
+
+macro class MyMacro implements ClassTypesMacro {
+ const MyMacro();
+
+ FutureOr<void> buildTypesForClass(clazz, builder) {
+ builder.declareType(
+ '$className',
+ DeclarationCode.fromString('class $className {}'),
+ );
+ }
+}
+''');
+ }
+
+ var macroFile = newFileWithFixedNameMacro('MacroA');
+
+ // The macro will generate `MacroA`, so no errors.
+ await assertNoErrorsInCode('''
+import 'my_macro.dart';
+
+@MyMacro()
+class A {}
+
+void f(MacroA a) {}
+''');
+
+ // The macro will generate `MacroB`.
+ newFileWithFixedNameMacro('MacroB');
+
+ // Notify about changes.
+ var analysisContext = contextFor(macroFile.path);
+ analysisContext.changeFile(macroFile.path);
+ await analysisContext.applyPendingFileChanges();
+
+ // Resolve the test file, it still references `MacroA`, but the updated
+ // macro generates `MacroB` now, so we have an error.
+ await resolveTestFile();
+ assertErrorsInResult([
+ error(CompileTimeErrorCode.UNDEFINED_CLASS, 55, 6),
+ ]);
+ }
+
void _assertContainsLinkedCycle(Set<String> expectedPosix,
{bool andClear = false}) {
var expected = expectedPosix.map(convertPath).toSet();
diff --git a/pkg/analyzer/test/src/dart/resolution/macro_test.dart b/pkg/analyzer/test/src/dart/resolution/macro_test.dart
index 0178ec0..64b9e01 100644
--- a/pkg/analyzer/test/src/dart/resolution/macro_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/macro_test.dart
@@ -33,9 +33,6 @@
void setUp() {
super.setUp();
- // TODO(scheglov) Dependency tracking for macros is not right yet.
- useEmptyByteStore();
-
writeTestPackageConfig(
PackageConfigFileBuilder(),
macrosEnvironment: MacrosEnvironment.instance,