fix: review follow-ups for #5 and the Swift lexical preview #8

Merged
trevon merged 9 commits from fix/review-followups into main 2026-09-25 13:33:41 +00:00
Showing only changes of commit 3fbb9275a5 - Show all commits

perf(scan): index import edges once instead of scanning per file

imported_paths found the file node with a linear scan of all nodes and
then filtered all edges, for every Python/Dart file it built a set for:
O(files x (nodes + edges)) during resolution, about 10^10 comparisons
for 10k files and a million edges.

Imports edges are now indexed by source file on first use and the file
node is looked up by its stable id, as resolve_import already does. The
index is complete when first built: resolve_relationships resolves
every import before any call. Output is unchanged (all 124 tests pass).
trvon 2026-09-24 21:05:11 -06:00
Signed by: trevon
GPG key ID: 38686EA797D7A8A7

View file

@ -248,6 +248,9 @@ struct ScanState {
references: HashMap<String, HashSet<String>>,
/// Lexical-language file -> repository files it imports (resolved edges).
imported_paths: HashMap<String, Rc<HashSet<String>>>,
/// Imports edges by source file id, built once on first use: scanning every
/// node and edge per file made resolution O(files x (nodes + edges)).
imports_by_file: Option<HashMap<String, Vec<String>>>,
/// `name:` of the root pubspec.yaml, so `package:<name>/x.dart` maps to lib/x.dart.
dart_package: Option<String>,
}
@ -283,6 +286,7 @@ impl ScanState {
rust: RustIndex::default(),
references: HashMap::new(),
imported_paths: HashMap::new(),
imports_by_file: None,
}
}
@ -1319,20 +1323,27 @@ impl ScanState {
if let Some(found) = self.imported_paths.get(path) {
return Rc::clone(found);
}
if self.imports_by_file.is_none() {
let mut index: HashMap<String, Vec<String>> = HashMap::new();
for edge in self.edges.iter().filter(|e| e.kind == EdgeKind::Imports) {
index
.entry(edge.source.clone())
.or_default()
.push(edge.target.clone());
}
self.imports_by_file = Some(index);
}
let file_id = stable_id("file", path);
let paths: HashSet<String> = self
.nodes
.iter()
.find(|n| n.kind == NodeKind::File && n.path.as_deref() == Some(path))
.map(|file| {
self.edges
.iter()
.filter(|e| e.kind == EdgeKind::Imports && e.source == file.id)
.filter_map(|e| self.node(&e.target))
.filter(|n| n.kind == NodeKind::File)
.filter_map(|n| n.path.clone())
.collect()
})
.unwrap_or_default();
.imports_by_file
.as_ref()
.and_then(|index| index.get(&file_id))
.into_iter()
.flatten()
.filter_map(|target| self.node(target))
.filter(|n| n.kind == NodeKind::File)
.filter_map(|n| n.path.clone())
.collect();
let paths = Rc::new(paths);
self.imported_paths
.insert(path.to_owned(), Rc::clone(&paths));