Skip to main content

rustpython_codegen/
symboltable.rs

1/* Python code is pre-scanned for symbols in the ast.
2
3This ensures that global and nonlocal keywords are picked up.
4Then the compiler can use the symbol table to generate proper
5load and store instructions for names.
6
7Inspirational file: https://github.com/python/cpython/blob/main/Python/symtable.c
8*/
9
10use crate::{
11    IndexMap, IndexSet,
12    error::{CodegenError, CodegenErrorType},
13};
14use alloc::{borrow::Cow, fmt};
15use bitflags::bitflags;
16use ruff_python_ast as ast;
17use ruff_text_size::{Ranged, TextRange};
18use rustpython_compiler_core::{PositionEncoding, SourceFile, SourceLocation};
19
20/// Captures all symbols in the current scope, and has a list of sub-scopes in this scope.
21#[derive(Clone)]
22pub struct SymbolTable {
23    /// The name of this symbol table. Often the name of the class or function.
24    pub name: String,
25
26    /// The type of symbol table
27    pub typ: CompilerScope,
28
29    /// The line number in the source code where this symboltable begins.
30    pub line_number: u32,
31
32    // Return True if the block is a nested class or function
33    pub is_nested: bool,
34
35    /// A set of symbols present on this scope level.
36    pub symbols: IndexMap<String, Symbol>,
37
38    /// A list of sub-scopes in the order as found in the
39    /// AST nodes.
40    pub sub_tables: Vec<SymbolTable>,
41
42    /// Cursor pointing to the next sub-table to consume during compilation.
43    pub next_sub_table: usize,
44
45    /// Variable names in definition order (parameters first, then locals)
46    pub varnames: Vec<String>,
47
48    /// Whether this class scope needs an implicit __class__ cell
49    pub needs_class_closure: bool,
50
51    /// Whether this class scope needs an implicit __classdict__ cell
52    pub needs_classdict: bool,
53
54    /// Whether this type param scope can see the parent class scope
55    pub can_see_class_scope: bool,
56
57    /// Whether this scope contains yield/yield from (is a generator function)
58    pub is_generator: bool,
59
60    /// Whether this comprehension scope should be inlined (PEP 709)
61    /// True for list/set/dict comprehensions in non-generator expressions
62    pub comp_inlined: bool,
63
64    /// PEP 649: Reference to annotation scope for this block
65    /// Annotations are compiled as a separate `__annotate__` function
66    pub annotation_block: Option<Box<SymbolTable>>,
67
68    /// PEP 649: Whether this scope has conditional annotations
69    /// (annotations inside if/for/while/etc. blocks or at module level)
70    pub has_conditional_annotations: bool,
71
72    /// Whether `from __future__ import annotations` is active
73    pub future_annotations: bool,
74
75    /// Names of type parameters that should still be mangled in type param scopes.
76    /// When Some, only names in this set are mangled; other names are left unmangled.
77    /// Set on type param blocks for generic classes; inherited by non-class child scopes.
78    pub mangled_names: Option<IndexSet<String>>,
79}
80
81impl SymbolTable {
82    fn new(name: String, typ: CompilerScope, line_number: u32, is_nested: bool) -> Self {
83        Self {
84            name,
85            typ,
86            line_number,
87            is_nested,
88            symbols: IndexMap::default(),
89            sub_tables: vec![],
90            next_sub_table: 0,
91            varnames: Vec::new(),
92            needs_class_closure: false,
93            needs_classdict: false,
94            can_see_class_scope: false,
95            is_generator: false,
96            comp_inlined: false,
97            annotation_block: None,
98            has_conditional_annotations: false,
99            future_annotations: false,
100            mangled_names: None,
101        }
102    }
103
104    pub fn scan_program(
105        program: &ast::ModModule,
106        source_file: SourceFile,
107    ) -> SymbolTableResult<Self> {
108        let mut builder = SymbolTableBuilder::new(source_file);
109        builder.scan_statements(program.body.as_ref())?;
110        builder.finish()
111    }
112
113    pub fn scan_expr(
114        expr: &ast::ModExpression,
115        source_file: SourceFile,
116    ) -> SymbolTableResult<Self> {
117        let mut builder = SymbolTableBuilder::new(source_file);
118        builder.scan_expression(expr.body.as_ref(), ExpressionContext::Load)?;
119        builder.finish()
120    }
121
122    pub fn lookup(&self, name: &str) -> Option<&Symbol> {
123        self.symbols.get(name)
124    }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum CompilerScope {
129    Module,
130    Class,
131    Function,
132    AsyncFunction,
133    Lambda,
134    Comprehension,
135    TypeParams,
136    /// PEP 649: Annotation scope for deferred evaluation
137    Annotation,
138}
139
140impl fmt::Display for CompilerScope {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        match self {
143            Self::Module => write!(f, "module"),
144            Self::Class => write!(f, "class"),
145            Self::Function => write!(f, "function"),
146            Self::AsyncFunction => write!(f, "async function"),
147            Self::Lambda => write!(f, "lambda"),
148            Self::Comprehension => write!(f, "comprehension"),
149            Self::TypeParams => write!(f, "type parameter"),
150            Self::Annotation => write!(f, "annotation"),
151            // TODO missing types from the C implementation
152            // if self._table.type == _symtable.TYPE_TYPE_VAR_BOUND:
153            //     return "TypeVar bound"
154            // if self._table.type == _symtable.TYPE_TYPE_ALIAS:
155            //     return "type alias"
156        }
157    }
158}
159
160/// Indicator for a single symbol what the scope of this symbol is.
161/// The scope can be unknown, which is unfortunate, but not impossible.
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum SymbolScope {
164    Unknown,
165    Local,
166    GlobalExplicit,
167    GlobalImplicit,
168    Free,
169    Cell,
170}
171
172bitflags! {
173    #[derive(Copy, Clone, Debug, PartialEq)]
174    pub struct SymbolFlags: u16 {
175        const REFERENCED = 0x001;  // USE
176        const ASSIGNED = 0x002;    // DEF_LOCAL
177        const PARAMETER = 0x004;   // DEF_PARAM
178        const ANNOTATED = 0x008;   // DEF_ANNOT
179        const IMPORTED = 0x010;    // DEF_IMPORT
180        const NONLOCAL = 0x020;    // DEF_NONLOCAL
181        // indicates if the symbol gets a value assigned by a named expression in a comprehension
182        // this is required to correct the scope in the analysis.
183        const ASSIGNED_IN_COMPREHENSION = 0x040;
184        // indicates that the symbol is used a bound iterator variable. We distinguish this case
185        // from normal assignment to detect disallowed re-assignment to iterator variables.
186        const ITER = 0x080;
187        /// indicates that the symbol is a free variable in a class method from the scope that the
188        /// class is defined in, e.g.:
189        /// ```python
190        /// def foo(x):
191        ///     class A:
192        ///         def method(self):
193        ///             return x // is_free_class
194        /// ```
195        const FREE_CLASS = 0x100;  // DEF_FREE_CLASS
196        const GLOBAL = 0x200;      // DEF_GLOBAL
197        const COMP_ITER = 0x400;   // DEF_COMP_ITER
198        const COMP_CELL = 0x800;   // DEF_COMP_CELL
199        const TYPE_PARAM = 0x1000; // DEF_TYPE_PARAM
200        const BOUND = Self::ASSIGNED.bits() | Self::PARAMETER.bits() | Self::IMPORTED.bits() | Self::ITER.bits() | Self::TYPE_PARAM.bits();
201    }
202}
203
204/// A single symbol in a table. Has various properties such as the scope
205/// of the symbol, and also the various uses of the symbol.
206#[derive(Debug, Clone)]
207pub struct Symbol {
208    pub name: String,
209    pub scope: SymbolScope,
210    pub flags: SymbolFlags,
211}
212
213impl Symbol {
214    fn new(name: &str) -> Self {
215        Self {
216            name: name.to_owned(),
217            // table,
218            scope: SymbolScope::Unknown,
219            flags: SymbolFlags::empty(),
220        }
221    }
222
223    pub const fn is_global(&self) -> bool {
224        matches!(
225            self.scope,
226            SymbolScope::GlobalExplicit | SymbolScope::GlobalImplicit
227        )
228    }
229
230    pub const fn is_local(&self) -> bool {
231        matches!(self.scope, SymbolScope::Local | SymbolScope::Cell)
232    }
233
234    pub const fn is_bound(&self) -> bool {
235        self.flags.intersects(SymbolFlags::BOUND)
236    }
237}
238
239#[derive(Debug)]
240pub struct SymbolTableError {
241    error: String,
242    location: Option<SourceLocation>,
243}
244
245impl SymbolTableError {
246    pub fn into_codegen_error(self, source_path: String) -> CodegenError {
247        CodegenError {
248            location: self.location,
249            error: CodegenErrorType::SyntaxError(self.error),
250            source_path,
251        }
252    }
253}
254
255type SymbolTableResult<T = ()> = Result<T, SymbolTableError>;
256
257impl core::fmt::Debug for SymbolTable {
258    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
259        write!(
260            f,
261            "SymbolTable({:?} symbols, {:?} sub scopes)",
262            self.symbols.len(),
263            self.sub_tables.len()
264        )
265    }
266}
267
268/* Perform some sort of analysis on nonlocals, globals etc..
269  See also: https://github.com/python/cpython/blob/main/Python/symtable.c#L410
270*/
271fn analyze_symbol_table(symbol_table: &mut SymbolTable) -> SymbolTableResult {
272    let mut analyzer = SymbolTableAnalyzer::default();
273    // Discard the newfree set at the top level - it's only needed for propagation
274    // Pass None for class_entry at top level
275    let _newfree = analyzer.analyze_symbol_table(symbol_table, None)?;
276    Ok(())
277}
278
279/* Drop __class__ and __classdict__ from free variables in class scope
280   and set the appropriate flags. Equivalent to CPython's drop_class_free().
281   See: https://github.com/python/cpython/blob/main/Python/symtable.c#L884
282
283   This function removes __class__ and __classdict__ from the
284   `newfree` set (which contains free variables collected from all child scopes)
285   and sets the corresponding flags on the class's symbol table entry.
286*/
287fn drop_class_free(symbol_table: &mut SymbolTable, newfree: &mut IndexSet<String>) {
288    // Check if __class__ is in the free variables collected from children
289    // If found, it means a child scope (method) references __class__
290    if newfree.shift_remove("__class__") {
291        symbol_table.needs_class_closure = true;
292    }
293
294    // Check if __classdict__ is in the free variables collected from children
295    if newfree.shift_remove("__classdict__") {
296        symbol_table.needs_classdict = true;
297    }
298
299    // Classes with function definitions need __classdict__ for PEP 649
300    // (but not when `from __future__ import annotations` is active)
301    if !symbol_table.needs_classdict && !symbol_table.future_annotations {
302        let has_functions = symbol_table.sub_tables.iter().any(|t| {
303            matches!(
304                t.typ,
305                CompilerScope::Function | CompilerScope::AsyncFunction
306            )
307        });
308        if has_functions {
309            symbol_table.needs_classdict = true;
310        }
311    }
312
313    // Check if __conditional_annotations__ is in the free variables collected from children
314    // Remove it from free set - it's handled specially in class scope
315    if newfree.shift_remove("__conditional_annotations__") {
316        symbol_table.has_conditional_annotations = true;
317    }
318}
319
320/// Check if an expression contains an `await` node (shallow, not into nested scopes).
321fn expr_contains_await(expr: &ast::Expr) -> bool {
322    use ast::visitor::Visitor;
323    struct AwaitFinder(bool);
324    impl ast::visitor::Visitor<'_> for AwaitFinder {
325        fn visit_expr(&mut self, expr: &ast::Expr) {
326            if !self.0 {
327                if matches!(expr, ast::Expr::Await(_)) {
328                    self.0 = true;
329                } else {
330                    ast::visitor::walk_expr(self, expr);
331                }
332            }
333        }
334    }
335    let mut finder = AwaitFinder(false);
336    finder.visit_expr(expr);
337    finder.0
338}
339
340/// PEP 709: Merge symbols from an inlined comprehension into the parent scope.
341/// Matches symtable.c inline_comprehension().
342fn inline_comprehension(
343    parent_symbols: &mut SymbolMap,
344    comp: &SymbolTable,
345    comp_free: &mut IndexSet<String>,
346    inlined_cells: &mut IndexSet<String>,
347    parent_type: CompilerScope,
348) {
349    for (name, sub_symbol) in &comp.symbols {
350        // Skip the .0 parameter
351        if sub_symbol.flags.contains(SymbolFlags::PARAMETER) {
352            continue;
353        }
354
355        // Track inlined cells
356        if sub_symbol.scope == SymbolScope::Cell
357            || sub_symbol.flags.contains(SymbolFlags::COMP_CELL)
358        {
359            inlined_cells.insert(name.clone());
360        }
361
362        // Handle __class__ in ClassBlock
363        let scope = if sub_symbol.scope == SymbolScope::Free
364            && parent_type == CompilerScope::Class
365            && name == "__class__"
366        {
367            comp_free.swap_remove(name);
368            SymbolScope::GlobalImplicit
369        } else {
370            sub_symbol.scope
371        };
372
373        if let Some(existing) = parent_symbols.get(name) {
374            // Name exists in parent
375            if existing.is_bound() && parent_type != CompilerScope::Class {
376                // Check if the name is free in any child of the comprehension
377                let is_free_in_child = comp.sub_tables.iter().any(|child| {
378                    child
379                        .symbols
380                        .get(name)
381                        .is_some_and(|s| s.scope == SymbolScope::Free)
382                });
383                if !is_free_in_child {
384                    comp_free.swap_remove(name);
385                }
386            }
387        } else {
388            // Name doesn't exist in parent, copy from comprehension.
389            // Reset scope to Unknown so analyze_symbol will resolve it
390            // in the parent's context.
391            let mut symbol = sub_symbol.clone();
392            symbol.scope = if sub_symbol.is_bound() {
393                SymbolScope::Unknown
394            } else {
395                scope
396            };
397            parent_symbols.insert(name.clone(), symbol);
398        }
399    }
400}
401
402type SymbolMap = IndexMap<String, Symbol>;
403
404mod stack {
405    use alloc::vec::Vec;
406    use core::ptr::NonNull;
407    pub struct StackStack<T> {
408        v: Vec<NonNull<T>>,
409    }
410    impl<T> Default for StackStack<T> {
411        fn default() -> Self {
412            Self { v: Vec::new() }
413        }
414    }
415    impl<T> StackStack<T> {
416        /// Appends a reference to this stack for the duration of the function `f`. When `f`
417        /// returns, the reference will be popped off the stack.
418        #[cfg(feature = "std")]
419        pub fn with_append<F, R>(&mut self, x: &mut T, f: F) -> R
420        where
421            F: FnOnce(&mut Self) -> R,
422        {
423            self.v.push(x.into());
424            let res = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| f(self)));
425            self.v.pop();
426            res.unwrap_or_else(|x| std::panic::resume_unwind(x))
427        }
428
429        /// Appends a reference to this stack for the duration of the function `f`. When `f`
430        /// returns, the reference will be popped off the stack.
431        ///
432        /// Without std, panic cleanup is not guaranteed (no catch_unwind).
433        #[cfg(not(feature = "std"))]
434        pub fn with_append<F, R>(&mut self, x: &mut T, f: F) -> R
435        where
436            F: FnOnce(&mut Self) -> R,
437        {
438            self.v.push(x.into());
439            let result = f(self);
440            self.v.pop();
441            result
442        }
443
444        pub fn iter(&self) -> impl DoubleEndedIterator<Item = &T> + '_ {
445            self.as_ref().iter().copied()
446        }
447        pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> + '_ {
448            self.as_mut().iter_mut().map(|x| &mut **x)
449        }
450        // pub fn top(&self) -> Option<&T> {
451        //     self.as_ref().last().copied()
452        // }
453        // pub fn top_mut(&mut self) -> Option<&mut T> {
454        //     self.as_mut().last_mut().map(|x| &mut **x)
455        // }
456        pub fn len(&self) -> usize {
457            self.v.len()
458        }
459        pub fn is_empty(&self) -> bool {
460            self.len() == 0
461        }
462
463        pub fn as_ref(&self) -> &[&T] {
464            unsafe { &*(self.v.as_slice() as *const [NonNull<T>] as *const [&T]) }
465        }
466
467        pub fn as_mut(&mut self) -> &mut [&mut T] {
468            unsafe { &mut *(self.v.as_mut_slice() as *mut [NonNull<T>] as *mut [&mut T]) }
469        }
470    }
471}
472use stack::StackStack;
473
474/// Symbol table analysis. Can be used to analyze a fully
475/// build symbol table structure. It will mark variables
476/// as local variables for example.
477#[derive(Default)]
478#[repr(transparent)]
479struct SymbolTableAnalyzer {
480    tables: StackStack<(SymbolMap, CompilerScope)>,
481}
482
483impl SymbolTableAnalyzer {
484    /// Analyze a symbol table and return the set of free variables.
485    /// See symtable.c analyze_block().
486    /// class_entry: PEP 649 - enclosing class symbols for annotation scopes
487    fn analyze_symbol_table(
488        &mut self,
489        symbol_table: &mut SymbolTable,
490        class_entry: Option<&SymbolMap>,
491    ) -> SymbolTableResult<IndexSet<String>> {
492        let symbols = core::mem::take(&mut symbol_table.symbols);
493        let sub_tables = &mut *symbol_table.sub_tables;
494
495        let annotation_block = &mut symbol_table.annotation_block;
496
497        // PEP 649: Determine class_entry to pass to children
498        let is_class = symbol_table.typ == CompilerScope::Class;
499
500        // Clone class symbols if needed for child scopes with can_see_class_scope
501        let needs_class_symbols = (is_class
502            && (sub_tables.iter().any(|st| st.can_see_class_scope)
503                || annotation_block
504                    .as_ref()
505                    .is_some_and(|b| b.can_see_class_scope)))
506            || (!is_class
507                && class_entry.is_some()
508                && sub_tables.iter().any(|st| st.can_see_class_scope));
509
510        let class_symbols_clone = if is_class && needs_class_symbols {
511            Some(symbols.clone())
512        } else {
513            None
514        };
515
516        // Collect (child_free, is_inlined) pairs from child scopes.
517        // We need to process inlined comprehensions after the closure
518        // when we have access to symbol_table.symbols.
519        let mut child_frees: Vec<(IndexSet<String>, bool)> = Vec::new();
520        let mut annotation_free: Option<IndexSet<String>> = None;
521
522        let mut info = (symbols, symbol_table.typ);
523        self.tables.with_append(&mut info, |list| {
524            let inner_scope = unsafe { &mut *(list as *mut _ as *mut Self) };
525            for sub_table in sub_tables.iter_mut() {
526                let child_class_entry = if sub_table.can_see_class_scope {
527                    if is_class {
528                        class_symbols_clone.as_ref()
529                    } else {
530                        class_entry
531                    }
532                } else {
533                    None
534                };
535                let child_free = inner_scope.analyze_symbol_table(sub_table, child_class_entry)?;
536                child_frees.push((child_free, sub_table.comp_inlined));
537            }
538            // PEP 649: Analyze annotation block if present
539            if let Some(annotation_table) = annotation_block {
540                let ann_class_entry = if annotation_table.can_see_class_scope {
541                    if is_class {
542                        class_symbols_clone.as_ref()
543                    } else {
544                        class_entry
545                    }
546                } else {
547                    None
548                };
549                let child_free =
550                    inner_scope.analyze_symbol_table(annotation_table, ann_class_entry)?;
551                annotation_free = Some(child_free);
552            }
553            Ok(())
554        })?;
555
556        symbol_table.symbols = info.0;
557
558        // PEP 709: Process inlined comprehensions.
559        // Merge symbols from inlined comps into parent scope without bail-out.
560        let mut inlined_cells: IndexSet<String> = IndexSet::default();
561        let mut newfree = IndexSet::default();
562        for (idx, (mut child_free, is_inlined)) in child_frees.into_iter().enumerate() {
563            if is_inlined {
564                inline_comprehension(
565                    &mut symbol_table.symbols,
566                    &sub_tables[idx],
567                    &mut child_free,
568                    &mut inlined_cells,
569                    symbol_table.typ,
570                );
571            }
572            newfree.extend(child_free);
573        }
574        if let Some(ann_free) = annotation_free {
575            // Propagate annotation-scope free names to this scope so
576            // implicit class-scope cells (__classdict__/__conditional_annotations__)
577            // can be materialized by drop_class_free when needed.
578            newfree.extend(ann_free);
579        }
580
581        let sub_tables = &*symbol_table.sub_tables;
582
583        // Analyze symbols in current scope
584        for symbol in symbol_table.symbols.values_mut() {
585            self.analyze_symbol(symbol, symbol_table.typ, sub_tables, class_entry)?;
586
587            // Collect free variables from this scope
588            if symbol.scope == SymbolScope::Free || symbol.flags.contains(SymbolFlags::FREE_CLASS) {
589                newfree.insert(symbol.name.clone());
590            }
591        }
592
593        // PEP 709: Promote LOCAL to CELL and set COMP_CELL for inlined cell vars
594        for symbol in symbol_table.symbols.values_mut() {
595            if inlined_cells.contains(&symbol.name) {
596                if symbol.scope == SymbolScope::Local {
597                    symbol.scope = SymbolScope::Cell;
598                }
599                symbol.flags.insert(SymbolFlags::COMP_CELL);
600            }
601        }
602
603        // Handle class-specific implicit cells
604        if symbol_table.typ == CompilerScope::Class {
605            drop_class_free(symbol_table, &mut newfree);
606        }
607
608        Ok(newfree)
609    }
610
611    fn analyze_symbol(
612        &mut self,
613        symbol: &mut Symbol,
614        st_typ: CompilerScope,
615        sub_tables: &[SymbolTable],
616        class_entry: Option<&SymbolMap>,
617    ) -> SymbolTableResult {
618        if symbol
619            .flags
620            .contains(SymbolFlags::ASSIGNED_IN_COMPREHENSION)
621            && st_typ == CompilerScope::Comprehension
622        {
623            // propagate symbol to next higher level that can hold it,
624            // i.e., function or module. Comprehension is skipped and
625            // Class is not allowed and detected as error.
626            //symbol.scope = SymbolScope::Nonlocal;
627            self.analyze_symbol_comprehension(symbol, 0)?
628        } else {
629            match symbol.scope {
630                SymbolScope::Free => {
631                    if !self.tables.as_ref().is_empty() {
632                        let scope_depth = self.tables.as_ref().len();
633                        // check if the name is already defined in any outer scope
634                        if scope_depth < 2
635                            || self.found_in_outer_scope(&symbol.name, st_typ)
636                                != Some(SymbolScope::Free)
637                        {
638                            return Err(SymbolTableError {
639                                error: format!("no binding for nonlocal '{}' found", symbol.name),
640                                // TODO: accurate location info, somehow
641                                location: None,
642                            });
643                        }
644                        // Check if the nonlocal binding refers to a type parameter
645                        if symbol.flags.contains(SymbolFlags::NONLOCAL) {
646                            for (symbols, _typ) in self.tables.iter().rev() {
647                                if let Some(sym) = symbols.get(&symbol.name) {
648                                    if sym.flags.contains(SymbolFlags::TYPE_PARAM) {
649                                        return Err(SymbolTableError {
650                                            error: format!(
651                                                "nonlocal binding not allowed for type parameter '{}'",
652                                                symbol.name
653                                            ),
654                                            location: None,
655                                        });
656                                    }
657                                    if sym.is_bound() {
658                                        break;
659                                    }
660                                }
661                            }
662                        }
663                    } else {
664                        return Err(SymbolTableError {
665                            error: format!(
666                                "nonlocal {} defined at place without an enclosing scope",
667                                symbol.name
668                            ),
669                            // TODO: accurate location info, somehow
670                            location: None,
671                        });
672                    }
673                }
674                SymbolScope::GlobalExplicit | SymbolScope::GlobalImplicit => {
675                    // TODO: add more checks for globals?
676                }
677                SymbolScope::Local | SymbolScope::Cell => {
678                    // all is well
679                }
680                SymbolScope::Unknown => {
681                    // Try hard to figure out what the scope of this symbol is.
682                    let scope = if symbol.is_bound() {
683                        self.found_in_inner_scope(sub_tables, &symbol.name, st_typ)
684                            .unwrap_or(SymbolScope::Local)
685                    } else if let Some(scope) = self.found_in_outer_scope(&symbol.name, st_typ) {
686                        // If found in enclosing scope (function/TypeParams), use that
687                        scope
688                    } else if let Some(class_symbols) = class_entry
689                        && let Some(class_sym) = class_symbols.get(&symbol.name)
690                        && class_sym.is_bound()
691                        && class_sym.scope != SymbolScope::Free
692                    {
693                        // If name is bound in enclosing class, use GlobalImplicit
694                        // so it can be accessed via __classdict__
695                        SymbolScope::GlobalImplicit
696                    } else if self.tables.is_empty() {
697                        // Don't make assumptions when we don't know.
698                        SymbolScope::Unknown
699                    } else {
700                        // If there are scopes above we assume global.
701                        SymbolScope::GlobalImplicit
702                    };
703                    symbol.scope = scope;
704                }
705            }
706        }
707        Ok(())
708    }
709
710    fn found_in_outer_scope(&mut self, name: &str, st_typ: CompilerScope) -> Option<SymbolScope> {
711        let mut decl_depth = None;
712        for (i, (symbols, typ)) in self.tables.iter().rev().enumerate() {
713            if matches!(typ, CompilerScope::Module)
714                || matches!(typ, CompilerScope::Class if name != "__class__" && name != "__classdict__" && name != "__conditional_annotations__")
715            {
716                continue;
717            }
718
719            // PEP 649: Annotation scope is conceptually a sibling of the function,
720            // not a child. Skip the immediate parent function scope when looking
721            // for outer variables from annotation scope.
722            if st_typ == CompilerScope::Annotation
723                && i == 0
724                && matches!(
725                    typ,
726                    CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda
727                )
728            {
729                continue;
730            }
731
732            // __class__ and __classdict__ are implicitly declared in class scope
733            // This handles the case where nested scopes reference them
734            if (name == "__class__" || name == "__classdict__")
735                && matches!(typ, CompilerScope::Class)
736            {
737                decl_depth = Some(i);
738                break;
739            }
740
741            // __conditional_annotations__ is implicitly declared in class scope
742            // for classes with conditional annotations
743            if name == "__conditional_annotations__" && matches!(typ, CompilerScope::Class) {
744                decl_depth = Some(i);
745                break;
746            }
747
748            if let Some(sym) = symbols.get(name) {
749                match sym.scope {
750                    SymbolScope::GlobalExplicit => return Some(SymbolScope::GlobalExplicit),
751                    SymbolScope::GlobalImplicit => {}
752                    _ => {
753                        if sym.is_bound() {
754                            decl_depth = Some(i);
755                            break;
756                        }
757                    }
758                }
759            }
760        }
761
762        if let Some(decl_depth) = decl_depth {
763            // decl_depth is the number of tables between the current one and
764            // the one that declared the cell var
765            // For implicit class scope variables (__classdict__, __conditional_annotations__),
766            // only propagate free to annotation/type-param scopes, not regular functions.
767            // Regular method functions don't need these in their freevars.
768            let is_class_implicit =
769                name == "__classdict__" || name == "__conditional_annotations__";
770
771            for (table, typ) in self.tables.iter_mut().rev().take(decl_depth) {
772                if let CompilerScope::Class = typ {
773                    if let Some(free_class) = table.get_mut(name) {
774                        free_class.flags.insert(SymbolFlags::FREE_CLASS)
775                    } else {
776                        let mut symbol = Symbol::new(name);
777                        symbol.flags.insert(SymbolFlags::FREE_CLASS);
778                        symbol.scope = SymbolScope::Free;
779                        table.insert(name.to_owned(), symbol);
780                    }
781                } else if is_class_implicit
782                    && matches!(
783                        typ,
784                        CompilerScope::Function
785                            | CompilerScope::AsyncFunction
786                            | CompilerScope::Lambda
787                    )
788                {
789                    // Skip: don't add __classdict__/__conditional_annotations__
790                    // as free vars in regular functions — only annotation/type scopes need them
791                } else if !table.contains_key(name) {
792                    let mut symbol = Symbol::new(name);
793                    symbol.scope = SymbolScope::Free;
794                    table.insert(name.to_owned(), symbol);
795                }
796            }
797        }
798
799        decl_depth.map(|_| SymbolScope::Free)
800    }
801
802    fn found_in_inner_scope(
803        &self,
804        sub_tables: &[SymbolTable],
805        name: &str,
806        st_typ: CompilerScope,
807    ) -> Option<SymbolScope> {
808        sub_tables.iter().find_map(|st| {
809            // PEP 709: For inlined comprehensions, check their children
810            // instead of the comp itself (its symbols are merged into parent).
811            if st.comp_inlined {
812                return self.found_in_inner_scope(&st.sub_tables, name, st_typ);
813            }
814            let sym = st.symbols.get(name)?;
815            if sym.scope == SymbolScope::Free || sym.flags.contains(SymbolFlags::FREE_CLASS) {
816                if st_typ == CompilerScope::Class && name != "__class__" {
817                    None
818                } else {
819                    Some(SymbolScope::Cell)
820                }
821            } else if sym.scope == SymbolScope::GlobalExplicit && self.tables.is_empty() {
822                // the symbol is defined on the module level, and an inner scope declares
823                // a global that points to it
824                Some(SymbolScope::GlobalExplicit)
825            } else {
826                None
827            }
828        })
829    }
830
831    // Implements the symbol analysis and scope extension for names
832    // assigned by a named expression in a comprehension. See:
833    // https://github.com/python/cpython/blob/7b78e7f9fd77bb3280ee39fb74b86772a7d46a70/Python/symtable.c#L1435
834    fn analyze_symbol_comprehension(
835        &mut self,
836        symbol: &mut Symbol,
837        parent_offset: usize,
838    ) -> SymbolTableResult {
839        // when this is called, we expect to be in the direct parent scope of the scope that contains 'symbol'
840        let last = self.tables.iter_mut().rev().nth(parent_offset).unwrap();
841        let symbols = &mut last.0;
842        let table_type = last.1;
843
844        // it is not allowed to use an iterator variable as assignee in a named expression
845        if symbol.flags.contains(SymbolFlags::ITER) {
846            return Err(SymbolTableError {
847                error: format!(
848                    "assignment expression cannot rebind comprehension iteration variable {}",
849                    symbol.name
850                ),
851                // TODO: accurate location info, somehow
852                location: None,
853            });
854        }
855
856        match table_type {
857            CompilerScope::Module => {
858                symbol.scope = SymbolScope::GlobalImplicit;
859            }
860            CompilerScope::Class => {
861                // named expressions are forbidden in comprehensions on class scope
862                return Err(SymbolTableError {
863                    error: "assignment expression within a comprehension cannot be used in a class body".to_string(),
864                    // TODO: accurate location info, somehow
865                    location: None,
866                });
867            }
868            CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda => {
869                if let Some(parent_symbol) = symbols.get_mut(&symbol.name) {
870                    if let SymbolScope::Unknown = parent_symbol.scope {
871                        // this information is new, as the assignment is done in inner scope
872                        parent_symbol.flags.insert(SymbolFlags::ASSIGNED);
873                    }
874
875                    symbol.scope = if parent_symbol.is_global() {
876                        parent_symbol.scope
877                    } else {
878                        SymbolScope::Free
879                    };
880                } else {
881                    let mut cloned_sym = symbol.clone();
882                    cloned_sym.scope = SymbolScope::Cell;
883                    last.0.insert(cloned_sym.name.to_owned(), cloned_sym);
884                }
885            }
886            CompilerScope::Comprehension => {
887                // TODO check for conflicts - requires more context information about variables
888                match symbols.get_mut(&symbol.name) {
889                    Some(parent_symbol) => {
890                        // check if assignee is an iterator in top scope
891                        if parent_symbol.flags.contains(SymbolFlags::ITER) {
892                            return Err(SymbolTableError {
893                                error: format!(
894                                    "assignment expression cannot rebind comprehension iteration variable {}",
895                                    symbol.name
896                                ),
897                                location: None,
898                            });
899                        }
900
901                        // we synthesize the assignment to the symbol from inner scope
902                        parent_symbol.flags.insert(SymbolFlags::ASSIGNED); // more checks are required
903                    }
904                    None => {
905                        // extend the scope of the inner symbol
906                        // as we are in a nested comprehension, we expect that the symbol is needed
907                        // outside, too, and set it therefore to non-local scope. I.e., we expect to
908                        // find a definition on a higher level
909                        let mut cloned_sym = symbol.clone();
910                        cloned_sym.scope = SymbolScope::Free;
911                        last.0.insert(cloned_sym.name.to_owned(), cloned_sym);
912                    }
913                }
914
915                self.analyze_symbol_comprehension(symbol, parent_offset + 1)?;
916            }
917            CompilerScope::TypeParams => {
918                // Named expression in comprehension cannot be used in type params
919                return Err(SymbolTableError {
920                    error: "assignment expression within a comprehension cannot be used within the definition of a generic".to_string(),
921                    location: None,
922                });
923            }
924            CompilerScope::Annotation => {
925                // Named expression is not allowed in annotation scope
926                return Err(SymbolTableError {
927                    error: "named expression cannot be used within an annotation".to_string(),
928                    location: None,
929                });
930            }
931        }
932        Ok(())
933    }
934}
935
936#[derive(Clone, Copy, Debug)]
937enum SymbolUsage {
938    Global,
939    Nonlocal,
940    Used,
941    Assigned,
942    Imported,
943    AnnotationAssigned,
944    Parameter,
945    AnnotationParameter,
946    AssignedNamedExprInComprehension,
947    Iter,
948    TypeParam,
949}
950
951struct SymbolTableBuilder {
952    class_name: Option<String>,
953    // Scope stack.
954    tables: Vec<SymbolTable>,
955    future_annotations: bool,
956    source_file: SourceFile,
957    // Current scope's varnames being collected (temporary storage)
958    current_varnames: Vec<String>,
959    // Stack to preserve parent varnames when entering nested scopes
960    varnames_stack: Vec<Vec<String>>,
961    // Track if we're inside an iterable definition expression (for nested comprehensions)
962    in_iter_def_exp: bool,
963    // Track if we're inside an annotation (yield/await/named expr not allowed)
964    in_annotation: bool,
965    // Track if we're inside a type alias (yield/await/named expr not allowed)
966    in_type_alias: bool,
967    // Track if we're scanning an inner loop iteration target (not the first generator)
968    in_comp_inner_loop_target: bool,
969    // Scope info for error messages (e.g., "a TypeVar bound")
970    scope_info: Option<&'static str>,
971    // PEP 649: Track if we're inside a conditional block (if/for/while/etc.)
972    in_conditional_block: bool,
973}
974
975/// Enum to indicate in what mode an expression
976/// was used.
977/// In cpython this is stored in the AST, but I think this
978/// is not logical, since it is not context free.
979#[derive(Copy, Clone, PartialEq)]
980enum ExpressionContext {
981    Load,
982    Store,
983    Delete,
984    Iter,
985    IterDefinitionExp,
986}
987
988impl SymbolTableBuilder {
989    fn new(source_file: SourceFile) -> Self {
990        let mut this = Self {
991            class_name: None,
992            tables: vec![],
993            future_annotations: false,
994            source_file,
995            current_varnames: Vec::new(),
996            varnames_stack: Vec::new(),
997            in_iter_def_exp: false,
998            in_annotation: false,
999            in_type_alias: false,
1000            in_comp_inner_loop_target: false,
1001            scope_info: None,
1002            in_conditional_block: false,
1003        };
1004        this.enter_scope("top", CompilerScope::Module, 0);
1005        this
1006    }
1007
1008    fn finish(mut self) -> Result<SymbolTable, SymbolTableError> {
1009        assert_eq!(self.tables.len(), 1);
1010        let mut symbol_table = self.tables.pop().unwrap();
1011        // Save varnames for the top-level module scope
1012        symbol_table.varnames = self.current_varnames;
1013        // Propagate future_annotations to the symbol table
1014        symbol_table.future_annotations = self.future_annotations;
1015        analyze_symbol_table(&mut symbol_table)?;
1016        Ok(symbol_table)
1017    }
1018
1019    fn enter_scope(&mut self, name: &str, typ: CompilerScope, line_number: u32) {
1020        let is_nested = self
1021            .tables
1022            .last()
1023            .map(|table| {
1024                table.is_nested
1025                    || matches!(
1026                        table.typ,
1027                        CompilerScope::Function | CompilerScope::AsyncFunction
1028                    )
1029            })
1030            .unwrap_or(false);
1031        // Inherit mangled_names from parent for non-class scopes
1032        let inherited_mangled_names = self
1033            .tables
1034            .last()
1035            .and_then(|t| t.mangled_names.clone())
1036            .filter(|_| typ != CompilerScope::Class);
1037        let mut table = SymbolTable::new(name.to_owned(), typ, line_number, is_nested);
1038        table.future_annotations = self.future_annotations;
1039        table.mangled_names = inherited_mangled_names;
1040        self.tables.push(table);
1041        // Save parent's varnames and start fresh for the new scope
1042        self.varnames_stack
1043            .push(core::mem::take(&mut self.current_varnames));
1044    }
1045
1046    fn enter_type_param_block(
1047        &mut self,
1048        name: &str,
1049        line_number: u32,
1050        for_class: bool,
1051    ) -> SymbolTableResult {
1052        // Check if we're in a class scope
1053        let in_class = self
1054            .tables
1055            .last()
1056            .is_some_and(|t| t.typ == CompilerScope::Class);
1057
1058        self.enter_scope(name, CompilerScope::TypeParams, line_number);
1059
1060        // Set properties on the newly created type param scope
1061        if let Some(table) = self.tables.last_mut() {
1062            table.can_see_class_scope = in_class;
1063            // For generic classes, create mangled_names set so that only
1064            // type parameter names get mangled (not bases or other expressions)
1065            if for_class {
1066                table.mangled_names = Some(IndexSet::default());
1067            }
1068        }
1069
1070        // Add __classdict__ as a USE symbol in type param scope if in class
1071        if in_class {
1072            self.register_name("__classdict__", SymbolUsage::Used, TextRange::default())?;
1073        }
1074
1075        // Register .type_params as a SET symbol (it will be converted to cell variable later)
1076        self.register_name(".type_params", SymbolUsage::Assigned, TextRange::default())?;
1077
1078        Ok(())
1079    }
1080
1081    /// Pop symbol table and add to sub table of parent table.
1082    fn leave_scope(&mut self) {
1083        let mut table = self.tables.pop().unwrap();
1084        // Save the collected varnames to the symbol table
1085        table.varnames = core::mem::take(&mut self.current_varnames);
1086        self.tables.last_mut().unwrap().sub_tables.push(table);
1087        // Restore parent's varnames
1088        self.current_varnames = self.varnames_stack.pop().unwrap_or_default();
1089    }
1090
1091    /// Enter annotation scope (PEP 649)
1092    /// Creates or reuses the annotation block for the current scope
1093    fn enter_annotation_scope(&mut self, line_number: u32) {
1094        let current = self.tables.last_mut().unwrap();
1095        let can_see_class_scope =
1096            current.typ == CompilerScope::Class || current.can_see_class_scope;
1097        let has_conditional = current.has_conditional_annotations;
1098
1099        // Create annotation block if not exists
1100        if current.annotation_block.is_none() {
1101            let mut annotation_table = SymbolTable::new(
1102                "__annotate__".to_owned(),
1103                CompilerScope::Annotation,
1104                line_number,
1105                true, // is_nested
1106            );
1107            // Annotation scope in class can see class scope
1108            annotation_table.can_see_class_scope = can_see_class_scope;
1109            // Add 'format' parameter
1110            annotation_table.varnames.push("format".to_owned());
1111            current.annotation_block = Some(Box::new(annotation_table));
1112        }
1113
1114        // Take the annotation block and push to stack for processing
1115        let annotation_table = current.annotation_block.take().unwrap();
1116        self.tables.push(*annotation_table);
1117        // Save parent's varnames and seed with existing annotation varnames (e.g., "format")
1118        self.varnames_stack
1119            .push(core::mem::take(&mut self.current_varnames));
1120        self.current_varnames = self.tables.last().unwrap().varnames.clone();
1121
1122        if can_see_class_scope && !self.future_annotations {
1123            self.add_classdict_freevar();
1124            // Also add __conditional_annotations__ as free var if parent has conditional annotations
1125            if has_conditional {
1126                self.add_conditional_annotations_freevar();
1127            }
1128        }
1129    }
1130
1131    /// Leave annotation scope (PEP 649)
1132    /// Stores the annotation block back to parent instead of sub_tables
1133    fn leave_annotation_scope(&mut self) {
1134        let mut table = self.tables.pop().unwrap();
1135        // Save the collected varnames to the symbol table
1136        table.varnames = core::mem::take(&mut self.current_varnames);
1137        // Store back to parent's annotation_block (not sub_tables)
1138        let parent = self.tables.last_mut().unwrap();
1139        parent.annotation_block = Some(Box::new(table));
1140        // Restore parent's varnames
1141        self.current_varnames = self.varnames_stack.pop().unwrap_or_default();
1142    }
1143
1144    fn add_classdict_freevar(&mut self) {
1145        let table = self.tables.last_mut().unwrap();
1146        let name = "__classdict__";
1147        let symbol = table
1148            .symbols
1149            .entry(name.to_owned())
1150            .or_insert_with(|| Symbol::new(name));
1151        symbol.scope = SymbolScope::Free;
1152        symbol
1153            .flags
1154            .insert(SymbolFlags::REFERENCED | SymbolFlags::FREE_CLASS);
1155    }
1156
1157    fn add_conditional_annotations_freevar(&mut self) {
1158        let table = self.tables.last_mut().unwrap();
1159        let name = "__conditional_annotations__";
1160        let symbol = table
1161            .symbols
1162            .entry(name.to_owned())
1163            .or_insert_with(|| Symbol::new(name));
1164        symbol.scope = SymbolScope::Free;
1165        symbol
1166            .flags
1167            .insert(SymbolFlags::REFERENCED | SymbolFlags::FREE_CLASS);
1168    }
1169
1170    /// Walk up the scope chain to determine if we're inside an async function.
1171    /// Annotation and TypeParams scopes act as async barriers (always non-async).
1172    /// Comprehension scopes are transparent (inherit parent's async context).
1173    fn is_in_async_context(&self) -> bool {
1174        // Annotations are evaluated in a non-async scope even when
1175        // the enclosing function is async.
1176        if self.in_annotation {
1177            return false;
1178        }
1179        for table in self.tables.iter().rev() {
1180            match table.typ {
1181                CompilerScope::AsyncFunction => return true,
1182                CompilerScope::Function
1183                | CompilerScope::Lambda
1184                | CompilerScope::Class
1185                | CompilerScope::Module
1186                | CompilerScope::Annotation
1187                | CompilerScope::TypeParams => return false,
1188                // Comprehension inherits parent's async context
1189                CompilerScope::Comprehension => continue,
1190            }
1191        }
1192        false
1193    }
1194
1195    fn line_index_start(&self, range: TextRange) -> u32 {
1196        self.source_file
1197            .to_source_code()
1198            .line_index(range.start())
1199            .get() as _
1200    }
1201
1202    fn scan_statements(&mut self, statements: &[ast::Stmt]) -> SymbolTableResult {
1203        for statement in statements {
1204            self.scan_statement(statement)?;
1205        }
1206        Ok(())
1207    }
1208
1209    fn scan_parameters(&mut self, parameters: &[ast::ParameterWithDefault]) -> SymbolTableResult {
1210        for parameter in parameters {
1211            self.scan_parameter(&parameter.parameter)?;
1212        }
1213        Ok(())
1214    }
1215
1216    fn scan_parameter(&mut self, parameter: &ast::Parameter) -> SymbolTableResult {
1217        self.check_name(
1218            parameter.name.as_str(),
1219            ExpressionContext::Store,
1220            parameter.name.range,
1221        )?;
1222
1223        let usage = if parameter.annotation.is_some() {
1224            SymbolUsage::AnnotationParameter
1225        } else {
1226            SymbolUsage::Parameter
1227        };
1228
1229        // Check for duplicate parameter names
1230        let table = self.tables.last().unwrap();
1231        if table.symbols.contains_key(parameter.name.as_str()) {
1232            return Err(SymbolTableError {
1233                error: format!(
1234                    "duplicate argument '{}' in function definition",
1235                    parameter.name
1236                ),
1237                location: Some(
1238                    self.source_file
1239                        .to_source_code()
1240                        .source_location(parameter.name.range.start(), PositionEncoding::Utf8),
1241                ),
1242            });
1243        }
1244
1245        self.register_ident(&parameter.name, usage)
1246    }
1247
1248    fn scan_annotation(&mut self, annotation: &ast::Expr) -> SymbolTableResult {
1249        self.scan_annotation_inner(annotation, false)
1250    }
1251
1252    /// Scan an annotation from an AnnAssign statement (can be conditional)
1253    fn scan_ann_assign_annotation(&mut self, annotation: &ast::Expr) -> SymbolTableResult {
1254        self.scan_annotation_inner(annotation, true)
1255    }
1256
1257    fn scan_annotation_inner(
1258        &mut self,
1259        annotation: &ast::Expr,
1260        is_ann_assign: bool,
1261    ) -> SymbolTableResult {
1262        let current_scope = self.tables.last().map(|t| t.typ);
1263
1264        // PEP 649: Only AnnAssign annotations can be conditional.
1265        // Function parameter/return annotations are never conditional.
1266        if is_ann_assign && !self.future_annotations {
1267            let is_conditional = matches!(current_scope, Some(CompilerScope::Module))
1268                || (matches!(current_scope, Some(CompilerScope::Class))
1269                    && self.in_conditional_block);
1270
1271            if is_conditional && !self.tables.last().unwrap().has_conditional_annotations {
1272                self.tables.last_mut().unwrap().has_conditional_annotations = true;
1273                self.register_name(
1274                    "__conditional_annotations__",
1275                    SymbolUsage::Assigned,
1276                    annotation.range(),
1277                )?;
1278                self.register_name(
1279                    "__conditional_annotations__",
1280                    SymbolUsage::Used,
1281                    annotation.range(),
1282                )?;
1283            }
1284        }
1285
1286        // Create annotation scope for deferred evaluation
1287        let line_number = self.line_index_start(annotation.range());
1288        self.enter_annotation_scope(line_number);
1289
1290        if self.future_annotations {
1291            // PEP 563: annotations are stringified at compile time
1292            // Don't scan expression - symbols would fail to resolve
1293            // Just create the annotation_block structure
1294            self.leave_annotation_scope();
1295            return Ok(());
1296        }
1297
1298        // PEP 649: scan expression for symbol references
1299        // Class annotations are evaluated in class locals (not module globals)
1300        let was_in_annotation = self.in_annotation;
1301        self.in_annotation = true;
1302        let result = self.scan_expression(annotation, ExpressionContext::Load);
1303        self.in_annotation = was_in_annotation;
1304
1305        self.leave_annotation_scope();
1306
1307        result
1308    }
1309
1310    fn scan_statement(&mut self, statement: &ast::Stmt) -> SymbolTableResult {
1311        use ast::*;
1312        if let Stmt::ImportFrom(StmtImportFrom { module, names, .. }) = &statement
1313            && module.as_ref().map(|id| id.as_str()) == Some("__future__")
1314        {
1315            self.future_annotations =
1316                self.future_annotations || names.iter().any(|future| &future.name == "annotations");
1317        }
1318
1319        match &statement {
1320            Stmt::Global(StmtGlobal { names, .. }) => {
1321                for name in names {
1322                    self.register_ident(name, SymbolUsage::Global)?;
1323                }
1324            }
1325            Stmt::Nonlocal(StmtNonlocal { names, .. }) => {
1326                for name in names {
1327                    self.register_ident(name, SymbolUsage::Nonlocal)?;
1328                }
1329            }
1330            Stmt::FunctionDef(StmtFunctionDef {
1331                name,
1332                body,
1333                parameters,
1334                decorator_list,
1335                type_params,
1336                returns,
1337                range,
1338                is_async,
1339                ..
1340            }) => {
1341                self.scan_decorators(decorator_list, ExpressionContext::Load)?;
1342                self.register_ident(name, SymbolUsage::Assigned)?;
1343
1344                // Save the parent's annotation_block before scanning function annotations,
1345                // so function annotations don't interfere with parent scope annotations.
1346                // This applies to both class scope (methods) and module scope (top-level functions).
1347                let parent_scope_typ = self.tables.last().map(|t| t.typ);
1348                let should_save_annotation_block = matches!(
1349                    parent_scope_typ,
1350                    Some(CompilerScope::Class)
1351                        | Some(CompilerScope::Module)
1352                        | Some(CompilerScope::Function)
1353                        | Some(CompilerScope::AsyncFunction)
1354                );
1355                let saved_annotation_block = if should_save_annotation_block {
1356                    self.tables.last_mut().unwrap().annotation_block.take()
1357                } else {
1358                    None
1359                };
1360
1361                // For generic functions, scan defaults before entering type_param_block
1362                // (defaults are evaluated in the enclosing scope, not the type param scope)
1363                let has_type_params = type_params.is_some();
1364                if has_type_params {
1365                    self.scan_parameter_defaults(parameters)?;
1366                }
1367
1368                // For generic functions, enter type_param block FIRST so that
1369                // annotation scopes are nested inside and can see type parameters.
1370                if let Some(type_params) = type_params {
1371                    self.enter_type_param_block(
1372                        &format!("<generic parameters of {}>", name.as_str()),
1373                        self.line_index_start(type_params.range),
1374                        false,
1375                    )?;
1376                    self.scan_type_params(type_params)?;
1377                }
1378                let has_return_annotation = if let Some(expression) = returns {
1379                    self.scan_annotation(expression)?;
1380                    true
1381                } else {
1382                    false
1383                };
1384                self.enter_scope_with_parameters(
1385                    name.as_str(),
1386                    parameters,
1387                    self.line_index_start(*range),
1388                    has_return_annotation,
1389                    *is_async,
1390                    has_type_params, // skip_defaults: already scanned above
1391                )?;
1392                self.scan_statements(body)?;
1393                self.leave_scope();
1394                if type_params.is_some() {
1395                    self.leave_scope();
1396                }
1397
1398                // Restore parent's annotation_block after processing the function
1399                if let Some(block) = saved_annotation_block {
1400                    self.tables.last_mut().unwrap().annotation_block = Some(block);
1401                }
1402            }
1403            Stmt::ClassDef(StmtClassDef {
1404                name,
1405                body,
1406                arguments,
1407                decorator_list,
1408                type_params,
1409                range,
1410                node_index: _,
1411            }) => {
1412                // Save class_name for the entire ClassDef processing
1413                let prev_class = self.class_name.take();
1414                if let Some(type_params) = type_params {
1415                    self.enter_type_param_block(
1416                        &format!("<generic parameters of {}>", name.as_str()),
1417                        self.line_index_start(type_params.range),
1418                        true, // for_class: enable selective mangling
1419                    )?;
1420                    // Set class_name for mangling in type param scope
1421                    self.class_name = Some(name.to_string());
1422                    self.scan_type_params(type_params)?;
1423                }
1424                self.enter_scope(
1425                    name.as_str(),
1426                    CompilerScope::Class,
1427                    self.line_index_start(*range),
1428                );
1429                // Reset in_conditional_block for new class scope
1430                let saved_in_conditional = self.in_conditional_block;
1431                self.in_conditional_block = false;
1432                self.class_name = Some(name.to_string());
1433                self.register_name("__module__", SymbolUsage::Assigned, *range)?;
1434                self.register_name("__qualname__", SymbolUsage::Assigned, *range)?;
1435                self.register_name("__doc__", SymbolUsage::Assigned, *range)?;
1436                self.register_name("__class__", SymbolUsage::Assigned, *range)?;
1437                self.scan_statements(body)?;
1438                self.leave_scope();
1439                self.in_conditional_block = saved_in_conditional;
1440                // For non-generic classes, restore class_name before base scanning.
1441                // Bases are evaluated in the enclosing scope, not the class scope.
1442                // For generic classes, bases are scanned within the type_param scope
1443                // where class_name is already correctly set.
1444                if type_params.is_none() {
1445                    self.class_name = prev_class.clone();
1446                }
1447                if let Some(arguments) = arguments {
1448                    self.scan_expressions(&arguments.args, ExpressionContext::Load)?;
1449                    for keyword in &arguments.keywords {
1450                        self.scan_expression(&keyword.value, ExpressionContext::Load)?;
1451                    }
1452                }
1453                if type_params.is_some() {
1454                    self.leave_scope();
1455                }
1456                // Restore class_name after all ClassDef processing
1457                self.class_name = prev_class;
1458                self.scan_decorators(decorator_list, ExpressionContext::Load)?;
1459                self.register_ident(name, SymbolUsage::Assigned)?;
1460            }
1461            Stmt::Expr(StmtExpr { value, .. }) => {
1462                self.scan_expression(value, ExpressionContext::Load)?
1463            }
1464            Stmt::If(StmtIf {
1465                test,
1466                body,
1467                elif_else_clauses,
1468                ..
1469            }) => {
1470                self.scan_expression(test, ExpressionContext::Load)?;
1471                // PEP 649: Track conditional block for annotations
1472                let saved_in_conditional_block = self.in_conditional_block;
1473                self.in_conditional_block = true;
1474                self.scan_statements(body)?;
1475                for elif in elif_else_clauses {
1476                    if let Some(test) = &elif.test {
1477                        self.scan_expression(test, ExpressionContext::Load)?;
1478                    }
1479                    self.scan_statements(&elif.body)?;
1480                }
1481                self.in_conditional_block = saved_in_conditional_block;
1482            }
1483            Stmt::For(StmtFor {
1484                target,
1485                iter,
1486                body,
1487                orelse,
1488                ..
1489            }) => {
1490                self.scan_expression(target, ExpressionContext::Store)?;
1491                self.scan_expression(iter, ExpressionContext::Load)?;
1492                // PEP 649: Track conditional block for annotations
1493                let saved_in_conditional_block = self.in_conditional_block;
1494                self.in_conditional_block = true;
1495                self.scan_statements(body)?;
1496                self.scan_statements(orelse)?;
1497                self.in_conditional_block = saved_in_conditional_block;
1498            }
1499            Stmt::While(StmtWhile {
1500                test, body, orelse, ..
1501            }) => {
1502                self.scan_expression(test, ExpressionContext::Load)?;
1503                // PEP 649: Track conditional block for annotations
1504                let saved_in_conditional_block = self.in_conditional_block;
1505                self.in_conditional_block = true;
1506                self.scan_statements(body)?;
1507                self.scan_statements(orelse)?;
1508                self.in_conditional_block = saved_in_conditional_block;
1509            }
1510            Stmt::Break(_) | Stmt::Continue(_) | Stmt::Pass(_) => {
1511                // No symbols here.
1512            }
1513            Stmt::Import(StmtImport { names, .. })
1514            | Stmt::ImportFrom(StmtImportFrom { names, .. }) => {
1515                for name in names {
1516                    if let Some(alias) = &name.asname {
1517                        // `import my_module as my_alias`
1518                        self.check_name(alias.as_str(), ExpressionContext::Store, alias.range)?;
1519                        self.register_ident(alias, SymbolUsage::Imported)?;
1520                    } else if name.name.as_str() == "*" {
1521                        // Star imports are only allowed at module level
1522                        if self.tables.last().unwrap().typ != CompilerScope::Module {
1523                            return Err(SymbolTableError {
1524                                error: "'import *' only allowed at module level".to_string(),
1525                                location: Some(self.source_file.to_source_code().source_location(
1526                                    name.name.range.start(),
1527                                    PositionEncoding::Utf8,
1528                                )),
1529                            });
1530                        }
1531                        // Don't register star imports as symbols
1532                    } else {
1533                        // `import module` or `from x import name`
1534                        let imported_name = name.name.split('.').next().unwrap();
1535                        self.check_name(imported_name, ExpressionContext::Store, name.name.range)?;
1536                        self.register_name(imported_name, SymbolUsage::Imported, name.name.range)?;
1537                    }
1538                }
1539            }
1540            Stmt::Return(StmtReturn { value, .. }) => {
1541                if let Some(expression) = value {
1542                    self.scan_expression(expression, ExpressionContext::Load)?;
1543                }
1544            }
1545            Stmt::Assert(StmtAssert { test, msg, .. }) => {
1546                self.scan_expression(test, ExpressionContext::Load)?;
1547                if let Some(expression) = msg {
1548                    self.scan_expression(expression, ExpressionContext::Load)?;
1549                }
1550            }
1551            Stmt::Delete(StmtDelete { targets, .. }) => {
1552                self.scan_expressions(targets, ExpressionContext::Delete)?;
1553            }
1554            Stmt::Assign(StmtAssign { targets, value, .. }) => {
1555                self.scan_expressions(targets, ExpressionContext::Store)?;
1556                self.scan_expression(value, ExpressionContext::Load)?;
1557            }
1558            Stmt::AugAssign(StmtAugAssign { target, value, .. }) => {
1559                self.scan_expression(target, ExpressionContext::Store)?;
1560                self.scan_expression(value, ExpressionContext::Load)?;
1561            }
1562            Stmt::AnnAssign(StmtAnnAssign {
1563                target,
1564                annotation,
1565                value,
1566                simple,
1567                range,
1568                node_index: _,
1569            }) => {
1570                // https://github.com/python/cpython/blob/main/Python/symtable.c#L1233
1571                match &**target {
1572                    Expr::Name(ast::ExprName { id, .. }) if *simple => {
1573                        let id_str = id.as_str();
1574
1575                        self.check_name(id_str, ExpressionContext::Store, *range)?;
1576
1577                        self.register_name(id_str, SymbolUsage::AnnotationAssigned, *range)?;
1578                        // PEP 649: Register annotate function in module/class scope
1579                        let current_scope = self.tables.last().map(|t| t.typ);
1580                        match current_scope {
1581                            Some(CompilerScope::Module) => {
1582                                self.register_name("__annotate__", SymbolUsage::Assigned, *range)?;
1583                            }
1584                            Some(CompilerScope::Class) => {
1585                                self.register_name(
1586                                    "__annotate_func__",
1587                                    SymbolUsage::Assigned,
1588                                    *range,
1589                                )?;
1590                            }
1591                            _ => {}
1592                        }
1593                    }
1594                    _ => {
1595                        self.scan_expression(target, ExpressionContext::Store)?;
1596                    }
1597                }
1598                // Only scan annotation in annotation scope for simple name targets.
1599                // Non-simple annotations (subscript, attribute, parenthesized) are
1600                // never compiled into __annotate__, so scanning them would create
1601                // sub_tables that cause mismatch in the annotation scope's sub_table index.
1602                let is_simple_name = *simple && matches!(&**target, Expr::Name(_));
1603                if is_simple_name {
1604                    self.scan_ann_assign_annotation(annotation)?;
1605                } else {
1606                    // Still validate annotation for forbidden expressions
1607                    // (yield, await, named) even for non-simple targets.
1608                    let was_in_annotation = self.in_annotation;
1609                    self.in_annotation = true;
1610                    let result = self.scan_expression(annotation, ExpressionContext::Load);
1611                    self.in_annotation = was_in_annotation;
1612                    result?;
1613                }
1614                if let Some(value) = value {
1615                    self.scan_expression(value, ExpressionContext::Load)?;
1616                }
1617            }
1618            Stmt::With(StmtWith { items, body, .. }) => {
1619                for item in items {
1620                    self.scan_expression(&item.context_expr, ExpressionContext::Load)?;
1621                    if let Some(expression) = &item.optional_vars {
1622                        self.scan_expression(expression, ExpressionContext::Store)?;
1623                    }
1624                }
1625                // PEP 649: Track conditional block for annotations
1626                let saved_in_conditional_block = self.in_conditional_block;
1627                self.in_conditional_block = true;
1628                self.scan_statements(body)?;
1629                self.in_conditional_block = saved_in_conditional_block;
1630            }
1631            Stmt::Try(StmtTry {
1632                body,
1633                handlers,
1634                orelse,
1635                finalbody,
1636                ..
1637            }) => {
1638                // PEP 649: Track conditional block for annotations
1639                let saved_in_conditional_block = self.in_conditional_block;
1640                self.in_conditional_block = true;
1641                self.scan_statements(body)?;
1642                for handler in handlers {
1643                    let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
1644                        type_,
1645                        name,
1646                        body,
1647                        ..
1648                    }) = &handler;
1649                    if let Some(expression) = type_ {
1650                        self.scan_expression(expression, ExpressionContext::Load)?;
1651                    }
1652                    if let Some(name) = name {
1653                        self.register_ident(name, SymbolUsage::Assigned)?;
1654                    }
1655                    self.scan_statements(body)?;
1656                }
1657                self.scan_statements(orelse)?;
1658                self.scan_statements(finalbody)?;
1659                self.in_conditional_block = saved_in_conditional_block;
1660            }
1661            Stmt::Match(StmtMatch { subject, cases, .. }) => {
1662                self.scan_expression(subject, ExpressionContext::Load)?;
1663                // PEP 649: Track conditional block for annotations
1664                let saved_in_conditional_block = self.in_conditional_block;
1665                self.in_conditional_block = true;
1666                for case in cases {
1667                    self.scan_pattern(&case.pattern)?;
1668                    if let Some(guard) = &case.guard {
1669                        self.scan_expression(guard, ExpressionContext::Load)?;
1670                    }
1671                    self.scan_statements(&case.body)?;
1672                }
1673                self.in_conditional_block = saved_in_conditional_block;
1674            }
1675            Stmt::Raise(StmtRaise { exc, cause, .. }) => {
1676                if let Some(expression) = exc {
1677                    self.scan_expression(expression, ExpressionContext::Load)?;
1678                }
1679                if let Some(expression) = cause {
1680                    self.scan_expression(expression, ExpressionContext::Load)?;
1681                }
1682            }
1683            Stmt::TypeAlias(StmtTypeAlias {
1684                name,
1685                value,
1686                type_params,
1687                ..
1688            }) => {
1689                let was_in_type_alias = self.in_type_alias;
1690                self.in_type_alias = true;
1691                // Check before entering any sub-scopes
1692                let in_class = self
1693                    .tables
1694                    .last()
1695                    .is_some_and(|t| t.typ == CompilerScope::Class);
1696                let is_generic = type_params.is_some();
1697                if let Some(type_params) = type_params {
1698                    self.enter_type_param_block(
1699                        "TypeAlias",
1700                        self.line_index_start(type_params.range),
1701                        false,
1702                    )?;
1703                    self.scan_type_params(type_params)?;
1704                }
1705                // Value scope for lazy evaluation
1706                self.enter_scope(
1707                    "TypeAlias",
1708                    CompilerScope::TypeParams,
1709                    self.line_index_start(value.range()),
1710                );
1711                // Evaluator takes a format parameter
1712                self.register_name("format", SymbolUsage::Parameter, TextRange::default())?;
1713                if in_class {
1714                    if let Some(table) = self.tables.last_mut() {
1715                        table.can_see_class_scope = true;
1716                    }
1717                    self.register_name("__classdict__", SymbolUsage::Used, TextRange::default())?;
1718                }
1719                self.scan_expression(value, ExpressionContext::Load)?;
1720                self.leave_scope();
1721                if is_generic {
1722                    self.leave_scope();
1723                }
1724                self.in_type_alias = was_in_type_alias;
1725                self.scan_expression(name, ExpressionContext::Store)?;
1726            }
1727            Stmt::IpyEscapeCommand(_) => todo!(),
1728        }
1729        Ok(())
1730    }
1731
1732    fn scan_decorators(
1733        &mut self,
1734        decorators: &[ast::Decorator],
1735        context: ExpressionContext,
1736    ) -> SymbolTableResult {
1737        for decorator in decorators {
1738            self.scan_expression(&decorator.expression, context)?;
1739        }
1740        Ok(())
1741    }
1742
1743    fn scan_expressions(
1744        &mut self,
1745        expressions: &[ast::Expr],
1746        context: ExpressionContext,
1747    ) -> SymbolTableResult {
1748        for expression in expressions {
1749            self.scan_expression(expression, context)?;
1750        }
1751        Ok(())
1752    }
1753
1754    fn scan_expression(
1755        &mut self,
1756        expression: &ast::Expr,
1757        context: ExpressionContext,
1758    ) -> SymbolTableResult {
1759        use ast::*;
1760
1761        // Check for expressions not allowed in certain contexts
1762        // (type parameters, annotations, type aliases, TypeVar bounds/defaults)
1763        if let Some(keyword) = match expression {
1764            Expr::Yield(_) | Expr::YieldFrom(_) => Some("yield"),
1765            Expr::Await(_) => Some("await"),
1766            Expr::Named(_) => Some("named"),
1767            _ => None,
1768        } {
1769            // Determine the context name for the error message
1770            // scope_info takes precedence (e.g., "a TypeVar bound")
1771            let context_name = if let Some(scope_info) = self.scope_info {
1772                Some(scope_info)
1773            } else if let Some(table) = self.tables.last()
1774                && table.typ == CompilerScope::TypeParams
1775            {
1776                Some("a type parameter")
1777            } else if self.in_annotation {
1778                Some("an annotation")
1779            } else if self.in_type_alias {
1780                Some("a type alias")
1781            } else {
1782                None
1783            };
1784
1785            if let Some(context_name) = context_name {
1786                return Err(SymbolTableError {
1787                    error: format!("{keyword} expression cannot be used within {context_name}"),
1788                    location: Some(
1789                        self.source_file
1790                            .to_source_code()
1791                            .source_location(expression.range().start(), PositionEncoding::Utf8),
1792                    ),
1793                });
1794            }
1795        }
1796
1797        match expression {
1798            Expr::BinOp(ExprBinOp {
1799                left,
1800                right,
1801                range: _,
1802                ..
1803            }) => {
1804                self.scan_expression(left, context)?;
1805                self.scan_expression(right, context)?;
1806            }
1807            Expr::BoolOp(ExprBoolOp {
1808                values, range: _, ..
1809            }) => {
1810                self.scan_expressions(values, context)?;
1811            }
1812            Expr::Compare(ExprCompare {
1813                left,
1814                comparators,
1815                range: _,
1816                ..
1817            }) => {
1818                self.scan_expression(left, context)?;
1819                self.scan_expressions(comparators, context)?;
1820            }
1821            Expr::Subscript(ExprSubscript {
1822                value,
1823                slice,
1824                range: _,
1825                ..
1826            }) => {
1827                self.scan_expression(value, ExpressionContext::Load)?;
1828                self.scan_expression(slice, ExpressionContext::Load)?;
1829            }
1830            Expr::Attribute(ExprAttribute {
1831                value, attr, range, ..
1832            }) => {
1833                self.check_name(attr.as_str(), context, *range)?;
1834                self.scan_expression(value, ExpressionContext::Load)?;
1835            }
1836            Expr::Dict(ExprDict {
1837                items,
1838                node_index: _,
1839                range: _,
1840            }) => {
1841                for item in items {
1842                    if let Some(key) = &item.key {
1843                        self.scan_expression(key, context)?;
1844                    }
1845                    self.scan_expression(&item.value, context)?;
1846                }
1847            }
1848            Expr::Await(ExprAwait {
1849                value,
1850                node_index: _,
1851                range: _,
1852            }) => {
1853                self.scan_expression(value, context)?;
1854            }
1855            Expr::Yield(ExprYield {
1856                value,
1857                node_index: _,
1858                range: _,
1859            }) => {
1860                self.tables.last_mut().unwrap().is_generator = true;
1861                if let Some(expression) = value {
1862                    self.scan_expression(expression, context)?;
1863                }
1864            }
1865            Expr::YieldFrom(ExprYieldFrom {
1866                value,
1867                node_index: _,
1868                range: _,
1869            }) => {
1870                self.tables.last_mut().unwrap().is_generator = true;
1871                self.scan_expression(value, context)?;
1872            }
1873            Expr::UnaryOp(ExprUnaryOp {
1874                operand, range: _, ..
1875            }) => {
1876                self.scan_expression(operand, context)?;
1877            }
1878            Expr::Starred(ExprStarred {
1879                value, range: _, ..
1880            }) => {
1881                self.scan_expression(value, context)?;
1882            }
1883            Expr::Tuple(ExprTuple { elts, range: _, .. })
1884            | Expr::Set(ExprSet { elts, range: _, .. })
1885            | Expr::List(ExprList { elts, range: _, .. }) => {
1886                self.scan_expressions(elts, context)?;
1887            }
1888            Expr::Slice(ExprSlice {
1889                lower,
1890                upper,
1891                step,
1892                node_index: _,
1893                range: _,
1894            }) => {
1895                if let Some(lower) = lower {
1896                    self.scan_expression(lower, context)?;
1897                }
1898                if let Some(upper) = upper {
1899                    self.scan_expression(upper, context)?;
1900                }
1901                if let Some(step) = step {
1902                    self.scan_expression(step, context)?;
1903                }
1904            }
1905            Expr::Generator(ExprGenerator {
1906                elt,
1907                generators,
1908                range,
1909                ..
1910            }) => {
1911                let was_in_iter_def_exp = self.in_iter_def_exp;
1912                if context == ExpressionContext::IterDefinitionExp {
1913                    self.in_iter_def_exp = true;
1914                }
1915                // Generator expression - is_generator = true
1916                self.scan_comprehension("<genexpr>", elt, None, generators, *range, true)?;
1917                self.in_iter_def_exp = was_in_iter_def_exp;
1918            }
1919            Expr::ListComp(ExprListComp {
1920                elt,
1921                generators,
1922                range,
1923                node_index: _,
1924            }) => {
1925                let was_in_iter_def_exp = self.in_iter_def_exp;
1926                if context == ExpressionContext::IterDefinitionExp {
1927                    self.in_iter_def_exp = true;
1928                }
1929                // List comprehension - is_generator = false (can be inlined)
1930                self.scan_comprehension("<listcomp>", elt, None, generators, *range, false)?;
1931                self.in_iter_def_exp = was_in_iter_def_exp;
1932            }
1933            Expr::SetComp(ExprSetComp {
1934                elt,
1935                generators,
1936                range,
1937                node_index: _,
1938            }) => {
1939                let was_in_iter_def_exp = self.in_iter_def_exp;
1940                if context == ExpressionContext::IterDefinitionExp {
1941                    self.in_iter_def_exp = true;
1942                }
1943                // Set comprehension - is_generator = false (can be inlined)
1944                self.scan_comprehension("<setcomp>", elt, None, generators, *range, false)?;
1945                self.in_iter_def_exp = was_in_iter_def_exp;
1946            }
1947            Expr::DictComp(ExprDictComp {
1948                key,
1949                value,
1950                generators,
1951                range,
1952                node_index: _,
1953            }) => {
1954                let was_in_iter_def_exp = self.in_iter_def_exp;
1955                if context == ExpressionContext::IterDefinitionExp {
1956                    self.in_iter_def_exp = true;
1957                }
1958                // Dict comprehension - is_generator = false (can be inlined)
1959                self.scan_comprehension("<dictcomp>", key, Some(value), generators, *range, false)?;
1960                self.in_iter_def_exp = was_in_iter_def_exp;
1961            }
1962            Expr::Call(ExprCall {
1963                func,
1964                arguments,
1965                node_index: _,
1966                range: _,
1967            }) => {
1968                match context {
1969                    ExpressionContext::IterDefinitionExp => {
1970                        self.scan_expression(func, ExpressionContext::IterDefinitionExp)?;
1971                    }
1972                    _ => {
1973                        self.scan_expression(func, ExpressionContext::Load)?;
1974                    }
1975                }
1976
1977                self.scan_expressions(&arguments.args, ExpressionContext::Load)?;
1978                for keyword in &arguments.keywords {
1979                    if let Some(arg) = &keyword.arg {
1980                        self.check_name(arg.as_str(), ExpressionContext::Store, keyword.range)?;
1981                    }
1982                    self.scan_expression(&keyword.value, ExpressionContext::Load)?;
1983                }
1984            }
1985            Expr::Name(ExprName { id, range, .. }) => {
1986                let id = id.as_str();
1987
1988                self.check_name(id, context, *range)?;
1989
1990                // Determine the contextual usage of this symbol:
1991                match context {
1992                    ExpressionContext::Delete => {
1993                        self.register_name(id, SymbolUsage::Assigned, *range)?;
1994                        self.register_name(id, SymbolUsage::Used, *range)?;
1995                    }
1996                    ExpressionContext::Load | ExpressionContext::IterDefinitionExp => {
1997                        self.register_name(id, SymbolUsage::Used, *range)?;
1998                    }
1999                    ExpressionContext::Store => {
2000                        self.register_name(id, SymbolUsage::Assigned, *range)?;
2001                    }
2002                    ExpressionContext::Iter => {
2003                        self.register_name(id, SymbolUsage::Iter, *range)?;
2004                    }
2005                }
2006                // Interesting stuff about the __class__ variable:
2007                // https://docs.python.org/3/reference/datamodel.html?highlight=__class__#creating-the-class-object
2008                if context == ExpressionContext::Load
2009                    && matches!(
2010                        self.tables.last().unwrap().typ,
2011                        CompilerScope::Function | CompilerScope::AsyncFunction
2012                    )
2013                    && id == "super"
2014                {
2015                    self.register_name("__class__", SymbolUsage::Used, *range)?;
2016                }
2017            }
2018            Expr::Lambda(ExprLambda {
2019                body,
2020                parameters,
2021                node_index: _,
2022                range: _,
2023            }) => {
2024                if let Some(parameters) = parameters {
2025                    self.enter_scope_with_parameters(
2026                        "lambda",
2027                        parameters,
2028                        self.line_index_start(expression.range()),
2029                        false, // lambdas have no return annotation
2030                        false, // lambdas are never async
2031                        false, // don't skip defaults
2032                    )?;
2033                } else {
2034                    self.enter_scope(
2035                        "lambda",
2036                        CompilerScope::Lambda,
2037                        self.line_index_start(expression.range()),
2038                    );
2039                }
2040                match context {
2041                    ExpressionContext::IterDefinitionExp => {
2042                        self.scan_expression(body, ExpressionContext::IterDefinitionExp)?;
2043                    }
2044                    _ => {
2045                        self.scan_expression(body, ExpressionContext::Load)?;
2046                    }
2047                }
2048                self.leave_scope();
2049            }
2050            Expr::FString(ExprFString { value, .. }) => {
2051                for expr in value.elements().filter_map(|x| x.as_interpolation()) {
2052                    self.scan_expression(&expr.expression, ExpressionContext::Load)?;
2053                    if let Some(format_spec) = &expr.format_spec {
2054                        for element in format_spec.elements.interpolations() {
2055                            self.scan_expression(&element.expression, ExpressionContext::Load)?
2056                        }
2057                    }
2058                }
2059            }
2060            Expr::TString(tstring) => {
2061                // Scan t-string interpolation expressions (similar to f-strings)
2062                for expr in tstring
2063                    .value
2064                    .elements()
2065                    .filter_map(|x| x.as_interpolation())
2066                {
2067                    self.scan_expression(&expr.expression, ExpressionContext::Load)?;
2068                    if let Some(format_spec) = &expr.format_spec {
2069                        for element in format_spec.elements.interpolations() {
2070                            self.scan_expression(&element.expression, ExpressionContext::Load)?
2071                        }
2072                    }
2073                }
2074            }
2075            // Constants
2076            Expr::StringLiteral(_)
2077            | Expr::BytesLiteral(_)
2078            | Expr::NumberLiteral(_)
2079            | Expr::BooleanLiteral(_)
2080            | Expr::NoneLiteral(_)
2081            | Expr::EllipsisLiteral(_) => {}
2082            Expr::IpyEscapeCommand(_) => todo!(),
2083            Expr::If(ExprIf {
2084                test,
2085                body,
2086                orelse,
2087                node_index: _,
2088                range: _,
2089            }) => {
2090                self.scan_expression(test, ExpressionContext::Load)?;
2091                self.scan_expression(body, ExpressionContext::Load)?;
2092                self.scan_expression(orelse, ExpressionContext::Load)?;
2093            }
2094
2095            Expr::Named(ExprNamed {
2096                target,
2097                value,
2098                range,
2099                node_index: _,
2100            }) => {
2101                // named expressions are not allowed in the definition of
2102                // comprehension iterator definitions (including nested comprehensions)
2103                if context == ExpressionContext::IterDefinitionExp || self.in_iter_def_exp {
2104                    return Err(SymbolTableError {
2105                          error: "assignment expression cannot be used in a comprehension iterable expression".to_string(),
2106                          location: Some(self.source_file.to_source_code().source_location(target.range().start(), PositionEncoding::Utf8)),
2107                      });
2108                }
2109
2110                self.scan_expression(value, ExpressionContext::Load)?;
2111
2112                // special handling for assigned identifier in named expressions
2113                // that are used in comprehensions. This required to correctly
2114                // propagate the scope of the named assigned named and not to
2115                // propagate inner names.
2116                if let Expr::Name(ExprName { id, .. }) = &**target {
2117                    let id = id.as_str();
2118                    self.check_name(id, ExpressionContext::Store, *range)?;
2119                    let table = self.tables.last().unwrap();
2120                    if table.typ == CompilerScope::Comprehension {
2121                        self.register_name(
2122                            id,
2123                            SymbolUsage::AssignedNamedExprInComprehension,
2124                            *range,
2125                        )?;
2126                    } else {
2127                        // omit one recursion. When the handling of an store changes for
2128                        // Identifiers this needs adapted - more forward safe would be
2129                        // calling scan_expression directly.
2130                        self.register_name(id, SymbolUsage::Assigned, *range)?;
2131                    }
2132                } else {
2133                    self.scan_expression(target, ExpressionContext::Store)?;
2134                }
2135            }
2136        }
2137        Ok(())
2138    }
2139
2140    fn scan_comprehension(
2141        &mut self,
2142        scope_name: &str,
2143        elt1: &ast::Expr,
2144        elt2: Option<&ast::Expr>,
2145        generators: &[ast::Comprehension],
2146        range: TextRange,
2147        is_generator: bool,
2148    ) -> SymbolTableResult {
2149        // Check for async comprehension outside async function
2150        // (list/set/dict comprehensions only, not generator expressions)
2151        let has_async_gen = generators.iter().any(|g| g.is_async);
2152        if has_async_gen && !is_generator && !self.is_in_async_context() {
2153            return Err(SymbolTableError {
2154                error: "asynchronous comprehension outside of an asynchronous function".to_owned(),
2155                location: Some(
2156                    self.source_file
2157                        .to_source_code()
2158                        .source_location(range.start(), PositionEncoding::Utf8),
2159                ),
2160            });
2161        }
2162
2163        // Comprehensions are compiled as functions, so create a scope for them:
2164        self.enter_scope(
2165            scope_name,
2166            CompilerScope::Comprehension,
2167            self.line_index_start(range),
2168        );
2169        // Generator expressions need the is_generator flag
2170        self.tables.last_mut().unwrap().is_generator = is_generator;
2171
2172        // PEP 709: Mark non-generator comprehensions for inlining.
2173        // Only in function-like scopes for now. Module/class scope inlining
2174        // needs more work (Cell name resolution, __class__ handling).
2175        // Also excluded: generator expressions, async comprehensions,
2176        // and annotation scopes nested in classes (can_see_class_scope).
2177        let element_has_await = expr_contains_await(elt1) || elt2.is_some_and(expr_contains_await);
2178        if !is_generator && !has_async_gen && !element_has_await {
2179            let parent = self.tables.iter().rev().nth(1);
2180            let parent_can_see_class = parent.is_some_and(|t| t.can_see_class_scope);
2181            let parent_is_func = parent.is_some_and(|t| {
2182                matches!(
2183                    t.typ,
2184                    CompilerScope::Function
2185                        | CompilerScope::AsyncFunction
2186                        | CompilerScope::Lambda
2187                        | CompilerScope::Comprehension
2188                )
2189            });
2190            if !parent_can_see_class && parent_is_func {
2191                self.tables.last_mut().unwrap().comp_inlined = true;
2192            }
2193        }
2194
2195        // Register the passed argument to the generator function as the name ".0"
2196        self.register_name(".0", SymbolUsage::Parameter, range)?;
2197
2198        self.scan_expression(elt1, ExpressionContext::Load)?;
2199        if let Some(elt2) = elt2 {
2200            self.scan_expression(elt2, ExpressionContext::Load)?;
2201        }
2202
2203        let mut is_first_generator = true;
2204        for generator in generators {
2205            // Set flag for INNER_LOOP_CONFLICT check (only for inner loops, not the first)
2206            if !is_first_generator {
2207                self.in_comp_inner_loop_target = true;
2208            }
2209            self.scan_expression(&generator.target, ExpressionContext::Iter)?;
2210            self.in_comp_inner_loop_target = false;
2211
2212            if is_first_generator {
2213                is_first_generator = false;
2214            } else {
2215                self.scan_expression(&generator.iter, ExpressionContext::IterDefinitionExp)?;
2216            }
2217
2218            for if_expr in &generator.ifs {
2219                self.scan_expression(if_expr, ExpressionContext::Load)?;
2220            }
2221        }
2222
2223        self.leave_scope();
2224
2225        // The first iterable is passed as an argument into the created function:
2226        assert!(!generators.is_empty());
2227        self.scan_expression(&generators[0].iter, ExpressionContext::IterDefinitionExp)?;
2228
2229        Ok(())
2230    }
2231
2232    /// Scan type parameter bound or default in a separate scope
2233    // = symtable_visit_type_param_bound_or_default
2234    fn scan_type_param_bound_or_default(
2235        &mut self,
2236        expr: &ast::Expr,
2237        scope_name: &str,
2238        scope_info: &'static str,
2239    ) -> SymbolTableResult {
2240        // Enter a new TypeParams scope for the bound/default expression
2241        // This allows the expression to access outer scope symbols
2242        let in_class = self.tables.last().is_some_and(|t| t.can_see_class_scope);
2243        let line_number = self.line_index_start(expr.range());
2244        self.enter_scope(scope_name, CompilerScope::TypeParams, line_number);
2245        // Evaluator takes a format parameter
2246        self.register_name("format", SymbolUsage::Parameter, TextRange::default())?;
2247
2248        if in_class {
2249            if let Some(table) = self.tables.last_mut() {
2250                table.can_see_class_scope = true;
2251            }
2252            self.register_name("__classdict__", SymbolUsage::Used, TextRange::default())?;
2253        }
2254
2255        // Set scope_info for better error messages
2256        let old_scope_info = self.scope_info;
2257        self.scope_info = Some(scope_info);
2258
2259        // Scan the expression in this new scope
2260        let result = self.scan_expression(expr, ExpressionContext::Load);
2261
2262        // Restore scope_info and exit the scope
2263        self.scope_info = old_scope_info;
2264        self.leave_scope();
2265
2266        result
2267    }
2268
2269    fn scan_type_params(&mut self, type_params: &ast::TypeParams) -> SymbolTableResult {
2270        // Check for duplicate type parameter names
2271        let mut seen_names: IndexSet<&str> = IndexSet::default();
2272        // Check for non-default type parameter after default type parameter
2273        let mut default_seen = false;
2274        for type_param in &type_params.type_params {
2275            let (name, range, has_default) = match type_param {
2276                ast::TypeParam::TypeVar(tv) => (tv.name.as_str(), tv.range, tv.default.is_some()),
2277                ast::TypeParam::ParamSpec(ps) => (ps.name.as_str(), ps.range, ps.default.is_some()),
2278                ast::TypeParam::TypeVarTuple(tvt) => {
2279                    (tvt.name.as_str(), tvt.range, tvt.default.is_some())
2280                }
2281            };
2282            if !seen_names.insert(name) {
2283                return Err(SymbolTableError {
2284                    error: format!("duplicate type parameter '{}'", name),
2285                    location: Some(
2286                        self.source_file
2287                            .to_source_code()
2288                            .source_location(range.start(), PositionEncoding::Utf8),
2289                    ),
2290                });
2291            }
2292            if has_default {
2293                default_seen = true;
2294            } else if default_seen {
2295                return Err(SymbolTableError {
2296                    error: format!(
2297                        "non-default type parameter '{}' follows default type parameter",
2298                        name
2299                    ),
2300                    location: Some(
2301                        self.source_file
2302                            .to_source_code()
2303                            .source_location(range.start(), PositionEncoding::Utf8),
2304                    ),
2305                });
2306            }
2307        }
2308
2309        // Register .type_params as a type parameter (automatically becomes cell variable)
2310        self.register_name(".type_params", SymbolUsage::TypeParam, type_params.range)?;
2311
2312        // First register all type parameters
2313        for type_param in &type_params.type_params {
2314            match type_param {
2315                ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
2316                    name,
2317                    bound,
2318                    range: type_var_range,
2319                    default,
2320                    node_index: _,
2321                }) => {
2322                    self.register_name(name.as_str(), SymbolUsage::TypeParam, *type_var_range)?;
2323
2324                    // Process bound in a separate scope
2325                    if let Some(binding) = bound {
2326                        let (scope_name, scope_info) = if binding.is_tuple_expr() {
2327                            (
2328                                format!("<TypeVar constraint of {name}>"),
2329                                "a TypeVar constraint",
2330                            )
2331                        } else {
2332                            (format!("<TypeVar bound of {name}>"), "a TypeVar bound")
2333                        };
2334                        self.scan_type_param_bound_or_default(binding, &scope_name, scope_info)?;
2335                    }
2336
2337                    // Process default in a separate scope
2338                    if let Some(default_value) = default {
2339                        let scope_name = format!("<TypeVar default of {name}>");
2340                        self.scan_type_param_bound_or_default(
2341                            default_value,
2342                            &scope_name,
2343                            "a TypeVar default",
2344                        )?;
2345                    }
2346                }
2347                ast::TypeParam::ParamSpec(ast::TypeParamParamSpec {
2348                    name,
2349                    range: param_spec_range,
2350                    default,
2351                    node_index: _,
2352                }) => {
2353                    self.register_name(name, SymbolUsage::TypeParam, *param_spec_range)?;
2354
2355                    // Process default in a separate scope
2356                    if let Some(default_value) = default {
2357                        let scope_name = format!("<ParamSpec default of {name}>");
2358                        self.scan_type_param_bound_or_default(
2359                            default_value,
2360                            &scope_name,
2361                            "a ParamSpec default",
2362                        )?;
2363                    }
2364                }
2365                ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple {
2366                    name,
2367                    range: type_var_tuple_range,
2368                    default,
2369                    node_index: _,
2370                }) => {
2371                    self.register_name(name, SymbolUsage::TypeParam, *type_var_tuple_range)?;
2372
2373                    // Process default in a separate scope
2374                    if let Some(default_value) = default {
2375                        let scope_name = format!("<TypeVarTuple default of {name}>");
2376                        self.scan_type_param_bound_or_default(
2377                            default_value,
2378                            &scope_name,
2379                            "a TypeVarTuple default",
2380                        )?;
2381                    }
2382                }
2383            }
2384        }
2385        Ok(())
2386    }
2387
2388    fn scan_patterns(&mut self, patterns: &[ast::Pattern]) -> SymbolTableResult {
2389        for pattern in patterns {
2390            self.scan_pattern(pattern)?;
2391        }
2392        Ok(())
2393    }
2394
2395    fn scan_pattern(&mut self, pattern: &ast::Pattern) -> SymbolTableResult {
2396        use ast::Pattern::*;
2397        match pattern {
2398            MatchValue(ast::PatternMatchValue { value, .. }) => {
2399                self.scan_expression(value, ExpressionContext::Load)?
2400            }
2401            MatchSingleton(_) => {}
2402            MatchSequence(ast::PatternMatchSequence { patterns, .. }) => {
2403                self.scan_patterns(patterns)?
2404            }
2405            MatchMapping(ast::PatternMatchMapping {
2406                keys,
2407                patterns,
2408                rest,
2409                ..
2410            }) => {
2411                self.scan_expressions(keys, ExpressionContext::Load)?;
2412                self.scan_patterns(patterns)?;
2413                if let Some(rest) = rest {
2414                    self.register_ident(rest, SymbolUsage::Assigned)?;
2415                }
2416            }
2417            MatchClass(ast::PatternMatchClass { cls, arguments, .. }) => {
2418                self.scan_expression(cls, ExpressionContext::Load)?;
2419                self.scan_patterns(&arguments.patterns)?;
2420                for kw in &arguments.keywords {
2421                    self.scan_pattern(&kw.pattern)?;
2422                }
2423            }
2424            MatchStar(ast::PatternMatchStar { name, .. }) => {
2425                if let Some(name) = name {
2426                    self.register_ident(name, SymbolUsage::Assigned)?;
2427                }
2428            }
2429            MatchAs(ast::PatternMatchAs { pattern, name, .. }) => {
2430                if let Some(pattern) = pattern {
2431                    self.scan_pattern(pattern)?;
2432                }
2433                if let Some(name) = name {
2434                    self.register_ident(name, SymbolUsage::Assigned)?;
2435                }
2436            }
2437            MatchOr(ast::PatternMatchOr { patterns, .. }) => self.scan_patterns(patterns)?,
2438        }
2439        Ok(())
2440    }
2441
2442    /// Scan default parameter values (evaluated in the enclosing scope)
2443    fn scan_parameter_defaults(&mut self, parameters: &ast::Parameters) -> SymbolTableResult {
2444        for default in parameters
2445            .posonlyargs
2446            .iter()
2447            .chain(parameters.args.iter())
2448            .chain(parameters.kwonlyargs.iter())
2449            .filter_map(|arg| arg.default.as_ref())
2450        {
2451            self.scan_expression(default, ExpressionContext::Load)?;
2452        }
2453        Ok(())
2454    }
2455
2456    fn enter_scope_with_parameters(
2457        &mut self,
2458        name: &str,
2459        parameters: &ast::Parameters,
2460        line_number: u32,
2461        has_return_annotation: bool,
2462        is_async: bool,
2463        skip_defaults: bool,
2464    ) -> SymbolTableResult {
2465        // Evaluate eventual default parameters (unless already scanned before type_param_block):
2466        if !skip_defaults {
2467            self.scan_parameter_defaults(parameters)?;
2468        }
2469
2470        // Annotations are scanned in outer scope:
2471        for annotation in parameters
2472            .posonlyargs
2473            .iter()
2474            .chain(parameters.args.iter())
2475            .chain(parameters.kwonlyargs.iter())
2476            .filter_map(|arg| arg.parameter.annotation.as_ref())
2477        {
2478            self.scan_annotation(annotation)?;
2479        }
2480        if let Some(annotation) = parameters
2481            .vararg
2482            .as_ref()
2483            .and_then(|arg| arg.annotation.as_ref())
2484        {
2485            self.scan_annotation(annotation)?;
2486        }
2487        if let Some(annotation) = parameters
2488            .kwarg
2489            .as_ref()
2490            .and_then(|arg| arg.annotation.as_ref())
2491        {
2492            self.scan_annotation(annotation)?;
2493        }
2494
2495        // Check if this function has any annotations (parameter or return)
2496        let has_param_annotations = parameters
2497            .posonlyargs
2498            .iter()
2499            .chain(parameters.args.iter())
2500            .chain(parameters.kwonlyargs.iter())
2501            .any(|p| p.parameter.annotation.is_some())
2502            || parameters
2503                .vararg
2504                .as_ref()
2505                .is_some_and(|p| p.annotation.is_some())
2506            || parameters
2507                .kwarg
2508                .as_ref()
2509                .is_some_and(|p| p.annotation.is_some());
2510
2511        let has_any_annotations = has_param_annotations || has_return_annotation;
2512
2513        // Take annotation_block if this function has any annotations.
2514        // When in class scope, the class's annotation_block was saved before scanning
2515        // function annotations, so the current annotation_block belongs to this function.
2516        let annotation_block = if has_any_annotations {
2517            self.tables.last_mut().unwrap().annotation_block.take()
2518        } else {
2519            None
2520        };
2521
2522        let scope_type = if is_async {
2523            CompilerScope::AsyncFunction
2524        } else {
2525            CompilerScope::Function
2526        };
2527        self.enter_scope(name, scope_type, line_number);
2528
2529        // Move annotation_block to function scope only if we have one
2530        if let Some(block) = annotation_block {
2531            self.tables.last_mut().unwrap().annotation_block = Some(block);
2532        }
2533
2534        // Fill scope with parameter names:
2535        self.scan_parameters(&parameters.posonlyargs)?;
2536        self.scan_parameters(&parameters.args)?;
2537        self.scan_parameters(&parameters.kwonlyargs)?;
2538        if let Some(name) = &parameters.vararg {
2539            self.scan_parameter(name)?;
2540        }
2541        if let Some(name) = &parameters.kwarg {
2542            self.scan_parameter(name)?;
2543        }
2544        Ok(())
2545    }
2546
2547    fn register_ident(&mut self, ident: &ast::Identifier, role: SymbolUsage) -> SymbolTableResult {
2548        self.register_name(ident.as_str(), role, ident.range)
2549    }
2550
2551    fn check_name(
2552        &self,
2553        name: &str,
2554        context: ExpressionContext,
2555        range: TextRange,
2556    ) -> SymbolTableResult {
2557        if name == "__debug__" {
2558            let location = Some(
2559                self.source_file
2560                    .to_source_code()
2561                    .source_location(range.start(), PositionEncoding::Utf8),
2562            );
2563            match context {
2564                ExpressionContext::Store | ExpressionContext::Iter => {
2565                    return Err(SymbolTableError {
2566                        error: "cannot assign to __debug__".to_owned(),
2567                        location,
2568                    });
2569                }
2570                ExpressionContext::Delete => {
2571                    return Err(SymbolTableError {
2572                        error: "cannot delete __debug__".to_owned(),
2573                        location,
2574                    });
2575                }
2576                _ => {}
2577            }
2578        }
2579        Ok(())
2580    }
2581
2582    fn register_name(
2583        &mut self,
2584        name: &str,
2585        role: SymbolUsage,
2586        range: TextRange,
2587    ) -> SymbolTableResult {
2588        let location = self
2589            .source_file
2590            .to_source_code()
2591            .source_location(range.start(), PositionEncoding::Utf8);
2592        let location = Some(location);
2593
2594        // Note: __debug__ checks are handled by check_name function, so no check needed here.
2595
2596        let scope_depth = self.tables.len();
2597        let table = self.tables.last_mut().unwrap();
2598
2599        // Add type param names to mangled_names set for selective mangling
2600        if matches!(role, SymbolUsage::TypeParam)
2601            && let Some(ref mut set) = table.mangled_names
2602        {
2603            set.insert(name.to_owned());
2604        }
2605
2606        let name = maybe_mangle_name(
2607            self.class_name.as_deref(),
2608            table.mangled_names.as_ref(),
2609            name,
2610        );
2611        // Some checks for the symbol that present on this scope level:
2612        let symbol = if let Some(symbol) = table.symbols.get_mut(name.as_ref()) {
2613            let flags = &symbol.flags;
2614
2615            // INNER_LOOP_CONFLICT: comprehension inner loop cannot rebind
2616            // a variable that was used as a named expression target
2617            // Example: [i for i in range(5) if (j := 0) for j in range(5)]
2618            // Here 'j' is used in named expr first, then as inner loop iter target
2619            if self.in_comp_inner_loop_target
2620                && flags.contains(SymbolFlags::ASSIGNED_IN_COMPREHENSION)
2621            {
2622                return Err(SymbolTableError {
2623                    error: format!(
2624                        "comprehension inner loop cannot rebind assignment expression target '{}'",
2625                        name
2626                    ),
2627                    location,
2628                });
2629            }
2630
2631            // Role already set..
2632            match role {
2633                SymbolUsage::Global if !symbol.is_global() => {
2634                    if flags.contains(SymbolFlags::PARAMETER) {
2635                        return Err(SymbolTableError {
2636                            error: format!("name '{name}' is parameter and global"),
2637                            location,
2638                        });
2639                    }
2640                    if flags.contains(SymbolFlags::REFERENCED) {
2641                        return Err(SymbolTableError {
2642                            error: format!("name '{name}' is used prior to global declaration"),
2643                            location,
2644                        });
2645                    }
2646                    if flags.contains(SymbolFlags::ANNOTATED) {
2647                        return Err(SymbolTableError {
2648                            error: format!("annotated name '{name}' can't be global"),
2649                            location,
2650                        });
2651                    }
2652                    if flags.contains(SymbolFlags::ASSIGNED) {
2653                        return Err(SymbolTableError {
2654                            error: format!(
2655                                "name '{name}' is assigned to before global declaration"
2656                            ),
2657                            location,
2658                        });
2659                    }
2660                }
2661                SymbolUsage::Nonlocal => {
2662                    if flags.contains(SymbolFlags::PARAMETER) {
2663                        return Err(SymbolTableError {
2664                            error: format!("name '{name}' is parameter and nonlocal"),
2665                            location,
2666                        });
2667                    }
2668                    if flags.contains(SymbolFlags::REFERENCED) {
2669                        return Err(SymbolTableError {
2670                            error: format!("name '{name}' is used prior to nonlocal declaration"),
2671                            location,
2672                        });
2673                    }
2674                    if flags.contains(SymbolFlags::ANNOTATED) {
2675                        return Err(SymbolTableError {
2676                            error: format!("annotated name '{name}' can't be nonlocal"),
2677                            location,
2678                        });
2679                    }
2680                    if flags.contains(SymbolFlags::ASSIGNED) {
2681                        return Err(SymbolTableError {
2682                            error: format!(
2683                                "name '{name}' is assigned to before nonlocal declaration"
2684                            ),
2685                            location,
2686                        });
2687                    }
2688                }
2689                _ => {
2690                    // Ok?
2691                }
2692            }
2693            symbol
2694        } else {
2695            // The symbol does not present on this scope level.
2696            // Some checks to insert new symbol into symbol table:
2697            match role {
2698                SymbolUsage::Nonlocal if scope_depth < 2 => {
2699                    return Err(SymbolTableError {
2700                        error: format!("cannot define nonlocal '{name}' at top level."),
2701                        location,
2702                    });
2703                }
2704                _ => {
2705                    // Ok!
2706                }
2707            }
2708            // Insert symbol when required:
2709            let symbol = Symbol::new(name.as_ref());
2710            table.symbols.entry(name.into_owned()).or_insert(symbol)
2711        };
2712
2713        // Set proper scope and flags on symbol:
2714        let flags = &mut symbol.flags;
2715        match role {
2716            SymbolUsage::Nonlocal => {
2717                symbol.scope = SymbolScope::Free;
2718                flags.insert(SymbolFlags::NONLOCAL);
2719            }
2720            SymbolUsage::Imported => {
2721                flags.insert(SymbolFlags::ASSIGNED | SymbolFlags::IMPORTED);
2722            }
2723            SymbolUsage::Parameter => {
2724                flags.insert(SymbolFlags::PARAMETER);
2725                // Parameters are always added to varnames first
2726                let name_str = symbol.name.clone();
2727                if !self.current_varnames.contains(&name_str) {
2728                    self.current_varnames.push(name_str);
2729                }
2730            }
2731            SymbolUsage::AnnotationParameter => {
2732                flags.insert(SymbolFlags::PARAMETER | SymbolFlags::ANNOTATED);
2733                // Annotated parameters are also added to varnames
2734                let name_str = symbol.name.clone();
2735                if !self.current_varnames.contains(&name_str) {
2736                    self.current_varnames.push(name_str);
2737                }
2738            }
2739            SymbolUsage::AnnotationAssigned => {
2740                flags.insert(SymbolFlags::ASSIGNED | SymbolFlags::ANNOTATED);
2741            }
2742            SymbolUsage::Assigned => {
2743                flags.insert(SymbolFlags::ASSIGNED);
2744                // Local variables (assigned) are added to varnames if they are local scope
2745                // and not already in varnames
2746                if symbol.scope == SymbolScope::Local {
2747                    let name_str = symbol.name.clone();
2748                    if !self.current_varnames.contains(&name_str) {
2749                        self.current_varnames.push(name_str);
2750                    }
2751                }
2752            }
2753            SymbolUsage::AssignedNamedExprInComprehension => {
2754                flags.insert(SymbolFlags::ASSIGNED | SymbolFlags::ASSIGNED_IN_COMPREHENSION);
2755                // Named expressions in comprehensions might also be locals
2756                if symbol.scope == SymbolScope::Local {
2757                    let name_str = symbol.name.clone();
2758                    if !self.current_varnames.contains(&name_str) {
2759                        self.current_varnames.push(name_str);
2760                    }
2761                }
2762            }
2763            SymbolUsage::Global => {
2764                symbol.scope = SymbolScope::GlobalExplicit;
2765                flags.insert(SymbolFlags::GLOBAL);
2766            }
2767            SymbolUsage::Used => {
2768                flags.insert(SymbolFlags::REFERENCED);
2769            }
2770            SymbolUsage::Iter => {
2771                flags.insert(SymbolFlags::ITER);
2772            }
2773            SymbolUsage::TypeParam => {
2774                flags.insert(SymbolFlags::ASSIGNED | SymbolFlags::TYPE_PARAM);
2775            }
2776        }
2777
2778        // and even more checking
2779        // it is not allowed to assign to iterator variables (by named expressions)
2780        if flags.contains(SymbolFlags::ITER)
2781            && flags.contains(SymbolFlags::ASSIGNED_IN_COMPREHENSION)
2782        {
2783            return Err(SymbolTableError {
2784                error: format!(
2785                    "assignment expression cannot rebind comprehension iteration variable '{}'",
2786                    symbol.name
2787                ),
2788                location,
2789            });
2790        }
2791        Ok(())
2792    }
2793}
2794
2795pub(crate) fn mangle_name<'a>(class_name: Option<&str>, name: &'a str) -> Cow<'a, str> {
2796    let class_name = match class_name {
2797        Some(n) => n,
2798        None => return name.into(),
2799    };
2800    if !name.starts_with("__") || name.ends_with("__") || name.contains('.') {
2801        return name.into();
2802    }
2803    // Strip leading underscores from class name
2804    let class_name = class_name.trim_start_matches('_');
2805    let mut ret = String::with_capacity(1 + class_name.len() + name.len());
2806    ret.push('_');
2807    ret.push_str(class_name);
2808    ret.push_str(name);
2809    ret.into()
2810}
2811
2812/// Selective mangling for type parameter scopes around generic classes.
2813/// If `mangled_names` is Some, only mangle names that are in the set;
2814/// other names are left unmangled.
2815pub(crate) fn maybe_mangle_name<'a>(
2816    class_name: Option<&str>,
2817    mangled_names: Option<&IndexSet<String>>,
2818    name: &'a str,
2819) -> Cow<'a, str> {
2820    if let Some(set) = mangled_names
2821        && !set.contains(name)
2822    {
2823        return name.into();
2824    }
2825    mangle_name(class_name, name)
2826}