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)]
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::{FileId, SourceFile};
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 #[test]
68 fn symbol_map_builds_and_memoizes() {
69 MEMO_CALLS.store(0, Ordering::SeqCst);
70 let mut host = AnalysisHost::new();
71 let file = SourceFile::new(
72 host.db(),
73 FileId(100),
74 Arc::<str>::from("file:///memo.php"),
75 Arc::<str>::from("<?php\nfunction greet(): void {}"),
76 None,
77 );
78
79 let _ = counted_memo(host.db(), file);
80 let _ = counted_memo(host.db(), file);
81 assert_eq!(
82 MEMO_CALLS.load(Ordering::SeqCst),
83 1,
84 "salsa should memoize the second call with unchanged input"
85 );
86 }
87
88 #[test]
89 fn symbol_map_invalidates_on_edit() {
90 INVAL_CALLS.store(0, Ordering::SeqCst);
91 let mut host = AnalysisHost::new();
92 let file = SourceFile::new(
93 host.db(),
94 FileId(101),
95 Arc::<str>::from("file:///inval.php"),
96 Arc::<str>::from("<?php\nfunction greet(): void {}"),
97 None,
98 );
99
100 let _ = counted_inval(host.db(), file);
101 assert_eq!(INVAL_CALLS.load(Ordering::SeqCst), 1);
102
103 file.set_text(host.db_mut())
104 .to(Arc::<str>::from("<?php\nfunction farewell(): void {}"));
105 let _ = counted_inval(host.db(), file);
106 assert_eq!(
107 INVAL_CALLS.load(Ordering::SeqCst),
108 2,
109 "symbol_map should re-run after source text change"
110 );
111 }
112}