Macro. Start implementing reusing cached results.
Change-Id: I35e145da373abb5d8b3065c7e1c5402434351905
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/368160
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 ac14344..becb113 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -41,6 +41,7 @@
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/uri_cache.dart';
import 'package:analyzer/src/workspace/workspace.dart';
+import 'package:collection/collection.dart';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:meta/meta.dart';
@@ -1731,6 +1732,9 @@
/// library uses any macros.
List<AugmentationImportWithFile> _macroImports = const [];
+ /// The cache for [apiSignature].
+ Uint8List? _apiSignature;
+
LibraryCycle? _libraryCycle;
LibraryFileKind({
@@ -1741,6 +1745,22 @@
file._fsState._libraryNameToFiles.add(this);
}
+ /// The unlinked API signature of all library files.
+ Uint8List get apiSignature {
+ if (_apiSignature case var apiSignature?) {
+ return apiSignature;
+ }
+
+ var builder = ApiSignature();
+
+ var sortedFiles = files.sortedBy((file) => file.path);
+ for (var file in sortedFiles) {
+ builder.addBytes(file.apiSignature);
+ }
+
+ return _apiSignature = builder.toByteList();
+ }
+
/// All augmentations of this library, in the depth-first pre-order order.
List<AugmentationFileKind> get augmentations {
var result = <AugmentationFileKind>[];
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_context.dart b/pkg/analyzer/lib/src/dart/analysis/library_context.dart
index 3aff823..cbde8c6 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_context.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_context.dart
@@ -26,6 +26,7 @@
import 'package:analyzer/src/summary2/link.dart';
import 'package:analyzer/src/summary2/linked_element_factory.dart';
import 'package:analyzer/src/summary2/macro.dart';
+import 'package:analyzer/src/summary2/macro_cache.dart';
import 'package:analyzer/src/summary2/reference.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:collection/collection.dart';
@@ -183,6 +184,16 @@
}
}
+ var macroResultKey = cycle.cachedMacrosKey;
+ var inputMacroResults = <LibraryFileKind, MacroResultInput>{};
+ if (byteStore.get(macroResultKey) case var bytes?) {
+ _readMacroResults(
+ cycle: cycle,
+ bytes: bytes,
+ macroResults: inputMacroResults,
+ );
+ }
+
var linkedBytes = byteStore.get(cycle.linkedKey);
if (linkedBytes == null) {
@@ -201,6 +212,7 @@
elementFactory: elementFactory,
performance: performance,
inputLibraries: cycle.libraries,
+ inputMacroResults: inputMacroResults,
macroExecutor: this.macroSupport?.executor,
);
},
@@ -216,6 +228,12 @@
testData?.forCycle(cycle).putKeys.add(cycle.linkedKey);
bytesPut += linkedBytes.length;
+ _writeMacroResults(
+ cycle: cycle,
+ linkResult: linkResult,
+ macroResultKey: macroResultKey,
+ );
+
librariesLinkedTimer.stop();
} else {
testData?.forCycle(cycle).getKeys.add(cycle.linkedKey);
@@ -346,6 +364,42 @@
}
}
+ /// Fills [macroResults] with results that can be reused.
+ void _readMacroResults({
+ required LibraryCycle cycle,
+ required Uint8List bytes,
+ required Map<LibraryFileKind, MacroResultInput> macroResults,
+ }) {
+ var bundle = MacroCacheBundle.fromBytes(cycle, bytes);
+ var testUsedList = <File>[];
+ for (var library in bundle.libraries) {
+ // If the library itself changed, then declarations that macros see
+ // could be different now.
+ if (!ListEquality<int>().equals(
+ library.apiSignature,
+ library.kind.apiSignature,
+ )) {
+ continue;
+ }
+
+ // TODO(scheglov): Record more specific dependencies.
+ if (library.hasAnyIntrospection) {
+ continue;
+ }
+
+ testUsedList.add(library.kind.file.resource);
+
+ macroResults[library.kind] = MacroResultInput(
+ code: library.code,
+ );
+ }
+
+ if (testUsedList.isNotEmpty) {
+ var cycleTestData = testData?.forCycle(cycle);
+ cycleTestData?.macrosUsedCached.add(testUsedList);
+ }
+ }
+
/// The [exception] was caught during the [cycle] linking.
///
/// Throw another exception that wraps the given one, with more information.
@@ -362,6 +416,37 @@
}
throw CaughtExceptionWithFiles(exception, stackTrace, fileContentMap);
}
+
+ void _writeMacroResults({
+ required LibraryCycle cycle,
+ required LinkResult linkResult,
+ required String macroResultKey,
+ }) {
+ var results = linkResult.macroResults;
+ if (results.isEmpty) {
+ return;
+ }
+
+ testData?.forCycle(cycle).macrosGenerated.add(
+ linkResult.macroResults
+ .map((result) => result.library.file.resource)
+ .toList(),
+ );
+
+ var bundle = MacroCacheBundle(
+ libraries: results.map((result) {
+ return MacroCacheLibrary(
+ kind: result.library,
+ apiSignature: result.library.apiSignature,
+ hasAnyIntrospection: result.processing.hasAnyIntrospection,
+ code: result.code,
+ );
+ }).toList(),
+ );
+
+ var bytes = bundle.toBytes();
+ byteStore.putGet(macroResultKey, bytes);
+ }
}
class LibraryContextTestData {
@@ -395,6 +480,8 @@
class LibraryCycleTestData {
final List<String> getKeys = [];
final List<String> putKeys = [];
+ final List<List<File>> macrosUsedCached = [];
+ final List<List<File>> macrosGenerated = [];
}
class _MacroFileEntry implements MacroFileEntry {
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
index 5958654..0fc73d4 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
@@ -7,9 +7,11 @@
import 'package:_fe_analyzer_shared/src/util/dependency_walker.dart' as graph
show DependencyWalker, Node;
+import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/summary/api_signature.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
+import 'package:collection/collection.dart';
/// Ensure that the [FileState.libraryCycle] for the [file] and anything it
/// depends on is computed.
@@ -59,6 +61,12 @@
/// include [implSignature] of the macro defining library.
final String implSignature;
+ /// The transitive macro implementation signature of this cycle.
+ ///
+ /// It is based on full code signatures of all files that might affect
+ /// macro implementation code.
+ final String implSignatureMacro;
+
late final bool declaresMacroClass = () {
for (var library in libraries) {
for (var file in library.files) {
@@ -96,12 +104,31 @@
required this.directDependencies,
required this.apiSignature,
required this.implSignature,
+ required this.implSignatureMacro,
}) {
for (var directDependency in directDependencies) {
directDependency.directUsers.add(this);
}
}
+ /// The key to store the bundle with multiple libraries, containing
+ /// potentially reusable macro generated code for each library.
+ String get cachedMacrosKey {
+ var builder = ApiSignature();
+ builder.addInt(AnalysisDriver.DATA_VERSION);
+
+ builder.addString(implSignatureMacro);
+
+ var sortedLibraries = libraries.sortedBy((l) => l.file.path);
+ for (var library in sortedLibraries) {
+ builder.addString(library.file.path);
+ builder.addString(library.file.uriStr);
+ }
+
+ var keyHex = builder.toHex();
+ return '$keyHex.macro_results';
+ }
+
/// The key of the linked libraries in the byte store.
String get linkedKey => '$apiSignature.linked';
@@ -193,8 +220,10 @@
void evaluateScc(List<_LibraryNode> scc) {
var apiSignature = ApiSignature();
var implSignature = ApiSignature();
+ var implSignature2 = ApiSignature();
apiSignature.addUint32List(_salt);
implSignature.addUint32List(_salt);
+ implSignature2.addUint32List(_salt);
// Sort libraries to produce stable signatures.
scc.sort((first, second) {
@@ -210,6 +239,7 @@
directDependencies,
apiSignature,
implSignature,
+ implSignature2,
graph.Node.getDependencies(node),
);
}
@@ -251,6 +281,7 @@
directDependencies: directDependencies,
apiSignature: apiSignature.toHex(),
implSignature: implSignature.toHex(),
+ implSignatureMacro: implSignature2.toHex(),
);
if (cycle.declaresMacroClass) {
@@ -271,6 +302,7 @@
Set<LibraryCycle> directDependencies,
ApiSignature apiSignature,
ApiSignature implSignature,
+ ApiSignature implSignatureMacro,
List<_LibraryNode> directlyReferenced,
) {
apiSignature.addInt(directlyReferenced.length);
@@ -282,11 +314,12 @@
if (referencedCycle == null) continue;
if (directDependencies.add(referencedCycle)) {
- apiSignature.addString(
- referencedCycle.declaresMacroClass
- ? referencedCycle.implSignature
- : referencedCycle.apiSignature,
- );
+ if (referencedCycle.declaresMacroClass) {
+ apiSignature.addString(referencedCycle.implSignature);
+ implSignatureMacro.addString(referencedCycle.implSignature);
+ } else {
+ apiSignature.addString(referencedCycle.apiSignature);
+ }
implSignature.addString(referencedCycle.implSignature);
}
}
diff --git a/pkg/analyzer/lib/src/summary2/library_builder.dart b/pkg/analyzer/lib/src/summary2/library_builder.dart
index 05c3704..c873379 100644
--- a/pkg/analyzer/lib/src/summary2/library_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/library_builder.dart
@@ -108,6 +108,12 @@
/// written or macro generated.
final Set<ConstFieldElementImpl> finalInstanceFields = Set.identity();
+ /// Set if the library reuses the cached macro result.
+ AugmentationImportWithFile? inputMacroAugmentationImport;
+
+ /// The sink for macro applying facts, for caching.
+ final MacroProcessing macroProcessing = MacroProcessing();
+
final List<List<macro.MacroExecutionResult>> _macroResults = [];
LibraryBuilder._({
@@ -460,6 +466,27 @@
return _augmentedBuilders[name];
}
+ MacroResultOutput? getCacheableMacroResult() {
+ // Nothing if we already reuse a cached result.
+ if (inputMacroAugmentationImport != null) {
+ return null;
+ }
+
+ var macroImport = kind.augmentationImports.lastOrNull;
+ if (macroImport is AugmentationImportWithFile) {
+ var importedFile = macroImport.importedFile;
+ if (importedFile.isMacroAugmentation) {
+ return MacroResultOutput(
+ library: kind,
+ processing: macroProcessing,
+ code: importedFile.content,
+ );
+ }
+ }
+
+ return null;
+ }
+
/// Merges accumulated [_macroResults] and corresponding macro augmentation
/// libraries into a single macro augmentation library.
Future<void> mergeMacroAugmentations({
@@ -738,6 +765,19 @@
_augmentationTargets[name] = augmentation;
}
+ /// Updates the element of the macro augmentation.
+ void updateInputMacroAugmentation() {
+ if (inputMacroAugmentationImport case var import?) {
+ var augmentation = element.augmentations.last;
+ var importedFile = import.importedFile;
+ var informativeBytes = importedFile.unlinked2.informativeBytes;
+ augmentation.macroGenerated = MacroGeneratedAugmentationLibrary(
+ code: importedFile.content,
+ informativeBytes: informativeBytes,
+ );
+ }
+ }
+
LibraryAugmentationElementImpl _addMacroAugmentation(
AugmentationImportWithFile state,
) {
@@ -1146,7 +1186,11 @@
}
}
- static void build(Linker linker, LibraryFileKind inputLibrary) {
+ static void build(
+ Linker linker,
+ LibraryFileKind inputLibrary,
+ MacroResultInput? inputMacroResult,
+ ) {
var elementFactory = linker.elementFactory;
var rootReference = linker.rootReference;
@@ -1288,6 +1332,14 @@
units: linkingUnits,
);
+ if (inputMacroResult != null) {
+ var import = inputLibrary.addMacroAugmentation(
+ inputMacroResult.code,
+ partialIndex: null,
+ );
+ builder.inputMacroAugmentationImport = import;
+ }
+
linker.builders[builder.uri] = builder;
}
diff --git a/pkg/analyzer/lib/src/summary2/link.dart b/pkg/analyzer/lib/src/summary2/link.dart
index 5c7c2f0..6c91105 100644
--- a/pkg/analyzer/lib/src/summary2/link.dart
+++ b/pkg/analyzer/lib/src/summary2/link.dart
@@ -26,6 +26,7 @@
import 'package:analyzer/src/summary2/types_builder.dart';
import 'package:analyzer/src/summary2/variance_builder.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/uri_cache.dart';
import 'package:macros/src/executor/multi_executor.dart' as macro;
@@ -33,15 +34,25 @@
required LinkedElementFactory elementFactory,
required OperationPerformanceImpl performance,
required List<LibraryFileKind> inputLibraries,
+ required Map<LibraryFileKind, MacroResultInput> inputMacroResults,
macro.MultiMacroExecutor? macroExecutor,
}) async {
var linker = Linker(elementFactory, macroExecutor);
await linker.link(
performance: performance,
inputLibraries: inputLibraries,
+ inputMacroResults: inputMacroResults,
);
+
+ var macroResultsOutput = <MacroResultOutput>[];
+ for (var builder in linker.builders.values) {
+ var result = builder.getCacheableMacroResult();
+ macroResultsOutput.addIfNotNull(result);
+ }
+
return LinkResult(
resolutionBytes: linker.resolutionBytes,
+ macroResults: macroResultsOutput,
);
}
@@ -93,9 +104,11 @@
Future<void> link({
required OperationPerformanceImpl performance,
required List<LibraryFileKind> inputLibraries,
+ required Map<LibraryFileKind, MacroResultInput> inputMacroResults,
}) async {
for (var inputLibrary in inputLibraries) {
- LibraryBuilder.build(this, inputLibrary);
+ var inputMacroResult = inputMacroResults[inputLibrary];
+ LibraryBuilder.build(this, inputLibrary, inputMacroResult);
}
await _buildOutlines(
@@ -195,7 +208,9 @@
);
for (var library in builders.values) {
- await library.fillMacroApplier(macroApplier);
+ if (library.inputMacroAugmentationImport == null) {
+ await library.fillMacroApplier(macroApplier);
+ }
}
return _macroApplier = macroApplier;
@@ -266,6 +281,9 @@
);
_disposeMacroApplications();
+ for (var library in builders.values) {
+ library.updateInputMacroAugmentation();
+ }
}
void _collectMixinSuperInvokedNames() {
@@ -457,7 +475,32 @@
class LinkResult {
final Uint8List resolutionBytes;
+ /// The results of applying macros in libraries.
+ final List<MacroResultOutput> macroResults;
+
LinkResult({
required this.resolutionBytes,
+ required this.macroResults,
+ });
+}
+
+class MacroResultInput {
+ final String code;
+
+ MacroResultInput({
+ required this.code,
+ });
+}
+
+/// The results of applying macros in [library].
+class MacroResultOutput {
+ final LibraryFileKind library;
+ final MacroProcessing processing;
+ final String code;
+
+ MacroResultOutput({
+ required this.library,
+ required this.processing,
+ required this.code,
});
}
diff --git a/pkg/analyzer/lib/src/summary2/macro_application.dart b/pkg/analyzer/lib/src/summary2/macro_application.dart
index 1def9f1..97a56bd 100644
--- a/pkg/analyzer/lib/src/summary2/macro_application.dart
+++ b/pkg/analyzer/lib/src/summary2/macro_application.dart
@@ -70,6 +70,7 @@
}
}
+// TODO(scheglov): The name is deceptive, this is not for a single library.
class LibraryMacroApplier {
@visibleForTesting
static bool testThrowExceptionTypes = false;
@@ -95,8 +96,12 @@
required OperationPerformanceImpl performance,
}) runDeclarationsPhase;
- /// The applications that currently run the declarations phase.
- final List<_MacroApplication> _declarationsPhaseRunning = [];
+ /// The applications that currently run.
+ ///
+ /// There can be more than one, because during the declarations phase we
+ /// can start introspecting another declaration, which runs the declarations
+ /// phase for that another declaration.
+ final List<_MacroApplication> _runningApplications = [];
/// The map from a declaration that has declarations phase introspection
/// cycle, to the cycle exception.
@@ -105,6 +110,7 @@
late final macro.TypePhaseIntrospector _typesPhaseIntrospector =
_TypePhaseIntrospector(
+ this,
elementFactory,
declarationBuilder,
OperationPerformanceImpl('<typesPhaseIntrospector>'),
@@ -118,6 +124,11 @@
required this.runDeclarationsPhase,
});
+ /// The currently running macro application.
+ _MacroApplication? get currentApplication {
+ return _runningApplications.lastOrNull;
+ }
+
Future<void> add({
required LibraryBuilder libraryBuilder,
required LibraryOrAugmentationElementImpl container,
@@ -292,10 +303,10 @@
required OperationPerformanceImpl performance,
}) async {
if (targetElement != null) {
- for (var i = 0; i < _declarationsPhaseRunning.length; i++) {
- var running = _declarationsPhaseRunning[i];
+ for (var i = 0; i < _runningApplications.length; i++) {
+ var running = _runningApplications[i];
if (running.target.element == targetElement) {
- var applications = _declarationsPhaseRunning.sublist(i);
+ var applications = _runningApplications.sublist(i);
var exception = _MacroIntrospectionCycleException(
'Declarations phase introspection cycle.',
applications: applications,
@@ -321,8 +332,7 @@
return null;
}
- _declarationsPhaseRunning.add(application);
-
+ _runningApplications.add(application);
var results = <macro.MacroExecutionResult>[];
await _runWithCatchingExceptions(
@@ -330,10 +340,10 @@
var target = _buildTarget(application.target.node);
var introspector = _DeclarationPhaseIntrospector(
+ this,
elementFactory,
declarationBuilder,
performance,
- this,
libraryBuilder.element.typeSystem,
);
@@ -356,7 +366,7 @@
annotationIndex: application.annotationIndex,
);
- _declarationsPhaseRunning.remove(application);
+ _runningApplications.removeLast();
return ApplicationResult(application, results);
}
@@ -371,6 +381,7 @@
return null;
}
+ _runningApplications.add(application);
var results = <macro.MacroExecutionResult>[];
await _runWithCatchingExceptions(
@@ -378,10 +389,10 @@
var target = _buildTarget(application.target.node);
var introspector = _DefinitionPhaseIntrospector(
+ this,
elementFactory,
declarationBuilder,
performance,
- this,
application.target.library.element.typeSystem,
);
@@ -404,6 +415,7 @@
annotationIndex: application.annotationIndex,
);
+ _runningApplications.removeLast();
return ApplicationResult(application, results);
}
@@ -417,6 +429,7 @@
return null;
}
+ _runningApplications.add(application);
var results = <macro.MacroExecutionResult>[];
await _runWithCatchingExceptions(
@@ -442,6 +455,7 @@
annotationIndex: application.annotationIndex,
);
+ _runningApplications.removeLast();
return ApplicationResult(application, results);
}
@@ -909,6 +923,11 @@
final List<_MacroApplication> _applications = [];
}
+/// Facts about applying macros in a library.
+class MacroProcessing {
+ bool hasAnyIntrospection = false;
+}
+
class _AnnotationMacro {
final LibraryElementImpl macroLibrary;
final BundleMacroExecutor bundleExecutor;
@@ -1048,14 +1067,13 @@
class _DeclarationPhaseIntrospector extends _TypePhaseIntrospector
implements macro.DeclarationPhaseIntrospector {
- final LibraryMacroApplier applier;
final TypeSystemImpl typeSystem;
_DeclarationPhaseIntrospector(
+ super.applier,
super.elementFactory,
super.declarationBuilder,
super.performance,
- this.applier,
this.typeSystem,
);
@@ -1068,6 +1086,7 @@
var element = (type as HasElement).element;
await _runDeclarationsPhase(element);
+ macroProcessing?.hasAnyIntrospection = true;
if (element case InterfaceElement(:var augmented)) {
return augmented.constructors
.map((e) => e.declaration as ConstructorElementImpl)
@@ -1089,6 +1108,7 @@
var element = (type as HasElement).element;
await _runDeclarationsPhase(element);
+ macroProcessing?.hasAnyIntrospection = true;
if (element case InstanceElement(:var augmented)) {
return augmented.fields
.whereNot((e) => e.isSynthetic)
@@ -1110,6 +1130,7 @@
var element = (type as HasElement).element;
await _runDeclarationsPhase(element);
+ macroProcessing?.hasAnyIntrospection = true;
if (element case InstanceElement(:var augmented)) {
return [
...augmented.accessors.whereNot((e) => e.isSynthetic),
@@ -1127,6 +1148,7 @@
@override
Future<macro.StaticType> resolve(macro.TypeAnnotationCode typeCode) async {
performance.getDataInt('resolve').increment();
+ macroProcessing?.hasAnyIntrospection = true;
var type = declarationBuilder.resolveType(typeCode);
return _StaticTypeImpl(typeSystem, type);
}
@@ -1136,6 +1158,7 @@
macro.Identifier identifier,
) async {
performance.getDataInt('typeDeclarationOf').increment();
+ macroProcessing?.hasAnyIntrospection = true;
return declarationBuilder.typeDeclarationOf(identifier);
}
@@ -1143,6 +1166,7 @@
Future<List<macro.TypeDeclaration>> typesOf(
covariant LibraryImplFromElement library,
) async {
+ macroProcessing?.hasAnyIntrospection = true;
return library.element.topLevelElements
.map((e) => declarationBuilder.declarationOfElement(e))
.whereType<macro.TypeDeclaration>()
@@ -1157,6 +1181,8 @@
await _runDeclarationsPhase(element);
element as EnumElementImpl;
+ macroProcessing?.hasAnyIntrospection = true;
+
// TODO(scheglov): use augmented
return element.constants
.map(declarationBuilder.declarationOfElement)
@@ -1166,7 +1192,7 @@
Future<void> _runDeclarationsPhase(ElementImpl element) async {
// Don't run for the current element.
- var current = applier._declarationsPhaseRunning.lastOrNull;
+ var current = applier._runningApplications.lastOrNull;
if (current?.target.element == element) {
return;
}
@@ -1189,10 +1215,10 @@
class _DefinitionPhaseIntrospector extends _DeclarationPhaseIntrospector
implements macro.DefinitionPhaseIntrospector {
_DefinitionPhaseIntrospector(
+ super.applier,
super.elementFactory,
super.declarationBuilder,
super.performance,
- super.applier,
super.typeSystem,
);
@@ -1214,6 +1240,7 @@
Future<List<macro.Declaration>> topLevelDeclarationsOf(
covariant LibraryImplFromElement library,
) async {
+ macroProcessing?.hasAnyIntrospection = true;
return library.element.topLevelElements
.whereNot((e) => e.isSynthetic)
.map((e) => declarationBuilder.declarationOfElement(e))
@@ -1289,24 +1316,33 @@
}
class _TypePhaseIntrospector implements macro.TypePhaseIntrospector {
+ final LibraryMacroApplier applier;
final LinkedElementFactory elementFactory;
final DeclarationBuilder declarationBuilder;
final OperationPerformanceImpl performance;
_TypePhaseIntrospector(
+ this.applier,
this.elementFactory,
this.declarationBuilder,
this.performance,
);
+ MacroProcessing? get macroProcessing {
+ return applier.currentApplication?.target.library.macroProcessing;
+ }
+
@override
Future<macro.Identifier> resolveIdentifier(Uri library, String name) async {
var libraryElement = elementFactory.libraryOfUri2(library);
+ macroProcessing?.hasAnyIntrospection = true;
+
var lookup = libraryElement.scope.lookup(name);
var element = lookup.getter ?? lookup.setter;
if (element is PropertyAccessorElement && element.isSynthetic) {
element = element.variable2;
}
+
if (element == null) {
throw macro.MacroImplementationExceptionImpl(
[
@@ -1317,6 +1353,7 @@
stackTrace: StackTrace.current.toString(),
);
}
+
return declarationBuilder.fromElement.identifier(element);
}
}
diff --git a/pkg/analyzer/lib/src/summary2/macro_cache.dart b/pkg/analyzer/lib/src/summary2/macro_cache.dart
new file mode 100644
index 0000000..866c916
--- /dev/null
+++ b/pkg/analyzer/lib/src/summary2/macro_cache.dart
@@ -0,0 +1,93 @@
+// 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 'dart:typed_data';
+
+import 'package:analyzer/src/dart/analysis/file_state.dart';
+import 'package:analyzer/src/dart/analysis/library_graph.dart';
+import 'package:analyzer/src/summary2/data_reader.dart';
+import 'package:analyzer/src/summary2/data_writer.dart';
+
+class MacroCacheBundle {
+ final List<MacroCacheLibrary> libraries;
+
+ MacroCacheBundle({
+ required this.libraries,
+ });
+
+ factory MacroCacheBundle.fromBytes(
+ LibraryCycle cycle,
+ Uint8List bytes,
+ ) {
+ return MacroCacheBundle.read(
+ cycle,
+ SummaryDataReader(bytes),
+ );
+ }
+
+ factory MacroCacheBundle.read(
+ LibraryCycle cycle,
+ SummaryDataReader reader,
+ ) {
+ return MacroCacheBundle(
+ libraries: reader.readTypedList(
+ () => MacroCacheLibrary.read(cycle, reader),
+ ),
+ );
+ }
+
+ Uint8List toBytes() {
+ var byteSink = ByteSink();
+ var sink = BufferedSink(byteSink);
+ write(sink);
+ return sink.flushAndTake();
+ }
+
+ void write(BufferedSink sink) {
+ sink.writeList(libraries, (library) {
+ library.write(sink);
+ });
+ }
+}
+
+class MacroCacheLibrary {
+ /// The file view of the library.
+ final LibraryFileKind kind;
+
+ /// The combination of API signatures of all library files.
+ final Uint8List apiSignature;
+
+ /// Whether any macro of the library introspected anything.
+ final bool hasAnyIntrospection;
+
+ final String code;
+
+ MacroCacheLibrary({
+ required this.kind,
+ required this.apiSignature,
+ required this.hasAnyIntrospection,
+ required this.code,
+ });
+
+ factory MacroCacheLibrary.read(
+ LibraryCycle cycle,
+ SummaryDataReader reader,
+ ) {
+ var path = reader.readStringUtf8();
+ return MacroCacheLibrary(
+ // This is safe because the key of the bundle depends on paths.
+ kind: cycle.libraries.firstWhere((e) => e.file.path == path),
+ apiSignature: reader.readUint8List(),
+ hasAnyIntrospection: reader.readBool(),
+ code: reader.readStringUtf8(),
+ );
+ }
+
+ void write(BufferedSink sink) {
+ sink.writeStringUtf8(kind.file.path);
+ sink.writeUint8List(apiSignature);
+ sink.writeBool(hasAnyIntrospection);
+ sink.writeStringUtf8(code);
+ }
+}
diff --git a/pkg/analyzer/test/src/summary/elements_base.dart b/pkg/analyzer/test/src/summary/elements_base.dart
index 3344525..2877a12 100644
--- a/pkg/analyzer/test/src/summary/elements_base.dart
+++ b/pkg/analyzer/test/src/summary/elements_base.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -25,12 +26,12 @@
newFile(path, contents);
}
- Future<LibraryElementImpl> buildLibrary(String text) async {
- var file = newFile(testFile.path, text);
+ Future<LibraryElementImpl> buildFileLibrary(File file) async {
var analysisContext = contextFor(file);
var analysisSession = analysisContext.currentSession;
- var uriStr = 'package:test/test.dart';
+ var uri = analysisSession.uriConverter.pathToUri(file.path);
+ var uriStr = uri.toString();
var libraryResult = await analysisSession.getLibraryByUri(uriStr);
if (keepLinkingLibraries) {
@@ -43,6 +44,11 @@
}
}
+ Future<LibraryElementImpl> buildLibrary(String text) async {
+ var file = newFile(testFile.path, text);
+ return await buildFileLibrary(file);
+ }
+
void checkElementText(LibraryElementImpl library, String expected) {
var actual = getLibraryText(
library: library,
diff --git a/pkg/analyzer/test/src/summary/macro_test.dart b/pkg/analyzer/test/src/summary/macro_test.dart
index c0a4645..6b7091f 100644
--- a/pkg/analyzer/test/src/summary/macro_test.dart
+++ b/pkg/analyzer/test/src/summary/macro_test.dart
@@ -15,12 +15,15 @@
import 'package:analyzer/src/summary2/macro.dart';
import 'package:analyzer/src/summary2/macro_application.dart';
import 'package:analyzer/src/summary2/macro_application_error.dart';
+import 'package:analyzer/src/utilities/extensions/file_system.dart';
+import 'package:collection/collection.dart';
import 'package:macros/src/bootstrap.dart' as macro;
import 'package:macros/src/executor/serialization.dart' as macro;
import 'package:path/path.dart' as package_path;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import '../../util/tree_string_sink.dart';
import '../dart/resolution/context_collection_resolution.dart';
import '../dart/resolution/node_text_expectations.dart';
import 'element_text.dart';
@@ -38,6 +41,7 @@
defineReflectiveSuite(() {
defineReflectiveTests(MacroArgumentsTest);
+ defineReflectiveTests(MacroIncrementalTest);
defineReflectiveTests(MacroIntrospectNodeTest);
defineReflectiveTests(MacroIntrospectNodeDefinitionsTest);
defineReflectiveTests(MacroIntrospectElementTest);
@@ -9148,6 +9152,222 @@
}
@reflectiveTest
+class MacroIncrementalTest extends MacroElementsBaseTest {
+ @override
+ bool get keepLinkingLibraries => true;
+
+ @override
+ bool get retainDataForTesting => true;
+
+ @override
+ Future<void> setUp() async {
+ await super.setUp();
+ useEmptyByteStore();
+ }
+
+ test_changeDependency_noIntrospect() async {
+ var a = newFile('$testPackageLibPath/a.dart', r'''
+class A {}
+''');
+
+ var library1 = await buildLibrary(r'''
+import 'append.dart';
+import 'a.dart';
+
+@DeclareInType(' void foo() {}')
+class X {}
+''');
+
+ var expectedLibraryText = r'''
+library
+ imports
+ package:test/append.dart
+ package:test/a.dart
+ definingUnit
+ classes
+ class X @80
+ augmentation: self::@augmentation::package:test/test.macro.dart::@classAugmentation::X
+ augmented
+ methods
+ self::@augmentation::package:test/test.macro.dart::@classAugmentation::X::@method::foo
+ augmentationImports
+ package:test/test.macro.dart
+ macroGeneratedCode
+---
+augment library 'package:test/test.dart';
+
+augment class X {
+ void foo() {}
+}
+---
+ definingUnit
+ classes
+ augment class X @57
+ augmentationTarget: self::@class::X
+ methods
+ foo @68
+ returnType: void
+''';
+
+ configuration
+ ..withConstructors = false
+ ..withMetadata = false;
+ checkElementText(library1, expectedLibraryText);
+
+ // Generated and put into the cache.
+ _assertMacroCachedGenerated(testFile, r'''
+/home/test/lib/test.dart
+ usedCached
+ generated
+ /home/test/lib/test.dart
+''');
+
+ modifyFile2(a, r'''
+class A2 {}
+''');
+ driverFor(testFile).changeFile2(a);
+ await contextFor(testFile).applyPendingFileChanges();
+
+ var library2 = await buildFileLibrary(testFile);
+ checkElementText(library2, expectedLibraryText);
+
+ // Used cached, no new generated.
+ _assertMacroCachedGenerated(testFile, r'''
+/home/test/lib/test.dart
+ usedCached
+ /home/test/lib/test.dart
+ generated
+ /home/test/lib/test.dart
+''');
+ }
+
+ test_resolveIdentifier_class_notChanged() async {
+ var a = newFile('$testPackageLibPath/a.dart', r'''
+class A {}
+''');
+
+ var library1 = await buildLibrary(r'''
+import 'append.dart';
+import 'a.dart';
+
+@DeclareInType(' {{package:test/test.dart@A}} foo() {}')
+class X {}
+''');
+
+ var expectedLibraryText = r'''
+library
+ imports
+ package:test/append.dart
+ package:test/a.dart
+ definingUnit
+ classes
+ class X @104
+ augmentation: self::@augmentation::package:test/test.macro.dart::@classAugmentation::X
+ augmented
+ methods
+ self::@augmentation::package:test/test.macro.dart::@classAugmentation::X::@method::foo
+ augmentationImports
+ package:test/test.macro.dart
+ macroGeneratedCode
+---
+augment library 'package:test/test.dart';
+
+import 'package:test/a.dart' as prefix0;
+
+augment class X {
+ prefix0.A foo() {}
+}
+---
+ imports
+ package:test/a.dart as prefix0 @75
+ definingUnit
+ classes
+ augment class X @99
+ augmentationTarget: self::@class::X
+ methods
+ foo @115
+ returnType: A
+''';
+
+ configuration
+ ..withConstructors = false
+ ..withMetadata = false;
+ checkElementText(library1, expectedLibraryText);
+
+ // Generated and put into the cache.
+ _assertMacroCachedGenerated(testFile, r'''
+/home/test/lib/test.dart
+ usedCached
+ generated
+ /home/test/lib/test.dart
+''');
+
+ modifyFile2(a, r'''
+class A {}
+class B {}
+''');
+ driverFor(testFile).changeFile2(a);
+ await contextFor(testFile).applyPendingFileChanges();
+
+ var library2 = await buildFileLibrary(testFile);
+ checkElementText(library2, expectedLibraryText);
+
+ // Cannot prove that `package:test/test.dart@A` is still there.
+ // So, not used cached.
+ // TODO(scheglov): Make it use cached.
+ _assertMacroCachedGenerated(testFile, r'''
+/home/test/lib/test.dart
+ usedCached
+ generated
+ /home/test/lib/test.dart
+ /home/test/lib/test.dart
+''');
+ }
+
+ void _assertMacroCachedGenerated(File targetFile, String expected) {
+ var buffer = StringBuffer();
+ var sink = TreeStringSink(
+ sink: buffer,
+ indent: '',
+ );
+
+ var testData = driverFor(testFile).testView!.libraryContext;
+ for (var entry in testData.libraryCycles.entries) {
+ if (entry.key.map((fd) => fd.file).contains(targetFile)) {
+ var fileListStr = entry.key
+ .map((fileData) => fileData.file.posixPath)
+ .sorted()
+ .join(' ');
+ sink.writelnWithIndent(fileListStr);
+ sink.withIndent(() {
+ sink.writelnWithIndent('usedCached');
+ sink.withIndent(() {
+ for (var files in entry.value.macrosUsedCached) {
+ sink.writelnWithIndent(files.map((f) => f.posixPath).join(' '));
+ }
+ });
+
+ sink.writelnWithIndent('generated');
+ sink.withIndent(() {
+ for (var files in entry.value.macrosGenerated) {
+ sink.writelnWithIndent(files.map((f) => f.posixPath).join(' '));
+ }
+ });
+ });
+ }
+ }
+
+ var actual = buffer.toString();
+ if (actual != expected) {
+ print('-------- Actual --------');
+ print('$actual------------------------');
+ NodeTextExpectationsCollector.add(actual);
+ }
+ expect(actual, expected);
+ }
+}
+
+@reflectiveTest
class MacroIntrospectElementTest extends MacroElementsBaseTest {
@override
bool get keepLinkingLibraries => true;