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::{self as ast, name::Name};
17use ruff_text_size::{Ranged, TextRange};
18use rustpython_compiler_core::{PositionEncoding, SourceFile, SourceLocation};
19
20const DEFAULT_RECURSION_LIMIT: usize = 1000;
21const RECURSION_ERROR: &str = "maximum recursion depth exceeded during compilation";
22
23/// Captures all symbols in the current scope, and has a list of sub-scopes in this scope.
24#[derive(Clone)]
25pub struct SymbolTable {
26    /// The name of this symbol table. Often the name of the class or function.
27    pub name: Name,
28
29    /// The type of symbol table
30    pub typ: CompilerScope,
31
32    /// The line number in the source code where this symboltable begins.
33    pub line_number: u32,
34
35    /// Monotonic creation order used by public symtable children.
36    pub block_index: usize,
37
38    // Return True if the block is a nested class or function
39    pub is_nested: bool,
40
41    /// Whether this function-like scope was created directly in a class block.
42    pub is_method: bool,
43
44    /// A set of symbols present on this scope level.
45    pub symbols: IndexMap<Name, Symbol>,
46
47    /// A list of sub-scopes in the order as found in the
48    /// AST nodes.
49    pub sub_tables: Vec<Self>,
50
51    /// Annotation scopes registered in st_blocks but not added
52    /// to ste_children, e.g. future-annotation function signatures.
53    pub hidden_annotation_blocks: Vec<Self>,
54
55    /// Cursor pointing to the next hidden annotation block to consume.
56    pub next_hidden_annotation_block: usize,
57
58    /// Inlined comprehension scopes removed from ste_children but
59    /// can still find through st_blocks keyed by the comprehension expression.
60    pub inlined_comprehension_blocks: Vec<Self>,
61
62    /// Cursor pointing to the next inlined comprehension block to consume.
63    pub next_inlined_comprehension_block: usize,
64
65    /// Cursor pointing to the next sub-table to consume during compilation.
66    pub next_sub_table: usize,
67
68    /// Variable names in definition order (parameters first, then locals)
69    pub varnames: Vec<Name>,
70
71    /// Whether this class scope needs an implicit __class__ cell
72    pub needs_class_closure: bool,
73
74    /// Whether this class scope needs an implicit __classdict__ cell
75    pub needs_classdict: bool,
76
77    /// Whether this type param scope can see the parent class scope
78    pub can_see_class_scope: bool,
79
80    /// Whether this scope contains yield/yield from (is a generator function)
81    pub is_generator: bool,
82
83    /// Whether this scope contains await or async comprehension machinery.
84    pub is_coroutine: bool,
85
86    /// Whether this scope contains a return statement with a value.
87    pub returns_value: bool,
88
89    /// Whether this block visited at least one annotation expression.
90    pub annotations_used: bool,
91
92    /// Optional description of the current type-variable evaluator context.
93    pub scope_info: Option<&'static str>,
94
95    /// Whether this annotation block is currently visiting an unevaluated
96    /// function-local annotation.
97    pub in_unevaluated_annotation: bool,
98
99    /// Whether this comprehension scope should be inlined (PEP 709)
100    /// True for list/set/dict comprehensions in non-generator expressions
101    pub comp_inlined: bool,
102
103    /// PEP 649: Reference to annotation scope for this block
104    /// Annotations are compiled as a separate `__annotate__` function
105    pub annotation_block: Option<Box<Self>>,
106
107    /// True only for deferred function/class/module annotation scopes that
108    /// should resolve outer names as if they were siblings of the owning
109    /// function body, matching PEP 649 lookup rules.
110    pub skip_enclosing_function_scope: bool,
111
112    /// PEP 649: Whether this scope has conditional annotations
113    /// (annotations inside if/for/while/etc. blocks or at module level)
114    pub has_conditional_annotations: bool,
115
116    /// Whether `from __future__ import annotations` is active
117    pub future_annotations: bool,
118
119    /// Names of type parameters that should still be mangled in type param scopes.
120    /// When Some, only names in this set are mangled; other names are left unmangled.
121    /// Set on type param blocks for generic classes; inherited by non-class child scopes.
122    pub mangled_names: Option<IndexSet<Name>>,
123}
124
125impl SymbolTable {
126    fn new(
127        name: Name,
128        typ: CompilerScope,
129        line_number: u32,
130        is_nested: bool,
131        block_index: usize,
132    ) -> Self {
133        Self {
134            name,
135            typ,
136            line_number,
137            block_index,
138            is_nested,
139            is_method: false,
140            symbols: IndexMap::default(),
141            sub_tables: vec![],
142            hidden_annotation_blocks: vec![],
143            next_hidden_annotation_block: 0,
144            inlined_comprehension_blocks: vec![],
145            next_inlined_comprehension_block: 0,
146            next_sub_table: 0,
147            varnames: Vec::new(),
148            needs_class_closure: false,
149            needs_classdict: false,
150            can_see_class_scope: false,
151            is_generator: false,
152            is_coroutine: false,
153            returns_value: false,
154            annotations_used: false,
155            scope_info: None,
156            in_unevaluated_annotation: false,
157            comp_inlined: false,
158            annotation_block: None,
159            skip_enclosing_function_scope: false,
160            has_conditional_annotations: false,
161            future_annotations: false,
162            mangled_names: None,
163        }
164    }
165
166    fn add_format_parameter(&mut self) {
167        let name = Name::new_static(".format");
168        let symbol = self
169            .symbols
170            .entry(name.clone())
171            .or_insert_with(|| Symbol::new(name.clone()));
172        symbol
173            .flags
174            .insert(SymbolFlags::DEF_PARAM | SymbolFlags::USE);
175        if !self.varnames.contains(&name) {
176            self.varnames.push(name);
177        }
178    }
179
180    pub fn scan_program(
181        program: &ast::ModModule,
182        source_file: SourceFile,
183    ) -> SymbolTableResult<Self> {
184        Self::scan_program_with_options(program, source_file, false, false, DEFAULT_RECURSION_LIMIT)
185    }
186
187    pub fn scan_program_with_options(
188        program: &ast::ModModule,
189        source_file: SourceFile,
190        allow_top_level_await: bool,
191        future_annotations: bool,
192        recursion_limit: usize,
193    ) -> SymbolTableResult<Self> {
194        let mut builder = SymbolTableBuilder::new(source_file);
195        builder.allow_top_level_await = allow_top_level_await;
196        builder.recursion_limit = recursion_limit;
197        builder.future_annotations = future_annotations
198            || SymbolTableBuilder::future_annotations_from_module_body(program.body.as_ref());
199        builder.scan_statements(program.body.as_ref())?;
200        builder.finish()
201    }
202
203    pub fn scan_expr(
204        expr: &ast::ModExpression,
205        source_file: SourceFile,
206    ) -> SymbolTableResult<Self> {
207        Self::scan_expr_with_options(expr, source_file, false, false, DEFAULT_RECURSION_LIMIT)
208    }
209
210    pub fn scan_expr_with_options(
211        expr: &ast::ModExpression,
212        source_file: SourceFile,
213        allow_top_level_await: bool,
214        future_annotations: bool,
215        recursion_limit: usize,
216    ) -> SymbolTableResult<Self> {
217        let mut builder = SymbolTableBuilder::new(source_file);
218        builder.allow_top_level_await = allow_top_level_await;
219        builder.recursion_limit = recursion_limit;
220        builder.future_annotations = future_annotations;
221        builder.scan_expression(expr.body.as_ref(), ExpressionContext::Load)?;
222        builder.finish()
223    }
224
225    #[must_use]
226    pub fn lookup(&self, name: &Name) -> Option<&Symbol> {
227        self.symbols.get(name)
228    }
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232pub enum CompilerScope {
233    Module,
234    Class,
235    Function,
236    AsyncFunction,
237    Lambda,
238    Comprehension,
239    TypeParams,
240    /// PEP 649: Annotation scope for deferred evaluation
241    Annotation,
242    TypeAlias,
243    TypeVariable,
244}
245
246impl fmt::Display for CompilerScope {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        match self {
249            Self::Module => write!(f, "module"),
250            Self::Class => write!(f, "class"),
251            Self::Function => write!(f, "function"),
252            Self::AsyncFunction => write!(f, "async function"),
253            Self::Lambda => write!(f, "lambda"),
254            Self::Comprehension => write!(f, "comprehension"),
255            Self::TypeParams => write!(f, "type parameter"),
256            Self::Annotation => write!(f, "annotation"),
257            Self::TypeAlias => write!(f, "type alias"),
258            Self::TypeVariable => write!(f, "TypeVar bound"),
259        }
260    }
261}
262
263/// Indicator for a single symbol what the scope of this symbol is.
264/// The scope can be unknown, which is unfortunate, but not impossible.
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub enum SymbolScope {
267    Unknown,
268    Local,
269    GlobalExplicit,
270    GlobalImplicit,
271    Free,
272    Cell,
273}
274
275impl SymbolScope {
276    /// Returns the [`i32`] representation of this symbol scope.
277    ///
278    /// # See also
279    /// [CPython's definition](https://github.com/python/cpython/blob/v3.14.6/Include/internal/pycore_symtable.h#L180-L184)
280    #[must_use]
281    pub const fn as_i32(&self) -> i32 {
282        match self {
283            Self::Unknown => 0,
284            Self::Local => 1,
285            Self::GlobalExplicit => 2,
286            Self::GlobalImplicit => 3,
287            Self::Free => 4,
288            Self::Cell => 5,
289        }
290    }
291}
292
293impl From<SymbolScope> for i32 {
294    fn from(scope: SymbolScope) -> Self {
295        scope.as_i32()
296    }
297}
298
299bitflags! {
300    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
301    pub struct SymbolFlags: u16 {
302        const DEF_GLOBAL   = 1;
303        const DEF_LOCAL    = 2;
304        const DEF_PARAM    = 2 << 1;
305        const DEF_NONLOCAL = 2 << 2;
306        const USE          = 2 << 3;
307        /// indicates that the symbol is a free variable in a class method from the scope that the
308        /// class is defined in, e.g.:
309        /// ```python
310        /// def foo(x):
311        ///     class A:
312        ///         def method(self):
313        ///             return x // is_free_class
314        /// ```
315        const DEF_FREE_CLASS = 2 << 5;
316        const DEF_IMPORT     = 2 << 6;
317        const DEF_ANNOT      = 2 << 7;
318        const DEF_COMP_ITER  = 2 << 8;
319        const DEF_TYPE_PARAM = 2 << 9;
320        const DEF_COMP_CELL  = 2 << 10;
321        const DEF_BOUND = (
322            Self::DEF_LOCAL.bits()
323            | Self::DEF_PARAM.bits()
324            | Self::DEF_IMPORT.bits()
325            | Self::DEF_TYPE_PARAM.bits()
326        );
327    }
328}
329
330/// A single symbol in a table. Has various properties such as the scope
331/// of the symbol, and also the various uses of the symbol.
332#[derive(Debug, Clone)]
333pub struct Symbol {
334    pub name: Name,
335    pub scope: SymbolScope,
336    pub flags: SymbolFlags,
337    pub location: Option<SourceLocation>,
338    pub end_location: Option<SourceLocation>,
339}
340
341impl Symbol {
342    fn new(name: Name) -> Self {
343        Self {
344            name,
345            // table,
346            scope: SymbolScope::Unknown,
347            flags: SymbolFlags::empty(),
348            location: None,
349            end_location: None,
350        }
351    }
352
353    #[must_use]
354    pub const fn is_global(&self) -> bool {
355        matches!(
356            self.scope,
357            SymbolScope::GlobalExplicit | SymbolScope::GlobalImplicit
358        )
359    }
360
361    #[must_use]
362    pub const fn is_local(&self) -> bool {
363        matches!(self.scope, SymbolScope::Local | SymbolScope::Cell)
364    }
365
366    #[must_use]
367    pub const fn is_bound(&self) -> bool {
368        self.flags.intersects(SymbolFlags::DEF_BOUND)
369    }
370}
371
372#[derive(Debug)]
373pub struct SymbolTableError {
374    error: String,
375    location: Option<SourceLocation>,
376    end_location: Option<SourceLocation>,
377}
378
379impl SymbolTableError {
380    #[must_use]
381    pub fn into_codegen_error(self, source_path: String) -> CodegenError {
382        let error = if self.error == RECURSION_ERROR {
383            CodegenErrorType::RecursionError
384        } else {
385            CodegenErrorType::SyntaxError(self.error)
386        };
387        CodegenError {
388            location: self.location,
389            end_location: self.end_location,
390            error,
391            source_path,
392        }
393    }
394}
395
396type SymbolTableResult<T = ()> = Result<T, SymbolTableError>;
397
398impl core::fmt::Debug for SymbolTable {
399    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
400        write!(
401            f,
402            "SymbolTable({:?} symbols, {:?} sub scopes)",
403            self.symbols.len(),
404            self.sub_tables.len()
405        )
406    }
407}
408
409/* Perform some sort of analysis on nonlocals, globals etc..
410  See also: https://github.com/python/cpython/blob/main/Python/symtable.c#L410
411*/
412fn analyze_symbol_table(symbol_table: &mut SymbolTable) -> SymbolTableResult {
413    let mut analyzer = SymbolTableAnalyzer::default();
414    // Discard the newfree set at the top level - it's only needed for propagation
415    // Pass None for class_entry at top level
416    let _newfree = analyzer.analyze_symbol_table(symbol_table, None)?;
417    Ok(())
418}
419
420/* Drop __class__ and __classdict__ from free variables in class scope
421   and set the appropriate flags. Equivalent to drop_class_free().
422   See: https://github.com/python/cpython/blob/main/Python/symtable.c#L884
423
424   This function removes __class__ and __classdict__ from the
425   `newfree` set (which contains free variables collected from all child scopes)
426   and sets the corresponding flags on the class's symbol table entry.
427*/
428fn drop_class_free(symbol_table: &mut SymbolTable, newfree: &mut IndexSet<Name>) {
429    // Check if __class__ is in the free variables collected from children
430    // If found, it means a child scope (method) references __class__
431    if newfree.shift_remove("__class__") {
432        symbol_table.needs_class_closure = true;
433    }
434
435    // Check if __classdict__ is in the free variables collected from children
436    if newfree.shift_remove("__classdict__") {
437        symbol_table.needs_classdict = true;
438    }
439
440    // Check if __conditional_annotations__ is in the free variables collected from children
441    // Remove it from free set - it's handled specially in class scope
442    if newfree.shift_remove("__conditional_annotations__") {
443        symbol_table.has_conditional_annotations = true;
444    }
445}
446
447/// PEP 709: Merge symbols from an inlined comprehension into the parent scope.
448/// Matches symtable.c inline_comprehension().
449fn inline_comprehension(
450    parent_symbols: &mut SymbolMap,
451    comp: &SymbolTable,
452    comp_free: &mut IndexSet<Name>,
453    inlined_cells: &mut IndexSet<Name>,
454    parent_type: CompilerScope,
455) -> IndexSet<Name> {
456    let mut removed_class_implicits = IndexSet::default();
457    for (name, sub_symbol) in &comp.symbols {
458        // Skip the .0 parameter
459        if sub_symbol.flags.contains(SymbolFlags::DEF_PARAM) {
460            continue;
461        }
462
463        // Track inlined cells
464        if sub_symbol.scope == SymbolScope::Cell
465            || sub_symbol.flags.contains(SymbolFlags::DEF_COMP_CELL)
466        {
467            inlined_cells.insert(name.clone());
468        }
469
470        // `__class__` is never allowed to be free through a class scope, see
471        // drop_class_free(). `__classdict__` and `__conditional_annotations__`
472        // have no binding to reach either, and unlike CPython -- which fails with
473        // an internal error while compiling such a class -- they resolve as
474        // globals here.
475        let scope = if sub_symbol.scope == SymbolScope::Free
476            && parent_type == CompilerScope::Class
477            && matches!(
478                name.as_str(),
479                "__class__" | "__classdict__" | "__conditional_annotations__"
480            ) {
481            let is_free_in_child = comp.sub_tables.iter().any(|child| {
482                child
483                    .symbols
484                    .get(name)
485                    .is_some_and(|s| s.scope == SymbolScope::Free)
486            });
487            if !is_free_in_child {
488                comp_free.swap_remove(name);
489            }
490            removed_class_implicits.insert(name.clone());
491            SymbolScope::GlobalImplicit
492        } else {
493            sub_symbol.scope
494        };
495
496        if let Some(existing) = parent_symbols.get_mut(name) {
497            // A name the parent only learned about as a free variable of this
498            // comprehension carries no definition of its own; the comprehension's
499            // flags are what describe it.
500            if existing
501                .flags
502                .difference(SymbolFlags::DEF_FREE_CLASS)
503                .is_empty()
504            {
505                existing.flags.insert(sub_symbol.flags);
506                existing.scope = scope;
507            }
508            // Name exists in parent
509            if existing.is_bound() && parent_type != CompilerScope::Class {
510                // Check if the name is free in any child of the comprehension
511                let is_free_in_child = comp.sub_tables.iter().any(|child| {
512                    child
513                        .symbols
514                        .get(name)
515                        .is_some_and(|s| s.scope == SymbolScope::Free)
516                });
517                if !is_free_in_child {
518                    comp_free.swap_remove(name);
519                }
520            }
521        } else {
522            // Name doesn't exist in parent, copy the comprehension binding.
523            // Matches inline_comprehension(): newly introduced
524            // comprehension locals stay locals in the parent scope.
525            let mut symbol = sub_symbol.clone();
526            symbol.scope = scope;
527            parent_symbols.insert(name.clone(), symbol);
528        }
529    }
530    removed_class_implicits
531}
532
533type SymbolMap = IndexMap<Name, Symbol>;
534
535mod stack {
536    use alloc::vec::Vec;
537    use core::ptr::NonNull;
538    pub(super) struct StackStack<T> {
539        v: Vec<NonNull<T>>,
540    }
541    impl<T> Default for StackStack<T> {
542        fn default() -> Self {
543            Self { v: Vec::new() }
544        }
545    }
546    impl<T> StackStack<T> {
547        /// Appends a reference to this stack for the duration of the function `f`. When `f`
548        /// returns, the reference will be popped off the stack.
549        #[cfg(feature = "std")]
550        pub(super) fn with_append<F, R>(&mut self, x: &mut T, f: F) -> R
551        where
552            F: FnOnce(&mut Self) -> R,
553        {
554            self.v.push(x.into());
555            let res = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| f(self)));
556            self.v.pop();
557            res.unwrap_or_else(|x| std::panic::resume_unwind(x))
558        }
559
560        /// Appends a reference to this stack for the duration of the function `f`. When `f`
561        /// returns, the reference will be popped off the stack.
562        ///
563        /// Without std, panic cleanup is not guaranteed (no catch_unwind).
564        #[cfg(not(feature = "std"))]
565        pub fn with_append<F, R>(&mut self, x: &mut T, f: F) -> R
566        where
567            F: FnOnce(&mut Self) -> R,
568        {
569            self.v.push(x.into());
570            let result = f(self);
571            self.v.pop();
572            result
573        }
574
575        pub(super) fn iter(&self) -> impl DoubleEndedIterator<Item = &T> + '_ {
576            self.as_ref().iter().copied()
577        }
578        pub(super) fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> + '_ {
579            self.as_mut().iter_mut().map(|x| &mut **x)
580        }
581        pub(super) fn len(&self) -> usize {
582            self.v.len()
583        }
584        pub(super) fn is_empty(&self) -> bool {
585            self.len() == 0
586        }
587
588        pub(super) fn as_ref(&self) -> &[&T] {
589            unsafe { &*(self.v.as_slice() as *const [NonNull<T>] as *const [&T]) }
590        }
591
592        pub(super) fn as_mut(&mut self) -> &mut [&mut T] {
593            unsafe { &mut *(self.v.as_mut_slice() as *mut [NonNull<T>] as *mut [&mut T]) }
594        }
595    }
596}
597use stack::StackStack;
598
599/// Symbol table analysis. Can be used to analyze a fully
600/// build symbol table structure. It will mark variables
601/// as local variables for example.
602#[derive(Default)]
603#[repr(transparent)]
604struct SymbolTableAnalyzer {
605    tables: StackStack<(SymbolMap, CompilerScope, bool)>,
606}
607
608impl SymbolTableAnalyzer {
609    /// Analyze a symbol table and return the set of free variables.
610    /// See symtable.c analyze_block().
611    /// class_entry: PEP 649 - enclosing class symbols for annotation scopes
612    fn analyze_symbol_table(
613        &mut self,
614        symbol_table: &mut SymbolTable,
615        class_entry: Option<&SymbolMap>,
616    ) -> SymbolTableResult<IndexSet<Name>> {
617        let symbols = core::mem::take(&mut symbol_table.symbols);
618        let sub_tables = &mut *symbol_table.sub_tables;
619
620        let annotation_block = &mut symbol_table.annotation_block;
621
622        // PEP 649: Determine class_entry to pass to children
623        let is_class = symbol_table.typ == CompilerScope::Class;
624
625        // Clone class symbols if needed for child scopes with can_see_class_scope
626        let needs_class_symbols = (is_class
627            && (sub_tables.iter().any(|st| st.can_see_class_scope)
628                || annotation_block
629                    .as_ref()
630                    .is_some_and(|b| b.can_see_class_scope)))
631            || (!is_class
632                && class_entry.is_some()
633                && sub_tables.iter().any(|st| st.can_see_class_scope));
634
635        let class_symbols_clone = if is_class && needs_class_symbols {
636            Some(symbols.clone())
637        } else {
638            None
639        };
640
641        // Collect (child_free, is_inlined) pairs from child scopes.
642        // We need to process inlined comprehensions after the closure
643        // when we have access to symbol_table.symbols.
644        let mut child_frees: Vec<(IndexSet<Name>, bool)> = Vec::new();
645        let mut annotation_free: Option<IndexSet<Name>> = None;
646
647        let mut info = (
648            symbols,
649            symbol_table.typ,
650            symbol_table.skip_enclosing_function_scope,
651        );
652        let class_scope_entry = if is_class {
653            class_symbols_clone.as_ref()
654        } else {
655            class_entry
656        };
657        self.tables.with_append(&mut info, |list| {
658            let inner_scope = unsafe { &mut *(list as *mut _ as *mut Self) };
659            for sub_table in sub_tables.iter_mut() {
660                let child_class_entry = sub_table
661                    .can_see_class_scope
662                    .then_some(class_scope_entry)
663                    .flatten();
664                let child_free = inner_scope.analyze_symbol_table(sub_table, child_class_entry)?;
665                child_frees.push((child_free, sub_table.comp_inlined));
666            }
667            // PEP 649: Analyze annotation block if present
668            if let Some(annotation_table) = annotation_block {
669                let ann_class_entry = annotation_table
670                    .can_see_class_scope
671                    .then_some(class_scope_entry)
672                    .flatten();
673                let child_free =
674                    inner_scope.analyze_symbol_table(annotation_table, ann_class_entry)?;
675                annotation_free = Some(child_free);
676            }
677            Ok(())
678        })?;
679
680        symbol_table.symbols = info.0;
681
682        // PEP 709: Process inlined comprehensions.
683        // Merge symbols from inlined comps into parent scope without bail-out.
684        let mut inlined_cells: IndexSet<Name> = IndexSet::default();
685        let mut newfree = IndexSet::default();
686        for (idx, (mut child_free, is_inlined)) in child_frees.into_iter().enumerate() {
687            if is_inlined {
688                let removed_class_implicit = inline_comprehension(
689                    &mut symbol_table.symbols,
690                    &symbol_table.sub_tables[idx],
691                    &mut child_free,
692                    &mut inlined_cells,
693                    symbol_table.typ,
694                );
695                for name in removed_class_implicit {
696                    symbol_table.sub_tables[idx]
697                        .symbols
698                        .shift_remove(name.as_str());
699                }
700            }
701            newfree.extend(child_free);
702        }
703        if let Some(ann_free) = annotation_free
704            && symbol_table.typ == CompilerScope::Class
705        {
706            // Annotation-only free variables should not leak into function
707            // bodies. We only need to propagate them through class scopes so
708            // drop_class_free() can materialize implicit class cells when
709            // annotation scopes reference them.
710            newfree.extend(ann_free);
711        }
712
713        let mut inlined_blocks = Vec::new();
714        let mut idx = 0;
715        while idx < symbol_table.sub_tables.len() {
716            if symbol_table.sub_tables[idx].comp_inlined {
717                let comp = symbol_table.sub_tables.remove(idx);
718                let nested_inlined_blocks = comp.inlined_comprehension_blocks.clone();
719                let children = comp.sub_tables.clone();
720                let inserted = children.len();
721                inlined_blocks.push(comp);
722                inlined_blocks.extend(nested_inlined_blocks);
723                symbol_table.sub_tables.splice(idx..idx, children);
724                idx += inserted;
725            } else {
726                idx += 1;
727            }
728        }
729        symbol_table
730            .inlined_comprehension_blocks
731            .extend(inlined_blocks);
732
733        let sub_tables = &*symbol_table.sub_tables;
734
735        for symbol in symbol_table.symbols.values_mut() {
736            if inlined_cells.contains(&symbol.name) {
737                symbol.flags.insert(SymbolFlags::DEF_COMP_CELL);
738            }
739        }
740
741        // Analyze symbols in current scope
742        let function_like_scope = SymbolTableBuilder::is_function_like_scope(symbol_table.typ);
743        for symbol in symbol_table.symbols.values_mut() {
744            self.analyze_symbol(
745                symbol,
746                symbol_table.typ,
747                symbol_table.skip_enclosing_function_scope,
748                sub_tables,
749                class_entry,
750            )?;
751
752            // analyze_cells(): once a function-like scope owns a
753            // child-requested name as a cell, that name is no longer free in
754            // the enclosing scope.
755            if function_like_scope && symbol.scope == SymbolScope::Cell {
756                newfree.shift_remove(symbol.name.as_str());
757            }
758
759            // Collect free variables from this scope
760            if symbol.scope == SymbolScope::Free
761                || symbol.flags.contains(SymbolFlags::DEF_FREE_CLASS)
762            {
763                newfree.insert(symbol.name.clone());
764            }
765        }
766
767        // PEP 709 / symtable.c:
768        // - only promote LOCAL -> CELL in function-like scopes, where
769        //   analyze_cells() runs. Module and class scopes keep their normal
770        //   scope and rely on DEF_COMP_CELL for comprehension-only cells.
771        for symbol in symbol_table.symbols.values_mut() {
772            if inlined_cells.contains(&symbol.name)
773                && function_like_scope
774                && symbol.scope == SymbolScope::Local
775            {
776                symbol.scope = SymbolScope::Cell;
777            }
778        }
779
780        // Handle class-specific implicit cells
781        if symbol_table.typ == CompilerScope::Class {
782            drop_class_free(symbol_table, &mut newfree);
783        }
784
785        // update_symbols(..., classflag): after class implicit frees
786        // are dropped, a class block, or an annotation/type-params block that
787        // can see a class scope, records existing child-free names with
788        // DEF_FREE_CLASS. This preserves the current scope's own lookup kind
789        // (for example GLOBAL_IMPLICIT via __classdict__) while still making
790        // the name available as a closure cell for nested children such as
791        // generator expressions.
792        if symbol_table.typ == CompilerScope::Class || symbol_table.can_see_class_scope {
793            for name in &newfree {
794                if let Some(symbol) = symbol_table.symbols.get_mut(name) {
795                    symbol.flags.insert(SymbolFlags::DEF_FREE_CLASS);
796                }
797            }
798        }
799
800        Ok(newfree)
801    }
802
803    fn analyze_symbol(
804        &mut self,
805        symbol: &mut Symbol,
806        st_typ: CompilerScope,
807        skip_enclosing_function_scope: bool,
808        sub_tables: &[SymbolTable],
809        class_entry: Option<&SymbolMap>,
810    ) -> SymbolTableResult {
811        if symbol
812            .flags
813            .contains(SymbolFlags::DEF_GLOBAL | SymbolFlags::DEF_NONLOCAL)
814        {
815            return Err(SymbolTableError {
816                error: format!("name '{}' is nonlocal and global", symbol.name),
817                location: symbol.location,
818                end_location: symbol.end_location,
819            });
820        }
821        match symbol.scope {
822            // Only an explicit `nonlocal` has to name a binding. Every other free
823            // variable was already resolved by the scope it travelled up from.
824            SymbolScope::Free if symbol.flags.contains(SymbolFlags::DEF_NONLOCAL) => {
825                if !self.tables.as_ref().is_empty() {
826                    let scope_depth = self.tables.as_ref().len();
827                    // check if the name is already defined in any outer scope
828                    if scope_depth < 2
829                        || self.found_in_outer_scope(
830                            &symbol.name,
831                            st_typ,
832                            skip_enclosing_function_scope,
833                        ) != Some(SymbolScope::Free)
834                    {
835                        return Err(SymbolTableError {
836                            error: format!("no binding for nonlocal '{}' found", symbol.name),
837                            location: symbol.location,
838                            end_location: symbol.end_location,
839                        });
840                    }
841                    // Check if the nonlocal binding refers to a type parameter
842                    for (symbols, _typ, _skip) in self.tables.iter().rev() {
843                        if let Some(sym) = symbols.get(&symbol.name) {
844                            if sym.flags.contains(SymbolFlags::DEF_TYPE_PARAM) {
845                                return Err(SymbolTableError {
846                                    error: format!(
847                                        "nonlocal binding not allowed for type parameter '{}'",
848                                        symbol.name
849                                    ),
850                                    location: symbol.location,
851                                    end_location: symbol.end_location,
852                                });
853                            }
854                            if sym.is_bound() {
855                                break;
856                            }
857                        }
858                    }
859                } else {
860                    return Err(SymbolTableError {
861                        error: format!(
862                            "nonlocal {} defined at place without an enclosing scope",
863                            symbol.name
864                        ),
865                        location: symbol.location,
866                        end_location: symbol.end_location,
867                    });
868                }
869            }
870            SymbolScope::Free => {}
871            SymbolScope::GlobalExplicit | SymbolScope::GlobalImplicit => {}
872            SymbolScope::Local | SymbolScope::Cell => {}
873            SymbolScope::Unknown => {
874                // Try hard to figure out what the scope of this symbol is.
875                let scope = if symbol.is_bound() {
876                    if symbol.flags.contains(SymbolFlags::DEF_COMP_CELL)
877                        && matches!(st_typ, CompilerScope::Module | CompilerScope::Class)
878                    {
879                        // CPython keeps comprehension-only cells in
880                        // module/class scopes as normal local/name
881                        // bindings and uses DEF_COMP_CELL to allocate the
882                        // synthetic cell slot. The spliced comp child
883                        // should not force the outer name itself to CELL.
884                        SymbolScope::Local
885                    } else {
886                        self.found_in_inner_scope(sub_tables, &symbol.name, st_typ)
887                            .unwrap_or(SymbolScope::Local)
888                    }
889                } else if let Some(scope) = class_entry
890                    .and_then(|class_symbols| class_symbols.get(&symbol.name))
891                    .and_then(|class_sym| {
892                        if class_sym.flags.contains(SymbolFlags::DEF_GLOBAL) {
893                            Some(SymbolScope::GlobalExplicit)
894                        } else if class_sym.is_bound() && class_sym.scope != SymbolScope::Free {
895                            // If name is bound in enclosing class, use GlobalImplicit
896                            // so it can be accessed via __classdict__
897                            Some(SymbolScope::GlobalImplicit)
898                        } else {
899                            None
900                        }
901                    })
902                {
903                    scope
904                } else if let Some(scope) =
905                    self.found_in_outer_scope(&symbol.name, st_typ, skip_enclosing_function_scope)
906                {
907                    // If found in enclosing scope (function/TypeParams), use that
908                    scope
909                } else {
910                    // A name bound nowhere is global.
911                    SymbolScope::GlobalImplicit
912                };
913                symbol.scope = scope;
914            }
915        }
916        Ok(())
917    }
918
919    fn found_in_outer_scope(
920        &mut self,
921        name: &Name,
922        st_typ: CompilerScope,
923        skip_enclosing_function_scope: bool,
924    ) -> Option<SymbolScope> {
925        let mut decl_depth = None;
926        for (i, (symbols, typ, _skip)) in self.tables.iter().rev().enumerate() {
927            if matches!(typ, CompilerScope::Module)
928                || matches!(typ, CompilerScope::Class if name != "__class__" && name != "__classdict__" && name != "__conditional_annotations__")
929            {
930                continue;
931            }
932
933            // Real PEP 649 annotation blocks resolve names as siblings of the
934            // owning function body. Other annotation-like scopes such as type
935            // aliases and TypeVar bound/default evaluators keep normal lexical
936            // lookup and therefore leave this path disabled.
937            if st_typ == CompilerScope::Annotation
938                && skip_enclosing_function_scope
939                && i == 0
940                && matches!(
941                    typ,
942                    CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda
943                )
944            {
945                continue;
946            }
947
948            // __class__ and __classdict__ are implicitly declared in class scope
949            // This handles the case where nested scopes reference them
950            if (name == "__class__" || name == "__classdict__")
951                && matches!(typ, CompilerScope::Class)
952            {
953                decl_depth = Some(i);
954                break;
955            }
956
957            // __conditional_annotations__ is implicitly declared in class scope
958            // for classes with conditional annotations
959            if name == "__conditional_annotations__" && matches!(typ, CompilerScope::Class) {
960                decl_depth = Some(i);
961                break;
962            }
963
964            if let Some(sym) = symbols.get(name) {
965                match sym.scope {
966                    // A global declaration binds nothing, so a name it covers is
967                    // global here only by not being found anywhere else.
968                    SymbolScope::GlobalExplicit => return Some(SymbolScope::GlobalImplicit),
969                    SymbolScope::GlobalImplicit => {}
970                    _ => {
971                        if sym.is_bound() {
972                            decl_depth = Some(i);
973                            break;
974                        }
975                    }
976                }
977            }
978        }
979
980        if let Some(decl_depth) = decl_depth {
981            // decl_depth is the number of tables between the current one and
982            // the one that declared the cell var
983            // For implicit class scope variables (__classdict__, __conditional_annotations__),
984            // only propagate free to annotation/type-param scopes, not regular functions.
985            // Regular method functions don't need these in their freevars.
986            let is_class_implicit =
987                name == "__classdict__" || name == "__conditional_annotations__";
988
989            for (table, typ, _skip) in self.tables.iter_mut().rev().take(decl_depth) {
990                if let CompilerScope::Class = typ {
991                    if let Some(free_class) = table.get_mut(name) {
992                        free_class.flags.insert(SymbolFlags::DEF_FREE_CLASS)
993                    } else {
994                        let mut symbol = Symbol::new(name.clone());
995                        symbol.flags.insert(SymbolFlags::DEF_FREE_CLASS);
996                        symbol.scope = SymbolScope::Free;
997                        table.insert(name.to_owned(), symbol);
998                    }
999                } else if is_class_implicit
1000                    && matches!(
1001                        typ,
1002                        CompilerScope::Function
1003                            | CompilerScope::AsyncFunction
1004                            | CompilerScope::Lambda
1005                    )
1006                {
1007                    // Skip: don't add __classdict__/__conditional_annotations__
1008                    // as free vars in regular functions — only annotation/type scopes need them
1009                } else if !table.contains_key(name) {
1010                    let mut symbol = Symbol::new(name.clone());
1011                    symbol.scope = SymbolScope::Free;
1012                    table.insert(name.to_owned(), symbol);
1013                }
1014            }
1015        }
1016
1017        decl_depth.map(|_| SymbolScope::Free)
1018    }
1019
1020    fn found_in_inner_scope(
1021        &self,
1022        sub_tables: &[SymbolTable],
1023        name: &Name,
1024        st_typ: CompilerScope,
1025    ) -> Option<SymbolScope> {
1026        sub_tables.iter().find_map(|st| {
1027            // PEP 709: For inlined comprehensions, check their children
1028            // instead of the comp itself (its symbols are merged into parent).
1029            if st.comp_inlined {
1030                return self.found_in_inner_scope(&st.sub_tables, name, st_typ);
1031            }
1032            let sym = st.symbols.get(name)?;
1033            if sym.scope == SymbolScope::Free
1034                || (sym.flags.contains(SymbolFlags::DEF_FREE_CLASS)
1035                    && !matches!(st_typ, CompilerScope::Module))
1036            {
1037                if st_typ == CompilerScope::Class {
1038                    None
1039                } else {
1040                    Some(SymbolScope::Cell)
1041                }
1042            } else if sym.scope == SymbolScope::GlobalExplicit && self.tables.is_empty() {
1043                // the symbol is defined on the module level, and an inner scope declares
1044                // a global that points to it
1045                Some(SymbolScope::GlobalExplicit)
1046            } else {
1047                None
1048            }
1049        })
1050    }
1051}
1052
1053#[derive(Clone, Copy, Debug)]
1054enum SymbolUsage {
1055    Global,
1056    Nonlocal,
1057    Used,
1058    Assigned,
1059    Imported,
1060    AnnotationAssigned,
1061    Parameter,
1062    Iter,
1063    TypeParam,
1064}
1065
1066struct SymbolTableBuilder {
1067    class_name: Option<Name>,
1068    // Scope stack.
1069    tables: Vec<SymbolTable>,
1070    future_annotations: bool,
1071    allow_top_level_await: bool,
1072    source_file: SourceFile,
1073    // Current scope's varnames being collected (temporary storage)
1074    current_varnames: Vec<Name>,
1075    // Stack to preserve parent varnames when entering nested scopes
1076    varnames_stack: Vec<Vec<Name>>,
1077    // Track if we're inside an iterable definition expression (for nested comprehensions)
1078    in_iter_def_exp: bool,
1079    // yield/yield from inside comprehension scopes is rejected with a
1080    // message that names the comprehension kind.
1081    comprehension_yield_context: Option<&'static str>,
1082    // PEP 649: Track if we're inside a conditional block (if/for/while/etc.)
1083    in_conditional_block: bool,
1084    // Mirrors symtable ENTER_RECURSIVE guards during compilation.
1085    recursion_depth: usize,
1086    recursion_limit: usize,
1087    next_block_index: usize,
1088    done_with_future_stmts: DoneWithFuture,
1089}
1090
1091/// How far past the point where `from __future__ import` is still accepted the
1092/// module body has been scanned.
1093enum DoneWithFuture {
1094    No,
1095    DoneWithDoc,
1096    Yes,
1097}
1098
1099/// Enum to indicate in what mode an expression
1100/// was used.
1101/// In cpython this is stored in the AST, but I think this
1102/// is not logical, since it is not context free.
1103#[derive(Copy, Clone, PartialEq)]
1104enum ExpressionContext {
1105    Load,
1106    Store,
1107    Delete,
1108    Iter,
1109    IterDefinitionExp,
1110}
1111
1112impl SymbolTableBuilder {
1113    fn new(source_file: SourceFile) -> Self {
1114        let mut this = Self {
1115            class_name: None,
1116            tables: vec![],
1117            future_annotations: false,
1118            allow_top_level_await: false,
1119            source_file,
1120            current_varnames: Vec::new(),
1121            varnames_stack: Vec::new(),
1122            in_iter_def_exp: false,
1123            comprehension_yield_context: None,
1124            in_conditional_block: false,
1125            recursion_depth: 0,
1126            recursion_limit: DEFAULT_RECURSION_LIMIT,
1127            next_block_index: 0,
1128            done_with_future_stmts: DoneWithFuture::No,
1129        };
1130        this.enter_scope(&"top".into(), CompilerScope::Module, 0);
1131        this
1132    }
1133
1134    fn is_function_like_scope(typ: CompilerScope) -> bool {
1135        matches!(
1136            typ,
1137            CompilerScope::Function
1138                | CompilerScope::AsyncFunction
1139                | CompilerScope::Lambda
1140                | CompilerScope::Comprehension
1141                | CompilerScope::Annotation
1142                | CompilerScope::TypeAlias
1143                | CompilerScope::TypeVariable
1144                | CompilerScope::TypeParams
1145        )
1146    }
1147
1148    fn future_annotations_from_module_body(body: &[ast::Stmt]) -> bool {
1149        let mut statements = body.iter();
1150        if let Some(ast::Stmt::Expr(ast::StmtExpr { value, .. })) = statements.clone().next()
1151            && is_docstring_expr(value)
1152        {
1153            statements.next();
1154        }
1155        for statement in statements {
1156            match statement {
1157                ast::Stmt::ImportFrom(ast::StmtImportFrom {
1158                    module,
1159                    names,
1160                    level,
1161                    ..
1162                }) if *level == 0
1163                    && module.as_ref().map(|id| id.as_str()) == Some("__future__") =>
1164                {
1165                    if names
1166                        .iter()
1167                        .any(|future| future.name.as_str() == "annotations")
1168                    {
1169                        return true;
1170                    }
1171                }
1172                _ => return false,
1173            }
1174        }
1175        false
1176    }
1177
1178    fn finish(mut self) -> Result<SymbolTable, SymbolTableError> {
1179        assert_eq!(self.tables.len(), 1);
1180        let mut symbol_table = self.tables.pop().unwrap();
1181        // Save varnames for the top-level module scope
1182        symbol_table.varnames = self.current_varnames;
1183        // Propagate future_annotations to the symbol table
1184        symbol_table.future_annotations = self.future_annotations;
1185        analyze_symbol_table(&mut symbol_table)?;
1186        Ok(symbol_table)
1187    }
1188
1189    fn enter_scope(&mut self, name: &Name, typ: CompilerScope, line_number: u32) {
1190        let parent = self.tables.last();
1191        let is_nested =
1192            parent.is_some_and(|table| table.is_nested || Self::is_function_like_scope(table.typ));
1193        let is_method = parent.is_some_and(|table| {
1194            table.typ == CompilerScope::Class
1195                && matches!(
1196                    typ,
1197                    CompilerScope::Function
1198                        | CompilerScope::AsyncFunction
1199                        | CompilerScope::Lambda
1200                        | CompilerScope::Comprehension
1201                )
1202        });
1203        // Inherit mangled_names from parent for non-class scopes
1204        let inherited_mangled_names = self
1205            .tables
1206            .last()
1207            .and_then(|t| t.mangled_names.clone())
1208            .filter(|_| typ != CompilerScope::Class);
1209        let block_index = self.next_block_index;
1210        self.next_block_index += 1;
1211        let mut table = SymbolTable::new(name.to_owned(), typ, line_number, is_nested, block_index);
1212        table.is_method = is_method;
1213        table.future_annotations = self.future_annotations;
1214        table.mangled_names = inherited_mangled_names;
1215        self.tables.push(table);
1216        // Save parent's varnames and start fresh for the new scope
1217        self.varnames_stack
1218            .push(core::mem::take(&mut self.current_varnames));
1219    }
1220
1221    fn enter_type_param_block(
1222        &mut self,
1223        name: &Name,
1224        range: TextRange,
1225        for_class: bool,
1226        has_defaults: bool,
1227        has_kwdefaults: bool,
1228    ) -> SymbolTableResult {
1229        // Check if we're in a class scope
1230        let in_class = self
1231            .tables
1232            .last()
1233            .is_some_and(|t| t.typ == CompilerScope::Class);
1234
1235        self.enter_scope(
1236            name,
1237            CompilerScope::TypeParams,
1238            self.line_index_start(range),
1239        );
1240
1241        // Set properties on the newly created type param scope
1242        if let Some(table) = self.tables.last_mut() {
1243            table.can_see_class_scope = in_class;
1244            // For generic classes, create mangled_names set so that only
1245            // type parameter names get mangled (not bases or other expressions)
1246            if for_class {
1247                table.mangled_names = Some(IndexSet::default());
1248            }
1249        }
1250
1251        // Add __classdict__ as a USE symbol in type param scope if in class
1252        if in_class {
1253            self.register_name(&"__classdict__".into(), SymbolUsage::Used, range)?;
1254        }
1255
1256        if for_class {
1257            // It gets set when we create the type params tuple and used when
1258            // we build up the bases.
1259            self.register_name(&".type_params".into(), SymbolUsage::Assigned, range)?;
1260            self.register_name(&".type_params".into(), SymbolUsage::Used, range)?;
1261            self.register_name(&".generic_base".into(), SymbolUsage::Assigned, range)?;
1262            self.register_name(&".generic_base".into(), SymbolUsage::Used, range)?;
1263        }
1264        if has_defaults {
1265            self.register_name(&".defaults".into(), SymbolUsage::Parameter, range)?;
1266        }
1267        if has_kwdefaults {
1268            self.register_name(&".kwdefaults".into(), SymbolUsage::Parameter, range)?;
1269        }
1270
1271        Ok(())
1272    }
1273
1274    /// Pop symbol table and add to sub table of parent table.
1275    fn leave_scope(&mut self) {
1276        let mut table = self.tables.pop().unwrap();
1277        // Save the collected varnames to the symbol table
1278        table.varnames = core::mem::take(&mut self.current_varnames);
1279        self.tables.last_mut().unwrap().sub_tables.push(table);
1280        // Restore parent's varnames
1281        self.current_varnames = self.varnames_stack.pop().unwrap_or_default();
1282    }
1283
1284    /// Pop symbol table without adding it to the parent children list.
1285    fn discard_scope(&mut self) -> SymbolTable {
1286        let mut table = self.tables.pop().unwrap();
1287        table.varnames = core::mem::take(&mut self.current_varnames);
1288        self.current_varnames = self.varnames_stack.pop().unwrap_or_default();
1289        table
1290    }
1291
1292    fn resolve_future_annotation_names_as_globals(table: &mut SymbolTable) {
1293        for symbol in table.symbols.values_mut() {
1294            if symbol.scope == SymbolScope::Unknown
1295                && symbol.flags.contains(SymbolFlags::USE)
1296                && !symbol.is_bound()
1297            {
1298                symbol.scope = SymbolScope::GlobalImplicit;
1299            }
1300        }
1301    }
1302
1303    /// Enter annotation scope (PEP 649)
1304    /// Creates or reuses the annotation block for the current scope
1305    fn enter_annotation_scope(
1306        &mut self,
1307        line_number: u32,
1308        include_classdict_with_future: bool,
1309        include_conditional_annotations: bool,
1310    ) {
1311        let (can_see_class_scope, has_conditional, is_nested, needs_annotation_block) = {
1312            let current = self.tables.last().unwrap();
1313            (
1314                current.typ == CompilerScope::Class || current.can_see_class_scope,
1315                current.has_conditional_annotations,
1316                current.is_nested || Self::is_function_like_scope(current.typ),
1317                current.annotation_block.is_none(),
1318            )
1319        };
1320
1321        // Create annotation block if not exists
1322        if needs_annotation_block {
1323            let block_index = self.next_block_index;
1324            self.next_block_index += 1;
1325            let mut annotation_table = SymbolTable::new(
1326                Name::new_static("__annotate__"),
1327                CompilerScope::Annotation,
1328                line_number,
1329                is_nested,
1330                block_index,
1331            );
1332            // Annotation scope in class can see class scope
1333            annotation_table.can_see_class_scope = can_see_class_scope;
1334            annotation_table.skip_enclosing_function_scope = true;
1335            annotation_table.add_format_parameter();
1336            self.tables.last_mut().unwrap().annotation_block = Some(Box::new(annotation_table));
1337        }
1338
1339        // Take the annotation block and push to stack for processing
1340        let annotation_table = self
1341            .tables
1342            .last_mut()
1343            .unwrap()
1344            .annotation_block
1345            .take()
1346            .unwrap();
1347        self.tables.push(*annotation_table);
1348        // Save parent's varnames and seed with existing annotation varnames (e.g., "format")
1349        self.varnames_stack
1350            .push(core::mem::take(&mut self.current_varnames));
1351        self.current_varnames = self.tables.last().unwrap().varnames.clone();
1352
1353        if can_see_class_scope && (include_classdict_with_future || !self.future_annotations) {
1354            self.add_classdict_freevar();
1355            // The `__conditional_annotations__` cell is cooked up by the compiler,
1356            // so the block only records that it reads one.
1357            if include_conditional_annotations && has_conditional {
1358                self.tables.last_mut().unwrap().has_conditional_annotations = true;
1359            }
1360        }
1361    }
1362
1363    /// Leave annotation scope (PEP 649)
1364    /// Stores the annotation block back to parent instead of sub_tables
1365    fn leave_annotation_scope(&mut self) {
1366        let mut table = self.tables.pop().unwrap();
1367        // Save the collected varnames to the symbol table
1368        table.varnames = core::mem::take(&mut self.current_varnames);
1369        if self.future_annotations {
1370            Self::resolve_future_annotation_names_as_globals(&mut table);
1371        }
1372        // Store back to parent's annotation_block (not sub_tables)
1373        let parent = self.tables.last_mut().unwrap();
1374        parent.annotation_block = Some(Box::new(table));
1375        // Restore parent's varnames
1376        self.current_varnames = self.varnames_stack.pop().unwrap_or_default();
1377    }
1378
1379    fn add_classdict_freevar(&mut self) {
1380        let table = self.tables.last_mut().unwrap();
1381        let name = Name::new_static("__classdict__");
1382        let symbol = table
1383            .symbols
1384            .entry(name.clone())
1385            .or_insert_with(|| Symbol::new(name));
1386        symbol.scope = SymbolScope::Free;
1387        symbol
1388            .flags
1389            .insert(SymbolFlags::USE | SymbolFlags::DEF_FREE_CLASS);
1390    }
1391
1392    fn add_format_parameter(&mut self) {
1393        self.tables.last_mut().unwrap().add_format_parameter();
1394        if !self.current_varnames.iter().any(|name| name == ".format") {
1395            self.current_varnames.push(".format".into());
1396        }
1397    }
1398
1399    /// Walk up the scope chain to determine if we're inside an async function.
1400    /// Annotation and TypeParams scopes act as async barriers (always non-async).
1401    /// Comprehension scopes are transparent (inherit parent's async context).
1402    fn is_in_async_context(&self) -> bool {
1403        for table in self.tables.iter().rev() {
1404            match table.typ {
1405                CompilerScope::AsyncFunction => return true,
1406                CompilerScope::Function
1407                | CompilerScope::Lambda
1408                | CompilerScope::Class
1409                | CompilerScope::Module
1410                | CompilerScope::Annotation
1411                | CompilerScope::TypeAlias
1412                | CompilerScope::TypeVariable
1413                | CompilerScope::TypeParams => return false,
1414                // Comprehension inherits parent's async context
1415                CompilerScope::Comprehension => continue,
1416            }
1417        }
1418        false
1419    }
1420
1421    fn allows_top_level_await(&self) -> bool {
1422        self.allow_top_level_await
1423            && self
1424                .tables
1425                .last()
1426                .is_some_and(|table| table.typ == CompilerScope::Module)
1427    }
1428
1429    fn line_index_start(&self, range: TextRange) -> u32 {
1430        self.source_file
1431            .to_source_code()
1432            .line_index(range.start())
1433            .get() as _
1434    }
1435
1436    fn scan_statements(&mut self, statements: &[ast::Stmt]) -> SymbolTableResult {
1437        for statement in statements {
1438            self.scan_statement(statement)?;
1439        }
1440        Ok(())
1441    }
1442
1443    fn scan_parameters(&mut self, parameters: &[ast::ParameterWithDefault]) -> SymbolTableResult {
1444        for parameter in parameters {
1445            self.scan_parameter(&parameter.parameter)?;
1446        }
1447        Ok(())
1448    }
1449
1450    fn scan_parameter(&mut self, parameter: &ast::Parameter) -> SymbolTableResult {
1451        // Check for duplicate parameter names
1452        let table = self.tables.last().unwrap();
1453        if table.symbols.contains_key(parameter.name.as_str()) {
1454            return Err(self.error_ranged(
1455                format!(
1456                    "duplicate argument '{}' in function definition",
1457                    parameter.name
1458                ),
1459                parameter.name.range,
1460            ));
1461        }
1462
1463        self.register_ident(&parameter.name, SymbolUsage::Parameter)
1464    }
1465
1466    /// Scan an annotation from an AnnAssign statement (can be conditional)
1467    fn scan_ann_assign_annotation(&mut self, annotation: &ast::Expr) -> SymbolTableResult {
1468        self.scan_annotation_inner(annotation, true)
1469    }
1470
1471    fn scan_function_annotations(
1472        &mut self,
1473        parameters: &ast::Parameters,
1474        returns: Option<&ast::Expr>,
1475        line_number: u32,
1476    ) -> SymbolTableResult {
1477        let current = self.tables.last().unwrap();
1478        let can_see_class_scope =
1479            current.typ == CompilerScope::Class || current.can_see_class_scope;
1480        self.enter_scope(
1481            &"__annotate__".into(),
1482            CompilerScope::Annotation,
1483            line_number,
1484        );
1485        self.tables.last_mut().unwrap().can_see_class_scope = can_see_class_scope;
1486        self.add_format_parameter();
1487        if can_see_class_scope {
1488            self.register_name(
1489                &"__classdict__".into(),
1490                SymbolUsage::Used,
1491                TextRange::default(),
1492            )?;
1493        }
1494
1495        let was_in_unevaluated_annotation = self.tables.last().unwrap().in_unevaluated_annotation;
1496        self.tables.last_mut().unwrap().in_unevaluated_annotation = false;
1497
1498        let result = (|| {
1499            for annotation in parameters
1500                .posonlyargs
1501                .iter()
1502                .chain(parameters.args.iter())
1503                .filter_map(|arg| arg.parameter.annotation.as_ref())
1504            {
1505                self.tables.last_mut().unwrap().annotations_used = true;
1506                self.scan_expression(annotation, ExpressionContext::Load)?;
1507            }
1508            if let Some(annotation) = parameters
1509                .vararg
1510                .as_ref()
1511                .and_then(|arg| arg.annotation.as_ref())
1512            {
1513                self.tables.last_mut().unwrap().annotations_used = true;
1514                self.scan_expression(annotation, ExpressionContext::Load)?;
1515            }
1516            if let Some(annotation) = parameters
1517                .kwarg
1518                .as_ref()
1519                .and_then(|arg| arg.annotation.as_ref())
1520            {
1521                self.tables.last_mut().unwrap().annotations_used = true;
1522                self.scan_expression(annotation, ExpressionContext::Load)?;
1523            }
1524            for annotation in parameters
1525                .kwonlyargs
1526                .iter()
1527                .filter_map(|arg| arg.parameter.annotation.as_ref())
1528            {
1529                self.tables.last_mut().unwrap().annotations_used = true;
1530                self.scan_expression(annotation, ExpressionContext::Load)?;
1531            }
1532            if let Some(annotation) = returns {
1533                self.tables.last_mut().unwrap().annotations_used = true;
1534                self.scan_expression(annotation, ExpressionContext::Load)?;
1535            }
1536            Ok(())
1537        })();
1538
1539        self.tables.last_mut().unwrap().in_unevaluated_annotation = was_in_unevaluated_annotation;
1540        if self.future_annotations {
1541            let mut annotation_block = self.discard_scope();
1542            Self::resolve_future_annotation_names_as_globals(&mut annotation_block);
1543            self.tables
1544                .last_mut()
1545                .unwrap()
1546                .hidden_annotation_blocks
1547                .push(annotation_block);
1548        } else {
1549            self.leave_scope();
1550        }
1551        result
1552    }
1553
1554    fn scan_annotation_inner(
1555        &mut self,
1556        annotation: &ast::Expr,
1557        is_ann_assign: bool,
1558    ) -> SymbolTableResult {
1559        let current_scope = self.tables.last().map(|t| t.typ);
1560        let is_unevaluated = is_ann_assign
1561            && current_scope.is_some_and(|scope| {
1562                matches!(
1563                    scope,
1564                    CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda
1565                )
1566            });
1567        let needs_conditional_annotations = is_ann_assign
1568            && (matches!(current_scope, Some(CompilerScope::Module))
1569                || (matches!(current_scope, Some(CompilerScope::Class))
1570                    && self.in_conditional_block));
1571        let should_register_conditional_annotations = needs_conditional_annotations
1572            && !self.tables.last().unwrap().has_conditional_annotations;
1573
1574        // PEP 649: Only AnnAssign annotations can be conditional.
1575        // Function parameter/return annotations are never conditional.
1576        if needs_conditional_annotations {
1577            self.tables.last_mut().unwrap().has_conditional_annotations = true;
1578        }
1579
1580        if should_register_conditional_annotations {
1581            self.register_name(
1582                &"__conditional_annotations__".into(),
1583                SymbolUsage::Used,
1584                annotation.range(),
1585            )?;
1586        }
1587
1588        // Create annotation scope for deferred evaluation
1589        let line_number = self.line_index_start(annotation.range());
1590        self.enter_annotation_scope(line_number, false, true);
1591
1592        // PEP 649: scan expression for symbol references
1593        // Class annotations are evaluated in class locals (not module globals)
1594        let was_in_unevaluated_annotation = self.tables.last().unwrap().in_unevaluated_annotation;
1595        self.tables.last_mut().unwrap().in_unevaluated_annotation = is_unevaluated;
1596        let result = self.scan_expression(annotation, ExpressionContext::Load);
1597        self.tables.last_mut().unwrap().in_unevaluated_annotation = was_in_unevaluated_annotation;
1598
1599        self.leave_annotation_scope();
1600
1601        result
1602    }
1603
1604    /// Reject a `from __future__ import` that no longer opens the module.
1605    ///
1606    /// Only a docstring and other future statements may come first.
1607    // = future_parse
1608    fn track_future_statement(&mut self, statement: &ast::Stmt) -> SymbolTableResult {
1609        match statement {
1610            ast::Stmt::ImportFrom(ast::StmtImportFrom { module, level, .. })
1611                if *level == 0 && module.as_ref().map(|id| id.as_str()) == Some("__future__") =>
1612            {
1613                if matches!(self.done_with_future_stmts, DoneWithFuture::Yes) {
1614                    return Err(self.error_ranged(
1615                        "from __future__ imports must occur at the beginning of the file"
1616                            .to_owned(),
1617                        statement.range(),
1618                    ));
1619                }
1620                self.done_with_future_stmts = DoneWithFuture::DoneWithDoc;
1621            }
1622            ast::Stmt::Expr(ast::StmtExpr { value, .. })
1623                if is_docstring_expr(value)
1624                    && matches!(self.done_with_future_stmts, DoneWithFuture::No) =>
1625            {
1626                self.done_with_future_stmts = DoneWithFuture::DoneWithDoc;
1627            }
1628            _ => self.done_with_future_stmts = DoneWithFuture::Yes,
1629        }
1630        Ok(())
1631    }
1632
1633    fn scan_statement(&mut self, statement: &ast::Stmt) -> SymbolTableResult {
1634        if self.recursion_depth >= self.recursion_limit {
1635            return Err(SymbolTableError {
1636                error: RECURSION_ERROR.to_owned(),
1637                location: None,
1638                end_location: None,
1639            });
1640        }
1641        self.recursion_depth += 1;
1642        let result = (|| {
1643            use ast::*;
1644            self.track_future_statement(statement)?;
1645            match &statement {
1646                Stmt::Global(StmtGlobal { names, .. }) => {
1647                    for name in names {
1648                        self.register_name(name.id(), SymbolUsage::Global, statement.range())?;
1649                    }
1650                }
1651                Stmt::Nonlocal(StmtNonlocal { names, .. }) => {
1652                    for name in names {
1653                        self.register_name(name.id(), SymbolUsage::Nonlocal, statement.range())?;
1654                    }
1655                }
1656                Stmt::FunctionDef(StmtFunctionDef {
1657                    name,
1658                    body,
1659                    parameters,
1660                    decorator_list,
1661                    type_params,
1662                    returns,
1663                    range,
1664                    is_async,
1665                    ..
1666                }) => {
1667                    self.register_name(name.id(), SymbolUsage::Assigned, *range)?;
1668
1669                    let def_range = crate::decorated_definition_range(
1670                        &self.source_file,
1671                        *range,
1672                        decorator_list,
1673                        if *is_async { "async def " } else { "def " },
1674                    );
1675
1676                    self.scan_parameter_defaults(parameters)?;
1677                    self.scan_decorators(decorator_list, ExpressionContext::Load)?;
1678
1679                    // For generic functions, enter type_param block FIRST so that
1680                    // annotation scopes are nested inside and can see type parameters.
1681                    if let Some(type_params) = type_params {
1682                        self.enter_type_param_block(
1683                            name.id(),
1684                            def_range,
1685                            false,
1686                            // A generic function's type params scope always
1687                            // takes `.defaults`, even with no default anywhere
1688                            // in the signature.
1689                            true,
1690                            Self::has_kwonlydefaults(parameters),
1691                        )?;
1692                        self.scan_type_params(type_params)?;
1693                    }
1694                    self.enter_scope_with_parameters(
1695                        name.id(),
1696                        parameters,
1697                        self.line_index_start(def_range),
1698                        returns.as_deref(),
1699                        if *is_async {
1700                            CompilerScope::AsyncFunction
1701                        } else {
1702                            CompilerScope::Function
1703                        },
1704                        true, // skip_defaults: already scanned above
1705                        false,
1706                    )?;
1707                    if *is_async {
1708                        self.tables.last_mut().unwrap().is_coroutine = true;
1709                    }
1710                    self.scan_statements(body)?;
1711                    self.leave_scope();
1712                    if type_params.is_some() {
1713                        self.leave_scope();
1714                    }
1715                }
1716                Stmt::ClassDef(StmtClassDef {
1717                    name,
1718                    body,
1719                    arguments,
1720                    decorator_list,
1721                    type_params,
1722                    range,
1723                    ..
1724                }) => {
1725                    let prev_class = self.class_name.clone();
1726                    self.register_name(name.id(), SymbolUsage::Assigned, *range)?;
1727
1728                    let def_range = crate::decorated_definition_range(
1729                        &self.source_file,
1730                        *range,
1731                        decorator_list,
1732                        "class ",
1733                    );
1734
1735                    self.scan_decorators(decorator_list, ExpressionContext::Load)?;
1736
1737                    if let Some(type_params) = type_params {
1738                        self.enter_type_param_block(
1739                            name.id(),
1740                            def_range,
1741                            true, // for_class: enable selective mangling
1742                            false,
1743                            false,
1744                        )?;
1745                        // Set class_name for mangling in type param scope
1746                        self.class_name = Some(name.id().clone());
1747                        self.scan_type_params(type_params)?;
1748                    }
1749
1750                    if type_params.is_none() {
1751                        self.class_name.clone_from(&prev_class);
1752                    }
1753
1754                    if let Some(arguments) = arguments {
1755                        self.scan_expressions(&arguments.args, ExpressionContext::Load)?;
1756                        for keyword in &arguments.keywords {
1757                            if let Some(arg) = &keyword.arg {
1758                                self.check_name(arg.id(), ExpressionContext::Store, keyword.range)?;
1759                            }
1760                        }
1761                        for keyword in &arguments.keywords {
1762                            self.scan_expression(&keyword.value, ExpressionContext::Load)?;
1763                        }
1764                    }
1765
1766                    self.enter_scope(
1767                        name.id(),
1768                        CompilerScope::Class,
1769                        self.line_index_start(def_range),
1770                    );
1771                    // Reset in_conditional_block for new class scope
1772                    let saved_in_conditional = self.in_conditional_block;
1773                    self.in_conditional_block = false;
1774                    self.class_name = Some(name.id().clone());
1775                    if type_params.is_some() {
1776                        self.register_name(
1777                            &"__type_params__".into(),
1778                            SymbolUsage::Assigned,
1779                            *range,
1780                        )?;
1781                        self.register_name(&".type_params".into(), SymbolUsage::Used, *range)?;
1782                    }
1783                    self.scan_statements(body)?;
1784                    self.leave_scope();
1785                    self.in_conditional_block = saved_in_conditional;
1786                    if type_params.is_some() {
1787                        self.leave_scope();
1788                    }
1789                    // Restore class_name after all ClassDef processing
1790                    self.class_name = prev_class;
1791                }
1792                Stmt::Expr(StmtExpr { value, .. }) => {
1793                    self.scan_expression(value, ExpressionContext::Load)?
1794                }
1795                Stmt::If(StmtIf {
1796                    test,
1797                    body,
1798                    elif_else_clauses,
1799                    ..
1800                }) => {
1801                    self.scan_expression(test, ExpressionContext::Load)?;
1802                    // PEP 649: Track conditional block for annotations
1803                    let saved_in_conditional_block = self.in_conditional_block;
1804                    self.in_conditional_block = true;
1805                    self.scan_statements(body)?;
1806                    for elif in elif_else_clauses {
1807                        if let Some(test) = &elif.test {
1808                            self.scan_expression(test, ExpressionContext::Load)?;
1809                        }
1810                        self.scan_statements(&elif.body)?;
1811                    }
1812                    self.in_conditional_block = saved_in_conditional_block;
1813                }
1814                Stmt::For(StmtFor {
1815                    target,
1816                    iter,
1817                    body,
1818                    orelse,
1819                    is_async,
1820                    ..
1821                }) => {
1822                    if *is_async && self.allows_top_level_await() {
1823                        self.tables.last_mut().unwrap().is_coroutine = true;
1824                    }
1825                    if *is_async && !self.tables.last().unwrap().is_coroutine {
1826                        return Err(self.error_ranged(
1827                            "'async for' outside async function".to_owned(),
1828                            statement.range(),
1829                        ));
1830                    }
1831                    self.scan_expression(target, ExpressionContext::Store)?;
1832                    self.scan_expression(iter, ExpressionContext::Load)?;
1833                    // PEP 649: Track conditional block for annotations
1834                    let saved_in_conditional_block = self.in_conditional_block;
1835                    self.in_conditional_block = true;
1836                    self.scan_statements(body)?;
1837                    self.scan_statements(orelse)?;
1838                    self.in_conditional_block = saved_in_conditional_block;
1839                }
1840                Stmt::While(StmtWhile {
1841                    test, body, orelse, ..
1842                }) => {
1843                    self.scan_expression(test, ExpressionContext::Load)?;
1844                    // PEP 649: Track conditional block for annotations
1845                    let saved_in_conditional_block = self.in_conditional_block;
1846                    self.in_conditional_block = true;
1847                    self.scan_statements(body)?;
1848                    self.scan_statements(orelse)?;
1849                    self.in_conditional_block = saved_in_conditional_block;
1850                }
1851                Stmt::Break(_) | Stmt::Continue(_) | Stmt::Pass(_) => {
1852                    // No symbols here.
1853                }
1854                Stmt::Import(StmtImport { names, .. })
1855                | Stmt::ImportFrom(StmtImportFrom { names, .. }) => {
1856                    for name in names {
1857                        if let Some(alias) = &name.asname {
1858                            // `import my_module as my_alias`
1859                            self.register_name(alias.id(), SymbolUsage::Imported, name.name.range)?;
1860                        } else if name.name.as_str() == "*" {
1861                            // Star imports are only allowed at module level
1862                            if self.tables.last().unwrap().typ != CompilerScope::Module {
1863                                return Err(self.error_ranged(
1864                                    "import * only allowed at module level".to_string(),
1865                                    name.name.range,
1866                                ));
1867                            }
1868                            // Don't register star imports as symbols
1869                        } else {
1870                            // `import module` or `from x import name`
1871                            let imported_name = name.name.split('.').next().unwrap().into();
1872                            self.check_name(
1873                                &imported_name,
1874                                ExpressionContext::Store,
1875                                name.name.range,
1876                            )?;
1877                            self.register_name(
1878                                &imported_name,
1879                                SymbolUsage::Imported,
1880                                name.name.range,
1881                            )?;
1882                        }
1883                    }
1884                }
1885                Stmt::Return(StmtReturn { value, .. }) => {
1886                    if let Some(expression) = value {
1887                        self.scan_expression(expression, ExpressionContext::Load)?;
1888                        self.tables.last_mut().unwrap().returns_value = true;
1889                    }
1890                }
1891                Stmt::Assert(StmtAssert { test, msg, .. }) => {
1892                    self.scan_expression(test, ExpressionContext::Load)?;
1893                    if let Some(expression) = msg {
1894                        self.scan_expression(expression, ExpressionContext::Load)?;
1895                    }
1896                }
1897                Stmt::Delete(StmtDelete { targets, .. }) => {
1898                    self.scan_expressions(targets, ExpressionContext::Delete)?;
1899                }
1900                Stmt::Assign(StmtAssign { targets, value, .. }) => {
1901                    self.scan_expressions(targets, ExpressionContext::Store)?;
1902                    self.scan_expression(value, ExpressionContext::Load)?;
1903                }
1904                Stmt::AugAssign(StmtAugAssign { target, value, .. }) => {
1905                    self.scan_expression(target, ExpressionContext::Store)?;
1906                    self.scan_expression(value, ExpressionContext::Load)?;
1907                }
1908                Stmt::AnnAssign(StmtAnnAssign {
1909                    target,
1910                    annotation,
1911                    value,
1912                    simple,
1913                    range,
1914                    ..
1915                }) => {
1916                    self.tables.last_mut().unwrap().annotations_used = true;
1917                    // https://github.com/python/cpython/blob/main/Python/symtable.c#L1233
1918                    match &**target {
1919                        Expr::Name(ast::ExprName {
1920                            id,
1921                            range: target_range,
1922                            ..
1923                        }) => {
1924                            if *simple {
1925                                let existing_flags = self.tables.last().and_then(|table| {
1926                                    let name = maybe_mangle_name(
1927                                        self.class_name.as_ref(),
1928                                        table.mangled_names.as_ref(),
1929                                        id,
1930                                    );
1931                                    table.symbols.get(name.as_ref()).map(|symbol| symbol.flags)
1932                                });
1933                                if self
1934                                    .tables
1935                                    .last()
1936                                    .is_some_and(|table| table.typ != CompilerScope::Module)
1937                                    && let Some(flags) = existing_flags
1938                                    && flags.intersects(
1939                                        SymbolFlags::DEF_GLOBAL | SymbolFlags::DEF_NONLOCAL,
1940                                    )
1941                                {
1942                                    let usage = if flags.contains(SymbolFlags::DEF_GLOBAL) {
1943                                        "global"
1944                                    } else {
1945                                        "nonlocal"
1946                                    };
1947                                    return Err(self.error_ranged(
1948                                        format!("annotated name '{id}' can't be {usage}"),
1949                                        *range,
1950                                    ));
1951                                }
1952
1953                                self.register_name(
1954                                    id,
1955                                    SymbolUsage::AnnotationAssigned,
1956                                    *target_range,
1957                                )?;
1958                            } else if value.is_some() {
1959                                self.register_name(id, SymbolUsage::Assigned, *target_range)?;
1960                            }
1961                        }
1962                        _ => {
1963                            self.scan_expression(target, ExpressionContext::Store)?;
1964                        }
1965                    }
1966                    self.scan_ann_assign_annotation(annotation)?;
1967                    if let Some(value) = value {
1968                        self.scan_expression(value, ExpressionContext::Load)?;
1969                    }
1970                }
1971                Stmt::With(StmtWith {
1972                    items,
1973                    body,
1974                    is_async,
1975                    ..
1976                }) => {
1977                    if *is_async && self.allows_top_level_await() {
1978                        self.tables.last_mut().unwrap().is_coroutine = true;
1979                    }
1980                    if *is_async && !self.tables.last().unwrap().is_coroutine {
1981                        return Err(self.error_ranged(
1982                            "'async with' outside async function".to_owned(),
1983                            statement.range(),
1984                        ));
1985                    }
1986                    // PEP 649: Track conditional block for annotations
1987                    let saved_in_conditional_block = self.in_conditional_block;
1988                    self.in_conditional_block = true;
1989                    for item in items {
1990                        self.scan_expression(&item.context_expr, ExpressionContext::Load)?;
1991                        if let Some(expression) = &item.optional_vars {
1992                            self.scan_expression(expression, ExpressionContext::Store)?;
1993                        }
1994                    }
1995                    self.scan_statements(body)?;
1996                    self.in_conditional_block = saved_in_conditional_block;
1997                }
1998                Stmt::Try(StmtTry {
1999                    body,
2000                    handlers,
2001                    orelse,
2002                    finalbody,
2003                    ..
2004                }) => {
2005                    // PEP 649: Track conditional block for annotations
2006                    let saved_in_conditional_block = self.in_conditional_block;
2007                    self.in_conditional_block = true;
2008                    self.scan_statements(body)?;
2009                    for handler in handlers {
2010                        let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
2011                            type_,
2012                            name,
2013                            body,
2014                            ..
2015                        }) = &handler;
2016                        if let Some(expression) = type_ {
2017                            self.scan_expression(expression, ExpressionContext::Load)?;
2018                        }
2019                        if let Some(name) = name {
2020                            self.register_name(name.id(), SymbolUsage::Assigned, handler.range())?;
2021                        }
2022                        self.scan_statements(body)?;
2023                    }
2024                    self.scan_statements(orelse)?;
2025                    self.scan_statements(finalbody)?;
2026                    self.in_conditional_block = saved_in_conditional_block;
2027                }
2028                Stmt::Match(StmtMatch { subject, cases, .. }) => {
2029                    self.scan_expression(subject, ExpressionContext::Load)?;
2030                    // PEP 649: Track conditional block for annotations
2031                    let saved_in_conditional_block = self.in_conditional_block;
2032                    self.in_conditional_block = true;
2033                    for case in cases {
2034                        self.scan_pattern(&case.pattern)?;
2035                        if let Some(guard) = &case.guard {
2036                            self.scan_expression(guard, ExpressionContext::Load)?;
2037                        }
2038                        self.scan_statements(&case.body)?;
2039                    }
2040                    self.in_conditional_block = saved_in_conditional_block;
2041                }
2042                Stmt::Raise(StmtRaise { exc, cause, .. }) => {
2043                    if let Some(expression) = exc {
2044                        self.scan_expression(expression, ExpressionContext::Load)?;
2045                        if let Some(expression) = cause {
2046                            self.scan_expression(expression, ExpressionContext::Load)?;
2047                        }
2048                    }
2049                }
2050                Stmt::TypeAlias(StmtTypeAlias {
2051                    name,
2052                    value,
2053                    type_params,
2054                    range,
2055                    ..
2056                }) => {
2057                    let Some(name_expr) = name.as_name_expr() else {
2058                        return Err(
2059                            self.error_ranged("type alias expects name".to_owned(), name.range())
2060                        );
2061                    };
2062                    let alias_name = name_expr.id();
2063                    self.scan_expression(name, ExpressionContext::Store)?;
2064                    // Check before entering any sub-scopes
2065                    let in_class = self
2066                        .tables
2067                        .last()
2068                        .is_some_and(|t| t.typ == CompilerScope::Class);
2069                    let is_generic = type_params.is_some();
2070                    if let Some(type_params) = type_params {
2071                        self.enter_type_param_block(alias_name, *range, false, false, false)?;
2072                        self.scan_type_params(type_params)?;
2073                    }
2074                    // Value scope for lazy evaluation
2075                    self.enter_scope(
2076                        alias_name,
2077                        CompilerScope::TypeAlias,
2078                        self.line_index_start(*range),
2079                    );
2080                    // Evaluator takes a format parameter
2081                    self.register_name(&".format".into(), SymbolUsage::Parameter, *range)?;
2082                    self.register_name(&".format".into(), SymbolUsage::Used, *range)?;
2083                    if in_class {
2084                        if let Some(table) = self.tables.last_mut() {
2085                            table.can_see_class_scope = true;
2086                        }
2087                        self.register_name(
2088                            &"__classdict__".into(),
2089                            SymbolUsage::Used,
2090                            value.range(),
2091                        )?;
2092                    }
2093                    self.scan_expression(value, ExpressionContext::Load)?;
2094                    self.leave_scope();
2095                    if is_generic {
2096                        self.leave_scope();
2097                    }
2098                }
2099                Stmt::IpyEscapeCommand(stmt) => {
2100                    return Err(self.error_ranged("invalid syntax".to_owned(), stmt.range));
2101                }
2102            }
2103            Ok(())
2104        })();
2105        self.recursion_depth -= 1;
2106        result
2107    }
2108
2109    fn scan_decorators(
2110        &mut self,
2111        decorators: &[ast::Decorator],
2112        context: ExpressionContext,
2113    ) -> SymbolTableResult {
2114        for decorator in decorators {
2115            self.scan_expression(&decorator.expression, context)?;
2116        }
2117        Ok(())
2118    }
2119
2120    fn scan_expressions(
2121        &mut self,
2122        expressions: &[ast::Expr],
2123        context: ExpressionContext,
2124    ) -> SymbolTableResult {
2125        for expression in expressions {
2126            self.scan_expression(expression, context)?;
2127        }
2128        Ok(())
2129    }
2130
2131    /// Scan the interpolations of a format spec, and the format specs those
2132    /// carry in turn: `f"{x:{y:{z}}}"` only reaches `z` through two of them.
2133    fn scan_format_spec(
2134        &mut self,
2135        format_spec: &ast::InterpolatedStringFormatSpec,
2136    ) -> SymbolTableResult {
2137        for element in format_spec.elements.interpolations() {
2138            self.scan_expression(&element.expression, ExpressionContext::Load)?;
2139            if let Some(nested) = &element.format_spec {
2140                self.scan_format_spec(nested)?;
2141            }
2142        }
2143        Ok(())
2144    }
2145
2146    fn scan_expression(
2147        &mut self,
2148        expression: &ast::Expr,
2149        context: ExpressionContext,
2150    ) -> SymbolTableResult {
2151        if self.recursion_depth >= self.recursion_limit {
2152            return Err(SymbolTableError {
2153                error: RECURSION_ERROR.to_owned(),
2154                location: None,
2155                end_location: None,
2156            });
2157        }
2158        self.recursion_depth += 1;
2159        let result = (|| {
2160            use ast::*;
2161
2162            if expression.is_constant_expr() {
2163                return Ok(());
2164            }
2165
2166            // Check for expressions not allowed in certain contexts
2167            // (type parameters, annotations, type aliases, TypeVar bounds/defaults)
2168            if let Some(keyword) = match expression {
2169                Expr::Yield(_) | Expr::YieldFrom(_) => Some("yield"),
2170                Expr::Await(_) => Some("await"),
2171                Expr::Named(_) => Some("named"),
2172                _ => None,
2173            } {
2174                // Determine the context name for the error message from the
2175                // current symbol table entry, matching ste_type checks.
2176                let current_is_comprehension = self
2177                    .tables
2178                    .last()
2179                    .is_some_and(|table| table.typ == CompilerScope::Comprehension);
2180                let context_name = if keyword == "named" && current_is_comprehension {
2181                    None
2182                } else if let Some(table) = self.tables.last() {
2183                    match table.typ {
2184                        CompilerScope::Annotation => Some("an annotation"),
2185                        CompilerScope::TypeVariable => table.scope_info,
2186                        CompilerScope::TypeAlias => Some("a type alias"),
2187                        CompilerScope::TypeParams => Some("the definition of a generic"),
2188                        _ => None,
2189                    }
2190                } else {
2191                    None
2192                };
2193
2194                if let Some(context_name) = context_name {
2195                    return Err(self.error_ranged(
2196                        format!("{keyword} expression cannot be used within {context_name}"),
2197                        expression.range(),
2198                    ));
2199                }
2200            }
2201
2202            match expression {
2203                Expr::BinOp(ExprBinOp { left, right, .. }) => {
2204                    self.scan_expression(left, context)?;
2205                    self.scan_expression(right, context)?;
2206                }
2207                Expr::BoolOp(ExprBoolOp { values, .. }) => {
2208                    self.scan_expressions(values, context)?;
2209                }
2210                Expr::Compare(ExprCompare {
2211                    left, comparators, ..
2212                }) => {
2213                    self.scan_expression(left, context)?;
2214                    self.scan_expressions(comparators, context)?;
2215                }
2216                Expr::Subscript(ExprSubscript { value, slice, .. }) => {
2217                    self.scan_expression(value, ExpressionContext::Load)?;
2218                    self.scan_expression(slice, ExpressionContext::Load)?;
2219                }
2220                Expr::Attribute(ExprAttribute {
2221                    value, attr, range, ..
2222                }) => {
2223                    self.check_name(attr.id(), context, *range)?;
2224                    self.scan_expression(value, ExpressionContext::Load)?;
2225                }
2226                Expr::Dict(ExprDict { items, .. }) => {
2227                    for item in items {
2228                        if let Some(key) = &item.key {
2229                            self.scan_expression(key, context)?;
2230                        }
2231                    }
2232                    for item in items {
2233                        self.scan_expression(&item.value, context)?;
2234                    }
2235                }
2236                Expr::Await(ExprAwait { value, .. }) => {
2237                    let current_scope = self.tables.last().unwrap().typ;
2238                    if !self.allows_top_level_await()
2239                        && !Self::is_function_like_scope(current_scope)
2240                    {
2241                        return Err(self.error_ranged(
2242                            "'await' outside function".to_owned(),
2243                            expression.range(),
2244                        ));
2245                    }
2246                    if current_scope != CompilerScope::AsyncFunction
2247                        && current_scope != CompilerScope::Comprehension
2248                        && !self.allows_top_level_await()
2249                    {
2250                        return Err(self.error_ranged(
2251                            "'await' outside async function".to_owned(),
2252                            expression.range(),
2253                        ));
2254                    }
2255                    self.scan_expression(value, context)?;
2256                    self.tables.last_mut().unwrap().is_coroutine = true;
2257                }
2258                Expr::Yield(ExprYield { value, .. }) => {
2259                    if let Some(expression) = value {
2260                        self.scan_expression(expression, context)?;
2261                    }
2262                    self.tables.last_mut().unwrap().is_generator = true;
2263                    if let Some(context_name) = self.comprehension_yield_context
2264                        && self
2265                            .tables
2266                            .last()
2267                            .is_some_and(|table| table.typ == CompilerScope::Comprehension)
2268                    {
2269                        return Err(self.error_ranged(
2270                            format!("'yield' inside {context_name}"),
2271                            expression.range(),
2272                        ));
2273                    }
2274                }
2275                Expr::YieldFrom(ExprYieldFrom { value, .. }) => {
2276                    self.scan_expression(value, context)?;
2277                    self.tables.last_mut().unwrap().is_generator = true;
2278                    if let Some(context_name) = self.comprehension_yield_context
2279                        && self
2280                            .tables
2281                            .last()
2282                            .is_some_and(|table| table.typ == CompilerScope::Comprehension)
2283                    {
2284                        return Err(self.error_ranged(
2285                            format!("'yield' inside {context_name}"),
2286                            expression.range(),
2287                        ));
2288                    }
2289                }
2290                Expr::UnaryOp(ExprUnaryOp { operand, .. }) => {
2291                    self.scan_expression(operand, context)?;
2292                }
2293                Expr::Starred(ExprStarred { value, .. }) => {
2294                    self.scan_expression(value, context)?;
2295                }
2296                Expr::Tuple(ExprTuple { elts, .. })
2297                | Expr::Set(ExprSet { elts, .. })
2298                | Expr::List(ExprList { elts, .. }) => {
2299                    self.scan_expressions(elts, context)?;
2300                }
2301                Expr::Slice(ExprSlice {
2302                    lower, upper, step, ..
2303                }) => {
2304                    if let Some(lower) = lower {
2305                        self.scan_expression(lower, context)?;
2306                    }
2307                    if let Some(upper) = upper {
2308                        self.scan_expression(upper, context)?;
2309                    }
2310                    if let Some(step) = step {
2311                        self.scan_expression(step, context)?;
2312                    }
2313                }
2314                Expr::Generator(ExprGenerator {
2315                    elt,
2316                    generators,
2317                    range,
2318                    ..
2319                }) => {
2320                    let was_in_iter_def_exp = self.in_iter_def_exp;
2321                    if context == ExpressionContext::IterDefinitionExp {
2322                        self.in_iter_def_exp = true;
2323                    }
2324                    // Generator expression - is_generator = true
2325                    self.scan_comprehension(
2326                        &"genexpr".into(),
2327                        elt,
2328                        None,
2329                        generators,
2330                        *range,
2331                        true,
2332                    )?;
2333                    self.in_iter_def_exp = was_in_iter_def_exp;
2334                }
2335                Expr::ListComp(ExprListComp {
2336                    elt,
2337                    generators,
2338                    range,
2339                    ..
2340                }) => {
2341                    let was_in_iter_def_exp = self.in_iter_def_exp;
2342                    if context == ExpressionContext::IterDefinitionExp {
2343                        self.in_iter_def_exp = true;
2344                    }
2345                    // List comprehension - is_generator = false (can be inlined)
2346                    self.scan_comprehension(
2347                        &"<listcomp>".into(),
2348                        elt,
2349                        None,
2350                        generators,
2351                        *range,
2352                        false,
2353                    )?;
2354                    self.in_iter_def_exp = was_in_iter_def_exp;
2355                }
2356                Expr::SetComp(ExprSetComp {
2357                    elt,
2358                    generators,
2359                    range,
2360                    ..
2361                }) => {
2362                    let was_in_iter_def_exp = self.in_iter_def_exp;
2363                    if context == ExpressionContext::IterDefinitionExp {
2364                        self.in_iter_def_exp = true;
2365                    }
2366                    // Set comprehension - is_generator = false (can be inlined)
2367                    self.scan_comprehension(
2368                        &"<setcomp>".into(),
2369                        elt,
2370                        None,
2371                        generators,
2372                        *range,
2373                        false,
2374                    )?;
2375                    self.in_iter_def_exp = was_in_iter_def_exp;
2376                }
2377                Expr::DictComp(ExprDictComp {
2378                    key,
2379                    value,
2380                    generators,
2381                    range,
2382                    ..
2383                }) => {
2384                    let was_in_iter_def_exp = self.in_iter_def_exp;
2385                    if context == ExpressionContext::IterDefinitionExp {
2386                        self.in_iter_def_exp = true;
2387                    }
2388                    // Dict comprehension - is_generator = false (can be inlined)
2389                    let Some(key) = key.as_deref() else {
2390                        return Err(self.error_ranged(
2391                            "dict unpacking cannot be used in dict comprehension".to_owned(),
2392                            *range,
2393                        ));
2394                    };
2395                    self.scan_comprehension(
2396                        &"<dictcomp>".into(),
2397                        key,
2398                        Some(value),
2399                        generators,
2400                        *range,
2401                        false,
2402                    )?;
2403                    self.in_iter_def_exp = was_in_iter_def_exp;
2404                }
2405                Expr::Call(ExprCall {
2406                    func, arguments, ..
2407                }) => {
2408                    match context {
2409                        ExpressionContext::IterDefinitionExp => {
2410                            self.scan_expression(func, ExpressionContext::IterDefinitionExp)?;
2411                        }
2412                        _ => {
2413                            self.scan_expression(func, ExpressionContext::Load)?;
2414                        }
2415                    }
2416
2417                    self.scan_expressions(&arguments.args, ExpressionContext::Load)?;
2418                    for keyword in &arguments.keywords {
2419                        if let Some(arg) = &keyword.arg {
2420                            self.check_name(arg.id(), ExpressionContext::Store, keyword.range)?;
2421                        }
2422                    }
2423                    for keyword in &arguments.keywords {
2424                        self.scan_expression(&keyword.value, ExpressionContext::Load)?;
2425                    }
2426                }
2427                Expr::Name(ExprName { id, range, .. }) => {
2428                    self.check_name(id, context, *range)?;
2429
2430                    if !self
2431                        .tables
2432                        .last()
2433                        .is_some_and(|table| table.in_unevaluated_annotation)
2434                    {
2435                        // Determine the contextual usage of this symbol:
2436                        match context {
2437                            ExpressionContext::Delete => {
2438                                self.register_name(id, SymbolUsage::Assigned, *range)?;
2439                            }
2440                            ExpressionContext::Load | ExpressionContext::IterDefinitionExp => {
2441                                self.register_name(id, SymbolUsage::Used, *range)?;
2442                            }
2443                            ExpressionContext::Store => {
2444                                self.register_name(id, SymbolUsage::Assigned, *range)?;
2445                            }
2446                            ExpressionContext::Iter => {
2447                                self.register_name(id, SymbolUsage::Iter, *range)?;
2448                            }
2449                        }
2450                        // Interesting stuff about the __class__ variable:
2451                        // https://docs.python.org/3/reference/datamodel.html?highlight=__class__#creating-the-class-object
2452                        if context == ExpressionContext::Load
2453                            && Self::is_function_like_scope(self.tables.last().unwrap().typ)
2454                            && id == "super"
2455                        {
2456                            self.register_name(&"__class__".into(), SymbolUsage::Used, *range)?;
2457                        }
2458                    }
2459                }
2460                Expr::Lambda(ExprLambda {
2461                    body, parameters, ..
2462                }) => {
2463                    let was_in_iter_def_exp = self.in_iter_def_exp;
2464                    if let Some(parameters) = parameters {
2465                        if was_in_iter_def_exp {
2466                            self.scan_parameter_defaults(parameters)?;
2467                        }
2468                        self.enter_scope_with_parameters(
2469                            &"lambda".into(),
2470                            parameters,
2471                            self.line_index_start(expression.range()),
2472                            None, // lambdas have no return annotation
2473                            CompilerScope::Lambda,
2474                            was_in_iter_def_exp,
2475                            false,
2476                        )?;
2477                    } else {
2478                        self.enter_scope(
2479                            &"lambda".into(),
2480                            CompilerScope::Lambda,
2481                            self.line_index_start(expression.range()),
2482                        );
2483                    }
2484                    self.scan_expression(body, ExpressionContext::Load)?;
2485                    self.in_iter_def_exp = was_in_iter_def_exp;
2486                    self.leave_scope();
2487                }
2488                Expr::FString(fstring) => {
2489                    if let Some(joined_str) = &fstring.runtime_joined_str {
2490                        for expr in joined_str {
2491                            self.scan_expression(expr, ExpressionContext::Load)?;
2492                        }
2493                        return Ok(());
2494                    }
2495                    for expr in fstring
2496                        .value
2497                        .elements()
2498                        .filter_map(|x| x.as_interpolation())
2499                    {
2500                        self.scan_expression(&expr.expression, ExpressionContext::Load)?;
2501                        if let Some(format_spec) = &expr.runtime_formatted_value_format_spec {
2502                            self.scan_expression(format_spec, ExpressionContext::Load)?;
2503                        } else if let Some(format_spec) = &expr.format_spec {
2504                            self.scan_format_spec(format_spec)?;
2505                        }
2506                    }
2507                }
2508                Expr::TString(tstring) => {
2509                    if let Some(template_str) = &tstring.runtime_template_str {
2510                        for expr in template_str {
2511                            self.scan_expression(expr, ExpressionContext::Load)?;
2512                        }
2513                        return Ok(());
2514                    }
2515                    // Scan t-string interpolation expressions (similar to f-strings)
2516                    for expr in tstring
2517                        .value
2518                        .elements()
2519                        .filter_map(|x| x.as_interpolation())
2520                    {
2521                        self.scan_expression(&expr.expression, ExpressionContext::Load)?;
2522                        if expr.runtime_str.is_some() {
2523                            if let Some(format_spec) = &expr.runtime_interpolation_format_spec {
2524                                self.scan_expression(format_spec, ExpressionContext::Load)?;
2525                            }
2526                        } else if let Some(format_spec) = &expr.format_spec {
2527                            self.scan_format_spec(format_spec)?;
2528                        }
2529                    }
2530                }
2531                // Constants
2532                Expr::StringLiteral(_)
2533                | Expr::BytesLiteral(_)
2534                | Expr::NumberLiteral(_)
2535                | Expr::Constant(_)
2536                | Expr::BooleanLiteral(_)
2537                | Expr::NoneLiteral(_)
2538                | Expr::EllipsisLiteral(_) => {}
2539                Expr::IpyEscapeCommand(expr) => {
2540                    return Err(self.error_ranged("invalid syntax".to_owned(), expr.range));
2541                }
2542                Expr::If(ExprIf {
2543                    test, body, orelse, ..
2544                }) => {
2545                    self.scan_expression(test, ExpressionContext::Load)?;
2546                    self.scan_expression(body, ExpressionContext::Load)?;
2547                    self.scan_expression(orelse, ExpressionContext::Load)?;
2548                }
2549
2550                Expr::Named(ExprNamed {
2551                    target,
2552                    value,
2553                    range,
2554                    ..
2555                }) => {
2556                    // named expressions are not allowed in the definition of
2557                    // comprehension iterator definitions (including nested comprehensions)
2558                    if context == ExpressionContext::IterDefinitionExp || self.in_iter_def_exp {
2559                        return Err(self.error_ranged(
2560                            "assignment expression cannot be used in a comprehension iterable expression"
2561                                .to_string(),
2562                            *range,
2563                        ));
2564                    }
2565
2566                    let named_target = if let Expr::Name(ExprName {
2567                        id,
2568                        range: target_range,
2569                        ..
2570                    }) = &**target
2571                    {
2572                        self.check_name(id, ExpressionContext::Store, *target_range)?;
2573                        let table = self.tables.last().unwrap();
2574                        if table.typ == CompilerScope::Comprehension {
2575                            self.extend_namedexpr_scope(id, *target_range)?;
2576                        }
2577                        Some((id, *target_range))
2578                    } else {
2579                        None
2580                    };
2581
2582                    self.scan_expression(value, ExpressionContext::Load)?;
2583
2584                    if let Some((id, target_range)) = named_target {
2585                        self.register_name(id, SymbolUsage::Assigned, target_range)?;
2586                    } else {
2587                        self.scan_expression(target, ExpressionContext::Store)?;
2588                    }
2589                }
2590            }
2591            Ok(())
2592        })();
2593        self.recursion_depth -= 1;
2594        result
2595    }
2596
2597    fn scan_comprehension(
2598        &mut self,
2599        scope_name: &Name,
2600        elt1: &ast::Expr,
2601        elt2: Option<&ast::Expr>,
2602        generators: &[ast::Comprehension],
2603        range: TextRange,
2604        is_generator: bool,
2605    ) -> SymbolTableResult {
2606        assert!(!generators.is_empty());
2607        let outermost = &generators[0];
2608
2609        // CPython evaluates the outermost iterator in the enclosing scope
2610        // before entering the comprehension scope.
2611        let was_in_iter_def_exp = self.in_iter_def_exp;
2612        self.in_iter_def_exp = true;
2613        self.scan_expression(&outermost.iter, ExpressionContext::IterDefinitionExp)?;
2614        self.in_iter_def_exp = was_in_iter_def_exp;
2615
2616        // Comprehensions are compiled as functions, so create a scope for them:
2617        self.enter_scope(
2618            scope_name,
2619            CompilerScope::Comprehension,
2620            self.line_index_start(range),
2621        );
2622        if outermost.is_async {
2623            self.tables.last_mut().unwrap().is_coroutine = true;
2624        }
2625
2626        // PEP 709: Mark non-generator comprehensions for inlining.
2627        // symtable marks all non-generator comprehensions for
2628        // inlining, except scopes nested under a parent that can see class
2629        // scope (for example annotation scopes inside classes).
2630        if !is_generator {
2631            let parent = self.tables.iter().rev().nth(1);
2632            let parent_can_see_class = parent.is_some_and(|t| t.can_see_class_scope);
2633            if !parent_can_see_class {
2634                self.tables.last_mut().unwrap().comp_inlined = true;
2635            }
2636        }
2637
2638        // Register the passed argument to the generator function as the name ".0"
2639        self.register_name(&".0".into(), SymbolUsage::Parameter, range)?;
2640
2641        let saved_comprehension_yield_context = self.comprehension_yield_context;
2642        self.comprehension_yield_context = Some(match scope_name.as_ref() {
2643            "<listcomp>" => "list comprehension",
2644            "<setcomp>" => "set comprehension",
2645            "<dictcomp>" => "dict comprehension",
2646            "genexpr" => "generator expression",
2647            _ => "comprehension",
2648        });
2649
2650        self.scan_expression(&outermost.target, ExpressionContext::Iter)?;
2651        for if_expr in &outermost.ifs {
2652            self.scan_expression(if_expr, ExpressionContext::Load)?;
2653        }
2654
2655        for generator in &generators[1..] {
2656            self.scan_expression(&generator.target, ExpressionContext::Iter)?;
2657            let was_in_iter_def_exp = self.in_iter_def_exp;
2658            self.in_iter_def_exp = true;
2659            self.scan_expression(&generator.iter, ExpressionContext::IterDefinitionExp)?;
2660            self.in_iter_def_exp = was_in_iter_def_exp;
2661            for if_expr in &generator.ifs {
2662                self.scan_expression(if_expr, ExpressionContext::Load)?;
2663            }
2664            if generator.is_async {
2665                self.tables.last_mut().unwrap().is_coroutine = true;
2666            }
2667        }
2668
2669        if let Some(elt2) = elt2 {
2670            self.scan_expression(elt2, ExpressionContext::Load)?;
2671        }
2672        self.scan_expression(elt1, ExpressionContext::Load)?;
2673        self.tables.last_mut().unwrap().is_generator = is_generator;
2674        self.comprehension_yield_context = saved_comprehension_yield_context;
2675
2676        // symtable_handle_comprehension(): non-generator async
2677        // comprehensions propagate ste_coroutine to the enclosing scope after
2678        // the comprehension block is exited.
2679        let propagate_coroutine = self.tables.last().unwrap().is_coroutine && !is_generator;
2680        self.leave_scope();
2681        if propagate_coroutine
2682            && self
2683                .tables
2684                .last()
2685                .is_none_or(|table| table.typ != CompilerScope::Comprehension)
2686            && !self.is_in_async_context()
2687            && !self.allows_top_level_await()
2688        {
2689            return Err(self.error_ranged(
2690                "asynchronous comprehension outside of an asynchronous function".to_owned(),
2691                range,
2692            ));
2693        }
2694        if propagate_coroutine {
2695            self.tables.last_mut().unwrap().is_coroutine = true;
2696        }
2697
2698        Ok(())
2699    }
2700
2701    /// Scan type parameter bound or default in a separate scope
2702    // = symtable_visit_type_param_bound_or_default
2703    fn scan_type_param_bound_or_default(
2704        &mut self,
2705        expr: &ast::Expr,
2706        scope_name: &Name,
2707        scope_info: &'static str,
2708    ) -> SymbolTableResult {
2709        // Bounds/defaults are compiled as annotation scopes.
2710        let in_class = self.tables.last().is_some_and(|t| t.can_see_class_scope);
2711        let line_number = self.line_index_start(expr.range());
2712        self.enter_scope(scope_name, CompilerScope::TypeVariable, line_number);
2713        // Evaluator takes a format parameter
2714        self.register_name(&".format".into(), SymbolUsage::Parameter, expr.range())?;
2715        self.register_name(&".format".into(), SymbolUsage::Used, expr.range())?;
2716
2717        if in_class {
2718            if let Some(table) = self.tables.last_mut() {
2719                table.can_see_class_scope = true;
2720            }
2721            self.register_name(&"__classdict__".into(), SymbolUsage::Used, expr.range())?;
2722        }
2723
2724        self.tables.last_mut().unwrap().scope_info = Some(scope_info);
2725
2726        // Scan the expression in this new scope
2727        let result = self.scan_expression(expr, ExpressionContext::Load);
2728
2729        self.leave_scope();
2730
2731        result
2732    }
2733
2734    fn scan_type_params(&mut self, type_params: &ast::TypeParams) -> SymbolTableResult {
2735        // Each type parameter is visited as: register name, scan bound, scan default.
2736        for type_param in &type_params.type_params {
2737            if self.recursion_depth >= self.recursion_limit {
2738                return Err(SymbolTableError {
2739                    error: RECURSION_ERROR.to_owned(),
2740                    location: None,
2741                    end_location: None,
2742                });
2743            }
2744            self.recursion_depth += 1;
2745            let result = (|| {
2746                match type_param {
2747                    ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
2748                        name,
2749                        bound,
2750                        range: type_var_range,
2751                        default,
2752                        ..
2753                    }) => {
2754                        self.register_name(name.id(), SymbolUsage::TypeParam, *type_var_range)?;
2755                        if name.as_str() == "__classdict__" {
2756                            return Err(self.error_ranged(
2757                                format!(
2758                                    "reserved name '{}' cannot be used for type parameter",
2759                                    name.as_str()
2760                                ),
2761                                *type_var_range,
2762                            ));
2763                        }
2764
2765                        // Process bound in a separate scope
2766                        if let Some(binding) = bound {
2767                            let scope_info = if binding.is_tuple_expr() {
2768                                "a TypeVar constraint"
2769                            } else {
2770                                "a TypeVar bound"
2771                            };
2772                            self.scan_type_param_bound_or_default(binding, name.id(), scope_info)?;
2773                        }
2774
2775                        // Process default in a separate scope
2776                        if let Some(default_value) = default {
2777                            self.scan_type_param_bound_or_default(
2778                                default_value,
2779                                name.id(),
2780                                "a TypeVar default",
2781                            )?;
2782                        }
2783                    }
2784                    ast::TypeParam::ParamSpec(ast::TypeParamParamSpec {
2785                        name,
2786                        range: param_spec_range,
2787                        default,
2788                        ..
2789                    }) => {
2790                        self.register_name(name.id(), SymbolUsage::TypeParam, *param_spec_range)?;
2791                        if name == "__classdict__" {
2792                            return Err(self.error_ranged(
2793                                format!("reserved name '{name}' cannot be used for type parameter"),
2794                                *param_spec_range,
2795                            ));
2796                        }
2797
2798                        // Process default in a separate scope
2799                        if let Some(default_value) = default {
2800                            self.scan_type_param_bound_or_default(
2801                                default_value,
2802                                name.id(),
2803                                "a ParamSpec default",
2804                            )?;
2805                        }
2806                    }
2807                    ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple {
2808                        name,
2809                        range: type_var_tuple_range,
2810                        default,
2811                        ..
2812                    }) => {
2813                        self.register_name(
2814                            name.id(),
2815                            SymbolUsage::TypeParam,
2816                            *type_var_tuple_range,
2817                        )?;
2818                        if name == "__classdict__" {
2819                            return Err(self.error_ranged(
2820                                format!("reserved name '{name}' cannot be used for type parameter"),
2821                                *type_var_tuple_range,
2822                            ));
2823                        }
2824
2825                        // Process default in a separate scope
2826                        if let Some(default_value) = default {
2827                            self.scan_type_param_bound_or_default(
2828                                default_value,
2829                                name.id(),
2830                                "a TypeVarTuple default",
2831                            )?;
2832                        }
2833                    }
2834                }
2835                Ok(())
2836            })();
2837            self.recursion_depth -= 1;
2838            result?;
2839        }
2840        Ok(())
2841    }
2842
2843    fn scan_patterns(&mut self, patterns: &[ast::Pattern]) -> SymbolTableResult {
2844        for pattern in patterns {
2845            self.scan_pattern(pattern)?;
2846        }
2847        Ok(())
2848    }
2849
2850    fn scan_pattern(&mut self, pattern: &ast::Pattern) -> SymbolTableResult {
2851        if self.recursion_depth >= self.recursion_limit {
2852            return Err(SymbolTableError {
2853                error: RECURSION_ERROR.to_owned(),
2854                location: None,
2855                end_location: None,
2856            });
2857        }
2858        self.recursion_depth += 1;
2859        let result = (|| {
2860            use ast::Pattern::{
2861                MatchAs, MatchClass, MatchMapping, MatchOr, MatchSequence, MatchSingleton,
2862                MatchStar, MatchValue,
2863            };
2864            match pattern {
2865                MatchValue(ast::PatternMatchValue { value, .. }) => {
2866                    self.scan_expression(value, ExpressionContext::Load)?
2867                }
2868                MatchSingleton(_) => {}
2869                MatchSequence(ast::PatternMatchSequence { patterns, .. }) => {
2870                    self.scan_patterns(patterns)?
2871                }
2872                MatchMapping(ast::PatternMatchMapping {
2873                    keys,
2874                    patterns,
2875                    rest,
2876                    ..
2877                }) => {
2878                    self.scan_expressions(keys, ExpressionContext::Load)?;
2879                    self.scan_patterns(patterns)?;
2880                    if let Some(rest) = rest {
2881                        if rest.as_str() == "_" {
2882                            return Err(self.error_ranged("invalid syntax".to_owned(), rest.range));
2883                        }
2884                        self.register_name(rest.id(), SymbolUsage::Assigned, pattern.range())?;
2885                    }
2886                }
2887                MatchClass(ast::PatternMatchClass { cls, arguments, .. }) => {
2888                    self.scan_expression(cls, ExpressionContext::Load)?;
2889                    self.scan_patterns(&arguments.patterns)?;
2890                    for kw in &arguments.keywords {
2891                        self.check_name(
2892                            kw.attr.id(),
2893                            ExpressionContext::Store,
2894                            kw.pattern.range(),
2895                        )?;
2896                    }
2897                    for kw in &arguments.keywords {
2898                        self.scan_pattern(&kw.pattern)?;
2899                    }
2900                }
2901                MatchStar(ast::PatternMatchStar { name, .. }) => {
2902                    if let Some(name) = name {
2903                        self.register_name(name.id(), SymbolUsage::Assigned, pattern.range())?;
2904                    }
2905                }
2906                MatchAs(ast::PatternMatchAs {
2907                    pattern: as_pattern,
2908                    name,
2909                    ..
2910                }) => {
2911                    if let Some(as_pattern) = as_pattern {
2912                        self.scan_pattern(as_pattern)?;
2913                    }
2914                    if let Some(name) = name {
2915                        self.register_name(name.id(), SymbolUsage::Assigned, pattern.range())?;
2916                    }
2917                }
2918                MatchOr(ast::PatternMatchOr { patterns, .. }) => self.scan_patterns(patterns)?,
2919            }
2920            Ok(())
2921        })();
2922        self.recursion_depth -= 1;
2923        result
2924    }
2925
2926    /// Scan default parameter values (evaluated in the enclosing scope)
2927    fn scan_parameter_defaults(&mut self, parameters: &ast::Parameters) -> SymbolTableResult {
2928        for default in parameters
2929            .posonlyargs
2930            .iter()
2931            .chain(parameters.args.iter())
2932            .chain(parameters.kwonlyargs.iter())
2933            .filter_map(|arg| arg.default.as_ref())
2934        {
2935            self.scan_expression(default, ExpressionContext::Load)?;
2936        }
2937        Ok(())
2938    }
2939
2940    fn has_kwonlydefaults(parameters: &ast::Parameters) -> bool {
2941        parameters
2942            .kwonlyargs
2943            .iter()
2944            .any(|arg| arg.default.is_some())
2945    }
2946
2947    #[expect(
2948        clippy::too_many_arguments,
2949        reason = "keeps parameter/default scanning options explicit at call sites"
2950    )]
2951    fn enter_scope_with_parameters(
2952        &mut self,
2953        name: &Name,
2954        parameters: &ast::Parameters,
2955        line_number: u32,
2956        returns: Option<&ast::Expr>,
2957        scope_type: CompilerScope,
2958        skip_defaults: bool,
2959        skip_annotations: bool,
2960    ) -> SymbolTableResult {
2961        // Evaluate eventual default parameters (unless already scanned before type_param_block):
2962        if !skip_defaults {
2963            self.scan_parameter_defaults(parameters)?;
2964        }
2965
2966        let is_function_scope = matches!(
2967            scope_type,
2968            CompilerScope::Function | CompilerScope::AsyncFunction
2969        );
2970        if is_function_scope && !skip_annotations {
2971            self.scan_function_annotations(parameters, returns, line_number)?;
2972        }
2973
2974        self.enter_scope(name, scope_type, line_number);
2975
2976        // Fill scope with parameter names:
2977        self.scan_parameters(&parameters.posonlyargs)?;
2978        self.scan_parameters(&parameters.args)?;
2979        self.scan_parameters(&parameters.kwonlyargs)?;
2980        if let Some(name) = &parameters.vararg {
2981            self.scan_parameter(name)?;
2982        }
2983        if let Some(name) = &parameters.kwarg {
2984            self.scan_parameter(name)?;
2985        }
2986        Ok(())
2987    }
2988
2989    fn error_ranged(&self, error: String, range: TextRange) -> SymbolTableError {
2990        let source_code = self.source_file.to_source_code();
2991        SymbolTableError {
2992            error,
2993            location: Some(source_code.source_location(range.start(), PositionEncoding::Utf8)),
2994            end_location: Some(source_code.source_location(range.end(), PositionEncoding::Utf8)),
2995        }
2996    }
2997
2998    fn register_ident(&mut self, ident: &ast::Identifier, role: SymbolUsage) -> SymbolTableResult {
2999        self.register_name(ident.id(), role, ident.range)
3000    }
3001
3002    fn check_name(
3003        &self,
3004        name: &Name,
3005        context: ExpressionContext,
3006        range: TextRange,
3007    ) -> SymbolTableResult {
3008        if name == "__debug__" {
3009            match context {
3010                ExpressionContext::Store | ExpressionContext::Iter => {
3011                    return Err(self.error_ranged("cannot assign to __debug__".to_owned(), range));
3012                }
3013                ExpressionContext::Delete => {
3014                    return Err(self.error_ranged("cannot delete __debug__".to_owned(), range));
3015                }
3016                _ => {}
3017            }
3018        }
3019        Ok(())
3020    }
3021
3022    // Mirrors symtable_extend_namedexpr_scope(): assignment expressions
3023    // inside comprehensions bind in the nearest function/module-like scope, not
3024    // in the synthetic comprehension scope itself.
3025    fn extend_namedexpr_scope(&mut self, name: &Name, range: TextRange) -> SymbolTableResult {
3026        for table_idx in (0..self.tables.len()).rev() {
3027            let table_type = self.tables[table_idx].typ;
3028            let mangled = maybe_mangle_name(
3029                self.class_name.as_ref(),
3030                self.tables[table_idx].mangled_names.as_ref(),
3031                name,
3032            )
3033            .into_owned();
3034
3035            if table_type == CompilerScope::Comprehension {
3036                if self.tables[table_idx]
3037                    .symbols
3038                    .get(mangled.as_str())
3039                    .is_some_and(|symbol| {
3040                        symbol
3041                            .flags
3042                            .contains(SymbolFlags::DEF_LOCAL | SymbolFlags::DEF_COMP_ITER)
3043                    })
3044                {
3045                    return Err(self.error_ranged(format!( "assignment expression cannot rebind comprehension iteration variable '{name}'" ), range));
3046                }
3047                continue;
3048            }
3049
3050            match table_type {
3051                CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda => {
3052                    let parent_is_global = self.tables[table_idx]
3053                        .symbols
3054                        .get(mangled.as_str())
3055                        .is_some_and(|symbol| symbol.flags.contains(SymbolFlags::DEF_GLOBAL));
3056                    let current = self.tables.last_mut().unwrap();
3057                    let current_symbol = current
3058                        .symbols
3059                        .entry(mangled.clone())
3060                        .or_insert_with(|| Symbol::new(mangled.clone()));
3061                    if parent_is_global {
3062                        current_symbol.flags.insert(SymbolFlags::DEF_GLOBAL);
3063                        current_symbol.scope = SymbolScope::GlobalExplicit;
3064                    } else {
3065                        current_symbol.flags.insert(SymbolFlags::DEF_NONLOCAL);
3066                        current_symbol.scope = SymbolScope::Free;
3067                    }
3068
3069                    let symbol = self.tables[table_idx]
3070                        .symbols
3071                        .entry(mangled.clone())
3072                        .or_insert_with(|| Symbol::new(mangled.clone()));
3073                    symbol.flags.insert(SymbolFlags::DEF_LOCAL);
3074                    return Ok(());
3075                }
3076                CompilerScope::Module => {
3077                    let current = self.tables.last_mut().unwrap();
3078                    let current_symbol = current
3079                        .symbols
3080                        .entry(mangled.clone())
3081                        .or_insert_with(|| Symbol::new(mangled.clone()));
3082                    current_symbol.flags.insert(SymbolFlags::DEF_GLOBAL);
3083                    current_symbol.scope = SymbolScope::GlobalExplicit;
3084
3085                    let symbol = self.tables[table_idx]
3086                        .symbols
3087                        .entry(mangled.clone())
3088                        .or_insert_with(|| Symbol::new(mangled.clone()));
3089                    symbol.flags.insert(SymbolFlags::DEF_GLOBAL);
3090                    symbol.scope = SymbolScope::GlobalExplicit;
3091                    return Ok(());
3092                }
3093                CompilerScope::Class => {
3094                    return Err(self.error_ranged("assignment expression within a comprehension cannot be used in a class body".to_string(), range));
3095                }
3096                CompilerScope::TypeParams => {
3097                    return Err(self.error_ranged("assignment expression within a comprehension cannot be used within the definition of a generic".to_string(), range));
3098                }
3099                CompilerScope::TypeAlias => {
3100                    return Err(self.error_ranged("assignment expression within a comprehension cannot be used in a type alias" .to_string(), range));
3101                }
3102                CompilerScope::TypeVariable => {
3103                    return Err(self.error_ranged("assignment expression within a comprehension cannot be used in a TypeVar bound" .to_string(), range));
3104                }
3105                CompilerScope::Annotation => {}
3106                CompilerScope::Comprehension => unreachable!(),
3107            }
3108        }
3109
3110        unreachable!("named expression scope extension requires an enclosing scope")
3111    }
3112
3113    fn register_name(
3114        &mut self,
3115        name: &Name,
3116        role: SymbolUsage,
3117        range: TextRange,
3118    ) -> SymbolTableResult {
3119        let source_code = self.source_file.to_source_code();
3120        let location = Some(source_code.source_location(range.start(), PositionEncoding::Utf8));
3121        let end_location = Some(source_code.source_location(range.end(), PositionEncoding::Utf8));
3122
3123        // symtable_add_def_ctx() runs check_name() for definition
3124        // roles covered by DEF_PARAM | DEF_LOCAL | DEF_IMPORT before adding
3125        // the symbol. Several Rust callers reach register_name() directly
3126        // instead of going through scan_expression(Name), so keep the guard here.
3127        if matches!(
3128            role,
3129            SymbolUsage::Assigned
3130                | SymbolUsage::Imported
3131                | SymbolUsage::AnnotationAssigned
3132                | SymbolUsage::Parameter
3133                | SymbolUsage::Iter
3134                | SymbolUsage::TypeParam
3135        ) {
3136            self.check_name(name, ExpressionContext::Store, range)?;
3137        }
3138
3139        let scope_depth = self.tables.len();
3140        let table = self.tables.last_mut().unwrap();
3141        let current_scope = table.typ;
3142
3143        // Add type param names to mangled_names set for selective mangling
3144        if matches!(role, SymbolUsage::TypeParam)
3145            && let Some(ref mut set) = table.mangled_names
3146        {
3147            set.insert(name.to_owned());
3148        }
3149
3150        let original_name = name;
3151        let name = &maybe_mangle_name(self.class_name.as_ref(), table.mangled_names.as_ref(), name);
3152        // Some checks for the symbol that present on this scope level:
3153        let symbol = if let Some(symbol) = table.symbols.get_mut(name.as_ref()) {
3154            let flags = &symbol.flags;
3155
3156            // Mirrors CPython's INNER_LOOP_CONFLICT check. extend_namedexpr_scope()
3157            // marks named-expression targets as global or nonlocal in the comprehension.
3158            // Example: [i for i in range(5) if (j := 0) for j in range(5)]
3159            // Here 'j' is used in named expr first, then as inner loop iter target
3160            if matches!(role, SymbolUsage::Iter)
3161                && flags.intersects(SymbolFlags::DEF_GLOBAL | SymbolFlags::DEF_NONLOCAL)
3162            {
3163                return Err(SymbolTableError {
3164                    error: format!(
3165                        "comprehension inner loop cannot rebind assignment expression target '{original_name}'"
3166                    ),
3167                    location,
3168                    end_location,
3169                });
3170            }
3171
3172            if matches!(role, SymbolUsage::Parameter) && flags.contains(SymbolFlags::DEF_PARAM) {
3173                return Err(SymbolTableError {
3174                    error: format!("duplicate argument '{original_name}' in function definition"),
3175                    location,
3176                    end_location,
3177                });
3178            }
3179
3180            // Role already set..
3181            if matches!(role, SymbolUsage::TypeParam) && flags.contains(SymbolFlags::DEF_TYPE_PARAM)
3182            {
3183                return Err(SymbolTableError {
3184                    error: format!("duplicate type parameter '{name}'"),
3185                    location,
3186                    end_location,
3187                });
3188            }
3189            match role {
3190                SymbolUsage::Global if !symbol.is_global() => {
3191                    if flags.contains(SymbolFlags::DEF_PARAM) {
3192                        return Err(SymbolTableError {
3193                            error: format!("name '{name}' is parameter and global"),
3194                            location,
3195                            end_location,
3196                        });
3197                    }
3198                    if flags.contains(SymbolFlags::USE) {
3199                        return Err(SymbolTableError {
3200                            error: format!("name '{name}' is used prior to global declaration"),
3201                            location,
3202                            end_location,
3203                        });
3204                    }
3205                    if flags.contains(SymbolFlags::DEF_ANNOT) {
3206                        return Err(SymbolTableError {
3207                            error: format!("annotated name '{name}' can't be global"),
3208                            location,
3209                            end_location,
3210                        });
3211                    }
3212                    if flags.contains(SymbolFlags::DEF_LOCAL) {
3213                        return Err(SymbolTableError {
3214                            error: format!(
3215                                "name '{name}' is assigned to before global declaration"
3216                            ),
3217                            location,
3218                            end_location,
3219                        });
3220                    }
3221                }
3222                SymbolUsage::Nonlocal => {
3223                    if flags.contains(SymbolFlags::DEF_PARAM) {
3224                        return Err(SymbolTableError {
3225                            error: format!("name '{name}' is parameter and nonlocal"),
3226                            location,
3227                            end_location,
3228                        });
3229                    }
3230                    if flags.contains(SymbolFlags::USE) {
3231                        return Err(SymbolTableError {
3232                            error: format!("name '{name}' is used prior to nonlocal declaration"),
3233                            location,
3234                            end_location,
3235                        });
3236                    }
3237                    if flags.contains(SymbolFlags::DEF_ANNOT) {
3238                        return Err(SymbolTableError {
3239                            error: format!("annotated name '{name}' can't be nonlocal"),
3240                            location,
3241                            end_location,
3242                        });
3243                    }
3244                    if flags.contains(SymbolFlags::DEF_LOCAL) {
3245                        return Err(SymbolTableError {
3246                            error: format!(
3247                                "name '{name}' is assigned to before nonlocal declaration"
3248                            ),
3249                            location,
3250                            end_location,
3251                        });
3252                    }
3253                }
3254                SymbolUsage::AnnotationAssigned
3255                    if current_scope != CompilerScope::Module
3256                        && flags
3257                            .intersects(SymbolFlags::DEF_GLOBAL | SymbolFlags::DEF_NONLOCAL) =>
3258                {
3259                    let usage = if flags.contains(SymbolFlags::DEF_GLOBAL) {
3260                        "global"
3261                    } else {
3262                        "nonlocal"
3263                    };
3264                    return Err(SymbolTableError {
3265                        error: format!("annotated name '{name}' can't be {usage}"),
3266                        location,
3267                        end_location,
3268                    });
3269                }
3270                _ => {
3271                    // Ok?
3272                }
3273            }
3274            symbol
3275        } else {
3276            // The symbol does not present on this scope level.
3277            // Some checks to insert new symbol into symbol table:
3278            match role {
3279                SymbolUsage::Nonlocal if scope_depth < 2 => {
3280                    return Err(SymbolTableError {
3281                        error: "nonlocal declaration not allowed at module level".into(),
3282                        location,
3283                        end_location,
3284                    });
3285                }
3286                _ => {
3287                    // Ok!
3288                }
3289            }
3290            // Insert symbol when required:
3291            let symbol = Symbol::new(name.clone().into_owned());
3292            table
3293                .symbols
3294                .entry(name.clone().into_owned())
3295                .or_insert(symbol)
3296        };
3297
3298        if matches!(role, SymbolUsage::Global | SymbolUsage::Nonlocal) && symbol.location.is_none()
3299        {
3300            symbol.location = location;
3301            symbol.end_location = end_location;
3302        }
3303
3304        // Set proper scope and flags on symbol:
3305        let flags = &mut symbol.flags;
3306        match role {
3307            SymbolUsage::Nonlocal => {
3308                symbol.scope = SymbolScope::Free;
3309                flags.insert(SymbolFlags::DEF_NONLOCAL);
3310            }
3311            SymbolUsage::Imported => {
3312                flags.insert(SymbolFlags::DEF_IMPORT);
3313            }
3314            SymbolUsage::Parameter => {
3315                flags.insert(SymbolFlags::DEF_PARAM);
3316                // Parameters are always added to varnames first
3317                let name_str = symbol.name.clone();
3318                if !self.current_varnames.contains(&name_str) {
3319                    self.current_varnames.push(name_str);
3320                }
3321            }
3322            SymbolUsage::AnnotationAssigned => {
3323                flags.insert(SymbolFlags::DEF_LOCAL | SymbolFlags::DEF_ANNOT);
3324            }
3325            SymbolUsage::Assigned => {
3326                flags.insert(SymbolFlags::DEF_LOCAL);
3327            }
3328            SymbolUsage::Global => {
3329                symbol.scope = SymbolScope::GlobalExplicit;
3330                flags.insert(SymbolFlags::DEF_GLOBAL);
3331            }
3332            SymbolUsage::Used => {
3333                flags.insert(SymbolFlags::USE);
3334            }
3335            SymbolUsage::Iter => {
3336                flags.insert(SymbolFlags::DEF_LOCAL | SymbolFlags::DEF_COMP_ITER);
3337            }
3338            SymbolUsage::TypeParam => {
3339                flags.insert(SymbolFlags::DEF_LOCAL | SymbolFlags::DEF_TYPE_PARAM);
3340            }
3341        }
3342
3343        // A global declaration is recorded in the module block as well, so a name
3344        // declared global anywhere is global there too.
3345        if matches!(role, SymbolUsage::Global) {
3346            let module_table = self.tables.first_mut().expect("no module symbol table");
3347            let symbol = module_table
3348                .symbols
3349                .entry(name.clone().into_owned())
3350                .or_insert_with(|| Symbol::new(name.clone().into_owned()));
3351            symbol.flags.insert(SymbolFlags::DEF_GLOBAL);
3352            symbol.scope = SymbolScope::GlobalExplicit;
3353        }
3354
3355        Ok(())
3356    }
3357}
3358
3359fn is_docstring_expr(expr: &ast::Expr) -> bool {
3360    matches!(
3361        expr,
3362        ast::Expr::StringLiteral(_)
3363            | ast::Expr::Constant(ast::ExprConstant {
3364                value: ast::ConstantValue::Str(_),
3365                ..
3366            })
3367    )
3368}
3369
3370pub(crate) fn mangle_name<'a>(class_name: Option<&Name>, name: &'a Name) -> Cow<'a, Name> {
3371    let Some(class_name) = class_name else {
3372        return Cow::Borrowed(name);
3373    };
3374
3375    if !name.starts_with("__") || name.ends_with("__") || name.contains('.') {
3376        return Cow::Borrowed(name);
3377    }
3378    // Strip leading underscores from class name
3379    let class_name = class_name.trim_start_matches('_');
3380    if class_name.is_empty() {
3381        return Cow::Borrowed(name);
3382    }
3383    let mut ret = String::with_capacity(1 + class_name.len() + name.len());
3384    ret.push('_');
3385    ret.push_str(class_name);
3386    ret.push_str(name);
3387    Cow::Owned(ret.into())
3388}
3389
3390/// Selective mangling for type parameter scopes around generic classes.
3391/// If `mangled_names` is Some, only mangle names that are in the set;
3392/// other names are left unmangled.
3393pub(crate) fn maybe_mangle_name<'a>(
3394    class_name: Option<&Name>,
3395    mangled_names: Option<&IndexSet<Name>>,
3396    name: &'a Name,
3397) -> Cow<'a, Name> {
3398    if let Some(set) = mangled_names
3399        && !set.contains(name)
3400    {
3401        return Cow::Borrowed(name);
3402    }
3403    mangle_name(class_name, name)
3404}
3405
3406#[cfg(test)]
3407mod tests {
3408    use super::{CompilerScope, SymbolFlags, SymbolTable, mangle_name};
3409    use rustpython_compiler_core::SourceFileBuilder;
3410
3411    fn scan_source(source: &str) -> SymbolTable {
3412        scan_source_result(source).unwrap()
3413    }
3414
3415    fn scan_source_result(source: &str) -> Result<SymbolTable, super::SymbolTableError> {
3416        let source_file = SourceFileBuilder::new("source_path", source).finish();
3417        let parsed = ruff_python_parser::parse(
3418            source_file.source_text(),
3419            ruff_python_parser::Mode::Module.into(),
3420        )
3421        .unwrap()
3422        .into_syntax();
3423        let module = match parsed {
3424            ruff_python_ast::Mod::Module(module) => module,
3425            _ => unreachable!(),
3426        };
3427        SymbolTable::scan_program(&module, source_file)
3428    }
3429
3430    #[test]
3431    fn mangle_name_leaves_private_name_in_underscore_only_class() {
3432        assert_eq!(mangle_name(Some(&"_".into()), &"__a".into()), "__a".into());
3433        assert_eq!(mangle_name(Some(&"__".into()), &"__a".into()), "__a".into());
3434        assert_eq!(
3435            mangle_name(Some(&"___".into()), &"__a".into()),
3436            "__a".into()
3437        );
3438    }
3439
3440    #[test]
3441    fn mangle_name_strips_leading_class_underscores() {
3442        assert_eq!(
3443            mangle_name(Some(&"_a".into()), &"__a".into()),
3444            "_a__a".into()
3445        );
3446        assert_eq!(
3447            mangle_name(Some(&"__a".into()), &"__a".into()),
3448            "_a__a".into()
3449        );
3450    }
3451
3452    #[test]
3453    fn duplicate_parameter_check_uses_mangled_name_like_cpython() {
3454        let err = scan_source_result("class C:\n    def f(__x, _C__x):\n        pass\n")
3455            .expect_err("expected duplicate argument after class-private mangling");
3456
3457        assert_eq!(
3458            err.error,
3459            "duplicate argument '_C__x' in function definition"
3460        );
3461    }
3462
3463    #[test]
3464    fn super_name_marks_class_use_in_lambda_scope_like_cpython() {
3465        let table = scan_source("def f():\n    return lambda: super()\n");
3466        let function = table
3467            .sub_tables
3468            .iter()
3469            .find(|table| table.name == "f")
3470            .expect("missing function scope");
3471        let lambda = function
3472            .sub_tables
3473            .iter()
3474            .find(|table| table.typ == CompilerScope::Lambda)
3475            .expect("missing lambda scope");
3476
3477        assert!(
3478            lambda.lookup(&"__class__".into()).is_some(),
3479            "CPython symtable Name_kind treats super as a __class__ use in any function-like scope"
3480        );
3481    }
3482
3483    #[test]
3484    fn comprehension_iteration_target_sets_comp_iter_flag_like_cpython() {
3485        let table = scan_source("result = [i for i in xs]\n");
3486        let comprehension = table
3487            .inlined_comprehension_blocks
3488            .iter()
3489            .find(|table| table.typ == CompilerScope::Comprehension)
3490            .expect("missing comprehension scope");
3491        let symbol = comprehension
3492            .lookup(&"i".into())
3493            .expect("missing comprehension iteration target");
3494
3495        assert!(
3496            symbol.flags.contains(SymbolFlags::DEF_COMP_ITER),
3497            "CPython symtable_add_def_helper sets DEF_COMP_ITER on comprehension iteration targets"
3498        );
3499    }
3500
3501    #[test]
3502    fn inlined_comprehension_children_are_spliced_like_cpython() {
3503        let table = scan_source("result = [(lambda: i) for i in xs]\n");
3504
3505        assert!(
3506            !table
3507                .sub_tables
3508                .iter()
3509                .any(|table| table.typ == CompilerScope::Comprehension),
3510            "CPython removes inlined comprehension entries from ste_children"
3511        );
3512        assert!(
3513            table
3514                .sub_tables
3515                .iter()
3516                .any(|table| table.typ == CompilerScope::Lambda),
3517            "CPython splices children of inlined comprehensions into the parent children list"
3518        );
3519
3520        let comprehension = table
3521            .inlined_comprehension_blocks
3522            .iter()
3523            .find(|table| table.typ == CompilerScope::Comprehension)
3524            .expect("missing inlined comprehension block");
3525        assert!(
3526            comprehension.comp_inlined,
3527            "CPython keeps the comprehension entry addressable through st_blocks with ste_comp_inlined set"
3528        );
3529    }
3530
3531    #[test]
3532    fn future_annotations_annassign_still_scans_annotation_symbols_like_cpython() {
3533        let table = scan_source("from __future__ import annotations\nx: T\n");
3534        let annotation_block = table
3535            .annotation_block
3536            .as_ref()
3537            .expect("CPython still creates an AnnotationBlock for future annotations");
3538
3539        assert!(
3540            annotation_block.lookup(&"T".into()).is_some(),
3541            "CPython symtable_visit_annotation still visits the annotation expression with future annotations"
3542        );
3543    }
3544
3545    #[test]
3546    fn annotation_like_format_parameter_is_marked_used_like_cpython() {
3547        let table = scan_source("def f(x: T): pass\n");
3548        let annotation_block = table
3549            .sub_tables
3550            .iter()
3551            .find(|table| table.typ == CompilerScope::Annotation)
3552            .expect("missing function annotation block");
3553        let format = annotation_block
3554            .lookup(&".format".into())
3555            .expect("missing annotation .format parameter");
3556        assert_eq!(annotation_block.varnames, [".format"]);
3557        assert!(
3558            format
3559                .flags
3560                .contains(SymbolFlags::DEF_PARAM | SymbolFlags::USE),
3561            "CPython symtable_enter_block() adds both DEF_PARAM and USE for annotation-like .format"
3562        );
3563
3564        let table = scan_source("type A = T\n");
3565        let alias = table
3566            .sub_tables
3567            .iter()
3568            .find(|table| table.typ == CompilerScope::TypeAlias)
3569            .expect("missing type alias scope");
3570        let format = alias
3571            .lookup(&".format".into())
3572            .expect("missing type alias .format parameter");
3573        assert!(
3574            format
3575                .flags
3576                .contains(SymbolFlags::DEF_PARAM | SymbolFlags::USE),
3577            "CPython TypeAliasBlock .format has DEF_PARAM | USE"
3578        );
3579
3580        let table = scan_source("def f[T: B](): pass\n");
3581        let type_params = table
3582            .sub_tables
3583            .iter()
3584            .find(|table| table.typ == CompilerScope::TypeParams)
3585            .expect("missing type params scope");
3586        let type_variable = type_params
3587            .sub_tables
3588            .iter()
3589            .find(|table| table.typ == CompilerScope::TypeVariable)
3590            .expect("missing type variable scope");
3591        let format = type_variable
3592            .lookup(&".format".into())
3593            .expect("missing type variable .format parameter");
3594        assert!(
3595            format
3596                .flags
3597                .contains(SymbolFlags::DEF_PARAM | SymbolFlags::USE),
3598            "CPython TypeVariableBlock .format has DEF_PARAM | USE"
3599        );
3600    }
3601
3602    #[test]
3603    fn deferred_annotation_store_names_are_not_public_symbols() {
3604        let module = scan_source("x: int\n");
3605        assert!(module.lookup(&"__annotate__".into()).is_none());
3606        assert!(module.annotation_block.is_some());
3607
3608        let module = scan_source("class C:\n    y: str\n");
3609        let class = module
3610            .sub_tables
3611            .iter()
3612            .find(|table| table.typ == CompilerScope::Class)
3613            .expect("missing class scope");
3614        assert!(class.lookup(&"__annotate_func__".into()).is_none());
3615        assert!(class.annotation_block.is_some());
3616    }
3617
3618    #[test]
3619    fn generic_class_symbols_follow_cpython_insertion_order() {
3620        let module = scan_source("class C[T]:\n    q = [lambda: i for i in range(2)]\n");
3621        let type_params = module
3622            .sub_tables
3623            .iter()
3624            .find(|table| table.typ == CompilerScope::TypeParams)
3625            .expect("missing type parameter scope");
3626        let class = type_params
3627            .sub_tables
3628            .iter()
3629            .find(|table| table.typ == CompilerScope::Class)
3630            .expect("missing generic class scope");
3631        assert_eq!(
3632            class.symbols.keys().collect::<Vec<_>>(),
3633            ["__type_params__", ".type_params", "q", "range", "i"]
3634        );
3635    }
3636
3637    #[test]
3638    fn function_signature_annotation_block_is_sibling_like_cpython() {
3639        let table = scan_source("def f(x: T): pass\n");
3640        assert_eq!(table.sub_tables[0].typ, CompilerScope::Annotation);
3641        assert!(table.sub_tables[0].annotations_used);
3642        assert_eq!(table.sub_tables[1].typ, CompilerScope::Function);
3643        assert!(
3644            table.sub_tables[1].annotation_block.is_none(),
3645            "CPython stores the function signature AnnotationBlock as a child keyed by arguments, not on the function block"
3646        );
3647
3648        let table = scan_source("def f(x): pass\n");
3649        assert_eq!(table.sub_tables[0].typ, CompilerScope::Annotation);
3650        assert!(!table.sub_tables[0].annotations_used);
3651        assert_eq!(table.sub_tables[1].typ, CompilerScope::Function);
3652    }
3653
3654    #[test]
3655    fn future_function_signature_annotation_block_is_hidden_like_cpython() {
3656        let table = scan_source("from __future__ import annotations\ndef f(x: T): pass\n");
3657        assert_eq!(table.sub_tables[0].typ, CompilerScope::Function);
3658        assert_eq!(
3659            table.hidden_annotation_blocks[0].typ,
3660            CompilerScope::Annotation
3661        );
3662        assert!(table.hidden_annotation_blocks[0].annotations_used);
3663        assert!(
3664            table.sub_tables[0].annotation_block.is_none(),
3665            "CPython future AnnotationBlock stays in st_blocks and is not attached to the FunctionBlock"
3666        );
3667
3668        let table = scan_source("from __future__ import annotations\ndef f(x): pass\n");
3669        assert_eq!(table.sub_tables[0].typ, CompilerScope::Function);
3670        assert_eq!(
3671            table.hidden_annotation_blocks[0].typ,
3672            CompilerScope::Annotation
3673        );
3674        assert!(!table.hidden_annotation_blocks[0].annotations_used);
3675    }
3676
3677    #[test]
3678    fn annassign_marks_current_scope_annotations_used_like_cpython() {
3679        let table = scan_source("x: int\n");
3680        assert!(
3681            table.annotations_used,
3682            "CPython AnnAssign_kind sets ste_annotations_used on the current scope"
3683        );
3684
3685        let table = scan_source("class C:\n    x: int\n");
3686        let class = table
3687            .sub_tables
3688            .iter()
3689            .find(|table| table.typ == CompilerScope::Class)
3690            .expect("missing class scope");
3691        assert!(
3692            class.annotations_used,
3693            "CPython AnnAssign_kind sets ste_annotations_used on class scopes"
3694        );
3695
3696        let table = scan_source("def f():\n    x: int\n");
3697        let function = table
3698            .sub_tables
3699            .iter()
3700            .find(|table| table.typ == CompilerScope::Function)
3701            .expect("missing function scope");
3702        assert!(
3703            function.annotations_used,
3704            "CPython AnnAssign_kind also marks function-local annotations"
3705        );
3706    }
3707
3708    #[test]
3709    fn class_base_child_scope_precedes_class_scope_like_cpython() {
3710        let table = scan_source("class C((lambda: Base)()):\n    pass\n");
3711        assert_eq!(table.sub_tables[0].typ, CompilerScope::Lambda);
3712        assert_eq!(table.sub_tables[1].typ, CompilerScope::Class);
3713    }
3714
3715    #[test]
3716    fn try_handler_child_scope_precedes_else_scope_like_cpython() {
3717        let table = scan_source(
3718            "\
3719def f(x):
3720    try:
3721        pass
3722    except Exception:
3723        y = 1
3724        def h():
3725            return y
3726    else:
3727        def e():
3728            return x
3729",
3730        );
3731        let function = table
3732            .sub_tables
3733            .iter()
3734            .find(|table| table.name == "f")
3735            .expect("missing function scope");
3736
3737        let function_child_names = function
3738            .sub_tables
3739            .iter()
3740            .filter(|table| table.typ == CompilerScope::Function)
3741            .map(|table| table.name.as_str())
3742            .collect::<Vec<_>>();
3743        assert_eq!(function_child_names, vec!["h", "e"]);
3744    }
3745
3746    #[test]
3747    fn function_default_child_scope_precedes_decorator_scope_like_cpython() {
3748        let table = scan_source(
3749            "\
3750@(lambda decorator_arg: decorator_arg)
3751def f(x=(lambda: 1)()):
3752    pass
3753",
3754        );
3755        let lambdas = table
3756            .sub_tables
3757            .iter()
3758            .filter(|table| table.typ == CompilerScope::Lambda)
3759            .collect::<Vec<_>>();
3760
3761        assert_eq!(lambdas.len(), 2);
3762        assert!(
3763            lambdas[0].varnames.is_empty(),
3764            "CPython symtable visits function defaults before decorators"
3765        );
3766        assert_eq!(lambdas[1].varnames, vec!["decorator_arg"]);
3767    }
3768
3769    #[test]
3770    fn future_annotations_still_rejects_named_expr_in_annotation_like_cpython() {
3771        let err =
3772            scan_source_result("from __future__ import annotations\nx: (y := int)\n").unwrap_err();
3773
3774        assert_eq!(
3775            err.error,
3776            "named expression cannot be used within an annotation"
3777        );
3778    }
3779
3780    #[test]
3781    fn import_star_outside_module_uses_cpython_symtable_message() {
3782        let err = scan_source_result("def f():\n    from m import *\n").unwrap_err();
3783
3784        assert_eq!(err.error, "import * only allowed at module level");
3785    }
3786
3787    #[test]
3788    fn import_as_error_location_uses_alias_location_like_cpython() {
3789        let source = "import module as __debug__\n";
3790        let err = scan_source_result(source).unwrap_err();
3791
3792        assert_eq!(err.error, "cannot assign to __debug__");
3793        let location = err.location.unwrap();
3794        assert_eq!(location.line.get(), 1);
3795        assert_eq!(
3796            location.character_offset.get(),
3797            8,
3798            "CPython reports LOCATION(a) for import aliases, at the imported name"
3799        );
3800    }
3801
3802    #[test]
3803    fn function_def_error_location_uses_statement_location_like_cpython() {
3804        let source = "def __debug__():\n    pass\n";
3805        let err = scan_source_result(source).unwrap_err();
3806
3807        assert_eq!(err.error, "cannot assign to __debug__");
3808        let location = err.location.unwrap();
3809        assert_eq!(location.line.get(), 1);
3810        assert_eq!(
3811            location.character_offset.get(),
3812            1,
3813            "CPython reports LOCATION(s) for FunctionDef, at 'def'"
3814        );
3815    }
3816
3817    #[test]
3818    fn global_after_assign_error_location_uses_statement_location_like_cpython() {
3819        let source = "def f():\n    x = 1\n    global x\n";
3820        let err = scan_source_result(source).unwrap_err();
3821
3822        assert_eq!(
3823            err.error,
3824            "name 'x' is assigned to before global declaration"
3825        );
3826        let location = err.location.unwrap();
3827        assert_eq!(location.line.get(), 3);
3828        assert_eq!(
3829            location.character_offset.get(),
3830            5,
3831            "CPython reports LOCATION(s) for global directives, at 'global'"
3832        );
3833    }
3834
3835    #[test]
3836    fn type_param_debug_name_is_checked_like_cpython_add_def_ctx() {
3837        let source = "class C[__debug__]:\n    pass\n";
3838        let err = scan_source_result(source).unwrap_err();
3839
3840        assert_eq!(err.error, "cannot assign to __debug__");
3841        let location = err.location.unwrap();
3842        assert_eq!(location.line.get(), 1);
3843        assert_eq!(
3844            location.character_offset.get(),
3845            9,
3846            "CPython symtable_add_def_ctx checks DEF_TYPE_PARAM | DEF_LOCAL at LOCATION(tp)"
3847        );
3848    }
3849
3850    #[test]
3851    fn except_handler_name_error_location_uses_handler_location_like_cpython() {
3852        let source = "try:\n    pass\nexcept Exception as __debug__:\n    pass\n";
3853        let err = scan_source_result(source).unwrap_err();
3854
3855        assert_eq!(err.error, "cannot assign to __debug__");
3856        let location = err.location.unwrap();
3857        assert_eq!(location.line.get(), 3);
3858        assert_eq!(
3859            location.character_offset.get(),
3860            1,
3861            "CPython reports LOCATION(eh) for except-handler names, at 'except'"
3862        );
3863    }
3864
3865    #[test]
3866    fn match_star_capture_error_location_uses_pattern_location_like_cpython() {
3867        let source = "match subject:\n    case [*__debug__]:\n        pass\n";
3868        let err = scan_source_result(source).unwrap_err();
3869
3870        assert_eq!(err.error, "cannot assign to __debug__");
3871        let location = err.location.unwrap();
3872        assert_eq!(location.line.get(), 2);
3873        assert_eq!(
3874            location.character_offset.get(),
3875            11,
3876            "CPython reports LOCATION(p) for MatchStar, at the '*'"
3877        );
3878    }
3879
3880    #[test]
3881    fn named_expr_in_lambda_inside_comprehension_iter_is_rejected_like_cpython() {
3882        let err = scan_source_result("[x for x in (lambda: (y := 1))()]\n").unwrap_err();
3883
3884        assert_eq!(
3885            err.error,
3886            "assignment expression cannot be used in a comprehension iterable expression"
3887        );
3888    }
3889
3890    #[test]
3891    fn yield_in_lambda_inside_comprehension_body_is_not_comprehension_yield_like_cpython() {
3892        scan_source_result("[(lambda: (yield x)) for x in xs]\n").expect(
3893            "CPython checks ste_comprehension on the current lambda block, not the enclosing comprehension",
3894        );
3895    }
3896
3897    #[test]
3898    fn yield_in_comprehension_scans_value_before_comprehension_error_like_cpython() {
3899        let err = scan_source_result("[(yield (x := 1)) for x in xs]\n").unwrap_err();
3900
3901        assert_eq!(
3902            err.error,
3903            "assignment expression cannot rebind comprehension iteration variable 'x'"
3904        );
3905    }
3906
3907    #[test]
3908    fn named_expr_in_function_annotation_comprehension_is_allowed_like_cpython() {
3909        scan_source_result("def f(x: [(y := int) for _ in xs]): pass\n").expect(
3910            "CPython skips AnnotationBlock while extending namedexpr scope from a comprehension",
3911        );
3912    }
3913
3914    #[test]
3915    fn named_expr_in_class_annotation_comprehension_uses_cpython_message() {
3916        let err = scan_source_result("class C:\n    x: [(y := int) for _ in xs]\n").unwrap_err();
3917
3918        assert_eq!(
3919            err.error,
3920            "assignment expression within a comprehension cannot be used in a class body"
3921        );
3922    }
3923
3924    #[test]
3925    fn named_expr_in_type_alias_comprehension_uses_cpython_message() {
3926        let err = scan_source_result("type A = [(y := int) for _ in xs]\n").unwrap_err();
3927
3928        assert_eq!(
3929            err.error,
3930            "assignment expression within a comprehension cannot be used in a type alias"
3931        );
3932    }
3933
3934    #[test]
3935    fn named_expr_in_type_parameters_block_uses_cpython_message() {
3936        let err = scan_source_result("class C[T]((base := object)): pass\n").unwrap_err();
3937
3938        assert_eq!(
3939            err.error,
3940            "named expression cannot be used within the definition of a generic"
3941        );
3942    }
3943
3944    #[test]
3945    fn named_expr_in_typevar_bound_comprehension_uses_cpython_message() {
3946        let err = scan_source_result("def f[T: [(y := int) for _ in xs]](): pass\n").unwrap_err();
3947
3948        assert_eq!(
3949            err.error,
3950            "assignment expression within a comprehension cannot be used in a TypeVar bound"
3951        );
3952    }
3953}