Skip to main content

php_lsp/db/
symbol_map.rs

1//! `symbol_map` salsa query — derives a [`SymbolMap`] from a parsed document.
2//!
3//! Depends on `parsed_doc`, so editing a file reparses once and then the symbol
4//! map rebuilds. Between edits all lookups are served from the cache in O(1).
5
6use std::sync::Arc;
7
8use salsa::Database;
9
10use crate::db::input::SourceFile;
11use crate::db::parse::parsed_doc;
12use crate::types::symbol_map::SymbolMap;
13
14/// Arc wrapper for [`SymbolMap`]. Pointer equality drives salsa invalidation:
15/// every `build` call produces a new `Arc`, so a changed parse always propagates.
16#[derive(Clone)]
17pub struct SymbolMapArc(pub Arc<SymbolMap>);
18
19impl SymbolMapArc {
20    pub fn get(&self) -> &SymbolMap {
21        &self.0
22    }
23}
24
25crate::impl_arc_update!(SymbolMapArc);
26
27/// Build the symbol map for a file. `no_eq` because `SymbolMapArc` has no
28/// structural equality — invalidation flows from `parsed_doc`.
29#[salsa::tracked(no_eq, lru = 2048)]
30pub fn symbol_map(db: &dyn Database, file: SourceFile<'_>) -> SymbolMapArc {
31    let doc = parsed_doc(db, file);
32    SymbolMapArc(Arc::new(SymbolMap::build(doc.get())))
33}
34
35#[cfg(test)]
36mod tests {
37    use std::sync::Arc;
38    use std::sync::atomic::{AtomicUsize, Ordering};
39
40    use salsa::Setter;
41
42    use super::*;
43    use crate::db::analysis::AnalysisHost;
44    use crate::db::input::{FileText, Workspace, workspace_files};
45
46    static MEMO_CALLS: AtomicUsize = AtomicUsize::new(0);
47    static INVAL_CALLS: AtomicUsize = AtomicUsize::new(0);
48
49    #[salsa::tracked]
50    fn counted_memo(db: &dyn Database, file: SourceFile<'_>) -> usize {
51        MEMO_CALLS.fetch_add(1, Ordering::SeqCst);
52        symbol_map(db, file)
53            .get()
54            .lookup("greet", |_| true)
55            .is_some() as usize
56    }
57
58    #[salsa::tracked]
59    fn counted_inval(db: &dyn Database, file: SourceFile<'_>) -> usize {
60        INVAL_CALLS.fetch_add(1, Ordering::SeqCst);
61        symbol_map(db, file)
62            .get()
63            .lookup("greet", |_| true)
64            .is_some() as usize
65    }
66
67    fn make_ws(host: &AnalysisHost, uri: &str, ft: FileText) -> Workspace {
68        Workspace::new(
69            host.db(),
70            std::sync::Arc::from([(Arc::<str>::from(uri), ft)]),
71            mir_analyzer::PhpVersion::LATEST,
72        )
73    }
74
75    #[test]
76    fn symbol_map_builds_and_memoizes() {
77        MEMO_CALLS.store(0, Ordering::SeqCst);
78        let host = AnalysisHost::new();
79        let ft = FileText::new(
80            host.db(),
81            Arc::<str>::from("<?php\nfunction greet(): void {}"),
82            None,
83        );
84        let ws = make_ws(&host, "file:///memo.php", ft);
85        let files = workspace_files(host.db(), ws);
86        let _ = counted_memo(host.db(), files[0]);
87        let _ = counted_memo(host.db(), files[0]);
88        assert_eq!(
89            MEMO_CALLS.load(Ordering::SeqCst),
90            1,
91            "salsa should memoize the second call with unchanged input"
92        );
93    }
94
95    #[test]
96    fn symbol_map_invalidates_on_edit() {
97        INVAL_CALLS.store(0, Ordering::SeqCst);
98        let mut host = AnalysisHost::new();
99        let ft = FileText::new(
100            host.db(),
101            Arc::<str>::from("<?php\nfunction greet(): void {}"),
102            None,
103        );
104        let ws = make_ws(&host, "file:///inval.php", ft);
105        {
106            let files = workspace_files(host.db(), ws);
107            let _ = counted_inval(host.db(), files[0]);
108            assert_eq!(INVAL_CALLS.load(Ordering::SeqCst), 1);
109        }
110
111        ft.set_text(host.db_mut())
112            .to(Arc::<str>::from("<?php\nfunction farewell(): void {}"));
113        {
114            let files = workspace_files(host.db(), ws);
115            let _ = counted_inval(host.db(), files[0]);
116            assert_eq!(
117                INVAL_CALLS.load(Ordering::SeqCst),
118                2,
119                "symbol_map should re-run after source text change"
120            );
121        }
122    }
123}