Skip to main content

llmcc_rust/
bind.rs

1#![allow(clippy::collapsible_if, clippy::needless_return)]
2
3use llmcc_core::context::CompileUnit;
4use llmcc_core::ir::{HirNode, HirScope};
5use llmcc_core::scope::Scope;
6use llmcc_core::symbol::{
7    SYM_KIND_ALL, SYM_KIND_CALLABLE, SYM_KIND_IMPL_TARGETS, SYM_KIND_TYPES, SymKind, SymKindSet,
8    Symbol,
9};
10use llmcc_resolver::{BinderScopes, ResolverOption};
11
12use crate::infer::infer_type;
13use crate::pattern::bind_pattern_types;
14use crate::token::AstVisitorRust;
15use crate::token::LangRust;
16
17type ScopeEnterFn<'tcx> =
18    Box<dyn FnOnce(&CompileUnit<'tcx>, &'tcx HirScope<'tcx>, &mut BinderScopes<'tcx>) + 'tcx>;
19
20/// Visitor for resolving symbol bindings and establishing relationships.
21#[derive(Debug)]
22pub struct BinderVisitor<'tcx> {
23    #[allow(dead_code)]
24    config: ResolverOption,
25    phantom: std::marker::PhantomData<&'tcx ()>,
26}
27
28impl<'tcx> BinderVisitor<'tcx> {
29    fn new(config: ResolverOption) -> Self {
30        Self {
31            config,
32            phantom: std::marker::PhantomData,
33        }
34    }
35
36    #[tracing::instrument(skip_all)]
37    #[allow(clippy::too_many_arguments)]
38    // AST: Helper for named scope nodes (struct, enum, mod, impl, etc.)
39    fn visit_scoped_named(
40        &mut self,
41        unit: &CompileUnit<'tcx>,
42        node: &HirNode<'tcx>,
43        sn: &'tcx HirScope<'tcx>,
44        scopes: &mut BinderScopes<'tcx>,
45        namespace: &'tcx Scope<'tcx>,
46        parent: Option<&Symbol>,
47        on_scope_enter: Option<ScopeEnterFn<'tcx>>,
48    ) {
49        tracing::trace!(
50            "visiting scoped named node kind: {:?}, namespace id: {:?}, parent: {:?}",
51            node.kind_id(),
52            namespace.id(),
53            parent.map(|p| p.format(Some(unit.interner()))),
54        );
55        let depth = scopes.scope_depth();
56
57        // Any visibility modifier (pub, pub(crate), pub(super), etc.) makes the symbol global
58        if let Some(_vis_modifier) = node.child_by_kind(unit, LangRust::visibility_modifier)
59            && let Some(sym) = sn.opt_symbol()
60        {
61            sym.set_is_global(true);
62            scopes.globals().insert(sym);
63        }
64
65        let child_parent = sn.opt_symbol().or(parent);
66
67        // Push scope (always succeeds for Rust since collector sets all scopes)
68        scopes.push_scope_node(sn);
69
70        if let Some(scope_enter) = on_scope_enter {
71            scope_enter(unit, sn, scopes);
72        }
73        self.visit_children(unit, node, scopes, scopes.top(), child_parent);
74        scopes.pop_until(depth);
75    }
76}
77
78impl<'tcx> AstVisitorRust<'tcx, BinderScopes<'tcx>> for BinderVisitor<'tcx> {
79    // Note: Test items (#[test] functions, #[cfg(test)] modules) are already filtered out
80    // at the HIR building stage in ir_builder.rs, so they won't appear in the HIR tree.
81
82    fn visit_source_file(
83        &mut self,
84        unit: &CompileUnit<'tcx>,
85        node: &HirNode<'tcx>,
86        scopes: &mut BinderScopes<'tcx>,
87        _namespace: &'tcx Scope<'tcx>,
88        _parent: Option<&Symbol>,
89    ) {
90        let file_path = unit.file_path().unwrap();
91        let depth = scopes.scope_depth();
92        let meta = unit.unit_meta();
93
94        tracing::trace!("binding source_file: {}", file_path);
95        if let Some(ref crate_name) = meta.package_name
96            && let Some(symbol) =
97                scopes.lookup_symbol(crate_name, SymKindSet::from_kind(SymKind::Crate))
98            && let Some(scope_id) = symbol.opt_scope()
99        {
100            tracing::trace!("pushing crate scope {:?}", scope_id);
101            scopes.push_scope(scope_id);
102        }
103
104        if let Some(ref module_name) = meta.module_name
105            && let Some(symbol) =
106                scopes.lookup_symbol(module_name, SymKindSet::from_kind(SymKind::Module))
107            && let Some(scope_id) = symbol.opt_scope()
108        {
109            tracing::trace!("pushing module scope {:?}", scope_id);
110            scopes.push_scope(scope_id);
111        }
112
113        if let Some(ref file_name) = meta.file_name
114            && let Some(file_sym) =
115                scopes.lookup_symbol(file_name, SymKindSet::from_kind(SymKind::File))
116            && let Some(scope_id) = file_sym.opt_scope()
117        {
118            tracing::trace!("pushing file scope {} {:?}", file_path, scope_id);
119            scopes.push_scope(scope_id);
120
121            let file_scope = unit.get_scope(scope_id);
122            self.visit_children(unit, node, scopes, file_scope, Some(file_sym));
123            scopes.pop_until(depth);
124        }
125    }
126
127    // AST: identifier (variable, function name, type name, etc.)
128    #[tracing::instrument(skip_all)]
129    fn visit_identifier(
130        &mut self,
131        _unit: &CompileUnit<'tcx>,
132        node: &HirNode<'tcx>,
133        scopes: &mut BinderScopes<'tcx>,
134        _namespace: &'tcx Scope<'tcx>,
135        _parent: Option<&Symbol>,
136    ) {
137        let ident = node.as_ident().unwrap();
138        if let Some(existing) = ident.opt_symbol()
139            && existing.kind().is_resolved()
140        {
141            return;
142        }
143
144        if let Some(symbol) = scopes.lookup_symbol(ident.name, SYM_KIND_ALL) {
145            ident.set_symbol(symbol);
146        }
147    }
148
149    // AST: type_identifier (refers to struct, enum, trait, etc.)
150    #[tracing::instrument(skip_all)]
151    fn visit_type_identifier(
152        &mut self,
153        _unit: &CompileUnit<'tcx>,
154        node: &HirNode<'tcx>,
155        scopes: &mut BinderScopes<'tcx>,
156        _namespace: &'tcx Scope<'tcx>,
157        _parent: Option<&Symbol>,
158    ) {
159        let ident = node.as_ident().unwrap();
160        if let Some(existing) = ident.opt_symbol()
161            && existing.kind().is_resolved()
162        {
163            return;
164        }
165
166        if let Some(symbol) = scopes.lookup_symbol(ident.name, SYM_KIND_TYPES) {
167            ident.set_symbol(symbol);
168        }
169    }
170
171    // AST: primitive_type (i32, u64, bool, f32, str, etc.)
172    #[tracing::instrument(skip_all)]
173    fn visit_primitive_type(
174        &mut self,
175        _unit: &CompileUnit<'tcx>,
176        node: &HirNode<'tcx>,
177        scopes: &mut BinderScopes<'tcx>,
178        _namespace: &'tcx Scope<'tcx>,
179        _parent: Option<&Symbol>,
180    ) {
181        let ident = node.as_ident().unwrap();
182        if let Some(symbol) =
183            scopes.lookup_global(ident.name, SymKindSet::from_kind(SymKind::Primitive))
184        {
185            ident.set_symbol(symbol);
186        }
187    }
188
189    // AST: type parameter T or T: Trait or T=Default in generics
190    // Sets type_of on the type parameter to point to its first trait bound or default type
191    #[tracing::instrument(skip_all)]
192    fn visit_type_parameter(
193        &mut self,
194        unit: &CompileUnit<'tcx>,
195        node: &HirNode<'tcx>,
196        scopes: &mut BinderScopes<'tcx>,
197        namespace: &'tcx Scope<'tcx>,
198        parent: Option<&Symbol>,
199    ) {
200        self.visit_children(unit, node, scopes, namespace, parent);
201
202        // Get the type parameter symbol
203        if let Some(type_param_sym) = node.ident_symbol_by_field(unit, LangRust::field_name) {
204            // Priority 1: Look for trait bounds (T: Trait)
205            if let Some(bounds_node) = node.child_by_field(unit, LangRust::field_bounds) {
206                // trait_bounds contains the trait types - get the first one
207                if let Some(first_bound) = infer_type(unit, scopes, &bounds_node) {
208                    type_param_sym.set_type_of(first_bound.id());
209                    tracing::trace!(
210                        "type parameter '{}' has bound '{}'",
211                        type_param_sym.format(Some(unit.interner())),
212                        first_bound.format(Some(unit.interner())),
213                    );
214                    return;
215                }
216            }
217            // Priority 2: Look for default type (RHS=Self)
218            if let Some(default_node) = node.child_by_field(unit, LangRust::field_default_type) {
219                if let Some(default_type) = infer_type(unit, scopes, &default_node) {
220                    type_param_sym.set_type_of(default_type.id());
221                    tracing::trace!(
222                        "type parameter '{}' has default '{}'",
223                        type_param_sym.format(Some(unit.interner())),
224                        default_type.format(Some(unit.interner())),
225                    );
226                }
227            }
228        }
229    }
230
231    // AST: block { ... statements ... }
232    fn visit_block(
233        &mut self,
234        unit: &CompileUnit<'tcx>,
235        node: &HirNode<'tcx>,
236        scopes: &mut BinderScopes<'tcx>,
237        namespace: &'tcx Scope<'tcx>,
238        parent: Option<&Symbol>,
239    ) {
240        let sn = node.as_scope().unwrap();
241        scopes.push_scope(sn.scope().id());
242        self.visit_children(unit, node, scopes, namespace, parent);
243        scopes.pop_scope();
244    }
245
246    // AST: mod name { ... items ... }
247    #[tracing::instrument(skip_all)]
248    fn visit_mod_item(
249        &mut self,
250        unit: &CompileUnit<'tcx>,
251        node: &HirNode<'tcx>,
252        scopes: &mut BinderScopes<'tcx>,
253        namespace: &'tcx Scope<'tcx>,
254        parent: Option<&Symbol>,
255    ) {
256        if node.child_by_field(unit, LangRust::field_body).is_none() {
257            return;
258        }
259
260        let sn = node.as_scope().unwrap();
261        self.visit_scoped_named(unit, node, sn, scopes, namespace, parent, None);
262    }
263
264    // AST: fn foo(args) -> ret_type;
265    #[tracing::instrument(skip_all)]
266    fn visit_function_signature_item(
267        &mut self,
268        unit: &CompileUnit<'tcx>,
269        node: &HirNode<'tcx>,
270        scopes: &mut BinderScopes<'tcx>,
271        namespace: &'tcx Scope<'tcx>,
272        parent: Option<&Symbol>,
273    ) {
274        self.visit_function_item(unit, node, scopes, namespace, parent);
275    }
276
277    // AST: fn name(args) -> ret_type { body }
278    #[tracing::instrument(skip_all)]
279    fn visit_function_item(
280        &mut self,
281        unit: &CompileUnit<'tcx>,
282        node: &HirNode<'tcx>,
283        scopes: &mut BinderScopes<'tcx>,
284        namespace: &'tcx Scope<'tcx>,
285        parent: Option<&Symbol>,
286    ) {
287        let sn = node.as_scope().unwrap();
288        self.visit_scoped_named(unit, node, sn, scopes, namespace, parent, None);
289
290        let Some(fn_sym) = sn.opt_symbol() else {
291            return;
292        };
293
294        if let Some(return_type) = node.ident_symbol_by_field(unit, LangRust::field_return_type) {
295            tracing::trace!(
296                "binding function return type '{}' to '{}'",
297                return_type.format(Some(unit.interner())),
298                fn_sym.format(Some(unit.interner()))
299            );
300            fn_sym.set_type_of(return_type.id());
301        }
302
303        // Extract nested types from generic return types (e.g., Result<User, Error>)
304        if let Some(return_type_node) = node.child_by_field(unit, LangRust::field_return_type) {
305            extract_nested_types(unit, scopes, &return_type_node, fn_sym);
306        }
307
308        if unit.resolve_name(fn_sym.name) == "main" {
309            fn_sym.set_is_global(true);
310            scopes.globals().insert(fn_sym);
311        }
312    }
313
314    // AST: field_identifier (struct field name)
315    #[tracing::instrument(skip_all)]
316    fn visit_field_identifier(
317        &mut self,
318        unit: &CompileUnit<'tcx>,
319        node: &HirNode<'tcx>,
320        scopes: &mut BinderScopes<'tcx>,
321        namespace: &'tcx Scope<'tcx>,
322        parent: Option<&Symbol>,
323    ) {
324        self.visit_identifier(unit, node, scopes, namespace, parent);
325    }
326
327    // AST: struct Name { field1: Type1, field2: Type2 }
328    #[tracing::instrument(skip_all)]
329    fn visit_struct_item(
330        &mut self,
331        unit: &CompileUnit<'tcx>,
332        node: &HirNode<'tcx>,
333        scopes: &mut BinderScopes<'tcx>,
334        namespace: &'tcx Scope<'tcx>,
335        parent: Option<&Symbol>,
336    ) {
337        let sn = node.as_scope().unwrap();
338        self.visit_scoped_named(
339            unit,
340            node,
341            sn,
342            scopes,
343            namespace,
344            parent,
345            Some(Box::new(|unit, sn, scopes| {
346                for key in ["Self", "self"] {
347                    if let Some(self_sym) =
348                        scopes.lookup_symbol(key, SymKindSet::from_kind(SymKind::TypeAlias))
349                        && let Some(struct_sym) = sn.opt_symbol()
350                    {
351                        tracing::trace!(
352                            "binding '{}' to struct type '{}'",
353                            key,
354                            struct_sym.format(Some(unit.interner())),
355                        );
356                        self_sym.set_type_of(struct_sym.id());
357                        // assign scope
358                        if let Some(struct_scope) = struct_sym.opt_scope() {
359                            self_sym.set_scope(struct_scope);
360                        }
361                    }
362                }
363            })),
364        );
365    }
366
367    // AST: trait Name { methods... }
368    // Purpose: Bind Self/self to the trait for methods inside the trait
369    #[tracing::instrument(skip_all)]
370    fn visit_trait_item(
371        &mut self,
372        unit: &CompileUnit<'tcx>,
373        node: &HirNode<'tcx>,
374        scopes: &mut BinderScopes<'tcx>,
375        namespace: &'tcx Scope<'tcx>,
376        parent: Option<&Symbol>,
377    ) {
378        let sn = node.as_scope().unwrap();
379        self.visit_scoped_named(
380            unit,
381            node,
382            sn,
383            scopes,
384            namespace,
385            parent,
386            Some(Box::new(|unit, sn, scopes| {
387                for key in ["Self", "self"] {
388                    if let Some(self_sym) =
389                        scopes.lookup_symbol(key, SymKindSet::from_kind(SymKind::TypeAlias))
390                        && let Some(trait_sym) = sn.opt_symbol()
391                    {
392                        tracing::trace!(
393                            "binding '{}' to trait type '{}'",
394                            key,
395                            trait_sym.format(Some(unit.interner())),
396                        );
397                        self_sym.set_type_of(trait_sym.id());
398                        // assign scope
399                        if let Some(trait_scope) = trait_sym.opt_scope() {
400                            self_sym.set_scope(trait_scope);
401                        }
402                    }
403                }
404            })),
405        );
406    }
407
408    // AST: field: Type
409    #[tracing::instrument(skip_all)]
410    fn visit_field_declaration(
411        &mut self,
412        unit: &CompileUnit<'tcx>,
413        node: &HirNode<'tcx>,
414        scopes: &mut BinderScopes<'tcx>,
415        namespace: &'tcx Scope<'tcx>,
416        parent: Option<&Symbol>,
417    ) {
418        self.visit_children(unit, node, scopes, namespace, parent);
419
420        if let Some(field_sym) = node.ident_symbol_by_field(unit, LangRust::field_name) {
421            if let Some(struct_sym) = namespace.opt_symbol() {
422                field_sym.set_field_of(struct_sym.id());
423            }
424
425            if let Some(type_node) = node.child_by_field(unit, LangRust::field_type)
426                && let Some(field_type) = infer_type(unit, scopes, &type_node)
427            {
428                field_sym.set_type_of(field_type.id());
429                if let Some(struct_sym) = namespace.opt_symbol() {
430                    struct_sym.add_nested_type(field_type.id());
431                }
432                // Extract nested types from generic field types to the FIELD symbol
433                // e.g., for `data: Triple<User, Error>`, add User/Error to field's nested_types
434                // This allows edges: User -> Triple, Error -> Triple (not User -> AllThree)
435                extract_nested_types(unit, scopes, &type_node, field_sym);
436            }
437        }
438    }
439
440    // AST: impl [<Trait> for] Type { methods }
441    #[tracing::instrument(skip_all)]
442    fn visit_impl_item(
443        &mut self,
444        unit: &CompileUnit<'tcx>,
445        node: &HirNode<'tcx>,
446        scopes: &mut BinderScopes<'tcx>,
447        _namespace: &'tcx Scope<'tcx>,
448        _parent: Option<&Symbol>,
449    ) {
450        let target_ident = node.ident_by_field(unit, LangRust::field_type);
451        if let Some(target_ident) = target_ident
452            && let Some(target_sym) = target_ident.opt_symbol()
453        {
454            // Look up the impl target type (struct or enum that the trait is implemented for)
455            let target_resolved = scopes.lookup_symbol(target_ident.name, SYM_KIND_IMPL_TARGETS);
456
457            if target_sym.kind() == SymKind::UnresolvedType {
458                // Resolve the type for the impl type now
459                if let Some(resolved) = target_resolved
460                    && resolved.id() != target_sym.id()
461                    && !matches!(resolved.kind(), SymKind::UnresolvedType)
462                {
463                    tracing::trace!(
464                        "resolving impl target type '{}' to '{}'",
465                        target_sym.format(Some(unit.interner())),
466                        resolved.format(Some(unit.interner())),
467                    );
468                    // Update the unresolved symbol to point to the actual type
469                    target_sym.set_type_of(resolved.id());
470                    target_sym.set_kind(resolved.kind());
471                    target_sym.set_is_global(resolved.is_global());
472
473                    if let Some(resolved_scope) = resolved.opt_scope()
474                        && let Some(target_scoped) = target_sym.opt_scope()
475                    {
476                        // Build parent scope relationship
477                        tracing::trace!(
478                            "connecting impl target '{}' and resolved type '{}'",
479                            resolved_scope,
480                            target_scoped
481                        );
482                        let target_scope = unit.get_scope(target_scoped);
483                        let resolved_scope = unit.get_scope(resolved_scope);
484                        target_scope.add_parent(resolved_scope);
485                        resolved_scope.add_parent(target_scope);
486                    }
487                }
488            }
489
490            if let Some(trait_node) = node.child_by_field(unit, LangRust::field_trait)
491                && let Some(trait_ident) = trait_node.find_ident(unit)
492            {
493                // Only look for Trait kind - don't use SYM_KIND_TYPES which includes TypeParameter
494                // If not found here, keep the existing symbol (UnresolvedType from collection)
495                // and let graph phase handle cross-file resolution
496                let trait_sym =
497                    scopes.lookup_symbol(trait_ident.name, SymKindSet::from_kind(SymKind::Trait));
498
499                if let Some(trait_sym) = trait_sym {
500                    // Update the trait identifier's symbol to point to the resolved trait
501                    trait_ident.set_symbol(trait_sym);
502
503                    // Build parent scope relationship if target is resolved
504                    if let Some(target_resolved) = target_resolved
505                        && let Some(target_scope) = target_resolved.opt_scope()
506                        && let Some(trait_scope) = trait_sym.opt_scope()
507                    {
508                        let target_scope = unit.get_scope(target_scope);
509                        let trait_scope = unit.get_scope(trait_scope);
510                        tracing::trace!(
511                            "adding impl realtion: target '{}' implements trait '{}'",
512                            target_resolved.format(Some(unit.interner())),
513                            trait_sym.format(Some(unit.interner())),
514                        );
515                        target_scope.add_parent(trait_scope);
516                    }
517                }
518
519                // Extract type arguments from trait reference (e.g., User from `impl Repository<User>`)
520                // and store them on the target symbol's nested_types for graph edge building
521                // Use target_sym (always available) rather than target_resolved (may be None for cross-file)
522                if let Some(type_args) =
523                    trait_node.child_by_field(unit, LangRust::field_type_arguments)
524                {
525                    for &child_id in type_args.child_ids() {
526                        let child = unit.hir_node(child_id);
527                        if child.is_trivia() || child.kind_id() == LangRust::lifetime {
528                            continue;
529                        }
530                        if let Some(type_arg_sym) = infer_type(unit, scopes, &child) {
531                            if type_arg_sym.kind().is_defined_type() {
532                                target_sym.add_nested_type(type_arg_sym.id());
533                                tracing::trace!(
534                                    "impl type arg: {} -> {}",
535                                    type_arg_sym.format(Some(unit.interner())),
536                                    target_sym.format(Some(unit.interner())),
537                                );
538                            }
539                        }
540                    }
541                }
542            }
543
544            let sn = node.as_scope().unwrap();
545            let target_scope = unit.get_scope(target_sym.opt_scope().unwrap());
546            self.visit_scoped_named(unit, node, sn, scopes, target_scope, Some(target_sym), None);
547        }
548    }
549
550    // AST: func(args) or obj.method(args) or path::func(args)
551    // The function part (identifier, scoped_identifier, field_expression) is resolved
552    // by visit_children through the appropriate visitor (visit_identifier, visit_scoped_identifier, etc.)
553    #[tracing::instrument(skip_all)]
554    fn visit_call_expression(
555        &mut self,
556        unit: &CompileUnit<'tcx>,
557        node: &HirNode<'tcx>,
558        scopes: &mut BinderScopes<'tcx>,
559        namespace: &'tcx Scope<'tcx>,
560        parent: Option<&Symbol>,
561    ) {
562        self.visit_children(unit, node, scopes, namespace, parent);
563
564        // For simple identifiers that weren't resolved by visit_identifier,
565        // try to resolve them as callable symbols
566        if let Some(func_node) = node.child_by_field(unit, LangRust::field_function)
567            && func_node.kind_id() == LangRust::identifier
568            && let Some(ident) = func_node.as_ident()
569            && ident.opt_symbol().is_none()
570            && let Some(symbol) = scopes.lookup_symbol(ident.name, SYM_KIND_CALLABLE)
571        {
572            ident.set_symbol(symbol);
573        }
574    }
575
576    // AST: enum Name { Variant1, Variant2, ... }
577    #[tracing::instrument(skip_all)]
578    fn visit_enum_item(
579        &mut self,
580        unit: &CompileUnit<'tcx>,
581        node: &HirNode<'tcx>,
582        scopes: &mut BinderScopes<'tcx>,
583        namespace: &'tcx Scope<'tcx>,
584        parent: Option<&Symbol>,
585    ) {
586        let sn = node.as_scope().unwrap();
587        self.visit_scoped_named(unit, node, sn, scopes, namespace, parent, None);
588    }
589
590    // AST: macro_rules! name { ... }
591    fn visit_macro_definition(
592        &mut self,
593        unit: &CompileUnit<'tcx>,
594        node: &HirNode<'tcx>,
595        scopes: &mut BinderScopes<'tcx>,
596        namespace: &'tcx Scope<'tcx>,
597        parent: Option<&Symbol>,
598    ) {
599        let sn = node.as_scope().unwrap();
600        self.visit_scoped_named(unit, node, sn, scopes, namespace, parent, None);
601    }
602
603    // AST: macro!(args) or macro![args]
604    fn visit_macro_invocation(
605        &mut self,
606        unit: &CompileUnit<'tcx>,
607        node: &HirNode<'tcx>,
608        scopes: &mut BinderScopes<'tcx>,
609        namespace: &'tcx Scope<'tcx>,
610        parent: Option<&Symbol>,
611    ) {
612        self.visit_children(unit, node, scopes, namespace, parent);
613    }
614
615    fn visit_generic_type(
616        &mut self,
617        unit: &CompileUnit<'tcx>,
618        node: &HirNode<'tcx>,
619        scopes: &mut BinderScopes<'tcx>,
620        namespace: &'tcx Scope<'tcx>,
621        parent: Option<&Symbol>,
622    ) {
623        self.visit_children(unit, node, scopes, namespace, parent);
624    }
625
626    // AST: const NAME: Type = value;
627    fn visit_const_item(
628        &mut self,
629        unit: &CompileUnit<'tcx>,
630        node: &HirNode<'tcx>,
631        scopes: &mut BinderScopes<'tcx>,
632        namespace: &'tcx Scope<'tcx>,
633        parent: Option<&Symbol>,
634    ) {
635        self.visit_children(unit, node, scopes, namespace, parent);
636
637        if let Some(const_sym) = node.ident_symbol_by_field(unit, LangRust::field_name)
638            && let Some(const_ty) = node.child_by_field(unit, LangRust::field_type)
639            && let Some(ty) = infer_type(unit, scopes, &const_ty)
640        {
641            const_sym.set_type_of(ty.id());
642        }
643    }
644
645    // AST: static [mut] NAME: Type = value;
646    fn visit_static_item(
647        &mut self,
648        unit: &CompileUnit<'tcx>,
649        node: &HirNode<'tcx>,
650        scopes: &mut BinderScopes<'tcx>,
651        namespace: &'tcx Scope<'tcx>,
652        parent: Option<&Symbol>,
653    ) {
654        self.visit_const_item(unit, node, scopes, namespace, parent);
655    }
656
657    // AST: type Alias = ConcreteType;
658    #[tracing::instrument(skip_all)]
659    fn visit_type_item(
660        &mut self,
661        unit: &CompileUnit<'tcx>,
662        node: &HirNode<'tcx>,
663        scopes: &mut BinderScopes<'tcx>,
664        namespace: &'tcx Scope<'tcx>,
665        parent: Option<&Symbol>,
666    ) {
667        self.visit_children(unit, node, scopes, namespace, parent);
668
669        if let Some(name_sym) = node.ident_symbol(unit)
670            && let Some(type_sym) = node.ident_symbol_by_field(unit, LangRust::field_type)
671        {
672            name_sym.set_type_of(type_sym.id());
673        }
674    }
675
676    // AST: [ElementType; length] or [ElementType]
677    fn visit_array_type(
678        &mut self,
679        unit: &CompileUnit<'tcx>,
680        node: &HirNode<'tcx>,
681        scopes: &mut BinderScopes<'tcx>,
682        namespace: &'tcx Scope<'tcx>,
683        parent: Option<&Symbol>,
684    ) {
685        let sn = node.as_scope().unwrap();
686        self.visit_children(unit, node, scopes, namespace, parent);
687
688        if let Some(ident) = sn.opt_ident()
689            && let Some(symbol) =
690                scopes.lookup_symbol(ident.name, SymKindSet::from_kind(SymKind::CompositeType))
691            && symbol.nested_types().is_none()
692        {
693            if let Some(array_type_sym) = node.ident_symbol_by_field(unit, LangRust::field_element)
694            {
695                symbol.add_nested_type(array_type_sym.id());
696            }
697        }
698    }
699
700    // AST: (Type1, Type2, Type3)
701    fn visit_tuple_type(
702        &mut self,
703        unit: &CompileUnit<'tcx>,
704        node: &HirNode<'tcx>,
705        scopes: &mut BinderScopes<'tcx>,
706        namespace: &'tcx Scope<'tcx>,
707        parent: Option<&Symbol>,
708    ) {
709        let sn = node.as_scope().unwrap();
710        self.visit_children(unit, node, scopes, namespace, parent);
711
712        if let Some(tuple_ident) = sn.opt_ident()
713            && let Some(tuple_symbol) = scopes.lookup_symbol(
714                tuple_ident.name,
715                SymKindSet::from_kind(SymKind::CompositeType),
716            )
717            && tuple_symbol.nested_types().is_none()
718        {
719            for type_ident in node.collect_idents(unit) {
720                if let Some(type_sym) = type_ident.opt_symbol() {
721                    tuple_symbol.add_nested_type(type_sym.id());
722                }
723            }
724        }
725    }
726
727    // AST: dyn Trait or impl Trait
728    fn visit_abstract_type(
729        &mut self,
730        unit: &CompileUnit<'tcx>,
731        node: &HirNode<'tcx>,
732        scopes: &mut BinderScopes<'tcx>,
733        namespace: &'tcx Scope<'tcx>,
734        parent: Option<&Symbol>,
735    ) {
736        self.visit_children(unit, node, scopes, namespace, parent);
737    }
738
739    // AST: enum Variant or enum Variant { fields } or enum Variant(types)
740    #[tracing::instrument(skip_all)]
741    fn visit_enum_variant(
742        &mut self,
743        unit: &CompileUnit<'tcx>,
744        node: &HirNode<'tcx>,
745        scopes: &mut BinderScopes<'tcx>,
746        namespace: &'tcx Scope<'tcx>,
747        parent: Option<&Symbol>,
748    ) {
749        self.visit_struct_item(unit, node, scopes, namespace, parent);
750    }
751
752    // AST: object.field or tuple.0 (field access expression)
753    #[tracing::instrument(skip_all)]
754    fn visit_field_expression(
755        &mut self,
756        unit: &CompileUnit<'tcx>,
757        node: &HirNode<'tcx>,
758        scopes: &mut BinderScopes<'tcx>,
759        namespace: &'tcx Scope<'tcx>,
760        parent: Option<&Symbol>,
761    ) {
762        self.visit_children(unit, node, scopes, namespace, parent);
763
764        if let Some(value_sym) = node.ident_symbol_by_field(unit, LangRust::field_value)
765            && let Some(field_node) = node.child_by_field(unit, LangRust::field_field)
766        {
767            // numeric field access (tuple indexing like tuple.0)
768            if field_node.kind_id() == LangRust::integer_literal {
769                if let Some(field_sym) = field_node.ident_symbol(unit) {
770                    field_sym.set_field_of(value_sym.id());
771
772                    // try to resolve element type from tuple's nested_types
773                    if let Some(text) = field_node.as_text().map(|s| s.text()) {
774                        if let Ok(index) = text.parse::<usize>() {
775                            if let Some(value_type_id) = value_sym.type_of()
776                                && let Some(value_type) = unit.opt_get_symbol(value_type_id)
777                                && let Some(nested) = value_type.nested_types()
778                                && index < nested.len()
779                            {
780                                field_sym.set_type_of(nested[index]);
781                                tracing::trace!(
782                                    "tuple indexing: {} has type from nested_types[{}]",
783                                    field_sym.format(Some(unit.interner())),
784                                    index
785                                );
786                            }
787                        }
788                    }
789                }
790            }
791            // named field access (struct.field)
792            else if let Some(field_ident) = field_node.find_ident(unit) {
793                if let Some(field_sym) = field_ident.opt_symbol() {
794                    field_sym.set_field_of(value_sym.id());
795                }
796            }
797        }
798    }
799
800    // AST: path::to::identifier or module::item
801    // For nested paths like `crate_b::utils::helper`:
802    //   - path child is `scoped_identifier` (crate_b::utils)
803    //   - name child is `identifier` (helper)
804    // After visit_children, the path's name (utils) should have its symbol set.
805    fn visit_scoped_identifier(
806        &mut self,
807        unit: &CompileUnit<'tcx>,
808        node: &HirNode<'tcx>,
809        scopes: &mut BinderScopes<'tcx>,
810        namespace: &'tcx Scope<'tcx>,
811        parent: Option<&Symbol>,
812    ) {
813        self.visit_children(unit, node, scopes, namespace, parent);
814
815        let name_ident = node.ident_by_field(unit, LangRust::field_name);
816        let path_node = node.child_by_field(unit, LangRust::field_path);
817
818        if let Some(name_ident) = name_ident
819            && let Some(path_node) = path_node
820        {
821            // Get the path symbol - depends on whether path is a simple identifier or nested scoped_identifier
822            let path_sym = if path_node.kind_id() == LangRust::scoped_identifier {
823                // For nested scoped_identifier, the symbol is on the path's "name" field
824                // (which was resolved by the recursive visit_children call)
825                path_node
826                    .ident_by_field(unit, LangRust::field_name)
827                    .and_then(|i| i.opt_symbol())
828            } else if path_node.kind_id() == LangRust::identifier {
829                // For simple identifier path, get or resolve it
830                let path_ident = path_node.as_ident();
831                if let Some(ident) = path_ident {
832                    if let Some(sym) = ident.opt_symbol() {
833                        Some(sym)
834                    } else {
835                        // Path doesn't have a symbol yet - look it up
836                        let sym = scopes.lookup_symbol(ident.name, SymKindSet::empty());
837                        if let Some(s) = sym {
838                            ident.set_symbol(s);
839                        }
840                        sym
841                    }
842                } else {
843                    None
844                }
845            } else {
846                None
847            };
848
849            if let Some(path_sym) = path_sym {
850                if let Some(name_sym) = scopes.lookup_member_symbol(path_sym, name_ident.name, None)
851                {
852                    name_ident.set_symbol(name_sym);
853                }
854            }
855        }
856    }
857
858    // AST: fn foo(param: Type, ...)
859    #[tracing::instrument(skip_all)]
860    fn visit_parameter(
861        &mut self,
862        unit: &CompileUnit<'tcx>,
863        node: &HirNode<'tcx>,
864        scopes: &mut BinderScopes<'tcx>,
865        namespace: &'tcx Scope<'tcx>,
866        parent: Option<&Symbol>,
867    ) {
868        self.visit_children(unit, node, scopes, namespace, parent);
869
870        if let Some(type_node) = node.child_by_field(unit, LangRust::field_type)
871            && let Some(pattern) = node.child_by_field_recursive(unit, LangRust::field_pattern)
872        {
873            if let Some(type_sym) = infer_type(unit, scopes, &type_node) {
874                bind_pattern_types(unit, scopes, &pattern, type_sym);
875            }
876        }
877    }
878
879    // AST: fn method(&self) or fn method(&mut self) or fn method(self)
880    // The self parameter has implicit type of Self, which was bound to the struct during impl visit
881    fn visit_self_param(
882        &mut self,
883        unit: &CompileUnit<'tcx>,
884        node: &HirNode<'tcx>,
885        scopes: &mut BinderScopes<'tcx>,
886        namespace: &'tcx Scope<'tcx>,
887        parent: Option<&Symbol>,
888    ) {
889        self.visit_children(unit, node, scopes, namespace, parent);
890
891        // Look up the "self" TypeAlias symbol which was defined in the impl scope
892        // and has type_of pointing to the struct
893        if let Some(self_sym) =
894            scopes.lookup_symbol("self", SymKindSet::from_kind(SymKind::TypeAlias))
895        {
896            // Find the "self" identifier child and set its symbol
897            for &child_id in node.child_ids() {
898                let child = unit.hir_node(child_id);
899                if let Some(ident) = child.as_ident() {
900                    if ident.name == "self" {
901                        ident.set_symbol(self_sym);
902                    }
903                }
904            }
905        }
906    }
907
908    // AST: path::to::type or module::item
909    fn visit_scoped_type_identifier(
910        &mut self,
911        unit: &CompileUnit<'tcx>,
912        node: &HirNode<'tcx>,
913        scopes: &mut BinderScopes<'tcx>,
914        namespace: &'tcx Scope<'tcx>,
915        parent: Option<&Symbol>,
916    ) {
917        self.visit_scoped_identifier(unit, node, scopes, namespace, parent);
918    }
919
920    // AST: let name: Type = value; or let name = value;
921    #[tracing::instrument(skip_all)]
922    fn visit_let_declaration(
923        &mut self,
924        unit: &CompileUnit<'tcx>,
925        node: &HirNode<'tcx>,
926        scopes: &mut BinderScopes<'tcx>,
927        namespace: &'tcx Scope<'tcx>,
928        parent: Option<&Symbol>,
929    ) {
930        self.visit_children(unit, node, scopes, namespace, parent);
931
932        // Handle explicit type annotation: let x: Type = value;
933        // Use infer_type to handle composite types (tuples, arrays, etc.)
934        if let Some(type_node) = node.child_by_field(unit, LangRust::field_type)
935            && let Some(pattern) = node.child_by_field_recursive(unit, LangRust::field_pattern)
936        {
937            if let Some(type_sym) = infer_type(unit, scopes, &type_node) {
938                bind_pattern_types(unit, scopes, &pattern, type_sym);
939                return;
940            }
941            // Fallback to direct ident symbol lookup for simple types
942            if let Some(type_sym) = type_node.ident_symbol(unit) {
943                bind_pattern_types(unit, scopes, &pattern, type_sym);
944                return;
945            }
946        }
947
948        // Handle type inference from value: let x = value;
949        if let Some(value_node) = node.child_by_field(unit, LangRust::field_value)
950            && let Some(pattern) = node.child_by_field_recursive(unit, LangRust::field_pattern)
951            && let Some(type_sym) = infer_type(unit, scopes, &value_node)
952        {
953            bind_pattern_types(unit, scopes, &pattern, type_sym);
954            return;
955        }
956    }
957
958    // AST: Pattern { field1, field2 } or TupleVariant(a, b, c)
959    fn visit_tuple_struct_pattern(
960        &mut self,
961        unit: &CompileUnit<'tcx>,
962        node: &HirNode<'tcx>,
963        scopes: &mut BinderScopes<'tcx>,
964        namespace: &'tcx Scope<'tcx>,
965        parent: Option<&Symbol>,
966    ) {
967        self.visit_children(unit, node, scopes, namespace, parent);
968
969        let type_node = node.child_by_field(unit, LangRust::field_type);
970        if let Some(type_node) = type_node
971            && let Some(type_ident) = type_node.find_ident(unit)
972            // type_sym is the struct type
973            && let Some(type_sym) = type_ident.opt_symbol()
974        {
975            if type_sym.nested_types().is_some() {
976                for (i, child) in node.collect_idents(unit).into_iter().enumerate() {
977                    if let Some(child_sym) = child.opt_symbol()
978                        && let Some(nested_types) = type_sym.nested_types()
979                        && i >= 2
980                        && i < nested_types.len()
981                    {
982                        child_sym.set_type_of(nested_types[i]);
983                    }
984                }
985            }
986        }
987    }
988
989    // AST: StructName { field1: value1, field2: value2 }
990    fn visit_struct_expression(
991        &mut self,
992        unit: &CompileUnit<'tcx>,
993        node: &HirNode<'tcx>,
994        scopes: &mut BinderScopes<'tcx>,
995        namespace: &'tcx Scope<'tcx>,
996        parent: Option<&Symbol>,
997    ) {
998        self.visit_children(unit, node, scopes, namespace, parent);
999    }
1000
1001    // AST: match scrutinee { pattern1 => expr1, pattern2 => expr2 }
1002    #[tracing::instrument(skip_all)]
1003    fn visit_match_expression(
1004        &mut self,
1005        unit: &CompileUnit<'tcx>,
1006        node: &HirNode<'tcx>,
1007        scopes: &mut BinderScopes<'tcx>,
1008        namespace: &'tcx Scope<'tcx>,
1009        parent: Option<&Symbol>,
1010    ) {
1011        self.visit_children(unit, node, scopes, namespace, parent);
1012    }
1013
1014    // AST: match arm body or block in match expression
1015    #[tracing::instrument(skip_all)]
1016    fn visit_match_block(
1017        &mut self,
1018        unit: &CompileUnit<'tcx>,
1019        node: &HirNode<'tcx>,
1020        scopes: &mut BinderScopes<'tcx>,
1021        namespace: &'tcx Scope<'tcx>,
1022        parent: Option<&Symbol>,
1023    ) {
1024        self.visit_block(unit, node, scopes, namespace, parent);
1025    }
1026}
1027
1028/// Recursively extract all nested type arguments from a type node.
1029/// For `Result<User, Error>`, this yields [User, Error].
1030/// For `Vec<Vec<User>>`, this yields [Vec<User>, User] (outer Vec then inner).
1031fn extract_nested_types<'tcx>(
1032    unit: &CompileUnit<'tcx>,
1033    scopes: &BinderScopes<'tcx>,
1034    type_node: &HirNode<'tcx>,
1035    target_sym: &Symbol,
1036) {
1037    // Check if this is a generic type node
1038    if type_node.kind_id() == LangRust::generic_type {
1039        // Get the type_arguments child
1040        if let Some(type_args) = type_node.child_by_field(unit, LangRust::field_type_arguments) {
1041            for &child_id in type_args.child_ids() {
1042                let child = unit.hir_node(child_id);
1043                if child.is_trivia() || child.kind_id() == LangRust::lifetime {
1044                    continue;
1045                }
1046                // Infer the type of each type argument
1047                if let Some(type_arg_sym) = infer_type(unit, scopes, &child) {
1048                    target_sym.add_nested_type(type_arg_sym.id());
1049                }
1050                // Recursively extract from nested generics
1051                extract_nested_types(unit, scopes, &child, target_sym);
1052            }
1053        }
1054    }
1055    // Handle scoped type identifiers (e.g., std::result::Result<T, E>)
1056    else if type_node.kind_id() == LangRust::scoped_type_identifier {
1057        if let Some(type_args) = type_node.child_by_field(unit, LangRust::field_type_arguments) {
1058            for &child_id in type_args.child_ids() {
1059                let child = unit.hir_node(child_id);
1060                if child.is_trivia() || child.kind_id() == LangRust::lifetime {
1061                    continue;
1062                }
1063                if let Some(type_arg_sym) = infer_type(unit, scopes, &child) {
1064                    target_sym.add_nested_type(type_arg_sym.id());
1065                }
1066                extract_nested_types(unit, scopes, &child, target_sym);
1067            }
1068        }
1069    }
1070}
1071
1072pub fn bind_symbols<'tcx>(
1073    unit: CompileUnit<'tcx>,
1074    node: &HirNode<'tcx>,
1075    namespace: &'tcx Scope<'tcx>,
1076    config: &ResolverOption,
1077) {
1078    let mut scopes = BinderScopes::new(unit, namespace);
1079    let mut visit = BinderVisitor::new(config.clone());
1080    visit.visit_node(&unit, node, &mut scopes, namespace, None);
1081}