Skip to main content

php_lsp/db/
mir_queries.rs

1//! php-lsp salsa queries on mir's shared database: keyed on mir's `SourceFile`
2//! input and run over `&dyn MirDatabase`, so they live in the single converged
3//! db. The Arc-wrapper types they return (`ParsedArc`, `IndexArc`,
4//! `SymbolMapArc`) live in `parse.rs` / `index.rs` / `symbol_map.rs`.
5
6use std::sync::Arc;
7
8use mir_analyzer::db::{MirDatabase, SourceFile};
9use tower_lsp_server::ls_types::Uri;
10
11use crate::db::index::IndexArc;
12use crate::db::parse::ParsedArc;
13use crate::db::symbol_map::SymbolMapArc;
14use crate::db::workspace_index::{WorkspaceIndexArc, WorkspaceIndexData};
15use crate::document::ast::ParsedDoc;
16use crate::index::file_index::FileIndex;
17use crate::types::symbol_map::SymbolMap;
18
19/// Per-file php-lsp input on the converged db: pairs mir's shared `SourceFile`
20/// (the text source of truth) with the optional warm-start `cached_index`
21/// (a `FileIndex` loaded from the on-disk cache). `file_index` keys on this so
22/// the cached fast-path survives the merge; an edit updates `source`'s text and
23/// clears `cached_index`, both invalidating `file_index`.
24#[salsa::input]
25pub struct LspWsFile {
26    pub source: SourceFile,
27    pub cached_index: Option<Arc<FileIndex>>,
28}
29
30/// php-lsp's workspace scoping input on the converged db: the set of project
31/// files to aggregate into the workspace index (mir's stub/vendor source files
32/// are intentionally excluded). Changes only on file add/remove.
33#[salsa::input]
34pub struct LspWorkspace {
35    pub files: Arc<[LspWsFile]>,
36}
37
38/// Parse the file's source text into an arena-backed `ParsedDoc`, memoized on
39/// the converged db. Counterpart to [`crate::db::parse::parsed_doc`] keyed on
40/// mir's `SourceFile`.
41#[salsa::tracked(no_eq, lru = 2048)]
42pub fn parsed_doc(db: &dyn MirDatabase, file: SourceFile) -> ParsedArc {
43    ParsedArc(Arc::new(ParsedDoc::parse(file.text(db).clone())))
44}
45
46/// Build the symbol map for a file. Shares the converged-db [`parsed_doc`], so
47/// a file open via `get_doc_salsa` and queried for completion parses once.
48#[salsa::tracked(no_eq, lru = 2048)]
49pub fn symbol_map(db: &dyn MirDatabase, file: SourceFile) -> SymbolMapArc {
50    let doc = parsed_doc(db, file);
51    SymbolMapArc(Arc::new(SymbolMap::build(doc.get())))
52}
53
54/// Compact per-file declaration index. Warm-start fast path: if a disk-cached
55/// index was seeded onto `wf`, return it without parsing. Otherwise share the
56/// converged-db [`parsed_doc`] and extract.
57#[salsa::tracked]
58pub fn file_index(db: &dyn MirDatabase, wf: LspWsFile) -> IndexArc {
59    if let Some(cached) = wf.cached_index(db) {
60        return IndexArc(cached.clone());
61    }
62    let doc = parsed_doc(db, *wf.source(db));
63    IndexArc(Arc::new(FileIndex::extract(doc.get())))
64}
65
66/// Aggregate workspace index over the project files in `ws`. Depends on each
67/// file's [`file_index`]; salsa invalidates only the touched file's contribution
68/// on an edit.
69#[salsa::tracked(no_eq)]
70pub fn workspace_index(db: &dyn MirDatabase, ws: LspWorkspace) -> WorkspaceIndexArc {
71    let ws_files = ws.files(db);
72    let mut files: Vec<(Uri, Arc<FileIndex>)> = Vec::with_capacity(ws_files.len());
73    for wf in ws_files.iter() {
74        let Ok(url) = (wf.source(db).path(db)).parse::<Uri>() else {
75            continue;
76        };
77        let idx = file_index(db, *wf).0.clone();
78        files.push((url, idx));
79    }
80    WorkspaceIndexArc(Arc::new(WorkspaceIndexData::from_files(files)))
81}