llmcc_python/
bind.rs

1use std::collections::HashSet;
2use std::path::{Path, PathBuf};
3use llmcc_core::context::CompileUnit;
4use llmcc_core::interner::InternedStr;
5use llmcc_core::ir::HirNode;
6use llmcc_core::symbol::{Scope, ScopeStack, Symbol, SymbolKind};
7
8use crate::token::{AstVisitorPython, LangPython};
9
10#[derive(Debug, Default)]
11pub struct BindingResult {
12    pub calls: Vec<CallBinding>,
13}
14
15#[derive(Debug, Clone)]
16pub struct CallBinding {
17    pub caller: String,
18    pub target: String,
19}
20
21#[derive(Debug)]
22struct SymbolBinder<'tcx> {
23    unit: CompileUnit<'tcx>,
24    scopes: ScopeStack<'tcx>,
25    calls: Vec<CallBinding>,
26    module_imports: Vec<&'tcx Symbol>,
27}
28
29impl<'tcx> SymbolBinder<'tcx> {
30    pub fn new(unit: CompileUnit<'tcx>, globals: &'tcx Scope<'tcx>) -> Self {
31        let mut scopes = ScopeStack::new(&unit.cc.arena, &unit.cc.interner, &unit.cc.symbol_map);
32        scopes.push(globals);
33        Self {
34            unit,
35            scopes,
36            calls: Vec::new(),
37            module_imports: Vec::new(),
38        }
39    }
40
41    fn interner(&self) -> &llmcc_core::interner::InternPool {
42        self.unit.interner()
43    }
44
45    fn current_symbol(&self) -> Option<&'tcx Symbol> {
46        self.scopes.scoped_symbol()
47    }
48
49    fn module_segments_from_path(path: &Path) -> Vec<String> {
50        if path.extension().and_then(|ext| ext.to_str()) != Some("py") {
51            return Vec::new();
52        }
53
54        let mut segments: Vec<String> = Vec::new();
55
56        if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
57            if stem != "__init__" && !stem.is_empty() {
58                segments.push(stem.to_string());
59            }
60        }
61
62        let mut current = path.parent();
63        while let Some(dir) = current {
64            let dir_name = match dir.file_name().and_then(|n| n.to_str()) {
65                Some(name) if !name.is_empty() => name.to_string(),
66                _ => break,
67            };
68
69            let has_init = dir.join("__init__.py").exists() || dir.join("__init__.pyi").exists();
70            if has_init {
71                segments.push(dir_name);
72                current = dir.parent();
73                continue;
74            }
75
76            if segments.is_empty() {
77                segments.push(dir_name);
78            }
79            break;
80        }
81
82        segments.reverse();
83        segments
84    }
85
86    fn ensure_module_symbol(&mut self, node: &HirNode<'tcx>) -> Option<&'tcx Symbol> {
87        let scope = self.unit.alloc_scope(node.hir_id());
88        if let Some(symbol) = scope.symbol() {
89            return Some(symbol);
90        }
91
92        let raw_path = self
93            .unit
94            .file_path()
95            .or_else(|| self.unit.file().path());
96        let path = raw_path
97            .map(PathBuf::from)
98            .and_then(|p| p.canonicalize().ok().or(Some(p)))
99            .unwrap_or_else(|| PathBuf::from("__module__"));
100
101        let segments = Self::module_segments_from_path(&path);
102        let interner = self.unit.interner();
103
104        let (name, fqn) = if segments.is_empty() {
105            let fallback = path
106                .file_stem()
107                .and_then(|s| s.to_str())
108                .unwrap_or("__module__")
109                .to_string();
110            (fallback.clone(), fallback)
111        } else {
112            let name = segments.last().cloned().unwrap_or_else(|| "__module__".to_string());
113            let fqn = segments.join("::");
114            (name, fqn)
115        };
116
117        let key = interner.intern(&name);
118        let symbol = Symbol::new(node.hir_id(), name.clone(), key);
119        let symbol = self.unit.cc.arena.alloc(symbol);
120        symbol.set_kind(SymbolKind::Module);
121        symbol.set_unit_index(self.unit.index);
122        symbol.set_fqn(fqn, interner);
123
124        self.unit
125            .cc
126            .symbol_map
127            .borrow_mut()
128            .insert(symbol.id, symbol);
129
130        let _ = self.scopes.insert_symbol(symbol, true);
131        scope.set_symbol(Some(symbol));
132        Some(symbol)
133    }
134
135    #[allow(dead_code)]
136    fn visit_children_scope(&mut self, node: &HirNode<'tcx>, symbol: Option<&'tcx Symbol>) {
137        let depth = self.scopes.depth();
138        if let Some(symbol) = symbol {
139            if let Some(parent) = self.scopes.scoped_symbol() {
140                parent.add_dependency(symbol);
141            }
142        }
143
144        let scope = self.unit.opt_get_scope(node.hir_id());
145        if let Some(scope) = scope {
146            self.scopes.push_with_symbol(scope, symbol);
147            self.visit_children(node);
148            self.scopes.pop_until(depth);
149        } else {
150            self.visit_children(node);
151        }
152    }
153
154    fn lookup_symbol_suffix(
155        &mut self,
156        suffix: &[InternedStr],
157        kind: Option<SymbolKind>,
158    ) -> Option<&'tcx Symbol> {
159        let file_index = self.unit.index;
160        self.scopes
161            .find_scoped_suffix_with_filters(suffix, kind, Some(file_index))
162            .or_else(|| {
163                self.scopes
164                    .find_scoped_suffix_with_filters(suffix, kind, None)
165            })
166            .or_else(|| {
167                self.scopes
168                    .find_global_suffix_with_filters(suffix, kind, Some(file_index))
169            })
170            .or_else(|| {
171                self.scopes
172                    .find_global_suffix_with_filters(suffix, kind, None)
173            })
174    }
175
176    fn add_symbol_relation(&mut self, symbol: Option<&'tcx Symbol>) {
177        let Some(target) = symbol else { return };
178        let Some(current) = self.current_symbol() else { return };
179
180        current.add_dependency(target);
181
182        match current.kind() {
183            SymbolKind::Function => {
184                let parent_class = self
185                    .scopes
186                    .iter()
187                    .rev()
188                    .filter_map(|scope| scope.symbol())
189                    .find(|symbol| symbol.kind() == SymbolKind::Struct);
190
191                if let Some(class_symbol) = parent_class {
192                    class_symbol.add_dependency(target);
193                }
194            }
195            SymbolKind::Module => {
196                if !self.module_imports.iter().any(|&sym| sym.id == target.id) {
197                    self.module_imports.push(target);
198                }
199            }
200            _ => {}
201        }
202    }
203
204    fn record_segments_dependency(&mut self, segments: &[String]) {
205        if segments.is_empty() {
206            return;
207        }
208
209        let interner = self.interner();
210        let suffix: Vec<_> = segments.iter().rev().map(|s| interner.intern(s)).collect();
211
212        let target = self
213            .lookup_symbol_suffix(&suffix, Some(SymbolKind::Struct))
214            .or_else(|| self.lookup_symbol_suffix(&suffix, Some(SymbolKind::Enum)))
215            .or_else(|| self.lookup_symbol_suffix(&suffix, Some(SymbolKind::Module)))
216            .or_else(|| self.lookup_symbol_suffix(&suffix, None));
217
218        self.add_symbol_relation(target);
219    }
220
221    fn build_attribute_path(&mut self, node: &HirNode<'tcx>, out: &mut Vec<String>) {
222        if node.kind_id() == LangPython::attribute {
223            if let Some(object_node) = node.opt_child_by_field(self.unit, LangPython::field_object)
224            {
225                self.build_attribute_path(&object_node, out);
226            }
227            if let Some(attr_node) =
228                node.opt_child_by_field(self.unit, LangPython::field_attribute)
229            {
230                if let Some(ident) = attr_node.as_ident() {
231                    out.push(ident.name.clone());
232                }
233            }
234        } else if node.kind_id() == LangPython::identifier {
235            if let Some(ident) = node.as_ident() {
236                out.push(ident.name.clone());
237            }
238        } else {
239            for child_id in node.children() {
240                let child = self.unit.hir_node(*child_id);
241                self.build_attribute_path(&child, out);
242            }
243        }
244    }
245
246    fn collect_identifier_paths(
247        &mut self,
248        node: &HirNode<'tcx>,
249        results: &mut Vec<Vec<String>>,
250    ) {
251        if node.kind_id() == LangPython::identifier {
252            if let Some(ident) = node.as_ident() {
253                results.push(vec![ident.name.clone()]);
254            }
255            return;
256        }
257
258        if node.kind_id() == LangPython::attribute {
259            let mut path = Vec::new();
260            self.build_attribute_path(node, &mut path);
261            if !path.is_empty() {
262                results.push(path);
263            }
264            return;
265        }
266
267        for child_id in node.children() {
268            let child = self.unit.hir_node(*child_id);
269            self.collect_identifier_paths(&child, results);
270        }
271    }
272
273    fn add_type_dependencies(&mut self, node: &HirNode<'tcx>) {
274        let mut paths = Vec::new();
275        self.collect_identifier_paths(node, &mut paths);
276
277        let mut seen = HashSet::new();
278        for path in paths {
279            if path.is_empty() {
280                continue;
281            }
282            let key = path.join("::");
283            if seen.insert(key) {
284                self.record_segments_dependency(&path);
285            }
286        }
287    }
288
289    fn record_import_path(&mut self, path: &str) {
290        let segments: Vec<String> = path
291            .split('.')
292            .filter(|segment| !segment.is_empty())
293            .map(|segment| segment.trim().to_string())
294            .collect();
295        if segments.is_empty() {
296            return;
297        }
298
299        let interner = self.interner();
300        let suffix: Vec<_> = segments.iter().rev().map(|s| interner.intern(s)).collect();
301
302        let target = self
303            .lookup_symbol_suffix(&suffix, Some(SymbolKind::Struct))
304            .or_else(|| self.lookup_symbol_suffix(&suffix, Some(SymbolKind::Enum)))
305            .or_else(|| self.lookup_symbol_suffix(&suffix, Some(SymbolKind::Module)))
306            .or_else(|| self.lookup_symbol_suffix(&suffix, None));
307
308        self.add_symbol_relation(target);
309    }
310
311    fn visit_children(&mut self, node: &HirNode<'tcx>) {
312        // Use HIR children instead of tree-sitter children
313        for child_id in node.children() {
314            let child = self.unit.hir_node(*child_id);
315            self.visit_node(child);
316        }
317    }
318
319    fn visit_decorated_def(&mut self, node: &HirNode<'tcx>) {
320        let mut decorator_symbols = Vec::new();
321        let mut definition_idx = None;
322
323        for (idx, child_id) in node.children().iter().enumerate() {
324            let child = self.unit.hir_node(*child_id);
325            let kind_id = child.kind_id();
326
327            if kind_id == LangPython::decorator {
328                let content = self.unit.file().content();
329                let ts_node = child.inner_ts_node();
330                if let Ok(decorator_text) = ts_node.utf8_text(&content) {
331                    let decorator_name = decorator_text.trim_start_matches('@').trim();
332                    let key = self.interner().intern(decorator_name);
333                    if let Some(decorator_symbol) =
334                        self.lookup_symbol_suffix(&[key], Some(SymbolKind::Function))
335                    {
336                        decorator_symbols.push(decorator_symbol);
337                    }
338                }
339            } else if kind_id == LangPython::function_definition
340                || kind_id == LangPython::class_definition
341            {
342                definition_idx = Some(idx);
343                break;
344            }
345        }
346
347        if let Some(idx) = definition_idx {
348            let definition_id = node.children()[idx];
349            let definition = self.unit.hir_node(definition_id);
350            self.visit_definition_node(&definition, &decorator_symbols);
351        }
352    }
353
354    fn visit_call_impl(&mut self, node: &HirNode<'tcx>) {
355        // Extract function being called
356        let ts_node = node.inner_ts_node();
357
358        // In tree-sitter-python, call has a `function` field
359        if let Some(func_node) = ts_node.child_by_field_name("function") {
360            let content = self.unit.file().content();
361            let record_target = |name: &str, this: &mut SymbolBinder<'tcx>| {
362                let key = this.interner().intern(name);
363
364                // First try to find in current scoped context (for method calls)
365                if let Some(target) = this.lookup_symbol_suffix(&[key], Some(SymbolKind::Function))
366                {
367                    this.add_symbol_relation(Some(target));
368                    let caller_name = this
369                        .current_symbol()
370                        .map(|s| s.fqn_name.borrow().clone())
371                        .unwrap_or_else(|| "<module>".to_string());
372                    let target_name = target.fqn_name.borrow().clone();
373                    this.calls.push(CallBinding {
374                        caller: caller_name,
375                        target: target_name,
376                    });
377                    return true;
378                }
379
380                // Try to find a struct (class) constructor call
381                if let Some(target) = this.lookup_symbol_suffix(&[key], Some(SymbolKind::Struct)) {
382                    this.add_symbol_relation(Some(target));
383                    return true;
384                }
385
386                // For method calls (self.method()), try looking up within parent class
387                // If current symbol is a method, parent is the class
388                if let Some(current) = this.current_symbol() {
389                    if current.kind() == SymbolKind::Function {
390                        let fqn = current.fqn_name.borrow();
391                        // Split "ClassName.method_name" to get class name
392                        if let Some(dot_pos) = fqn.rfind("::") {
393                            let class_name = &fqn[..dot_pos];
394                            // Build the method FQN: "ClassName.method_name"
395                            let method_fqn = format!("{}::{}", class_name, name);
396                            // Look up the method with no kind filter first
397                            if let Some(target) = this.scopes.find_global_suffix_with_filters(
398                                &[this.interner().intern(&method_fqn)],
399                                None,
400                                None,
401                            ) {
402                                if target.kind() == SymbolKind::Function {
403                                    this.add_symbol_relation(Some(target));
404                                    let caller_name = fqn.clone();
405                                    let target_name = target.fqn_name.borrow().clone();
406                                    this.calls.push(CallBinding {
407                                        caller: caller_name,
408                                        target: target_name,
409                                    });
410                                    return true;
411                                }
412                            }
413                        }
414                    }
415                }
416
417                // If not found, try looking with no kind filter (generic lookup)
418                if let Some(target) = this.lookup_symbol_suffix(&[key], None) {
419                    this.add_symbol_relation(Some(target));
420                    if target.kind() == SymbolKind::Function {
421                        let caller_name = this
422                            .current_symbol()
423                            .map(|s| s.fqn_name.borrow().clone())
424                            .unwrap_or_else(|| "<module>".to_string());
425                        let target_name = target.fqn_name.borrow().clone();
426                        this.calls.push(CallBinding {
427                            caller: caller_name,
428                            target: target_name,
429                        });
430                    }
431                    return true;
432                }
433
434                false
435            };
436            let handled = match func_node.kind_id() {
437                id if id == LangPython::identifier => {
438                    if let Ok(name) = func_node.utf8_text(&content) {
439                        record_target(name, self)
440                    } else {
441                        false
442                    }
443                }
444                id if id == LangPython::attribute => {
445                    // For attribute access (e.g., self.method()), extract the method name
446                    if let Some(attr_node) = func_node.child_by_field_name("attribute") {
447                        if let Ok(name) = attr_node.utf8_text(&content) {
448                            record_target(name, self)
449                        } else {
450                            false
451                        }
452                    } else {
453                        false
454                    }
455                }
456                _ => false,
457            };
458
459            if !handled {
460                if let Ok(name) = func_node.utf8_text(&content) {
461                    let _ = record_target(name.trim(), self);
462                }
463            }
464        }
465
466        self.visit_children(node);
467    }
468
469    fn visit_definition_node(&mut self, node: &HirNode<'tcx>, decorator_symbols: &[&'tcx Symbol]) {
470        let kind_id = node.kind_id();
471        let name_node = match node.opt_child_by_field(self.unit, LangPython::field_name) {
472            Some(name) => name,
473            None => {
474                self.visit_children(node);
475                return;
476            }
477        };
478
479        let ident = match name_node.as_ident() {
480            Some(ident) => ident,
481            None => {
482                self.visit_children(node);
483                return;
484            }
485        };
486
487        let key = self.interner().intern(&ident.name);
488        let preferred_kind = if kind_id == LangPython::function_definition {
489            Some(SymbolKind::Function)
490        } else if kind_id == LangPython::class_definition {
491            Some(SymbolKind::Struct)
492        } else {
493            None
494        };
495
496        let mut symbol = preferred_kind
497            .and_then(|kind| self.lookup_symbol_suffix(&[key], Some(kind)))
498            .or_else(|| self.lookup_symbol_suffix(&[key], None));
499
500        let parent_symbol = self.current_symbol();
501
502        if let Some(scope) = self.unit.opt_get_scope(node.hir_id()) {
503            if symbol.is_none() {
504                symbol = scope.symbol();
505            }
506
507            let depth = self.scopes.depth();
508            self.scopes.push_with_symbol(scope, symbol);
509
510            if let Some(current_symbol) = self.current_symbol() {
511                if kind_id == LangPython::function_definition {
512                    if let Some(class_symbol) = parent_symbol {
513                        if class_symbol.kind() == SymbolKind::Struct {
514                            class_symbol.add_dependency(current_symbol);
515                        }
516                    }
517                } else if kind_id == LangPython::class_definition {
518                    self.add_base_class_dependencies(node, current_symbol);
519                }
520
521                for decorator_symbol in decorator_symbols {
522                    current_symbol.add_dependency(decorator_symbol);
523                }
524            }
525
526            self.visit_children(node);
527            self.scopes.pop_until(depth);
528        } else {
529            self.visit_children(node);
530        }
531    }
532
533    fn add_base_class_dependencies(&mut self, node: &HirNode<'tcx>, class_symbol: &Symbol) {
534        for child_id in node.children() {
535            let child = self.unit.hir_node(*child_id);
536            if child.kind_id() == LangPython::argument_list {
537                for base_id in child.children() {
538                    let base_node = self.unit.hir_node(*base_id);
539
540                    if let Some(ident) = base_node.as_ident() {
541                        let key = self.interner().intern(&ident.name);
542                        if let Some(base_symbol) =
543                            self.lookup_symbol_suffix(&[key], Some(SymbolKind::Struct))
544                        {
545                            class_symbol.add_dependency(base_symbol);
546                        }
547                    } else if base_node.kind_id() == LangPython::attribute {
548                        if let Some(attr_node) =
549                            base_node.inner_ts_node().child_by_field_name("attribute")
550                        {
551                            let content = self.unit.file().content();
552                            if let Ok(name) = attr_node.utf8_text(&content) {
553                                let key = self.interner().intern(name);
554                                if let Some(base_symbol) =
555                                    self.lookup_symbol_suffix(&[key], Some(SymbolKind::Struct))
556                                {
557                                    class_symbol.add_dependency(base_symbol);
558                                }
559                            }
560                        }
561                    }
562                }
563            }
564        }
565    }
566}
567
568impl<'tcx> AstVisitorPython<'tcx> for SymbolBinder<'tcx> {
569    fn unit(&self) -> CompileUnit<'tcx> {
570        self.unit
571    }
572
573    fn visit_source_file(&mut self, node: HirNode<'tcx>) {
574        self.module_imports.clear();
575        let module_symbol = self.ensure_module_symbol(&node);
576        self.visit_children_scope(&node, module_symbol);
577    }
578
579    fn visit_function_definition(&mut self, node: HirNode<'tcx>) {
580        let name_node = match node.opt_child_by_field(self.unit, LangPython::field_name) {
581            Some(n) => n,
582            None => {
583                self.visit_children(&node);
584                return;
585            }
586        };
587
588        let ident = match name_node.as_ident() {
589            Some(id) => id,
590            None => {
591                self.visit_children(&node);
592                return;
593            }
594        };
595
596        let key = self.interner().intern(&ident.name);
597        let mut symbol = self.lookup_symbol_suffix(&[key], Some(SymbolKind::Function));
598
599        // Get the parent symbol before pushing a new scope
600        let parent_symbol = self.current_symbol();
601
602        if let Some(scope) = self.unit.opt_get_scope(node.hir_id()) {
603            // If symbol not found by lookup, get it from the scope
604            if symbol.is_none() {
605                symbol = scope.symbol();
606            }
607
608            let depth = self.scopes.depth();
609            self.scopes.push_with_symbol(scope, symbol);
610
611            if let Some(current_symbol) = self.current_symbol() {
612                // If parent is a class, class depends on method
613                if let Some(parent) = parent_symbol {
614                    if parent.kind() == SymbolKind::Struct {
615                        parent.add_dependency(current_symbol);
616                    }
617                }
618            }
619
620            let is_init = ident.name.as_str() == "__init__";
621            if is_init {
622                self.visit_children(&node);
623            }
624            self.scopes.pop_until(depth);
625        } else {
626            if ident.name.as_str() == "__init__" {
627                self.visit_children(&node);
628            }
629        }
630    }
631
632    fn visit_class_definition(&mut self, node: HirNode<'tcx>) {
633        let name_node = match node.opt_child_by_field(self.unit, LangPython::field_name) {
634            Some(n) => n,
635            None => {
636                self.visit_children(&node);
637                return;
638            }
639        };
640
641        let ident = match name_node.as_ident() {
642            Some(id) => id,
643            None => {
644                self.visit_children(&node);
645                return;
646            }
647        };
648
649        let key = self.interner().intern(&ident.name);
650        let mut symbol = self.lookup_symbol_suffix(&[key], Some(SymbolKind::Struct));
651
652        if let Some(scope) = self.unit.opt_get_scope(node.hir_id()) {
653            // If symbol not found by lookup, get it from the scope
654            if symbol.is_none() {
655                symbol = scope.symbol();
656            }
657
658            let depth = self.scopes.depth();
659            self.scopes.push_with_symbol(scope, symbol);
660
661            if let Some(current_symbol) = self.current_symbol() {
662                self.add_base_class_dependencies(&node, current_symbol);
663                for import_symbol in &self.module_imports {
664                    current_symbol.add_dependency(import_symbol);
665                }
666            }
667
668            self.visit_children(&node);
669            self.scopes.pop_until(depth);
670        } else {
671            self.visit_children(&node);
672        }
673    }
674
675    fn visit_decorated_definition(&mut self, node: HirNode<'tcx>) {
676        self.visit_decorated_def(&node);
677    }
678
679    fn visit_block(&mut self, node: HirNode<'tcx>) {
680        self.visit_children_scope(&node, None);
681    }
682
683    fn visit_call(&mut self, node: HirNode<'tcx>) {
684        // Delegate to the existing visit_call method
685        self.visit_call_impl(&node);
686    }
687
688    fn visit_assignment(&mut self, node: HirNode<'tcx>) {
689        if let Some(type_node) = node.opt_child_by_field(self.unit, LangPython::field_type) {
690            self.add_type_dependencies(&type_node);
691        } else {
692            for child_id in node.children() {
693                let child = self.unit.hir_node(*child_id);
694                if child.kind_id() == LangPython::type_node {
695                    self.add_type_dependencies(&child);
696                }
697            }
698        }
699
700        self.visit_children(&node);
701    }
702
703    fn visit_import_statement(&mut self, node: HirNode<'tcx>) {
704        let content = self.unit.file().content();
705        let ts_node = node.inner_ts_node();
706        let mut cursor = ts_node.walk();
707
708        for child in ts_node.children(&mut cursor) {
709            match child.kind() {
710                "dotted_name" | "identifier" => {
711                    if let Ok(text) = child.utf8_text(&content) {
712                        self.record_import_path(text);
713                    }
714                }
715                "aliased_import" => {
716                    if let Some(name_node) = child.child_by_field_name("name") {
717                        if let Ok(text) = name_node.utf8_text(&content) {
718                            self.record_import_path(text);
719                        }
720                    }
721                }
722                _ => {}
723            }
724        }
725
726        self.visit_children(&node);
727    }
728
729    fn visit_import_from(&mut self, node: HirNode<'tcx>) {
730        let content = self.unit.file().content();
731        let ts_node = node.inner_ts_node();
732        let mut cursor = ts_node.walk();
733
734        for child in ts_node.children(&mut cursor) {
735            match child.kind() {
736                "dotted_name" | "identifier" => {
737                    if let Ok(text) = child.utf8_text(&content) {
738                        self.record_import_path(text);
739                    }
740                }
741                "aliased_import" => {
742                    if let Some(name_node) = child.child_by_field_name("name") {
743                        if let Ok(text) = name_node.utf8_text(&content) {
744                            self.record_import_path(text);
745                        }
746                    }
747                }
748                _ => {}
749            }
750        }
751
752        self.visit_children(&node);
753    }
754
755    fn visit_unknown(&mut self, node: HirNode<'tcx>) {
756        self.visit_children(&node);
757    }
758}
759
760pub fn bind_symbols<'tcx>(unit: CompileUnit<'tcx>, globals: &'tcx Scope<'tcx>) -> BindingResult {
761    let mut binder = SymbolBinder::new(unit, globals);
762
763    if let Some(file_start_id) = unit.file_start_hir_id() {
764        if let Some(root) = unit.opt_hir_node(file_start_id) {
765            binder.visit_children(&root);
766        }
767    }
768
769    BindingResult {
770        calls: binder.calls,
771    }
772}