Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to php-lsp are documented here.

## [Unreleased]

### Fixed

- **Stale diagnostics survive the initial workspace scan**: a file opened while indexing was still running was analyzed against a partial index, and the analysis cache — keyed on `(source, decl_version)` — was never invalidated when the scan finished, so the post-index republish re-served the same memo. Symbols declared in files the scan had not yet reached stayed reported as undefined until the file was edited. `mark_index_ready` now bumps `decl_version`, the same invalidation `note_new_file_declarations` performs on the `didChangeWatchedFiles` path.

## [0.25.1] — 2026-08-22

### Fixed
Expand Down
37 changes: 37 additions & 0 deletions src/document/document_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,10 @@ impl DocumentStore {
/// Mark the workspace reference index as fully built. Called by the scan
/// when its final phase completes (alongside `$/php-lsp/indexReady`).
pub fn mark_index_ready(&self) {
// Every cached analysis predates the finished workspace, and the
// scan bumped nothing on its way through — `note_new_file_declarations`
// is the same invalidation on the `didChangeWatchedFiles` path.
self.caches.bump_decl_version();
self.index_ready.store(true, Ordering::Release);
}

Expand Down Expand Up @@ -4022,6 +4026,39 @@ mod tests {
);
}

/// The same staleness, reached by the other caller on that path: the
/// initial workspace scan mirrors every file it reads and never calls
/// `note_new_file_declarations`, so a file opened while the scan is still
/// running keeps the memo it left behind against a partial index.
#[test]
fn stale_cached_analysis_not_invalidated_by_finished_scan() {
let store = DocumentStore::new();
let consumer_uri = uri("/app.php");
let dep_uri = uri("/Mage.php");

store.mirror_text(&consumer_uri, "<?php\n\nnew Mage();\n");
let issues = store.get_semantic_issues_salsa(&consumer_uri).unwrap();
assert!(
issues
.iter()
.any(|i| matches!(i.kind, mir_issues::IssueKind::UndefinedClass { .. })),
"sanity check: Mage must be reported missing before the scan reaches it"
);

// The scan reaches Mage.php and then finishes. `mirror_text` alone is
// what `index::workspace_scan` does with every file it reads.
store.mirror_text(&dep_uri, "<?php\nclass Mage {}\n");
store.mark_index_ready();

let issues = store.get_semantic_issues_salsa(&consumer_uri).unwrap();
assert!(
!issues
.iter()
.any(|i| matches!(i.kind, mir_issues::IssueKind::UndefinedClass { .. })),
"the finished scan did not invalidate app.php's analysis: {issues:?}"
);
}

/// Issue #191 regression: workspace-wide scans (find-references, rename,
/// call-hierarchy) must not re-parse closed/indexed files on repeated
/// invocations. Once a file's `ParsedDoc` has been produced, subsequent
Expand Down
Loading