Issue 47804. Recompute invlaidated libraries after processing all changed files.
The issue has the discussion.
Bug: https://github.com/dart-lang/sdk/issues/47804
Change-Id: I301fe8fe44b0e8e7e3b413df0363fabc44061c7d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/221564
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/services/available_declarations.dart b/pkg/analyzer/lib/src/services/available_declarations.dart
index 588b150..b5a3097 100644
--- a/pkg/analyzer/lib/src/services/available_declarations.dart
+++ b/pkg/analyzer/lib/src/services/available_declarations.dart
@@ -504,6 +504,10 @@
/// The list of changed file paths.
final List<String> _changedPaths = [];
+ /// While processing [_changedPaths] we accumulate libraries here.
+ /// Then we process all of them at once, and reset to the empty set.
+ Set<_File> _invalidatedLibraries = {};
+
/// The list of files scheduled for processing. It may include parts and
/// libraries, but parts are ignored when we detect them.
final List<_ScheduledFile> _scheduledFiles = [];
@@ -529,7 +533,9 @@
_whenKnownFilesPulled = now;
pullKnownFiles();
}
- return _changedPaths.isNotEmpty || _scheduledFiles.isNotEmpty;
+ return _changedPaths.isNotEmpty ||
+ _invalidatedLibraries.isNotEmpty ||
+ _scheduledFiles.isNotEmpty;
}
/// Add the [analysisContext], so that its libraries are reported via the
@@ -595,6 +601,13 @@
return;
}
+ // There are no more changes as far as we know.
+ // So, recompute exported declarations for all invalidated libraries.
+ if (_invalidatedLibraries.isNotEmpty) {
+ _processInvalidatedLibraries(_invalidatedLibraries);
+ _invalidatedLibraries = {};
+ }
+
if (_scheduledFiles.isNotEmpty) {
var scheduledFile = _scheduledFiles.removeLast();
var file = _getFileByPath(scheduledFile.context, [], scheduledFile.path)!;
@@ -794,11 +807,33 @@
_invalidateExportedDeclarations(invalidatedLibraries, newLibrary);
}
}
- _computeExportedDeclarations(invalidatedLibraries);
- var changedLibraries = <Library>[];
+ // Don't compute exported declarations now, there might be more changes.
+ // Instead, accumulate invalidated libraries, and recompute all later.
+ _invalidatedLibraries.addAll(invalidatedLibraries);
+
var removedLibraries = <int>[];
for (var libraryFile in invalidatedLibraries) {
+ if (!libraryFile.exists) {
+ _idToLibrary.remove(libraryFile.id);
+ removedLibraries.add(libraryFile.id);
+ }
+ }
+ for (var file in notLibraries) {
+ _idToLibrary.remove(file.id);
+ removedLibraries.add(file.id);
+ }
+ if (removedLibraries.isNotEmpty) {
+ _changesController.add(
+ LibraryChange._([], removedLibraries),
+ );
+ }
+ }
+
+ void _processInvalidatedLibraries(Set<_File> invalidatedLibraries) {
+ _computeExportedDeclarations(invalidatedLibraries);
+ var changedLibraries = <Library>[];
+ for (var libraryFile in invalidatedLibraries) {
if (libraryFile.exists) {
var library = Library._(
libraryFile.id,
@@ -809,17 +844,10 @@
);
_idToLibrary[library.id] = library;
changedLibraries.add(library);
- } else {
- _idToLibrary.remove(libraryFile.id);
- removedLibraries.add(libraryFile.id);
}
}
- for (var file in notLibraries) {
- _idToLibrary.remove(file.id);
- removedLibraries.add(file.id);
- }
_changesController.add(
- LibraryChange._(changedLibraries, removedLibraries),
+ LibraryChange._(changedLibraries, []),
);
}
diff --git a/pkg/analyzer/test/src/services/available_declarations_test.dart b/pkg/analyzer/test/src/services/available_declarations_test.dart
index 17908dd..357ca89 100644
--- a/pkg/analyzer/test/src/services/available_declarations_test.dart
+++ b/pkg/analyzer/test/src/services/available_declarations_test.dart
@@ -914,6 +914,75 @@
]);
}
+ /// https://github.com/dart-lang/sdk/issues/47804
+ test_updated_exported2() async {
+ var a = convertPath('/home/test/lib/a.dart');
+ var b = convertPath('/home/test/lib/b.dart');
+ var c = convertPath('/home/test/lib/c.dart');
+
+ newFile(a, content: r'''
+class A {}
+''');
+ newFile(b, content: r'''
+class B {}
+''');
+ newFile(c, content: r'''
+export 'a.dart';
+export 'b.dart';
+class C {}
+''');
+ tracker.addContext(testAnalysisContext);
+
+ await _doAllTrackerWork();
+ _assertHasLibrary('package:test/c.dart', declarations: [
+ _ExpectedDeclaration.class_('A', [
+ _ExpectedDeclaration.constructor(''),
+ ]),
+ _ExpectedDeclaration.class_('B', [
+ _ExpectedDeclaration.constructor(''),
+ ]),
+ _ExpectedDeclaration.class_('C', [
+ _ExpectedDeclaration.constructor(''),
+ ]),
+ ]);
+
+ changes.clear();
+
+ newFile(a, content: r'''
+class A2 {}
+''');
+ newFile(b, content: r'''
+class B2 {}
+''');
+ tracker.changeFile(a);
+ tracker.changeFile(b);
+ await _doAllTrackerWork();
+
+ // In general it is OK to get duplicate libraries.
+ // But here we notified about both `a.dart` and `b.dart` changes before
+ // performing any work. So, there is no reason do handle `c.dart` twice.
+ var uniquePathSet = <String>{};
+ for (var change in changes) {
+ for (var library in change.changed) {
+ if (!uniquePathSet.add(library.path)) {
+ fail('Not unique path: ${library.path}');
+ }
+ }
+ }
+
+ _assertHasLibrary('package:test/c.dart', declarations: [
+ _ExpectedDeclaration.class_('A2', [
+ _ExpectedDeclaration.constructor(''),
+ ]),
+ _ExpectedDeclaration.class_('B2', [
+ _ExpectedDeclaration.constructor(''),
+ ]),
+ _ExpectedDeclaration.class_('C', [
+ _ExpectedDeclaration.constructor(''),
+ ]),
+ ]);
+ }
+
test_updated_library() async {
var a = convertPath('/home/test/lib/a.dart');
var b = convertPath('/home/test/lib/b.dart');