Skip to content

Commit 4a18ca9

Browse files
committed
Track parsed URIs to avoid redundant file parsing
1 parent bfea120 commit 4a18ca9

6 files changed

Lines changed: 33 additions & 18 deletions

File tree

src/definition/implementation.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ impl Backend {
707707
// Phase 1 and can be skipped.
708708
let classmap_paths: HashSet<PathBuf> = self.classmap.read().values().cloned().collect();
709709

710-
let loaded_uris: HashSet<String> = self.ast_map.read().keys().cloned().collect();
710+
let loaded_uris: HashSet<String> = self.parsed_uris.read().iter().cloned().collect();
711711

712712
for path in &classmap_paths {
713713
let uri = crate::util::path_to_uri(path);
@@ -802,8 +802,7 @@ impl Backend {
802802
.collect()
803803
};
804804

805-
// Refresh loaded URIs — Phase 3 may have added entries.
806-
let loaded_uris_p5: HashSet<String> = self.ast_map.read().keys().cloned().collect();
805+
let loaded_uris_p5: HashSet<String> = self.parsed_uris.read().iter().cloned().collect();
807806

808807
for dir in &psr4_dirs {
809808
for php_file in collect_php_files(dir, &vendor_dir_paths) {

src/definition/resolve.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ impl Backend {
512512
let mut lazy_result = None;
513513
for path in &paths {
514514
let uri = crate::util::path_to_uri(path);
515-
if self.ast_map.read().contains_key(&uri) {
515+
if self.parsed_uris.read().contains(&uri) {
516516
continue;
517517
}
518518

@@ -655,13 +655,11 @@ impl Backend {
655655
) -> Option<Location> {
656656
let target_uri_string = crate::util::path_to_uri(file_path);
657657

658-
// Ensure the file is parsed and cached. If the file is already in
659-
// `ast_map` (opened via `did_open`, loaded from autoload files, or
660-
// parsed in a previous cross-file jump), `parse_and_cache_file`
661-
// will re-parse it — but the cost is negligible compared to the
662-
// disk I/O we'd do anyway. A future optimisation can skip the
663-
// re-parse when an `ast_map` entry already exists.
664-
let already_cached = self.ast_map.read().contains_key(&target_uri_string);
658+
// Ensure the file is parsed and cached. If the file has
659+
// already been parsed (opened via `did_open`, loaded from
660+
// autoload files, or parsed in a previous cross-file jump),
661+
// skip re-parsing.
662+
let already_cached = self.parsed_uris.read().contains(&target_uri_string);
665663

666664
if !already_cached {
667665
self.parse_and_cache_file(file_path);

src/hover/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ impl Backend {
864864
let paths = self.autoload_file_paths.read().clone();
865865
for path in &paths {
866866
let uri = crate::util::path_to_uri(path);
867-
if self.ast_map.read().contains_key(&uri) {
867+
if self.parsed_uris.read().contains(&uri) {
868868
continue;
869869
}
870870
if let Ok(content) = std::fs::read_to_string(path) {

src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,14 @@ pub struct Backend {
343343
/// classmap contains a phar-based path (detected by a `!` separator,
344344
/// e.g. `/path/to/phpstan.phar!src/Type/Type.php`).
345345
pub(crate) phar_archives: Arc<RwLock<HashMap<PathBuf, phar::PharArchive>>>,
346+
/// Set of file URIs that have been fully parsed at least once.
347+
///
348+
/// Used as a lightweight "has this file been parsed?" check by
349+
/// consumers that need to skip redundant re-parsing (e.g.
350+
/// `find_or_load_function`, `resolve_constant_definition`,
351+
/// `find_implementors`). Populated in `update_ast_inner` and
352+
/// `parse_and_cache_content_versioned`.
353+
pub(crate) parsed_uris: Arc<RwLock<HashSet<String>>>,
346354
/// Set of file URIs currently being parsed by another thread.
347355
///
348356
/// Used by [`parse_and_cache_file`](Self::parse_and_cache_file) to avoid
@@ -655,6 +663,7 @@ impl Backend {
655663
class_not_found_cache: Arc::new(RwLock::new(HashSet::new())),
656664
classmap: Arc::new(RwLock::new(HashMap::new())),
657665
phar_archives: Arc::new(RwLock::new(HashMap::new())),
666+
parsed_uris: Arc::new(RwLock::new(HashSet::new())),
658667
parse_inflight: Arc::new(Mutex::new(HashSet::new())),
659668
stub_index: RwLock::new(stubs::build_stub_class_index()),
660669
stub_function_index: RwLock::new(stubs::build_stub_function_index()),
@@ -732,6 +741,7 @@ impl Backend {
732741
class_not_found_cache: Arc::new(RwLock::new(HashSet::new())),
733742
classmap: Arc::new(RwLock::new(HashMap::new())),
734743
phar_archives: Arc::new(RwLock::new(HashMap::new())),
744+
parsed_uris: Arc::new(RwLock::new(HashSet::new())),
735745
parse_inflight: Arc::new(Mutex::new(HashSet::new())),
736746
stub_index: RwLock::new(HashMap::new()),
737747
stub_function_index: RwLock::new(HashMap::new()),
@@ -1081,6 +1091,7 @@ impl Backend {
10811091
fqn_index: Arc::clone(&self.fqn_index),
10821092
classmap: Arc::clone(&self.classmap),
10831093
phar_archives: Arc::clone(&self.phar_archives),
1094+
parsed_uris: Arc::clone(&self.parsed_uris),
10841095
parse_inflight: Arc::clone(&self.parse_inflight),
10851096
class_not_found_cache: Arc::clone(&self.class_not_found_cache),
10861097
stub_index: RwLock::new(self.stub_index.read().clone()),

src/parser/ast_update.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ impl Backend {
516516
uri_string.clone(),
517517
classes.into_iter().map(Arc::new).collect(),
518518
);
519+
self.parsed_uris.write().insert(uri_string.clone());
519520

520521
// Populate the global method store for O(1) method lookup.
521522
self.evict_methods_for_fqns(&old_fqns);

src/resolution.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -435,19 +435,25 @@ impl Backend {
435435
// Wrap each ClassInfo in Arc before inserting into the maps.
436436
let arc_classes: Vec<Arc<ClassInfo>> = classes.into_iter().map(Arc::new).collect();
437437

438-
// Check whether this URI already has an ast_map entry before
439-
// we overwrite it. Used below to decide whether resolved-class
440-
// cache eviction is needed (only on re-parse, not first load).
441-
let was_already_parsed = self.ast_map.read().contains_key(uri);
438+
// Check whether this URI already has been parsed before.
439+
// Used below to decide whether resolved-class cache eviction
440+
// is needed (only on re-parse, not first load).
441+
let was_already_parsed = self.parsed_uris.read().contains(uri);
442442

443+
// Record that this URI has been parsed.
444+
self.parsed_uris.write().insert(uri.to_owned());
445+
446+
// Store in ast_map for wait_for_cached_result (concurrent
447+
// parse deduplication) and for consumers that iterate classes
448+
// by URI. The memory cost is negligible (just Arc pointers).
443449
self.ast_map
444450
.write()
445451
.insert(uri.to_owned(), arc_classes.clone());
446452
// NOTE: use_map and namespace_map are intentionally NOT stored
447453
// for lazily-loaded files (vendor, stubs, PSR-4). These maps
448454
// are only needed for files open in the editor (populated by
449455
// update_ast_inner). Skipping them reduces memory usage across
450-
// thousands of vendor files. See P13 (tiered storage).
456+
// thousands of vendor files. See P13.
451457

452458
// Populate the fqn_index so that `find_class_in_ast_map` can
453459
// resolve these classes via O(1) hash lookup.
@@ -595,7 +601,7 @@ impl Backend {
595601
// Skip files that have already been fully parsed (their
596602
// functions are already in global_functions via Phase 1).
597603
let uri = crate::util::path_to_uri(path);
598-
if self.ast_map.read().contains_key(&uri) {
604+
if self.parsed_uris.read().contains(&uri) {
599605
continue;
600606
}
601607

0 commit comments

Comments
 (0)