1use std::sync::Arc;
11
12use salsa::{Database, Update};
13
14use crate::db::codebase::codebase;
15use crate::db::input::{SourceFile, Workspace};
16use crate::db::parse::parsed_doc;
17
18#[derive(Debug, Clone)]
22pub struct FileRefRecord {
23 pub key: Arc<str>,
24 pub start: u32,
25 pub end: u32,
26}
27
28#[derive(Clone)]
29pub struct FileRefsArc(pub Arc<Vec<FileRefRecord>>);
30
31impl FileRefsArc {
32 pub fn get(&self) -> &[FileRefRecord] {
33 &self.0
34 }
35}
36
37unsafe impl Update for FileRefsArc {
40 unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
41 let old_ref = unsafe { &mut *old_pointer };
42 if Arc::ptr_eq(&old_ref.0, &new_value.0) {
43 false
44 } else {
45 *old_ref = new_value;
46 true
47 }
48 }
49}
50
51#[derive(Clone)]
52pub struct SymbolRefsArc(pub Arc<Vec<(Arc<str>, u32, u32)>>);
53
54impl SymbolRefsArc {
55 pub fn get(&self) -> &[(Arc<str>, u32, u32)] {
56 &self.0
57 }
58}
59
60unsafe impl Update for SymbolRefsArc {
61 unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
62 let old_ref = unsafe { &mut *old_pointer };
63 if Arc::ptr_eq(&old_ref.0, &new_value.0) {
64 false
65 } else {
66 *old_ref = new_value;
67 true
68 }
69 }
70}
71
72#[salsa::tracked(no_eq)]
81pub fn file_refs(db: &dyn Database, ws: Workspace, file: SourceFile) -> FileRefsArc {
82 let cb = codebase(db, ws);
83 let doc = parsed_doc(db, file);
84 let uri = file.uri(db);
85 let source = file.text(db);
86 let map = php_rs_parser::source_map::SourceMap::new(&source);
87 let mut issue_buffer = mir_issues::IssueBuffer::new();
88 let mut symbols = Vec::new();
89 {
90 let mut analyzer = mir_analyzer::stmt::StatementsAnalyzer::new(
91 cb.get(),
92 uri,
93 &source,
94 &map,
95 &mut issue_buffer,
96 &mut symbols,
97 ws.php_version(db),
98 );
99 let mut ctx = mir_analyzer::context::Context::new();
100 analyzer.analyze_stmts(&doc.get().program().stmts, &mut ctx);
101 }
102
103 let records: Vec<FileRefRecord> = symbols
104 .into_iter()
105 .filter_map(|s| {
106 let key = s.codebase_key()?;
107 Some(FileRefRecord {
108 key: Arc::from(key),
109 start: s.span.start,
110 end: s.span.end,
111 })
112 })
113 .collect();
114 FileRefsArc(Arc::new(records))
115}
116
117#[salsa::tracked(no_eq)]
121pub fn symbol_refs(db: &dyn Database, ws: Workspace, key: String) -> SymbolRefsArc {
122 let files = ws.files(db);
123 let mut out: Vec<(Arc<str>, u32, u32)> = Vec::new();
124 for sf in files.iter() {
125 let refs = file_refs(db, ws, *sf);
126 let uri = sf.uri(db);
127 for r in refs.get() {
128 if r.key.as_ref() == key.as_str() {
129 out.push((uri.clone(), r.start, r.end));
130 }
131 }
132 }
133 SymbolRefsArc(Arc::new(out))
134}
135
136#[cfg(test)]
137mod tests {
138 use std::sync::Arc;
139
140 use super::*;
141 use crate::db::analysis::AnalysisHost;
142 use crate::db::input::{FileId, SourceFile};
143
144 #[test]
145 fn symbol_refs_finds_function_call_across_files() {
146 let host = AnalysisHost::new();
147 let f1 = SourceFile::new(
148 host.db(),
149 FileId(0),
150 Arc::<str>::from("file:///a.php"),
151 Arc::<str>::from("<?php\nfunction greet(): void {}"),
152 None,
153 );
154 let f2 = SourceFile::new(
155 host.db(),
156 FileId(1),
157 Arc::<str>::from("file:///b.php"),
158 Arc::<str>::from("<?php\ngreet();"),
159 None,
160 );
161 let ws = Workspace::new(
162 host.db(),
163 Arc::from([f1, f2]),
164 mir_analyzer::PhpVersion::LATEST,
165 );
166
167 let locs = symbol_refs(host.db(), ws, "greet".to_string());
168 let found: Vec<&str> = locs.get().iter().map(|(u, _, _)| u.as_ref()).collect();
169 assert!(
170 found.iter().any(|u| *u == "file:///b.php"),
171 "expected a reference from b.php, got {:?}",
172 found
173 );
174 }
175
176 #[test]
177 fn symbol_refs_memoizes_per_key() {
178 let host = AnalysisHost::new();
179 let f1 = SourceFile::new(
180 host.db(),
181 FileId(0),
182 Arc::<str>::from("file:///a.php"),
183 Arc::<str>::from("<?php\nfunction hi(): void {}\nhi();"),
184 None,
185 );
186 let ws = Workspace::new(host.db(), Arc::from([f1]), mir_analyzer::PhpVersion::LATEST);
187
188 let a = symbol_refs(host.db(), ws, "hi".to_string());
189 let b = symbol_refs(host.db(), ws, "hi".to_string());
190 assert!(Arc::ptr_eq(&a.0, &b.0), "second call should be memoized");
191 }
192}