1use 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::{AbsPath, NodeId, SourceLoc};
8
9pub fn dedup_locations(locations: Vec<Location>) -> Vec<Location> {
16 if locations.len() <= 1 {
17 return locations;
18 }
19
20 let mut unique_locations = Vec::new();
22 let mut seen = std::collections::HashSet::new();
23 for location in locations {
24 let key = (
25 location.uri.clone(),
26 location.range.start.line,
27 location.range.start.character,
28 location.range.end.line,
29 location.range.end.character,
30 );
31 if seen.insert(key) {
32 unique_locations.push(location);
33 }
34 }
35
36 let mut to_remove = vec![false; unique_locations.len()];
43 for i in 0..unique_locations.len() {
44 if to_remove[i] {
45 continue;
46 }
47 for j in (i + 1)..unique_locations.len() {
48 if to_remove[j] {
49 continue;
50 }
51 if unique_locations[i].uri != unique_locations[j].uri {
52 continue;
53 }
54 let ri = unique_locations[i].range;
55 let rj = unique_locations[j].range;
56 if range_contains(ri, rj) {
58 to_remove[i] = true;
59 }
60 if range_contains(rj, ri) {
62 to_remove[j] = true;
63 }
64 }
65 }
66
67 unique_locations
68 .into_iter()
69 .enumerate()
70 .filter(|(i, _)| !to_remove[*i])
71 .map(|(_, loc)| loc)
72 .collect()
73}
74
75fn range_contains(outer: Range, inner: Range) -> bool {
78 if outer == inner {
79 return false;
80 }
81 let outer_start = (outer.start.line, outer.start.character);
82 let outer_end = (outer.end.line, outer.end.character);
83 let inner_start = (inner.start.line, inner.start.character);
84 let inner_end = (inner.end.line, inner.end.character);
85 outer_start <= inner_start && inner_end <= outer_end
86}
87
88pub fn all_references(
89 nodes: &HashMap<AbsPath, HashMap<NodeId, NodeInfo>>,
90) -> HashMap<NodeId, Vec<NodeId>> {
91 let mut all_refs: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
92 for file_nodes in nodes.values() {
93 for (node_id, node_info) in file_nodes {
94 if let Some(reference_id) = node_info.referenced_declaration {
95 all_refs.entry(reference_id).or_default().push(*node_id);
96 all_refs.entry(*node_id).or_default().push(reference_id);
97 }
98 }
99 }
100 all_refs
101}
102
103pub fn byte_to_decl_via_external_refs(
106 external_refs: &ExternalRefs,
107 id_to_path: &HashMap<crate::types::SolcFileId, String>,
108 abs_path: &str,
109 byte_position: usize,
110) -> Option<NodeId> {
111 let path_to_file_id: HashMap<&str, &crate::types::SolcFileId> =
113 id_to_path.iter().map(|(id, p)| (p.as_str(), id)).collect();
114 let current_file_id = path_to_file_id.get(abs_path)?;
115
116 for (src_str, decl_id) in external_refs {
117 let Some(src_loc) = SourceLoc::parse(src_str.as_str()) else {
118 continue;
119 };
120 if src_loc.file_id_str() != **current_file_id {
122 continue;
123 }
124 if src_loc.offset <= byte_position && byte_position < src_loc.end() {
125 return Some(*decl_id);
126 }
127 }
128 None
129}
130
131pub fn byte_to_id(
132 nodes: &HashMap<AbsPath, HashMap<NodeId, NodeInfo>>,
133 abs_path: &str,
134 byte_position: usize,
135) -> Option<NodeId> {
136 let file_nodes = nodes.get(abs_path)?;
137 let mut refs: HashMap<usize, (NodeId, bool)> = HashMap::new();
138 for (id, node_info) in file_nodes {
139 let Some(src_loc) = SourceLoc::parse(node_info.src.as_str()) else {
140 continue;
141 };
142
143 if src_loc.offset <= byte_position && byte_position < src_loc.end() {
144 let diff = src_loc.length;
145 let has_ref = node_info.referenced_declaration.is_some();
146 match refs.entry(diff) {
147 std::collections::hash_map::Entry::Vacant(e) => {
148 e.insert((*id, has_ref));
149 }
150 std::collections::hash_map::Entry::Occupied(mut e) => {
151 if has_ref && !e.get().1 {
157 e.insert((*id, has_ref));
158 }
159 }
160 }
161 }
162 }
163 refs.keys().min().map(|min_diff| refs[min_diff].0)
164}
165
166pub fn id_to_location(
167 nodes: &HashMap<AbsPath, HashMap<NodeId, NodeInfo>>,
168 id_to_path: &HashMap<crate::types::SolcFileId, String>,
169 node_id: NodeId,
170) -> Option<Location> {
171 id_to_location_with_index(nodes, id_to_path, node_id, None)
172}
173
174pub fn id_to_location_with_index(
175 nodes: &HashMap<AbsPath, HashMap<NodeId, NodeInfo>>,
176 id_to_path: &HashMap<crate::types::SolcFileId, String>,
177 node_id: NodeId,
178 name_location_index: Option<usize>,
179) -> Option<Location> {
180 let mut target_node: Option<&NodeInfo> = None;
181 for file_nodes in nodes.values() {
182 if let Some(node) = file_nodes.get(&node_id) {
183 target_node = Some(node);
184 break;
185 }
186 }
187 let node = target_node?;
188
189 let loc_str = if let Some(index) = name_location_index
190 && let Some(name_loc) = node.name_locations.get(index)
191 {
192 name_loc.as_str()
193 } else if let Some(name_location) = &node.name_location {
194 name_location.as_str()
195 } else {
196 node.src.as_str()
198 };
199
200 let loc = SourceLoc::parse(loc_str)?;
201 let file_path = id_to_path.get(&loc.file_id_str())?;
202
203 let absolute_path = if std::path::Path::new(file_path).is_absolute() {
204 std::path::PathBuf::from(file_path)
205 } else {
206 std::env::current_dir().ok()?.join(file_path)
207 };
208 let source_bytes = std::fs::read(&absolute_path).ok()?;
209 let start_pos = bytes_to_pos(&source_bytes, loc.offset)?;
210 let end_pos = bytes_to_pos(&source_bytes, loc.end())?;
211 let uri = Url::from_file_path(&absolute_path).ok()?;
212
213 Some(Location {
214 uri,
215 range: Range {
216 start: start_pos,
217 end: end_pos,
218 },
219 })
220}
221
222pub fn goto_references_cached(
225 build: &CachedBuild,
226 file_uri: &Url,
227 position: Position,
228 source_bytes: &[u8],
229 name_location_index: Option<usize>,
230 include_declaration: bool,
231) -> Vec<Location> {
232 let all_refs = all_references(&build.nodes);
233 let path = match file_uri.to_file_path() {
234 Ok(p) => p,
235 Err(_) => return vec![],
236 };
237 let path_str = match path.to_str() {
238 Some(s) => s,
239 None => return vec![],
240 };
241 let abs_path = match build.path_to_abs.get(path_str) {
242 Some(ap) => ap,
243 None => return vec![],
244 };
245 let byte_position = pos_to_bytes(source_bytes, position);
246
247 if let Some(qualifier_target) = resolve_qualifier_target(&build.nodes, abs_path, byte_position)
252 {
253 return collect_qualifier_references(
254 build,
255 qualifier_target,
256 include_declaration,
257 &all_refs,
258 );
259 }
260
261 let target_node_id = if let Some(decl_id) = byte_to_decl_via_external_refs(
263 &build.external_refs,
264 &build.id_to_path_map,
265 abs_path,
266 byte_position,
267 ) {
268 decl_id
269 } else {
270 let node_id = match byte_to_id(&build.nodes, abs_path, byte_position) {
271 Some(id) => id,
272 None => return vec![],
273 };
274 let file_nodes = match build.nodes.get(abs_path) {
275 Some(nodes) => nodes,
276 None => return vec![],
277 };
278 if let Some(node_info) = file_nodes.get(&node_id) {
279 node_info.referenced_declaration.unwrap_or(node_id)
280 } else {
281 node_id
282 }
283 };
284
285 let mut results: HashSet<NodeId> = HashSet::new();
286 if include_declaration {
287 results.insert(target_node_id);
288 }
289 if let Some(refs) = all_refs.get(&target_node_id) {
290 results.extend(refs.iter().copied());
291 }
292 let mut locations = Vec::new();
293 for id in results {
294 if let Some(location) =
295 id_to_location_with_index(&build.nodes, &build.id_to_path_map, id, name_location_index)
296 {
297 locations.push(location);
298 }
299 }
300
301 for (src_str, decl_id) in &build.external_refs {
303 if *decl_id == target_node_id
304 && let Some(location) = src_to_location(src_str.as_str(), &build.id_to_path_map)
305 {
306 locations.push(location);
307 }
308 }
309
310 dedup_locations(locations)
311}
312
313fn resolve_qualifier_target(
317 nodes: &HashMap<AbsPath, HashMap<NodeId, NodeInfo>>,
318 abs_path: &str,
319 byte_position: usize,
320) -> Option<NodeId> {
321 let node_id = byte_to_id(nodes, abs_path, byte_position)?;
322 let file_nodes = nodes.get(abs_path)?;
323 let node_info = file_nodes.get(&node_id)?;
324
325 if node_info.node_type.as_deref() != Some("IdentifierPath")
327 || node_info.name_locations.len() <= 1
328 {
329 return None;
330 }
331
332 let first_loc = SourceLoc::parse(&node_info.name_locations[0])?;
334 if byte_position < first_loc.offset || byte_position >= first_loc.end() {
335 return None;
336 }
337
338 let ref_decl_id = node_info.referenced_declaration?;
340 for file_nodes in nodes.values() {
342 if let Some(decl_node) = file_nodes.get(&ref_decl_id) {
343 return decl_node.scope;
344 }
345 }
346 None
347}
348
349fn collect_qualifier_references(
353 build: &CachedBuild,
354 container_id: NodeId,
355 include_declaration: bool,
356 all_refs: &HashMap<NodeId, Vec<NodeId>>,
357) -> Vec<Location> {
358 let mut results: HashSet<NodeId> = HashSet::new();
359 if include_declaration {
360 results.insert(container_id);
361 }
362
363 if let Some(refs) = all_refs.get(&container_id) {
365 results.extend(refs.iter().copied());
366 }
367
368 let mut locations = Vec::new();
369
370 for id in &results {
372 if let Some(location) =
373 id_to_location_with_index(&build.nodes, &build.id_to_path_map, *id, None)
374 {
375 locations.push(location);
376 }
377 }
378
379 if let Some(qualifier_node_ids) = build.qualifier_refs.get(&container_id) {
383 for &qnode_id in qualifier_node_ids {
384 if let Some(location) = id_to_location_with_index(
385 &build.nodes,
386 &build.id_to_path_map,
387 qnode_id,
388 Some(0), ) {
390 locations.push(location);
391 }
392 }
393 }
394
395 for (src_str, decl_id) in &build.external_refs {
397 if *decl_id == container_id
398 && let Some(location) = src_to_location(src_str.as_str(), &build.id_to_path_map)
399 {
400 locations.push(location);
401 }
402 }
403
404 dedup_locations(locations)
405}
406
407pub fn resolve_target_location(
412 build: &CachedBuild,
413 file_uri: &Url,
414 position: Position,
415 source_bytes: &[u8],
416) -> Option<(String, usize)> {
417 let path = file_uri.to_file_path().ok()?;
418 let path_str = path.to_str()?;
419 let abs_path = build.path_to_abs.get(path_str)?;
420 let byte_position = pos_to_bytes(source_bytes, position);
421
422 if let Some(container_id) = resolve_qualifier_target(&build.nodes, abs_path, byte_position) {
425 for (file_abs_path, file_nodes) in &build.nodes {
426 if let Some(node_info) = file_nodes.get(&container_id) {
427 let loc_str = node_info
428 .name_location
429 .as_deref()
430 .unwrap_or(node_info.src.as_str());
431 if let Some(src_loc) = SourceLoc::parse(loc_str) {
432 return Some((file_abs_path.to_string(), src_loc.offset));
433 }
434 }
435 }
436 return None;
437 }
438
439 let target_node_id = if let Some(decl_id) = byte_to_decl_via_external_refs(
441 &build.external_refs,
442 &build.id_to_path_map,
443 abs_path,
444 byte_position,
445 ) {
446 decl_id
447 } else {
448 let node_id = byte_to_id(&build.nodes, abs_path, byte_position)?;
449 let file_nodes = build.nodes.get(abs_path)?;
450 if let Some(node_info) = file_nodes.get(&node_id) {
451 node_info.referenced_declaration.unwrap_or(node_id)
452 } else {
453 node_id
454 }
455 };
456
457 for (file_abs_path, file_nodes) in &build.nodes {
465 if let Some(node_info) = file_nodes.get(&target_node_id) {
466 let loc_str = node_info
467 .name_location
468 .as_deref()
469 .unwrap_or(node_info.src.as_str());
470 if let Some(src_loc) = SourceLoc::parse(loc_str) {
471 return Some((file_abs_path.to_string(), src_loc.offset));
472 }
473 }
474 }
475 None
476}
477
478pub fn goto_references_for_target(
489 build: &CachedBuild,
490 def_abs_path: &str,
491 def_byte_offset: usize,
492 name_location_index: Option<usize>,
493 include_declaration: bool,
494 exclude_abs_path: Option<&str>,
495) -> Vec<Location> {
496 let target_node_id = match byte_to_id(&build.nodes, def_abs_path, def_byte_offset) {
498 Some(id) => {
499 if let Some(file_nodes) = build.nodes.get(def_abs_path) {
501 if let Some(node_info) = file_nodes.get(&id) {
502 node_info.referenced_declaration.unwrap_or(id)
503 } else {
504 id
505 }
506 } else {
507 id
508 }
509 }
510 None => return vec![],
511 };
512
513 let mut target_ids: HashSet<NodeId> = HashSet::new();
517 target_ids.insert(target_node_id);
518 if let Some(related_ids) = build.base_function_implementation.get(&target_node_id) {
519 for &related_id in related_ids {
520 target_ids.insert(related_id);
521 }
522 }
523
524 let is_qualifier_target =
530 !build.qualifier_refs.is_empty() && build.qualifier_refs.contains_key(&target_node_id);
531
532 let excluded_ids: HashSet<NodeId> = if let Some(excl) = exclude_abs_path {
535 build
536 .nodes
537 .get(excl)
538 .map(|file_nodes| file_nodes.keys().copied().collect())
539 .unwrap_or_default()
540 } else {
541 HashSet::new()
542 };
543
544 let mut results: HashSet<NodeId> = HashSet::new();
547 if include_declaration {
548 for &tid in &target_ids {
549 results.insert(tid);
550 }
551 }
552 for file_nodes in build.nodes.values() {
553 for (id, node_info) in file_nodes {
554 if node_info
555 .referenced_declaration
556 .is_some_and(|rd| target_ids.contains(&rd))
557 {
558 results.insert(*id);
559 }
560 }
561 }
562
563 let mut locations = Vec::new();
564 for id in results {
565 if excluded_ids.contains(&id) {
566 continue;
567 }
568 if let Some(location) =
569 id_to_location_with_index(&build.nodes, &build.id_to_path_map, id, name_location_index)
570 {
571 locations.push(location);
572 }
573 }
574
575 if is_qualifier_target {
579 if let Some(qualifier_node_ids) = build.qualifier_refs.get(&target_node_id) {
580 for &qnode_id in qualifier_node_ids {
581 if excluded_ids.contains(&qnode_id) {
582 continue;
583 }
584 if let Some(location) = id_to_location_with_index(
585 &build.nodes,
586 &build.id_to_path_map,
587 qnode_id,
588 Some(0), ) {
590 locations.push(location);
591 }
592 }
593 }
594 }
595
596 for (src_str, decl_id) in &build.external_refs {
598 if target_ids.contains(decl_id) {
599 if let Some(excl) = exclude_abs_path {
601 if let Some(src_loc) = SourceLoc::parse(src_str.as_str()) {
602 if let Some(ref_path) = build.id_to_path_map.get(&src_loc.file_id_str()) {
603 if ref_path == excl {
604 continue;
605 }
606 }
607 }
608 }
609 if let Some(location) = src_to_location(src_str.as_str(), &build.id_to_path_map) {
610 locations.push(location);
611 }
612 }
613 }
614
615 dedup_locations(locations)
616}