Don't read referenced files in AnalysisDriver until necessary.
Bug: https://github.com/dart-lang/sdk/issues/43744
Change-Id: I06d58b2a11fdf2db9bd6800f350ac25b8aab3fb4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/168382
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
index 924f983..13a6e34 100644
--- a/pkg/analyzer/CHANGELOG.md
+++ b/pkg/analyzer/CHANGELOG.md
@@ -1,5 +1,7 @@
## 0.40.5-dev
* Deprecated `GenericTypeAliasElement`. Use `FunctionTypeAliasElement`.
+* Read imports, exports, and parts on demand in `AnalysisDriver`.
+ Specifically, `parseFileSync` will not read any referenced files.
## 0.40.4
* Deprecated `IndexExpression.auxiliaryElements` and
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index 2524193..00d6c6f 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -192,23 +192,54 @@
/// Return the set of all directly referenced files - imported, exported or
/// parted.
- Set<FileState> get directReferencedFiles => _directReferencedFiles;
+ Set<FileState> get directReferencedFiles {
+ return _directReferencedFiles ??= <FileState>{
+ ...importedFiles,
+ ...exportedFiles,
+ ...partedFiles,
+ };
+ }
/// Return the set of all directly referenced libraries - imported or
/// exported.
- Set<FileState> get directReferencedLibraries => _directReferencedLibraries;
+ Set<FileState> get directReferencedLibraries {
+ return _directReferencedLibraries ??= <FileState>{
+ ...importedFiles,
+ ...exportedFiles,
+ };
+ }
/// Return `true` if the file exists.
bool get exists => _exists;
/// The list of files this file exports.
- List<FileState> get exportedFiles => _exportedFiles;
+ List<FileState> get exportedFiles {
+ if (_exportedFiles == null) {
+ _exportedFiles = <FileState>[];
+ for (var directive in _unlinked2.exports) {
+ var uri = _selectRelativeUri(directive);
+ var file = _fileForRelativeUri(uri);
+ _exportedFiles.add(file);
+ }
+ }
+ return _exportedFiles;
+ }
@override
int get hashCode => uri.hashCode;
/// The list of files this file imports.
- List<FileState> get importedFiles => _importedFiles;
+ List<FileState> get importedFiles {
+ if (_importedFiles == null) {
+ _importedFiles = <FileState>[];
+ for (var directive in _unlinked2.imports) {
+ var uri = _selectRelativeUri(directive);
+ var file = _fileForRelativeUri(uri);
+ _importedFiles.add(file);
+ }
+ }
+ return _importedFiles;
+ }
LibraryCycle get internal_libraryCycle => _libraryCycle;
@@ -237,6 +268,7 @@
/// of. Return `null` if a library is not known, for example because we have
/// not processed a library file yet.
FileState get library {
+ _fsState.readPartsForLibraries();
List<FileState> libraries = _fsState._partToLibraries[this];
if (libraries == null || libraries.isEmpty) {
return null;
@@ -264,13 +296,27 @@
/// The list of files files that this library consists of, i.e. this library
/// file itself and its [partedFiles].
- List<FileState> get libraryFiles => _libraryFiles;
+ List<FileState> get libraryFiles {
+ return _libraryFiles ??= [this, ...partedFiles];
+ }
/// Return information about line in the file.
LineInfo get lineInfo => _lineInfo;
/// The list of files this library file references as parts.
- List<FileState> get partedFiles => _partedFiles;
+ List<FileState> get partedFiles {
+ if (_partedFiles == null) {
+ _partedFiles = <FileState>[];
+ for (var uri in _unlinked2.parts) {
+ var file = _fileForRelativeUri(uri);
+ _partedFiles.add(file);
+ _fsState._partToLibraries
+ .putIfAbsent(file, () => <FileState>[])
+ .add(this);
+ }
+ }
+ return _partedFiles;
+ }
/// The external names referenced by the file.
Set<String> get referencedNames {
@@ -287,7 +333,7 @@
void appendReferenced(FileState file) {
if (transitiveFiles.add(file)) {
- file._directReferencedFiles?.forEach(appendReferenced);
+ file.directReferencedFiles.forEach(appendReferenced);
}
}
@@ -436,39 +482,16 @@
}
}
- // Build the graph.
- _importedFiles = <FileState>[];
- _exportedFiles = <FileState>[];
- _partedFiles = <FileState>[];
- for (var directive in _unlinked2.imports) {
- var uri = _selectRelativeUri(directive);
- var file = _fileForRelativeUri(uri);
- _importedFiles.add(file);
- }
- for (var directive in _unlinked2.exports) {
- var uri = _selectRelativeUri(directive);
- var file = _fileForRelativeUri(uri);
- _exportedFiles.add(file);
- }
- for (var uri in _unlinked2.parts) {
- var file = _fileForRelativeUri(uri);
- _partedFiles.add(file);
- _fsState._partToLibraries
- .putIfAbsent(file, () => <FileState>[])
- .add(this);
- }
- _libraryFiles = [this, ..._partedFiles];
+ // Read imports/exports on demand.
+ _importedFiles = null;
+ _exportedFiles = null;
+ _directReferencedFiles = null;
+ _directReferencedLibraries = null;
- // Compute referenced files.
- _directReferencedFiles = <FileState>{
- ..._importedFiles,
- ..._exportedFiles,
- ..._partedFiles,
- };
- _directReferencedLibraries = <FileState>{
- ..._importedFiles,
- ..._exportedFiles,
- };
+ // Read parts on demand.
+ _fsState._librariesWithoutPartsRead.add(this);
+ _partedFiles = null;
+ _libraryFiles = null;
// Update mapping from subtyped names to files.
for (var name in _driverUnlinkedUnit.subtypedNames) {
@@ -748,6 +771,11 @@
/// Mapping from a path to the corresponding canonical [FileState].
final Map<String, FileState> _pathToCanonicalFile = {};
+ /// We don't read parts until requested, but if we need to know the
+ /// library for a file, we need to read parts of every file to know
+ /// which libraries reference this part.
+ final List<FileState> _librariesWithoutPartsRead = [];
+
/// Mapping from a part to the libraries it is a part of.
final Map<FileState, List<FileState>> _partToLibraries = {};
@@ -950,6 +978,19 @@
_fileContentCache.remove(path);
}
+ void readPartsForLibraries() {
+ // Make a copy, because reading new files will update it.
+ var libraryToProcess = _librariesWithoutPartsRead.toList();
+
+ // We will process these files, so clear it now.
+ // It will be filled with new files during the loop below.
+ _librariesWithoutPartsRead.clear();
+
+ for (var library in libraryToProcess) {
+ library.partedFiles;
+ }
+ }
+
/// Remove the file with the given [path].
void removeFile(String path) {
markFileForReading(path);
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index 7d59c48..16cf839 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -2349,6 +2349,49 @@
}
}
+ test_parseFileSync_doesNotReadImportedFiles() async {
+ var a = convertPath('/test/lib/a.dart');
+ var b = convertPath('/test/lib/b.dart');
+
+ newFile(a, content: '');
+ newFile(b, content: r'''
+import 'a.dart';
+''');
+
+ expect(driver.fsState.knownFilePaths, isEmpty);
+
+ // Don't read `a.dart` when parse.
+ driver.parseFileSync(b);
+ expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
+
+ // Still don't read `a.dart` when parse the second time.
+ driver.parseFileSync(b);
+ expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
+ }
+
+ test_parseFileSync_doesNotReadPartedFiles() async {
+ var a = convertPath('/test/lib/a.dart');
+ var b = convertPath('/test/lib/b.dart');
+
+ newFile(a, content: r'''
+part of my;
+''');
+ newFile(b, content: r'''
+library my;
+part 'a.dart';
+''');
+
+ expect(driver.fsState.knownFilePaths, isEmpty);
+
+ // Don't read `a.dart` when parse.
+ driver.parseFileSync(b);
+ expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
+
+ // Still don't read `a.dart` when parse the second time.
+ driver.parseFileSync(b);
+ expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
+ }
+
test_parseFileSync_languageVersion() async {
var path = convertPath('/test/lib/test.dart');