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 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 let ts_node = node.inner_ts_node();
357
358 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 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 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 if let Some(current) = this.current_symbol() {
389 if current.kind() == SymbolKind::Function {
390 let fqn = current.fqn_name.borrow();
391 if let Some(dot_pos) = fqn.rfind("::") {
393 let class_name = &fqn[..dot_pos];
394 let method_fqn = format!("{}::{}", class_name, name);
396 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 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 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 let parent_symbol = self.current_symbol();
601
602 if let Some(scope) = self.unit.opt_get_scope(node.hir_id()) {
603 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 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.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 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}