1use llmcc_core::context::CompileUnit;
2use llmcc_core::interner::InternedStr;
3use llmcc_core::ir::HirNode;
4use llmcc_core::symbol::{Scope, ScopeStack, Symbol, SymbolKind};
5use std::collections::HashSet;
6use std::path::{Path, PathBuf};
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.unit.file_path().or_else(|| self.unit.file().path());
93 let path = raw_path
94 .map(PathBuf::from)
95 .and_then(|p| p.canonicalize().ok().or(Some(p)))
96 .unwrap_or_else(|| PathBuf::from("__module__"));
97
98 let segments = Self::module_segments_from_path(&path);
99 let interner = self.unit.interner();
100
101 let (name, fqn) = if segments.is_empty() {
102 let fallback = path
103 .file_stem()
104 .and_then(|s| s.to_str())
105 .unwrap_or("__module__")
106 .to_string();
107 (fallback.clone(), fallback)
108 } else {
109 let name = segments
110 .last()
111 .cloned()
112 .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 {
179 return;
180 };
181
182 current.add_dependency(target);
183
184 match current.kind() {
185 SymbolKind::Function => {
186 let parent_class = self
187 .scopes
188 .iter()
189 .rev()
190 .filter_map(|scope| scope.symbol())
191 .find(|symbol| symbol.kind() == SymbolKind::Struct);
192
193 if let Some(class_symbol) = parent_class {
194 class_symbol.add_dependency(target);
195 }
196 }
197 SymbolKind::Module => {
198 if !self.module_imports.iter().any(|&sym| sym.id == target.id) {
199 self.module_imports.push(target);
200 }
201 }
202 _ => {}
203 }
204 }
205
206 fn record_segments_dependency(&mut self, segments: &[String]) {
207 if segments.is_empty() {
208 return;
209 }
210
211 let interner = self.interner();
212 let suffix: Vec<_> = segments.iter().rev().map(|s| interner.intern(s)).collect();
213
214 let target = self
215 .lookup_symbol_suffix(&suffix, Some(SymbolKind::Struct))
216 .or_else(|| self.lookup_symbol_suffix(&suffix, Some(SymbolKind::Enum)))
217 .or_else(|| self.lookup_symbol_suffix(&suffix, Some(SymbolKind::Module)))
218 .or_else(|| self.lookup_symbol_suffix(&suffix, None));
219
220 self.add_symbol_relation(target);
221 }
222
223 fn build_attribute_path(&mut self, node: &HirNode<'tcx>, out: &mut Vec<String>) {
224 if node.kind_id() == LangPython::attribute {
225 if let Some(object_node) = node.opt_child_by_field(self.unit, LangPython::field_object)
226 {
227 self.build_attribute_path(&object_node, out);
228 }
229 if let Some(attr_node) = node.opt_child_by_field(self.unit, LangPython::field_attribute)
230 {
231 if let Some(ident) = attr_node.as_ident() {
232 out.push(ident.name.clone());
233 }
234 }
235 } else if node.kind_id() == LangPython::identifier {
236 if let Some(ident) = node.as_ident() {
237 out.push(ident.name.clone());
238 }
239 } else {
240 for child_id in node.children() {
241 let child = self.unit.hir_node(*child_id);
242 self.build_attribute_path(&child, out);
243 }
244 }
245 }
246
247 fn collect_identifier_paths(&mut self, node: &HirNode<'tcx>, results: &mut Vec<Vec<String>>) {
248 if node.kind_id() == LangPython::identifier {
249 if let Some(ident) = node.as_ident() {
250 results.push(vec![ident.name.clone()]);
251 }
252 return;
253 }
254
255 if node.kind_id() == LangPython::attribute {
256 let mut path = Vec::new();
257 self.build_attribute_path(node, &mut path);
258 if !path.is_empty() {
259 results.push(path);
260 }
261 return;
262 }
263
264 for child_id in node.children() {
265 let child = self.unit.hir_node(*child_id);
266 self.collect_identifier_paths(&child, results);
267 }
268 }
269
270 fn add_type_dependencies(&mut self, node: &HirNode<'tcx>) {
271 let mut paths = Vec::new();
272 self.collect_identifier_paths(node, &mut paths);
273
274 let mut seen = HashSet::new();
275 for path in paths {
276 if path.is_empty() {
277 continue;
278 }
279 let key = path.join("::");
280 if seen.insert(key) {
281 self.record_segments_dependency(&path);
282 }
283 }
284 }
285
286 fn record_import_path(&mut self, path: &str) {
287 let segments: Vec<String> = path
288 .split('.')
289 .filter(|segment| !segment.is_empty())
290 .map(|segment| segment.trim().to_string())
291 .collect();
292 if segments.is_empty() {
293 return;
294 }
295
296 let interner = self.interner();
297 let suffix: Vec<_> = segments.iter().rev().map(|s| interner.intern(s)).collect();
298
299 let target = self
300 .lookup_symbol_suffix(&suffix, Some(SymbolKind::Struct))
301 .or_else(|| self.lookup_symbol_suffix(&suffix, Some(SymbolKind::Enum)))
302 .or_else(|| self.lookup_symbol_suffix(&suffix, Some(SymbolKind::Module)))
303 .or_else(|| self.lookup_symbol_suffix(&suffix, None));
304
305 self.add_symbol_relation(target);
306 }
307
308 fn visit_children(&mut self, node: &HirNode<'tcx>) {
309 for child_id in node.children() {
311 let child = self.unit.hir_node(*child_id);
312 self.visit_node(child);
313 }
314 }
315
316 fn visit_decorated_def(&mut self, node: &HirNode<'tcx>) {
317 let mut decorator_symbols = Vec::new();
318 let mut definition_idx = None;
319
320 for (idx, child_id) in node.children().iter().enumerate() {
321 let child = self.unit.hir_node(*child_id);
322 let kind_id = child.kind_id();
323
324 if kind_id == LangPython::decorator {
325 let content = self.unit.file().content();
326 let ts_node = child.inner_ts_node();
327 if let Ok(decorator_text) = ts_node.utf8_text(&content) {
328 let decorator_name = decorator_text.trim_start_matches('@').trim();
329 let key = self.interner().intern(decorator_name);
330 if let Some(decorator_symbol) =
331 self.lookup_symbol_suffix(&[key], Some(SymbolKind::Function))
332 {
333 decorator_symbols.push(decorator_symbol);
334 }
335 }
336 } else if kind_id == LangPython::function_definition
337 || kind_id == LangPython::class_definition
338 {
339 definition_idx = Some(idx);
340 break;
341 }
342 }
343
344 if let Some(idx) = definition_idx {
345 let definition_id = node.children()[idx];
346 let definition = self.unit.hir_node(definition_id);
347 self.visit_definition_node(&definition, &decorator_symbols);
348 }
349 }
350
351 fn visit_call_impl(&mut self, node: &HirNode<'tcx>) {
352 let ts_node = node.inner_ts_node();
354
355 if let Some(func_node) = ts_node.child_by_field_name("function") {
357 let content = self.unit.file().content();
358 let record_target = |name: &str, this: &mut SymbolBinder<'tcx>| {
359 let key = this.interner().intern(name);
360
361 if let Some(target) = this.lookup_symbol_suffix(&[key], Some(SymbolKind::Function))
363 {
364 this.add_symbol_relation(Some(target));
365 let caller_name = this
366 .current_symbol()
367 .map(|s| s.fqn_name.borrow().clone())
368 .unwrap_or_else(|| "<module>".to_string());
369 let target_name = target.fqn_name.borrow().clone();
370 this.calls.push(CallBinding {
371 caller: caller_name,
372 target: target_name,
373 });
374 return true;
375 }
376
377 if let Some(target) = this.lookup_symbol_suffix(&[key], Some(SymbolKind::Struct)) {
379 this.add_symbol_relation(Some(target));
380 return true;
381 }
382
383 if let Some(current) = this.current_symbol() {
386 if current.kind() == SymbolKind::Function {
387 let fqn = current.fqn_name.borrow();
388 if let Some(dot_pos) = fqn.rfind("::") {
390 let class_name = &fqn[..dot_pos];
391 let method_fqn = format!("{}::{}", class_name, name);
393 if let Some(target) = this.scopes.find_global_suffix_with_filters(
395 &[this.interner().intern(&method_fqn)],
396 None,
397 None,
398 ) {
399 if target.kind() == SymbolKind::Function {
400 this.add_symbol_relation(Some(target));
401 let caller_name = fqn.clone();
402 let target_name = target.fqn_name.borrow().clone();
403 this.calls.push(CallBinding {
404 caller: caller_name,
405 target: target_name,
406 });
407 return true;
408 }
409 }
410 }
411 }
412 }
413
414 if let Some(target) = this.lookup_symbol_suffix(&[key], None) {
416 this.add_symbol_relation(Some(target));
417 if target.kind() == SymbolKind::Function {
418 let caller_name = this
419 .current_symbol()
420 .map(|s| s.fqn_name.borrow().clone())
421 .unwrap_or_else(|| "<module>".to_string());
422 let target_name = target.fqn_name.borrow().clone();
423 this.calls.push(CallBinding {
424 caller: caller_name,
425 target: target_name,
426 });
427 }
428 return true;
429 }
430
431 false
432 };
433 let handled = match func_node.kind_id() {
434 id if id == LangPython::identifier => {
435 if let Ok(name) = func_node.utf8_text(&content) {
436 record_target(name, self)
437 } else {
438 false
439 }
440 }
441 id if id == LangPython::attribute => {
442 if let Some(attr_node) = func_node.child_by_field_name("attribute") {
444 if let Ok(name) = attr_node.utf8_text(&content) {
445 record_target(name, self)
446 } else {
447 false
448 }
449 } else {
450 false
451 }
452 }
453 _ => false,
454 };
455
456 if !handled {
457 if let Ok(name) = func_node.utf8_text(&content) {
458 let _ = record_target(name.trim(), self);
459 }
460 }
461 }
462
463 self.visit_children(node);
464 }
465
466 fn visit_definition_node(&mut self, node: &HirNode<'tcx>, decorator_symbols: &[&'tcx Symbol]) {
467 let kind_id = node.kind_id();
468 let name_node = match node.opt_child_by_field(self.unit, LangPython::field_name) {
469 Some(name) => name,
470 None => {
471 self.visit_children(node);
472 return;
473 }
474 };
475
476 let ident = match name_node.as_ident() {
477 Some(ident) => ident,
478 None => {
479 self.visit_children(node);
480 return;
481 }
482 };
483
484 let key = self.interner().intern(&ident.name);
485 let preferred_kind = if kind_id == LangPython::function_definition {
486 Some(SymbolKind::Function)
487 } else if kind_id == LangPython::class_definition {
488 Some(SymbolKind::Struct)
489 } else {
490 None
491 };
492
493 let mut symbol = preferred_kind
494 .and_then(|kind| self.lookup_symbol_suffix(&[key], Some(kind)))
495 .or_else(|| self.lookup_symbol_suffix(&[key], None));
496
497 let parent_symbol = self.current_symbol();
498
499 if let Some(scope) = self.unit.opt_get_scope(node.hir_id()) {
500 if symbol.is_none() {
501 symbol = scope.symbol();
502 }
503
504 let depth = self.scopes.depth();
505 self.scopes.push_with_symbol(scope, symbol);
506
507 if let Some(current_symbol) = self.current_symbol() {
508 if kind_id == LangPython::function_definition {
509 if let Some(class_symbol) = parent_symbol {
510 if class_symbol.kind() == SymbolKind::Struct {
511 class_symbol.add_dependency(current_symbol);
512 }
513 }
514 } else if kind_id == LangPython::class_definition {
515 self.add_base_class_dependencies(node, current_symbol);
516 }
517
518 for decorator_symbol in decorator_symbols {
519 current_symbol.add_dependency(decorator_symbol);
520 }
521 }
522
523 self.visit_children(node);
524 self.scopes.pop_until(depth);
525 } else {
526 self.visit_children(node);
527 }
528 }
529
530 fn add_base_class_dependencies(&mut self, node: &HirNode<'tcx>, class_symbol: &Symbol) {
531 for child_id in node.children() {
532 let child = self.unit.hir_node(*child_id);
533 if child.kind_id() == LangPython::argument_list {
534 for base_id in child.children() {
535 let base_node = self.unit.hir_node(*base_id);
536
537 if let Some(ident) = base_node.as_ident() {
538 let key = self.interner().intern(&ident.name);
539 if let Some(base_symbol) =
540 self.lookup_symbol_suffix(&[key], Some(SymbolKind::Struct))
541 {
542 class_symbol.add_dependency(base_symbol);
543 }
544 } else if base_node.kind_id() == LangPython::attribute {
545 if let Some(attr_node) =
546 base_node.inner_ts_node().child_by_field_name("attribute")
547 {
548 let content = self.unit.file().content();
549 if let Ok(name) = attr_node.utf8_text(&content) {
550 let key = self.interner().intern(name);
551 if let Some(base_symbol) =
552 self.lookup_symbol_suffix(&[key], Some(SymbolKind::Struct))
553 {
554 class_symbol.add_dependency(base_symbol);
555 }
556 }
557 }
558 }
559 }
560 }
561 }
562 }
563}
564
565impl<'tcx> AstVisitorPython<'tcx> for SymbolBinder<'tcx> {
566 fn unit(&self) -> CompileUnit<'tcx> {
567 self.unit
568 }
569
570 fn visit_source_file(&mut self, node: HirNode<'tcx>) {
571 self.module_imports.clear();
572 let module_symbol = self.ensure_module_symbol(&node);
573 self.visit_children_scope(&node, module_symbol);
574 }
575
576 fn visit_function_definition(&mut self, node: HirNode<'tcx>) {
577 let name_node = match node.opt_child_by_field(self.unit, LangPython::field_name) {
578 Some(n) => n,
579 None => {
580 self.visit_children(&node);
581 return;
582 }
583 };
584
585 let ident = match name_node.as_ident() {
586 Some(id) => id,
587 None => {
588 self.visit_children(&node);
589 return;
590 }
591 };
592
593 let key = self.interner().intern(&ident.name);
594 let mut symbol = self.lookup_symbol_suffix(&[key], Some(SymbolKind::Function));
595
596 let parent_symbol = self.current_symbol();
598
599 if let Some(scope) = self.unit.opt_get_scope(node.hir_id()) {
600 if symbol.is_none() {
602 symbol = scope.symbol();
603 }
604
605 let depth = self.scopes.depth();
606 self.scopes.push_with_symbol(scope, symbol);
607
608 if let Some(current_symbol) = self.current_symbol() {
609 if let Some(parent) = parent_symbol {
611 if parent.kind() == SymbolKind::Struct {
612 parent.add_dependency(current_symbol);
613 }
614 }
615 }
616
617 self.visit_children(&node);
618 self.scopes.pop_until(depth);
619 } else {
620 self.visit_children(&node);
621 }
622 }
623
624 fn visit_class_definition(&mut self, node: HirNode<'tcx>) {
625 let name_node = match node.opt_child_by_field(self.unit, LangPython::field_name) {
626 Some(n) => n,
627 None => {
628 self.visit_children(&node);
629 return;
630 }
631 };
632
633 let ident = match name_node.as_ident() {
634 Some(id) => id,
635 None => {
636 self.visit_children(&node);
637 return;
638 }
639 };
640
641 let key = self.interner().intern(&ident.name);
642 let mut symbol = self.lookup_symbol_suffix(&[key], Some(SymbolKind::Struct));
643
644 if let Some(scope) = self.unit.opt_get_scope(node.hir_id()) {
645 if symbol.is_none() {
647 symbol = scope.symbol();
648 }
649
650 let depth = self.scopes.depth();
651 self.scopes.push_with_symbol(scope, symbol);
652
653 if let Some(current_symbol) = self.current_symbol() {
654 self.add_base_class_dependencies(&node, current_symbol);
655 for import_symbol in &self.module_imports {
656 current_symbol.add_dependency(import_symbol);
657 }
658 }
659
660 self.visit_children(&node);
661 self.scopes.pop_until(depth);
662 } else {
663 self.visit_children(&node);
664 }
665 }
666
667 fn visit_decorated_definition(&mut self, node: HirNode<'tcx>) {
668 self.visit_decorated_def(&node);
669 }
670
671 fn visit_block(&mut self, node: HirNode<'tcx>) {
672 self.visit_children_scope(&node, None);
673 }
674
675 fn visit_call(&mut self, node: HirNode<'tcx>) {
676 self.visit_call_impl(&node);
678 }
679
680 fn visit_assignment(&mut self, node: HirNode<'tcx>) {
681 if let Some(type_node) = node.opt_child_by_field(self.unit, LangPython::field_type) {
682 self.add_type_dependencies(&type_node);
683 } else {
684 for child_id in node.children() {
685 let child = self.unit.hir_node(*child_id);
686 if child.kind_id() == LangPython::type_node {
687 self.add_type_dependencies(&child);
688 }
689 }
690 }
691
692 self.visit_children(&node);
693 }
694
695 fn visit_import_statement(&mut self, node: HirNode<'tcx>) {
696 let content = self.unit.file().content();
697 let ts_node = node.inner_ts_node();
698 let mut cursor = ts_node.walk();
699
700 for child in ts_node.children(&mut cursor) {
701 match child.kind() {
702 "dotted_name" | "identifier" => {
703 if let Ok(text) = child.utf8_text(&content) {
704 self.record_import_path(text);
705 }
706 }
707 "aliased_import" => {
708 if let Some(name_node) = child.child_by_field_name("name") {
709 if let Ok(text) = name_node.utf8_text(&content) {
710 self.record_import_path(text);
711 }
712 }
713 }
714 _ => {}
715 }
716 }
717
718 self.visit_children(&node);
719 }
720
721 fn visit_import_from(&mut self, node: HirNode<'tcx>) {
722 let content = self.unit.file().content();
723 let ts_node = node.inner_ts_node();
724 let mut cursor = ts_node.walk();
725
726 for child in ts_node.children(&mut cursor) {
727 match child.kind() {
728 "dotted_name" | "identifier" => {
729 if let Ok(text) = child.utf8_text(&content) {
730 self.record_import_path(text);
731 }
732 }
733 "aliased_import" => {
734 if let Some(name_node) = child.child_by_field_name("name") {
735 if let Ok(text) = name_node.utf8_text(&content) {
736 self.record_import_path(text);
737 }
738 }
739 }
740 _ => {}
741 }
742 }
743
744 self.visit_children(&node);
745 }
746
747 fn visit_unknown(&mut self, node: HirNode<'tcx>) {
748 self.visit_children(&node);
749 }
750}
751
752pub fn bind_symbols<'tcx>(unit: CompileUnit<'tcx>, globals: &'tcx Scope<'tcx>) -> BindingResult {
753 let mut binder = SymbolBinder::new(unit, globals);
754
755 if let Some(file_start_id) = unit.file_start_hir_id() {
756 if let Some(root) = unit.opt_hir_node(file_start_id) {
757 binder.visit_children(&root);
758 }
759 }
760
761 BindingResult {
762 calls: binder.calls,
763 }
764}