Macro. Keep FileState for macro file, refresh if its content changes.
Bug: https://github.com/dart-lang/sdk/issues/54713
Change-Id: I8311bddc77ea3535a66e26e241c9bac8f51d713c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/348367
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 3a0f311..c279d06 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -1838,25 +1838,33 @@
final macroRelativeUri = uriCache.parse(macroFileName);
final macroUri = uriCache.resolveRelative(file.uri, macroRelativeUri);
- // Normally this should not happen.
- // But it happens, when LSP asks for the file while we linking.
- if (file._fsState._uriToFile[macroUri] case var existing?) {
- _disposeMacroFile(existing);
- }
-
final contentBytes = utf8.encoder.convert(augmentationContent);
final hashBytes = md5.convert(contentBytes).bytes;
final hashStr = hex.encode(hashBytes);
- file._fsState._macroFileContent = StoredFileContent(
+ final fileContent = StoredFileContent(
content: augmentationContent,
contentHash: hashStr,
exists: true,
);
+ // This content will be consumed by the next `refresh()`.
+ // This might happen during `getFileForUri()` below.
+ // Or this happens during the explicit `refresh()`, more below.
+ file._fsState._macroFileContent = fileContent;
+
final macroFileResolution = file._fsState.getFileForUri(macroUri);
macroFileResolution as UriResolutionFile;
final macroFile = macroFileResolution.file;
+ // If the file existed, and has different content, force `refresh()`.
+ // This will ensure that the file has the required content.
+ if (macroFile.content != fileContent.content) {
+ macroFile.refresh();
+ }
+
+ // We are done with the file, stop forcing its content.
+ file._fsState._macroFileContent = null;
+
final import = AugmentationImportWithFile(
container: this,
unlinked: UnlinkedAugmentationImportDirective(
@@ -1906,12 +1914,14 @@
/// macros might potentially generate different code, or no code at all. So,
/// we discard the existing macro augmentation library, it will be rebuilt
/// during linking.
- void disposeMacroAugmentations() {
+ void disposeMacroAugmentations({
+ required bool disposeFiles,
+ }) {
for (final macroImport in _macroImports) {
_augmentationImports = augmentationImports.withoutLast.toFixedList();
- // Discard the file.
- final macroFile = macroImport.importedFile;
- _disposeMacroFile(macroFile);
+ if (disposeFiles) {
+ _disposeMacroFile(macroImport.importedFile);
+ }
}
_macroImports = const [];
}
@@ -1929,7 +1939,8 @@
void internal_setLibraryCycle(LibraryCycle? cycle) {
_libraryCycle = cycle;
- disposeMacroAugmentations();
+ // Keep the merged augmentation file, as we do for normal files.
+ disposeMacroAugmentations(disposeFiles: false);
}
@override
diff --git a/pkg/analyzer/lib/src/summary2/library_builder.dart b/pkg/analyzer/lib/src/summary2/library_builder.dart
index a9ecf96..c795e2a 100644
--- a/pkg/analyzer/lib/src/summary2/library_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/library_builder.dart
@@ -466,7 +466,7 @@
return;
}
- kind.disposeMacroAugmentations();
+ kind.disposeMacroAugmentations(disposeFiles: true);
// Remove import for partial macro augmentations.
element.augmentationImports = element.augmentationImports
diff --git a/pkg/analyzer/test/src/summary/macro_test.dart b/pkg/analyzer/test/src/summary/macro_test.dart
index ec6badf..fdea434 100644
--- a/pkg/analyzer/test/src/summary/macro_test.dart
+++ b/pkg/analyzer/test/src/summary/macro_test.dart
@@ -13,6 +13,7 @@
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/src/dart/analysis/results.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/summary2/macro.dart';
import 'package:analyzer/src/summary2/macro_application_error.dart';
@@ -1558,8 +1559,8 @@
''');
}
- test_macroGeneratedFileByName_beforeLinking() async {
- // See https://dart-review.googlesource.com/c/sdk/+/348202
+ test_macroGeneratedFile_existedBeforeLinking() async {
+ // See https://github.com/dart-lang/sdk/issues/54713
// Create `FileState` with the same name as would be macro generated.
// If we don't have implementation to discard it, we will get exception.
driverFor(testFile).getFileSync('$testPackageLibPath/test.macro.dart');
@@ -9442,7 +9443,7 @@
package:test/test.dart
''');
- // When we discard the library, we remove its macro file.
+ // When we discard the library, we keep its macro file.
driverFor(testFile).changeFile(testFile.path);
await driverFor(testFile).applyPendingFileChanges();
assertDriverStateString(testFile, r'''
@@ -9459,7 +9460,7 @@
libraries: library_0
apiSignature_0
users: cycle_1
- referencingFiles: file_1
+ referencingFiles: file_1 file_3
unlinkedKey: k00
/home/test/lib/b.dart
uri: package:test/b.dart
@@ -9493,6 +9494,25 @@
unlinkedKey: k02
/home/test/lib/test.macro.dart
uri: package:test/test.macro.dart
+ current
+ id: file_3
+ content
+---
+library augment 'test.dart';
+
+import 'package:test/a.dart' as prefix0;
+
+class MyClass {
+ void foo(prefix0.A _) {}
+}
+---
+ kind: augmentation_3
+ uriFile: file_2
+ libraryImports
+ library_0
+ library_10 dart:core synthetic
+ referencingFiles: file_2
+ unlinkedKey: k03
libraryCycles
/home/test/lib/a.dart
current: cycle_0
@@ -9837,6 +9857,631 @@
''');
}
}
+
+ test_macroGeneratedFile_changeLibrary_noMacroApplication_restore() async {
+ if (!keepLinkingLibraries) return;
+ useEmptyByteStore();
+
+ var library = await buildLibrary(r'''
+import 'append.dart';
+
+@DeclareInLibrary('class B {}')
+class A {}
+''');
+
+ _assertMacroCode(library, r'''
+library augment 'test.dart';
+
+class B {}
+''');
+
+ // Note that we have `test.macro.dart` file.
+ assertDriverStateString(testFile, r'''
+files
+ /home/test/lib/append.dart
+ uri: package:test/append.dart
+ current
+ id: file_0
+ kind: library_0
+ libraryImports
+ library_3 package:macro/api.dart
+ library_9 dart:core synthetic
+ cycle_0
+ dependencies: dart:core package:macro/api.dart
+ libraries: library_0
+ apiSignature_0
+ users: cycle_1
+ referencingFiles: file_1
+ unlinkedKey: k00
+ /home/test/lib/test.dart
+ uri: package:test/test.dart
+ current
+ id: file_1
+ kind: library_1
+ libraryImports
+ library_0
+ library_9 dart:core synthetic
+ augmentationImports
+ augmentation_2
+ cycle_1
+ dependencies: cycle_0 dart:core
+ libraries: library_1
+ apiSignature_1
+ unlinkedKey: k01
+ /home/test/lib/test.macro.dart
+ uri: package:test/test.macro.dart
+ current
+ id: file_2
+ kind: augmentation_2
+ augmented: library_1
+ library: library_1
+ libraryImports
+ library_9 dart:core synthetic
+ referencingFiles: file_1
+ unlinkedKey: k02
+libraryCycles
+ /home/test/lib/append.dart
+ current: cycle_0
+ key: k03
+ get: []
+ put: [k03]
+ /home/test/lib/test.dart
+ current: cycle_1
+ key: k04
+ get: []
+ put: [k04]
+elementFactory
+ hasElement
+ package:test/append.dart
+ package:test/test.dart
+''');
+
+ // Change the library content, no macro applications.
+ modifyFile2(testFile, r'''
+class A {}
+''');
+ driverFor(testFile).changeFile2(testFile);
+
+ // Ask the library, will be relinked.
+ await driverFor(testFile).getLibraryByUri('package:test/test.dart');
+
+ // For `test.dart`.
+ // This is the same `FileState` instance.
+ // We refreshed it, it has different `unlinkedKey`, `kind`, `cycle`.
+ // We linked new summary, and put it into the byte store.
+ //
+ // For `test.macro.dart`.
+ // This is the same `FileState` instance.
+ // We did not refresh it, same `unlinkedKey`, `kind`.
+ // Its `kind.library` is empty, `test.dart` does not import it.
+ assertDriverStateString(testFile, r'''
+files
+ /home/test/lib/append.dart
+ uri: package:test/append.dart
+ current
+ id: file_0
+ kind: library_0
+ libraryImports
+ library_3 package:macro/api.dart
+ library_9 dart:core synthetic
+ cycle_0
+ dependencies: dart:core package:macro/api.dart
+ libraries: library_0
+ apiSignature_0
+ unlinkedKey: k00
+ /home/test/lib/test.dart
+ uri: package:test/test.dart
+ current
+ id: file_1
+ kind: library_15
+ libraryImports
+ library_9 dart:core synthetic
+ cycle_5
+ dependencies: dart:core
+ libraries: library_15
+ apiSignature_2
+ unlinkedKey: k05
+ /home/test/lib/test.macro.dart
+ uri: package:test/test.macro.dart
+ current
+ id: file_2
+ kind: augmentation_2
+ uriFile: file_1
+ libraryImports
+ library_9 dart:core synthetic
+ referencingFiles: file_1
+ unlinkedKey: k02
+libraryCycles
+ /home/test/lib/append.dart
+ current: cycle_0
+ key: k03
+ get: []
+ put: [k03]
+ /home/test/lib/test.dart
+ current: cycle_5
+ key: k06
+ get: []
+ put: [k04, k06]
+elementFactory
+ hasElement
+ package:test/append.dart
+ package:test/test.dart
+''');
+
+ // Use the same library as initially.
+ modifyFile2(testFile, r'''
+import 'append.dart';
+
+@DeclareInLibrary('class B {}')
+class A {}
+''');
+ driverFor(testFile).changeFile2(testFile);
+
+ // Ask the library, will be relinked.
+ await driverFor(testFile).getLibraryByUri('package:test/test.dart');
+
+ // For `test.dart`.
+ // This is the same `FileState` instance.
+ // We refreshed it, it has different `unlinkedKey`, `kind`, `cycle`.
+ // We read the linked summary, see `get`.
+ //
+ // For `test.macro.dart`.
+ // This is the same `FileState` instance.
+ // Its content is the same as it already was, so we did not `refresh()` it.
+ // Its `kind.library` now points at the new `kind` of `test.dart`.
+ assertDriverStateString(testFile, r'''
+files
+ /home/test/lib/append.dart
+ uri: package:test/append.dart
+ current
+ id: file_0
+ kind: library_0
+ libraryImports
+ library_3 package:macro/api.dart
+ library_9 dart:core synthetic
+ cycle_0
+ dependencies: dart:core package:macro/api.dart
+ libraries: library_0
+ apiSignature_0
+ users: cycle_6
+ referencingFiles: file_1
+ unlinkedKey: k00
+ /home/test/lib/test.dart
+ uri: package:test/test.dart
+ current
+ id: file_1
+ kind: library_16
+ libraryImports
+ library_0
+ library_9 dart:core synthetic
+ augmentationImports
+ augmentation_2
+ cycle_6
+ dependencies: cycle_0 dart:core
+ libraries: library_16
+ apiSignature_1
+ unlinkedKey: k01
+ /home/test/lib/test.macro.dart
+ uri: package:test/test.macro.dart
+ current
+ id: file_2
+ kind: augmentation_2
+ augmented: library_16
+ library: library_16
+ libraryImports
+ library_9 dart:core synthetic
+ referencingFiles: file_1
+ unlinkedKey: k02
+libraryCycles
+ /home/test/lib/append.dart
+ current: cycle_0
+ key: k03
+ get: []
+ put: [k03]
+ /home/test/lib/test.dart
+ current: cycle_6
+ key: k04
+ get: [k04]
+ put: [k04, k06]
+elementFactory
+ hasElement
+ package:test/append.dart
+ package:test/test.dart
+ hasReader
+ package:test/test.dart
+''');
+ }
+
+ test_macroGeneratedFile_changeLibrary_updateMacroApplication() async {
+ if (!keepLinkingLibraries) return;
+ useEmptyByteStore();
+
+ var library = await buildLibrary(r'''
+import 'append.dart';
+
+@DeclareInLibrary('class B {}')
+class A {}
+''');
+
+ _assertMacroCode(library, r'''
+library augment 'test.dart';
+
+class B {}
+''');
+
+ // Note that we have `test.macro.dart` file.
+ assertDriverStateString(testFile, r'''
+files
+ /home/test/lib/append.dart
+ uri: package:test/append.dart
+ current
+ id: file_0
+ kind: library_0
+ libraryImports
+ library_3 package:macro/api.dart
+ library_9 dart:core synthetic
+ cycle_0
+ dependencies: dart:core package:macro/api.dart
+ libraries: library_0
+ apiSignature_0
+ users: cycle_1
+ referencingFiles: file_1
+ unlinkedKey: k00
+ /home/test/lib/test.dart
+ uri: package:test/test.dart
+ current
+ id: file_1
+ kind: library_1
+ libraryImports
+ library_0
+ library_9 dart:core synthetic
+ augmentationImports
+ augmentation_2
+ cycle_1
+ dependencies: cycle_0 dart:core
+ libraries: library_1
+ apiSignature_1
+ unlinkedKey: k01
+ /home/test/lib/test.macro.dart
+ uri: package:test/test.macro.dart
+ current
+ id: file_2
+ kind: augmentation_2
+ augmented: library_1
+ library: library_1
+ libraryImports
+ library_9 dart:core synthetic
+ referencingFiles: file_1
+ unlinkedKey: k02
+libraryCycles
+ /home/test/lib/append.dart
+ current: cycle_0
+ key: k03
+ get: []
+ put: [k03]
+ /home/test/lib/test.dart
+ current: cycle_1
+ key: k04
+ get: []
+ put: [k04]
+elementFactory
+ hasElement
+ package:test/append.dart
+ package:test/test.dart
+''');
+
+ // Change the library content.
+ modifyFile2(testFile, r'''
+import 'append.dart';
+
+@DeclareInLibrary('class B2 {}')
+class A {}
+''');
+ driverFor(testFile).changeFile2(testFile);
+
+ // Ask the library, will be relinked.
+ var result2 =
+ await driverFor(testFile).getLibraryByUri('package:test/test.dart');
+
+ // For `test.dart`.
+ // This is the same `FileState` instance.
+ // We refreshed it, it has different `unlinkedKey`, `kind`, `cycle`.
+ // We linked new summary, and put it into the byte store.
+ //
+ // For `test.macro.dart`.
+ // This is the same `FileState` instance.
+ // We refreshed it, it has different `unlinkedKey`, `kind`.
+ // Its `library` points at `test.dart` library.
+ assertDriverStateString(testFile, r'''
+files
+ /home/test/lib/append.dart
+ uri: package:test/append.dart
+ current
+ id: file_0
+ kind: library_0
+ libraryImports
+ library_3 package:macro/api.dart
+ library_9 dart:core synthetic
+ cycle_0
+ dependencies: dart:core package:macro/api.dart
+ libraries: library_0
+ apiSignature_0
+ users: cycle_5
+ referencingFiles: file_1
+ unlinkedKey: k00
+ /home/test/lib/test.dart
+ uri: package:test/test.dart
+ current
+ id: file_1
+ kind: library_15
+ libraryImports
+ library_0
+ library_9 dart:core synthetic
+ augmentationImports
+ augmentation_16
+ cycle_5
+ dependencies: cycle_0 dart:core
+ libraries: library_15
+ apiSignature_2
+ unlinkedKey: k05
+ /home/test/lib/test.macro.dart
+ uri: package:test/test.macro.dart
+ current
+ id: file_2
+ kind: augmentation_16
+ augmented: library_15
+ library: library_15
+ libraryImports
+ library_9 dart:core synthetic
+ referencingFiles: file_1
+ unlinkedKey: k06
+libraryCycles
+ /home/test/lib/append.dart
+ current: cycle_0
+ key: k03
+ get: []
+ put: [k03]
+ /home/test/lib/test.dart
+ current: cycle_5
+ key: k07
+ get: []
+ put: [k04, k07]
+elementFactory
+ hasElement
+ package:test/append.dart
+ package:test/test.dart
+''');
+
+ // Check that it has `class B2 {}`, as requested.
+ result2 as LibraryElementResultImpl;
+ _assertMacroCode(result2.element as LibraryElementImpl, r'''
+library augment 'test.dart';
+
+class B2 {}
+''');
+ }
+
+ test_macroGeneratedFile_dispose_restore() async {
+ if (!keepLinkingLibraries) return;
+ useEmptyByteStore();
+
+ var library = await buildLibrary(r'''
+import 'append.dart';
+
+@DeclareInLibrary('class B {}')
+class A {}
+''');
+
+ _assertMacroCode(library, r'''
+library augment 'test.dart';
+
+class B {}
+''');
+
+ // Note that we have `test.macro.dart` file.
+ assertDriverStateString(testFile, r'''
+files
+ /home/test/lib/append.dart
+ uri: package:test/append.dart
+ current
+ id: file_0
+ kind: library_0
+ libraryImports
+ library_3 package:macro/api.dart
+ library_9 dart:core synthetic
+ cycle_0
+ dependencies: dart:core package:macro/api.dart
+ libraries: library_0
+ apiSignature_0
+ users: cycle_1
+ referencingFiles: file_1
+ unlinkedKey: k00
+ /home/test/lib/test.dart
+ uri: package:test/test.dart
+ current
+ id: file_1
+ kind: library_1
+ libraryImports
+ library_0
+ library_9 dart:core synthetic
+ augmentationImports
+ augmentation_2
+ cycle_1
+ dependencies: cycle_0 dart:core
+ libraries: library_1
+ apiSignature_1
+ unlinkedKey: k01
+ /home/test/lib/test.macro.dart
+ uri: package:test/test.macro.dart
+ current
+ id: file_2
+ kind: augmentation_2
+ augmented: library_1
+ library: library_1
+ libraryImports
+ library_9 dart:core synthetic
+ referencingFiles: file_1
+ unlinkedKey: k02
+libraryCycles
+ /home/test/lib/append.dart
+ current: cycle_0
+ key: k03
+ get: []
+ put: [k03]
+ /home/test/lib/test.dart
+ current: cycle_1
+ key: k04
+ get: []
+ put: [k04]
+elementFactory
+ hasElement
+ package:test/append.dart
+ package:test/test.dart
+''');
+
+ // "Touch" the library file, so dispose it.
+ // But don't load the library yet.
+ driverFor(testFile).changeFile2(testFile);
+ await pumpEventQueue(times: 5000);
+
+ // For `test.dart`.
+ // No `current` in `libraryCycles`, it was disposed.
+ // It has a new instance `cycle_X`.
+ // Actually the cycle was also disposed, but the printer re-created it.
+ //
+ // For `test.macro.dart`.
+ // It still has the same `current`.
+ // No `current` library cycle.
+ assertDriverStateString(testFile, r'''
+files
+ /home/test/lib/append.dart
+ uri: package:test/append.dart
+ current
+ id: file_0
+ kind: library_0
+ libraryImports
+ library_3 package:macro/api.dart
+ library_9 dart:core synthetic
+ cycle_0
+ dependencies: dart:core package:macro/api.dart
+ libraries: library_0
+ apiSignature_0
+ users: cycle_5
+ referencingFiles: file_1
+ unlinkedKey: k00
+ /home/test/lib/test.dart
+ uri: package:test/test.dart
+ current
+ id: file_1
+ kind: library_15
+ libraryImports
+ library_0
+ library_9 dart:core synthetic
+ cycle_5
+ dependencies: cycle_0 dart:core
+ libraries: library_15
+ apiSignature_1
+ unlinkedKey: k01
+ /home/test/lib/test.macro.dart
+ uri: package:test/test.macro.dart
+ current
+ id: file_2
+ kind: augmentation_2
+ uriFile: file_1
+ libraryImports
+ library_9 dart:core synthetic
+ referencingFiles: file_1
+ unlinkedKey: k02
+libraryCycles
+ /home/test/lib/append.dart
+ current: cycle_0
+ key: k03
+ get: []
+ put: [k03]
+ /home/test/lib/test.dart
+ get: []
+ put: [k04]
+elementFactory
+ hasElement
+ package:test/append.dart
+''');
+
+ // Load the library from bytes.
+ await driverFor(testFile).getLibraryByUri('package:test/test.dart');
+
+ // For `test.dart`.
+ // It has `current` in `libraryCycles`.
+ // This is a new instance.
+ // It has `get` with the same id as was put before.
+ //
+ // For `test.macro.dart`.
+ // The same instance of `kind` as before.
+ // We read the `test.dart` linked summary from bytes, and added the
+ // augmentation file `test.macro.dart` from the stored the code. The code
+ // was the same as before, so we did not `refresh()` the file. So, we did
+ // not change the existing `kind`.
+ assertDriverStateString(testFile, r'''
+files
+ /home/test/lib/append.dart
+ uri: package:test/append.dart
+ current
+ id: file_0
+ kind: library_0
+ libraryImports
+ library_3 package:macro/api.dart
+ library_9 dart:core synthetic
+ cycle_0
+ dependencies: dart:core package:macro/api.dart
+ libraries: library_0
+ apiSignature_0
+ users: cycle_5
+ referencingFiles: file_1
+ unlinkedKey: k00
+ /home/test/lib/test.dart
+ uri: package:test/test.dart
+ current
+ id: file_1
+ kind: library_15
+ libraryImports
+ library_0
+ library_9 dart:core synthetic
+ augmentationImports
+ augmentation_2
+ cycle_5
+ dependencies: cycle_0 dart:core
+ libraries: library_15
+ apiSignature_1
+ unlinkedKey: k01
+ /home/test/lib/test.macro.dart
+ uri: package:test/test.macro.dart
+ current
+ id: file_2
+ kind: augmentation_2
+ augmented: library_15
+ library: library_15
+ libraryImports
+ library_9 dart:core synthetic
+ referencingFiles: file_1
+ unlinkedKey: k02
+libraryCycles
+ /home/test/lib/append.dart
+ current: cycle_0
+ key: k03
+ get: []
+ put: [k03]
+ /home/test/lib/test.dart
+ current: cycle_5
+ key: k04
+ get: [k04]
+ put: [k04]
+elementFactory
+ hasElement
+ package:test/append.dart
+ package:test/test.dart
+ hasReader
+ package:test/test.dart
+''');
+ }
}
@reflectiveTest