1use std::sync::Arc;
7
8use salsa::Database;
9
10use crate::db::input::SourceFile;
11use crate::db::parse::parsed_doc;
12use crate::symbol_map::SymbolMap;
13
14#[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#[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}