llmcc_resolver/
binder.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2use std::time::Instant;
3
4use llmcc_core::context::CompileUnit;
5use llmcc_core::interner::InternPool;
6use llmcc_core::ir::HirScope;
7use llmcc_core::scope::{LookupOptions, Scope, ScopeStack};
8use llmcc_core::symbol::{ScopeId, SymKind, SymKindSet, Symbol};
9use llmcc_core::{CompileCtxt, LanguageTraitImpl};
10
11use rayon::prelude::*;
12
13use crate::ResolverOption;
14
15#[derive(Debug)]
16pub struct BinderScopes<'a> {
17    unit: CompileUnit<'a>,
18    scopes: ScopeStack<'a>,
19}
20
21impl<'a> BinderScopes<'a> {
22    pub fn new(unit: CompileUnit<'a>, globals: &'a Scope<'a>) -> Self {
23        let scopes = ScopeStack::new(&unit.cc.arena, &unit.cc.interner);
24        scopes.push(globals);
25
26        Self { unit, scopes }
27    }
28
29    #[inline]
30    pub fn top(&self) -> &'a Scope<'a> {
31        self.scopes.top().unwrap()
32    }
33
34    #[inline]
35    pub fn unit(&self) -> CompileUnit<'a> {
36        self.unit
37    }
38
39    #[inline]
40    pub fn interner(&self) -> &InternPool {
41        self.unit.interner()
42    }
43
44    #[inline]
45    pub fn scopes(&self) -> &ScopeStack<'a> {
46        &self.scopes
47    }
48
49    #[inline]
50    pub fn scopes_mut(&mut self) -> &mut ScopeStack<'a> {
51        &mut self.scopes
52    }
53
54    /// Gets the current depth of the scope stack.
55    ///
56    /// - 0 means no scope has been pushed yet
57    /// - 1 means global scope is active
58    /// - 2+ means nested scopes are active
59    #[inline]
60    pub fn scope_depth(&self) -> usize {
61        self.scopes.depth()
62    }
63
64    /// Pushes a scope onto the stack by looking it up from the compilation unit.
65    pub fn push_scope(&mut self, id: ScopeId) {
66        tracing::trace!("push_scope: {:?}", id);
67        let scope = self.unit.get_scope(id);
68        self.scopes.push(scope);
69    }
70
71    /// Pushes a scope recursively with all its parent scopes.
72    pub fn push_scope_recursive(&mut self, id: ScopeId) {
73        tracing::trace!("push_scope_recursive: {:?}", id);
74        let scope = self.unit.get_scope(id);
75        self.scopes.push_recursive(scope);
76    }
77
78    /// Pushes the scope represented by a HirScope node.
79    pub fn push_scope_node(&mut self, sn: &'a HirScope<'a>) {
80        if sn.opt_ident().is_some() {
81            self.push_scope_recursive(sn.scope().id());
82        } else {
83            self.push_scope(sn.scope().id());
84        }
85    }
86
87    /// Pops the current scope from the stack.
88    #[inline]
89    pub fn pop_scope(&mut self) {
90        tracing::trace!("pop_scope: depth {}", self.scopes.depth());
91        self.scopes.pop();
92    }
93
94    /// Pops scopes until reaching the specified depth.
95    #[inline]
96    pub fn pop_until(&mut self, depth: usize) {
97        tracing::trace!("pop_until: {} -> {}", self.scopes.depth(), depth);
98        self.scopes.pop_until(depth);
99    }
100
101    /// Gets the global scope (always at index 0).
102    #[inline]
103    pub fn globals(&self) -> &'a Scope<'a> {
104        self.scopes.globals()
105    }
106
107    #[inline]
108    pub fn lookup_globals(&self, name: &str, kind_filters: SymKindSet) -> Option<Vec<&'a Symbol>> {
109        tracing::trace!("lookup globals '{}' with filters {:?}", name, kind_filters);
110        let options = LookupOptions::current().with_kind_set(kind_filters);
111        let name_key = self.unit.cc.interner.intern(name);
112        self.scopes.globals().lookup_symbols(name_key, options)
113    }
114
115    #[inline]
116    pub fn lookup_global(&self, name: &str, kind_filters: SymKindSet) -> Option<&'a Symbol> {
117        let symbols = self.lookup_globals(name, kind_filters)?;
118        if symbols.len() > 1 {
119            tracing::warn!(
120                "multiple global symbols found for '{}', returning the last one",
121                name
122            );
123        }
124        symbols.last().copied()
125    }
126
127    /// Lookup symbols by name with options
128    #[inline]
129    pub fn lookup_symbols(&self, name: &str, kind_filters: SymKindSet) -> Option<Vec<&'a Symbol>> {
130        tracing::trace!("lookup symbols '{}' with filters {:?}", name, kind_filters);
131        let options = LookupOptions::current().with_kind_set(kind_filters);
132        self.scopes.lookup_symbols(name, options)
133    }
134
135    #[inline]
136    pub fn lookup_symbol(&self, name: &str, kind_filters: SymKindSet) -> Option<&'a Symbol> {
137        let symbols = self.lookup_symbols(name, kind_filters)?;
138        if symbols.len() > 1 {
139            let current_unit = self.unit.index;
140            let current_crate_index = self.unit.unit_meta().crate_index;
141
142            // 1. Prefer symbols from the current unit (same file)
143            if let Some(local_sym) = symbols
144                .iter()
145                .find(|s| s.unit_index() == Some(current_unit))
146            {
147                tracing::trace!(
148                    "lookup_symbol: multiple found for '{}', preferring local symbol {:?}",
149                    name,
150                    local_sym.id()
151                );
152                return Some(*local_sym);
153            }
154
155            // 2. Prefer symbols from the same crate over cross-crate symbols
156            // Only filter if there are actually cross-crate symbols
157            let same_crate_symbols: Vec<_> = symbols
158                .iter()
159                .filter(|s| s.crate_index() == Some(current_crate_index))
160                .copied()
161                .collect();
162
163            if !same_crate_symbols.is_empty() && same_crate_symbols.len() < symbols.len() {
164                // There are both same-crate and cross-crate symbols, prefer same-crate
165                tracing::trace!(
166                    "lookup_symbol: multiple found for '{}', preferring same-crate symbol {:?}",
167                    name,
168                    same_crate_symbols.last().map(|s| s.id())
169                );
170                return same_crate_symbols.last().copied();
171            }
172
173            tracing::warn!(
174                "multiple symbols found for '{}', returning the last one",
175                name
176            );
177        }
178        symbols.last().copied()
179    }
180
181    /// Look up a member symbol in a type's scope.
182    pub fn lookup_member_symbols(
183        &self,
184        obj_type_symbol: &'a Symbol,
185        member_name: &str,
186        kind_filters: SymKindSet,
187    ) -> Option<&'a Symbol> {
188        if !kind_filters.is_empty() {
189            tracing::trace!(
190                "looking up member '{}' in type scope with filters {:?}",
191                member_name,
192                kind_filters
193            );
194            // Try each kind individually for priority ordering
195            for kind in [
196                SymKind::Method,
197                SymKind::Function,
198                SymKind::Field,
199                SymKind::Variable,
200                SymKind::Const,
201                SymKind::Static,
202            ] {
203                if kind_filters.contains(kind) {
204                    tracing::trace!("  filter: {:?}", kind);
205                    let sym = self.lookup_member_symbol(obj_type_symbol, member_name, Some(kind));
206                    if sym.is_some() {
207                        return sym;
208                    }
209                }
210            }
211        } else {
212            tracing::trace!("looking up member '{}' in type scope", member_name);
213        }
214        None
215    }
216
217    /// Look up a member symbol in a type's scope.
218    pub fn lookup_member_symbol(
219        &self,
220        obj_type_symbol: &'a Symbol,
221        member_name: &str,
222        kind_filter: Option<SymKind>,
223    ) -> Option<&'a Symbol> {
224        tracing::trace!(
225            "lookup_member_symbol: '{}' in {:?} (kind={:?})",
226            member_name,
227            obj_type_symbol.name,
228            obj_type_symbol.kind()
229        );
230
231        // For TypeAlias (like Self), follow type_of to get the actual type
232        let effective_symbol = if obj_type_symbol.kind() == SymKind::TypeAlias {
233            if let Some(type_of_id) = obj_type_symbol.type_of() {
234                let resolved = self
235                    .unit
236                    .cc
237                    .opt_get_symbol(type_of_id)
238                    .unwrap_or(obj_type_symbol);
239                tracing::trace!(
240                    "  -> followed type_of to {:?} (kind={:?})",
241                    resolved.name,
242                    resolved.kind()
243                );
244                resolved
245            } else {
246                obj_type_symbol
247            }
248        } else {
249            obj_type_symbol
250        };
251
252        let scope_id = effective_symbol.opt_scope()?;
253        let scope = self.unit.get_scope(scope_id);
254
255        // Create isolated scope stack for member lookup to avoid falling back to lexical scopes
256        let scopes = ScopeStack::new(&self.unit.cc.arena, &self.unit.cc.interner);
257        scopes.push_recursive(scope);
258
259        let options = if let Some(filter) = kind_filter {
260            LookupOptions::current().with_kind_set(SymKindSet::from_kind(filter))
261        } else {
262            LookupOptions::current()
263        };
264
265        scopes
266            .lookup_symbols(member_name, options)?
267            .into_iter()
268            .last()
269    }
270
271    /// Look up a qualified path (e.g., foo::Bar::baz) with optional kind filters.
272    pub fn lookup_qualified(
273        &self,
274        qualified_name: &[&str],
275        kind_filters: SymKindSet,
276    ) -> Option<Vec<&'a Symbol>> {
277        tracing::trace!(
278            "lookup qualified {:?} with kind_filters {:?}",
279            qualified_name,
280            kind_filters
281        );
282        let mut options = LookupOptions::default().with_shift_start(true);
283        if !kind_filters.is_empty() {
284            options = options.with_kind_set(kind_filters)
285        }
286        let symbols = self.scopes.lookup_qualified(qualified_name, options)?;
287        Some(symbols)
288    }
289
290    /// Look up a qualified path and apply same-crate preference for multi-crate scenarios.
291    pub fn lookup_qualified_symbol(
292        &self,
293        qualified_name: &[&str],
294        kind_filters: SymKindSet,
295    ) -> Option<&'a Symbol> {
296        let symbols = self.lookup_qualified(qualified_name, kind_filters)?;
297        let current_unit = self.unit.index;
298
299        tracing::trace!(
300            "lookup_qualified_symbol: '{:?}' found {} symbols, current_unit={}: {:?}",
301            qualified_name,
302            symbols.len(),
303            current_unit,
304            symbols
305                .iter()
306                .map(|s| (s.id(), s.unit_index()))
307                .collect::<Vec<_>>()
308        );
309
310        if symbols.len() > 1 {
311            // 1. Prefer symbols from the current unit (same file)
312            if let Some(local_sym) = symbols
313                .iter()
314                .find(|s| s.unit_index() == Some(current_unit))
315            {
316                tracing::trace!("  -> preferring local symbol {:?}", local_sym.id());
317                return Some(*local_sym);
318            }
319
320            // 2. Prefer symbols from the same crate using crate_index (O(1) check)
321            let current_crate_index = self.unit.unit_meta().crate_index;
322            if let Some(same_crate_sym) = symbols
323                .iter()
324                .find(|s| s.crate_index() == Some(current_crate_index))
325            {
326                tracing::trace!(
327                    "preferring same-crate symbol for qualified '{:?}' crate_index={}",
328                    qualified_name,
329                    current_crate_index
330                );
331                return Some(*same_crate_sym);
332            }
333
334            tracing::warn!(
335                "multiple symbols found for qualified '{:?}', returning the last one",
336                qualified_name
337            );
338        }
339        symbols.last().copied()
340    }
341}
342
343/// Bind symbols from all compilation units, optionally in parallel.
344///
345/// The binding phase resolves all symbol references and establishes relationships between symbols
346/// across compilation units. This happens after collection when all symbols have been discovered.
347pub fn bind_symbols_with<'a, L: LanguageTraitImpl>(
348    cc: &'a CompileCtxt<'a>,
349    globals: &'a Scope<'a>,
350    config: &ResolverOption,
351) {
352    let total_start = Instant::now();
353
354    tracing::info!("starting symbol binding for total {} units", cc.files.len());
355
356    // Atomic counter for parallel CPU time
357    let bind_cpu_time_ns = AtomicU64::new(0);
358
359    let bind_unit = |unit_index: usize| {
360        let bind_start = Instant::now();
361
362        tracing::debug!("binding symbols for unit {}", unit_index);
363        let unit = cc.compile_unit(unit_index);
364        let id = unit.file_root_id().unwrap();
365        let node = unit.hir_node(id);
366        L::bind_symbols(unit, node, globals, config);
367
368        bind_cpu_time_ns.fetch_add(bind_start.elapsed().as_nanos() as u64, Ordering::Relaxed);
369    };
370
371    let parallel_start = Instant::now();
372    if config.sequential {
373        tracing::debug!("running symbol binding sequentially");
374        (0..cc.files.len()).for_each(bind_unit);
375    } else {
376        tracing::debug!("running symbol binding in parallel");
377        (0..cc.files.len()).into_par_iter().for_each(bind_unit);
378    }
379    let parallel_time = parallel_start.elapsed();
380
381    let total_time = total_start.elapsed();
382    let bind_cpu_ms = bind_cpu_time_ns.load(Ordering::Relaxed) as f64 / 1_000_000.0;
383
384    tracing::info!(
385        "binding breakdown: parallel={:.2}ms (bind_cpu={:.2}ms), total={:.2}ms",
386        parallel_time.as_secs_f64() * 1000.0,
387        bind_cpu_ms,
388        total_time.as_secs_f64() * 1000.0,
389    );
390
391    tracing::info!("symbol binding complete");
392}