llmcc_core/
scope.rs

1//! Scope management and symbol lookup for the code graph.
2use dashmap::DashMap;
3use parking_lot::RwLock;
4use std::collections::HashSet;
5use std::fmt;
6use std::sync::atomic::Ordering;
7
8use crate::interner::{InternPool, InternedStr};
9use crate::ir::{Arena, HirId};
10use crate::symbol::{NEXT_SCOPE_ID, ScopeId, SymId, SymKindSet, Symbol};
11
12/// Represents a single level in the scope hierarchy.
13pub struct Scope<'tcx> {
14    /// Unique monotonic scope ID.
15    id: ScopeId,
16    /// Map of interned symbol names to vectors of symbols (allows for overloading/shadowing within same scope).
17    /// Using DashMap for better concurrent read/write performance.
18    symbols: DashMap<InternedStr, Vec<&'tcx Symbol>>,
19    /// The HIR node that owns/introduces this scope.
20    owner: HirId,
21    /// The symbol that introduced this scope (e.g., function symbol).
22    symbol: RwLock<Option<&'tcx Symbol>>,
23    /// Parent scopes for inheritance (lexical chaining).
24    parents: RwLock<Vec<&'tcx Scope<'tcx>>>,
25    /// Child scopes nested within this scope.
26    #[allow(dead_code)]
27    children: RwLock<Vec<&'tcx Scope<'tcx>>>,
28    /// If this scope was merged into another, points to the target scope ID.
29    /// Used to redirect lookups from merged scopes to their parent scope.
30    redirect: RwLock<Option<ScopeId>>,
31    /// Optional interner for resolving symbol names during debugging.
32    interner: Option<&'tcx InternPool>,
33}
34
35impl<'tcx> Scope<'tcx> {
36    /// Creates a new scope owned by the given HIR node.
37    pub fn new(owner: HirId) -> Self {
38        Self::new_with(owner, None, None)
39    }
40
41    /// Creates a new scope owned by the given HIR node and associated with a symbol.
42    pub fn new_with(
43        owner: HirId,
44        symbol: Option<&'tcx Symbol>,
45        interner: Option<&'tcx InternPool>,
46    ) -> Self {
47        Self {
48            id: ScopeId(NEXT_SCOPE_ID.fetch_add(1, Ordering::Relaxed)),
49            symbols: DashMap::new(),
50            owner,
51            symbol: RwLock::new(symbol),
52            parents: RwLock::new(Vec::new()),
53            children: RwLock::new(Vec::new()),
54            redirect: RwLock::new(None),
55            interner,
56        }
57    }
58
59    /// Creates a new scope with specified shard count for the DashMap.
60    /// Use this for high-contention scopes like globals.
61    pub fn new_with_shards(
62        owner: HirId,
63        symbol: Option<&'tcx Symbol>,
64        interner: Option<&'tcx InternPool>,
65        shard_count: usize,
66    ) -> Self {
67        Self {
68            id: ScopeId(NEXT_SCOPE_ID.fetch_add(1, Ordering::Relaxed)),
69            symbols: DashMap::with_hasher_and_shard_amount(
70                std::hash::RandomState::new(),
71                shard_count,
72            ),
73            owner,
74            symbol: RwLock::new(symbol),
75            parents: RwLock::new(Vec::new()),
76            children: RwLock::new(Vec::new()),
77            redirect: RwLock::new(None),
78            interner,
79        }
80    }
81
82    /// Merge existing scope into this scope.
83    #[inline]
84    pub fn merge_with(&self, other: &'tcx Scope<'tcx>, _arena: &'tcx Arena<'tcx>) {
85        tracing::trace!(
86            "merge: from scope {:?} to {:?}, {} symbol entries",
87            other.id(),
88            self.id(),
89            other.symbols.len()
90        );
91
92        for entry in other.symbols.iter() {
93            let name_key = *entry.key();
94            let symbol_vec = entry.value().clone();
95            self.symbols.entry(name_key).or_default().extend(symbol_vec);
96        }
97    }
98
99    #[inline]
100    pub fn add_parent(&self, parent: &'tcx Scope<'tcx>) {
101        self.parents.write().push(parent);
102    }
103
104    #[inline]
105    pub fn owner(&self) -> HirId {
106        self.owner
107    }
108
109    #[inline]
110    pub fn set_symbol(&self, symbol: &'tcx Symbol) {
111        *self.symbol.write() = Some(symbol);
112    }
113
114    #[inline]
115    pub fn opt_symbol(&self) -> Option<&'tcx Symbol> {
116        *self.symbol.read()
117    }
118
119    /// Find a parent scope with a symbol of the given kind.
120    /// Walks up the parent chain (BFS) looking for a scope whose symbol matches.
121    pub fn find_parent_by_kind(&self, kind: crate::symbol::SymKind) -> Option<&'tcx Symbol> {
122        use std::collections::VecDeque;
123        let mut queue = VecDeque::new();
124        queue.extend(self.parents());
125
126        while let Some(parent) = queue.pop_front() {
127            if let Some(sym) = parent.opt_symbol()
128                && sym.kind() == kind
129            {
130                return Some(sym);
131            }
132            queue.extend(parent.parents());
133        }
134        None
135    }
136
137    #[inline]
138    pub fn id(&self) -> ScopeId {
139        self.id
140    }
141
142    /// If this scope was redirected (merged into another), get the target scope ID.
143    pub fn get_redirect(&self) -> Option<ScopeId> {
144        *self.redirect.read()
145    }
146
147    /// Set the redirect for this scope (used when merging scopes).
148    pub fn set_redirect(&self, target: ScopeId) {
149        *self.redirect.write() = Some(target);
150    }
151
152    #[inline]
153    pub fn parents(&self) -> Vec<&'tcx Scope<'tcx>> {
154        self.parents.read().clone()
155    }
156
157    /// Invokes a closure for each symbol in this scope.
158    /// Iterates in deterministic order by sorting keys.
159    pub fn for_each_symbol<F>(&self, mut visit: F)
160    where
161        F: FnMut(&'tcx Symbol),
162    {
163        // Collect keys for deterministic iteration order
164        let mut keys: Vec<_> = self.symbols.iter().map(|e| *e.key()).collect();
165        keys.sort();
166        for key in keys {
167            if let Some(symbol_vec) = self.symbols.get(&key) {
168                for symbol in symbol_vec.iter() {
169                    visit(symbol);
170                }
171            }
172        }
173    }
174
175    /// Inserts a symbol into this scope.
176    pub fn insert(&self, symbol: &'tcx Symbol) -> SymId {
177        self.symbols.entry(symbol.name).or_default().push(symbol);
178        symbol.id
179    }
180
181    pub fn lookup_symbols(
182        &self,
183        name: InternedStr,
184        options: LookupOptions,
185    ) -> Option<Vec<&'tcx Symbol>> {
186        let guard = self.symbols.get(&name)?;
187        let symbols = guard.value();
188
189        // Fast path: no filtering needed
190        if options.kind_filters.is_empty() && options.unit_filters.is_none() {
191            return Some(symbols.clone());
192        }
193
194        // Filter inline without cloning first
195        let filtered: Vec<&'tcx Symbol> = symbols
196            .iter()
197            .filter(|symbol| {
198                // O(1) bitset check instead of O(n) iteration
199                if !options.kind_filters.is_empty() && !options.kind_filters.contains(symbol.kind())
200                {
201                    return false;
202                }
203                if let Some(unit) = options.unit_filters
204                    && symbol.unit_index() != Some(unit)
205                {
206                    return false;
207                }
208                true
209            })
210            .copied()
211            .collect();
212
213        if filtered.is_empty() {
214            None
215        } else {
216            Some(filtered)
217        }
218    }
219}
220
221impl<'tcx> fmt::Debug for ScopeStack<'tcx> {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        let stack = self.stack.read();
224        let depth = stack.len();
225
226        // Create a vector of scope debug representations
227        let scopes_debug: Vec<_> = stack
228            .iter()
229            .map(|scope| {
230                let mut symbol_entries: Vec<String> = Vec::new();
231
232                scope.for_each_symbol(|s| {
233                    symbol_entries.push(s.format(Some(self.interner)));
234                });
235
236                (scope.id(), scope.owner, symbol_entries)
237            })
238            .collect();
239
240        f.debug_struct("ScopeStack")
241            .field("depth", &depth)
242            .field("scopes", &scopes_debug)
243            .finish()
244    }
245}
246
247impl<'tcx> fmt::Debug for Scope<'tcx> {
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        let symbol_desc = self.opt_symbol().cloned();
250        let mut symbol_entries: Vec<String> = Vec::new();
251
252        self.for_each_symbol(|s| {
253            symbol_entries.push(s.format(self.interner));
254        });
255
256        f.debug_struct("Scope")
257            .field("id", &self.id)
258            .field("owner", &self.owner)
259            .field("symbol", &symbol_desc)
260            .field("symbols", &symbol_entries)
261            .finish()
262    }
263}
264
265/// Manages a stack of nested scopes for symbol resolution and insertion.
266pub struct ScopeStack<'tcx> {
267    arena: &'tcx Arena<'tcx>,
268    interner: &'tcx InternPool,
269    /// Stack of nested scopes (Index 0 = Global, Index End = Current).
270    stack: RwLock<Vec<&'tcx Scope<'tcx>>>,
271}
272
273impl<'tcx> Clone for ScopeStack<'tcx> {
274    fn clone(&self) -> Self {
275        Self {
276            arena: self.arena,
277            interner: self.interner,
278            stack: RwLock::new(self.stack.read().clone()),
279        }
280    }
281}
282
283impl<'tcx> ScopeStack<'tcx> {
284    pub fn new(arena: &'tcx Arena<'tcx>, interner: &'tcx InternPool) -> Self {
285        Self {
286            arena,
287            interner,
288            stack: RwLock::new(Vec::new()),
289        }
290    }
291
292    #[inline]
293    pub fn depth(&self) -> usize {
294        self.stack.read().len()
295    }
296
297    #[inline]
298    pub fn push(&self, scope: &'tcx Scope<'tcx>) {
299        self.stack.write().push(scope);
300    }
301
302    #[inline]
303    pub fn globals(&self) -> &'tcx Scope<'tcx> {
304        self.stack.read().first().copied().unwrap()
305    }
306
307    #[inline]
308    pub fn top(&self) -> Option<&'tcx Scope<'tcx>> {
309        self.stack.read().last().copied()
310    }
311
312    /// Recursively pushes a scope and all its base (parent) scopes onto the stack.
313    pub fn push_recursive(&self, scope: &'tcx Scope<'tcx>) {
314        let mut candidates = vec![scope];
315        let mut linear_chain = Vec::new();
316        let mut visited = HashSet::new();
317
318        // Traverse parents graph to build a linear stack
319        while let Some(current) = candidates.pop() {
320            if !visited.insert(current.id()) {
321                continue;
322            }
323            linear_chain.push(current);
324
325            let parents = current.parents.read();
326            // Push parents in reverse order so the primary parent is processed last (LIFO)
327            for base in parents.iter().rev() {
328                if !visited.contains(&base.id()) {
329                    candidates.push(base);
330                }
331            }
332        }
333
334        // Apply to stack (reverse the chain so `scope` is at the top)
335        let mut stack = self.stack.write();
336        for s in linear_chain.iter().rev() {
337            stack.push(s);
338        }
339    }
340
341    #[inline]
342    pub fn pop(&self) -> Option<&'tcx Scope<'tcx>> {
343        self.stack.write().pop()
344    }
345
346    pub fn pop_until(&self, depth: usize) {
347        let mut stack = self.stack.write();
348        while stack.len() > depth {
349            stack.pop();
350        }
351    }
352
353    /// This should be the only pure api to lookup symbols following the
354    /// lexical scope backwards.
355    pub fn lookup_symbols(&self, name: &str, options: LookupOptions) -> Option<Vec<&'tcx Symbol>> {
356        if name.is_empty() {
357            return None;
358        }
359        let name_key = self.interner.intern(name);
360        let stack = self.stack.read();
361        tracing::trace!("stack: {:#?}", stack);
362
363        let symbols = stack.iter().rev().find_map(|scope| {
364            let result = scope.lookup_symbols(name_key, options);
365            if let Some(ref syms) = result {
366                tracing::trace!(
367                    "found '{}' in scope {:?}, symbols: {:?}",
368                    name,
369                    scope.id(),
370                    syms.iter()
371                        .map(|s| (s.id(), s.unit_index()))
372                        .collect::<Vec<_>>()
373                );
374            }
375            result
376        });
377        tracing::trace!(
378            "lookup_symbols: '{}' found {:?} in scope stack",
379            name,
380            symbols.as_ref().map(|syms| syms
381                .iter()
382                .map(|s| s.format(Some(self.interner)))
383                .collect::<Vec<_>>())
384        );
385        symbols
386    }
387
388    /// Normalize name helper.
389    fn normalize_name(&self, name: &str, force: bool) -> Option<InternedStr> {
390        let name_to_intern = if !name.is_empty() {
391            name
392        } else if force {
393            "___llmcc_anonymous___"
394        } else {
395            return None;
396        };
397        Some(self.interner.intern(name_to_intern))
398    }
399
400    /// Internal implementation for lookup/insert logic.
401    /// This is the only entry point to create a symbol
402    pub fn lookup_or_insert(
403        &self,
404        name: &str,
405        node: HirId,
406        options: LookupOptions,
407    ) -> Option<Vec<&'tcx Symbol>> {
408        let name_key = self.normalize_name(name, options.force)?;
409
410        let stack = self.stack.read();
411        if stack.is_empty() {
412            return None;
413        }
414
415        let scope = if options.global {
416            tracing::trace!("lookup_or_insert: '{}' in global scope", name);
417            stack.first().copied()?
418        } else if options.parent && stack.len() >= 2 {
419            tracing::trace!("lookup_or_insert: '{}' in parent scope", name);
420            stack.get(stack.len() - 2).copied()?
421        } else {
422            tracing::trace!("lookup_or_insert: '{}' in current scope", name);
423            stack.last().copied()?
424        };
425
426        // Pass through kind_filters to lookup to support kind-specific lookup/insert
427        let lookup_options = if !options.kind_filters.is_empty() {
428            LookupOptions::default().with_kind_set(options.kind_filters)
429        } else {
430            LookupOptions::default()
431        };
432        let symbols = scope.lookup_symbols(name_key, lookup_options);
433        if let Some(mut symbols) = symbols {
434            debug_assert!(!symbols.is_empty());
435
436            if options.chained {
437                tracing::debug!("chained symbol '{}' in scope {:?}", name, scope.id());
438                // create new symbol chained to existing one
439                let symbol = symbols.last().copied().unwrap();
440                let new_symbol = Symbol::new(node, name_key);
441                new_symbol.set_previous(symbol.id);
442                let sym_id = new_symbol.id().0;
443                let allocated = self.arena.alloc_with_id(sym_id, new_symbol);
444                scope.insert(allocated);
445                symbols.push(allocated);
446                Some(symbols)
447            } else {
448                tracing::trace!("found existing symbol '{}' in scope {:?}", name, scope.id());
449                Some(symbols)
450            }
451        } else {
452            tracing::trace!("create new symbol '{}' in scope {:?}", name, scope.id());
453            // not found, create new symbol
454            let new_symbol = Symbol::new(node, name_key);
455            let sym_id = new_symbol.id().0;
456            let allocated = self.arena.alloc_with_id(sym_id, new_symbol);
457            scope.insert(allocated);
458            Some(vec![allocated])
459        }
460    }
461
462    /// Lookup a qualified name like `crate::foo::bar` by resolving each part sequentially.
463    /// Starts from the global (first) scope and follows the scope chain through the symbol hierarchy.
464    pub fn lookup_qualified(
465        &self,
466        qualified_name: &[&str],
467        options: LookupOptions,
468    ) -> Option<Vec<&'tcx Symbol>> {
469        if qualified_name.is_empty() {
470            return None;
471        }
472
473        let stack = self.stack.read();
474        if stack.is_empty() {
475            return None;
476        }
477
478        // search in forward order to find the current scope where names[0] is defined
479        let name_key = self.interner.intern(qualified_name[0]);
480        let mut current_scope = *stack.first()?;
481        if options.shift_start {
482            for i in 0..stack.len() {
483                if stack[i].lookup_symbols(name_key, options).is_some() {
484                    current_scope = stack[i];
485                    break;
486                }
487            }
488            tracing::trace!(
489                "lookup_qualified: shifted start to scope {:?} for '{}'",
490                current_scope.id(),
491                qualified_name[0]
492            );
493        }
494
495        if current_scope.lookup_symbols(name_key, options).is_none() {
496            tracing::trace!(
497                "lookup_qualified: starting scope {:?} does not contain '{}' options: {:?}, stack {:#?}",
498                current_scope.id(),
499                qualified_name[0],
500                options,
501                stack
502            );
503            return None;
504        }
505
506        // Recursively try all symbol choices
507        self.lookup_qualified_recursive(current_scope, qualified_name, 0, &options)
508    }
509
510    /// Helper for recursive qualified lookup that tries all symbol choices.
511    fn lookup_qualified_recursive(
512        &self,
513        scope: &'tcx Scope<'tcx>,
514        qualified_name: &[&str],
515        index: usize,
516        options: &LookupOptions,
517    ) -> Option<Vec<&'tcx Symbol>> {
518        if index >= qualified_name.len() {
519            return None;
520        }
521
522        tracing::trace!(
523            "lookup_qualified_recursive: scope {:?}, part '{}'",
524            scope.id(),
525            qualified_name[index]
526        );
527        let part = qualified_name[index];
528        let name_key = self.interner.intern(part);
529        let symbols = scope.lookup_symbols(name_key, *options)?;
530        tracing::trace!(
531            "lookup_qualified_recursive: found {:?} symbols for '{}' in scope {:?}",
532            symbols
533                .iter()
534                .map(|s| s.format(Some(self.interner)))
535                .collect::<Vec<_>>(),
536            part,
537            scope.id()
538        );
539
540        // If this is the last part, return the symbols
541        if index == qualified_name.len() - 1 {
542            return Some(symbols);
543        }
544
545        // Try each symbol as a potential next scope in the hierarchy
546        let mut results = Vec::new();
547        for symbol in symbols {
548            if let Some(symbol_scope_id) = symbol.opt_scope() {
549                // Get scope from DashMap by ID (O(1) lookup)
550                if let Some(next_scope) = self.arena.get_scope(symbol_scope_id.0) {
551                    debug_assert!(next_scope.id() == symbol_scope_id);
552                    tracing::trace!(
553                        "lookup_qualified_recursive: descending into scope {:?} for symbol '{}'",
554                        next_scope.id(),
555                        symbol.format(Some(self.interner))
556                    );
557                    // Recursively try to find the rest of the path
558                    if let Some(result) = self.lookup_qualified_recursive(
559                        next_scope,
560                        qualified_name,
561                        index + 1,
562                        options,
563                    ) {
564                        results.extend(result);
565                    }
566                }
567            }
568        }
569
570        if results.is_empty() {
571            None
572        } else {
573            Some(results)
574        }
575    }
576}
577
578#[derive(Debug, Clone, Copy, Default)]
579pub struct LookupOptions {
580    pub global: bool,
581    pub parent: bool,
582    pub chained: bool,
583    pub force: bool,
584    pub shift_start: bool,
585    pub kind_filters: SymKindSet,
586    pub unit_filters: Option<usize>,
587}
588
589impl LookupOptions {
590    pub fn current() -> Self {
591        Self::default()
592    }
593
594    pub fn global() -> Self {
595        Self {
596            global: true,
597            ..Default::default()
598        }
599    }
600
601    pub fn parent() -> Self {
602        Self {
603            parent: true,
604            ..Default::default()
605        }
606    }
607
608    pub fn chained() -> Self {
609        Self {
610            chained: true,
611            ..Default::default()
612        }
613    }
614
615    pub fn anonymous() -> Self {
616        Self {
617            force: true,
618            ..Default::default()
619        }
620    }
621
622    pub fn with_global(mut self, global: bool) -> Self {
623        self.global = global;
624        self
625    }
626
627    pub fn with_parent(mut self, parent: bool) -> Self {
628        self.parent = parent;
629        self
630    }
631
632    pub fn with_chained(mut self, chained: bool) -> Self {
633        self.chained = chained;
634        self
635    }
636
637    pub fn with_force(mut self, force: bool) -> Self {
638        self.force = force;
639        self
640    }
641
642    pub fn with_shift_start(mut self, shift: bool) -> Self {
643        self.shift_start = shift;
644        self
645    }
646
647    pub fn with_kind_set(mut self, kind_set: SymKindSet) -> Self {
648        self.kind_filters = kind_set;
649        self
650    }
651
652    pub fn with_unit_filter(mut self, unit: usize) -> Self {
653        self.unit_filters = Some(unit);
654        self
655    }
656}