Skip to main content

llmcc_rust/
collect.rs

1use llmcc_core::context::CompileUnit;
2use llmcc_core::ir::{HirId, HirIdent, HirNode, HirScope};
3use llmcc_core::next_hir_id;
4use llmcc_core::scope::{Scope, ScopeStack};
5use llmcc_core::symbol::{ScopeId, SymKind, SymKindSet, Symbol};
6use llmcc_resolver::{CollectorScopes, ResolverOption};
7
8use std::collections::HashMap;
9
10use crate::LangRust;
11use crate::token::AstVisitorRust;
12
13/// Check if a function is in a method context (parent is a type: Struct, Enum, Trait, or UnresolvedType).
14/// This is used to distinguish between free functions and methods inside impl blocks.
15fn is_method_context(parent: Option<&Symbol>) -> bool {
16    parent.is_some_and(|p| {
17        matches!(
18            p.kind(),
19            SymKind::Struct | SymKind::Enum | SymKind::Trait | SymKind::UnresolvedType
20        )
21    })
22}
23
24/// Callback type for scope entry actions
25type ScopeEntryCallback<'tcx> = Box<dyn FnOnce(&HirNode<'tcx>, &mut CollectorScopes<'tcx>) + 'tcx>;
26
27#[derive(Debug)]
28pub struct CollectorVisitor<'tcx> {
29    scope_map: HashMap<ScopeId, &'tcx Scope<'tcx>>,
30}
31
32impl<'tcx> CollectorVisitor<'tcx> {
33    fn new() -> Self {
34        Self {
35            scope_map: HashMap::new(),
36        }
37    }
38
39    /// Declare a symbol from a named field in the AST node
40    fn declare_symbol(
41        &self,
42        unit: &CompileUnit<'tcx>,
43        node: &HirNode<'tcx>,
44        scopes: &CollectorScopes<'tcx>,
45        kind: SymKind,
46        field_id: u16,
47    ) -> Option<&'tcx Symbol> {
48        // try to find identifier by field first, if not found try scope's identifier
49        let ident = node
50            .ident_by_field(unit, field_id)
51            .or_else(|| node.as_scope().and_then(|sn| sn.opt_ident()))?;
52
53        tracing::trace!("declaring symbol '{}' of kind {:?}", ident.name, kind);
54        let sym = scopes.lookup_or_insert(ident.name, node, kind)?;
55        ident.set_symbol(sym);
56
57        // Also set the ident on the scope so set_block_id can find it
58        if let Some(sn) = node.as_scope() {
59            sn.set_ident(ident);
60        }
61
62        Some(sym)
63    }
64
65    /// Find all identifiers in a pattern node (recursive)
66    fn collect_pattern_identifiers(
67        unit: &CompileUnit<'tcx>,
68        node: &HirNode<'tcx>,
69        scopes: &CollectorScopes<'tcx>,
70        kind: SymKind,
71    ) -> Vec<&'tcx Symbol> {
72        let mut symbols = Vec::new();
73        Self::collect_pattern_identifiers_impl(unit, node, scopes, kind, &mut symbols);
74        symbols
75    }
76
77    /// Recursive worker for [`collect_pattern_identifiers`].
78    ///
79    /// Examples of the shapes we cover:
80    /// - `let (a, b): (i32, i32)` will visit both tuple elements.
81    /// - `let Foo { x, y: (left, right) } = value;` walks nested struct/tuple patterns.
82    fn collect_pattern_identifiers_impl(
83        unit: &CompileUnit<'tcx>,
84        node: &HirNode<'tcx>,
85        scopes: &CollectorScopes<'tcx>,
86        kind: SymKind,
87        symbols: &mut Vec<&'tcx Symbol>,
88    ) {
89        if matches!(
90            node.kind_id(),
91            // Skip non-binding identifiers
92            LangRust::type_identifier | LangRust::primitive_type | LangRust::field_identifier |
93            // Special handling for scoped identifiers: don't collect them as variables
94            LangRust::scoped_identifier | LangRust::scoped_type_identifier
95        ) {
96            return;
97        }
98
99        if let Some(ident) = node.as_ident() {
100            let name = ident.name.to_string();
101            let sym = scopes.lookup_or_insert(&name, node, kind);
102
103            if let Some(sym) = sym {
104                ident.set_symbol(sym);
105                symbols.push(sym);
106            }
107        }
108        for &child_id in node.child_ids() {
109            let child = unit.hir_node(child_id);
110            Self::collect_pattern_identifiers_impl(unit, &child, scopes, kind, symbols);
111        }
112    }
113
114    fn alloc_scope(&mut self, unit: &CompileUnit<'tcx>, symbol: &'tcx Symbol) -> &'tcx Scope<'tcx> {
115        let scope = unit.cc.alloc_scope(symbol.owner());
116        scope.set_symbol(symbol);
117        self.scope_map.insert(scope.id(), scope);
118        scope
119    }
120
121    fn get_scope(&self, scope_id: ScopeId) -> Option<&'tcx Scope<'tcx>> {
122        self.scope_map.get(&scope_id).copied()
123    }
124
125    /// Lookup a symbol by name, trying primary kind first, then UnresolvedType, then inserting new
126    fn lookup_or_convert(
127        &mut self,
128        unit: &CompileUnit<'tcx>,
129        scopes: &mut CollectorScopes<'tcx>,
130        name: &str,
131        node: &HirNode<'tcx>,
132        kind: SymKind,
133    ) -> Option<&'tcx Symbol> {
134        tracing::trace!(
135            "looking up or converting symbol '{}' of kind {:?}",
136            name,
137            kind
138        );
139
140        // Try looking up by primary kind
141        if let Some(symbol) = scopes.lookup_symbol(name, SymKindSet::from_kind(kind)) {
142            tracing::trace!(
143                "found existing symbol '{}' of kind {:?} {:?}",
144                name,
145                kind,
146                symbol,
147            );
148            return Some(symbol);
149        }
150
151        // Try unresolved type if not found - convert to target kind
152        if let Some(symbol) =
153            scopes.lookup_symbol(name, SymKindSet::from_kind(SymKind::UnresolvedType))
154        {
155            tracing::trace!(
156                "found existing unresolved symbol '{}', converting to {:?}, {:?}",
157                name,
158                kind,
159                symbol,
160            );
161            // Actually convert the UnresolvedType to the target kind
162            symbol.set_kind(kind);
163            return Some(symbol);
164        }
165
166        // Insert new symbol with primary kind
167        if let Some(symbol) = scopes.lookup_or_insert(name, node, kind) {
168            tracing::trace!("inserting new symbol '{}' of kind {:?}", name, kind,);
169            // create a scope for this symbol if needed
170            if symbol.opt_scope().is_none() {
171                let scope = self.alloc_scope(unit, symbol);
172                symbol.set_scope(scope.id());
173            }
174            return Some(symbol);
175        }
176
177        None
178    }
179
180    /// AST: Any scoped node (module, function, trait, impl, etc.)
181    /// Purpose: Set up scope hierarchy, link identifiers to symbols, and push/pop scopes
182    #[allow(clippy::too_many_arguments)]
183    #[tracing::instrument(skip_all)]
184    fn visit_with_scope(
185        &mut self,
186        unit: &CompileUnit<'tcx>,
187        node: &HirNode<'tcx>,
188        scopes: &mut CollectorScopes<'tcx>,
189        sym: &'tcx Symbol,
190        sn: &'tcx HirScope<'tcx>,
191        ident: &'tcx HirIdent<'tcx>,
192        on_scope_enter: Option<ScopeEntryCallback<'tcx>>,
193    ) {
194        tracing::trace!(
195            "visiting with scope for symbol {}",
196            sym.format(Some(scopes.interner()))
197        );
198        ident.set_symbol(sym);
199        sn.set_ident(ident);
200
201        let scope = if sym.opt_scope().is_none() {
202            tracing::trace!(
203                "allocating new scope for symbol {}",
204                sym.format(Some(scopes.interner()))
205            );
206            self.alloc_scope(unit, sym)
207        } else {
208            tracing::trace!(
209                "use existing scope for symbol {}",
210                sym.format(Some(scopes.interner()))
211            );
212            match self.get_scope(sym.scope()) {
213                Some(s) => s,
214                None => {
215                    tracing::warn!(
216                        "scope not found for symbol {}, allocating new",
217                        sym.format(Some(scopes.interner()))
218                    );
219                    self.alloc_scope(unit, sym)
220                }
221            }
222        };
223        sym.set_scope(scope.id());
224        sn.set_scope(scope);
225
226        scopes.push_scope(scope);
227        if let Some(callback) = on_scope_enter {
228            callback(node, scopes);
229        }
230        self.visit_children(unit, node, scopes, scope, Some(sym));
231        scopes.pop_scope();
232    }
233
234    /// AST: Generic scoped-named item handler (module, function, struct, enum, trait, macro, etc.)
235    /// Purpose: Declare a named symbol with scope, lookup or insert it, and establish scope hierarchy
236    #[allow(clippy::too_many_arguments)]
237    #[tracing::instrument(skip_all)]
238    fn visit_scoped_named(
239        &mut self,
240        unit: &CompileUnit<'tcx>,
241        node: &HirNode<'tcx>,
242        scopes: &mut CollectorScopes<'tcx>,
243        _namespace: &'tcx Scope<'tcx>,
244        _parent: Option<&Symbol>,
245        kind: SymKind,
246        field_id: u16,
247        on_scope_enter: Option<ScopeEntryCallback<'tcx>>,
248    ) {
249        if let Some((sn, ident)) = node.scope_and_ident_by_field(unit, field_id) {
250            tracing::trace!(
251                "visiting scoped named node with kind '{:?}' '{}'",
252                kind,
253                ident.name
254            );
255            if let Some(sym) = self.lookup_or_convert(unit, scopes, ident.name, node, kind) {
256                self.visit_with_scope(unit, node, scopes, sym, sn, ident, on_scope_enter);
257            }
258        } else {
259            tracing::warn!("scoped named node is missing scope or ident, skipping");
260        }
261    }
262}
263
264impl<'tcx> AstVisitorRust<'tcx, CollectorScopes<'tcx>> for CollectorVisitor<'tcx> {
265    // Note: Test items (#[test] functions, #[cfg(test)] modules) are already filtered out
266    // at the HIR building stage in ir_builder.rs, so they won't appear in the HIR tree.
267
268    /// AST: block { ... }
269    /// Purpose: Create a new lexical scope for block-scoped variables and statements
270    #[tracing::instrument(skip_all)]
271    fn visit_block(
272        &mut self,
273        unit: &CompileUnit<'tcx>,
274        node: &HirNode<'tcx>,
275        scopes: &mut CollectorScopes<'tcx>,
276        _namespace: &'tcx Scope<'tcx>,
277        parent: Option<&Symbol>,
278    ) {
279        if let Some(sn) = node.as_scope() {
280            let scope = unit.cc.alloc_scope(node.id());
281            sn.set_scope(scope);
282
283            scopes.push_scope(scope);
284            self.visit_children(unit, node, scopes, scope, parent);
285            scopes.pop_scope();
286        }
287    }
288
289    fn visit_match_block(
290        &mut self,
291        unit: &CompileUnit<'tcx>,
292        node: &HirNode<'tcx>,
293        scopes: &mut CollectorScopes<'tcx>,
294        namespace: &'tcx Scope<'tcx>,
295        parent: Option<&Symbol>,
296    ) {
297        self.visit_block(unit, node, scopes, namespace, parent);
298    }
299
300    /// AST: source_file - root node of the compilation unit
301    /// Purpose: Parse crate/module names, create file scope, set up global symbol namespace
302    #[rustfmt::skip]
303    #[tracing::instrument(skip_all)]
304    fn visit_source_file(
305        &mut self,
306        unit: &CompileUnit<'tcx>,
307        node: &HirNode<'tcx>,
308        scopes: &mut CollectorScopes<'tcx>,
309        namespace: &'tcx Scope<'tcx>,
310        parent: Option<&Symbol>,
311    ) {
312        let start_depth = scopes.scope_depth();
313        let meta = unit.unit_meta();
314
315        // Track crate scope for parent relationships
316        let mut crate_scope: Option<&'tcx Scope<'tcx>> = None;
317
318        // Set up crate scope from unit metadata
319        if let Some(ref crate_name) = meta.package_name
320            && let Some(symbol) = scopes.lookup_or_insert_global(crate_name, node, SymKind::Crate)
321        {
322            tracing::trace!("insert crate symbol in globals '{}'", crate_name);
323            scopes.push_scope_with(node, Some(symbol));
324            crate_scope = scopes.top();
325        }
326
327        // For files in subdirectories (like utils/helper.rs), create a module scope
328        // for proper hierarchy traversal. The module scope has a Module symbol.
329        let mut module_wrapper_scope: Option<&'tcx Scope<'tcx>> = None;
330        if let Some(ref module_name) = meta.module_name
331            && let Some(module_sym) = scopes.lookup_or_insert_global(module_name, node, SymKind::Module)
332        {
333            tracing::trace!("insert module symbol in globals '{}'", module_name);
334            // Create a wrapper scope with the module symbol for hierarchy traversal.
335            // This scope is separate from the module symbol's resolution scope.
336            let mod_scope = self.alloc_scope(unit, module_sym);
337            if let Some(crate_s) = crate_scope {
338                mod_scope.add_parent(crate_s);
339            }
340            module_wrapper_scope = Some(mod_scope);
341            // Note: We don't change module_sym.set_scope() - that stays pointing to
342            // the file scope for path resolution (e.g., `::helper` resolves in utils.rs scope)
343        }
344
345        let sn = node.as_scope().unwrap();
346        if let Some(ref file_name) = meta.file_name
347            && let Some(file_sym) = scopes.lookup_or_insert(file_name, node, SymKind::File)
348        {
349            tracing::trace!("insert file symbol '{}' in current scope", file_name);
350            let arena_name = unit.cc.arena().alloc_str(file_name);
351            let ident = unit.cc.alloc_file_ident(next_hir_id(), arena_name, file_sym);
352            ident.set_symbol(file_sym);
353            sn.set_ident(ident);
354
355            let scope = self.alloc_scope(unit, file_sym);
356            file_sym.set_scope(scope.id());
357            sn.set_scope(scope);
358
359            // Add crate and module scopes as parents for hierarchy traversal
360            if let Some(crate_s) = crate_scope {
361                scope.add_parent(crate_s);
362            }
363            if let Some(module_s) = module_wrapper_scope {
364                scope.add_parent(module_s);
365            }
366
367            scopes.push_scope(scope);
368
369            if let Some(crate_sym) = scopes.lookup_or_insert_global("crate", node, SymKind::Module) {
370                tracing::trace!("insert 'crate' symbol in globals");
371                // Use the shared globals scope (first on stack), not the unit's local globals
372                crate_sym.set_scope(scopes.scopes().globals().id());
373            }
374
375            // For top-level files (not lib.rs or main.rs), create a module symbol
376            // in the crate scope that links to this file's scope.
377            // This enables paths like `crate::models::Config` to resolve.
378            // Also insert into the crate scope for cross-crate qualified path resolution
379            // (e.g., `crate_b::utils::helper` needs `utils` in `crate_b`'s scope).
380            if file_name != "lib" && file_name != "main" && module_wrapper_scope.is_none() {
381                // Insert into global for backward compatibility
382                if let Some(mod_sym) = scopes.insert_in_global(file_name, node, SymKind::Module) {
383                    tracing::trace!("link file '{}' as module in global scope", file_name);
384                    mod_sym.set_scope(scope.id());
385                }
386                // Also insert into crate scope for qualified path resolution like `crate_b::utils`
387                if let Some(crate_s) = crate_scope
388                    && let Some(mod_sym) =
389                        scopes.insert_in_scope(crate_s, file_name, node, SymKind::Module)
390                {
391                    tracing::trace!(
392                        "link file '{}' as module in crate scope {:?}",
393                        file_name,
394                        crate_s.id()
395                    );
396                    mod_sym.set_scope(scope.id());
397                }
398            }
399        }
400
401        // Use visit_children which handles test filtering automatically
402        self.visit_children(unit, node, scopes, namespace, parent);
403
404        scopes.pop_until(start_depth);
405    }
406
407    /// AST: mod name { ... } or mod name;
408    /// Purpose: Create namespace scope for module, declare module symbol
409    #[tracing::instrument(skip_all)]
410    fn visit_mod_item(
411        &mut self,
412        unit: &CompileUnit<'tcx>,
413        node: &HirNode<'tcx>,
414        scopes: &mut CollectorScopes<'tcx>,
415        namespace: &'tcx Scope<'tcx>,
416        parent: Option<&Symbol>,
417    ) {
418        if node.child_by_field(unit, LangRust::field_body).is_none() {
419            return;
420        }
421
422        // Use the current top of the stack (actual parent scope for super::)
423        let parent_scope_id = scopes.top().map(|s| s.id()).unwrap_or(namespace.id());
424
425        // Callback to insert `super` symbol pointing to parent module scope
426        let on_scope_enter: ScopeEntryCallback<'tcx> = Box::new(move |node, scopes| {
427            // Insert `super` symbol in current (module) scope, pointing to parent scope
428            if let Some(super_sym) = scopes.lookup_or_insert("super", node, SymKind::Module) {
429                super_sym.set_scope(parent_scope_id);
430                tracing::trace!(
431                    "inserted 'super' symbol pointing to scope {:?}",
432                    parent_scope_id
433                );
434            }
435        });
436
437        self.visit_scoped_named(
438            unit,
439            node,
440            scopes,
441            namespace,
442            parent,
443            SymKind::Namespace,
444            LangRust::field_name,
445            Some(on_scope_enter),
446        );
447    }
448
449    /// AST: fn name(...) -> Type { ... }
450    /// Purpose: Declare function symbol, create function scope for parameters and body
451    #[tracing::instrument(skip_all)]
452    fn visit_function_item(
453        &mut self,
454        unit: &CompileUnit<'tcx>,
455        node: &HirNode<'tcx>,
456        scopes: &mut CollectorScopes<'tcx>,
457        namespace: &'tcx Scope<'tcx>,
458        parent: Option<&Symbol>,
459    ) {
460        // Determine if this is a method (inside impl) or a free function
461        let is_method = is_method_context(parent);
462        let kind = if is_method {
463            SymKind::Method
464        } else {
465            SymKind::Function
466        };
467
468        self.visit_scoped_named(
469            unit,
470            node,
471            scopes,
472            namespace,
473            parent,
474            kind,
475            LangRust::field_name,
476            None,
477        );
478
479        // For free functions, also add a reference in unit_globals for cross-crate resolution
480        // This happens after visit_scoped_named so the symbol already exists with its scope
481        if !is_method
482            && let Some((_, ident)) = node.scope_and_ident_by_field(unit, LangRust::field_name)
483            && let Some(sym) = scopes.lookup_symbol(ident.name, SymKindSet::from_kind(kind))
484        {
485            scopes.globals().insert(sym);
486        }
487    }
488
489    /// AST: extern "C" fn signature or trait method signature
490    /// Purpose: Declare function symbol for extern/trait function signatures
491    #[tracing::instrument(skip_all)]
492    fn visit_function_signature_item(
493        &mut self,
494        unit: &CompileUnit<'tcx>,
495        node: &HirNode<'tcx>,
496        scopes: &mut CollectorScopes<'tcx>,
497        namespace: &'tcx Scope<'tcx>,
498        parent: Option<&Symbol>,
499    ) {
500        // Trait method signatures are also methods
501        let kind = if is_method_context(parent) {
502            SymKind::Method
503        } else {
504            SymKind::Function
505        };
506        self.visit_scoped_named(
507            unit,
508            node,
509            scopes,
510            namespace,
511            parent,
512            kind,
513            LangRust::field_name,
514            None,
515        );
516    }
517
518    /// AST: struct Name { fields... } or struct Name(types...);
519    /// Purpose: Declare struct symbol, create struct scope for fields and methods
520    #[tracing::instrument(skip_all)]
521    fn visit_struct_item(
522        &mut self,
523        unit: &CompileUnit<'tcx>,
524        node: &HirNode<'tcx>,
525        scopes: &mut CollectorScopes<'tcx>,
526        namespace: &'tcx Scope<'tcx>,
527        parent: Option<&Symbol>,
528    ) {
529        self.visit_scoped_named(
530            unit,
531            node,
532            scopes,
533            namespace,
534            parent,
535            SymKind::Struct,
536            LangRust::field_name,
537            Some(Box::new(|node, scopes| {
538                let _ = scopes.lookup_or_insert("self", node, SymKind::TypeAlias);
539                let _ = scopes.lookup_or_insert("Self", node, SymKind::TypeAlias);
540            })),
541        );
542
543        // Also add struct to unit_globals for cross-crate type resolution
544        if let Some((_, ident)) = node.scope_and_ident_by_field(unit, LangRust::field_name)
545            && let Some(sym) =
546                scopes.lookup_symbol(ident.name, SymKindSet::from_kind(SymKind::Struct))
547        {
548            scopes.globals().insert(sym);
549        }
550    }
551
552    /// AST: enum Name { variants... }
553    /// Purpose: Declare enum symbol, create enum scope for variants
554    #[tracing::instrument(skip_all)]
555    fn visit_enum_item(
556        &mut self,
557        unit: &CompileUnit<'tcx>,
558        node: &HirNode<'tcx>,
559        scopes: &mut CollectorScopes<'tcx>,
560        namespace: &'tcx Scope<'tcx>,
561        parent: Option<&Symbol>,
562    ) {
563        self.visit_scoped_named(
564            unit,
565            node,
566            scopes,
567            namespace,
568            parent,
569            SymKind::Enum,
570            LangRust::field_name,
571            None,
572        );
573
574        // Also add enum to unit_globals for cross-crate type resolution
575        if let Some((_, ident)) = node.scope_and_ident_by_field(unit, LangRust::field_name)
576            && let Some(sym) =
577                scopes.lookup_symbol(ident.name, SymKindSet::from_kind(SymKind::Enum))
578        {
579            scopes.globals().insert(sym);
580        }
581    }
582
583    /// AST: trait Name { associated items... }
584    /// Purpose: Declare trait symbol, create trait scope for methods and associated types
585    #[tracing::instrument(skip_all)]
586    fn visit_trait_item(
587        &mut self,
588        unit: &CompileUnit<'tcx>,
589        node: &HirNode<'tcx>,
590        scopes: &mut CollectorScopes<'tcx>,
591        namespace: &'tcx Scope<'tcx>,
592        parent: Option<&Symbol>,
593    ) {
594        self.visit_scoped_named(
595            unit,
596            node,
597            scopes,
598            namespace,
599            parent,
600            SymKind::Trait,
601            LangRust::field_name,
602            Some(Box::new(|node, scopes| {
603                let _ = scopes.lookup_or_insert("self", node, SymKind::TypeAlias);
604                let _ = scopes.lookup_or_insert("Self", node, SymKind::TypeAlias);
605            })),
606        );
607
608        // Also add trait to unit_globals for cross-crate type resolution
609        if let Some((_, ident)) = node.scope_and_ident_by_field(unit, LangRust::field_name)
610            && let Some(sym) =
611                scopes.lookup_symbol(ident.name, SymKindSet::from_kind(SymKind::Trait))
612        {
613            scopes.globals().insert(sym);
614        }
615    }
616
617    /// AST: impl [Trait for] Type { methods... }
618    /// Purpose: Create impl scope for methods
619    #[tracing::instrument(skip_all)]
620    fn visit_impl_item(
621        &mut self,
622        unit: &CompileUnit<'tcx>,
623        node: &HirNode<'tcx>,
624        scopes: &mut CollectorScopes<'tcx>,
625        _namespace: &'tcx Scope<'tcx>,
626        _parent: Option<&Symbol>,
627    ) {
628        // For impl trait references, first try Trait, then UnresolvedType for cross-file resolution
629        if let Some(ti) = node.ident_by_field(unit, LangRust::field_trait) {
630            // First try looking up as Trait (in-file case)
631            let symbol = scopes
632                .lookup_symbol(ti.name, SymKindSet::from_kind(SymKind::Trait))
633                // Then try looking up as UnresolvedType (existing placeholder)
634                .or_else(|| {
635                    scopes.lookup_symbol(ti.name, SymKindSet::from_kind(SymKind::UnresolvedType))
636                })
637                // Finally create UnresolvedType placeholder for cross-file resolution during binding
638                .or_else(|| scopes.lookup_or_insert(ti.name, node, SymKind::UnresolvedType));
639            if let Some(symbol) = symbol {
640                ti.set_symbol(symbol);
641            }
642        }
643
644        if let Some((sn, ti)) = node.scope_and_ident_by_field(unit, LangRust::field_type)
645            && let Some(symbol) =
646                self.lookup_or_convert(unit, scopes, ti.name, node, SymKind::UnresolvedType)
647        {
648            tracing::trace!(
649                "visiting impl for type '{}', {:?}",
650                ti.name,
651                symbol.format(Some(scopes.interner())),
652            );
653            ti.set_symbol(symbol);
654            self.visit_with_scope(unit, node, scopes, symbol, sn, ti, None);
655        }
656    }
657
658    /// AST: macro_rules! name { ... }
659    /// Purpose: Declare macro symbol for later macro invocation resolution
660    #[tracing::instrument(skip_all)]
661    fn visit_macro_definition(
662        &mut self,
663        unit: &CompileUnit<'tcx>,
664        node: &HirNode<'tcx>,
665        scopes: &mut CollectorScopes<'tcx>,
666        namespace: &'tcx Scope<'tcx>,
667        parent: Option<&Symbol>,
668    ) {
669        self.visit_scoped_named(
670            unit,
671            node,
672            scopes,
673            namespace,
674            parent,
675            SymKind::Macro,
676            LangRust::field_name,
677            None,
678        );
679    }
680
681    /// AST: const NAME: Type = value;
682    /// Purpose: Declare const symbol and visit initializer expression for dependencies
683    #[tracing::instrument(skip_all)]
684    fn visit_const_item(
685        &mut self,
686        unit: &CompileUnit<'tcx>,
687        node: &HirNode<'tcx>,
688        scopes: &mut CollectorScopes<'tcx>,
689        namespace: &'tcx Scope<'tcx>,
690        _parent: Option<&Symbol>,
691    ) {
692        if let Some(symbol) =
693            self.declare_symbol(unit, node, scopes, SymKind::Const, LangRust::field_name)
694        {
695            self.visit_children(unit, node, scopes, namespace, Some(symbol));
696        }
697    }
698
699    /// AST: static NAME: Type = value;
700    /// Purpose: Declare static symbol and visit initializer expression for dependencies
701    #[tracing::instrument(skip_all)]
702    fn visit_static_item(
703        &mut self,
704        unit: &CompileUnit<'tcx>,
705        node: &HirNode<'tcx>,
706        scopes: &mut CollectorScopes<'tcx>,
707        namespace: &'tcx Scope<'tcx>,
708        _parent: Option<&Symbol>,
709    ) {
710        if let Some(symbol) =
711            self.declare_symbol(unit, node, scopes, SymKind::Static, LangRust::field_name)
712        {
713            self.visit_children(unit, node, scopes, namespace, Some(symbol));
714        }
715    }
716
717    /// AST: type Name = AnotherType;
718    /// Purpose: Declare type alias symbol and visit the aliased type for dependencies
719    #[tracing::instrument(skip_all)]
720    fn visit_type_item(
721        &mut self,
722        unit: &CompileUnit<'tcx>,
723        node: &HirNode<'tcx>,
724        scopes: &mut CollectorScopes<'tcx>,
725        namespace: &'tcx Scope<'tcx>,
726        _parent: Option<&Symbol>,
727    ) {
728        if let Some(symbol) =
729            self.declare_symbol(unit, node, scopes, SymKind::TypeAlias, LangRust::field_name)
730        {
731            self.visit_children(unit, node, scopes, namespace, Some(symbol));
732        }
733    }
734
735    /// AST: Generic type parameter T or K in fn<T, K>(...) or struct<T> { ... }
736    /// Purpose: Declare type parameter symbol within generic scope
737    #[tracing::instrument(skip_all)]
738    fn visit_type_parameter(
739        &mut self,
740        unit: &CompileUnit<'tcx>,
741        node: &HirNode<'tcx>,
742        scopes: &mut CollectorScopes<'tcx>,
743        namespace: &'tcx Scope<'tcx>,
744        parent: Option<&Symbol>,
745    ) {
746        let _ = self.declare_symbol(
747            unit,
748            node,
749            scopes,
750            SymKind::TypeParameter,
751            LangRust::field_name,
752        );
753        self.visit_children(unit, node, scopes, namespace, parent);
754    }
755
756    /// AST: Generic const parameter N in fn<const N: usize>(...) or struct<const N: usize> { ... }
757    /// Purpose: Declare const parameter symbol and add dependency to owner
758    #[tracing::instrument(skip_all)]
759    fn visit_const_parameter(
760        &mut self,
761        unit: &CompileUnit<'tcx>,
762        node: &HirNode<'tcx>,
763        scopes: &mut CollectorScopes<'tcx>,
764        namespace: &'tcx Scope<'tcx>,
765        parent: Option<&Symbol>,
766    ) {
767        let _ = self.declare_symbol(unit, node, scopes, SymKind::Const, LangRust::field_name);
768        self.visit_children(unit, node, scopes, namespace, parent);
769    }
770
771    /// AST: type Assoc = Type; in trait definition
772    /// Purpose: Declare associated type symbol within trait scope
773    #[tracing::instrument(skip_all)]
774    fn visit_associated_type(
775        &mut self,
776        unit: &CompileUnit<'tcx>,
777        node: &HirNode<'tcx>,
778        scopes: &mut CollectorScopes<'tcx>,
779        namespace: &'tcx Scope<'tcx>,
780        parent: Option<&Symbol>,
781    ) {
782        let _ = self.declare_symbol(unit, node, scopes, SymKind::TypeAlias, LangRust::field_name);
783        self.visit_children(unit, node, scopes, namespace, parent);
784    }
785
786    /// AST: where T: Trait, U: Send, ... in generic bounds
787    /// Purpose: Visit where clause bounds for type dependency tracking
788    #[tracing::instrument(skip_all)]
789    fn visit_where_predicate(
790        &mut self,
791        unit: &CompileUnit<'tcx>,
792        node: &HirNode<'tcx>,
793        scopes: &mut CollectorScopes<'tcx>,
794        namespace: &'tcx Scope<'tcx>,
795        parent: Option<&Symbol>,
796    ) {
797        // let _ = self.declare_symbol(unit, node, scopes, SymKind::Field, LangRust::field_name);
798        self.visit_children(unit, node, scopes, namespace, parent);
799    }
800
801    /// AST: [Type; N] or [Type]
802    /// Purpose: visit array type element and length for dependency tracking
803    #[tracing::instrument(skip_all)]
804    fn visit_array_type(
805        &mut self,
806        unit: &CompileUnit<'tcx>,
807        node: &HirNode<'tcx>,
808        scopes: &mut CollectorScopes<'tcx>,
809        namespace: &'tcx Scope<'tcx>,
810        parent: Option<&Symbol>,
811    ) {
812        let _ = self.declare_symbol(
813            unit,
814            node,
815            scopes,
816            SymKind::CompositeType,
817            LangRust::field_name,
818        );
819        self.visit_children(unit, node, scopes, namespace, parent);
820    }
821
822    /// AST: (Type1, Type2, ...) tuple type
823    /// Purpose: Visit tuple element types for dependency tracking
824    #[tracing::instrument(skip_all)]
825    fn visit_tuple_type(
826        &mut self,
827        unit: &CompileUnit<'tcx>,
828        node: &HirNode<'tcx>,
829        scopes: &mut CollectorScopes<'tcx>,
830        namespace: &'tcx Scope<'tcx>,
831        parent: Option<&Symbol>,
832    ) {
833        self.visit_array_type(unit, node, scopes, namespace, parent);
834    }
835
836    /// AST: i32, u64, f32, bool, str, etc. - primitive type keyword
837    /// Purpose: Visit primitive type children (minimal, mostly a no-op)
838    #[tracing::instrument(skip_all)]
839    fn visit_primitive_type(
840        &mut self,
841        unit: &CompileUnit<'tcx>,
842        node: &HirNode<'tcx>,
843        scopes: &mut CollectorScopes<'tcx>,
844        namespace: &'tcx Scope<'tcx>,
845        parent: Option<&Symbol>,
846    ) {
847        self.visit_children(unit, node, scopes, namespace, parent);
848    }
849
850    /// AST: dyn Trait or impl Trait or other advanced type constructs
851    /// Purpose: Visit abstract type children for trait dependency tracking
852    fn visit_abstract_type(
853        &mut self,
854        unit: &CompileUnit<'tcx>,
855        node: &HirNode<'tcx>,
856        scopes: &mut CollectorScopes<'tcx>,
857        namespace: &'tcx Scope<'tcx>,
858        parent: Option<&Symbol>,
859    ) {
860        self.visit_children(unit, node, scopes, namespace, parent);
861    }
862
863    /// AST: field_name: Type in struct body
864    /// Purpose: Declare field symbol and visit field type for dependencies
865    fn visit_field_declaration(
866        &mut self,
867        unit: &CompileUnit<'tcx>,
868        node: &HirNode<'tcx>,
869        scopes: &mut CollectorScopes<'tcx>,
870        namespace: &'tcx Scope<'tcx>,
871        parent: Option<&Symbol>,
872    ) {
873        let _ = self.declare_symbol(unit, node, scopes, SymKind::Field, LangRust::field_name);
874        self.visit_children(unit, node, scopes, namespace, parent);
875    }
876
877    /// AST: Variant in enum { Variant, Variant(Type), Variant { field: Type }, ... }
878    /// Purpose: Declare enum variant symbol and link it to parent enum via type_of
879    fn visit_enum_variant(
880        &mut self,
881        unit: &CompileUnit<'tcx>,
882        node: &HirNode<'tcx>,
883        scopes: &mut CollectorScopes<'tcx>,
884        namespace: &'tcx Scope<'tcx>,
885        parent: Option<&Symbol>,
886    ) {
887        // Get the parent enum symbol before creating the variant
888        let parent_enum = parent.or_else(|| namespace.opt_symbol());
889
890        self.visit_scoped_named(
891            unit,
892            node,
893            scopes,
894            namespace,
895            parent,
896            SymKind::EnumVariant,
897            LangRust::field_name,
898            None,
899        );
900
901        // Set type_of on the variant to point to the parent enum
902        if let Some(enum_sym) = parent_enum
903            && let Some(ident) = node.ident_by_field(unit, LangRust::field_name)
904            && let Some(variant_sym) =
905                scopes.lookup_symbol(ident.name, SymKindSet::from_kind(SymKind::EnumVariant))
906        {
907            variant_sym.set_type_of(enum_sym.id);
908        }
909    }
910
911    /// AST: parameter in function signature param: Type or pattern, Type in closure
912    /// Purpose: Declare function/closure parameter as variable symbol
913    fn visit_parameter(
914        &mut self,
915        unit: &CompileUnit<'tcx>,
916        node: &HirNode<'tcx>,
917        scopes: &mut CollectorScopes<'tcx>,
918        namespace: &'tcx Scope<'tcx>,
919        _parent: Option<&Symbol>,
920    ) {
921        // Check if this is a 'self' parameter
922        if let Some(ident) = node.ident_by_field(unit, LangRust::field_pattern)
923            && ident.name == "self"
924            && let Some(symbol) =
925                scopes.lookup_symbol(ident.name, SymKindSet::from_kind(SymKind::Field))
926        {
927            // For 'self' parameters, try to resolve it as a Field in the current scope
928            ident.set_symbol(symbol);
929            self.visit_children(unit, node, scopes, namespace, Some(symbol));
930            return;
931        }
932
933        // Get the pattern node to check if it's a complex pattern (tuple, struct, etc.)
934        if let Some(pattern) = node.child_by_field(unit, LangRust::field_pattern) {
935            // Check if this is a simple identifier pattern or a complex pattern
936            if pattern.as_ident().is_some() {
937                // Simple identifier pattern: declare as variable directly
938                if let Some(symbol) = self.declare_symbol(
939                    unit,
940                    node,
941                    scopes,
942                    SymKind::Variable,
943                    LangRust::field_pattern,
944                ) {
945                    self.visit_children(unit, node, scopes, namespace, Some(symbol));
946                    return;
947                }
948            } else {
949                // Complex pattern (tuple, struct, etc.): collect all identifiers
950                let _ =
951                    Self::collect_pattern_identifiers(unit, &pattern, scopes, SymKind::Variable);
952                self.visit_children(unit, node, scopes, namespace, None);
953                return;
954            }
955        }
956
957        // Fallback: try to declare using the old method
958        if let Some(symbol) = self.declare_symbol(
959            unit,
960            node,
961            scopes,
962            SymKind::Variable,
963            LangRust::field_pattern,
964        ) {
965            self.visit_children(unit, node, scopes, namespace, Some(symbol));
966        }
967    }
968
969    /// AST: |param1, param2| { body } - closure/anonymous function
970    /// Purpose: Create closure scope, declare closure parameters as variables
971    fn visit_closure_expression(
972        &mut self,
973        unit: &CompileUnit<'tcx>,
974        node: &HirNode<'tcx>,
975        scopes: &mut CollectorScopes<'tcx>,
976        namespace: &'tcx Scope<'tcx>,
977        parent: Option<&Symbol>,
978    ) {
979        // Create a scope for the closure
980        if let Some(sn) = node.as_scope() {
981            let scope = unit.cc.alloc_scope(node.id());
982            sn.set_scope(scope);
983
984            // Link scope to parent namespace
985            scope.add_parent(namespace);
986
987            scopes.push_scope(scope);
988
989            // Collect closure parameters
990            if let Some(params) = node.child_by_field(unit, LangRust::field_parameters) {
991                let _ = Self::collect_pattern_identifiers(unit, &params, scopes, SymKind::Variable);
992            }
993
994            self.visit_children(unit, node, scopes, scope, parent);
995            scopes.pop_scope();
996        }
997    }
998
999    /// AST: let pattern = value; or let pattern: Type = value; statement
1000    /// Purpose: Collect pattern identifiers as variables, handle closure special case
1001    fn visit_let_declaration(
1002        &mut self,
1003        unit: &CompileUnit<'tcx>,
1004        node: &HirNode<'tcx>,
1005        scopes: &mut CollectorScopes<'tcx>,
1006        namespace: &'tcx Scope<'tcx>,
1007        parent: Option<&Symbol>,
1008    ) {
1009        // Check if value is a closure expression to determine symbol kind
1010        let is_closure = node
1011            .child_by_field(unit, LangRust::field_value)
1012            .map(|v| v.kind_id() == LangRust::closure_expression)
1013            .unwrap_or(false);
1014
1015        let kind = if is_closure {
1016            SymKind::Closure
1017        } else {
1018            SymKind::Variable
1019        };
1020
1021        // Collect the pattern identifier(s) with appropriate kind
1022        let let_syms = if let Some(pattern) = node.child_by_field(unit, LangRust::field_pattern) {
1023            Self::collect_pattern_identifiers(unit, &pattern, scopes, kind)
1024        } else {
1025            vec![]
1026        };
1027
1028        // For closures, pass the let symbol as parent so closure scope gets linked
1029        // Use first symbol if it's a simple pattern, otherwise use parent
1030        if is_closure && !let_syms.is_empty() {
1031            self.visit_children(unit, node, scopes, namespace, Some(let_syms[0]));
1032        } else {
1033            self.visit_children(unit, node, scopes, namespace, parent);
1034        }
1035    }
1036}
1037
1038pub fn collect_symbols<'tcx>(
1039    unit: CompileUnit<'tcx>,
1040    node: &HirNode<'tcx>,
1041    scope_stack: ScopeStack<'tcx>,
1042    _config: &ResolverOption,
1043) -> &'tcx Scope<'tcx> {
1044    let cc = unit.cc;
1045    let arena = cc.arena();
1046    let unit_globals_val = Scope::new(HirId(unit.index));
1047    let scope_id = unit_globals_val.id().0;
1048    let unit_globals = arena.alloc_with_id(scope_id, unit_globals_val);
1049    let mut scopes = CollectorScopes::new(cc, unit.index, scope_stack, unit_globals);
1050
1051    let mut visit = CollectorVisitor::new();
1052    visit.visit_node(&unit, node, &mut scopes, unit_globals, None);
1053
1054    unit_globals
1055}