Skip to main content

fluidattacks_blends_domain/query/
scope.rs

1//! Counterpart of `blends/query/scope.py`.
2
3use alloc::collections::BTreeSet;
4use alloc::vec::Vec;
5
6use crate::syntax::SyntaxGraph;
7use crate::NodeId;
8
9fn is_self_ref(graph: &SyntaxGraph, def_nid: NodeId, lookup_nid: NodeId) -> bool {
10    graph.nodes.get(&def_nid).is_some_and(|node| {
11        node.value_id() == Some(lookup_nid) || node.variable_id() == Some(lookup_nid)
12    })
13}
14
15fn is_valid_def(graph: &SyntaxGraph, def_nid: NodeId, lookup_nid: NodeId) -> bool {
16    !is_self_ref(graph, def_nid, lookup_nid) && def_nid < lookup_nid
17}
18
19fn dedup_by_subpath(graph: &SyntaxGraph, definitions: Vec<NodeId>) -> Vec<NodeId> {
20    let mut seen_subpaths: BTreeSet<Option<NodeId>> = BTreeSet::new();
21    definitions
22        .into_iter()
23        .filter(|def_nid| {
24            let subpath = graph.subpath_index.get(def_nid).copied().flatten();
25            seen_subpaths.insert(subpath)
26        })
27        .collect()
28}
29
30fn valid_defs_in_scope(
31    graph: &SyntaxGraph,
32    def_nids: &[NodeId],
33    lookup_nid: NodeId,
34) -> Option<Vec<NodeId>> {
35    let valid_defs: Vec<NodeId> = def_nids
36        .iter()
37        .copied()
38        .filter(|&def_nid| is_valid_def(graph, def_nid, lookup_nid))
39        .collect();
40    (!valid_defs.is_empty()).then(|| dedup_by_subpath(graph, valid_defs))
41}
42
43fn find_defs_in_scope(
44    graph: &SyntaxGraph,
45    symbol: &str,
46    scope: NodeId,
47    lookup_nid: NodeId,
48) -> Option<Vec<NodeId>> {
49    let direct = graph
50        .symbol_index
51        .get(symbol)
52        .and_then(|scope_defs| scope_defs.get(&scope))
53        .and_then(|def_nids| valid_defs_in_scope(graph, def_nids, lookup_nid));
54    if direct.is_some() {
55        return direct;
56    }
57
58    graph
59        .symbol_index
60        .iter()
61        .filter(|(compound_key, _)| {
62            compound_key.contains(',') && compound_key.split(',').any(|part| part == symbol)
63        })
64        .find_map(|(_, scope_defs)| {
65            scope_defs
66                .get(&scope)
67                .and_then(|def_nids| valid_defs_in_scope(graph, def_nids, lookup_nid))
68        })
69}
70
71#[must_use]
72pub fn get_all_scope_definitions(graph: &SyntaxGraph, lookup_nid: NodeId) -> Vec<NodeId> {
73    let Some(node) = graph.nodes.get(&lookup_nid) else {
74        return Vec::new();
75    };
76    let (Some(symbol), Some(scope)) = (
77        node.symbol().filter(|symbol| !symbol.is_empty()),
78        node.symbol_scope(),
79    ) else {
80        return Vec::new();
81    };
82
83    let mut current_scope = scope;
84    loop {
85        if let Some(definitions) = find_defs_in_scope(graph, symbol, current_scope, lookup_nid) {
86            return definitions;
87        }
88        match graph.scope_parent.get(&current_scope) {
89            Some(parent_scope) => current_scope = *parent_scope,
90            None => return Vec::new(),
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use alloc::borrow::ToOwned;
98    use alloc::vec;
99    use alloc::vec::Vec;
100
101    use super::get_all_scope_definitions;
102    use crate::syntax::{SyntaxGraph, SyntaxNode};
103    use crate::NodeId;
104
105    const SCOPE: NodeId = NodeId(1);
106    const PARENT_SCOPE: NodeId = NodeId(0);
107    const LOOKUP: NodeId = NodeId(50);
108
109    fn declaration(value_id: Option<NodeId>, variable_id: Option<NodeId>) -> SyntaxNode {
110        SyntaxNode::VariableDeclaration {
111            variable: "x".to_owned(),
112            variable_type: None,
113            value_id,
114            variable_id,
115            access_modifier: None,
116        }
117    }
118
119    fn graph_with_lookup(symbol: &str, symbol_scope: Option<NodeId>) -> SyntaxGraph {
120        let mut graph = SyntaxGraph::new();
121        graph.add_node(
122            LOOKUP,
123            SyntaxNode::SymbolLookup {
124                symbol: symbol.to_owned(),
125                symbol_scope,
126                value: None,
127            },
128        );
129        graph
130    }
131
132    fn index(graph: &mut SyntaxGraph, symbol: &str, scope: NodeId, defs: Vec<NodeId>) {
133        for def_nid in &defs {
134            if !graph.nodes.contains_key(def_nid) {
135                graph.add_node(*def_nid, declaration(None, None));
136            }
137        }
138        graph
139            .symbol_index
140            .entry(symbol.to_owned())
141            .or_default()
142            .insert(scope, defs);
143    }
144
145    #[test]
146    fn resolves_a_definition_in_the_lookup_scope() {
147        let mut graph = graph_with_lookup("x", Some(SCOPE));
148        index(&mut graph, "x", SCOPE, vec![NodeId(10)]);
149
150        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(10)]);
151    }
152
153    #[test]
154    fn climbs_the_scope_chain_to_a_parent_definition() {
155        let mut graph = graph_with_lookup("x", Some(SCOPE));
156        graph.scope_parent.insert(SCOPE, PARENT_SCOPE);
157        index(&mut graph, "x", PARENT_SCOPE, vec![NodeId(10)]);
158
159        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(10)]);
160    }
161
162    #[test]
163    fn the_lookup_scope_shadows_the_parent_definition() {
164        let mut graph = graph_with_lookup("x", Some(SCOPE));
165        graph.scope_parent.insert(SCOPE, PARENT_SCOPE);
166        index(&mut graph, "x", PARENT_SCOPE, vec![NodeId(10)]);
167        index(&mut graph, "x", SCOPE, vec![NodeId(20)]);
168
169        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(20)]);
170    }
171
172    #[test]
173    fn ignores_definitions_declared_after_the_lookup() {
174        let mut graph = graph_with_lookup("x", Some(SCOPE));
175        index(&mut graph, "x", SCOPE, vec![NodeId(60)]);
176
177        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), Vec::new());
178    }
179
180    #[test]
181    fn skips_definitions_referencing_the_lookup_itself() {
182        let mut graph = graph_with_lookup("x", Some(SCOPE));
183        graph.add_node(NodeId(10), declaration(Some(LOOKUP), None));
184        graph.add_node(NodeId(20), declaration(None, Some(LOOKUP)));
185        index(
186            &mut graph,
187            "x",
188            SCOPE,
189            vec![NodeId(10), NodeId(20), NodeId(30)],
190        );
191
192        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(30)]);
193    }
194
195    #[test]
196    fn keeps_the_first_definition_per_subpath() {
197        let mut graph = graph_with_lookup("x", Some(SCOPE));
198        graph.subpath_index.insert(NodeId(10), Some(NodeId(9)));
199        graph.subpath_index.insert(NodeId(20), Some(NodeId(9)));
200        index(&mut graph, "x", SCOPE, vec![NodeId(10), NodeId(20)]);
201
202        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(10)]);
203    }
204
205    #[test]
206    fn resolves_a_symbol_inside_a_compound_key() {
207        let mut graph = graph_with_lookup("value", Some(SCOPE));
208        index(&mut graph, "done,value", SCOPE, vec![NodeId(10)]);
209
210        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(10)]);
211    }
212
213    #[test]
214    fn a_compound_fragment_match_does_not_count() {
215        let mut graph = graph_with_lookup("val", Some(SCOPE));
216        index(&mut graph, "done,value", SCOPE, vec![NodeId(10)]);
217
218        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), Vec::new());
219    }
220
221    #[test]
222    fn returns_empty_without_a_symbol_scope() {
223        let mut graph = graph_with_lookup("x", None);
224        index(&mut graph, "x", SCOPE, vec![NodeId(10)]);
225
226        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), Vec::new());
227    }
228
229    #[test]
230    fn returns_empty_for_an_empty_symbol() {
231        let mut graph = graph_with_lookup("", Some(SCOPE));
232        index(&mut graph, "", SCOPE, vec![NodeId(10)]);
233
234        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), Vec::new());
235    }
236
237    #[test]
238    fn returns_empty_for_a_missing_lookup_node() {
239        let graph = SyntaxGraph::new();
240
241        assert_eq!(get_all_scope_definitions(&graph, LOOKUP), Vec::new());
242    }
243}