Skip to main content

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}