fluidattacks_blends_domain/query/
scope.rs1use 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(¤t_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}