llmcc_core/
graph.rs

1use rayon::prelude::*;
2
3use crate::block::{BasicBlock, BlockId, BlockRelation};
4use crate::context::{CompileCtxt, CompileUnit};
5
6#[derive(Debug, Clone)]
7pub struct UnitGraph {
8    /// Compile unit this graph belongs to
9    unit_index: usize,
10    /// Root block ID of this unit
11    root: BlockId,
12}
13
14impl UnitGraph {
15    pub fn new(unit_index: usize, root: BlockId) -> Self {
16        Self { unit_index, root }
17    }
18
19    pub fn unit_index(&self) -> usize {
20        self.unit_index
21    }
22
23    pub fn root(&self) -> BlockId {
24        self.root
25    }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct UnitNode {
30    pub unit_index: usize,
31    pub block_id: BlockId,
32}
33
34/// ProjectGraph represents a complete compilation project with all units
35/// and their inter-dependencies.
36#[derive(Debug)]
37pub struct ProjectGraph<'tcx> {
38    /// Reference to the compilation context containing all symbols
39    pub cc: &'tcx CompileCtxt<'tcx>,
40    /// Per-unit graphs containing blocks and intra-unit relations
41    units: Vec<UnitGraph>,
42}
43
44impl<'tcx> ProjectGraph<'tcx> {
45    pub fn new(cc: &'tcx CompileCtxt<'tcx>) -> Self {
46        Self {
47            cc,
48            units: Vec::new(),
49        }
50    }
51
52    pub fn add_child(&mut self, graph: UnitGraph) {
53        self.units.push(graph);
54        self.units.sort_by_key(|g| g.unit_index());
55    }
56
57    /// Add multiple unit graphs to the project graph.
58    pub fn add_children(&mut self, graphs: Vec<UnitGraph>) {
59        self.units.extend(graphs);
60        self.units.sort_by_key(|g| g.unit_index());
61    }
62
63    /// Get the units in this project graph.
64    pub fn units(&self) -> &[UnitGraph] {
65        &self.units
66    }
67
68    /// Get a specific unit graph by index, if it exists.
69    pub fn unit_graph(&self, index: usize) -> Option<&UnitGraph> {
70        self.units.iter().find(|u| u.unit_index() == index)
71    }
72
73    /// Get top-k limit (currently always None - no PageRank filtering).
74    pub fn top_k(&self) -> Option<usize> {
75        None
76    }
77
78    /// Check if PageRank ranking is enabled (currently always false).
79    pub fn pagerank_enabled(&self) -> bool {
80        false
81    }
82
83    /// Connect all blocks by discovering and recording their relationships.
84    pub fn connect_blocks(&self) {
85        // Process each unit in parallel - they are independent
86        self.units.par_iter().for_each(|unit_graph| {
87            let unit = CompileUnit {
88                cc: self.cc,
89                index: unit_graph.unit_index(),
90            };
91            let root_block = unit.bb(unit_graph.root());
92            self.dfs_connect(&unit, &root_block, None);
93        });
94    }
95
96    /// Recursively connect blocks in pre-order DFS traversal.
97    fn dfs_connect(
98        &self,
99        unit: &CompileUnit<'tcx>,
100        block: &BasicBlock<'tcx>,
101        parent: Option<BlockId>,
102    ) {
103        let block_id = block.id();
104
105        // 1. Link structural parent/child relationship
106        if let Some(parent_id) = parent {
107            self.add_relation(parent_id, BlockRelation::Contains, block_id);
108            self.add_relation(block_id, BlockRelation::ContainedBy, parent_id);
109        }
110
111        // 2. Link kind-specific relationships
112        match block {
113            BasicBlock::Func(func) => self.link_func(unit, block_id, func),
114            BasicBlock::Class(class) => self.link_class(unit, block_id, class),
115            BasicBlock::Impl(impl_block) => self.link_impl(unit, block_id, impl_block),
116            BasicBlock::Trait(trait_block) => self.link_trait(unit, block_id, trait_block),
117            BasicBlock::Interface(iface_block) => self.link_interface(unit, block_id, iface_block),
118            BasicBlock::Enum(enum_block) => self.link_enum(unit, block_id, enum_block),
119            BasicBlock::Call(call) => self.link_call(unit, block_id, call),
120            BasicBlock::Field(field) => self.link_field(unit, block_id, field),
121            BasicBlock::Return(ret) => self.link_return(unit, block_id, ret),
122            BasicBlock::Parameter(param) => self.link_parameter(unit, block_id, param),
123            BasicBlock::Const(const_block) => self.link_const(unit, block_id, const_block),
124            BasicBlock::Alias(alias) => self.link_alias(unit, block_id, alias),
125            // Root - no special linking needed
126            _ => {}
127        }
128
129        // 3. Recurse into children (pre-order: process this node before children)
130        for child_id in block.children() {
131            let child = unit.bb(child_id);
132            self.dfs_connect(unit, &child, Some(block_id));
133        }
134    }
135
136    /// Add a relationship to the related_map.
137    #[inline]
138    fn add_relation(&self, from: BlockId, relation: BlockRelation, to: BlockId) {
139        self.cc.related_map.add_relation_impl(from, relation, to);
140    }
141
142    /// Link function/method relationships.
143    fn link_func(
144        &self,
145        unit: &CompileUnit<'tcx>,
146        block_id: BlockId,
147        func: &crate::block::BlockFunc<'tcx>,
148    ) {
149        // Parameters - now individual BlockParameter blocks
150        for param_id in func.get_parameters() {
151            self.add_relation(block_id, BlockRelation::HasParameters, param_id);
152        }
153
154        // Return type
155        if let Some(ret_id) = func.get_returns() {
156            self.add_relation(block_id, BlockRelation::HasReturn, ret_id);
157        }
158
159        // Type parameter bounds: for `function<T extends HasLength>`, create edge HasLength -> func
160        // HasLength (bound) is used by func (this function)
161        if let Some(func_sym) = func.base.symbol
162            && let Some(scope_id) = func_sym.opt_scope()
163        {
164            let scope = unit.get_scope(scope_id);
165            // Look for type parameters in the function's scope
166            scope.for_each_symbol(|sym| {
167                if sym.kind() == crate::symbol::SymKind::TypeParameter {
168                    // Get the bound type from type_of
169                    if let Some(bound_id) = sym.type_of()
170                        && let Some(bound_sym) = unit.opt_get_symbol(bound_id)
171                        && let Some(bound_block_id) = bound_sym.block_id()
172                    {
173                        // Create edge: bound --UsedBy--> this_func
174                        // This means: bound is used by this_func (as a type parameter constraint)
175                        self.add_relation(bound_block_id, BlockRelation::UsedBy, block_id);
176                        self.add_relation(block_id, BlockRelation::Uses, bound_block_id);
177                    }
178                }
179            });
180        }
181
182        // Populate type_deps from function symbol's nested_types (generic return type args)
183        // e.g., for `fn get_user() -> Result<User, Error>`, nested_types contains User and Error
184        if let Some(func_sym) = func.base.symbol
185            && let Some(nested_types) = func_sym.nested_types()
186        {
187            for type_id in nested_types {
188                // Follow type_of chain to get actual type symbol
189                let type_sym = unit.opt_get_symbol(type_id).and_then(|sym| {
190                    sym.type_of()
191                        .and_then(|id| unit.opt_get_symbol(id))
192                        .or(Some(sym))
193                });
194                if let Some(type_sym) = type_sym
195                    && let Some(type_block_id) = type_sym.block_id()
196                {
197                    func.add_type_dep(type_block_id);
198                }
199            }
200        }
201
202        // Decorators (TypeScript/JavaScript `@decorator` on functions/methods)
203        if let Some(func_sym) = func.base.symbol
204            && let Some(decorators) = func_sym.decorators()
205        {
206            for decorator_id in decorators {
207                if let Some(decorator_sym) = unit.opt_get_symbol(decorator_id)
208                    && let Some(decorator_block_id) = decorator_sym.block_id()
209                {
210                    func.add_type_dep(decorator_block_id);
211                }
212            }
213        }
214
215        // Find calls within this function's children and link to callees
216        // Also populate type_deps and func_deps
217        for child_id in func.base.get_children() {
218            self.find_calls_recursive(unit, block_id, func, child_id);
219        }
220
221        // Add Uses/UsedBy edges for type dependencies
222        for type_id in func.get_type_deps() {
223            self.add_relation(block_id, BlockRelation::Uses, type_id);
224            self.add_relation(type_id, BlockRelation::UsedBy, block_id);
225        }
226
227        // Add Calls/CalledBy edges for type dependencies
228        for type_id in func.get_func_deps() {
229            self.add_relation(block_id, BlockRelation::Calls, type_id);
230            self.add_relation(type_id, BlockRelation::CalledBy, block_id);
231        }
232    }
233
234    /// Recursively find call blocks and link them to this function as caller.
235    /// Also populates func_deps (free functions) and type_deps (static method receivers).
236    fn find_calls_recursive(
237        &self,
238        unit: &CompileUnit<'tcx>,
239        caller_func_id: BlockId,
240        caller_func: &crate::block::BlockFunc<'tcx>,
241        block_id: BlockId,
242    ) {
243        let block = unit.bb(block_id);
244
245        // Check for Call blocks (explicit call blocks in Rust)
246        if let BasicBlock::Call(call) = &block {
247            // Get the callee symbol to check its kind
248            if let Some(callee_sym) = call.base.node.ident_symbol(unit) {
249                self.process_callee_symbol(unit, caller_func_id, caller_func, callee_sym);
250            }
251        }
252
253        // For nodes that are call/new expressions but not Call blocks (TypeScript),
254        // check if the node's symbol is a callable or constructable type.
255        // This handles cases where we visit call_expression/new_expression nodes
256        // that weren't converted to Call blocks.
257        if let Some(base) = block.base() {
258            let node = &base.node;
259            // Check if this node has a resolved symbol that's a function or struct
260            if let Some(callee_sym) = node.ident_symbol(unit) {
261                let kind = callee_sym.kind();
262                // Only process if it's not already a Call block and is callable
263                if !matches!(&block, BasicBlock::Call(_))
264                    && matches!(
265                        kind,
266                        crate::symbol::SymKind::Function
267                            | crate::symbol::SymKind::Struct
268                            | crate::symbol::SymKind::Enum
269                    )
270                {
271                    self.process_callee_symbol(unit, caller_func_id, caller_func, callee_sym);
272                }
273            }
274        }
275
276        // Recurse into children
277        for child_id in block.children() {
278            self.find_calls_recursive(unit, caller_func_id, caller_func, child_id);
279        }
280    }
281
282    /// Process a callee symbol to add func_deps/type_deps and establish relations
283    fn process_callee_symbol(
284        &self,
285        _unit: &CompileUnit<'tcx>,
286        caller_func_id: BlockId,
287        caller_func: &crate::block::BlockFunc<'tcx>,
288        callee_sym: &crate::symbol::Symbol,
289    ) {
290        let callee_kind = callee_sym.kind();
291        let callee_block_id_opt = callee_sym.block_id();
292
293        match callee_kind {
294            crate::symbol::SymKind::Function => {
295                // Free function call → add to func_deps
296                if let Some(callee_block_id) = callee_block_id_opt {
297                    caller_func.add_func_dep(callee_block_id);
298                    // Also establish caller-callee relation
299                    self.add_relation(caller_func_id, BlockRelation::Calls, callee_block_id);
300                    self.add_relation(callee_block_id, BlockRelation::CalledBy, caller_func_id);
301                }
302            }
303            crate::symbol::SymKind::Method => {
304                // Method call → check if it has a type receiver (Foo::method)
305                // The type is tracked via type_of on the callee symbol
306                if let Some(type_sym_id) = callee_sym.type_of()
307                    && let Some(type_sym) = self.cc.opt_get_symbol(type_sym_id)
308                    && let Some(type_block_id) = type_sym.block_id()
309                {
310                    caller_func.add_type_dep(type_block_id);
311                }
312                // Establish caller-callee relation for methods too
313                if let Some(callee_block_id) = callee_sym.block_id() {
314                    self.add_relation(caller_func_id, BlockRelation::Calls, callee_block_id);
315                    self.add_relation(callee_block_id, BlockRelation::CalledBy, caller_func_id);
316                }
317            }
318            _ => {
319                // Other kinds (e.g., Struct for associated functions like Foo::new or new Class())
320                // Add type to type_deps and create Uses/UsedBy relations
321                if let Some(callee_block_id) = callee_sym.block_id()
322                    && (callee_kind == crate::symbol::SymKind::Struct
323                        || callee_kind == crate::symbol::SymKind::Enum)
324                {
325                    caller_func.add_type_dep(callee_block_id);
326                    // Add Uses relation for type_dep edges in arch-graph
327                    self.add_relation(caller_func_id, BlockRelation::Uses, callee_block_id);
328                    self.add_relation(callee_block_id, BlockRelation::UsedBy, caller_func_id);
329                }
330            }
331        }
332    }
333
334    /// Link struct/class relationships.
335    fn link_class(
336        &self,
337        unit: &CompileUnit<'tcx>,
338        block_id: BlockId,
339        class: &crate::block::BlockClass<'tcx>,
340    ) {
341        // Fields
342        for field_id in class.get_fields() {
343            self.add_relation(block_id, BlockRelation::HasField, field_id);
344            self.add_relation(field_id, BlockRelation::FieldOf, block_id);
345        }
346
347        // Methods
348        for method_id in class.get_methods() {
349            self.add_relation(block_id, BlockRelation::HasMethod, method_id);
350            self.add_relation(method_id, BlockRelation::MethodOf, block_id);
351        }
352
353        if let Some(class_sym) = class.base.symbol {
354            // Extended class (from extends_clause) - stored in type_of
355            if let Some(extends_id) = class_sym.type_of()
356                && let Some(extends_sym) = unit.opt_get_symbol(extends_id)
357                && let Some(extends_block_id) = extends_sym.block_id()
358            {
359                // Set extends relation (don't add to type_dep since @extends already shows the edge)
360                self.add_relation(block_id, BlockRelation::Extends, extends_block_id);
361                self.add_relation(extends_block_id, BlockRelation::ExtendedBy, block_id);
362
363                // Populate the extends field on the BlockClass for display
364                let extends_name = unit.resolve_name(extends_sym.name);
365                class.set_extends(extends_name, Some(extends_block_id));
366            }
367
368            // Implemented interfaces (from implements_clause) - stored in nested_types
369            // For TypeScript: nested_types = implemented interfaces
370            // For Rust: nested_types = field types (traits from dyn Trait)
371            if let Some(nested) = class_sym.nested_types() {
372                for type_id in nested {
373                    if let Some(type_sym) = unit.opt_get_symbol(type_id)
374                        && let Some(type_block_id) = type_sym.block_id()
375                    {
376                        // Add as type_dep
377                        class.base.add_type_dep(type_block_id);
378
379                        // Only create Implements relation for Trait/Interface, not regular types
380                        // In Rust, nested_types may include field types which are not implementations
381                        let is_trait = type_sym.kind() == crate::symbol::SymKind::Trait;
382                        let is_interface = type_sym.kind() == crate::symbol::SymKind::Interface;
383
384                        if is_trait {
385                            // For Rust Traits (from dyn Trait): create Uses/UsedBy for bound edges
386                            // Don't create Implements relation here - Rust impl blocks handle that
387                            self.add_relation(block_id, BlockRelation::Uses, type_block_id);
388                            self.add_relation(type_block_id, BlockRelation::UsedBy, block_id);
389                        } else if is_interface {
390                            // TypeScript Interfaces: create Implements relation for interface -> implements edges
391                            self.add_relation(block_id, BlockRelation::Implements, type_block_id);
392                            self.add_relation(
393                                type_block_id,
394                                BlockRelation::ImplementedBy,
395                                block_id,
396                            );
397                        }
398                    }
399                }
400            }
401
402            // Decorators (from @decorator syntax in TypeScript/JavaScript)
403            if let Some(decorators) = class_sym.decorators() {
404                for decorator_id in decorators {
405                    if let Some(decorator_sym) = unit.opt_get_symbol(decorator_id)
406                        && let Some(decorator_block_id) = decorator_sym.block_id()
407                    {
408                        // Add as type_dep for decorators
409                        class.base.add_type_dep(decorator_block_id);
410                        self.add_relation(block_id, BlockRelation::Uses, decorator_block_id);
411                        self.add_relation(decorator_block_id, BlockRelation::UsedBy, block_id);
412                    }
413                }
414            }
415        }
416        // Note: Field type argument edges (e.g., User -> Triple for `data: Triple<User>`)
417        // are created during graph_render edge collection from field.symbol.nested_types
418    }
419
420    /// Link impl block relationships.
421    fn link_impl(
422        &self,
423        unit: &CompileUnit<'tcx>,
424        block_id: BlockId,
425        impl_block: &crate::block::BlockImpl<'tcx>,
426    ) {
427        // Methods
428        for method_id in impl_block.get_methods() {
429            self.add_relation(block_id, BlockRelation::HasMethod, method_id);
430            self.add_relation(method_id, BlockRelation::MethodOf, block_id);
431        }
432
433        // Target type - resolve from symbol if block_id wasn't available during building
434        let target_id = impl_block
435            .get_target()
436            .or_else(|| impl_block.target_sym.and_then(|sym| sym.block_id()));
437        if let Some(target_id) = target_id {
438            impl_block.set_target(target_id);
439            self.add_relation(block_id, BlockRelation::ImplFor, target_id);
440            self.add_relation(target_id, BlockRelation::HasImpl, block_id);
441
442            // Populate type_deps from the target symbol's nested_types
443            // These were set during binding from impl's trait type arguments (e.g., User from `impl Repository<User>`)
444            // Note: target_sym may be a local symbol with type_of pointing to actual struct, so we get
445            // nested_types from target_sym but add type_deps to the actual target block
446            if let Some(target_sym) = impl_block.target_sym
447                && let Some(nested_types) = target_sym.nested_types()
448            {
449                let target_block = unit.bb(target_id);
450                if let Some(base) = target_block.base() {
451                    for type_id in nested_types {
452                        // Follow type_of chain to get actual type symbol
453                        let type_sym = unit.opt_get_symbol(type_id).and_then(|sym| {
454                            sym.type_of()
455                                .and_then(|id| unit.opt_get_symbol(id))
456                                .or(Some(sym))
457                        });
458                        if let Some(type_sym) = type_sym
459                            && let Some(type_block_id) = type_sym.block_id()
460                        {
461                            base.type_deps.write().insert(type_block_id);
462                        }
463                    }
464                }
465            }
466        }
467
468        // Trait reference - resolve from symbol if block_id wasn't available during building
469        let trait_id = impl_block
470            .get_trait_ref()
471            .or_else(|| impl_block.trait_sym.and_then(|sym| sym.block_id()));
472        if let Some(trait_id) = trait_id {
473            impl_block.set_trait_ref(trait_id);
474            self.add_relation(block_id, BlockRelation::Implements, trait_id);
475            self.add_relation(trait_id, BlockRelation::ImplementedBy, block_id);
476        }
477    }
478
479    /// Link trait relationships (Rust traits).
480    fn link_trait(
481        &self,
482        unit: &CompileUnit<'tcx>,
483        block_id: BlockId,
484        trait_block: &crate::block::BlockTrait<'tcx>,
485    ) {
486        // Methods
487        for method_id in trait_block.get_methods() {
488            self.add_relation(block_id, BlockRelation::HasMethod, method_id);
489            self.add_relation(method_id, BlockRelation::MethodOf, block_id);
490        }
491
492        // Type parameter bounds: for `trait Foo<T: Bar>`, create edge Bar -> Foo
493        // Bar (bound) is used by Foo (this trait)
494        if let Some(trait_sym) = trait_block.base.symbol
495            && let Some(scope_id) = trait_sym.opt_scope()
496        {
497            let scope = unit.get_scope(scope_id);
498            // Look for type parameters in the trait's scope
499            scope.for_each_symbol(|sym| {
500                if sym.kind() == crate::symbol::SymKind::TypeParameter {
501                    // Get the bound trait from type_of
502                    if let Some(bound_id) = sym.type_of()
503                        && let Some(bound_sym) = unit.opt_get_symbol(bound_id)
504                        && let Some(bound_block_id) = bound_sym.block_id()
505                    {
506                        // Create edge: bound --UsedBy--> this_trait
507                        // This means: bound is used by this_trait (as a type parameter constraint)
508                        self.add_relation(bound_block_id, BlockRelation::UsedBy, block_id);
509                        self.add_relation(block_id, BlockRelation::Uses, bound_block_id);
510                    }
511                }
512            });
513        }
514    }
515
516    /// Link interface relationships (TypeScript interfaces).
517    fn link_interface(
518        &self,
519        unit: &CompileUnit<'tcx>,
520        block_id: BlockId,
521        iface_block: &crate::block::BlockInterface<'tcx>,
522    ) {
523        // Methods
524        for method_id in iface_block.get_methods() {
525            self.add_relation(block_id, BlockRelation::HasMethod, method_id);
526            self.add_relation(method_id, BlockRelation::MethodOf, block_id);
527        }
528
529        // Fields
530        for field_id in iface_block.get_fields() {
531            self.add_relation(block_id, BlockRelation::HasField, field_id);
532            self.add_relation(field_id, BlockRelation::FieldOf, block_id);
533        }
534
535        // Extended/inherited types: for `interface Foo extends Bar`, create edge Foo -> Bar
536        if let Some(iface_sym) = iface_block.base.symbol
537            && let Some(nested) = iface_sym.nested_types()
538        {
539            for base_type_id in nested {
540                if let Some(base_sym) = unit.opt_get_symbol(base_type_id)
541                    && let Some(base_block_id) = base_sym.block_id()
542                {
543                    // Create edge: this_interface --Extends--> base_interface
544                    self.add_relation(block_id, BlockRelation::Extends, base_block_id);
545                    self.add_relation(base_block_id, BlockRelation::ExtendedBy, block_id);
546
547                    // Also populate the extends field on the BlockInterface for display
548                    let base_name = unit.resolve_name(base_sym.name);
549                    iface_block.add_extends(base_name, Some(base_block_id));
550                }
551            }
552        }
553
554        // Type parameter bounds: for `interface EventHandler<T extends Event>`, create edge Event -> EventHandler
555        if let Some(iface_sym) = iface_block.base.symbol
556            && let Some(scope_id) = iface_sym.opt_scope()
557        {
558            let scope = unit.get_scope(scope_id);
559            // Look for type parameters in the interface's scope
560            scope.for_each_symbol(|sym| {
561                if sym.kind() == crate::symbol::SymKind::TypeParameter {
562                    // Get the bound type from type_of
563                    if let Some(bound_id) = sym.type_of()
564                        && let Some(bound_sym) = unit.opt_get_symbol(bound_id)
565                        && let Some(bound_block_id) = bound_sym.block_id()
566                    {
567                        // Create edge: bound --UsedBy--> this_interface
568                        // This means: bound is used by this_interface (as a type parameter constraint)
569                        self.add_relation(bound_block_id, BlockRelation::UsedBy, block_id);
570                        self.add_relation(block_id, BlockRelation::Uses, bound_block_id);
571                    }
572                }
573            });
574        }
575    }
576
577    /// Link enum relationships.
578    fn link_enum(
579        &self,
580        _unit: &CompileUnit<'tcx>,
581        block_id: BlockId,
582        enum_block: &crate::block::BlockEnum<'tcx>,
583    ) {
584        // Variants are like fields
585        for variant_id in enum_block.get_variants() {
586            self.add_relation(block_id, BlockRelation::HasField, variant_id);
587            self.add_relation(variant_id, BlockRelation::FieldOf, block_id);
588        }
589        // Note: Variant type argument edges are created during graph_render edge collection
590    }
591
592    /// Link call site relationships.
593    fn link_call(
594        &self,
595        _unit: &CompileUnit<'tcx>,
596        block_id: BlockId,
597        call: &crate::block::BlockCall<'tcx>,
598    ) {
599        // Link call site to callee
600        // Already set by graph_builder when creating BlockCall
601        if let Some(callee_id) = call.get_callee() {
602            self.add_relation(block_id, BlockRelation::Calls, callee_id);
603            self.add_relation(callee_id, BlockRelation::CalledBy, block_id);
604        }
605    }
606
607    /// Link return type relationships.
608    /// Uses symbol.type_of() chain for cross-file safe lookup.
609    fn link_return(
610        &self,
611        unit: &CompileUnit<'tcx>,
612        block_id: BlockId,
613        ret: &crate::block::BlockReturn<'tcx>,
614    ) {
615        // First try the block's type_ref directly (set during block building)
616        if let Some(type_id) = ret.get_type_ref() {
617            self.add_relation(block_id, BlockRelation::TypeOf, type_id);
618            self.add_relation(type_id, BlockRelation::TypeFor, block_id);
619            return;
620        }
621
622        // Fallback: resolve via symbol (handles cross-file references)
623        let type_id = self.resolve_type_ref(unit, &ret.base);
624
625        if let Some(type_id) = type_id {
626            // Update the block's type_ref so rendering shows the correct reference
627            ret.set_type_ref(type_id);
628            self.add_relation(block_id, BlockRelation::TypeOf, type_id);
629            self.add_relation(type_id, BlockRelation::TypeFor, block_id);
630        }
631    }
632
633    /// Link parameter type relationships.
634    /// Uses symbol.type_of() chain for cross-file safe lookup.
635    fn link_parameter(
636        &self,
637        unit: &CompileUnit<'tcx>,
638        block_id: BlockId,
639        param: &crate::block::BlockParameter<'tcx>,
640    ) {
641        // First try the block's type_ref directly (set during block building)
642        if let Some(type_id) = param.get_type_ref() {
643            self.add_relation(block_id, BlockRelation::TypeOf, type_id);
644            self.add_relation(type_id, BlockRelation::TypeFor, block_id);
645            return;
646        }
647
648        // Fallback: resolve via symbol (handles cross-file references)
649        let type_id = self.resolve_type_ref(unit, &param.base);
650
651        if let Some(type_id) = type_id {
652            // Update the block's type_ref so rendering shows the correct reference
653            param.set_type_ref(type_id);
654            self.add_relation(block_id, BlockRelation::TypeOf, type_id);
655            self.add_relation(type_id, BlockRelation::TypeFor, block_id);
656        }
657    }
658
659    /// Link field relationships.
660    /// Uses symbol.type_of() chain for cross-file safe lookup.
661    fn link_field(
662        &self,
663        unit: &CompileUnit<'tcx>,
664        block_id: BlockId,
665        field: &crate::block::BlockField<'tcx>,
666    ) {
667        // Link type reference
668        // First try the block's type_ref directly (set during block building)
669        if let Some(type_id) = field.get_type_ref() {
670            self.add_relation(block_id, BlockRelation::TypeOf, type_id);
671            self.add_relation(type_id, BlockRelation::TypeFor, block_id);
672        } else {
673            // Fallback: resolve via symbol (handles cross-file references)
674            let type_id = self.resolve_type_ref(unit, &field.base);
675
676            if let Some(type_id) = type_id {
677                // Update the block's type_ref so rendering shows the correct reference
678                field.set_type_ref(type_id);
679                self.add_relation(block_id, BlockRelation::TypeOf, type_id);
680                self.add_relation(type_id, BlockRelation::TypeFor, block_id);
681            }
682        }
683
684        // Link nested fields (for enum variants with struct-like fields)
685        for child_id in field.base.get_children() {
686            self.add_relation(block_id, BlockRelation::HasField, child_id);
687            self.add_relation(child_id, BlockRelation::FieldOf, block_id);
688        }
689    }
690
691    /// Link const relationships (type annotation).
692    /// Uses symbol.type_of() chain for cross-file safe lookup.
693    fn link_const(
694        &self,
695        unit: &CompileUnit<'tcx>,
696        block_id: BlockId,
697        const_block: &crate::block::BlockConst<'tcx>,
698    ) {
699        // First try the block's type_ref directly (set during block building)
700        if let Some(type_id) = const_block.get_type_ref() {
701            self.add_relation(block_id, BlockRelation::TypeOf, type_id);
702            self.add_relation(type_id, BlockRelation::TypeFor, block_id);
703            return;
704        }
705
706        // Fallback: resolve via symbol (handles cross-file references)
707        let type_id = self.resolve_type_ref(unit, &const_block.base);
708
709        if let Some(type_id) = type_id {
710            // Update the block's type_ref so rendering shows the correct reference
711            const_block.set_type_ref(type_id);
712            self.add_relation(block_id, BlockRelation::TypeOf, type_id);
713            self.add_relation(type_id, BlockRelation::TypeFor, block_id);
714        }
715    }
716
717    /// Link type alias relationships.
718    /// Uses symbol.type_of() chain for cross-file safe lookup.
719    fn link_alias(
720        &self,
721        unit: &CompileUnit<'tcx>,
722        block_id: BlockId,
723        alias: &crate::block::BlockAlias<'tcx>,
724    ) {
725        // Try symbol.type_of() chain first (cross-file safe)
726        let type_id = self.resolve_type_ref(unit, &alias.base);
727
728        if let Some(type_id) = type_id {
729            self.add_relation(block_id, BlockRelation::TypeOf, type_id);
730            self.add_relation(type_id, BlockRelation::TypeFor, block_id);
731        }
732    }
733
734    /// Resolve type reference from a block's symbol.type_of() chain.
735    /// This is cross-file safe since binding already resolved the types.
736    fn resolve_type_ref(
737        &self,
738        _unit: &CompileUnit<'tcx>,
739        base: &crate::block::BlockBase<'tcx>,
740    ) -> Option<BlockId> {
741        // Use symbol.type_of() chain (cross-file safe)
742        if let Some(sym) = base.symbol() {
743            if let Some(type_of_id) = sym.type_of()
744                && let Some(type_sym) = self.cc.opt_get_symbol(type_of_id)
745            {
746                return type_sym.block_id();
747            }
748            // If symbol IS the type (no type_of), use its own block_id
749            // Type kinds include: Struct, Enum, Trait, TypeAlias, Primitive, etc.
750            if crate::symbol::SYM_KIND_TYPES.contains(sym.kind()) {
751                return sym.block_id();
752            }
753        }
754        None
755    }
756}