Macro. Support for macro generated files in getFilesReferencingName()

Change-Id: Ie3fe2f10780d7f5b8a119ed35ef4de9c25f5eabd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/348767
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2024-01-29 22:28:12 +00:00 committed by Commit Queue
parent 36e4bf1709
commit 6293002ed7
4 changed files with 231 additions and 153 deletions

View file

@ -192,8 +192,12 @@ class AnalysisDriver {
/// Set to `true` after first [discoverAvailableFiles].
bool _hasAvailableFilesDiscovered = false;
/// The list of tasks to compute files referencing a name.
final _referencingNameTasks = <_FilesReferencingNameTask>[];
/// The requests to compute files defining a class member with the name.
final _definingClassMemberNameRequests =
<_GetFilesDefiningClassMemberNameRequest>[];
/// The requests to compute files referencing a name.
final _referencingNameRequests = <_GetFilesReferencingNameRequest>[];
/// The mapping from the files for which errors were requested using
/// [getErrors] to the [Completer]s to report the result.
@ -437,7 +441,10 @@ class AnalysisDriver {
if (_requestedLibraries.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_referencingNameTasks.isNotEmpty) {
if (_definingClassMemberNameRequests.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_referencingNameRequests.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_errorsRequestedFiles.isNotEmpty) {
@ -488,7 +495,8 @@ class AnalysisDriver {
_requestedLibraries.isNotEmpty ||
_requestedFiles.isNotEmpty ||
_errorsRequestedFiles.isNotEmpty ||
_referencingNameTasks.isNotEmpty ||
_definingClassMemberNameRequests.isNotEmpty ||
_referencingNameRequests.isNotEmpty ||
_indexRequestedFiles.isNotEmpty ||
_unitElementRequestedFiles.isNotEmpty ||
_disposeRequests.isNotEmpty;
@ -783,36 +791,19 @@ class AnalysisDriver {
/// Completes with files that define a class member with the [name].
Future<List<FileState>> getFilesDefiningClassMemberName(String name) async {
await discoverAvailableFiles();
// Get library elements, so macro generated files are added.
for (var file in knownFiles.toList()) {
await getLibraryByUri(file.uriStr);
}
var definingFiles = <FileState>[];
for (var file in knownFiles) {
if (file.definedClassMemberNames.contains(name)) {
definingFiles.add(file);
}
}
return definingFiles;
}
/// Return a [Future] that completes with the list of known files that
/// reference the given external [name].
Future<List<String>> getFilesReferencingName(String name) {
discoverAvailableFiles();
var task = _FilesReferencingNameTask(this, name);
_referencingNameTasks.add(task);
var request = _GetFilesDefiningClassMemberNameRequest(name);
_definingClassMemberNameRequests.add(request);
_scheduler.notify();
return task.completer.future;
return request.completer.future;
}
/// See [getFilesReferencingName].
Future<List<File>> getFilesReferencingName2(String name) async {
final pathList = await getFilesReferencingName(name);
return pathList.map((path) => resourceProvider.getFile(path)).toList();
/// Completes with files that reference the given external [name].
Future<List<FileState>> getFilesReferencingName(String name) async {
await discoverAvailableFiles();
var request = _GetFilesReferencingNameRequest(name);
_referencingNameRequests.add(request);
_scheduler.notify();
return request.completer.future;
}
/// Return the [FileResult] for the Dart file with the given [path].
@ -1223,12 +1214,15 @@ class AnalysisDriver {
return;
}
// Compute files defining a class member.
if (_definingClassMemberNameRequests.removeLastOrNull() case var request?) {
await _getFilesDefiningClassMemberName(request);
return;
}
// Compute files referencing a name.
if (_referencingNameTasks.firstOrNull case var task?) {
bool isDone = task.perform();
if (isDone) {
_referencingNameTasks.remove(task);
}
if (_referencingNameRequests.removeLastOrNull() case var request?) {
await _getFilesReferencingName(request);
return;
}
@ -1637,6 +1631,24 @@ class AnalysisDriver {
}
}
Future<void> _ensureMacroGeneratedFiles() async {
for (var file in knownFiles.toList()) {
if (file.kind case LibraryFileKind libraryKind) {
var libraryCycle = libraryKind.libraryCycle;
if (libraryCycle.importsMacroClass) {
if (!libraryCycle.hasMacroFilesCreated) {
libraryCycle.hasMacroFilesCreated = true;
// We create macro-generated FileState(s) when load bundles.
await libraryContext.load(
targetLibrary: libraryKind,
performance: OperationPerformanceImpl('<root>'),
);
}
}
}
}
}
void _fillSalt() {
_fillSaltForUnlinked();
_fillSaltForElements();
@ -1707,6 +1719,34 @@ class AnalysisDriver {
return errors;
}
Future<void> _getFilesDefiningClassMemberName(
_GetFilesDefiningClassMemberNameRequest request,
) async {
await _ensureMacroGeneratedFiles();
var result = <FileState>[];
for (var file in knownFiles) {
if (file.definedClassMemberNames.contains(request.name)) {
result.add(file);
}
}
request.completer.complete(result);
}
Future<void> _getFilesReferencingName(
_GetFilesReferencingNameRequest request,
) async {
await _ensureMacroGeneratedFiles();
var result = <FileState>[];
for (var file in knownFiles) {
if (file.referencedNames.contains(request.name)) {
result.add(file);
}
}
request.completer.complete(result);
}
Future<void> _getIndex(String path) async {
final file = _fsState.getFileForPath(path);
@ -2518,62 +2558,18 @@ class _FileChange {
enum _FileChangeKind { add, change, remove }
/// Task that computes the list of files that were added to the driver and
/// have at least one reference to an identifier [name] defined outside of the
/// file.
class _FilesReferencingNameTask {
static const int _WORK_FILES = 100;
static const int _MS_WORK_INTERVAL = 5;
final AnalysisDriver driver;
class _GetFilesDefiningClassMemberNameRequest {
final String name;
final Completer<List<String>> completer = Completer<List<String>>();
final completer = Completer<List<FileState>>();
int fileStamp = -1;
List<FileState>? filesToCheck;
int filesToCheckIndex = -1;
_GetFilesDefiningClassMemberNameRequest(this.name);
}
final List<String> referencingFiles = <String>[];
class _GetFilesReferencingNameRequest {
final String name;
final completer = Completer<List<FileState>>();
_FilesReferencingNameTask(this.driver, this.name);
/// Perform work for a fixed length of time, and complete the [completer] to
/// either return `true` to indicate that the task is done, or return `false`
/// to indicate that the task should continue to be run.
///
/// Each invocation of an asynchronous method has overhead, which looks as
/// `_SyncCompleter.complete` invocation, we see as much as 62% in some
/// scenarios. Instead we use a fixed length of time, so we can spend less time
/// overall and keep quick enough response time.
bool perform() {
if (driver._fsState.fileStamp != fileStamp) {
filesToCheck = null;
referencingFiles.clear();
}
// Prepare files to check.
if (filesToCheck == null) {
fileStamp = driver._fsState.fileStamp;
filesToCheck = driver._fsState.knownFiles.toList();
filesToCheckIndex = 0;
}
Stopwatch timer = Stopwatch()..start();
while (filesToCheckIndex < filesToCheck!.length) {
if (filesToCheckIndex % _WORK_FILES == 0 &&
timer.elapsedMilliseconds > _MS_WORK_INTERVAL) {
return false;
}
FileState file = filesToCheck![filesToCheckIndex++];
if (file.referencedNames.contains(name)) {
referencingFiles.add(file.path);
}
}
// If no more files to check, complete and done.
completer.complete(referencingFiles);
return true;
}
_GetFilesReferencingNameRequest(this.name);
}
class _ResolveForCompletionRequest {

View file

@ -55,7 +55,7 @@ class LibraryCycle {
/// include [implSignature] of the macro defining library.
String implSignature;
late final bool hasMacroClass = () {
late final bool declaresMacroClass = () {
for (final library in libraries) {
for (final file in library.files) {
if (file.unlinked2.macroClasses.isNotEmpty) {
@ -71,6 +71,21 @@ class LibraryCycle {
/// imported into a cycle that declares one.
bool mightBeExecutedByMacroClass = false;
/// If a cycle imports a library that declares a macro, then it can have
/// macro applications, and so macro-generated files.
late final bool importsMacroClass = () {
for (final dependency in directDependencies) {
if (dependency.declaresMacroClass) {
return true;
}
}
return false;
}();
/// Set to `true` if this library cycle [importsMacroClass], and we have
/// already created macro generated [FileState]s.
bool hasMacroFilesCreated = false;
LibraryCycle({
required this.libraries,
required this.directDependencies,
@ -229,7 +244,7 @@ class _LibraryWalker extends graph.DependencyWalker<_LibraryNode> {
implSignature: implSignature.toHex(),
);
if (cycle.hasMacroClass) {
if (cycle.declaresMacroClass) {
cycle.markMightBeExecutedByMacroClass();
}
@ -259,7 +274,7 @@ class _LibraryWalker extends graph.DependencyWalker<_LibraryNode> {
if (directDependencies.add(referencedCycle)) {
apiSignature.addString(
referencedCycle.hasMacroClass
referencedCycle.declaresMacroClass
? referencedCycle.implSignature
: referencedCycle.apiSignature,
);

View file

@ -495,13 +495,13 @@ class Search {
}
// Prepare the list of files that reference the name.
List<String> files = await _driver.getFilesReferencingName(name);
var files = await _driver.getFilesReferencingName(name);
// Check the index of every file that references the element name.
List<SearchResult> results = [];
for (String file in files) {
if (searchedFiles.add(file, this)) {
var index = await _driver.getIndex(file);
for (var file in files) {
if (searchedFiles.add(file.path, this)) {
var index = await _driver.getIndex(file.path);
if (index != null) {
_IndexRequest request = _IndexRequest(index);
var fileResults = await request.getUnresolvedMemberReferences(
@ -512,7 +512,7 @@ class Search {
IndexRelationKind.IS_READ_WRITTEN_BY: SearchResultKind.READ_WRITE,
IndexRelationKind.IS_INVOKED_BY: SearchResultKind.INVOCATION
},
() => _getUnitElement(file),
() => _getUnitElement(file.path),
);
results.addAll(fileResults);
}
@ -534,9 +534,14 @@ class Search {
name = element.enclosingElement.displayName;
}
var elementPath = element.source!.fullName;
var elementFile = _driver.fsState.getExistingFromPath(elementPath);
if (elementFile == null) {
return;
}
// Prepare the list of files that reference the element name.
List<String> files = <String>[];
String path = element.source!.fullName;
var files = <FileState>[];
if (name.startsWith('_')) {
String libraryPath = element.library!.source.fullName;
if (searchedFiles.add(libraryPath, this)) {
@ -544,8 +549,8 @@ class Search {
final libraryKind = libraryFile.kind;
if (libraryKind is LibraryFileKind) {
for (final file in libraryKind.files) {
if (file.path == path || file.referencedNames.contains(name)) {
files.add(file.path);
if (file == elementFile || file.referencedNames.contains(name)) {
files.add(file);
}
}
}
@ -554,21 +559,24 @@ class Search {
if (filesToCheck != null) {
for (FileState file in filesToCheck) {
if (file.referencedNames.contains(name)) {
files.add(file.path);
files.add(file);
}
}
} else {
files = await _driver.getFilesReferencingName(name);
}
if (searchedFiles.add(path, this) && !files.contains(path)) {
files.add(path);
if (searchedFiles.add(elementFile.path, this)) {
if (!files.contains(elementFile)) {
files.add(elementFile);
}
}
}
// Check the index of every file that references the element name.
for (String file in files) {
if (searchedFiles.add(file, this)) {
await _addResultsInFile(results, element, relationToResultKind, file);
for (var file in files) {
if (searchedFiles.add(file.path, this)) {
await _addResultsInFile(
results, element, relationToResultKind, file.path);
}
}
}

View file

@ -1732,14 +1732,9 @@ class D {
driver.addFile2(c);
driver.addFile2(d);
Future<List<File>> forName(String name) async {
var files = await driver.getFilesDefiningClassMemberName(name);
return files.resources;
}
expect(await forName('m1'), unorderedEquals([a]));
expect(await forName('m2'), unorderedEquals([b, c]));
expect(await forName('m3'), unorderedEquals([d]));
await driver.assertFilesDefiningClassMemberName('m1', [a]);
await driver.assertFilesDefiningClassMemberName('m2', [b, c]);
await driver.assertFilesDefiningClassMemberName('m3', [d]);
}
test_getFilesDefiningClassMemberName_macroGenerated() async {
@ -1768,30 +1763,24 @@ import 'append.dart';
class C {}
''');
final driver = driverFor(testFile);
driver.addFile2(a);
driver.addFile2(b);
driver.addFile2(c);
// Run twice: when linking, and when reading.
for (var i = 0; i < 2; i++) {
final driver = driverFor(testFile);
driver.addFile2(a);
driver.addFile2(b);
driver.addFile2(c);
Future<List<File>> forName(String name) async {
var files = await driver.getFilesDefiningClassMemberName(name);
return files.resources;
}
expect(
await forName('foo'),
unorderedEquals([
await driver.assertFilesDefiningClassMemberName('foo', [
a.macroForLibrary,
c.macroForLibrary,
]),
);
]);
expect(
await forName('bar'),
unorderedEquals([
await driver.assertFilesDefiningClassMemberName('bar', [
b.macroForLibrary,
]),
);
]);
await disposeAnalysisContextCollection();
}
}
test_getFilesDefiningClassMemberName_mixin() async {
@ -1825,14 +1814,9 @@ mixin D {
driver.addFile2(c);
driver.addFile2(d);
Future<List<File>> forName(String name) async {
var files = await driver.getFilesDefiningClassMemberName(name);
return files.resources;
}
expect(await forName('m1'), unorderedEquals([a]));
expect(await forName('m2'), unorderedEquals([b, c]));
expect(await forName('m3'), unorderedEquals([d]));
await driver.assertFilesDefiningClassMemberName('m1', [a]);
await driver.assertFilesDefiningClassMemberName('m2', [b, c]);
await driver.assertFilesDefiningClassMemberName('m3', [d]);
}
test_getFilesReferencingName() async {
@ -1871,15 +1855,17 @@ void main() {}
// `c` references an external `A`.
// `d` references the local `A`.
// `e` does not reference `A` at all.
expect(
await driver.getFilesReferencingName2('A'),
unorderedEquals([b, c]),
await driver.assertFilesReferencingName(
'A',
includesAll: [b, c],
excludesAll: [d, e],
);
// We get the same results second time.
expect(
await driver.getFilesReferencingName2('A'),
unorderedEquals([b, c]),
await driver.assertFilesReferencingName(
'A',
includesAll: [b, c],
excludesAll: [d, e],
);
}
@ -1903,17 +1889,66 @@ int b = 0;
''');
final c = newFile('$packagesRootPath/ccc/lib/c.dart', '''
int c = 0
int c = 0;
''');
final driver = driverFor(testFile);
driver.addFile2(t);
final files = await driver.getFilesReferencingName2('int');
expect(files, contains(t));
expect(files, contains(a));
expect(files, contains(b));
expect(files, isNot(contains(c)));
await driver.assertFilesReferencingName(
'int',
includesAll: [t, a, b],
excludesAll: [c],
);
}
test_getFilesReferencingName_macroGenerated() async {
if (!_configureWithCommonMacros()) {
return;
}
final a = newFile('$testPackageLibPath/a.dart', r'''
import 'append.dart';
@DeclareInLibrary('{{dart:core@int}} get foo => 0;')
class A {}
''');
final b = newFile('$testPackageLibPath/b.dart', r'''
import 'append.dart';
@DeclareInLibrary('{{dart:core@double}} get foo => 1.2;')
class B {}
''');
final c = newFile('$testPackageLibPath/c.dart', r'''
import 'append.dart';
@DeclareInLibrary('{{dart:core@int}} get foo => 0;')
class C {}
''');
// Run twice: when linking, and when reading.
for (var i = 0; i < 2; i++) {
final driver = driverFor(testFile);
driver.addFile2(a);
driver.addFile2(b);
driver.addFile2(c);
await driver.assertFilesReferencingName(
'int',
includesAll: [a.macroForLibrary, c.macroForLibrary],
excludesAll: [b.macroForLibrary],
);
await driver.assertFilesReferencingName(
'double',
includesAll: [b.macroForLibrary],
excludesAll: [a.macroForLibrary, c.macroForLibrary],
);
await disposeAnalysisContextCollection();
}
}
test_getFileSync_changedFile() async {
@ -5869,6 +5904,30 @@ class DriverEventCollector {
}
extension on AnalysisDriver {
Future<void> assertFilesDefiningClassMemberName(
String name,
List<File?> expected,
) async {
var fileStateList = await getFilesDefiningClassMemberName(name);
var files = fileStateList.resources;
expect(files, unorderedEquals(expected));
}
Future<void> assertFilesReferencingName(
String name, {
required List<File?> includesAll,
required List<File?> excludesAll,
}) async {
var fileStateList = await getFilesReferencingName(name);
var files = fileStateList.resources;
for (var expected in includesAll) {
expect(files, contains(expected));
}
for (var expected in excludesAll) {
expect(files, isNot(contains(expected)));
}
}
void assertLoadedLibraryUriSet({
Iterable<String>? included,
Iterable<String>? excluded,