use std::path::Path;
use std::ptr;
use llmcc_core::context::CompileUnit;
use llmcc_core::interner::InternedStr;
use llmcc_core::ir::HirNode;
use llmcc_core::symbol::{Scope, ScopeStack, Symbol, SymbolKind};
use crate::descriptor::function::parse_type_expr;
use crate::descriptor::{CallDescriptor, CallTarget, TypeExpr};
use crate::token::{AstVisitorRust, LangRust};
#[derive(Debug)]
struct SymbolBinder<'tcx> {
unit: CompileUnit<'tcx>,
scopes: ScopeStack<'tcx>,
preferred_crate: Option<String>,
}
impl<'tcx> SymbolBinder<'tcx> {
pub fn new(unit: CompileUnit<'tcx>, globals: &'tcx Scope<'tcx>) -> Self {
let mut scopes = ScopeStack::new(&unit.cc.arena, &unit.cc.interner, &unit.cc.symbol_map);
scopes.push(globals);
let preferred_crate = unit
.file_path()
.or_else(|| unit.file().path())
.and_then(Self::extract_crate_key);
Self {
unit,
scopes,
preferred_crate,
}
}
fn interner(&self) -> &llmcc_core::interner::InternPool {
self.unit.interner()
}
fn current_symbol(&self) -> Option<&'tcx Symbol> {
self.scopes.scoped_symbol()
}
fn visit_children_scope(&mut self, node: HirNode<'tcx>, symbol: Option<&'tcx Symbol>) {
let depth = self.scopes.depth();
if let Some(symbol) = symbol {
if let Some(parent) = self.scopes.scoped_symbol() {
parent.add_dependency(symbol);
}
}
let scope = self.unit.opt_get_scope(node.hir_id());
if let Some(scope) = scope {
self.scopes.push_with_symbol(scope, symbol);
self.visit_children(&node);
self.scopes.pop_until(depth);
} else {
self.visit_children(&node);
}
}
fn lookup_symbol_suffix(
&mut self,
suffix: &[InternedStr],
kind: Option<SymbolKind>,
) -> Option<&'tcx Symbol> {
let file_index = self.unit.index;
self.lookup_in_local_scopes(suffix, kind, Some(file_index))
.or_else(|| self.lookup_in_local_scopes(suffix, kind, None))
.or_else(|| self.lookup_in_global_scope(suffix, kind, Some(file_index)))
.or_else(|| self.lookup_in_global_scope(suffix, kind, None))
}
fn find_symbol_from_field(
&mut self,
node: &HirNode<'tcx>,
field_id: u16,
expected: SymbolKind,
) -> Option<&'tcx Symbol> {
let child = node.opt_child_by_field(self.unit, field_id)?;
let ident = child.as_ident()?;
let key = self.interner().intern(&ident.name);
self.lookup_symbol_suffix(&[key], Some(expected))
}
fn add_symbol_relation(&mut self, symbol: Option<&'tcx Symbol>) {
if let (Some(current), Some(target)) = (self.current_symbol(), symbol) {
current.add_dependency(target);
}
}
fn add_symbol_relation_by_field(&mut self, node: &HirNode<'tcx>, field_id: u16) {
let symbol = self.find_symbol_from_field(node, field_id, SymbolKind::EnumVariant);
self.add_symbol_relation(symbol);
}
fn resolve_impl_target(&mut self, node: &HirNode<'tcx>) -> Option<&'tcx Symbol> {
let type_node = node.opt_child_by_field(self.unit, LangRust::field_type)?;
let segments = self.type_segments(&type_node)?;
self.resolve_symbol(&segments, None)
}
fn resolve_symbol(
&mut self,
segments: &[String],
kind: Option<SymbolKind>,
) -> Option<&'tcx Symbol> {
if segments.is_empty() {
return None;
}
let suffix: Vec<_> = segments
.iter()
.rev()
.map(|segment| self.interner().intern(segment))
.collect();
self.lookup_symbol_suffix(&suffix, kind)
}
fn resolve_method_symbol(&mut self, method: &str) -> Option<&'tcx Symbol> {
if let Some(symbol) = self.resolve_symbol(&[method.to_string()], Some(SymbolKind::Function))
{
return Some(symbol);
}
let owner_fqns: Vec<String> = self
.scopes
.iter()
.rev()
.filter_map(|scope| {
scope
.symbol()
.map(|symbol| symbol.fqn_name.borrow().clone())
})
.collect();
for fqn in owner_fqns {
if fqn.is_empty() {
continue;
}
let mut segments: Vec<String> = fqn
.split("::")
.filter(|segment| !segment.is_empty())
.map(|segment| segment.to_string())
.collect();
segments.push(method.to_string());
if let Some(symbol) = self.resolve_symbol(&segments, Some(SymbolKind::Function)) {
return Some(symbol);
}
}
None
}
fn resolve_type_symbol(&mut self, node: &HirNode<'tcx>) -> Option<&'tcx Symbol> {
let segments = self.type_segments(node)?;
self.resolve_symbol(&segments, Some(SymbolKind::Struct))
.or_else(|| self.resolve_symbol(&segments, Some(SymbolKind::Enum)))
}
fn type_segments(&mut self, node: &HirNode<'tcx>) -> Option<Vec<String>> {
let ts_node = node.inner_ts_node();
let expr = parse_type_expr(self.unit, ts_node);
extract_path_segments(&expr)
}
fn parse_type_expr_from_node(&self, node: &HirNode<'tcx>) -> TypeExpr {
let ts_node = node.inner_ts_node();
parse_type_expr(self.unit, ts_node)
}
fn collect_type_expr_symbols(&mut self, expr: &TypeExpr, symbols: &mut Vec<&'tcx Symbol>) {
match expr {
TypeExpr::Path { segments, generics } => {
let struct_symbol = self.resolve_symbol(segments, Some(SymbolKind::Struct));
let enum_symbol = if struct_symbol.is_none() {
self.resolve_symbol(segments, Some(SymbolKind::Enum))
} else {
None
};
let any_symbol = if struct_symbol.is_none() && enum_symbol.is_none() {
self.resolve_symbol(segments, None)
} else {
None
};
let symbol = struct_symbol.or(enum_symbol).or(any_symbol);
if let Some(symbol) = symbol {
if !symbols.iter().any(|existing| existing.id == symbol.id) {
symbols.push(symbol);
}
}
for generic in generics {
self.collect_type_expr_symbols(generic, symbols);
}
}
TypeExpr::Reference { inner, .. } => {
self.collect_type_expr_symbols(inner, symbols);
}
TypeExpr::Tuple(items) => {
for item in items {
self.collect_type_expr_symbols(item, symbols);
}
}
TypeExpr::ImplTrait { .. } | TypeExpr::Unknown(_) => {}
}
}
fn type_symbols_from_node(&mut self, node: &HirNode<'tcx>) -> Vec<&'tcx Symbol> {
let expr = self.parse_type_expr_from_node(node);
let mut symbols = Vec::new();
self.collect_type_expr_symbols(&expr, &mut symbols);
symbols
}
fn lookup_in_local_scopes(
&self,
suffix: &[InternedStr],
kind: Option<SymbolKind>,
file: Option<usize>,
) -> Option<&'tcx Symbol> {
let global_scope = self.scopes.iter().next();
for scope in self.scopes.iter().rev() {
if let Some(global) = global_scope {
if ptr::eq(scope, global) {
continue;
}
}
let symbols = scope.lookup_suffix_symbols(suffix);
if let Some(symbol) = self.select_matching_symbol(&symbols, kind, file) {
return Some(symbol);
}
}
None
}
fn lookup_in_global_scope(
&self,
suffix: &[InternedStr],
kind: Option<SymbolKind>,
file: Option<usize>,
) -> Option<&'tcx Symbol> {
if let Some(global_scope) = self.scopes.iter().next() {
let symbols = global_scope.lookup_suffix_symbols(suffix);
self.select_matching_symbol(&symbols, kind, file)
} else {
None
}
}
fn select_matching_symbol(
&self,
candidates: &[&'tcx Symbol],
kind: Option<SymbolKind>,
file: Option<usize>,
) -> Option<&'tcx Symbol> {
if candidates.is_empty() {
return None;
}
if let Some(kind) = kind {
if let Some(file) = file {
if let Some(symbol) = candidates
.iter()
.copied()
.find(|symbol| symbol.kind() == kind && symbol.unit_index() == Some(file))
{
return Some(symbol);
}
}
if let Some(preferred) = self.preferred_crate.as_ref() {
if let Some(symbol) = candidates
.iter()
.copied()
.find(|symbol| symbol.kind() == kind && self.symbol_in_crate(symbol, preferred))
{
return Some(symbol);
}
}
if let Some(symbol) = candidates
.iter()
.copied()
.find(|symbol| symbol.kind() == kind)
{
return Some(symbol);
}
return None;
}
if let Some(file) = file {
if let Some(symbol) = candidates
.iter()
.copied()
.find(|symbol| symbol.unit_index() == Some(file))
{
return Some(symbol);
}
}
if let Some(preferred) = self.preferred_crate.as_ref() {
if let Some(symbol) = candidates
.iter()
.copied()
.find(|symbol| self.symbol_in_crate(symbol, preferred))
{
return Some(symbol);
}
}
candidates.first().copied()
}
fn symbol_in_crate(&self, symbol: &'tcx Symbol, crate_key: &str) -> bool {
let Some(unit_index) = symbol.unit_index() else {
return false;
};
let path = self.unit.cc.file_path(unit_index);
let Some(path) = path else {
return false;
};
Self::extract_crate_key(path).as_deref() == Some(crate_key)
}
fn extract_crate_key(path: &str) -> Option<String> {
let p = Path::new(path);
for (idx, component) in p.components().enumerate() {
if component.as_os_str() == "crates" {
return p
.components()
.nth(idx + 1)
.map(|c| c.as_os_str().to_string_lossy().to_string());
}
}
p.parent()
.and_then(|parent| parent.file_name())
.map(|name| name.to_string_lossy().to_string())
}
fn record_call_target(&mut self, target: &CallTarget) {
match target {
CallTarget::Path { segments, .. } => {
if let Some(symbol) = self.resolve_symbol(segments, Some(SymbolKind::Function)) {
if symbol.kind() == SymbolKind::Function {
self.add_symbol_relation(Some(symbol));
self.add_type_dependency_for_segments(segments);
}
} else if segments.len() > 1 {
self.add_type_dependency_for_segments(segments);
}
}
CallTarget::Method { method, .. } => {
if let Some(symbol) = self.resolve_method_symbol(method) {
self.add_symbol_relation(Some(symbol));
}
}
CallTarget::Chain { base, segments } => {
if let Some(symbol) = self.resolve_path_text(base) {
self.add_symbol_relation(Some(symbol));
}
for segment in segments {
if let Some(symbol) = self.resolve_method_symbol(&segment.method) {
self.add_symbol_relation(Some(symbol));
}
}
}
CallTarget::Unknown(_) => {}
}
}
fn resolve_path_text(&mut self, text: &str) -> Option<&'tcx Symbol> {
if text.is_empty() {
return None;
}
let cleaned = text.split('<').next().unwrap_or(text);
let segments: Vec<String> = cleaned
.split("::")
.map(|segment| segment.trim().to_string())
.filter(|segment| !segment.is_empty())
.collect();
self.resolve_symbol(&segments, None)
}
fn add_type_dependency_for_segments(&mut self, segments: &[String]) {
if segments.len() <= 1 {
return;
}
let base_segments = &segments[..segments.len() - 1];
let struct_sym = self.resolve_symbol(base_segments, Some(SymbolKind::Struct));
let enum_sym = if struct_sym.is_none() {
self.resolve_symbol(base_segments, Some(SymbolKind::Enum))
} else {
None
};
if let Some(sym) = struct_sym.or(enum_sym) {
self.add_symbol_relation(Some(sym));
}
}
fn propagate_child_dependencies(&mut self, parent: &'tcx Symbol, child: &'tcx Symbol) {
let dependencies: Vec<_> = child.depends.borrow().iter().copied().collect();
for dep_id in dependencies {
if dep_id == parent.id {
continue;
}
if let Some(dep_symbol) = self.unit.opt_get_symbol(dep_id) {
if dep_symbol.kind() == SymbolKind::Function {
continue;
}
if dep_symbol.depends.borrow().contains(&parent.id) {
continue;
}
parent.add_dependency(dep_symbol);
}
}
}
}
impl<'tcx> AstVisitorRust<'tcx> for SymbolBinder<'tcx> {
fn unit(&self) -> CompileUnit<'tcx> {
self.unit
}
fn visit_source_file(&mut self, node: HirNode<'tcx>) {
self.visit_children_scope(node, None);
}
fn visit_mod_item(&mut self, node: HirNode<'tcx>) {
let symbol = self.find_symbol_from_field(&node, LangRust::field_name, SymbolKind::Module);
self.visit_children_scope(node, symbol);
}
fn visit_struct_item(&mut self, node: HirNode<'tcx>) {
let symbol = self.find_symbol_from_field(&node, LangRust::field_name, SymbolKind::Struct);
self.visit_children_scope(node, symbol);
}
fn visit_enum_item(&mut self, node: HirNode<'tcx>) {
let symbol = self.find_symbol_from_field(&node, LangRust::field_name, SymbolKind::Enum);
self.visit_children_scope(node, symbol);
}
fn visit_function_item(&mut self, node: HirNode<'tcx>) {
let symbol = self.find_symbol_from_field(&node, LangRust::field_name, SymbolKind::Function);
let parent_symbol = self.current_symbol();
if let Some(func_symbol) = symbol {
if let Some(return_type_node) =
node.opt_child_by_field(self.unit, LangRust::field_return_type)
{
let expr = self.parse_type_expr_from_node(&return_type_node);
let mut symbols = Vec::new();
self.collect_type_expr_symbols(&expr, &mut symbols);
for return_type_sym in symbols {
func_symbol.add_dependency(return_type_sym);
}
}
if let Some(parent_symbol) = parent_symbol {
let kind = parent_symbol.kind();
if matches!(kind, SymbolKind::Struct | SymbolKind::Enum) {
func_symbol.add_dependency(parent_symbol);
}
}
}
self.visit_children_scope(node, symbol);
if let (Some(parent_symbol), Some(func_symbol)) = (parent_symbol, symbol) {
if matches!(
parent_symbol.kind(),
SymbolKind::Struct | SymbolKind::Enum | SymbolKind::Impl
) {
self.propagate_child_dependencies(parent_symbol, func_symbol);
}
}
}
fn visit_impl_item(&mut self, node: HirNode<'tcx>) {
let symbol = self.resolve_impl_target(&node);
let trait_node = node.inner_ts_node();
if let Some(trait_ts) = trait_node.child_by_field_name("trait") {
let trait_segments = extract_segments_from_ts_node(self.unit, trait_ts);
if let Some(trait_symbol) = self.resolve_symbol(&trait_segments, None) {
if let Some(target_symbol) = symbol {
trait_symbol.add_dependency(target_symbol);
}
}
}
self.visit_children_scope(node, symbol);
}
fn visit_trait_item(&mut self, node: HirNode<'tcx>) {
let symbol = self.find_symbol_from_field(&node, LangRust::field_name, SymbolKind::Trait);
self.visit_children_scope(node, symbol);
}
fn visit_block(&mut self, node: HirNode<'tcx>) {
self.visit_children_scope(node, None);
}
fn visit_let_declaration(&mut self, node: HirNode<'tcx>) {
if let Some(type_node) = node.opt_child_by_field(self.unit, LangRust::field_type) {
for type_symbol in self.type_symbols_from_node(&type_node) {
self.add_symbol_relation(Some(type_symbol));
}
}
self.visit_children(&node);
}
fn visit_parameter(&mut self, node: HirNode<'tcx>) {
self.visit_children(&node);
}
fn visit_const_item(&mut self, node: HirNode<'tcx>) {
let symbol = self.find_symbol_from_field(&node, LangRust::field_name, SymbolKind::Const);
self.visit_children_scope(node, symbol);
}
fn visit_static_item(&mut self, node: HirNode<'tcx>) {
let symbol = self.find_symbol_from_field(&node, LangRust::field_name, SymbolKind::Static);
self.visit_children_scope(node, symbol);
}
fn visit_enum_variant(&mut self, node: HirNode<'tcx>) {
self.add_symbol_relation_by_field(&node, LangRust::field_name);
self.visit_children(&node);
}
fn visit_field_declaration(&mut self, node: HirNode<'tcx>) {
if let Some(type_node) = node.opt_child_by_field(self.unit, LangRust::field_type) {
for type_symbol in self.type_symbols_from_node(&type_node) {
self.add_symbol_relation(Some(type_symbol));
}
}
self.visit_children(&node);
}
fn visit_call_expression(&mut self, node: HirNode<'tcx>) {
let enclosing = self
.current_symbol()
.map(|symbol| symbol.fqn_name.borrow().clone());
let descriptor = CallDescriptor::from_call(self.unit, &node, enclosing);
self.record_call_target(&descriptor.target);
self.visit_children(&node);
}
fn visit_scoped_identifier(&mut self, node: HirNode<'tcx>) {
if self.identifier_is_call_function(&node) {
self.visit_children(&node);
return;
}
let text = node_text_simple(self.unit, &node);
let segments: Vec<String> = text
.split("::")
.filter(|segment| !segment.is_empty())
.map(|segment| segment.trim().to_string())
.collect();
let target = self
.resolve_symbol(&segments, Some(SymbolKind::Function))
.or_else(|| self.resolve_symbol(&segments, Some(SymbolKind::Struct)))
.or_else(|| self.resolve_symbol(&segments, Some(SymbolKind::Enum)));
self.add_symbol_relation(target);
self.visit_children(&node);
}
fn visit_type_identifier(&mut self, node: HirNode<'tcx>) {
if self.identifier_is_call_function(&node) {
self.visit_children(&node);
return;
}
if let Some(symbol) = self.resolve_type_symbol(&node) {
if matches!(symbol.kind(), SymbolKind::Struct | SymbolKind::Enum) {
if let Some(parent_id) = node.parent() {
let parent = self.unit.hir_node(parent_id);
if parent.kind_id() == LangRust::call_expression {
self.visit_children(&node);
return;
}
}
}
self.add_symbol_relation(Some(symbol));
}
self.visit_children(&node);
}
fn visit_identifier(&mut self, node: HirNode<'tcx>) {
let Some(ident) = node.as_ident() else {
self.visit_children(&node);
return;
};
let key = self.interner().intern(&ident.name);
let symbol = if let Some(local) = self.scopes.find_symbol_local(&ident.name) {
Some(local)
} else {
self.scopes
.find_scoped_suffix_with_filters(&[key], None, Some(self.unit.index))
.or_else(|| {
self.scopes
.find_scoped_suffix_with_filters(&[key], None, None)
})
.or_else(|| {
self.scopes
.find_global_suffix_with_filters(&[key], None, Some(self.unit.index))
})
.or_else(|| {
self.scopes
.find_global_suffix_with_filters(&[key], None, None)
})
};
if let Some(sym) = symbol {
if !matches!(sym.kind(), SymbolKind::Struct | SymbolKind::Enum) {
self.add_symbol_relation(Some(sym));
}
}
}
fn visit_unknown(&mut self, node: HirNode<'tcx>) {
self.visit_children(&node);
}
}
impl<'tcx> SymbolBinder<'tcx> {
fn identifier_is_call_function(&self, node: &HirNode<'tcx>) -> bool {
let Some(parent_id) = node.parent() else {
return false;
};
let parent = self.unit.hir_node(parent_id);
if parent.kind_id() != LangRust::call_expression {
return false;
}
let parent_ts = parent.inner_ts_node();
let Some(function_ts) = parent_ts.child_by_field_name("function") else {
return false;
};
let node_ts = node.inner_ts_node();
function_ts.start_byte() == node_ts.start_byte()
&& function_ts.end_byte() == node_ts.end_byte()
}
}
pub fn bind_symbols<'tcx>(unit: CompileUnit<'tcx>, globals: &'tcx Scope<'tcx>) {
let root = unit.file_start_hir_id().unwrap();
let node = unit.hir_node(root);
let mut binder = SymbolBinder::new(unit, globals);
binder.visit_node(node);
}
fn extract_path_segments(expr: &TypeExpr) -> Option<Vec<String>> {
match expr {
TypeExpr::Path { segments, .. } => Some(segments.clone()),
TypeExpr::Reference { inner, .. } => extract_path_segments(inner),
TypeExpr::Tuple(items) if items.len() == 1 => extract_path_segments(&items[0]),
_ => None,
}
}
fn extract_segments_from_ts_node<'tcx>(
unit: CompileUnit<'tcx>,
node: tree_sitter::Node<'tcx>,
) -> Vec<String> {
let text = unit.file().get_text(node.start_byte(), node.end_byte());
text.split("::")
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
}
fn node_text_simple<'tcx>(unit: CompileUnit<'tcx>, node: &HirNode<'tcx>) -> String {
unit.file().get_text(node.start_byte(), node.end_byte())
}