sqry_core/graph/unified/bind/
mod.rs1pub mod alias;
12pub mod plane;
13pub mod query;
14pub mod shadow;
15
16pub use alias::{AliasEntry, AliasEntryId, AliasTable};
17pub use plane::{BindingPlane, BindingResolution};
18pub use shadow::{ShadowEntry, ShadowEntryId, ShadowTable};
19pub use witness::WitnessRendering;
20pub mod scope;
21pub mod witness;
22
23pub use query::BindingQuery;
24pub use witness::{
25 RejectionReason, ResolutionStep, TieBreakReason, UnresolvedReason, VisibilityReason,
26};
27
28use serde::{Deserialize, Serialize};
29
30use super::concurrent::GraphSnapshot;
31use super::edge::kind::{EdgeKind, ExportKind};
32use super::node::id::NodeId;
33use super::node::kind::NodeKind;
34use super::resolution::{NormalizedSymbolQuery, SymbolCandidateBucket, SymbolResolutionOutcome};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub enum SymbolClassification {
39 Declaration,
42 Reference,
44 Import,
47 Ambiguous,
50 Unknown,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct ResolvedBinding {
57 pub node_id: NodeId,
59 pub classification: SymbolClassification,
61 pub bucket: SymbolCandidateBucket,
63 pub kind: NodeKind,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69pub struct BindingResult {
70 pub query: Option<NormalizedSymbolQuery>,
72 pub bindings: Vec<ResolvedBinding>,
74 pub outcome: SymbolResolutionOutcome,
76}
77
78pub(crate) fn classify_node(
88 snapshot: &GraphSnapshot,
89 node_id: NodeId,
90 kind: NodeKind,
91) -> SymbolClassification {
92 if kind == NodeKind::Import {
94 return SymbolClassification::Import;
95 }
96
97 if kind == NodeKind::Export {
98 let incoming = snapshot.edges().edges_to(node_id);
100 let has_reexport = incoming.iter().any(|e| {
101 matches!(
102 &e.kind,
103 EdgeKind::Exports {
104 kind: ExportKind::Reexport | ExportKind::Namespace,
105 ..
106 }
107 )
108 });
109 let outgoing = snapshot.edges().edges_from(node_id);
111 let has_reexport_outgoing = outgoing.iter().any(|e| {
112 matches!(
113 &e.kind,
114 EdgeKind::Exports {
115 kind: ExportKind::Reexport | ExportKind::Namespace,
116 ..
117 }
118 )
119 });
120
121 return if has_reexport || has_reexport_outgoing {
122 SymbolClassification::Ambiguous
123 } else {
124 SymbolClassification::Import
125 };
126 }
127
128 if kind == NodeKind::CallSite {
129 return SymbolClassification::Reference;
130 }
131
132 let incoming = snapshot.edges().edges_to(node_id);
134 let has_structural = incoming
135 .iter()
136 .any(|e| matches!(&e.kind, EdgeKind::Defines | EdgeKind::Contains));
137
138 if has_structural {
139 return SymbolClassification::Declaration;
140 }
141
142 if !incoming.is_empty() {
144 return SymbolClassification::Reference;
145 }
146
147 let outgoing = snapshot.edges().edges_from(node_id);
150 let has_outgoing_structural = outgoing
151 .iter()
152 .any(|e| matches!(&e.kind, EdgeKind::Defines | EdgeKind::Contains));
153
154 if has_outgoing_structural {
155 return SymbolClassification::Declaration;
156 }
157
158 SymbolClassification::Unknown
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::graph::node::Language;
165 use crate::graph::unified::concurrent::CodeGraph;
166 use crate::graph::unified::file::FileId;
167 use crate::graph::unified::storage::arena::NodeEntry;
168
169 struct TestGraph {
170 graph: CodeGraph,
171 file_id: Option<FileId>,
172 }
173
174 impl TestGraph {
175 fn new() -> Self {
176 Self {
177 graph: CodeGraph::new(),
178 file_id: None,
179 }
180 }
181
182 fn ensure_file_id(&mut self) -> FileId {
183 if let Some(fid) = self.file_id {
184 return fid;
185 }
186 let file_path = std::path::PathBuf::from("/bind-tests/test.rs");
187 let fid = self
188 .graph
189 .files_mut()
190 .register_with_language(&file_path, Some(Language::Rust))
191 .unwrap();
192 self.file_id = Some(fid);
193 fid
194 }
195
196 fn add_node(&mut self, name: &str, kind: NodeKind) -> NodeId {
197 let file_id = self.ensure_file_id();
198 let name_id = self.graph.strings_mut().intern(name).unwrap();
199 let qn_id = self
200 .graph
201 .strings_mut()
202 .intern(&format!("test::{name}"))
203 .unwrap();
204
205 let entry = NodeEntry::new(kind, name_id, file_id)
206 .with_qualified_name(qn_id)
207 .with_location(1, 0, 10, 0);
208
209 let node_id = self.graph.nodes_mut().alloc(entry).unwrap();
210 self.graph
211 .indices_mut()
212 .add(node_id, kind, name_id, Some(qn_id), file_id);
213 node_id
214 }
215
216 fn add_edge(&mut self, source: NodeId, target: NodeId, kind: EdgeKind) {
217 let file_id = self.ensure_file_id();
218 self.graph
219 .edges_mut()
220 .add_edge(source, target, kind, file_id);
221 }
222
223 fn snapshot(&self) -> GraphSnapshot {
224 self.graph.snapshot()
225 }
226 }
227
228 #[test]
229 fn declaration_classification() {
230 let mut tg = TestGraph::new();
231 let module_node = tg.add_node("my_module", NodeKind::Module);
232 let func_node = tg.add_node("my_func", NodeKind::Function);
233 tg.add_edge(module_node, func_node, EdgeKind::Defines);
234
235 let snapshot = tg.snapshot();
236 let result = BindingQuery::new("my_func").resolve(&snapshot);
237
238 assert!(!result.bindings.is_empty(), "expected at least one binding");
239 let binding = result
240 .bindings
241 .iter()
242 .find(|b| b.node_id == func_node)
243 .expect("expected binding for func_node");
244 assert_eq!(binding.classification, SymbolClassification::Declaration);
245 assert_eq!(binding.kind, NodeKind::Function);
246 }
247
248 #[test]
249 fn reference_classification_callsite() {
250 let mut tg = TestGraph::new();
251 let _call_node = tg.add_node("some_call", NodeKind::CallSite);
252
253 let snapshot = tg.snapshot();
254 let result = BindingQuery::new("some_call").resolve(&snapshot);
255
256 assert!(!result.bindings.is_empty());
257 assert_eq!(
258 result.bindings[0].classification,
259 SymbolClassification::Reference
260 );
261 }
262
263 #[test]
264 fn import_classification() {
265 let mut tg = TestGraph::new();
266 let _import_node = tg.add_node("imported_sym", NodeKind::Import);
267
268 let snapshot = tg.snapshot();
269 let result = BindingQuery::new("imported_sym").resolve(&snapshot);
270
271 assert!(!result.bindings.is_empty());
272 assert_eq!(
273 result.bindings[0].classification,
274 SymbolClassification::Import
275 );
276 }
277
278 #[test]
279 fn export_direct_classification() {
280 let mut tg = TestGraph::new();
281 let _export_node = tg.add_node("exported_sym", NodeKind::Export);
282
283 let snapshot = tg.snapshot();
284 let result = BindingQuery::new("exported_sym").resolve(&snapshot);
285
286 assert!(!result.bindings.is_empty());
287 assert_eq!(
289 result.bindings[0].classification,
290 SymbolClassification::Import
291 );
292 }
293
294 #[test]
295 fn export_reexport_ambiguous() {
296 let mut tg = TestGraph::new();
297 let source = tg.add_node("source_mod", NodeKind::Module);
298 let export_node = tg.add_node("reexported", NodeKind::Export);
299 tg.add_edge(
300 source,
301 export_node,
302 EdgeKind::Exports {
303 kind: ExportKind::Reexport,
304 alias: None,
305 },
306 );
307
308 let snapshot = tg.snapshot();
309 let result = BindingQuery::new("reexported").resolve(&snapshot);
310
311 assert!(!result.bindings.is_empty());
312 let binding = result
313 .bindings
314 .iter()
315 .find(|b| b.node_id == export_node)
316 .expect("expected binding for export_node");
317 assert_eq!(binding.classification, SymbolClassification::Ambiguous);
318 }
319
320 #[test]
321 fn not_found_result() {
322 let tg = TestGraph::new();
323 let snapshot = tg.snapshot();
324 let result = BindingQuery::new("nonexistent_symbol_xyz").resolve(&snapshot);
325
326 assert!(result.bindings.is_empty());
327 assert_eq!(result.outcome, SymbolResolutionOutcome::NotFound);
328 }
329
330 #[test]
331 fn declaration_via_contains_edge() {
332 let mut tg = TestGraph::new();
333 let class_node = tg.add_node("MyClass", NodeKind::Class);
334 let method_node = tg.add_node("my_method", NodeKind::Method);
335 tg.add_edge(class_node, method_node, EdgeKind::Contains);
336
337 let snapshot = tg.snapshot();
338 let result = BindingQuery::new("my_method").resolve(&snapshot);
339
340 assert!(!result.bindings.is_empty());
341 let binding = result
342 .bindings
343 .iter()
344 .find(|b| b.node_id == method_node)
345 .expect("expected binding for method_node");
346 assert_eq!(binding.classification, SymbolClassification::Declaration);
347 }
348
349 #[test]
350 fn root_declaration_with_outgoing_defines() {
351 let mut tg = TestGraph::new();
352 let module_node = tg.add_node("root_mod", NodeKind::Module);
353 let child = tg.add_node("child_func", NodeKind::Function);
354 tg.add_edge(module_node, child, EdgeKind::Defines);
355
356 let snapshot = tg.snapshot();
357 let result = BindingQuery::new("root_mod").resolve(&snapshot);
358
359 assert!(!result.bindings.is_empty());
360 let binding = result
361 .bindings
362 .iter()
363 .find(|b| b.node_id == module_node)
364 .expect("expected binding for module_node");
365 assert_eq!(binding.classification, SymbolClassification::Declaration);
367 }
368}