llmcc_core/
graph_builder.rs

1use rayon::iter::{IntoParallelIterator, ParallelIterator};
2use std::marker::PhantomData;
3
4use crate::DynError;
5pub use crate::block::{BasicBlock, BlockId, BlockKind, BlockRelation};
6use crate::block::{
7    BlockAlias, BlockCall, BlockClass, BlockConst, BlockEnum, BlockField, BlockFunc, BlockImpl,
8    BlockInterface, BlockModule, BlockParameter, BlockReturn, BlockRoot, BlockTrait,
9};
10use crate::context::{CompileCtxt, CompileUnit};
11use crate::graph::UnitGraph;
12use crate::ir::HirNode;
13use crate::lang_def::LanguageTrait;
14use crate::visit::HirVisitor;
15
16#[derive(Debug, Clone, Copy, Default)]
17pub struct GraphBuildConfig;
18
19#[derive(Debug, Clone, Copy, Default)]
20pub struct GraphBuildOption {
21    pub sequential: bool,
22}
23
24impl GraphBuildOption {
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    pub fn with_sequential(mut self, sequential: bool) -> Self {
30        self.sequential = sequential;
31        self
32    }
33}
34
35#[derive(Debug)]
36struct GraphBuilder<'tcx, Language> {
37    unit: CompileUnit<'tcx>,
38    root: Option<BlockId>,
39    /// Stack of children being collected. Each entry is (BlockId, BlockKind) pairs.
40    children_stack: Vec<Vec<(BlockId, BlockKind)>>,
41    /// Stack of parent kinds - tracks what kind of block we're currently inside
42    parent_kind_stack: Vec<BlockKind>,
43    _config: GraphBuildConfig,
44    _marker: PhantomData<Language>,
45}
46
47impl<'tcx, Language: LanguageTrait> GraphBuilder<'tcx, Language> {
48    fn new(unit: CompileUnit<'tcx>, config: GraphBuildConfig) -> Self {
49        Self {
50            unit,
51            root: None,
52            children_stack: Vec::new(),
53            parent_kind_stack: Vec::new(),
54            _config: config,
55            _marker: PhantomData,
56        }
57    }
58
59    fn next_id(&self) -> BlockId {
60        self.unit.reserve_block_id()
61    }
62
63    /// Resolve type info from a symbol, following the type_of chain.
64    /// Returns (type_name, type_block_id) tuple.
65    fn resolve_type_info(
66        &self,
67        symbol: Option<&'tcx crate::symbol::Symbol>,
68    ) -> (String, Option<BlockId>) {
69        let sym = match symbol {
70            Some(s) => s,
71            None => return (String::new(), None),
72        };
73
74        // Special case: EnumVariant symbols don't have a type - they ARE the enum's members
75        // Don't show @type for enum variants
76        if sym.kind() == crate::symbol::SymKind::EnumVariant {
77            return (String::new(), None);
78        }
79
80        // First try type_of (for symbols that point to a type)
81        if let Some(type_sym_id) = sym.type_of()
82            && let Some(type_sym) = self.unit.opt_get_symbol(type_sym_id)
83        {
84            // Check if type_sym is a TypeParameter with a bound - use the bound type
85            let effective_type = if type_sym.kind() == crate::symbol::SymKind::TypeParameter
86                && let Some(bound_id) = type_sym.type_of()
87            {
88                self.unit.opt_get_symbol(bound_id).unwrap_or(type_sym)
89            } else {
90                type_sym
91            };
92
93            let type_name = self
94                .unit
95                .resolve_interned_owned(effective_type.name)
96                .unwrap_or_default();
97            let type_block_id = effective_type.block_id();
98            return (type_name, type_block_id);
99        }
100
101        // Fallback: use the symbol directly (for cases where symbol IS the type)
102        let type_name = self
103            .unit
104            .resolve_interned_owned(sym.name)
105            .unwrap_or_default();
106        let type_block_id = sym.block_id();
107        (type_name, type_block_id)
108    }
109
110    /// Extract the defining symbol from a HIR node.
111    /// For scoped nodes (class, func, etc.): gets symbol from scope
112    /// For identifier nodes: gets the resolved symbol
113    /// Returns None for nodes without an associated symbol
114    fn extract_symbol(
115        &self,
116        node: HirNode<'tcx>,
117        kind: BlockKind,
118    ) -> Option<&'tcx crate::symbol::Symbol> {
119        // Impl blocks reference existing type symbols, not their own
120        // Don't extract symbol for impl - it will be set via relation linking
121        if kind == BlockKind::Impl {
122            return None;
123        }
124
125        // Try scope first (for class/func/enum etc.)
126        if let Some(scope) = node.as_scope()
127            && let Some(sym) = scope.opt_symbol()
128        {
129            return Some(sym);
130        }
131
132        // For fields, use the language's name_field to avoid finding decorator identifiers
133        if kind == BlockKind::Field
134            && let Some(ident) = node.ident_by_field(&self.unit, Language::name_field())
135            && let Some(sym) = ident.opt_symbol()
136        {
137            return Some(sym);
138        }
139
140        // For parameters, prefer a bound variable identifier over decorator identifiers.
141        // This avoids cases like `handle(@inject req: string)` where `find_ident` returns `inject`.
142        if kind == BlockKind::Parameter {
143            for ident in node.collect_idents(&self.unit) {
144                if let Some(sym) = ident.opt_symbol()
145                    && matches!(sym.kind(), crate::symbol::SymKind::Variable)
146                {
147                    return Some(sym);
148                }
149            }
150        }
151
152        // Try identifier (for parameters, etc.)
153        if let Some(ident) = node.find_ident(&self.unit)
154            && let Some(sym) = ident.opt_symbol()
155        {
156            return Some(sym);
157        }
158
159        // Try children's identifiers (for self_parameter where the identifier is a child)
160        for child in node.children(&self.unit) {
161            if let Some(ident) = child.as_ident()
162                && let Some(sym) = ident.opt_symbol()
163            {
164                return Some(sym);
165            }
166        }
167
168        None
169    }
170
171    fn create_block(
172        &self,
173        id: BlockId,
174        node: HirNode<'tcx>,
175        kind: BlockKind,
176        parent: Option<BlockId>,
177        children: Vec<BlockId>,
178    ) -> BasicBlock<'tcx> {
179        // Extract symbol for this block (if applicable)
180        let symbol = self.extract_symbol(node, kind);
181
182        // NOTE: block_id is set on the node's symbol in build_block() BEFORE visiting children
183        // This allows children to resolve their parent's type (e.g., enum variants -> enum)
184        match kind {
185            BlockKind::Root => {
186                // Get file path from HirFile node or from compile unit
187                let file_name = node
188                    .as_file()
189                    .map(|file| file.file_path.clone())
190                    .or_else(|| self.unit.file_path().map(|s| s.to_string()));
191                let block = BlockRoot::new_with_symbol(
192                    id,
193                    node,
194                    parent,
195                    children,
196                    file_name.clone(),
197                    symbol,
198                );
199
200                // Populate crate_name and module_path from scope chain
201                // Populate crate_name and module_path from scope chain.
202                // The binding phase sets up proper parent scopes with Module/Crate symbols.
203                if let Some(scope_node) = node.as_scope()
204                    && let Some(scope) = scope_node.opt_scope()
205                {
206                    use crate::symbol::SymKind;
207
208                    // Use unit_meta for package/module info
209                    let meta = self.unit.unit_meta();
210                    if let Some(ref pkg_name) = meta.package_name {
211                        block.set_crate_name(pkg_name.clone());
212                    }
213                    if let Some(ref pkg_root) = meta.package_root {
214                        block.set_crate_root(pkg_root.display().to_string());
215                    }
216                    if let Some(ref mod_name) = meta.module_name {
217                        block.set_module_path(mod_name.clone());
218                    }
219                    if let Some(ref mod_root) = meta.module_root {
220                        block.set_module_root(mod_root.display().to_string());
221                    }
222
223                    // Fall back to scope chain if unit_meta is not populated
224                    if meta.package_name.is_none()
225                        && let Some(crate_sym) = scope.find_parent_by_kind(SymKind::Crate)
226                        && let Some(name) = self.unit.cc.interner.resolve_owned(crate_sym.name)
227                    {
228                        block.set_crate_name(name);
229                    }
230                    if meta.module_name.is_none()
231                        && let Some(module_sym) = scope.find_parent_by_kind(SymKind::Module)
232                        && let Some(name) = self.unit.cc.interner.resolve_owned(module_sym.name)
233                    {
234                        block.set_module_path(name);
235                    }
236                }
237
238                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
239                BasicBlock::Root(block_ref)
240            }
241            BlockKind::Func | BlockKind::Method => {
242                let block = BlockFunc::new_with_symbol(id, node, kind, parent, children, symbol);
243                if kind == BlockKind::Method {
244                    block.set_is_method(true);
245                }
246                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
247                BasicBlock::Func(block_ref)
248            }
249            BlockKind::Class => {
250                let block = BlockClass::new_with_symbol(id, node, parent, children, symbol);
251                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
252                BasicBlock::Class(block_ref)
253            }
254            BlockKind::Trait => {
255                let block = BlockTrait::new_with_symbol(id, node, parent, children, symbol);
256                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
257                BasicBlock::Trait(block_ref)
258            }
259            BlockKind::Interface => {
260                let block = BlockInterface::new_with_symbol(id, node, parent, children, symbol);
261                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
262                BasicBlock::Interface(block_ref)
263            }
264            BlockKind::Call => {
265                // For call blocks, symbol is the callee (if resolved)
266                let stmt = BlockCall::new_with_symbol(id, node, parent, children, symbol);
267                // Set callee from resolved symbol
268                if let Some(callee_sym) = node.ident_symbol(&self.unit)
269                    && let Some(callee_block_id) = callee_sym.block_id()
270                {
271                    stmt.set_callee(callee_block_id);
272                }
273                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, stmt);
274                BasicBlock::Call(block_ref)
275            }
276            BlockKind::Enum => {
277                let enum_ty = BlockEnum::new_with_symbol(id, node, parent, children, symbol);
278                let block_ref = self
279                    .unit
280                    .cc
281                    .block_arena
282                    .alloc_with_id(id.0 as usize, enum_ty);
283                BasicBlock::Enum(block_ref)
284            }
285            BlockKind::Const => {
286                let mut stmt = BlockConst::new_with_symbol(id, node, parent, children, symbol);
287                // Find identifier name from children
288                if let Some(ident) = node.find_ident(&self.unit) {
289                    stmt.name = ident.name.to_string();
290                }
291                // Resolve and set type info
292                let (type_name, type_ref) = self.resolve_type_info(symbol);
293                stmt.set_type_info(type_name, type_ref);
294                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, stmt);
295                BasicBlock::Const(block_ref)
296            }
297            BlockKind::Impl => {
298                // Impl blocks: resolve target and trait references using field-based access
299                let mut block = BlockImpl::new(id, node, parent, children);
300
301                // Get target type from the "type" field (e.g., `impl Foo` or `impl Trait for Foo`)
302                if let Some(target_ident) = node.ident_by_field(&self.unit, Language::type_field())
303                    && let Some(sym) = target_ident.opt_symbol()
304                {
305                    // Follow type_of chain to get the actual type symbol for block_id
306                    let resolved = sym
307                        .type_of()
308                        .and_then(|id| self.unit.opt_get_symbol(id))
309                        .unwrap_or(sym);
310                    // Store original sym (which has nested_types from impl type args) not resolved
311                    block.set_target_info(resolved.block_id(), Some(sym));
312                }
313
314                // Get trait from the "trait" field (e.g., `impl Trait for Foo`)
315                if let Some(trait_ident) = node.ident_by_field(&self.unit, Language::trait_field())
316                    && let Some(sym) = trait_ident.opt_symbol()
317                {
318                    // Follow type_of chain to get the actual trait symbol
319                    let resolved = sym
320                        .type_of()
321                        .and_then(|id| self.unit.opt_get_symbol(id))
322                        .unwrap_or(sym);
323                    block.set_trait_info(resolved.block_id(), Some(resolved));
324                }
325
326                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
327                BasicBlock::Impl(block_ref)
328            }
329            BlockKind::Field => {
330                let mut block = BlockField::new_with_symbol(id, node, parent, children, symbol);
331                // Find identifier name from children using ident_by_field with the language's name field
332                // This avoids finding decorator identifiers before the actual field name
333                if let Some(ident) = node.ident_by_field(&self.unit, Language::name_field()) {
334                    block.name = ident.name.to_string();
335                } else if let Some(ident) = node.find_ident(&self.unit) {
336                    // Fallback to find_ident for languages that don't use name field
337                    block.name = ident.name.to_string();
338                }
339                // Resolve and set type info
340                let (type_name, type_ref) = self.resolve_type_info(symbol);
341                block.set_type_info(type_name, type_ref);
342                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
343                BasicBlock::Field(block_ref)
344            }
345            BlockKind::Parameter => {
346                let mut block = BlockParameter::new_with_symbol(id, node, parent, children, symbol);
347                // Prefer variable identifier for parameter name to avoid decorator identifiers.
348                if let Some(ident) = node.collect_idents(&self.unit).into_iter().find(|ident| {
349                    ident
350                        .opt_symbol()
351                        .is_some_and(|sym| matches!(sym.kind(), crate::symbol::SymKind::Variable))
352                }) {
353                    block.name = ident.name.to_string();
354                } else if let Some(ident) = node.find_ident(&self.unit) {
355                    block.name = ident.name.to_string();
356                } else if let Some(text) = node.find_text(&self.unit) {
357                    // Fallback: look for text nodes like "self" keyword
358                    block.name = text.to_string();
359                }
360                let (type_name, type_ref) = self.resolve_type_info(symbol);
361                block.set_type_info(type_name, type_ref);
362                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
363                BasicBlock::Parameter(block_ref)
364            }
365            BlockKind::Return => {
366                // Return blocks: symbol should already have type_of set during binding
367                let mut block = BlockReturn::new_with_symbol(id, node, parent, children, symbol);
368                let (type_name, type_ref) = self.resolve_type_info(symbol);
369                block.set_type_info(type_name, type_ref);
370                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
371                BasicBlock::Return(block_ref)
372            }
373            BlockKind::Alias => {
374                let mut block = BlockAlias::new_with_symbol(id, node, parent, children, symbol);
375                // Find identifier name from children
376                if let Some(ident) = node.find_ident(&self.unit) {
377                    block.name = ident.name.to_string();
378                }
379                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
380                BasicBlock::Alias(block_ref)
381            }
382            BlockKind::Module => {
383                // Get module name from identifier
384                let name = node
385                    .find_ident(&self.unit)
386                    .map(|ident| ident.name.to_string())
387                    .unwrap_or_default();
388                // Inline modules have children (the module body), file modules don't
389                let is_inline = !children.is_empty();
390                let block = BlockModule::new_with_symbol(
391                    id, node, parent, children, name, is_inline, symbol,
392                );
393                let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
394                BasicBlock::Module(block_ref)
395            }
396            _ => {
397                panic!("unknown block kind: {kind}")
398            }
399        }
400    }
401
402    fn build_block(
403        &mut self,
404        _unit: CompileUnit<'tcx>,
405        node: HirNode<'tcx>,
406        parent: BlockId,
407        recursive: bool,
408    ) {
409        let id = self.next_id();
410        // Try field-based block_kind first, then fall back to node-based
411        let field_kind = Language::block_kind(node.field_id());
412        let mut block_kind = if field_kind != BlockKind::Undefined {
413            field_kind
414        } else {
415            Language::block_kind(node.kind_id())
416        };
417        assert_ne!(block_kind, BlockKind::Undefined);
418
419        // Override Func -> Method if:
420        // 1. Symbol kind is Method (set during collection phase), OR
421        // 2. Parent block is an Impl (handles field/method name collision case)
422        if block_kind == BlockKind::Func {
423            let is_method_by_symbol = node
424                .opt_symbol()
425                .is_some_and(|sym| sym.kind() == crate::symbol::SymKind::Method);
426
427            // Check if parent is an impl block using parent_kind_stack
428            let is_in_impl = self
429                .parent_kind_stack
430                .last()
431                .is_some_and(|&k| k == BlockKind::Impl);
432
433            if is_method_by_symbol || is_in_impl {
434                block_kind = BlockKind::Method;
435            }
436        }
437
438        if self.root.is_none() {
439            self.root = Some(id);
440        }
441
442        // Set block_id on the node's symbol BEFORE visiting children
443        // This allows children to resolve their parent's type (e.g., enum variants -> enum)
444        // Don't set for impl blocks - they reference existing type symbols
445        // Don't set for return blocks - the return type node's symbol belongs to the type definition
446        if block_kind != BlockKind::Impl && block_kind != BlockKind::Return {
447            node.set_block_id(id);
448        }
449
450        let children_with_kinds = if recursive {
451            self.children_stack.push(Vec::new());
452            self.parent_kind_stack.push(block_kind);
453            self.visit_children(self.unit, node, id);
454            self.parent_kind_stack.pop();
455            self.children_stack.pop().unwrap()
456        } else {
457            Vec::new()
458        };
459
460        let child_ids: Vec<BlockId> = children_with_kinds.iter().map(|(id, _)| *id).collect();
461        let block = self.create_block(id, node, block_kind, Some(parent), child_ids);
462        self.populate_block_fields(node, &block, &children_with_kinds);
463        self.unit.insert_block(id, block, parent);
464
465        if let Some(children) = self.children_stack.last_mut() {
466            children.push((id, block_kind));
467        }
468    }
469
470    /// Build a block with a pre-determined kind (used for context-dependent block creation)
471    /// For tuple struct fields, the index is used as the field name.
472    fn build_block_with_kind_and_index(
473        &mut self,
474        _unit: CompileUnit<'tcx>,
475        node: HirNode<'tcx>,
476        parent: BlockId,
477        block_kind: BlockKind,
478        index: usize,
479    ) {
480        let id = self.next_id();
481
482        if self.root.is_none() {
483            self.root = Some(id);
484        }
485
486        // For context-dependent blocks (like tuple struct fields), don't recurse
487        let child_ids = Vec::new();
488
489        // Create the block - for tuple struct fields, use index as name
490        let block = if block_kind == BlockKind::Field {
491            self.create_tuple_field_block(id, node, Some(parent), child_ids, index)
492        } else {
493            self.create_block(id, node, block_kind, Some(parent), child_ids)
494        };
495
496        self.unit.insert_block(id, block, parent);
497
498        if let Some(children) = self.children_stack.last_mut() {
499            children.push((id, block_kind));
500        }
501    }
502
503    /// Create a field block for tuple struct with index as name
504    fn create_tuple_field_block(
505        &self,
506        id: BlockId,
507        node: HirNode<'tcx>,
508        parent: Option<BlockId>,
509        children: Vec<BlockId>,
510        index: usize,
511    ) -> BasicBlock<'tcx> {
512        // NOTE: Don't call set_block_id here - the node is a type_identifier that's
513        // bound to the struct symbol, and we don't want to overwrite the struct's block_id
514
515        // For tuple fields, the node is the type itself. Find the type symbol.
516        // Strategy 1: Use find_ident to get the identifier and its symbol
517        let mut type_symbol = node
518            .find_ident(&self.unit)
519            .and_then(|ident| ident.opt_symbol());
520
521        // Strategy 2: Look at children for symbol
522        if type_symbol.is_none() {
523            for child in node.children(&self.unit) {
524                if let Some(sym) = child.opt_symbol() {
525                    type_symbol = Some(sym);
526                    break;
527                }
528            }
529        }
530        // Strategy 3: Node's own scope/ident
531        if type_symbol.is_none()
532            && let Some(scope) = node.as_scope()
533            && let Some(ident) = *scope.ident.read()
534        {
535            type_symbol = ident.opt_symbol();
536        }
537        // Strategy 4: Node's own symbol
538        if type_symbol.is_none() {
539            type_symbol = node.opt_symbol();
540        }
541
542        let mut block = BlockField::new_with_symbol(id, node, parent, children, type_symbol);
543        block.name = index.to_string();
544        // Resolve and set type info
545        let (type_name, type_ref) = self.resolve_type_info(type_symbol);
546        block.set_type_info(type_name, type_ref);
547        let block_ref = self.unit.cc.block_arena.alloc_with_id(id.0 as usize, block);
548        BasicBlock::Field(block_ref)
549    }
550
551    /// Populate block-specific fields
552    fn populate_block_fields(
553        &self,
554        _node: HirNode<'tcx>,
555        block: &BasicBlock<'tcx>,
556        children: &[(BlockId, BlockKind)],
557    ) {
558        match block {
559            BasicBlock::Func(func) => {
560                for &(child_id, child_kind) in children {
561                    match child_kind {
562                        BlockKind::Parameter => func.add_parameter(child_id),
563                        BlockKind::Return => func.set_returns(child_id),
564                        _ => {}
565                    }
566                }
567            }
568            BasicBlock::Class(class) => {
569                for &(child_id, child_kind) in children {
570                    match child_kind {
571                        BlockKind::Field => class.add_field(child_id),
572                        BlockKind::Func | BlockKind::Method => class.add_method(child_id),
573                        _ => {}
574                    }
575                }
576            }
577            BasicBlock::Enum(enum_block) => {
578                for &(child_id, child_kind) in children {
579                    if child_kind == BlockKind::Field {
580                        enum_block.add_variant(child_id);
581                    }
582                }
583            }
584            BasicBlock::Trait(trait_block) => {
585                for &(child_id, child_kind) in children {
586                    if matches!(child_kind, BlockKind::Func | BlockKind::Method) {
587                        trait_block.add_method(child_id);
588                    }
589                }
590            }
591            BasicBlock::Interface(iface_block) => {
592                for &(child_id, child_kind) in children {
593                    match child_kind {
594                        BlockKind::Field => iface_block.add_field(child_id),
595                        BlockKind::Func | BlockKind::Method => iface_block.add_method(child_id),
596                        _ => {}
597                    }
598                }
599            }
600            BasicBlock::Impl(impl_block) => {
601                // Add methods to impl
602                for &(child_id, child_kind) in children {
603                    if matches!(child_kind, BlockKind::Func | BlockKind::Method) {
604                        impl_block.add_method(child_id);
605                    }
606                }
607                // Note: target and trait_ref are resolved in connect_blocks (graph.rs)
608                // where all blocks are built and cross-file references work
609            }
610            _ => {}
611        }
612    }
613
614    /// Get the effective block kind for a node, checking field first then node type.
615    fn effective_block_kind(node: HirNode<'tcx>) -> BlockKind {
616        let field_kind = Language::block_kind(node.field_id());
617        if field_kind != BlockKind::Undefined {
618            field_kind
619        } else {
620            Language::block_kind(node.kind_id())
621        }
622    }
623
624    /// Check if a block kind should trigger block creation.
625    fn is_block_kind(kind: BlockKind) -> bool {
626        matches!(
627            kind,
628            BlockKind::Func
629                | BlockKind::Method
630                | BlockKind::Class
631                | BlockKind::Trait
632                | BlockKind::Interface
633                | BlockKind::Enum
634                | BlockKind::Const
635                | BlockKind::Impl
636                | BlockKind::Field
637                | BlockKind::Parameter
638                | BlockKind::Return
639                | BlockKind::Call
640                | BlockKind::Root
641                | BlockKind::Alias
642                | BlockKind::Module
643        )
644    }
645}
646
647impl<'tcx, Language: LanguageTrait> HirVisitor<'tcx> for GraphBuilder<'tcx, Language> {
648    fn visit_children(&mut self, unit: CompileUnit<'tcx>, node: HirNode<'tcx>, parent: BlockId) {
649        let parent_kind_id = node.kind_id();
650        let children = node.child_ids();
651        let children_vec: Vec<_> = children.iter().map(|id| unit.hir_node(*id)).collect();
652        let mut tuple_field_index = 0usize;
653
654        // Note: Test items (#[test] functions, #[cfg(test)] modules) are already filtered out
655        // at the HIR building stage in ir_builder.rs, so they won't appear in children_vec.
656
657        for child in children_vec.iter() {
658            // Check for context-dependent blocks (like tuple struct fields)
659            // Only intercept if the parent context changes the block kind
660            let base_kind = Self::effective_block_kind(*child);
661            let context_kind =
662                Language::block_kind_with_parent(child.kind_id(), child.field_id(), parent_kind_id);
663
664            if context_kind != base_kind && Self::is_block_kind(context_kind) {
665                // Parent context creates a block that wouldn't exist otherwise
666                // For tuple struct fields, pass the index as the name
667                self.build_block_with_kind_and_index(
668                    unit,
669                    *child,
670                    parent,
671                    context_kind,
672                    tuple_field_index,
673                );
674                tuple_field_index += 1;
675            } else if context_kind == BlockKind::Undefined && Self::is_block_kind(base_kind) {
676                // Parent context suppresses block creation (e.g., return_type inside function_type)
677                // Just visit children without creating a block
678                self.visit_children(unit, *child, parent);
679            } else {
680                // Normal path - let visit_node handle it
681                self.visit_node(unit, *child, parent);
682            }
683        }
684    }
685
686    fn visit_file(&mut self, unit: CompileUnit<'tcx>, node: HirNode<'tcx>, parent: BlockId) {
687        self.children_stack.push(Vec::new());
688        self.build_block(unit, node, parent, true);
689    }
690
691    fn visit_internal(&mut self, unit: CompileUnit<'tcx>, node: HirNode<'tcx>, parent: BlockId) {
692        let kind = Self::effective_block_kind(node);
693        if Self::is_block_kind(kind) && kind != BlockKind::Root {
694            self.build_block(unit, node, parent, false);
695        } else {
696            self.visit_children(unit, node, parent);
697        }
698    }
699
700    fn visit_scope(&mut self, unit: CompileUnit<'tcx>, node: HirNode<'tcx>, parent: BlockId) {
701        let kind = Self::effective_block_kind(node);
702        if Self::is_block_kind(kind) {
703            self.build_block(unit, node, parent, true);
704        } else {
705            self.visit_children(unit, node, parent);
706        }
707    }
708
709    fn visit_ident(&mut self, unit: CompileUnit<'tcx>, node: HirNode<'tcx>, parent: BlockId) {
710        let kind = Self::effective_block_kind(node);
711        if Self::is_block_kind(kind) {
712            self.build_block(unit, node, parent, false);
713        } else {
714            self.visit_children(unit, node, parent);
715        }
716    }
717}
718
719pub fn build_unit_graph<'tcx, L: LanguageTrait>(
720    unit: CompileUnit<'tcx>,
721    unit_index: usize,
722    config: GraphBuildConfig,
723) -> Result<Option<UnitGraph>, DynError> {
724    let root_hir = unit.file_root_id().ok_or("missing file start HIR id")?;
725    let mut builder = GraphBuilder::<L>::new(unit, config);
726    let root_node = unit.hir_node(root_hir);
727    builder.visit_node(unit, root_node, BlockId::ROOT_PARENT);
728
729    // Empty files or files with no blocks produce no root - this is OK, just skip them
730    match builder.root {
731        Some(root_block) => Ok(Some(UnitGraph::new(unit_index, root_block))),
732        None => Ok(None),
733    }
734}
735
736/// Build unit graphs for all compilation units in parallel.
737pub fn build_llmcc_graph<'tcx, L: LanguageTrait>(
738    cc: &'tcx CompileCtxt<'tcx>,
739    config: GraphBuildOption,
740) -> Result<Vec<UnitGraph>, DynError> {
741    let unit_graphs: Vec<UnitGraph> = if config.sequential {
742        (0..cc.get_files().len())
743            .map(|index| {
744                let unit = cc.compile_unit(index);
745                build_unit_graph::<L>(unit, index, GraphBuildConfig)
746            })
747            .collect::<Result<Vec<_>, DynError>>()?
748            .into_iter()
749            .flatten()
750            .collect()
751    } else {
752        (0..cc.get_files().len())
753            .into_par_iter()
754            .map(|index| {
755                let unit = cc.compile_unit(index);
756                build_unit_graph::<L>(unit, index, GraphBuildConfig)
757            })
758            .collect::<Result<Vec<_>, DynError>>()?
759            .into_iter()
760            .flatten()
761            .collect()
762    };
763
764    // No sorting needed: DashMap provides O(1) lookup by ID
765
766    Ok(unit_graphs)
767}