solidity_language_server/
references.rs1use std::collections::{HashMap, HashSet};
2use tower_lsp::lsp_types::{Location, Position, Range, Url};
3
4use crate::goto::{
5 CachedBuild, ExternalRefs, NodeInfo, bytes_to_pos, pos_to_bytes, src_to_location,
6};
7use crate::types::{NodeId, SourceLoc};
8
9pub fn all_references(
10 nodes: &HashMap<String, HashMap<NodeId, NodeInfo>>,
11) -> HashMap<NodeId, Vec<NodeId>> {
12 let mut all_refs: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
13 for file_nodes in nodes.values() {
14 for (id, node_info) in file_nodes {
15 if let Some(ref_id) = node_info.referenced_declaration {
16 all_refs.entry(ref_id).or_default().push(*id);
17 all_refs.entry(*id).or_default().push(ref_id);
18 }
19 }
20 }
21 all_refs
22}
23
24pub fn byte_to_decl_via_external_refs(
27 external_refs: &ExternalRefs,
28 id_to_path: &HashMap<String, String>,
29 abs_path: &str,
30 byte_position: usize,
31) -> Option<NodeId> {
32 let path_to_file_id: HashMap<&str, &str> = id_to_path
34 .iter()
35 .map(|(id, p)| (p.as_str(), id.as_str()))
36 .collect();
37 let current_file_id = path_to_file_id.get(abs_path)?;
38
39 for (src_str, decl_id) in external_refs {
40 let Some(src_loc) = SourceLoc::parse(src_str) else {
41 continue;
42 };
43 if src_loc.file_id_str() != *current_file_id {
45 continue;
46 }
47 if src_loc.offset <= byte_position && byte_position < src_loc.end() {
48 return Some(*decl_id);
49 }
50 }
51 None
52}
53
54pub fn byte_to_id(
55 nodes: &HashMap<String, HashMap<NodeId, NodeInfo>>,
56 abs_path: &str,
57 byte_position: usize,
58) -> Option<NodeId> {
59 let file_nodes = nodes.get(abs_path)?;
60 let mut refs: HashMap<usize, (NodeId, bool)> = HashMap::new();
61 for (id, node_info) in file_nodes {
62 let Some(src_loc) = SourceLoc::parse(&node_info.src) else {
63 continue;
64 };
65
66 if src_loc.offset <= byte_position && byte_position < src_loc.end() {
67 let diff = src_loc.length;
68 let has_ref = node_info.referenced_declaration.is_some();
69 match refs.entry(diff) {
70 std::collections::hash_map::Entry::Vacant(e) => {
71 e.insert((*id, has_ref));
72 }
73 std::collections::hash_map::Entry::Occupied(mut e) => {
74 if has_ref && !e.get().1 {
80 e.insert((*id, has_ref));
81 }
82 }
83 }
84 }
85 }
86 refs.keys().min().map(|min_diff| refs[min_diff].0)
87}
88
89pub fn id_to_location(
90 nodes: &HashMap<String, HashMap<NodeId, NodeInfo>>,
91 id_to_path: &HashMap<String, String>,
92 node_id: NodeId,
93) -> Option<Location> {
94 id_to_location_with_index(nodes, id_to_path, node_id, None)
95}
96
97pub fn id_to_location_with_index(
98 nodes: &HashMap<String, HashMap<NodeId, NodeInfo>>,
99 id_to_path: &HashMap<String, String>,
100 node_id: NodeId,
101 name_location_index: Option<usize>,
102) -> Option<Location> {
103 let mut target_node: Option<&NodeInfo> = None;
104 for file_nodes in nodes.values() {
105 if let Some(node) = file_nodes.get(&node_id) {
106 target_node = Some(node);
107 break;
108 }
109 }
110 let node = target_node?;
111
112 let loc_str = if let Some(index) = name_location_index
113 && let Some(name_loc) = node.name_locations.get(index)
114 {
115 name_loc.as_str()
116 } else if let Some(name_location) = &node.name_location {
117 name_location.as_str()
118 } else {
119 node.src.as_str()
121 };
122
123 let loc = SourceLoc::parse(loc_str)?;
124 let file_path = id_to_path.get(&loc.file_id_str())?;
125
126 let absolute_path = if std::path::Path::new(file_path).is_absolute() {
127 std::path::PathBuf::from(file_path)
128 } else {
129 std::env::current_dir().ok()?.join(file_path)
130 };
131 let source_bytes = std::fs::read(&absolute_path).ok()?;
132 let start_pos = bytes_to_pos(&source_bytes, loc.offset)?;
133 let end_pos = bytes_to_pos(&source_bytes, loc.end())?;
134 let uri = Url::from_file_path(&absolute_path).ok()?;
135
136 Some(Location {
137 uri,
138 range: Range {
139 start: start_pos,
140 end: end_pos,
141 },
142 })
143}
144
145pub fn goto_references_cached(
148 build: &CachedBuild,
149 file_uri: &Url,
150 position: Position,
151 source_bytes: &[u8],
152 name_location_index: Option<usize>,
153 include_declaration: bool,
154) -> Vec<Location> {
155 let all_refs = all_references(&build.nodes);
156 let path = match file_uri.to_file_path() {
157 Ok(p) => p,
158 Err(_) => return vec![],
159 };
160 let path_str = match path.to_str() {
161 Some(s) => s,
162 None => return vec![],
163 };
164 let abs_path = match build.path_to_abs.get(path_str) {
165 Some(ap) => ap,
166 None => return vec![],
167 };
168 let byte_position = pos_to_bytes(source_bytes, position);
169
170 let target_node_id = if let Some(decl_id) = byte_to_decl_via_external_refs(
172 &build.external_refs,
173 &build.id_to_path_map,
174 abs_path,
175 byte_position,
176 ) {
177 decl_id
178 } else {
179 let node_id = match byte_to_id(&build.nodes, abs_path, byte_position) {
180 Some(id) => id,
181 None => return vec![],
182 };
183 let file_nodes = match build.nodes.get(abs_path) {
184 Some(nodes) => nodes,
185 None => return vec![],
186 };
187 if let Some(node_info) = file_nodes.get(&node_id) {
188 node_info.referenced_declaration.unwrap_or(node_id)
189 } else {
190 node_id
191 }
192 };
193
194 let mut results: HashSet<NodeId> = HashSet::new();
195 if include_declaration {
196 results.insert(target_node_id);
197 }
198 if let Some(refs) = all_refs.get(&target_node_id) {
199 results.extend(refs.iter().copied());
200 }
201 let mut locations = Vec::new();
202 for id in results {
203 if let Some(location) =
204 id_to_location_with_index(&build.nodes, &build.id_to_path_map, id, name_location_index)
205 {
206 locations.push(location);
207 }
208 }
209
210 for (src_str, decl_id) in &build.external_refs {
212 if *decl_id == target_node_id
213 && let Some(location) = src_to_location(src_str, &build.id_to_path_map)
214 {
215 locations.push(location);
216 }
217 }
218
219 let mut unique_locations = Vec::new();
220 let mut seen = std::collections::HashSet::new();
221 for location in locations {
222 let key = (
223 location.uri.clone(),
224 location.range.start.line,
225 location.range.start.character,
226 location.range.end.line,
227 location.range.end.character,
228 );
229 if seen.insert(key) {
230 unique_locations.push(location);
231 }
232 }
233 unique_locations
234}
235
236pub fn resolve_target_location(
241 build: &CachedBuild,
242 file_uri: &Url,
243 position: Position,
244 source_bytes: &[u8],
245) -> Option<(String, usize)> {
246 let path = file_uri.to_file_path().ok()?;
247 let path_str = path.to_str()?;
248 let abs_path = build.path_to_abs.get(path_str)?;
249 let byte_position = pos_to_bytes(source_bytes, position);
250
251 let target_node_id = if let Some(decl_id) = byte_to_decl_via_external_refs(
253 &build.external_refs,
254 &build.id_to_path_map,
255 abs_path,
256 byte_position,
257 ) {
258 decl_id
259 } else {
260 let node_id = byte_to_id(&build.nodes, abs_path, byte_position)?;
261 let file_nodes = build.nodes.get(abs_path)?;
262 if let Some(node_info) = file_nodes.get(&node_id) {
263 node_info.referenced_declaration.unwrap_or(node_id)
264 } else {
265 node_id
266 }
267 };
268
269 for (file_abs_path, file_nodes) in &build.nodes {
277 if let Some(node_info) = file_nodes.get(&target_node_id) {
278 let loc_str = node_info.name_location.as_deref().unwrap_or(&node_info.src);
279 if let Some(src_loc) = SourceLoc::parse(loc_str) {
280 return Some((file_abs_path.clone(), src_loc.offset));
281 }
282 }
283 }
284 None
285}
286
287pub fn goto_references_for_target(
292 build: &CachedBuild,
293 def_abs_path: &str,
294 def_byte_offset: usize,
295 name_location_index: Option<usize>,
296 include_declaration: bool,
297) -> Vec<Location> {
298 let target_node_id = match byte_to_id(&build.nodes, def_abs_path, def_byte_offset) {
300 Some(id) => {
301 if let Some(file_nodes) = build.nodes.get(def_abs_path) {
303 if let Some(node_info) = file_nodes.get(&id) {
304 node_info.referenced_declaration.unwrap_or(id)
305 } else {
306 id
307 }
308 } else {
309 id
310 }
311 }
312 None => return vec![],
313 };
314
315 let mut results: HashSet<NodeId> = HashSet::new();
317 if include_declaration {
318 results.insert(target_node_id);
319 }
320 for file_nodes in build.nodes.values() {
321 for (id, node_info) in file_nodes {
322 if node_info.referenced_declaration == Some(target_node_id) {
323 results.insert(*id);
324 }
325 }
326 }
327
328 let mut locations = Vec::new();
329 for id in results {
330 if let Some(location) =
331 id_to_location_with_index(&build.nodes, &build.id_to_path_map, id, name_location_index)
332 {
333 locations.push(location);
334 }
335 }
336
337 for (src_str, decl_id) in &build.external_refs {
339 if *decl_id == target_node_id
340 && let Some(location) = src_to_location(src_str, &build.id_to_path_map)
341 {
342 locations.push(location);
343 }
344 }
345
346 locations
347}