Skip to content

Commit 211c97b

Browse files
committed
Fix class definition lookup for unopened files via PSR-4
1 parent 4b5ad0c commit 211c97b

3 files changed

Lines changed: 29 additions & 7 deletions

File tree

src/definition/member/file_lookup.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,24 @@ impl Backend {
112112
.map(|(u, _)| u.clone())
113113
}
114114
}
115-
// Fallback: the target file may have been closed (didClose clears
116-
// ast_map). Check class_index which survives close (issue #99).
117-
.or_else(|| self.class_index.read().get(class_name).cloned())?;
115+
.or_else(|| {
116+
// Fallback: the target file may have been closed (didClose
117+
// clears ast_map) or was never opened. Check class_index
118+
// which survives close.
119+
self.class_index.read().get(class_name).cloned()
120+
})
121+
.or_else(|| {
122+
// Last resort: resolve the class via PSR-4 mappings to get
123+
// the file URI. This handles classes that were loaded into
124+
// fqn_index (via parse_and_cache_file) but whose ast_map
125+
// entry was later cleared by didClose, and whose class_index
126+
// entry was never created (parse_and_cache_content does not
127+
// populate class_index).
128+
let workspace_root = self.workspace_root.read().clone()?;
129+
let mappings = self.psr4_mappings.read();
130+
let file_path = crate::composer::resolve_class_path(&mappings, &workspace_root, class_name)?;
131+
Some(crate::util::path_to_uri(&file_path))
132+
})?;
118133

119134
// Get the file content.
120135
let file_content = if uri == current_uri {

src/resolution.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ impl Backend {
118118
// classes that don't follow PSR-4 conventions and aren't in the
119119
// Composer classmap — e.g. global-namespace classes like `Mockery`
120120
// that are loaded via Composer's `files` autoloading.
121-
if let Some(file_uri) = self.class_index.read().get(class_name).cloned()
121+
let class_index_uri = self.class_index.read().get(class_name).cloned();
122+
if let Some(file_uri) = class_index_uri
122123
&& let Some(file_path) = Url::parse(&file_uri)
123124
.ok()
124125
.and_then(|u| u.to_file_path().ok())
@@ -456,15 +457,21 @@ impl Backend {
456457
// thousands of vendor files. See P13.
457458

458459
// Populate the fqn_index so that `find_class_in_ast_map` can
459-
// resolve these classes via O(1) hash lookup.
460+
// resolve these classes via O(1) hash lookup. Also populate
461+
// class_index (FQN → URI) so that `find_class_file_content` can
462+
// locate the source file even after the ast_map entry is cleared
463+
// by didClose. The class_index cost is negligible (one string
464+
// pair per class).
460465
{
461466
let mut fqn_idx = self.fqn_index.write();
467+
let mut class_idx = self.class_index.write();
462468
for cls in &arc_classes {
463469
if cls.name.starts_with("__anonymous@") {
464470
continue;
465471
}
466472
let fqn = cls.fqn().to_string();
467-
fqn_idx.insert(fqn, Arc::clone(cls));
473+
fqn_idx.insert(fqn.clone(), Arc::clone(cls));
474+
class_idx.entry(fqn).or_insert_with(|| uri.to_owned());
468475
}
469476
}
470477

test_lsp.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,4 @@ with open("example.php", "r") as f:
5353

5454
# 8. Exit
5555
send '{"jsonrpc":"2.0","method":"exit","params":{}}'
56-
} | cargo run 2>/dev/null
56+
} | cargo run 2>/dev/null

0 commit comments

Comments
 (0)