Skip to main content

ryo_analysis/query/
builder_graph_v2.rs

1//! GraphBuilderV2 - Build CodeGraphV2 from parsed code.
2//!
3//! Key differences from V1:
4//! - Uses CodeGraphV2 (petgraph-free)
5//! - Uses MatchExprDataV2 (String-free: FileId + SymbolId)
6//! - Incrementally builds kind index during add_symbol
7
8use super::graph_v2::{CodeEdgeV2, CodeGraphV2, MatchExprDataV2};
9use crate::symbol::{FileId, SymbolId, SymbolPath, SymbolRegistry};
10use crate::SymbolKind;
11
12/// Builder for constructing CodeGraphV2.
13///
14/// Provides a convenient API for adding symbols and relationships to the graph.
15///
16/// # Example
17///
18/// ```rust,ignore
19/// let mut registry = SymbolRegistry::new();
20/// let mut builder = GraphBuilderV2::new(&mut registry);
21///
22/// let foo = builder.add_symbol(SymbolPath::parse("mylib::foo")?, SymbolKind::Function)?;
23/// let bar = builder.add_symbol(SymbolPath::parse("mylib::bar")?, SymbolKind::Function)?;
24/// builder.add_call(foo, bar);
25///
26/// let graph = builder.build();
27/// ```
28pub struct GraphBuilderV2<'a> {
29    graph: CodeGraphV2,
30    registry: &'a mut SymbolRegistry,
31}
32
33impl<'a> GraphBuilderV2<'a> {
34    /// Create a new builder.
35    pub fn new(registry: &'a mut SymbolRegistry) -> Self {
36        Self {
37            graph: CodeGraphV2::new(),
38            registry,
39        }
40    }
41
42    /// Create a new builder with pre-allocated capacity.
43    pub fn with_capacity(registry: &'a mut SymbolRegistry, nodes: usize, edges: usize) -> Self {
44        Self {
45            graph: CodeGraphV2::with_capacity(nodes, edges),
46            registry,
47        }
48    }
49
50    // ========================================================================
51    // Symbol Management
52    // ========================================================================
53
54    /// Add a symbol to both registry and graph.
55    ///
56    /// Returns the SymbolId for the added symbol.
57    /// Also adds the symbol to the kind index for efficient kind-based queries.
58    pub fn add_symbol(
59        &mut self,
60        path: SymbolPath,
61        kind: SymbolKind,
62    ) -> Result<SymbolId, crate::symbol::RegistrationError> {
63        let id = self.registry.register(path, kind)?;
64        self.graph.add_node(id);
65        self.graph.add_to_kind_index(id, kind);
66        Ok(id)
67    }
68
69    /// Add an existing symbol to the graph (already registered in registry).
70    ///
71    /// Use this when the symbol is already registered but not yet in the graph.
72    pub fn add_existing_symbol(&mut self, id: SymbolId, kind: SymbolKind) {
73        self.graph.add_node(id);
74        self.graph.add_to_kind_index(id, kind);
75    }
76
77    /// Add a crate root.
78    pub fn add_crate_root(
79        &mut self,
80        path: SymbolPath,
81    ) -> Result<SymbolId, crate::symbol::RegistrationError> {
82        let id = self.registry.register(path, SymbolKind::Mod)?;
83        self.graph.add_crate_root(id);
84        self.graph.add_to_kind_index(id, SymbolKind::Mod);
85        Ok(id)
86    }
87
88    /// Add an existing symbol as a crate root.
89    pub fn add_existing_crate_root(&mut self, id: SymbolId) {
90        self.graph.add_crate_root(id);
91        self.graph.add_to_kind_index(id, SymbolKind::Mod);
92    }
93
94    // ========================================================================
95    // Edge Management
96    // ========================================================================
97
98    /// Add a Contains relationship (parent → child).
99    pub fn add_contains(&mut self, parent: SymbolId, child: SymbolId) {
100        self.graph.add_edge(parent, child, CodeEdgeV2::Contains);
101    }
102
103    /// Add a Calls relationship (caller → callee).
104    pub fn add_call(&mut self, caller: SymbolId, callee: SymbolId) {
105        self.graph.add_edge(caller, callee, CodeEdgeV2::Calls);
106    }
107
108    /// Add an Implements relationship (implementor → trait/type).
109    pub fn add_implements(&mut self, implementor: SymbolId, trait_or_type: SymbolId) {
110        self.graph
111            .add_edge(implementor, trait_or_type, CodeEdgeV2::Implements);
112    }
113
114    // ========================================================================
115    // Match Expression Management
116    // ========================================================================
117
118    /// Add a match expression to a function (String-free version).
119    ///
120    /// Uses FileId and SymbolId instead of PathBuf and String.
121    ///
122    /// # Arguments
123    ///
124    /// * `function_id` - The function containing the match expression
125    /// * `file_id` - The file containing the match expression
126    /// * `enum_id` - The enum being matched
127    /// * `offset` - Byte offset in the file
128    /// * `line` - Line number (1-indexed)
129    pub fn add_match_expr(
130        &mut self,
131        function_id: SymbolId,
132        file_id: FileId,
133        enum_id: SymbolId,
134        offset: u32,
135        line: u32,
136    ) {
137        self.graph.add_match_expr(
138            function_id,
139            MatchExprDataV2 {
140                file_id,
141                enum_id,
142                offset,
143                line,
144            },
145        );
146    }
147
148    // ========================================================================
149    // Build
150    // ========================================================================
151
152    /// Build the graph, consuming the builder.
153    ///
154    /// Note: Unlike V1, indices are built incrementally during add_symbol,
155    /// so no post-build index rebuild is needed.
156    pub fn build(self) -> CodeGraphV2 {
157        self.graph
158    }
159
160    /// Get a reference to the registry.
161    pub fn registry(&self) -> &SymbolRegistry {
162        self.registry
163    }
164
165    /// Get a mutable reference to the registry.
166    pub fn registry_mut(&mut self) -> &mut SymbolRegistry {
167        self.registry
168    }
169
170    /// Get a reference to the graph being built.
171    pub fn graph(&self) -> &CodeGraphV2 {
172        &self.graph
173    }
174}
175
176// ============================================================================
177// Tests
178// ============================================================================
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_builder_basic() {
186        let mut registry = SymbolRegistry::new();
187        let mut builder = GraphBuilderV2::new(&mut registry);
188
189        let foo = builder
190            .add_symbol(
191                SymbolPath::parse("mylib::foo").unwrap(),
192                SymbolKind::Function,
193            )
194            .unwrap();
195        let bar = builder
196            .add_symbol(
197                SymbolPath::parse("mylib::bar").unwrap(),
198                SymbolKind::Function,
199            )
200            .unwrap();
201
202        builder.add_call(foo, bar);
203
204        let graph = builder.build();
205        assert_eq!(graph.node_count(), 2);
206        assert_eq!(graph.edge_count(), 1);
207    }
208
209    #[test]
210    fn test_builder_module_structure() {
211        let mut registry = SymbolRegistry::new();
212        let mut builder = GraphBuilderV2::new(&mut registry);
213
214        let crate_root = builder
215            .add_crate_root(SymbolPath::parse("mylib").unwrap())
216            .unwrap();
217        let module = builder
218            .add_symbol(
219                SymbolPath::parse("mylib::handlers").unwrap(),
220                SymbolKind::Mod,
221            )
222            .unwrap();
223        let func = builder
224            .add_symbol(
225                SymbolPath::parse("mylib::handlers::handle").unwrap(),
226                SymbolKind::Function,
227            )
228            .unwrap();
229
230        builder.add_contains(crate_root, module);
231        builder.add_contains(module, func);
232
233        let graph = builder.build();
234        let children: Vec<_> = graph.children_of(crate_root).collect();
235        assert_eq!(children.len(), 1);
236
237        let module_children: Vec<_> = graph.children_of(module).collect();
238        assert_eq!(module_children.len(), 1);
239
240        assert_eq!(graph.parent_of(func), Some(module));
241    }
242
243    #[test]
244    fn test_builder_kind_index() {
245        let mut registry = SymbolRegistry::new();
246        let mut builder = GraphBuilderV2::new(&mut registry);
247
248        builder
249            .add_symbol(SymbolPath::parse("mylib::Foo").unwrap(), SymbolKind::Struct)
250            .unwrap();
251        builder
252            .add_symbol(
253                SymbolPath::parse("mylib::bar").unwrap(),
254                SymbolKind::Function,
255            )
256            .unwrap();
257        builder
258            .add_symbol(
259                SymbolPath::parse("mylib::baz").unwrap(),
260                SymbolKind::Function,
261            )
262            .unwrap();
263
264        let graph = builder.build();
265
266        let structs: Vec<_> = graph.iter_by_kind(SymbolKind::Struct).collect();
267        assert_eq!(structs.len(), 1);
268
269        let functions: Vec<_> = graph.iter_by_kind(SymbolKind::Function).collect();
270        assert_eq!(functions.len(), 2);
271    }
272
273    #[test]
274    fn test_builder_with_capacity() {
275        let mut registry = SymbolRegistry::new();
276        let builder = GraphBuilderV2::with_capacity(&mut registry, 100, 200);
277        let graph = builder.build();
278        assert!(graph.is_empty());
279    }
280
281    #[test]
282    fn test_builder_callers_callees() {
283        let mut registry = SymbolRegistry::new();
284        let mut builder = GraphBuilderV2::new(&mut registry);
285
286        let main = builder
287            .add_symbol(
288                SymbolPath::parse("mylib::main").unwrap(),
289                SymbolKind::Function,
290            )
291            .unwrap();
292        let helper1 = builder
293            .add_symbol(
294                SymbolPath::parse("mylib::helper1").unwrap(),
295                SymbolKind::Function,
296            )
297            .unwrap();
298        let helper2 = builder
299            .add_symbol(
300                SymbolPath::parse("mylib::helper2").unwrap(),
301                SymbolKind::Function,
302            )
303            .unwrap();
304        let util = builder
305            .add_symbol(
306                SymbolPath::parse("mylib::util").unwrap(),
307                SymbolKind::Function,
308            )
309            .unwrap();
310
311        // main calls helper1 and helper2
312        builder.add_call(main, helper1);
313        builder.add_call(main, helper2);
314        // helper1 and helper2 both call util
315        builder.add_call(helper1, util);
316        builder.add_call(helper2, util);
317
318        let graph = builder.build();
319
320        // Check callees of main
321        let main_callees: Vec<_> = graph.callees_of(main).collect();
322        assert_eq!(main_callees.len(), 2);
323
324        // Check callers of util
325        let util_callers: Vec<_> = graph.callers_of(util).collect();
326        assert_eq!(util_callers.len(), 2);
327    }
328
329    #[test]
330    fn test_builder_implements() {
331        let mut registry = SymbolRegistry::new();
332        let mut builder = GraphBuilderV2::new(&mut registry);
333
334        let trait_id = builder
335            .add_symbol(
336                SymbolPath::parse("mylib::MyTrait").unwrap(),
337                SymbolKind::Trait,
338            )
339            .unwrap();
340        let impl1 = builder
341            .add_symbol(SymbolPath::parse("mylib::Foo").unwrap(), SymbolKind::Struct)
342            .unwrap();
343        let impl2 = builder
344            .add_symbol(SymbolPath::parse("mylib::Bar").unwrap(), SymbolKind::Struct)
345            .unwrap();
346
347        builder.add_implements(impl1, trait_id);
348        builder.add_implements(impl2, trait_id);
349
350        let graph = builder.build();
351
352        let implementors: Vec<_> = graph.implementors_of(trait_id).collect();
353        assert_eq!(implementors.len(), 2);
354    }
355}