devscout_rs/query/symbol.rs
1use super::index::GraphIndex;
2
3// ============================================================================
4// resolve_symbol.
5// ============================================================================
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8/// Represents `Resolution`.
9pub enum Resolution {
10 /// Represents `Resolved`.
11 Resolved(String),
12 /// Represents `Ambiguous`.
13 Ambiguous(Vec<String>),
14 /// Represents `NotFound`.
15 NotFound,
16}
17
18/// "Never guess" resolution ladder: exact id, then unique exact name, then
19/// unique case-insensitive name; two-or-more candidates at any step is reported
20/// as ambiguous, not resolved further.
21pub fn resolve_symbol(index: &GraphIndex, query: &str) -> Resolution {
22 if index.by_id.contains_key(query) {
23 return Resolution::Resolved(query.to_string());
24 }
25 if let Some(exact) = index.by_simple_name.get(query) {
26 if exact.len() == 1 {
27 return Resolution::Resolved(index.graph.defs[exact[0]].id.clone());
28 }
29 if exact.len() > 1 {
30 return Resolution::Ambiguous(
31 exact
32 .iter()
33 .map(|&i| index.graph.defs[i].id.clone())
34 .collect(),
35 );
36 }
37 }
38 let lower = query.to_lowercase();
39 if let Some(ci) = index.by_lower_name.get(&lower) {
40 if ci.len() == 1 {
41 return Resolution::Resolved(index.graph.defs[ci[0]].id.clone());
42 }
43 if ci.len() > 1 {
44 return Resolution::Ambiguous(
45 ci.iter().map(|&i| index.graph.defs[i].id.clone()).collect(),
46 );
47 }
48 }
49 // A TAIL of a def id, which is how a caller spells an enum member:
50 // `Toggles.EnableX`, never the namespace-qualified
51 // `App.Config.Toggles.EnableX` the graph keys it under. Reached only after
52 // every step above has missed, so no query that resolved before still
53 // resolves differently. Restricted to a dotted query on purpose -- every
54 // def is indexed under its simple name, so `by_simple_name` above is
55 // already exhaustive for an undotted one and this step could only repeat
56 // it. Two or more ids ending in the same tail is an ambiguity, reported as
57 // one rather than guessed at. Iterates `graph.defs` directly (its array
58 // order) rather than the unordered `by_id` map.
59 if query.contains('.') {
60 let suffix = format!(".{query}");
61 let tail: Vec<String> = index
62 .graph
63 .defs
64 .iter()
65 .filter(|d| d.id.ends_with(&suffix))
66 .map(|d| d.id.clone())
67 .collect();
68 if tail.len() == 1 {
69 return Resolution::Resolved(tail.into_iter().next().expect("one tail match"));
70 }
71 if tail.len() > 1 {
72 return Resolution::Ambiguous(tail);
73 }
74 }
75 Resolution::NotFound
76}