sqry_core/graph/unified/bind/
query.rs1use crate::graph::unified::concurrent::GraphSnapshot;
17use crate::graph::unified::resolution::{FileScope, ResolutionMode, SymbolQuery};
18
19use super::BindingResult;
20use super::plane::BindingResolution;
21
22pub struct BindingQuery<'a> {
33 symbol: &'a str,
34 file_scope: FileScope<'a>,
35 mode: ResolutionMode,
36}
37
38impl<'a> BindingQuery<'a> {
39 #[must_use]
43 pub fn new(symbol: &'a str) -> Self {
44 Self {
45 symbol,
46 file_scope: FileScope::Any,
47 mode: ResolutionMode::AllowSuffixCandidates,
48 }
49 }
50
51 #[must_use]
53 pub fn file_scope(mut self, scope: FileScope<'a>) -> Self {
54 self.file_scope = scope;
55 self
56 }
57
58 #[must_use]
60 pub fn mode(mut self, mode: ResolutionMode) -> Self {
61 self.mode = mode;
62 self
63 }
64
65 #[must_use]
77 pub fn resolve(self, snapshot: &GraphSnapshot) -> BindingResult {
78 let query = SymbolQuery {
79 symbol: self.symbol,
80 file_scope: self.file_scope,
81 mode: self.mode,
82 };
83 super::plane::resolve_shared(&query, snapshot).result
84 }
85
86 #[must_use]
100 pub fn resolve_with_witness(self, snapshot: &GraphSnapshot) -> BindingResolution {
101 let query = SymbolQuery {
102 symbol: self.symbol,
103 file_scope: self.file_scope,
104 mode: self.mode,
105 };
106 super::plane::resolve_shared(&query, snapshot)
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::graph::node::Language;
114 use crate::graph::unified::concurrent::CodeGraph;
115 use crate::graph::unified::edge::kind::EdgeKind;
116 use crate::graph::unified::node::kind::NodeKind;
117 use crate::graph::unified::storage::arena::NodeEntry;
118
119 #[test]
120 fn builder_defaults() {
121 let query = BindingQuery::new("test_sym");
122 assert_eq!(query.symbol, "test_sym");
123 assert_eq!(query.file_scope, FileScope::Any);
124 assert_eq!(query.mode, ResolutionMode::AllowSuffixCandidates);
125 }
126
127 fn make_graph_with_function(sym: &str) -> CodeGraph {
129 let mut graph = CodeGraph::new();
130 let path = std::path::PathBuf::from("/query-tests/test.rs");
131 let file_id = graph
132 .files_mut()
133 .register_with_language(&path, Some(Language::Rust))
134 .expect("register file");
135 let name = graph.strings_mut().intern(sym).expect("intern sym");
136 let qn = graph
137 .strings_mut()
138 .intern(&format!("crate::{sym}"))
139 .expect("intern qn");
140 let mod_name = graph.strings_mut().intern("root").expect("intern root");
141 let mod_qn = graph.strings_mut().intern("crate").expect("intern crate");
142 let mod_id = graph
143 .nodes_mut()
144 .alloc(
145 NodeEntry::new(NodeKind::Module, mod_name, file_id)
146 .with_qualified_name(mod_qn)
147 .with_byte_range(0, 100),
148 )
149 .expect("alloc mod");
150 graph
151 .indices_mut()
152 .add(mod_id, NodeKind::Module, mod_name, Some(mod_qn), file_id);
153 let fn_id = graph
154 .nodes_mut()
155 .alloc(
156 NodeEntry::new(NodeKind::Function, name, file_id)
157 .with_qualified_name(qn)
158 .with_byte_range(5, 80),
159 )
160 .expect("alloc fn");
161 graph
162 .indices_mut()
163 .add(fn_id, NodeKind::Function, name, Some(qn), file_id);
164 graph
165 .edges_mut()
166 .add_edge(mod_id, fn_id, EdgeKind::Contains, file_id);
167 graph
168 }
169
170 #[test]
173 fn resolve_with_witness_result_matches_resolve() {
174 let graph = make_graph_with_function("witness_fn");
175 let snapshot = graph.snapshot();
176
177 let result_only = BindingQuery::new("witness_fn")
178 .file_scope(FileScope::Any)
179 .mode(ResolutionMode::AllowSuffixCandidates)
180 .resolve(&snapshot);
181
182 let with_witness = BindingQuery::new("witness_fn")
183 .file_scope(FileScope::Any)
184 .mode(ResolutionMode::AllowSuffixCandidates)
185 .resolve_with_witness(&snapshot);
186
187 assert_eq!(
188 result_only, with_witness.result,
189 "resolve_with_witness().result must be byte-equal to resolve()"
190 );
191 }
192
193 #[test]
196 fn resolve_with_witness_has_non_empty_steps_on_found() {
197 let graph = make_graph_with_function("stepped_fn");
198 let snapshot = graph.snapshot();
199
200 let resolution = BindingQuery::new("stepped_fn")
201 .file_scope(FileScope::Any)
202 .mode(ResolutionMode::AllowSuffixCandidates)
203 .resolve_with_witness(&snapshot);
204
205 assert!(
206 !resolution.witness.steps.is_empty(),
207 "witness step trace must be non-empty for a resolved symbol"
208 );
209 }
210
211 #[test]
214 fn resolve_with_witness_not_found_consistent_with_resolve() {
215 let graph = make_graph_with_function("any_fn");
216 let snapshot = graph.snapshot();
217
218 let result_only = BindingQuery::new("does_not_exist")
219 .file_scope(FileScope::Any)
220 .mode(ResolutionMode::AllowSuffixCandidates)
221 .resolve(&snapshot);
222
223 let with_witness = BindingQuery::new("does_not_exist")
224 .file_scope(FileScope::Any)
225 .mode(ResolutionMode::AllowSuffixCandidates)
226 .resolve_with_witness(&snapshot);
227
228 assert_eq!(
229 result_only, with_witness.result,
230 "resolve_with_witness().result must match resolve() for missing symbols"
231 );
232 assert!(
233 !with_witness.witness.steps.is_empty(),
234 "witness step trace must be non-empty even for unresolved symbols"
235 );
236 }
237}