Skip to main content

rustpython_codegen/
compile.rs

1//!
2//! Take an AST and transform it into bytecode
3//!
4//! Inspirational code:
5//!   <https://github.com/python/cpython/blob/main/Python/compile.c>
6//!   <https://github.com/micropython/micropython/blob/master/py/compile.c>
7
8// spell-checker:ignore starunpack subscripter
9
10#![deny(clippy::cast_possible_truncation)]
11
12use crate::{
13    IndexMap, IndexSet, ToPythonName,
14    error::{CodegenError, CodegenErrorType, InternalError, PatternUnreachableReason},
15    ir::{self, BlockIdx},
16    symboltable::{self, CompilerScope, Symbol, SymbolFlags, SymbolScope, SymbolTable},
17    unparse::UnparseExpr,
18};
19use alloc::borrow::Cow;
20use itertools::Itertools;
21use malachite_bigint::BigInt;
22use num_complex::Complex;
23use num_traits::{Num, ToPrimitive};
24use ruff_python_ast as ast;
25use ruff_text_size::{Ranged, TextRange, TextSize};
26use rustpython_compiler_core::{
27    Mode, OneIndexed, PositionEncoding, SourceFile, SourceLocation,
28    bytecode::{
29        self, AnyInstruction, Arg as OpArgMarker, BinaryOperator, BuildSliceArgCount, CodeObject,
30        ComparisonOperator, ConstantData, ConvertValueOparg, Instruction, IntrinsicFunction1,
31        Invert, LoadAttr, LoadSuperAttr, OpArg, OpArgType, PseudoInstruction, SpecialMethod,
32        UnpackExArgs, oparg,
33    },
34};
35use rustpython_wtf8::Wtf8Buf;
36
37/// Extension trait for `ast::Expr` to add constant checking methods
38trait ExprExt {
39    /// Check if an expression is a constant literal
40    fn is_constant(&self) -> bool;
41
42    /// Check if a slice expression has all constant elements
43    fn is_constant_slice(&self) -> bool;
44
45    /// Check if we should use BINARY_SLICE/STORE_SLICE optimization
46    fn should_use_slice_optimization(&self) -> bool;
47}
48
49impl ExprExt for ast::Expr {
50    fn is_constant(&self) -> bool {
51        matches!(
52            self,
53            ast::Expr::NumberLiteral(_)
54                | ast::Expr::StringLiteral(_)
55                | ast::Expr::BytesLiteral(_)
56                | ast::Expr::NoneLiteral(_)
57                | ast::Expr::BooleanLiteral(_)
58                | ast::Expr::EllipsisLiteral(_)
59        ) || matches!(self, ast::Expr::Tuple(ast::ExprTuple { elts, .. }) if elts.iter().all(ExprExt::is_constant))
60    }
61
62    fn is_constant_slice(&self) -> bool {
63        match self {
64            ast::Expr::Slice(s) => {
65                let lower_const =
66                    s.lower.is_none() || s.lower.as_deref().is_some_and(|e| e.is_constant());
67                let upper_const =
68                    s.upper.is_none() || s.upper.as_deref().is_some_and(|e| e.is_constant());
69                let step_const =
70                    s.step.is_none() || s.step.as_deref().is_some_and(|e| e.is_constant());
71                lower_const && upper_const && step_const
72            }
73            _ => false,
74        }
75    }
76
77    fn should_use_slice_optimization(&self) -> bool {
78        !self.is_constant_slice() && matches!(self, ast::Expr::Slice(s) if s.step.is_none())
79    }
80}
81
82const MAXBLOCKS: usize = 20;
83
84#[derive(Debug, Clone, Copy)]
85pub enum FBlockType {
86    WhileLoop,
87    ForLoop,
88    TryExcept,
89    FinallyTry,
90    FinallyEnd,
91    With,
92    AsyncWith,
93    HandlerCleanup,
94    PopValue,
95    ExceptionHandler,
96    ExceptionGroupHandler,
97    AsyncComprehensionGenerator,
98    StopIteration,
99}
100
101/// Stores additional data for fblock unwinding
102// fb_datum
103#[derive(Debug, Clone)]
104pub enum FBlockDatum {
105    None,
106    /// For FinallyTry: stores the finally body statements to compile during unwind
107    FinallyBody(Vec<ast::Stmt>),
108    /// For HandlerCleanup: stores the exception variable name (e.g., "e" in "except X as e")
109    ExceptionName(String),
110}
111
112/// Type of super() call optimization detected by can_optimize_super_call()
113#[derive(Debug, Clone)]
114enum SuperCallType<'a> {
115    /// super(class, self) - explicit 2-argument form
116    TwoArg {
117        class_arg: &'a ast::Expr,
118        self_arg: &'a ast::Expr,
119    },
120    /// super() - implicit 0-argument form (uses __class__ cell)
121    ZeroArg,
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125enum BuiltinGeneratorCallKind {
126    Tuple,
127    List,
128    Set,
129    All,
130    Any,
131}
132
133#[derive(Debug, Clone)]
134pub struct FBlockInfo {
135    pub fb_type: FBlockType,
136    pub fb_block: BlockIdx,
137    pub fb_exit: BlockIdx,
138    // additional data for fblock unwinding
139    pub fb_datum: FBlockDatum,
140}
141
142pub(crate) type InternalResult<T> = Result<T, InternalError>;
143type CompileResult<T> = Result<T, CodegenError>;
144
145#[derive(PartialEq, Eq, Clone, Copy)]
146enum NameUsage {
147    Load,
148    Store,
149    Delete,
150}
151/// Main structure holding the state of compilation.
152struct Compiler {
153    code_stack: Vec<ir::CodeInfo>,
154    symbol_table_stack: Vec<SymbolTable>,
155    source_file: SourceFile,
156    // current_source_location: SourceLocation,
157    current_source_range: TextRange,
158    done_with_future_stmts: DoneWithFuture,
159    future_annotations: bool,
160    ctx: CompileContext,
161    opts: CompileOpts,
162    in_annotation: bool,
163    /// True when compiling in "single" (interactive) mode.
164    /// Expression statements at module scope emit CALL_INTRINSIC_1(Print).
165    interactive: bool,
166    /// Counter for dead-code elimination during constant folding.
167    /// When > 0, the compiler walks AST (consuming sub_tables) but emits no bytecode.
168    /// Mirrors CPython's `c_do_not_emit_bytecode`.
169    do_not_emit_bytecode: u32,
170}
171
172#[derive(Clone, Copy)]
173enum DoneWithFuture {
174    No,
175    DoneWithDoc,
176    Yes,
177}
178
179#[derive(Clone, Copy, Debug)]
180pub struct CompileOpts {
181    /// How optimized the bytecode output should be; any optimize > 0 does
182    /// not emit assert statements
183    pub optimize: u8,
184    /// Include column info in bytecode (-X no_debug_ranges disables)
185    pub debug_ranges: bool,
186}
187
188impl Default for CompileOpts {
189    fn default() -> Self {
190        Self {
191            optimize: 0,
192            debug_ranges: true,
193        }
194    }
195}
196
197#[derive(Debug, Clone, Copy)]
198struct CompileContext {
199    loop_data: Option<(BlockIdx, BlockIdx)>,
200    in_class: bool,
201    func: FunctionContext,
202    /// True if we're anywhere inside an async function (even inside nested comprehensions)
203    in_async_scope: bool,
204}
205
206#[derive(Debug, Clone, Copy, PartialEq)]
207enum FunctionContext {
208    NoFunction,
209    Function,
210    AsyncFunction,
211}
212
213impl CompileContext {
214    fn in_func(self) -> bool {
215        self.func != FunctionContext::NoFunction
216    }
217}
218
219/// Segment of a parsed %-format string for optimize_format_str.
220struct FormatSegment {
221    literal: String,
222    conversion: Option<oparg::ConvertValueOparg>,
223}
224
225#[derive(Debug, Clone, Copy, PartialEq)]
226enum ComprehensionType {
227    Generator,
228    List,
229    Set,
230    Dict,
231}
232
233fn validate_duplicate_params(params: &ast::Parameters) -> Result<(), CodegenErrorType> {
234    let mut seen_params = IndexSet::default();
235    for param in params {
236        let param_name = param.name().as_str();
237        if !seen_params.insert(param_name) {
238            return Err(CodegenErrorType::SyntaxError(format!(
239                r#"Duplicate parameter "{param_name}""#
240            )));
241        }
242    }
243
244    Ok(())
245}
246
247/// Compile an Mod produced from ruff parser
248pub fn compile_top(
249    ast: ruff_python_ast::Mod,
250    source_file: SourceFile,
251    mode: Mode,
252    opts: CompileOpts,
253) -> CompileResult<CodeObject> {
254    match ast {
255        ruff_python_ast::Mod::Module(module) => match mode {
256            Mode::Exec | Mode::Eval => compile_program(&module, source_file, opts),
257            Mode::Single => compile_program_single(&module, source_file, opts),
258            Mode::BlockExpr => compile_block_expression(&module, source_file, opts),
259        },
260        ruff_python_ast::Mod::Expression(expr) => compile_expression(&expr, source_file, opts),
261    }
262}
263
264/// Compile a standard Python program to bytecode
265pub fn compile_program(
266    ast: &ast::ModModule,
267    source_file: SourceFile,
268    opts: CompileOpts,
269) -> CompileResult<CodeObject> {
270    let symbol_table = SymbolTable::scan_program(ast, source_file.clone())
271        .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))?;
272    let mut compiler = Compiler::new(opts, source_file, "<module>".to_owned());
273    compiler.compile_program(ast, symbol_table)?;
274    let code = compiler.exit_scope();
275    trace!("Compilation completed: {code:?}");
276    Ok(code)
277}
278
279/// Compile a Python program to bytecode for the context of a REPL
280pub fn compile_program_single(
281    ast: &ast::ModModule,
282    source_file: SourceFile,
283    opts: CompileOpts,
284) -> CompileResult<CodeObject> {
285    let symbol_table = SymbolTable::scan_program(ast, source_file.clone())
286        .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))?;
287    let mut compiler = Compiler::new(opts, source_file, "<module>".to_owned());
288    compiler.compile_program_single(&ast.body, symbol_table)?;
289    let code = compiler.exit_scope();
290    trace!("Compilation completed: {code:?}");
291    Ok(code)
292}
293
294pub fn compile_block_expression(
295    ast: &ast::ModModule,
296    source_file: SourceFile,
297    opts: CompileOpts,
298) -> CompileResult<CodeObject> {
299    let symbol_table = SymbolTable::scan_program(ast, source_file.clone())
300        .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))?;
301    let mut compiler = Compiler::new(opts, source_file, "<module>".to_owned());
302    compiler.compile_block_expr(&ast.body, symbol_table)?;
303    let code = compiler.exit_scope();
304    trace!("Compilation completed: {code:?}");
305    Ok(code)
306}
307
308pub fn compile_expression(
309    ast: &ast::ModExpression,
310    source_file: SourceFile,
311    opts: CompileOpts,
312) -> CompileResult<CodeObject> {
313    let symbol_table = SymbolTable::scan_expr(ast, source_file.clone())
314        .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))?;
315    let mut compiler = Compiler::new(opts, source_file, "<module>".to_owned());
316    compiler.compile_eval(ast, symbol_table)?;
317    let code = compiler.exit_scope();
318    Ok(code)
319}
320
321macro_rules! emit {
322    // Struct variant with single identifier (e.g., Foo::A { arg })
323    ($c:expr, $enum:ident :: $op:ident { $arg:ident $(,)? } $(,)?) => {
324        $c.emit_arg($arg, |x| $enum::$op { $arg: x })
325    };
326
327    // Struct variant with explicit value (e.g., Foo::A { arg: 42 })
328    ($c:expr, $enum:ident :: $op:ident { $arg:ident : $arg_val:expr $(,)? } $(,)?) => {
329        $c.emit_arg($arg_val, |x| $enum::$op { $arg: x })
330    };
331
332    // Tuple variant (e.g., Foo::B(42)). Should never be reached, here for validation.
333    ($c:expr, $enum:ident :: $op:ident($arg_val:expr $(,)? ) $(,)?) => {
334        panic!("No instruction should be defined as `Instruction::Foo(value)` use `Instruction::Foo { x: value }` instead")
335    };
336
337    // No-arg variant (e.g., Foo::C)
338    ($c:expr, $enum:ident :: $op:ident $(,)?) => {
339        $c.emit_no_arg($enum::$op)
340    };
341}
342
343fn eprint_location(zelf: &Compiler) {
344    let start = zelf
345        .source_file
346        .to_source_code()
347        .source_location(zelf.current_source_range.start(), PositionEncoding::Utf8);
348    let end = zelf
349        .source_file
350        .to_source_code()
351        .source_location(zelf.current_source_range.end(), PositionEncoding::Utf8);
352    eprintln!(
353        "LOCATION: {} from {}:{} to {}:{}",
354        zelf.source_file.name(),
355        start.line,
356        start.character_offset,
357        end.line,
358        end.character_offset
359    );
360}
361
362/// Better traceback for internal error
363#[track_caller]
364fn unwrap_internal<T>(zelf: &Compiler, r: InternalResult<T>) -> T {
365    if let Err(ref r_err) = r {
366        eprintln!("=== CODEGEN PANIC INFO ===");
367        eprintln!("This IS an internal error: {r_err}");
368        eprint_location(zelf);
369        eprintln!("=== END PANIC INFO ===");
370    }
371    r.unwrap()
372}
373
374fn compiler_unwrap_option<T>(zelf: &Compiler, o: Option<T>) -> T {
375    if o.is_none() {
376        eprintln!("=== CODEGEN PANIC INFO ===");
377        eprintln!("This IS an internal error, an option was unwrapped during codegen");
378        eprint_location(zelf);
379        eprintln!("=== END PANIC INFO ===");
380    }
381    o.unwrap()
382}
383
384// fn compiler_result_unwrap<T, E: core::fmt::Debug>(zelf: &Compiler, result: Result<T, E>) -> T {
385//     if result.is_err() {
386//         eprintln!("=== CODEGEN PANIC INFO ===");
387//         eprintln!("This IS an internal error, an result was unwrapped during codegen");
388//         eprint_location(zelf);
389//         eprintln!("=== END PANIC INFO ===");
390//     }
391//     result.unwrap()
392// }
393
394/// The pattern context holds information about captured names and jump targets.
395#[derive(Clone)]
396pub struct PatternContext {
397    /// A list of names captured by the pattern.
398    pub stores: Vec<String>,
399    /// If false, then any name captures against our subject will raise.
400    pub allow_irrefutable: bool,
401    /// A list of jump target labels used on pattern failure.
402    pub fail_pop: Vec<BlockIdx>,
403    /// The number of items on top of the stack that should remain.
404    pub on_top: usize,
405}
406
407impl Default for PatternContext {
408    fn default() -> Self {
409        Self::new()
410    }
411}
412
413impl PatternContext {
414    pub const fn new() -> Self {
415        Self {
416            stores: Vec::new(),
417            allow_irrefutable: false,
418            fail_pop: Vec::new(),
419            on_top: 0,
420        }
421    }
422
423    pub fn fail_pop_size(&self) -> usize {
424        self.fail_pop.len()
425    }
426}
427
428enum JumpOp {
429    Jump,
430    PopJumpIfFalse,
431}
432
433/// Type of collection to build in starunpack_helper
434#[derive(Debug, Clone, Copy, PartialEq)]
435enum CollectionType {
436    Tuple,
437    List,
438    Set,
439}
440
441impl Compiler {
442    fn new(opts: CompileOpts, source_file: SourceFile, code_name: String) -> Self {
443        let module_code = ir::CodeInfo {
444            flags: bytecode::CodeFlags::NEWLOCALS,
445            source_path: source_file.name().to_owned(),
446            private: None,
447            blocks: vec![ir::Block::default()],
448            current_block: BlockIdx::new(0),
449            metadata: ir::CodeUnitMetadata {
450                name: code_name.clone(),
451                qualname: Some(code_name),
452                consts: IndexSet::default(),
453                names: IndexSet::default(),
454                varnames: IndexSet::default(),
455                cellvars: IndexSet::default(),
456                freevars: IndexSet::default(),
457                fast_hidden: IndexMap::default(),
458                argcount: 0,
459                posonlyargcount: 0,
460                kwonlyargcount: 0,
461                firstlineno: OneIndexed::MIN,
462            },
463            static_attributes: None,
464            in_inlined_comp: false,
465            fblock: Vec::with_capacity(MAXBLOCKS),
466            symbol_table_index: 0, // Module is always the first symbol table
467            in_conditional_block: 0,
468            next_conditional_annotation_index: 0,
469        };
470        Self {
471            code_stack: vec![module_code],
472            symbol_table_stack: Vec::new(),
473            source_file,
474            // current_source_location: SourceLocation::default(),
475            current_source_range: TextRange::default(),
476            done_with_future_stmts: DoneWithFuture::No,
477            future_annotations: false,
478            ctx: CompileContext {
479                loop_data: None,
480                in_class: false,
481                func: FunctionContext::NoFunction,
482                in_async_scope: false,
483            },
484            opts,
485            in_annotation: false,
486            interactive: false,
487            do_not_emit_bytecode: 0,
488        }
489    }
490
491    /// Compile just start and stop of a slice (for BINARY_SLICE/STORE_SLICE)
492    // = codegen_slice_two_parts
493    fn compile_slice_two_parts(&mut self, s: &ast::ExprSlice) -> CompileResult<()> {
494        // Compile lower (or None)
495        if let Some(lower) = &s.lower {
496            self.compile_expression(lower)?;
497        } else {
498            self.emit_load_const(ConstantData::None);
499        }
500
501        // Compile upper (or None)
502        if let Some(upper) = &s.upper {
503            self.compile_expression(upper)?;
504        } else {
505            self.emit_load_const(ConstantData::None);
506        }
507
508        Ok(())
509    }
510    /// Compile a subscript expression
511    // = compiler_subscript
512    fn compile_subscript(
513        &mut self,
514        value: &ast::Expr,
515        slice: &ast::Expr,
516        ctx: ast::ExprContext,
517    ) -> CompileResult<()> {
518        // Save full subscript expression range (set by compile_expression before this call)
519        let subscript_range = self.current_source_range;
520
521        // VISIT(c, expr, e->v.Subscript.value)
522        self.compile_expression(value)?;
523
524        // Handle two-element non-constant slice with BINARY_SLICE/STORE_SLICE
525        let use_slice_opt = matches!(ctx, ast::ExprContext::Load | ast::ExprContext::Store)
526            && slice.should_use_slice_optimization();
527        if use_slice_opt {
528            match slice {
529                ast::Expr::Slice(s) => self.compile_slice_two_parts(s)?,
530                _ => unreachable!(
531                    "should_use_slice_optimization should only return true for ast::Expr::Slice"
532                ),
533            };
534        } else {
535            // VISIT(c, expr, e->v.Subscript.slice)
536            self.compile_expression(slice)?;
537        }
538
539        // Restore full subscript expression range before emitting
540        self.set_source_range(subscript_range);
541
542        match (use_slice_opt, ctx) {
543            (true, ast::ExprContext::Load) => emit!(self, Instruction::BinarySlice),
544            (true, ast::ExprContext::Store) => emit!(self, Instruction::StoreSlice),
545            (true, _) => unreachable!(),
546            (false, ast::ExprContext::Load) => emit!(
547                self,
548                Instruction::BinaryOp {
549                    op: BinaryOperator::Subscr
550                }
551            ),
552            (false, ast::ExprContext::Store) => emit!(self, Instruction::StoreSubscr),
553            (false, ast::ExprContext::Del) => emit!(self, Instruction::DeleteSubscr),
554            (false, ast::ExprContext::Invalid) => {
555                return Err(self.error(CodegenErrorType::SyntaxError(
556                    "Invalid expression context".to_owned(),
557                )));
558            }
559        }
560
561        Ok(())
562    }
563
564    /// Helper function for compiling tuples/lists/sets with starred expressions
565    ///
566    /// ast::Parameters:
567    /// - elts: The elements to compile
568    /// - pushed: Number of items already on the stack
569    /// - collection_type: What type of collection to build (tuple, list, set)
570    ///
571    // = starunpack_helper in compile.c
572    fn starunpack_helper(
573        &mut self,
574        elts: &[ast::Expr],
575        pushed: u32,
576        collection_type: CollectionType,
577    ) -> CompileResult<()> {
578        let n = elts.len().to_u32();
579        let seen_star = elts.iter().any(|e| matches!(e, ast::Expr::Starred(_)));
580
581        // Determine collection size threshold for optimization
582        let big = match collection_type {
583            CollectionType::Set => n > 8,
584            _ => n > 4,
585        };
586
587        // Fold all-constant collections (>= 3 elements) regardless of size
588        if !seen_star
589            && pushed == 0
590            && n >= 3
591            && elts.iter().all(|e| e.is_constant())
592            && let Some(folded) = self.try_fold_constant_collection(elts)?
593        {
594            match collection_type {
595                CollectionType::Tuple => {
596                    self.emit_load_const(folded);
597                }
598                CollectionType::List => {
599                    emit!(self, Instruction::BuildList { count: 0 });
600                    self.emit_load_const(folded);
601                    emit!(self, Instruction::ListExtend { i: 1 });
602                }
603                CollectionType::Set => {
604                    emit!(self, Instruction::BuildSet { count: 0 });
605                    self.emit_load_const(folded);
606                    emit!(self, Instruction::SetUpdate { i: 1 });
607                }
608            }
609            return Ok(());
610        }
611
612        // If no stars and not too big, compile all elements and build once
613        if !seen_star && !big {
614            for elt in elts {
615                self.compile_expression(elt)?;
616            }
617            let total_size = n + pushed;
618            match collection_type {
619                CollectionType::List => {
620                    emit!(self, Instruction::BuildList { count: total_size });
621                }
622                CollectionType::Set => {
623                    emit!(self, Instruction::BuildSet { count: total_size });
624                }
625                CollectionType::Tuple => {
626                    emit!(self, Instruction::BuildTuple { count: total_size });
627                }
628            }
629            return Ok(());
630        }
631
632        // Has stars or too big: use streaming approach
633        let mut sequence_built = false;
634        let mut i = 0u32;
635
636        for elt in elts.iter() {
637            if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = elt {
638                // When we hit first star, build sequence with elements so far
639                if !sequence_built {
640                    match collection_type {
641                        CollectionType::List => {
642                            emit!(self, Instruction::BuildList { count: i + pushed });
643                        }
644                        CollectionType::Set => {
645                            emit!(self, Instruction::BuildSet { count: i + pushed });
646                        }
647                        CollectionType::Tuple => {
648                            emit!(self, Instruction::BuildList { count: i + pushed });
649                        }
650                    }
651                    sequence_built = true;
652                }
653
654                // Compile the starred expression and extend
655                self.compile_expression(value)?;
656                match collection_type {
657                    CollectionType::List => {
658                        emit!(self, Instruction::ListExtend { i: 1 });
659                    }
660                    CollectionType::Set => {
661                        emit!(self, Instruction::SetUpdate { i: 1 });
662                    }
663                    CollectionType::Tuple => {
664                        emit!(self, Instruction::ListExtend { i: 1 });
665                    }
666                }
667            } else {
668                // Non-starred element
669                self.compile_expression(elt)?;
670
671                if sequence_built {
672                    // Sequence already exists, append to it
673                    match collection_type {
674                        CollectionType::List => {
675                            emit!(self, Instruction::ListAppend { i: 1 });
676                        }
677                        CollectionType::Set => {
678                            emit!(self, Instruction::SetAdd { i: 1 });
679                        }
680                        CollectionType::Tuple => {
681                            emit!(self, Instruction::ListAppend { i: 1 });
682                        }
683                    }
684                } else {
685                    // Still collecting elements before first star
686                    i += 1;
687                }
688            }
689        }
690
691        // If we never built sequence (all non-starred), build it now
692        if !sequence_built {
693            match collection_type {
694                CollectionType::List => {
695                    emit!(self, Instruction::BuildList { count: i + pushed });
696                }
697                CollectionType::Set => {
698                    emit!(self, Instruction::BuildSet { count: i + pushed });
699                }
700                CollectionType::Tuple => {
701                    emit!(self, Instruction::BuildTuple { count: i + pushed });
702                }
703            }
704        } else if collection_type == CollectionType::Tuple {
705            // For tuples, convert the list to tuple
706            emit!(
707                self,
708                Instruction::CallIntrinsic1 {
709                    func: IntrinsicFunction1::ListToTuple
710                }
711            );
712        }
713
714        Ok(())
715    }
716
717    fn error(&mut self, error: CodegenErrorType) -> CodegenError {
718        self.error_ranged(error, self.current_source_range)
719    }
720
721    fn error_ranged(&mut self, error: CodegenErrorType, range: TextRange) -> CodegenError {
722        let location = self
723            .source_file
724            .to_source_code()
725            .source_location(range.start(), PositionEncoding::Utf8);
726        CodegenError {
727            error,
728            location: Some(location),
729            source_path: self.source_file.name().to_owned(),
730        }
731    }
732
733    /// Get the SymbolTable for the current scope.
734    fn current_symbol_table(&self) -> &SymbolTable {
735        self.symbol_table_stack
736            .last()
737            .expect("symbol_table_stack is empty! This is a compiler bug.")
738    }
739
740    /// Check if a name is imported in current scope or any enclosing scope.
741    fn is_name_imported(&self, name: &str) -> bool {
742        let current = self.current_symbol_table();
743        if let Some(sym) = current.symbols.get(name) {
744            if sym.flags.contains(SymbolFlags::IMPORTED) {
745                // Module/class scope imports use plain LOAD_ATTR
746                // Function-local imports use method mode (scope is Local)
747                return !matches!(
748                    current.typ,
749                    CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda
750                );
751            }
752            if sym.scope == SymbolScope::Local {
753                return false;
754            }
755        }
756        // Check enclosing scopes for module-level imports accessed as globals
757        self.symbol_table_stack.iter().rev().skip(1).any(|table| {
758            table
759                .symbols
760                .get(name)
761                .is_some_and(|sym| sym.flags.contains(SymbolFlags::IMPORTED))
762        })
763    }
764
765    /// Get the cell-relative index of a free variable.
766    /// Returns ncells + freevar_idx. Fixed up to localsplus index during finalize.
767    fn get_free_var_index(&mut self, name: &str) -> CompileResult<oparg::VarNum> {
768        let info = self.code_stack.last_mut().unwrap();
769        let idx = info
770            .metadata
771            .freevars
772            .get_index_of(name)
773            .unwrap_or_else(|| info.metadata.freevars.insert_full(name.to_owned()).0);
774        Ok((idx + info.metadata.cellvars.len()).to_u32().into())
775    }
776
777    /// Get the cell-relative index of a cell variable.
778    /// Returns cellvar_idx. Fixed up to localsplus index during finalize.
779    fn get_cell_var_index(&mut self, name: &str) -> CompileResult<oparg::VarNum> {
780        let info = self.code_stack.last_mut().unwrap();
781        let idx = info
782            .metadata
783            .cellvars
784            .get_index_of(name)
785            .unwrap_or_else(|| info.metadata.cellvars.insert_full(name.to_owned()).0);
786        Ok(idx.to_u32().into())
787    }
788
789    /// Get the index of a local variable.
790    fn get_local_var_index(&mut self, name: &str) -> CompileResult<oparg::VarNum> {
791        let info = self.code_stack.last_mut().unwrap();
792        let idx = info
793            .metadata
794            .varnames
795            .get_index_of(name)
796            .unwrap_or_else(|| info.metadata.varnames.insert_full(name.to_owned()).0);
797        Ok(idx.to_u32().into())
798    }
799
800    /// Get the index of a global name.
801    fn get_global_name_index(&mut self, name: &str) -> u32 {
802        let info = self.code_stack.last_mut().unwrap();
803        let idx = info
804            .metadata
805            .names
806            .get_index_of(name)
807            .unwrap_or_else(|| info.metadata.names.insert_full(name.to_owned()).0);
808        idx.to_u32()
809    }
810
811    /// Push the next symbol table on to the stack
812    fn push_symbol_table(&mut self) -> CompileResult<&SymbolTable> {
813        // Look up the next table contained in the scope of the current table
814        let current_table = self
815            .symbol_table_stack
816            .last_mut()
817            .expect("no current symbol table");
818
819        if current_table.next_sub_table >= current_table.sub_tables.len() {
820            let name = current_table.name.clone();
821            let typ = current_table.typ;
822            return Err(self.error(CodegenErrorType::SyntaxError(format!(
823                "no symbol table available in {} (type: {:?})",
824                name, typ
825            ))));
826        }
827
828        let idx = current_table.next_sub_table;
829        current_table.next_sub_table += 1;
830        let table = current_table.sub_tables[idx].clone();
831
832        // Push the next table onto the stack
833        self.symbol_table_stack.push(table);
834        Ok(self.current_symbol_table())
835    }
836
837    /// Push the annotation symbol table from the next sub_table's annotation_block
838    /// The annotation_block is stored in the function's scope, which is the next sub_table
839    /// Returns true if annotation_block exists, false otherwise
840    fn push_annotation_symbol_table(&mut self) -> bool {
841        let current_table = self
842            .symbol_table_stack
843            .last_mut()
844            .expect("no current symbol table");
845
846        // The annotation_block is in the next sub_table (function scope)
847        let next_idx = current_table.next_sub_table;
848        if next_idx >= current_table.sub_tables.len() {
849            return false;
850        }
851
852        let next_table = &mut current_table.sub_tables[next_idx];
853        if let Some(annotation_block) = next_table.annotation_block.take() {
854            self.symbol_table_stack.push(*annotation_block);
855            true
856        } else {
857            false
858        }
859    }
860
861    /// Push the annotation symbol table for module/class level annotations
862    /// This takes annotation_block from the current symbol table (not sub_tables)
863    fn push_current_annotation_symbol_table(&mut self) -> bool {
864        let current_table = self
865            .symbol_table_stack
866            .last_mut()
867            .expect("no current symbol table");
868
869        // For modules/classes, annotation_block is directly in the current table
870        if let Some(annotation_block) = current_table.annotation_block.take() {
871            self.symbol_table_stack.push(*annotation_block);
872            true
873        } else {
874            false
875        }
876    }
877
878    /// Pop the annotation symbol table and restore it to the function scope's annotation_block
879    fn pop_annotation_symbol_table(&mut self) {
880        let annotation_table = self.symbol_table_stack.pop().expect("compiler bug");
881        let current_table = self
882            .symbol_table_stack
883            .last_mut()
884            .expect("no current symbol table");
885
886        // Restore to the next sub_table (function scope) where it came from
887        let next_idx = current_table.next_sub_table;
888        if next_idx < current_table.sub_tables.len() {
889            current_table.sub_tables[next_idx].annotation_block = Some(Box::new(annotation_table));
890        }
891    }
892
893    /// Pop the current symbol table off the stack
894    fn pop_symbol_table(&mut self) -> SymbolTable {
895        self.symbol_table_stack.pop().expect("compiler bug")
896    }
897
898    /// Check if a super() call can be optimized
899    /// Returns Some(SuperCallType) if optimization is possible, None otherwise
900    fn can_optimize_super_call<'a>(
901        &self,
902        value: &'a ast::Expr,
903        attr: &str,
904    ) -> Option<SuperCallType<'a>> {
905        // 1. value must be a Call expression
906        let ast::Expr::Call(ast::ExprCall {
907            func, arguments, ..
908        }) = value
909        else {
910            return None;
911        };
912
913        // 2. func must be Name("super")
914        let ast::Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else {
915            return None;
916        };
917        if id.as_str() != "super" {
918            return None;
919        }
920
921        // 3. attr must not be "__class__"
922        if attr == "__class__" {
923            return None;
924        }
925
926        // 4. No keyword arguments
927        if !arguments.keywords.is_empty() {
928            return None;
929        }
930
931        // 5. Must be inside a function (not at module level or class body)
932        if !self.ctx.in_func() {
933            return None;
934        }
935
936        // 6. "super" must be GlobalImplicit (not redefined locally or at module level)
937        let table = self.current_symbol_table();
938        if let Some(symbol) = table.lookup("super")
939            && symbol.scope != SymbolScope::GlobalImplicit
940        {
941            return None;
942        }
943        // Also check top-level scope to detect module-level shadowing.
944        // Only block if super is actually *bound* at module level (not just used).
945        if let Some(top_table) = self.symbol_table_stack.first()
946            && let Some(sym) = top_table.lookup("super")
947            && sym.scope != SymbolScope::GlobalImplicit
948        {
949            return None;
950        }
951
952        // 7. Check argument pattern
953        let args = &arguments.args;
954
955        // No starred expressions allowed
956        if args.iter().any(|arg| matches!(arg, ast::Expr::Starred(_))) {
957            return None;
958        }
959
960        match args.len() {
961            2 => {
962                // 2-arg: super(class, self)
963                Some(SuperCallType::TwoArg {
964                    class_arg: &args[0],
965                    self_arg: &args[1],
966                })
967            }
968            0 => {
969                // 0-arg: super() - need __class__ cell and first parameter
970                // Enclosing function should have at least one positional argument
971                let info = self.code_stack.last()?;
972                if info.metadata.argcount == 0 && info.metadata.posonlyargcount == 0 {
973                    return None;
974                }
975
976                // Check if __class__ is available as a cell/free variable
977                // The scope must be Free (from enclosing class) or have FREE_CLASS flag
978                if let Some(symbol) = table.lookup("__class__") {
979                    if symbol.scope != SymbolScope::Free
980                        && !symbol.flags.contains(SymbolFlags::FREE_CLASS)
981                    {
982                        return None;
983                    }
984                } else {
985                    // __class__ not in symbol table, optimization not possible
986                    return None;
987                }
988
989                Some(SuperCallType::ZeroArg)
990            }
991            _ => None, // 1 or 3+ args - not optimizable
992        }
993    }
994
995    /// Load arguments for super() optimization onto the stack
996    /// Stack result: [global_super, class, self]
997    fn load_args_for_super(&mut self, super_type: &SuperCallType<'_>) -> CompileResult<()> {
998        // 1. Load global super
999        self.compile_name("super", NameUsage::Load)?;
1000
1001        match super_type {
1002            SuperCallType::TwoArg {
1003                class_arg,
1004                self_arg,
1005            } => {
1006                // 2-arg: load provided arguments
1007                self.compile_expression(class_arg)?;
1008                self.compile_expression(self_arg)?;
1009            }
1010            SuperCallType::ZeroArg => {
1011                // 0-arg: load __class__ cell and first parameter
1012                // Load __class__ from cell/free variable
1013                let scope = self.get_ref_type("__class__").map_err(|e| self.error(e))?;
1014                let idx = match scope {
1015                    SymbolScope::Cell => self.get_cell_var_index("__class__")?,
1016                    SymbolScope::Free => self.get_free_var_index("__class__")?,
1017                    _ => {
1018                        return Err(self.error(CodegenErrorType::SyntaxError(
1019                            "super(): __class__ cell not found".to_owned(),
1020                        )));
1021                    }
1022                };
1023                emit!(self, Instruction::LoadDeref { i: idx });
1024
1025                // Load first parameter (typically 'self').
1026                // Safety: can_optimize_super_call() ensures argcount > 0, and
1027                // parameters are always added to varnames first (see symboltable.rs).
1028                let first_param = {
1029                    let info = self.code_stack.last().unwrap();
1030                    info.metadata.varnames.first().cloned()
1031                };
1032                let first_param = first_param.ok_or_else(|| {
1033                    self.error(CodegenErrorType::SyntaxError(
1034                        "super(): no arguments and no first parameter".to_owned(),
1035                    ))
1036                })?;
1037                self.compile_name(&first_param, NameUsage::Load)?;
1038            }
1039        }
1040        Ok(())
1041    }
1042
1043    /// Check if this is an inlined comprehension context (PEP 709).
1044    /// PEP 709: Inline comprehensions in function-like scopes.
1045    /// TODO: Module/class scope inlining needs more work (Cell name resolution edge cases).
1046    /// Generator expressions are never inlined.
1047    fn is_inlined_comprehension_context(&self, comprehension_type: ComprehensionType) -> bool {
1048        if comprehension_type == ComprehensionType::Generator {
1049            return false;
1050        }
1051        if !self.ctx.in_func() {
1052            return false;
1053        }
1054        self.symbol_table_stack
1055            .last()
1056            .and_then(|t| t.sub_tables.get(t.next_sub_table))
1057            .is_some_and(|st| st.comp_inlined)
1058    }
1059
1060    /// Enter a new scope
1061    // = compiler_enter_scope
1062    fn enter_scope(
1063        &mut self,
1064        name: &str,
1065        scope_type: CompilerScope,
1066        key: usize, // In RustPython, we use the index in symbol_table_stack as key
1067        lineno: u32,
1068    ) -> CompileResult<()> {
1069        // Allocate a new compiler unit
1070
1071        // In Rust, we'll create the structure directly
1072        let source_path = self.source_file.name().to_owned();
1073
1074        // Lookup symbol table entry using key (_PySymtable_Lookup)
1075        let ste = match self.symbol_table_stack.get(key) {
1076            Some(v) => v,
1077            None => {
1078                return Err(self.error(CodegenErrorType::SyntaxError(
1079                    "unknown symbol table entry".to_owned(),
1080                )));
1081            }
1082        };
1083
1084        // Use varnames from symbol table (already collected in definition order)
1085        let varname_cache: IndexSet<String> = ste.varnames.iter().cloned().collect();
1086
1087        // Build cellvars using dictbytype (CELL scope or COMP_CELL flag, sorted)
1088        let mut cellvar_cache = IndexSet::default();
1089        // CPython ordering: parameter cells first (in parameter order),
1090        // then non-parameter cells (alphabetically sorted)
1091        let cell_symbols: Vec<_> = ste
1092            .symbols
1093            .iter()
1094            .filter(|(_, s)| {
1095                s.scope == SymbolScope::Cell || s.flags.contains(SymbolFlags::COMP_CELL)
1096            })
1097            .map(|(name, sym)| (name.clone(), sym.flags))
1098            .collect();
1099        let mut param_cells = Vec::new();
1100        let mut nonparam_cells = Vec::new();
1101        for (name, flags) in cell_symbols {
1102            if flags.contains(SymbolFlags::PARAMETER) {
1103                param_cells.push(name);
1104            } else {
1105                nonparam_cells.push(name);
1106            }
1107        }
1108        // param_cells are already in parameter order (from varname_cache insertion order)
1109        param_cells.sort_by_key(|n| varname_cache.get_index_of(n.as_str()).unwrap_or(usize::MAX));
1110        nonparam_cells.sort();
1111        for name in param_cells {
1112            cellvar_cache.insert(name);
1113        }
1114        for name in nonparam_cells {
1115            cellvar_cache.insert(name);
1116        }
1117
1118        // Handle implicit __class__ cell if needed
1119        if ste.needs_class_closure {
1120            // Cook up an implicit __class__ cell
1121            debug_assert_eq!(scope_type, CompilerScope::Class);
1122            cellvar_cache.insert("__class__".to_string());
1123        }
1124
1125        // Handle implicit __classdict__ cell if needed
1126        if ste.needs_classdict {
1127            // Cook up an implicit __classdict__ cell
1128            debug_assert_eq!(scope_type, CompilerScope::Class);
1129            cellvar_cache.insert("__classdict__".to_string());
1130        }
1131
1132        // Handle implicit __conditional_annotations__ cell if needed
1133        if ste.has_conditional_annotations
1134            && matches!(scope_type, CompilerScope::Class | CompilerScope::Module)
1135        {
1136            cellvar_cache.insert("__conditional_annotations__".to_string());
1137        }
1138
1139        // Build freevars using dictbytype (FREE scope, offset by cellvars size)
1140        let mut freevar_cache = IndexSet::default();
1141        let mut free_names: Vec<_> = ste
1142            .symbols
1143            .iter()
1144            .filter(|(_, s)| {
1145                s.scope == SymbolScope::Free || s.flags.contains(SymbolFlags::FREE_CLASS)
1146            })
1147            .map(|(name, _)| name.clone())
1148            .collect();
1149        free_names.sort();
1150        for name in free_names {
1151            freevar_cache.insert(name);
1152        }
1153
1154        // Initialize u_metadata fields
1155        let (flags, posonlyarg_count, arg_count, kwonlyarg_count) = match scope_type {
1156            CompilerScope::Module => (bytecode::CodeFlags::empty(), 0, 0, 0),
1157            CompilerScope::Class => (bytecode::CodeFlags::empty(), 0, 0, 0),
1158            CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda => (
1159                bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
1160                0, // Will be set later in enter_function
1161                0, // Will be set later in enter_function
1162                0, // Will be set later in enter_function
1163            ),
1164            CompilerScope::Comprehension => (
1165                bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
1166                0,
1167                1, // comprehensions take one argument (.0)
1168                0,
1169            ),
1170            CompilerScope::TypeParams => (
1171                bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
1172                0,
1173                0,
1174                0,
1175            ),
1176            CompilerScope::Annotation => (
1177                bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
1178                1, // format is positional-only
1179                1, // annotation scope takes one argument (format)
1180                0,
1181            ),
1182        };
1183
1184        // Set CO_NESTED for scopes defined inside another function/class/etc.
1185        // (i.e., not at module level)
1186        let flags = if self.code_stack.len() > 1 {
1187            flags | bytecode::CodeFlags::NESTED
1188        } else {
1189            flags
1190        };
1191
1192        // Get private name from parent scope
1193        let private = if !self.code_stack.is_empty() {
1194            self.code_stack.last().unwrap().private.clone()
1195        } else {
1196            None
1197        };
1198
1199        // Create the new compilation unit
1200        let code_info = ir::CodeInfo {
1201            flags,
1202            source_path: source_path.clone(),
1203            private,
1204            blocks: vec![ir::Block::default()],
1205            current_block: BlockIdx::new(0),
1206            metadata: ir::CodeUnitMetadata {
1207                name: name.to_owned(),
1208                qualname: None, // Will be set below
1209                consts: IndexSet::default(),
1210                names: IndexSet::default(),
1211                varnames: varname_cache,
1212                cellvars: cellvar_cache,
1213                freevars: freevar_cache,
1214                fast_hidden: IndexMap::default(),
1215                argcount: arg_count,
1216                posonlyargcount: posonlyarg_count,
1217                kwonlyargcount: kwonlyarg_count,
1218                firstlineno: OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN),
1219            },
1220            static_attributes: if scope_type == CompilerScope::Class {
1221                Some(IndexSet::default())
1222            } else {
1223                None
1224            },
1225            in_inlined_comp: false,
1226            fblock: Vec::with_capacity(MAXBLOCKS),
1227            symbol_table_index: key,
1228            in_conditional_block: 0,
1229            next_conditional_annotation_index: 0,
1230        };
1231
1232        // Push the old compiler unit on the stack (like PyCapsule)
1233        // This happens before setting qualname
1234        self.code_stack.push(code_info);
1235
1236        // Set qualname after pushing (uses compiler_set_qualname logic)
1237        if scope_type != CompilerScope::Module {
1238            self.set_qualname();
1239        }
1240
1241        // Emit COPY_FREE_VARS first, then MAKE_CELL (CPython order)
1242        {
1243            let nfrees = self.code_stack.last().unwrap().metadata.freevars.len();
1244            if nfrees > 0 {
1245                emit!(
1246                    self,
1247                    Instruction::CopyFreeVars {
1248                        n: u32::try_from(nfrees).expect("too many freevars"),
1249                    }
1250                );
1251            }
1252        }
1253        {
1254            let ncells = self.code_stack.last().unwrap().metadata.cellvars.len();
1255            for i in 0..ncells {
1256                let i_varnum: oparg::VarNum = u32::try_from(i).expect("too many cellvars").into();
1257                emit!(self, Instruction::MakeCell { i: i_varnum });
1258            }
1259        }
1260
1261        // Emit RESUME (handles async preamble and module lineno 0)
1262        // CPython: LOCATION(lineno, lineno, 0, 0), then loc.lineno = 0 for module
1263        self.emit_resume_for_scope(scope_type, lineno);
1264
1265        Ok(())
1266    }
1267
1268    /// Emit RESUME instruction with proper handling for async preamble and module lineno.
1269    /// codegen_enter_scope equivalent for RESUME emission.
1270    fn emit_resume_for_scope(&mut self, scope_type: CompilerScope, lineno: u32) {
1271        // For generators and async functions, emit RETURN_GENERATOR + POP_TOP before RESUME
1272        let is_gen =
1273            scope_type == CompilerScope::AsyncFunction || self.current_symbol_table().is_generator;
1274        if is_gen {
1275            emit!(self, Instruction::ReturnGenerator);
1276            emit!(self, Instruction::PopTop);
1277        }
1278
1279        // CPython: LOCATION(lineno, lineno, 0, 0)
1280        // Module scope: loc.lineno = 0 (before the first line)
1281        let lineno_override = if scope_type == CompilerScope::Module {
1282            Some(0)
1283        } else {
1284            None
1285        };
1286
1287        // Use lineno for location (col = 0 as in CPython)
1288        let location = SourceLocation {
1289            line: OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN),
1290            character_offset: OneIndexed::MIN, // col = 0
1291        };
1292        let end_location = location; // end_lineno = lineno, end_col = 0
1293        let except_handler = None;
1294
1295        self.current_block().instructions.push(ir::InstructionInfo {
1296            instr: Instruction::Resume {
1297                context: OpArgMarker::marker(),
1298            }
1299            .into(),
1300            arg: OpArg::new(oparg::ResumeLocation::AtFuncStart.into()),
1301            target: BlockIdx::NULL,
1302            location,
1303            end_location,
1304            except_handler,
1305            lineno_override,
1306            cache_entries: 0,
1307        });
1308    }
1309
1310    fn push_output(
1311        &mut self,
1312        flags: bytecode::CodeFlags,
1313        posonlyarg_count: u32,
1314        arg_count: u32,
1315        kwonlyarg_count: u32,
1316        obj_name: String,
1317    ) -> CompileResult<()> {
1318        // First push the symbol table
1319        let table = self.push_symbol_table()?;
1320        let scope_type = table.typ;
1321
1322        // The key is the current position in the symbol table stack
1323        let key = self.symbol_table_stack.len() - 1;
1324
1325        // Get the line number
1326        let lineno = self.get_source_line_number().get();
1327
1328        // Call enter_scope which does most of the work
1329        self.enter_scope(&obj_name, scope_type, key, lineno.to_u32())?;
1330
1331        // Override the values that push_output sets explicitly
1332        // enter_scope sets default values based on scope_type, but push_output
1333        // allows callers to specify exact values
1334        if let Some(info) = self.code_stack.last_mut() {
1335            // Preserve NESTED flag set by enter_scope
1336            info.flags = flags | (info.flags & bytecode::CodeFlags::NESTED);
1337            info.metadata.argcount = arg_count;
1338            info.metadata.posonlyargcount = posonlyarg_count;
1339            info.metadata.kwonlyargcount = kwonlyarg_count;
1340        }
1341        Ok(())
1342    }
1343
1344    // compiler_exit_scope
1345    fn exit_scope(&mut self) -> CodeObject {
1346        let _table = self.pop_symbol_table();
1347
1348        // Various scopes can have sub_tables:
1349        // - ast::TypeParams scope can have sub_tables (the function body's symbol table)
1350        // - Module scope can have sub_tables (for TypeAlias scopes, nested functions, classes)
1351        // - Function scope can have sub_tables (for nested functions, classes)
1352        // - Class scope can have sub_tables (for nested classes, methods)
1353
1354        let pop = self.code_stack.pop();
1355        let stack_top = compiler_unwrap_option(self, pop);
1356        // No parent scope stack to maintain
1357        unwrap_internal(self, stack_top.finalize_code(&self.opts))
1358    }
1359
1360    /// Exit annotation scope - similar to exit_scope but restores annotation_block to parent
1361    fn exit_annotation_scope(&mut self, saved_ctx: CompileContext) -> CodeObject {
1362        self.pop_annotation_symbol_table();
1363        self.ctx = saved_ctx;
1364
1365        let pop = self.code_stack.pop();
1366        let stack_top = compiler_unwrap_option(self, pop);
1367        unwrap_internal(self, stack_top.finalize_code(&self.opts))
1368    }
1369
1370    /// Enter annotation scope using the symbol table's annotation_block.
1371    /// Returns None if no annotation_block exists.
1372    /// On success, returns the saved CompileContext to pass to exit_annotation_scope.
1373    fn enter_annotation_scope(
1374        &mut self,
1375        _func_name: &str,
1376    ) -> CompileResult<Option<CompileContext>> {
1377        if !self.push_annotation_symbol_table() {
1378            return Ok(None);
1379        }
1380
1381        // Annotation scopes are never async (even inside async functions)
1382        let saved_ctx = self.ctx;
1383        self.ctx = CompileContext {
1384            loop_data: None,
1385            in_class: saved_ctx.in_class,
1386            func: FunctionContext::Function,
1387            in_async_scope: false,
1388        };
1389
1390        let key = self.symbol_table_stack.len() - 1;
1391        let lineno = self.get_source_line_number().get();
1392        self.enter_scope(
1393            "__annotate__",
1394            CompilerScope::Annotation,
1395            key,
1396            lineno.to_u32(),
1397        )?;
1398
1399        // Override arg_count since enter_scope sets it to 1 but we need the varnames
1400        // setup to be correct too
1401        self.current_code_info()
1402            .metadata
1403            .varnames
1404            .insert("format".to_owned());
1405
1406        // Emit format validation: if format > VALUE_WITH_FAKE_GLOBALS: raise NotImplementedError
1407        // VALUE_WITH_FAKE_GLOBALS = 2 (from annotationlib.Format)
1408        self.emit_format_validation()?;
1409
1410        Ok(Some(saved_ctx))
1411    }
1412
1413    /// Emit format parameter validation for annotation scope
1414    /// if format > VALUE_WITH_FAKE_GLOBALS (2): raise NotImplementedError
1415    fn emit_format_validation(&mut self) -> CompileResult<()> {
1416        // Load format parameter (first local variable, index 0)
1417        emit!(
1418            self,
1419            Instruction::LoadFast {
1420                var_num: oparg::VarNum::from_u32(0)
1421            }
1422        );
1423
1424        // Load VALUE_WITH_FAKE_GLOBALS constant (2)
1425        self.emit_load_const(ConstantData::Integer { value: 2.into() });
1426
1427        // Compare: format > 2
1428        emit!(
1429            self,
1430            Instruction::CompareOp {
1431                opname: ComparisonOperator::Greater
1432            }
1433        );
1434
1435        // Jump to body if format <= 2 (comparison is false)
1436        let body_block = self.new_block();
1437        emit!(self, Instruction::PopJumpIfFalse { delta: body_block });
1438
1439        // Raise NotImplementedError
1440        emit!(
1441            self,
1442            Instruction::LoadCommonConstant {
1443                idx: bytecode::CommonConstant::NotImplementedError
1444            }
1445        );
1446        emit!(
1447            self,
1448            Instruction::RaiseVarargs {
1449                argc: bytecode::RaiseKind::Raise
1450            }
1451        );
1452
1453        // Body label - continue with annotation evaluation
1454        self.switch_to_block(body_block);
1455
1456        Ok(())
1457    }
1458
1459    /// Push a new fblock
1460    // = compiler_push_fblock
1461    fn push_fblock(
1462        &mut self,
1463        fb_type: FBlockType,
1464        fb_block: BlockIdx,
1465        fb_exit: BlockIdx,
1466    ) -> CompileResult<()> {
1467        self.push_fblock_full(fb_type, fb_block, fb_exit, FBlockDatum::None)
1468    }
1469
1470    /// Push an fblock with all parameters including fb_datum
1471    fn push_fblock_full(
1472        &mut self,
1473        fb_type: FBlockType,
1474        fb_block: BlockIdx,
1475        fb_exit: BlockIdx,
1476        fb_datum: FBlockDatum,
1477    ) -> CompileResult<()> {
1478        let code = self.current_code_info();
1479        if code.fblock.len() >= MAXBLOCKS {
1480            return Err(self.error(CodegenErrorType::SyntaxError(
1481                "too many statically nested blocks".to_owned(),
1482            )));
1483        }
1484        code.fblock.push(FBlockInfo {
1485            fb_type,
1486            fb_block,
1487            fb_exit,
1488            fb_datum,
1489        });
1490        Ok(())
1491    }
1492
1493    /// Pop an fblock
1494    // = compiler_pop_fblock
1495    fn pop_fblock(&mut self, _expected_type: FBlockType) -> FBlockInfo {
1496        let code = self.current_code_info();
1497        // TODO: Add assertion to check expected type matches
1498        // assert!(matches!(fblock.fb_type, expected_type));
1499        code.fblock.pop().expect("fblock stack underflow")
1500    }
1501
1502    /// Unwind a single fblock, emitting cleanup code
1503    /// preserve_tos: if true, preserve the top of stack (e.g., return value)
1504    fn unwind_fblock(&mut self, info: &FBlockInfo, preserve_tos: bool) -> CompileResult<()> {
1505        match info.fb_type {
1506            FBlockType::WhileLoop
1507            | FBlockType::ExceptionHandler
1508            | FBlockType::ExceptionGroupHandler
1509            | FBlockType::AsyncComprehensionGenerator
1510            | FBlockType::StopIteration => {
1511                // No cleanup needed
1512            }
1513
1514            FBlockType::ForLoop => {
1515                // Pop the iterator
1516                if preserve_tos {
1517                    emit!(self, Instruction::Swap { i: 2 });
1518                }
1519                emit!(self, Instruction::PopIter);
1520            }
1521
1522            FBlockType::TryExcept => {
1523                emit!(self, PseudoInstruction::PopBlock);
1524            }
1525
1526            FBlockType::FinallyTry => {
1527                // FinallyTry is now handled specially in unwind_fblock_stack
1528                // to avoid infinite recursion when the finally body contains return/break/continue.
1529                // This branch should not be reached.
1530                unreachable!("FinallyTry should be handled by unwind_fblock_stack");
1531            }
1532
1533            FBlockType::FinallyEnd => {
1534                // codegen_unwind_fblock(FINALLY_END)
1535                if preserve_tos {
1536                    emit!(self, Instruction::Swap { i: 2 });
1537                }
1538                emit!(self, Instruction::PopTop); // exc_value
1539                if preserve_tos {
1540                    emit!(self, Instruction::Swap { i: 2 });
1541                }
1542                emit!(self, PseudoInstruction::PopBlock);
1543                emit!(self, Instruction::PopExcept);
1544            }
1545
1546            FBlockType::With | FBlockType::AsyncWith => {
1547                // Stack: [..., exit_func, self_exit, return_value (if preserve_tos)]
1548                emit!(self, PseudoInstruction::PopBlock);
1549
1550                if preserve_tos {
1551                    // Rotate return value below the exit pair
1552                    // [exit_func, self_exit, value] → [value, exit_func, self_exit]
1553                    emit!(self, Instruction::Swap { i: 3 }); // [value, self_exit, exit_func]
1554                    emit!(self, Instruction::Swap { i: 2 }); // [value, exit_func, self_exit]
1555                }
1556
1557                // Call exit_func(self_exit, None, None, None)
1558                self.emit_load_const(ConstantData::None);
1559                self.emit_load_const(ConstantData::None);
1560                self.emit_load_const(ConstantData::None);
1561                emit!(self, Instruction::Call { argc: 3 });
1562
1563                // For async with, await the result
1564                if matches!(info.fb_type, FBlockType::AsyncWith) {
1565                    emit!(self, Instruction::GetAwaitable { r#where: 2 });
1566                    self.emit_load_const(ConstantData::None);
1567                    let _ = self.compile_yield_from_sequence(true)?;
1568                }
1569
1570                // Pop the __exit__ result
1571                emit!(self, Instruction::PopTop);
1572            }
1573
1574            FBlockType::HandlerCleanup => {
1575                // codegen_unwind_fblock(HANDLER_CLEANUP)
1576                if let FBlockDatum::ExceptionName(_) = info.fb_datum {
1577                    // Named handler: PopBlock for inner SETUP_CLEANUP
1578                    emit!(self, PseudoInstruction::PopBlock);
1579                }
1580                if preserve_tos {
1581                    emit!(self, Instruction::Swap { i: 2 });
1582                }
1583                // PopBlock for outer SETUP_CLEANUP (ExceptionHandler)
1584                emit!(self, PseudoInstruction::PopBlock);
1585                emit!(self, Instruction::PopExcept);
1586
1587                // If there's an exception name, clean it up
1588                if let FBlockDatum::ExceptionName(ref name) = info.fb_datum {
1589                    self.emit_load_const(ConstantData::None);
1590                    self.store_name(name)?;
1591                    self.compile_name(name, NameUsage::Delete)?;
1592                }
1593            }
1594
1595            FBlockType::PopValue => {
1596                if preserve_tos {
1597                    emit!(self, Instruction::Swap { i: 2 });
1598                }
1599                emit!(self, Instruction::PopTop);
1600            }
1601        }
1602        Ok(())
1603    }
1604
1605    /// Unwind the fblock stack, emitting cleanup code for each block
1606    /// preserve_tos: if true, preserve the top of stack (e.g., return value)
1607    /// stop_at_loop: if true, stop when encountering a loop (for break/continue)
1608    fn unwind_fblock_stack(&mut self, preserve_tos: bool, stop_at_loop: bool) -> CompileResult<()> {
1609        // Collect the info we need, with indices for FinallyTry blocks
1610        #[derive(Clone)]
1611        enum UnwindInfo {
1612            Normal(FBlockInfo),
1613            FinallyTry {
1614                body: Vec<ruff_python_ast::Stmt>,
1615                fblock_idx: usize,
1616            },
1617        }
1618        let mut unwind_infos = Vec::new();
1619
1620        {
1621            let code = self.current_code_info();
1622            for i in (0..code.fblock.len()).rev() {
1623                // Check for exception group handler (forbidden)
1624                if matches!(code.fblock[i].fb_type, FBlockType::ExceptionGroupHandler) {
1625                    return Err(self.error(CodegenErrorType::BreakContinueReturnInExceptStar));
1626                }
1627
1628                // Stop at loop if requested
1629                if stop_at_loop
1630                    && matches!(
1631                        code.fblock[i].fb_type,
1632                        FBlockType::WhileLoop | FBlockType::ForLoop
1633                    )
1634                {
1635                    break;
1636                }
1637
1638                if matches!(code.fblock[i].fb_type, FBlockType::FinallyTry) {
1639                    if let FBlockDatum::FinallyBody(ref body) = code.fblock[i].fb_datum {
1640                        unwind_infos.push(UnwindInfo::FinallyTry {
1641                            body: body.clone(),
1642                            fblock_idx: i,
1643                        });
1644                    }
1645                } else {
1646                    unwind_infos.push(UnwindInfo::Normal(code.fblock[i].clone()));
1647                }
1648            }
1649        }
1650
1651        // Process each fblock
1652        for info in unwind_infos {
1653            match info {
1654                UnwindInfo::Normal(fblock_info) => {
1655                    self.unwind_fblock(&fblock_info, preserve_tos)?;
1656                }
1657                UnwindInfo::FinallyTry { body, fblock_idx } => {
1658                    // codegen_unwind_fblock(FINALLY_TRY)
1659                    emit!(self, PseudoInstruction::PopBlock);
1660
1661                    // Temporarily remove the FinallyTry fblock so nested return/break/continue
1662                    // in the finally body won't see it again
1663                    let code = self.current_code_info();
1664                    let saved_fblock = code.fblock.remove(fblock_idx);
1665
1666                    // Push PopValue fblock if preserving tos
1667                    if preserve_tos {
1668                        self.push_fblock(
1669                            FBlockType::PopValue,
1670                            saved_fblock.fb_block,
1671                            saved_fblock.fb_block,
1672                        )?;
1673                    }
1674
1675                    self.compile_statements(&body)?;
1676
1677                    if preserve_tos {
1678                        self.pop_fblock(FBlockType::PopValue);
1679                    }
1680
1681                    // Restore the fblock
1682                    let code = self.current_code_info();
1683                    code.fblock.insert(fblock_idx, saved_fblock);
1684                }
1685            }
1686        }
1687
1688        Ok(())
1689    }
1690
1691    // could take impl Into<Cow<str>>, but everything is borrowed from ast structs; we never
1692    // actually have a `String` to pass
1693    fn name(&mut self, name: &str) -> bytecode::NameIdx {
1694        self._name_inner(name, |i| &mut i.metadata.names)
1695    }
1696
1697    fn varname(&mut self, name: &str) -> CompileResult<oparg::VarNum> {
1698        // Note: __debug__ checks are now handled in symboltable phase
1699        Ok(oparg::VarNum::from_u32(
1700            self._name_inner(name, |i| &mut i.metadata.varnames),
1701        ))
1702    }
1703
1704    fn _name_inner(
1705        &mut self,
1706        name: &str,
1707        cache: impl FnOnce(&mut ir::CodeInfo) -> &mut IndexSet<String>,
1708    ) -> u32 {
1709        let name = self.mangle(name);
1710        let cache = cache(self.current_code_info());
1711        cache
1712            .get_index_of(name.as_ref())
1713            .unwrap_or_else(|| cache.insert_full(name.into_owned()).0)
1714            .to_u32()
1715    }
1716
1717    /// Set the qualified name for the current code object
1718    // = compiler_set_qualname
1719    fn set_qualname(&mut self) -> String {
1720        let qualname = self.make_qualname();
1721        self.current_code_info().metadata.qualname = Some(qualname.clone());
1722        qualname
1723    }
1724    fn make_qualname(&mut self) -> String {
1725        let stack_size = self.code_stack.len();
1726        assert!(stack_size >= 1);
1727
1728        let current_obj_name = self.current_code_info().metadata.name.clone();
1729
1730        // If we're at the module level (stack_size == 1), qualname is just the name
1731        if stack_size <= 1 {
1732            return current_obj_name;
1733        }
1734
1735        // Check parent scope
1736        let mut parent_idx = stack_size - 2;
1737        let mut parent = &self.code_stack[parent_idx];
1738
1739        // If parent is ast::TypeParams scope, look at grandparent
1740        // Check if parent is a type params scope by name pattern
1741        if parent.metadata.name.starts_with("<generic parameters of ") {
1742            if stack_size == 2 {
1743                // If we're immediately within the module, qualname is just the name
1744                return current_obj_name;
1745            }
1746            // Use grandparent
1747            parent_idx = stack_size - 3;
1748            parent = &self.code_stack[parent_idx];
1749        }
1750
1751        // Check if this is a global class/function
1752        let mut force_global = false;
1753        if stack_size > self.symbol_table_stack.len() {
1754            // We might be in a situation where symbol table isn't pushed yet
1755            // In this case, check the parent symbol table
1756            if let Some(parent_table) = self.symbol_table_stack.last()
1757                && let Some(symbol) = parent_table.lookup(&current_obj_name)
1758                && symbol.scope == SymbolScope::GlobalExplicit
1759            {
1760                force_global = true;
1761            }
1762        } else if let Some(_current_table) = self.symbol_table_stack.last() {
1763            // Mangle the name if necessary (for private names in classes)
1764            let mangled_name = self.mangle(&current_obj_name);
1765
1766            // Look up in parent symbol table to check scope
1767            if self.symbol_table_stack.len() >= 2 {
1768                let parent_table = &self.symbol_table_stack[self.symbol_table_stack.len() - 2];
1769                if let Some(symbol) = parent_table.lookup(&mangled_name)
1770                    && symbol.scope == SymbolScope::GlobalExplicit
1771                {
1772                    force_global = true;
1773                }
1774            }
1775        }
1776
1777        // Build the qualified name
1778        if force_global {
1779            // For global symbols, qualname is just the name
1780            current_obj_name
1781        } else {
1782            // Check parent scope type
1783            let parent_obj_name = &parent.metadata.name;
1784
1785            // Determine if parent is a function-like scope
1786            let is_function_parent = parent.flags.contains(bytecode::CodeFlags::OPTIMIZED)
1787                && !parent_obj_name.starts_with("<") // Not a special scope like <lambda>, <listcomp>, etc.
1788                && parent_obj_name != "<module>"; // Not the module scope
1789
1790            if is_function_parent {
1791                // For functions, append .<locals> to parent qualname
1792                // Use parent's qualname if available, otherwise use parent_obj_name
1793                let parent_qualname = parent.metadata.qualname.as_ref().unwrap_or(parent_obj_name);
1794                format!("{parent_qualname}.<locals>.{current_obj_name}")
1795            } else {
1796                // For classes and other scopes, use parent's qualname directly
1797                // Use parent's qualname if available, otherwise use parent_obj_name
1798                let parent_qualname = parent.metadata.qualname.as_ref().unwrap_or(parent_obj_name);
1799                if parent_qualname == "<module>" {
1800                    // Module level, just use the name
1801                    current_obj_name
1802                } else {
1803                    // Concatenate parent qualname with current name
1804                    format!("{parent_qualname}.{current_obj_name}")
1805                }
1806            }
1807        }
1808    }
1809
1810    fn compile_program(
1811        &mut self,
1812        body: &ast::ModModule,
1813        symbol_table: SymbolTable,
1814    ) -> CompileResult<()> {
1815        let size_before = self.code_stack.len();
1816        // Set future_annotations from symbol table (detected during symbol table scan)
1817        self.future_annotations = symbol_table.future_annotations;
1818
1819        // Module-level __conditional_annotations__ cell
1820        let has_module_cond_ann = symbol_table.has_conditional_annotations;
1821        if has_module_cond_ann {
1822            self.current_code_info()
1823                .metadata
1824                .cellvars
1825                .insert("__conditional_annotations__".to_string());
1826        }
1827
1828        self.symbol_table_stack.push(symbol_table);
1829
1830        // Emit MAKE_CELL for module-level cells (before RESUME)
1831        if has_module_cond_ann {
1832            let ncells = self.code_stack.last().unwrap().metadata.cellvars.len();
1833            for i in 0..ncells {
1834                let i_varnum: oparg::VarNum = u32::try_from(i).expect("too many cellvars").into();
1835                emit!(self, Instruction::MakeCell { i: i_varnum });
1836            }
1837        }
1838
1839        self.emit_resume_for_scope(CompilerScope::Module, 1);
1840
1841        let (doc, statements) = split_doc(&body.body, &self.opts);
1842        if let Some(value) = doc {
1843            self.emit_load_const(ConstantData::Str {
1844                value: value.into(),
1845            });
1846            let doc = self.name("__doc__");
1847            emit!(self, Instruction::StoreName { namei: doc })
1848        }
1849
1850        // Handle annotations based on future_annotations flag
1851        if Self::find_ann(statements) {
1852            if self.future_annotations {
1853                // PEP 563: Initialize __annotations__ dict
1854                emit!(self, Instruction::SetupAnnotations);
1855            } else {
1856                // PEP 649: Generate __annotate__ function FIRST (before statements)
1857                self.compile_module_annotate(statements)?;
1858
1859                // PEP 649: Initialize __conditional_annotations__ set after __annotate__
1860                if self.current_symbol_table().has_conditional_annotations {
1861                    emit!(self, Instruction::BuildSet { count: 0 });
1862                    self.store_name("__conditional_annotations__")?;
1863                }
1864            }
1865        }
1866
1867        // Compile all statements
1868        self.compile_statements(statements)?;
1869
1870        assert_eq!(self.code_stack.len(), size_before);
1871
1872        // Emit None at end:
1873        self.emit_return_const(ConstantData::None);
1874        Ok(())
1875    }
1876
1877    fn compile_program_single(
1878        &mut self,
1879        body: &[ast::Stmt],
1880        symbol_table: SymbolTable,
1881    ) -> CompileResult<()> {
1882        self.interactive = true;
1883        // Set future_annotations from symbol table (detected during symbol table scan)
1884        self.future_annotations = symbol_table.future_annotations;
1885        self.symbol_table_stack.push(symbol_table);
1886
1887        self.emit_resume_for_scope(CompilerScope::Module, 1);
1888
1889        // Handle annotations based on future_annotations flag
1890        if Self::find_ann(body) {
1891            if self.future_annotations {
1892                // PEP 563: Initialize __annotations__ dict
1893                emit!(self, Instruction::SetupAnnotations);
1894            } else {
1895                // PEP 649: Generate __annotate__ function FIRST (before statements)
1896                self.compile_module_annotate(body)?;
1897
1898                // PEP 649: Initialize __conditional_annotations__ set after __annotate__
1899                if self.current_symbol_table().has_conditional_annotations {
1900                    emit!(self, Instruction::BuildSet { count: 0 });
1901                    self.store_name("__conditional_annotations__")?;
1902                }
1903            }
1904        }
1905
1906        if let Some((last, body)) = body.split_last() {
1907            for statement in body {
1908                if let ast::Stmt::Expr(ast::StmtExpr { value, .. }) = &statement {
1909                    self.compile_expression(value)?;
1910                    emit!(
1911                        self,
1912                        Instruction::CallIntrinsic1 {
1913                            func: bytecode::IntrinsicFunction1::Print
1914                        }
1915                    );
1916
1917                    emit!(self, Instruction::PopTop);
1918                } else {
1919                    self.compile_statement(statement)?;
1920                }
1921            }
1922
1923            if let ast::Stmt::Expr(ast::StmtExpr { value, .. }) = &last {
1924                self.compile_expression(value)?;
1925                emit!(self, Instruction::Copy { i: 1 });
1926                emit!(
1927                    self,
1928                    Instruction::CallIntrinsic1 {
1929                        func: bytecode::IntrinsicFunction1::Print
1930                    }
1931                );
1932
1933                emit!(self, Instruction::PopTop);
1934            } else {
1935                self.compile_statement(last)?;
1936                self.emit_load_const(ConstantData::None);
1937            }
1938        } else {
1939            self.emit_load_const(ConstantData::None);
1940        };
1941
1942        self.emit_return_value();
1943        Ok(())
1944    }
1945
1946    fn compile_block_expr(
1947        &mut self,
1948        body: &[ast::Stmt],
1949        symbol_table: SymbolTable,
1950    ) -> CompileResult<()> {
1951        self.symbol_table_stack.push(symbol_table);
1952        self.emit_resume_for_scope(CompilerScope::Module, 1);
1953
1954        self.compile_statements(body)?;
1955
1956        if let Some(last_statement) = body.last() {
1957            match last_statement {
1958                ast::Stmt::Expr(_) => {
1959                    self.current_block().instructions.pop(); // pop Instruction::PopTop
1960                }
1961                ast::Stmt::FunctionDef(_) | ast::Stmt::ClassDef(_) => {
1962                    let pop_instructions = self.current_block().instructions.pop();
1963                    let store_inst = compiler_unwrap_option(self, pop_instructions); // pop Instruction::Store
1964                    emit!(self, Instruction::Copy { i: 1 });
1965                    self.current_block().instructions.push(store_inst);
1966                }
1967                _ => self.emit_load_const(ConstantData::None),
1968            }
1969        }
1970        self.emit_return_value();
1971
1972        Ok(())
1973    }
1974
1975    // Compile statement in eval mode:
1976    fn compile_eval(
1977        &mut self,
1978        expression: &ast::ModExpression,
1979        symbol_table: SymbolTable,
1980    ) -> CompileResult<()> {
1981        self.symbol_table_stack.push(symbol_table);
1982        self.emit_resume_for_scope(CompilerScope::Module, 1);
1983
1984        self.compile_expression(&expression.body)?;
1985        self.emit_return_value();
1986        Ok(())
1987    }
1988
1989    fn compile_statements(&mut self, statements: &[ast::Stmt]) -> CompileResult<()> {
1990        for statement in statements {
1991            self.compile_statement(statement)?
1992        }
1993        Ok(())
1994    }
1995
1996    fn load_name(&mut self, name: &str) -> CompileResult<()> {
1997        self.compile_name(name, NameUsage::Load)
1998    }
1999
2000    fn store_name(&mut self, name: &str) -> CompileResult<()> {
2001        self.compile_name(name, NameUsage::Store)
2002    }
2003
2004    fn mangle<'a>(&self, name: &'a str) -> Cow<'a, str> {
2005        // Use private from current code unit for name mangling
2006        let private = self
2007            .code_stack
2008            .last()
2009            .and_then(|info| info.private.as_deref());
2010        let mangled_names = self.current_symbol_table().mangled_names.as_ref();
2011        symboltable::maybe_mangle_name(private, mangled_names, name)
2012    }
2013
2014    fn module_name_declared_global_in_nested_scope(table: &SymbolTable, name: &str) -> bool {
2015        table.sub_tables.iter().any(|subtable| {
2016            (!subtable.comp_inlined
2017                && subtable
2018                    .lookup(name)
2019                    .is_some_and(|symbol| symbol.scope == SymbolScope::GlobalExplicit))
2020                || Self::module_name_declared_global_in_nested_scope(subtable, name)
2021        })
2022    }
2023
2024    // = compiler_nameop
2025    fn compile_name(&mut self, name: &str, usage: NameUsage) -> CompileResult<()> {
2026        enum NameOp {
2027            Fast,
2028            Global,
2029            Deref,
2030            Name,
2031            DictOrGlobals, // PEP 649: can_see_class_scope
2032        }
2033
2034        let name = self.mangle(name);
2035
2036        // Special handling for __debug__
2037        if NameUsage::Load == usage && name == "__debug__" {
2038            self.emit_load_const(ConstantData::Boolean {
2039                value: self.opts.optimize == 0,
2040            });
2041            return Ok(());
2042        }
2043
2044        // Determine the operation type based on symbol scope
2045        let is_function_like = self.ctx.in_func();
2046
2047        // Look up the symbol, handling ast::TypeParams and Annotation scopes specially
2048        let (symbol_scope, can_see_class_scope) = {
2049            let current_table = self.current_symbol_table();
2050            let is_typeparams = current_table.typ == CompilerScope::TypeParams;
2051            let is_annotation = current_table.typ == CompilerScope::Annotation;
2052            let can_see_class = current_table.can_see_class_scope;
2053
2054            // First try to find in current table
2055            let symbol = current_table.lookup(name.as_ref());
2056
2057            // If not found and we're in ast::TypeParams or Annotation scope, try parent scope
2058            let symbol = if symbol.is_none() && (is_typeparams || is_annotation) {
2059                self.symbol_table_stack
2060                    .get(self.symbol_table_stack.len() - 2) // Try to get parent index
2061                    .expect("Symbol has no parent! This is a compiler bug.")
2062                    .lookup(name.as_ref())
2063            } else {
2064                symbol
2065            };
2066
2067            (symbol.map(|s| s.scope), can_see_class)
2068        };
2069
2070        // Special handling for class scope implicit cell variables
2071        // These are treated as Cell even if not explicitly marked in symbol table
2072        // __class__ and __classdict__: only LOAD uses Cell (stores go to class namespace)
2073        // __conditional_annotations__: both LOAD and STORE use Cell (it's a mutable set
2074        // that the annotation scope accesses through the closure)
2075        let symbol_scope = {
2076            let current_table = self.current_symbol_table();
2077            if current_table.typ == CompilerScope::Class
2078                && ((usage == NameUsage::Load
2079                    && (name == "__class__"
2080                        || name == "__classdict__"
2081                        || name == "__conditional_annotations__"))
2082                    || (name == "__conditional_annotations__" && usage == NameUsage::Store))
2083            {
2084                Some(SymbolScope::Cell)
2085            } else {
2086                symbol_scope
2087            }
2088        };
2089
2090        // In annotation or type params scope, missing symbols are treated as global implicit
2091        // This allows referencing global names like Union, Optional, etc. that are imported
2092        // at module level but not explicitly bound in the function scope
2093        let actual_scope = match symbol_scope {
2094            Some(scope) => scope,
2095            None => {
2096                let current_table = self.current_symbol_table();
2097                if matches!(
2098                    current_table.typ,
2099                    CompilerScope::Annotation | CompilerScope::TypeParams
2100                ) {
2101                    SymbolScope::GlobalImplicit
2102                } else {
2103                    return Err(self.error(CodegenErrorType::SyntaxError(format!(
2104                        "the symbol '{name}' must be present in the symbol table"
2105                    ))));
2106                }
2107            }
2108        };
2109
2110        let module_global_from_nested_scope = {
2111            let current_table = self.current_symbol_table();
2112            current_table.typ == CompilerScope::Module
2113                && Self::module_name_declared_global_in_nested_scope(current_table, name.as_ref())
2114        };
2115
2116        // Determine operation type based on scope
2117        let op_type = match actual_scope {
2118            SymbolScope::Free => NameOp::Deref,
2119            SymbolScope::Cell => NameOp::Deref,
2120            SymbolScope::Local => {
2121                if module_global_from_nested_scope {
2122                    NameOp::Global
2123                } else if is_function_like {
2124                    NameOp::Fast
2125                } else {
2126                    NameOp::Name
2127                }
2128            }
2129            SymbolScope::GlobalImplicit => {
2130                // PEP 649: In annotation scope with class visibility, use DictOrGlobals
2131                // to check classdict first before globals
2132                if can_see_class_scope {
2133                    NameOp::DictOrGlobals
2134                } else if is_function_like {
2135                    NameOp::Global
2136                } else {
2137                    NameOp::Name
2138                }
2139            }
2140            SymbolScope::GlobalExplicit => NameOp::Global,
2141            SymbolScope::Unknown => {
2142                if module_global_from_nested_scope {
2143                    NameOp::Global
2144                } else {
2145                    NameOp::Name
2146                }
2147            }
2148        };
2149
2150        // Generate appropriate instructions based on operation type
2151        match op_type {
2152            NameOp::Deref => {
2153                let i = match actual_scope {
2154                    SymbolScope::Free => self.get_free_var_index(&name)?,
2155                    SymbolScope::Cell => self.get_cell_var_index(&name)?,
2156                    _ => unreachable!("Invalid scope for Deref operation"),
2157                };
2158
2159                // Mark cell variables accessed inside inlined comprehensions as hidden
2160                if self.current_code_info().in_inlined_comp {
2161                    let info = self.code_stack.last_mut().unwrap();
2162                    if info
2163                        .metadata
2164                        .fast_hidden
2165                        .get(name.as_ref())
2166                        .is_none_or(|&v| v)
2167                    {
2168                        info.metadata.fast_hidden.insert(name.to_string(), true);
2169                    }
2170                }
2171
2172                match usage {
2173                    NameUsage::Load => {
2174                        // ClassBlock (not inlined comp): LOAD_LOCALS first, then LOAD_FROM_DICT_OR_DEREF
2175                        if self.ctx.in_class && !self.ctx.in_func() {
2176                            emit!(self, Instruction::LoadLocals);
2177                            emit!(self, Instruction::LoadFromDictOrDeref { i });
2178                        // can_see_class_scope: LOAD_DEREF(__classdict__) first
2179                        } else if can_see_class_scope {
2180                            let classdict_idx = self.get_free_var_index("__classdict__")?;
2181                            emit!(self, Instruction::LoadDeref { i: classdict_idx });
2182                            emit!(self, Instruction::LoadFromDictOrDeref { i });
2183                        } else {
2184                            emit!(self, Instruction::LoadDeref { i });
2185                        }
2186                    }
2187                    NameUsage::Store => emit!(self, Instruction::StoreDeref { i }),
2188                    NameUsage::Delete => emit!(self, Instruction::DeleteDeref { i }),
2189                };
2190            }
2191            NameOp::Fast => {
2192                let var_num = self.get_local_var_index(&name)?;
2193                // Mark variables accessed inside inlined comprehensions as hidden
2194                if self.current_code_info().in_inlined_comp {
2195                    let info = self.code_stack.last_mut().unwrap();
2196                    if info
2197                        .metadata
2198                        .fast_hidden
2199                        .get(name.as_ref())
2200                        .is_none_or(|&v| v)
2201                    {
2202                        info.metadata.fast_hidden.insert(name.to_string(), true);
2203                    }
2204                }
2205                match usage {
2206                    NameUsage::Load => emit!(self, Instruction::LoadFast { var_num }),
2207                    NameUsage::Store => emit!(self, Instruction::StoreFast { var_num }),
2208                    NameUsage::Delete => emit!(self, Instruction::DeleteFast { var_num }),
2209                };
2210            }
2211            NameOp::Global => {
2212                let namei = self.get_global_name_index(&name);
2213                match usage {
2214                    NameUsage::Load => {
2215                        self.emit_load_global(namei, false);
2216                        return Ok(());
2217                    }
2218                    NameUsage::Store => emit!(self, Instruction::StoreGlobal { namei }),
2219                    NameUsage::Delete => emit!(self, Instruction::DeleteGlobal { namei }),
2220                };
2221            }
2222            NameOp::Name => {
2223                let namei = self.get_global_name_index(&name);
2224                match usage {
2225                    NameUsage::Load => emit!(self, Instruction::LoadName { namei }),
2226                    NameUsage::Store => emit!(self, Instruction::StoreName { namei }),
2227                    NameUsage::Delete => emit!(self, Instruction::DeleteName { namei }),
2228                };
2229            }
2230            NameOp::DictOrGlobals => {
2231                // PEP 649: First check classdict (from __classdict__ freevar), then globals
2232                let idx = self.get_global_name_index(&name);
2233                match usage {
2234                    NameUsage::Load => {
2235                        // Load __classdict__ first (it's a free variable in annotation scope)
2236                        let classdict_idx = self.get_free_var_index("__classdict__")?;
2237                        emit!(self, Instruction::LoadDeref { i: classdict_idx });
2238                        emit!(self, Instruction::LoadFromDictOrGlobals { i: idx });
2239                    }
2240                    // Store/Delete in annotation scope should use Name ops
2241                    NameUsage::Store => {
2242                        emit!(self, Instruction::StoreName { namei: idx });
2243                    }
2244                    NameUsage::Delete => {
2245                        emit!(self, Instruction::DeleteName { namei: idx });
2246                    }
2247                }
2248            }
2249        }
2250
2251        Ok(())
2252    }
2253
2254    fn compile_statement(&mut self, statement: &ast::Stmt) -> CompileResult<()> {
2255        trace!("Compiling {statement:?}");
2256        let prev_source_range = self.current_source_range;
2257        self.set_source_range(statement.range());
2258
2259        match &statement {
2260            // we do this here because `from __future__` still executes that `from` statement at runtime,
2261            // we still need to compile the ImportFrom down below
2262            ast::Stmt::ImportFrom(ast::StmtImportFrom { module, names, .. })
2263                if module.as_ref().map(|id| id.as_str()) == Some("__future__") =>
2264            {
2265                self.compile_future_features(names)?
2266            }
2267            // ignore module-level doc comments
2268            ast::Stmt::Expr(ast::StmtExpr { value, .. })
2269                if matches!(&**value, ast::Expr::StringLiteral(..))
2270                    && matches!(self.done_with_future_stmts, DoneWithFuture::No) =>
2271            {
2272                self.done_with_future_stmts = DoneWithFuture::DoneWithDoc
2273            }
2274            // if we find any other statement, stop accepting future statements
2275            _ => self.done_with_future_stmts = DoneWithFuture::Yes,
2276        }
2277
2278        match &statement {
2279            ast::Stmt::Import(ast::StmtImport { names, .. }) => {
2280                // import a, b, c as d
2281                for name in names {
2282                    let name = &name;
2283                    self.emit_load_const(ConstantData::Integer {
2284                        value: num_traits::Zero::zero(),
2285                    });
2286                    self.emit_load_const(ConstantData::None);
2287                    let namei = self.name(&name.name);
2288                    emit!(self, Instruction::ImportName { namei });
2289                    if let Some(alias) = &name.asname {
2290                        let parts: Vec<&str> = name.name.split('.').skip(1).collect();
2291                        for (i, part) in parts.iter().enumerate() {
2292                            let namei = self.name(part);
2293                            emit!(self, Instruction::ImportFrom { namei });
2294                            if i < parts.len() - 1 {
2295                                emit!(self, Instruction::Swap { i: 2 });
2296                                emit!(self, Instruction::PopTop);
2297                            }
2298                        }
2299                        self.store_name(alias.as_str())?;
2300                        if !parts.is_empty() {
2301                            emit!(self, Instruction::PopTop);
2302                        }
2303                    } else {
2304                        self.store_name(name.name.split('.').next().unwrap())?
2305                    }
2306                }
2307            }
2308            ast::Stmt::ImportFrom(ast::StmtImportFrom {
2309                level,
2310                module,
2311                names,
2312                ..
2313            }) => {
2314                let import_star = names.iter().any(|n| &n.name == "*");
2315
2316                let from_list = if import_star {
2317                    if self.ctx.in_func() {
2318                        return Err(self.error_ranged(
2319                            CodegenErrorType::FunctionImportStar,
2320                            statement.range(),
2321                        ));
2322                    }
2323                    vec![ConstantData::Str { value: "*".into() }]
2324                } else {
2325                    names
2326                        .iter()
2327                        .map(|n| ConstantData::Str {
2328                            value: n.name.as_str().into(),
2329                        })
2330                        .collect()
2331                };
2332
2333                // from .... import (*fromlist)
2334                self.emit_load_const(ConstantData::Integer {
2335                    value: (*level).into(),
2336                });
2337                self.emit_load_const(ConstantData::Tuple {
2338                    elements: from_list,
2339                });
2340
2341                let module_name = module.as_ref().map_or("", |s| s.as_str());
2342                let module_idx = self.name(module_name);
2343                emit!(self, Instruction::ImportName { namei: module_idx });
2344
2345                if import_star {
2346                    // from .... import *
2347                    emit!(
2348                        self,
2349                        Instruction::CallIntrinsic1 {
2350                            func: bytecode::IntrinsicFunction1::ImportStar
2351                        }
2352                    );
2353                    emit!(self, Instruction::PopTop);
2354                } else {
2355                    // from mod import a, b as c
2356
2357                    for name in names {
2358                        let name = &name;
2359                        let idx = self.name(name.name.as_str());
2360                        // import symbol from module:
2361                        emit!(self, Instruction::ImportFrom { namei: idx });
2362
2363                        // Store module under proper name:
2364                        if let Some(alias) = &name.asname {
2365                            self.store_name(alias.as_str())?
2366                        } else {
2367                            self.store_name(name.name.as_str())?
2368                        }
2369                    }
2370
2371                    // Pop module from stack:
2372                    emit!(self, Instruction::PopTop);
2373                }
2374            }
2375            ast::Stmt::Expr(ast::StmtExpr { value, .. }) => {
2376                // Optimize away constant expressions with no side effects.
2377                // In interactive mode, always compile (to print the result).
2378                let dominated_by_interactive =
2379                    self.interactive && !self.ctx.in_func() && !self.ctx.in_class;
2380                if !dominated_by_interactive && Self::is_const_expression(value) {
2381                    // Skip compilation entirely - the expression has no side effects
2382                } else {
2383                    self.compile_expression(value)?;
2384
2385                    if dominated_by_interactive {
2386                        emit!(
2387                            self,
2388                            Instruction::CallIntrinsic1 {
2389                                func: bytecode::IntrinsicFunction1::Print
2390                            }
2391                        );
2392                    }
2393
2394                    emit!(self, Instruction::PopTop);
2395                }
2396            }
2397            ast::Stmt::Global(_) | ast::Stmt::Nonlocal(_) => {
2398                // Handled during symbol table construction.
2399            }
2400            ast::Stmt::If(ast::StmtIf {
2401                test,
2402                body,
2403                elif_else_clauses,
2404                ..
2405            }) => {
2406                self.enter_conditional_block();
2407                self.compile_if(test, body, elif_else_clauses)?;
2408                self.leave_conditional_block();
2409            }
2410            ast::Stmt::While(ast::StmtWhile {
2411                test, body, orelse, ..
2412            }) => self.compile_while(test, body, orelse)?,
2413            ast::Stmt::With(ast::StmtWith {
2414                items,
2415                body,
2416                is_async,
2417                ..
2418            }) => self.compile_with(items, body, *is_async)?,
2419            ast::Stmt::For(ast::StmtFor {
2420                target,
2421                iter,
2422                body,
2423                orelse,
2424                is_async,
2425                ..
2426            }) => self.compile_for(target, iter, body, orelse, *is_async)?,
2427            ast::Stmt::Match(ast::StmtMatch { subject, cases, .. }) => {
2428                self.compile_match(subject, cases)?
2429            }
2430            ast::Stmt::Raise(ast::StmtRaise {
2431                exc, cause, range, ..
2432            }) => {
2433                let kind = match exc {
2434                    Some(value) => {
2435                        self.compile_expression(value)?;
2436                        match cause {
2437                            Some(cause) => {
2438                                self.compile_expression(cause)?;
2439                                bytecode::RaiseKind::RaiseCause
2440                            }
2441                            None => bytecode::RaiseKind::Raise,
2442                        }
2443                    }
2444                    None => bytecode::RaiseKind::BareRaise,
2445                };
2446                self.set_source_range(*range);
2447                emit!(self, Instruction::RaiseVarargs { argc: kind });
2448                // Start a new block so dead code after raise doesn't
2449                // corrupt the except stack in label_exception_targets
2450                let dead = self.new_block();
2451                self.switch_to_block(dead);
2452            }
2453            ast::Stmt::Try(ast::StmtTry {
2454                body,
2455                handlers,
2456                orelse,
2457                finalbody,
2458                is_star,
2459                ..
2460            }) => {
2461                self.enter_conditional_block();
2462                if *is_star {
2463                    self.compile_try_star_except(body, handlers, orelse, finalbody)?
2464                } else {
2465                    self.compile_try_statement(body, handlers, orelse, finalbody)?
2466                }
2467                self.leave_conditional_block();
2468            }
2469            ast::Stmt::FunctionDef(ast::StmtFunctionDef {
2470                name,
2471                parameters,
2472                body,
2473                decorator_list,
2474                returns,
2475                type_params,
2476                is_async,
2477                ..
2478            }) => {
2479                validate_duplicate_params(parameters).map_err(|e| self.error(e))?;
2480
2481                self.compile_function_def(
2482                    name.as_str(),
2483                    parameters,
2484                    body,
2485                    decorator_list,
2486                    returns.as_deref(),
2487                    *is_async,
2488                    type_params.as_deref(),
2489                )?
2490            }
2491            ast::Stmt::ClassDef(ast::StmtClassDef {
2492                name,
2493                body,
2494                decorator_list,
2495                type_params,
2496                arguments,
2497                ..
2498            }) => self.compile_class_def(
2499                name.as_str(),
2500                body,
2501                decorator_list,
2502                type_params.as_deref(),
2503                arguments.as_deref(),
2504            )?,
2505            ast::Stmt::Assert(ast::StmtAssert { test, msg, .. }) => {
2506                // if some flag, ignore all assert statements!
2507                if self.opts.optimize == 0 {
2508                    let after_block = self.new_block();
2509                    self.compile_jump_if(test, true, after_block)?;
2510
2511                    emit!(
2512                        self,
2513                        Instruction::LoadCommonConstant {
2514                            idx: bytecode::CommonConstant::AssertionError
2515                        }
2516                    );
2517                    if let Some(e) = msg {
2518                        emit!(self, Instruction::PushNull);
2519                        self.compile_expression(e)?;
2520                        emit!(self, Instruction::Call { argc: 1 });
2521                    }
2522                    emit!(
2523                        self,
2524                        Instruction::RaiseVarargs {
2525                            argc: bytecode::RaiseKind::Raise,
2526                        }
2527                    );
2528
2529                    self.switch_to_block(after_block);
2530                } else {
2531                    // Optimized-out asserts still need to consume any nested
2532                    // scope symbol tables they contain so later nested scopes
2533                    // stay aligned with AST traversal order.
2534                    self.consume_skipped_nested_scopes_in_expr(test)?;
2535                    if let Some(expr) = msg {
2536                        self.consume_skipped_nested_scopes_in_expr(expr)?;
2537                    }
2538                }
2539            }
2540            ast::Stmt::Break(_) => {
2541                emit!(self, Instruction::Nop); // NOP for line tracing
2542                // Unwind fblock stack until we find a loop, emitting cleanup for each fblock
2543                self.compile_break_continue(statement.range(), true)?;
2544                let dead = self.new_block();
2545                self.switch_to_block(dead);
2546            }
2547            ast::Stmt::Continue(_) => {
2548                emit!(self, Instruction::Nop); // NOP for line tracing
2549                // Unwind fblock stack until we find a loop, emitting cleanup for each fblock
2550                self.compile_break_continue(statement.range(), false)?;
2551                let dead = self.new_block();
2552                self.switch_to_block(dead);
2553            }
2554            ast::Stmt::Return(ast::StmtReturn { value, .. }) => {
2555                if !self.ctx.in_func() {
2556                    return Err(
2557                        self.error_ranged(CodegenErrorType::InvalidReturn, statement.range())
2558                    );
2559                }
2560
2561                match value {
2562                    Some(v) => {
2563                        if self.ctx.func == FunctionContext::AsyncFunction
2564                            && self
2565                                .current_code_info()
2566                                .flags
2567                                .contains(bytecode::CodeFlags::GENERATOR)
2568                        {
2569                            return Err(self.error_ranged(
2570                                CodegenErrorType::AsyncReturnValue,
2571                                statement.range(),
2572                            ));
2573                        }
2574                        let folded_constant = self.try_fold_constant_expr(v)?;
2575                        let preserve_tos = folded_constant.is_none();
2576                        if preserve_tos {
2577                            self.compile_expression(v)?;
2578                        }
2579                        self.unwind_fblock_stack(preserve_tos, false)?;
2580                        if let Some(constant) = folded_constant {
2581                            self.emit_load_const(constant);
2582                        }
2583                        self.emit_return_value();
2584                    }
2585                    None => {
2586                        // Unwind fblock stack with preserve_tos=false (no value to preserve)
2587                        self.unwind_fblock_stack(false, false)?;
2588                        self.emit_return_const(ConstantData::None);
2589                    }
2590                }
2591                let dead = self.new_block();
2592                self.switch_to_block(dead);
2593            }
2594            ast::Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
2595                self.compile_expression(value)?;
2596
2597                for (i, target) in targets.iter().enumerate() {
2598                    if i + 1 != targets.len() {
2599                        emit!(self, Instruction::Copy { i: 1 });
2600                    }
2601                    self.compile_store(target)?;
2602                }
2603            }
2604            ast::Stmt::AugAssign(ast::StmtAugAssign {
2605                target, op, value, ..
2606            }) => self.compile_augassign(target, op, value)?,
2607            ast::Stmt::AnnAssign(ast::StmtAnnAssign {
2608                target,
2609                annotation,
2610                value,
2611                simple,
2612                ..
2613            }) => {
2614                self.compile_annotated_assign(target, annotation, value.as_deref(), *simple)?;
2615                // Bare annotations in function scope emit no code; restore
2616                // source range so subsequent instructions keep the correct line.
2617                if value.is_none() && self.ctx.in_func() {
2618                    self.set_source_range(prev_source_range);
2619                }
2620            }
2621            ast::Stmt::Delete(ast::StmtDelete { targets, .. }) => {
2622                for target in targets {
2623                    self.compile_delete(target)?;
2624                }
2625            }
2626            ast::Stmt::Pass(_) => {
2627                emit!(self, Instruction::Nop); // NOP for line tracing
2628            }
2629            ast::Stmt::TypeAlias(ast::StmtTypeAlias {
2630                name,
2631                type_params,
2632                value,
2633                ..
2634            }) => {
2635                // let name_string = name.to_string();
2636                let Some(name) = name.as_name_expr() else {
2637                    // FIXME: is error here?
2638                    return Err(self.error(CodegenErrorType::SyntaxError(
2639                        "type alias expect name".to_owned(),
2640                    )));
2641                };
2642                let name_string = name.id.to_string();
2643
2644                // For PEP 695 syntax, we need to compile type_params first
2645                // so that they're available when compiling the value expression
2646                // Push name first
2647                self.emit_load_const(ConstantData::Str {
2648                    value: name_string.clone().into(),
2649                });
2650
2651                if let Some(type_params) = type_params {
2652                    // Outer scope for TypeParams
2653                    self.push_symbol_table()?;
2654                    let key = self.symbol_table_stack.len() - 1;
2655                    let lineno = self.get_source_line_number().get().to_u32();
2656                    let scope_name = format!("<generic parameters of {name_string}>");
2657                    self.enter_scope(&scope_name, CompilerScope::TypeParams, key, lineno)?;
2658
2659                    // TypeParams scope is function-like
2660                    let prev_ctx = self.ctx;
2661                    self.ctx = CompileContext {
2662                        loop_data: None,
2663                        in_class: prev_ctx.in_class,
2664                        func: FunctionContext::Function,
2665                        in_async_scope: false,
2666                    };
2667
2668                    // Compile type params inside the scope
2669                    self.compile_type_params(type_params)?;
2670                    // Stack: [type_params_tuple]
2671
2672                    // Inner closure for lazy value evaluation
2673                    self.push_symbol_table()?;
2674                    let inner_key = self.symbol_table_stack.len() - 1;
2675                    self.enter_scope("TypeAlias", CompilerScope::TypeParams, inner_key, lineno)?;
2676                    // Evaluator takes a positional-only format parameter
2677                    self.current_code_info().metadata.argcount = 1;
2678                    self.current_code_info().metadata.posonlyargcount = 1;
2679                    self.current_code_info()
2680                        .metadata
2681                        .varnames
2682                        .insert("format".to_owned());
2683                    self.emit_format_validation()?;
2684                    self.compile_expression(value)?;
2685                    emit!(self, Instruction::ReturnValue);
2686                    let value_code = self.exit_scope();
2687                    self.make_closure(value_code, bytecode::MakeFunctionFlags::new())?;
2688                    // Stack: [type_params_tuple, value_closure]
2689
2690                    // Swap so unpack_sequence reverse gives correct order
2691                    emit!(self, Instruction::Swap { i: 2 });
2692                    // Stack: [value_closure, type_params_tuple]
2693
2694                    // Build tuple and return from TypeParams scope
2695                    emit!(self, Instruction::BuildTuple { count: 2 });
2696                    emit!(self, Instruction::ReturnValue);
2697
2698                    let code = self.exit_scope();
2699                    self.ctx = prev_ctx;
2700                    self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
2701                    emit!(self, Instruction::PushNull);
2702                    emit!(self, Instruction::Call { argc: 0 });
2703
2704                    // Unpack: (value_closure, type_params_tuple)
2705                    // UnpackSequence reverses → stack: [name, type_params_tuple, value_closure]
2706                    emit!(self, Instruction::UnpackSequence { count: 2 });
2707                } else {
2708                    // Push None for type_params
2709                    self.emit_load_const(ConstantData::None);
2710                    // Stack: [name, None]
2711
2712                    // Create a closure for lazy evaluation of the value
2713                    self.push_symbol_table()?;
2714                    let key = self.symbol_table_stack.len() - 1;
2715                    let lineno = self.get_source_line_number().get().to_u32();
2716                    self.enter_scope("TypeAlias", CompilerScope::TypeParams, key, lineno)?;
2717                    // Evaluator takes a positional-only format parameter
2718                    self.current_code_info().metadata.argcount = 1;
2719                    self.current_code_info().metadata.posonlyargcount = 1;
2720                    self.current_code_info()
2721                        .metadata
2722                        .varnames
2723                        .insert("format".to_owned());
2724                    self.emit_format_validation()?;
2725
2726                    let prev_ctx = self.ctx;
2727                    self.ctx = CompileContext {
2728                        loop_data: None,
2729                        in_class: prev_ctx.in_class,
2730                        func: FunctionContext::Function,
2731                        in_async_scope: false,
2732                    };
2733
2734                    self.compile_expression(value)?;
2735                    emit!(self, Instruction::ReturnValue);
2736
2737                    let code = self.exit_scope();
2738                    self.ctx = prev_ctx;
2739                    self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
2740                    // Stack: [name, None, closure]
2741                }
2742
2743                // Build tuple of 3 elements and call intrinsic
2744                emit!(self, Instruction::BuildTuple { count: 3 });
2745                emit!(
2746                    self,
2747                    Instruction::CallIntrinsic1 {
2748                        func: bytecode::IntrinsicFunction1::TypeAlias
2749                    }
2750                );
2751                self.store_name(&name_string)?;
2752            }
2753            ast::Stmt::IpyEscapeCommand(_) => todo!(),
2754        }
2755        Ok(())
2756    }
2757
2758    fn compile_delete(&mut self, expression: &ast::Expr) -> CompileResult<()> {
2759        match &expression {
2760            ast::Expr::Name(ast::ExprName { id, .. }) => {
2761                self.compile_name(id.as_str(), NameUsage::Delete)?
2762            }
2763            ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
2764                self.compile_expression(value)?;
2765                let namei = self.name(attr.as_str());
2766                emit!(self, Instruction::DeleteAttr { namei });
2767            }
2768            ast::Expr::Subscript(ast::ExprSubscript {
2769                value, slice, ctx, ..
2770            }) => {
2771                self.compile_subscript(value, slice, *ctx)?;
2772            }
2773            ast::Expr::Tuple(ast::ExprTuple { elts, .. })
2774            | ast::Expr::List(ast::ExprList { elts, .. }) => {
2775                for element in elts {
2776                    self.compile_delete(element)?;
2777                }
2778            }
2779            ast::Expr::BinOp(_) | ast::Expr::UnaryOp(_) => {
2780                return Err(self.error(CodegenErrorType::Delete("expression")));
2781            }
2782            _ => return Err(self.error(CodegenErrorType::Delete(expression.python_name()))),
2783        }
2784        Ok(())
2785    }
2786
2787    fn enter_function(&mut self, name: &str, parameters: &ast::Parameters) -> CompileResult<()> {
2788        // TODO: partition_in_place
2789        let mut kw_without_defaults = vec![];
2790        let mut kw_with_defaults = vec![];
2791        for kwonlyarg in &parameters.kwonlyargs {
2792            if let Some(default) = &kwonlyarg.default {
2793                kw_with_defaults.push((&kwonlyarg.parameter, default));
2794            } else {
2795                kw_without_defaults.push(&kwonlyarg.parameter);
2796            }
2797        }
2798
2799        self.push_output(
2800            bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
2801            parameters.posonlyargs.len().to_u32(),
2802            (parameters.posonlyargs.len() + parameters.args.len()).to_u32(),
2803            parameters.kwonlyargs.len().to_u32(),
2804            name.to_owned(),
2805        )?;
2806
2807        let args_iter = core::iter::empty()
2808            .chain(&parameters.posonlyargs)
2809            .chain(&parameters.args)
2810            .map(|arg| &arg.parameter)
2811            .chain(kw_without_defaults)
2812            .chain(kw_with_defaults.into_iter().map(|(arg, _)| arg));
2813        for name in args_iter {
2814            self.varname(name.name.as_str())?;
2815        }
2816
2817        if let Some(name) = parameters.vararg.as_deref() {
2818            self.current_code_info().flags |= bytecode::CodeFlags::VARARGS;
2819            self.varname(name.name.as_str())?;
2820        }
2821        if let Some(name) = parameters.kwarg.as_deref() {
2822            self.current_code_info().flags |= bytecode::CodeFlags::VARKEYWORDS;
2823            self.varname(name.name.as_str())?;
2824        }
2825
2826        Ok(())
2827    }
2828
2829    /// Push decorators onto the stack in source order.
2830    /// For @dec1 @dec2 def foo(): stack becomes [dec1, dec2]
2831    fn prepare_decorators(&mut self, decorator_list: &[ast::Decorator]) -> CompileResult<()> {
2832        for decorator in decorator_list {
2833            self.compile_expression(&decorator.expression)?;
2834        }
2835        Ok(())
2836    }
2837
2838    /// Apply decorators: each decorator calls the function below it.
2839    /// Stack: [dec1, dec2, func] → CALL 0 → [dec1, dec2(func)] → CALL 0 → [dec1(dec2(func))]
2840    fn apply_decorators(&mut self, decorator_list: &[ast::Decorator]) {
2841        for _ in decorator_list {
2842            emit!(self, Instruction::Call { argc: 0 });
2843        }
2844    }
2845
2846    /// Compile type parameter bound or default in a separate scope and return closure
2847    fn compile_type_param_bound_or_default(
2848        &mut self,
2849        expr: &ast::Expr,
2850        name: &str,
2851        allow_starred: bool,
2852    ) -> CompileResult<()> {
2853        // Push the next symbol table onto the stack
2854        self.push_symbol_table()?;
2855
2856        // Get the current symbol table
2857        let key = self.symbol_table_stack.len() - 1;
2858        let lineno = self.get_source_line_number().get().to_u32();
2859
2860        // Enter scope with the type parameter name
2861        self.enter_scope(name, CompilerScope::TypeParams, key, lineno)?;
2862
2863        // Evaluator takes a positional-only format parameter
2864        self.current_code_info().metadata.argcount = 1;
2865        self.current_code_info().metadata.posonlyargcount = 1;
2866        self.current_code_info()
2867            .metadata
2868            .varnames
2869            .insert("format".to_owned());
2870
2871        self.emit_format_validation()?;
2872
2873        // TypeParams scope is function-like
2874        let prev_ctx = self.ctx;
2875        self.ctx = CompileContext {
2876            loop_data: None,
2877            in_class: prev_ctx.in_class,
2878            func: FunctionContext::Function,
2879            in_async_scope: false,
2880        };
2881
2882        // Compile the expression
2883        if allow_starred && matches!(expr, ast::Expr::Starred(_)) {
2884            if let ast::Expr::Starred(starred) = expr {
2885                self.compile_expression(&starred.value)?;
2886                emit!(self, Instruction::UnpackSequence { count: 1 });
2887            }
2888        } else {
2889            self.compile_expression(expr)?;
2890        }
2891
2892        // Return value
2893        emit!(self, Instruction::ReturnValue);
2894
2895        // Exit scope and create closure
2896        let code = self.exit_scope();
2897        self.ctx = prev_ctx;
2898
2899        // Create closure for lazy evaluation
2900        self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
2901
2902        Ok(())
2903    }
2904
2905    /// Store each type parameter so it is accessible to the current scope, and leave a tuple of
2906    /// all the type parameters on the stack. Handles default values per PEP 695.
2907    fn compile_type_params(&mut self, type_params: &ast::TypeParams) -> CompileResult<()> {
2908        // First, compile each type parameter and store it
2909        for type_param in &type_params.type_params {
2910            match type_param {
2911                ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
2912                    name,
2913                    bound,
2914                    default,
2915                    ..
2916                }) => {
2917                    self.emit_load_const(ConstantData::Str {
2918                        value: name.as_str().into(),
2919                    });
2920
2921                    if let Some(expr) = &bound {
2922                        let scope_name = if expr.is_tuple_expr() {
2923                            format!("<TypeVar constraint of {name}>")
2924                        } else {
2925                            format!("<TypeVar bound of {name}>")
2926                        };
2927                        self.compile_type_param_bound_or_default(expr, &scope_name, false)?;
2928
2929                        let intrinsic = if expr.is_tuple_expr() {
2930                            bytecode::IntrinsicFunction2::TypeVarWithConstraint
2931                        } else {
2932                            bytecode::IntrinsicFunction2::TypeVarWithBound
2933                        };
2934                        emit!(self, Instruction::CallIntrinsic2 { func: intrinsic });
2935                    } else {
2936                        emit!(
2937                            self,
2938                            Instruction::CallIntrinsic1 {
2939                                func: bytecode::IntrinsicFunction1::TypeVar
2940                            }
2941                        );
2942                    }
2943
2944                    if let Some(default_expr) = default {
2945                        let scope_name = format!("<TypeVar default of {name}>");
2946                        self.compile_type_param_bound_or_default(default_expr, &scope_name, false)?;
2947                        emit!(
2948                            self,
2949                            Instruction::CallIntrinsic2 {
2950                                func: bytecode::IntrinsicFunction2::SetTypeparamDefault
2951                            }
2952                        );
2953                    }
2954
2955                    emit!(self, Instruction::Copy { i: 1 });
2956                    self.store_name(name.as_ref())?;
2957                }
2958                ast::TypeParam::ParamSpec(ast::TypeParamParamSpec { name, default, .. }) => {
2959                    self.emit_load_const(ConstantData::Str {
2960                        value: name.as_str().into(),
2961                    });
2962                    emit!(
2963                        self,
2964                        Instruction::CallIntrinsic1 {
2965                            func: bytecode::IntrinsicFunction1::ParamSpec
2966                        }
2967                    );
2968
2969                    if let Some(default_expr) = default {
2970                        let scope_name = format!("<ParamSpec default of {name}>");
2971                        self.compile_type_param_bound_or_default(default_expr, &scope_name, false)?;
2972                        emit!(
2973                            self,
2974                            Instruction::CallIntrinsic2 {
2975                                func: bytecode::IntrinsicFunction2::SetTypeparamDefault
2976                            }
2977                        );
2978                    }
2979
2980                    emit!(self, Instruction::Copy { i: 1 });
2981                    self.store_name(name.as_ref())?;
2982                }
2983                ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple {
2984                    name, default, ..
2985                }) => {
2986                    self.emit_load_const(ConstantData::Str {
2987                        value: name.as_str().into(),
2988                    });
2989                    emit!(
2990                        self,
2991                        Instruction::CallIntrinsic1 {
2992                            func: bytecode::IntrinsicFunction1::TypeVarTuple
2993                        }
2994                    );
2995
2996                    if let Some(default_expr) = default {
2997                        // TypeVarTuple allows starred expressions
2998                        let scope_name = format!("<TypeVarTuple default of {name}>");
2999                        self.compile_type_param_bound_or_default(default_expr, &scope_name, true)?;
3000                        emit!(
3001                            self,
3002                            Instruction::CallIntrinsic2 {
3003                                func: bytecode::IntrinsicFunction2::SetTypeparamDefault
3004                            }
3005                        );
3006                    }
3007
3008                    emit!(self, Instruction::Copy { i: 1 });
3009                    self.store_name(name.as_ref())?;
3010                }
3011            };
3012        }
3013        emit!(
3014            self,
3015            Instruction::BuildTuple {
3016                count: u32::try_from(type_params.len()).unwrap(),
3017            }
3018        );
3019        Ok(())
3020    }
3021
3022    fn compile_try_statement(
3023        &mut self,
3024        body: &[ast::Stmt],
3025        handlers: &[ast::ExceptHandler],
3026        orelse: &[ast::Stmt],
3027        finalbody: &[ast::Stmt],
3028    ) -> CompileResult<()> {
3029        if finalbody.is_empty() {
3030            return self.compile_try_except_no_finally(body, handlers, orelse);
3031        }
3032
3033        let handler_block = self.new_block();
3034        let finally_block = self.new_block();
3035
3036        // finally needs TWO blocks:
3037        // - finally_block: normal path (no exception active)
3038        // - finally_except_block: exception path (PUSH_EXC_INFO -> body -> RERAISE)
3039        let finally_except_block = if !finalbody.is_empty() {
3040            Some(self.new_block())
3041        } else {
3042            None
3043        };
3044        let finally_cleanup_block = if finally_except_block.is_some() {
3045            Some(self.new_block())
3046        } else {
3047            None
3048        };
3049        // End block - continuation point after try-finally
3050        // Normal path jumps here to skip exception path blocks
3051        let end_block = self.new_block();
3052
3053        // Emit NOP at the try: line so LINE events fire for it
3054        emit!(self, Instruction::Nop);
3055
3056        // Setup a finally block if we have a finally statement.
3057        // Push fblock with handler info for exception table generation
3058        // IMPORTANT: handler goes to finally_except_block (exception path), not finally_block
3059        if !finalbody.is_empty() {
3060            // SETUP_FINALLY doesn't push lasti for try body handler
3061            // Exception table: L1 to L2 -> L4 [1] (no lasti)
3062            let setup_target = finally_except_block.unwrap_or(finally_block);
3063            emit!(
3064                self,
3065                PseudoInstruction::SetupFinally {
3066                    delta: setup_target
3067                }
3068            );
3069            // Store finally body in fb_datum for unwind_fblock to compile inline
3070            self.push_fblock_full(
3071                FBlockType::FinallyTry,
3072                finally_block,
3073                finally_block,
3074                FBlockDatum::FinallyBody(finalbody.to_vec()), // Clone finally body for unwind
3075            )?;
3076        }
3077
3078        let else_block = self.new_block();
3079
3080        // if handlers is empty, compile body directly
3081        // without wrapping in TryExcept (only FinallyTry is needed)
3082        if handlers.is_empty() {
3083            // Just compile body with FinallyTry fblock active (if finalbody exists)
3084            self.compile_statements(body)?;
3085
3086            // Pop FinallyTry fblock BEFORE compiling orelse/finally (normal path)
3087            // This prevents exception table from covering the normal path
3088            if !finalbody.is_empty() {
3089                emit!(self, PseudoInstruction::PopBlock);
3090                self.pop_fblock(FBlockType::FinallyTry);
3091            }
3092
3093            // Compile orelse (usually empty for try-finally without except)
3094            self.compile_statements(orelse)?;
3095
3096            // Snapshot sub_tables before first finally compilation
3097            // This allows us to restore them for the second compilation (exception path)
3098            let sub_table_cursor = if !finalbody.is_empty() && finally_except_block.is_some() {
3099                self.symbol_table_stack.last().map(|t| t.next_sub_table)
3100            } else {
3101                None
3102            };
3103
3104            // Compile finally body inline for normal path
3105            if !finalbody.is_empty() {
3106                self.compile_statements(finalbody)?;
3107            }
3108
3109            // Jump to end (skip exception path blocks)
3110            emit!(self, PseudoInstruction::Jump { delta: end_block });
3111
3112            if let Some(finally_except) = finally_except_block {
3113                // Restore sub_tables for exception path compilation
3114                if let Some(cursor) = sub_table_cursor
3115                    && let Some(current_table) = self.symbol_table_stack.last_mut()
3116                {
3117                    current_table.next_sub_table = cursor;
3118                }
3119
3120                self.switch_to_block(finally_except);
3121                // SETUP_CLEANUP before PUSH_EXC_INFO
3122                if let Some(cleanup) = finally_cleanup_block {
3123                    emit!(self, PseudoInstruction::SetupCleanup { delta: cleanup });
3124                }
3125                emit!(self, Instruction::PushExcInfo);
3126                if let Some(cleanup) = finally_cleanup_block {
3127                    self.push_fblock(FBlockType::FinallyEnd, cleanup, cleanup)?;
3128                }
3129                self.compile_statements(finalbody)?;
3130
3131                // Pop FinallyEnd fblock BEFORE emitting RERAISE
3132                // This ensures RERAISE routes to outer exception handler, not cleanup block
3133                // Cleanup block is only for new exceptions raised during finally body execution
3134                if finally_cleanup_block.is_some() {
3135                    emit!(self, PseudoInstruction::PopBlock);
3136                    self.pop_fblock(FBlockType::FinallyEnd);
3137                }
3138
3139                // Restore prev_exc as current exception before RERAISE
3140                // Stack: [prev_exc, exc] -> COPY 2 -> [prev_exc, exc, prev_exc]
3141                // POP_EXCEPT pops prev_exc and sets exc_info->exc_value = prev_exc
3142                // Stack after POP_EXCEPT: [prev_exc, exc]
3143                emit!(self, Instruction::Copy { i: 2 });
3144                emit!(self, Instruction::PopExcept);
3145
3146                // RERAISE 0: re-raise the original exception to outer handler
3147                emit!(self, Instruction::Reraise { depth: 0 });
3148            }
3149
3150            if let Some(cleanup) = finally_cleanup_block {
3151                self.switch_to_block(cleanup);
3152                emit!(self, Instruction::Copy { i: 3 });
3153                emit!(self, Instruction::PopExcept);
3154                emit!(self, Instruction::Reraise { depth: 1 });
3155            }
3156
3157            self.switch_to_block(end_block);
3158            return Ok(());
3159        }
3160
3161        // try:
3162        emit!(
3163            self,
3164            PseudoInstruction::SetupFinally {
3165                delta: handler_block
3166            }
3167        );
3168        self.push_fblock(FBlockType::TryExcept, handler_block, handler_block)?;
3169        self.compile_statements(body)?;
3170        emit!(self, PseudoInstruction::PopBlock);
3171        self.pop_fblock(FBlockType::TryExcept);
3172        emit!(self, PseudoInstruction::Jump { delta: else_block });
3173
3174        // except handlers:
3175        self.switch_to_block(handler_block);
3176
3177        // SETUP_CLEANUP(cleanup) for except block
3178        // This handles exceptions during exception matching
3179        // Exception table: L2 to L3 -> L5 [1] lasti
3180        // After PUSH_EXC_INFO, stack is [prev_exc, exc]
3181        // depth=1 means keep prev_exc on stack when routing to cleanup
3182        let cleanup_block = self.new_block();
3183        emit!(
3184            self,
3185            PseudoInstruction::SetupCleanup {
3186                delta: cleanup_block
3187            }
3188        );
3189        self.push_fblock(FBlockType::ExceptionHandler, cleanup_block, cleanup_block)?;
3190
3191        // Exception is on top of stack now, pushed by unwind_blocks
3192        // PUSH_EXC_INFO transforms [exc] -> [prev_exc, exc] for PopExcept
3193        emit!(self, Instruction::PushExcInfo);
3194        for handler in handlers {
3195            let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
3196                type_,
3197                name,
3198                body,
3199                range: handler_range,
3200                ..
3201            }) = &handler;
3202            self.set_source_range(*handler_range);
3203            let next_handler = self.new_block();
3204
3205            // If we gave a typ,
3206            // check if this handler can handle the exception:
3207            if let Some(exc_type) = type_ {
3208                // Check exception type:
3209                // Stack: [prev_exc, exc]
3210                self.compile_expression(exc_type)?;
3211                // Stack: [prev_exc, exc, type]
3212                emit!(self, Instruction::CheckExcMatch);
3213                // Stack: [prev_exc, exc, bool]
3214                emit!(
3215                    self,
3216                    Instruction::PopJumpIfFalse {
3217                        delta: next_handler
3218                    }
3219                );
3220                // Stack: [prev_exc, exc]
3221
3222                // We have a match, store in name (except x as y)
3223                if let Some(alias) = name {
3224                    self.store_name(alias.as_str())?
3225                } else {
3226                    // Drop exception from top of stack:
3227                    emit!(self, Instruction::PopTop);
3228                }
3229            } else {
3230                // Catch all!
3231                // Drop exception from top of stack:
3232                emit!(self, Instruction::PopTop);
3233            }
3234
3235            // If name is bound, we need a cleanup handler for RERAISE
3236            let handler_cleanup_block = if name.is_some() {
3237                // SETUP_CLEANUP(cleanup_end) for named handler
3238                let cleanup_end = self.new_block();
3239                emit!(self, PseudoInstruction::SetupCleanup { delta: cleanup_end });
3240                self.push_fblock_full(
3241                    FBlockType::HandlerCleanup,
3242                    cleanup_end,
3243                    cleanup_end,
3244                    FBlockDatum::ExceptionName(name.as_ref().unwrap().as_str().to_owned()),
3245                )?;
3246                Some(cleanup_end)
3247            } else {
3248                // no SETUP_CLEANUP for unnamed handler
3249                self.push_fblock(FBlockType::HandlerCleanup, finally_block, finally_block)?;
3250                None
3251            };
3252
3253            // Handler code:
3254            self.compile_statements(body)?;
3255
3256            self.pop_fblock(FBlockType::HandlerCleanup);
3257            // PopBlock for inner SETUP_CLEANUP (named handler only)
3258            if handler_cleanup_block.is_some() {
3259                emit!(self, PseudoInstruction::PopBlock);
3260            }
3261
3262            // cleanup_end block for named handler
3263            // IMPORTANT: In CPython, cleanup_end is within outer SETUP_CLEANUP scope.
3264            // so when RERAISE is executed, it goes to the cleanup block which does POP_EXCEPT.
3265            // We MUST compile cleanup_end BEFORE popping ExceptionHandler so RERAISE routes to cleanup_block.
3266            if let Some(cleanup_end) = handler_cleanup_block {
3267                let handler_normal_exit = self.new_block();
3268                emit!(
3269                    self,
3270                    PseudoInstruction::JumpNoInterrupt {
3271                        delta: handler_normal_exit,
3272                    }
3273                );
3274
3275                self.switch_to_block(cleanup_end);
3276                if let Some(alias) = name {
3277                    // name = None; del name; before RERAISE
3278                    self.emit_load_const(ConstantData::None);
3279                    self.store_name(alias.as_str())?;
3280                    self.compile_name(alias.as_str(), NameUsage::Delete)?;
3281                }
3282                // RERAISE 1 (with lasti) - exception is on stack from exception table routing
3283                // Stack at entry: [prev_exc (at handler_depth), lasti, exc]
3284                // This RERAISE is within ExceptionHandler scope, so it routes to cleanup_block
3285                // which does COPY 3; POP_EXCEPT; RERAISE
3286                emit!(self, Instruction::Reraise { depth: 1 });
3287
3288                // Switch to normal exit block - this is where handler body success continues
3289                self.switch_to_block(handler_normal_exit);
3290            }
3291
3292            // PopBlock for outer SETUP_CLEANUP (ExceptionHandler)
3293            emit!(self, PseudoInstruction::PopBlock);
3294            // Now pop ExceptionHandler - the normal path continues from here
3295            self.pop_fblock(FBlockType::ExceptionHandler);
3296            emit!(self, Instruction::PopExcept);
3297
3298            // Delete the exception variable if it was bound (normal path)
3299            if let Some(alias) = name {
3300                // Set the variable to None before deleting
3301                self.emit_load_const(ConstantData::None);
3302                self.store_name(alias.as_str())?;
3303                self.compile_name(alias.as_str(), NameUsage::Delete)?;
3304            }
3305
3306            // Pop FinallyTry block before jumping to finally body.
3307            // The else_block path also pops this; both paths must agree
3308            // on the except stack when entering finally_block.
3309            if !finalbody.is_empty() {
3310                emit!(self, PseudoInstruction::PopBlock);
3311            }
3312
3313            // Jump to finally block
3314            emit!(
3315                self,
3316                PseudoInstruction::JumpNoInterrupt {
3317                    delta: finally_block,
3318                }
3319            );
3320
3321            // Re-push ExceptionHandler for next handler in the loop
3322            // This will be popped at the end of handlers loop or when matched
3323            self.push_fblock(FBlockType::ExceptionHandler, cleanup_block, cleanup_block)?;
3324
3325            // Emit a new label for the next handler
3326            self.switch_to_block(next_handler);
3327        }
3328
3329        // If code flows here, we have an unhandled exception,
3330        // raise the exception again!
3331        // RERAISE 0
3332        // Stack: [prev_exc, exc] - exception is on stack from PUSH_EXC_INFO
3333        // NOTE: We emit RERAISE 0 BEFORE popping fblock so it is within cleanup handler scope
3334        emit!(self, Instruction::Reraise { depth: 0 });
3335
3336        // Pop EXCEPTION_HANDLER fblock
3337        // Pop after RERAISE so the instruction has the correct exception handler
3338        self.pop_fblock(FBlockType::ExceptionHandler);
3339
3340        // cleanup block (POP_EXCEPT_AND_RERAISE)
3341        // Stack at entry: [prev_exc, lasti, exc] (depth=1 + lasti + exc pushed)
3342        // COPY 3: copy prev_exc to top -> [prev_exc, lasti, exc, prev_exc]
3343        // POP_EXCEPT: pop prev_exc from stack and restore -> [prev_exc, lasti, exc]
3344        // RERAISE 1: reraise with lasti
3345        self.switch_to_block(cleanup_block);
3346        emit!(self, Instruction::Copy { i: 3 });
3347        emit!(self, Instruction::PopExcept);
3348        emit!(self, Instruction::Reraise { depth: 1 });
3349
3350        // We successfully ran the try block:
3351        // else:
3352        self.switch_to_block(else_block);
3353        self.compile_statements(orelse)?;
3354
3355        // Pop the FinallyTry fblock before jumping to finally
3356        if !finalbody.is_empty() {
3357            emit!(self, PseudoInstruction::PopBlock);
3358            self.pop_fblock(FBlockType::FinallyTry);
3359        }
3360
3361        // Snapshot sub_tables before first finally compilation (for double compilation issue)
3362        let sub_table_cursor = if !finalbody.is_empty() && finally_except_block.is_some() {
3363            self.symbol_table_stack.last().map(|t| t.next_sub_table)
3364        } else {
3365            None
3366        };
3367
3368        // finally (normal path):
3369        self.switch_to_block(finally_block);
3370        if !finalbody.is_empty() {
3371            self.compile_statements(finalbody)?;
3372            // Jump to end_block to skip exception path blocks
3373            // This prevents fall-through to finally_except_block
3374            emit!(self, PseudoInstruction::Jump { delta: end_block });
3375        }
3376
3377        // finally (exception path)
3378        // This is where exceptions go to run finally before reraise
3379        // Stack at entry: [lasti, exc] (from exception table with preserve_lasti=true)
3380        if let Some(finally_except) = finally_except_block {
3381            // Restore sub_tables for exception path compilation
3382            if let Some(cursor) = sub_table_cursor
3383                && let Some(current_table) = self.symbol_table_stack.last_mut()
3384            {
3385                current_table.next_sub_table = cursor;
3386            }
3387
3388            self.switch_to_block(finally_except);
3389
3390            // SETUP_CLEANUP for finally body
3391            // Exceptions during finally body need to go to cleanup block
3392            if let Some(cleanup) = finally_cleanup_block {
3393                emit!(self, PseudoInstruction::SetupCleanup { delta: cleanup });
3394            }
3395            emit!(self, Instruction::PushExcInfo);
3396            if let Some(cleanup) = finally_cleanup_block {
3397                self.push_fblock(FBlockType::FinallyEnd, cleanup, cleanup)?;
3398            }
3399
3400            // Run finally body
3401            self.compile_statements(finalbody)?;
3402
3403            // Pop FinallyEnd fblock BEFORE emitting RERAISE
3404            // This ensures RERAISE routes to outer exception handler, not cleanup block
3405            // Cleanup block is only for new exceptions raised during finally body execution
3406            if finally_cleanup_block.is_some() {
3407                emit!(self, PseudoInstruction::PopBlock);
3408                self.pop_fblock(FBlockType::FinallyEnd);
3409            }
3410
3411            // Restore prev_exc as current exception before RERAISE
3412            // Stack: [lasti, prev_exc, exc] -> COPY 2 -> [lasti, prev_exc, exc, prev_exc]
3413            // POP_EXCEPT pops prev_exc and sets exc_info->exc_value = prev_exc
3414            // Stack after POP_EXCEPT: [lasti, prev_exc, exc]
3415            emit!(self, Instruction::Copy { i: 2 });
3416            emit!(self, Instruction::PopExcept);
3417
3418            // RERAISE 0: re-raise the original exception to outer handler
3419            // Stack: [lasti, prev_exc, exc] - exception is on top
3420            emit!(self, Instruction::Reraise { depth: 0 });
3421        }
3422
3423        // finally cleanup block
3424        // This handles exceptions that occur during the finally body itself
3425        // Stack at entry: [lasti, prev_exc, lasti2, exc2] after exception table routing
3426        if let Some(cleanup) = finally_cleanup_block {
3427            self.switch_to_block(cleanup);
3428            // COPY 3: copy the exception from position 3
3429            emit!(self, Instruction::Copy { i: 3 });
3430            // POP_EXCEPT: restore prev_exc as current exception
3431            emit!(self, Instruction::PopExcept);
3432            // RERAISE 1: reraise with lasti from stack
3433            emit!(self, Instruction::Reraise { depth: 1 });
3434        }
3435
3436        // End block - continuation point after try-finally
3437        // Normal execution continues here after the finally block
3438        self.switch_to_block(end_block);
3439
3440        Ok(())
3441    }
3442
3443    fn compile_try_except_no_finally(
3444        &mut self,
3445        body: &[ast::Stmt],
3446        handlers: &[ast::ExceptHandler],
3447        orelse: &[ast::Stmt],
3448    ) -> CompileResult<()> {
3449        let handler_block = self.new_block();
3450        let cleanup_block = self.new_block();
3451        let orelse_block = self.new_block();
3452        let end_block = self.new_block();
3453
3454        emit!(self, Instruction::Nop);
3455        emit!(
3456            self,
3457            PseudoInstruction::SetupFinally {
3458                delta: handler_block
3459            }
3460        );
3461
3462        self.push_fblock(FBlockType::TryExcept, handler_block, handler_block)?;
3463        self.compile_statements(body)?;
3464        self.pop_fblock(FBlockType::TryExcept);
3465        emit!(self, PseudoInstruction::PopBlock);
3466        self.set_no_location();
3467        emit!(
3468            self,
3469            PseudoInstruction::JumpNoInterrupt {
3470                delta: orelse_block
3471            }
3472        );
3473        self.set_no_location();
3474
3475        self.switch_to_block(handler_block);
3476        emit!(
3477            self,
3478            PseudoInstruction::SetupCleanup {
3479                delta: cleanup_block
3480            }
3481        );
3482        self.set_no_location();
3483        emit!(self, Instruction::PushExcInfo);
3484        self.set_no_location();
3485        self.push_fblock(FBlockType::ExceptionHandler, cleanup_block, cleanup_block)?;
3486
3487        for handler in handlers {
3488            let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
3489                type_,
3490                name,
3491                body,
3492                range: handler_range,
3493                ..
3494            }) = handler;
3495            self.set_source_range(*handler_range);
3496            let next_handler = self.new_block();
3497
3498            if let Some(exc_type) = type_ {
3499                self.compile_expression(exc_type)?;
3500                emit!(self, Instruction::CheckExcMatch);
3501                emit!(
3502                    self,
3503                    Instruction::PopJumpIfFalse {
3504                        delta: next_handler
3505                    }
3506                );
3507            }
3508
3509            if let Some(alias) = name {
3510                self.store_name(alias.as_str())?;
3511
3512                let cleanup_end = self.new_block();
3513                let handler_normal_exit = self.new_block();
3514                emit!(self, PseudoInstruction::SetupCleanup { delta: cleanup_end });
3515                self.push_fblock_full(
3516                    FBlockType::HandlerCleanup,
3517                    cleanup_end,
3518                    cleanup_end,
3519                    FBlockDatum::ExceptionName(alias.as_str().to_owned()),
3520                )?;
3521
3522                self.compile_statements(body)?;
3523
3524                self.pop_fblock(FBlockType::HandlerCleanup);
3525                emit!(self, PseudoInstruction::PopBlock);
3526                self.set_no_location();
3527                emit!(
3528                    self,
3529                    PseudoInstruction::JumpNoInterrupt {
3530                        delta: handler_normal_exit
3531                    }
3532                );
3533                self.set_no_location();
3534
3535                self.switch_to_block(cleanup_end);
3536                self.emit_load_const(ConstantData::None);
3537                self.set_no_location();
3538                self.store_name(alias.as_str())?;
3539                self.set_no_location();
3540                self.compile_name(alias.as_str(), NameUsage::Delete)?;
3541                self.set_no_location();
3542                emit!(self, Instruction::Reraise { depth: 1 });
3543                self.set_no_location();
3544
3545                self.switch_to_block(handler_normal_exit);
3546                emit!(self, PseudoInstruction::PopBlock);
3547                self.set_no_location();
3548                self.pop_fblock(FBlockType::ExceptionHandler);
3549                emit!(self, Instruction::PopExcept);
3550                self.set_no_location();
3551
3552                self.emit_load_const(ConstantData::None);
3553                self.set_no_location();
3554                self.store_name(alias.as_str())?;
3555                self.set_no_location();
3556                self.compile_name(alias.as_str(), NameUsage::Delete)?;
3557                self.set_no_location();
3558
3559                emit!(
3560                    self,
3561                    PseudoInstruction::JumpNoInterrupt { delta: end_block }
3562                );
3563                self.set_no_location();
3564            } else {
3565                emit!(self, Instruction::PopTop);
3566                self.push_fblock(FBlockType::HandlerCleanup, end_block, end_block)?;
3567
3568                self.compile_statements(body)?;
3569
3570                self.pop_fblock(FBlockType::HandlerCleanup);
3571                emit!(self, PseudoInstruction::PopBlock);
3572                self.set_no_location();
3573                self.pop_fblock(FBlockType::ExceptionHandler);
3574                emit!(self, Instruction::PopExcept);
3575                self.set_no_location();
3576                emit!(
3577                    self,
3578                    PseudoInstruction::JumpNoInterrupt { delta: end_block }
3579                );
3580                self.set_no_location();
3581            }
3582
3583            self.push_fblock(FBlockType::ExceptionHandler, cleanup_block, cleanup_block)?;
3584            self.switch_to_block(next_handler);
3585        }
3586
3587        emit!(self, Instruction::Reraise { depth: 0 });
3588        self.set_no_location();
3589        self.pop_fblock(FBlockType::ExceptionHandler);
3590
3591        self.switch_to_block(cleanup_block);
3592        emit!(self, Instruction::Copy { i: 3 });
3593        self.set_no_location();
3594        emit!(self, Instruction::PopExcept);
3595        self.set_no_location();
3596        emit!(self, Instruction::Reraise { depth: 1 });
3597        self.set_no_location();
3598
3599        self.switch_to_block(orelse_block);
3600        self.set_no_location();
3601        self.compile_statements(orelse)?;
3602        emit!(
3603            self,
3604            PseudoInstruction::JumpNoInterrupt { delta: end_block }
3605        );
3606        self.set_no_location();
3607
3608        self.switch_to_block(end_block);
3609        Ok(())
3610    }
3611
3612    fn compile_try_star_except(
3613        &mut self,
3614        body: &[ast::Stmt],
3615        handlers: &[ast::ExceptHandler],
3616        orelse: &[ast::Stmt],
3617        finalbody: &[ast::Stmt],
3618    ) -> CompileResult<()> {
3619        // compiler_try_star_except
3620        // Stack layout during handler processing: [prev_exc, orig, list, rest]
3621        let handler_block = self.new_block();
3622        let finally_block = self.new_block();
3623        let else_block = self.new_block();
3624        let end_block = self.new_block();
3625        let reraise_star_block = self.new_block();
3626        let reraise_block = self.new_block();
3627        let finally_cleanup_block = if !finalbody.is_empty() {
3628            Some(self.new_block())
3629        } else {
3630            None
3631        };
3632        let exit_block = self.new_block();
3633
3634        // Emit NOP at the try: line so LINE events fire for it
3635        emit!(self, Instruction::Nop);
3636
3637        // Push fblock with handler info for exception table generation
3638        if !finalbody.is_empty() {
3639            emit!(
3640                self,
3641                PseudoInstruction::SetupFinally {
3642                    delta: finally_block
3643                }
3644            );
3645            self.push_fblock_full(
3646                FBlockType::FinallyTry,
3647                finally_block,
3648                finally_block,
3649                FBlockDatum::FinallyBody(finalbody.to_vec()),
3650            )?;
3651        }
3652
3653        // SETUP_FINALLY for try body
3654        emit!(
3655            self,
3656            PseudoInstruction::SetupFinally {
3657                delta: handler_block
3658            }
3659        );
3660        self.push_fblock(FBlockType::TryExcept, handler_block, handler_block)?;
3661        self.compile_statements(body)?;
3662        emit!(self, PseudoInstruction::PopBlock);
3663        self.pop_fblock(FBlockType::TryExcept);
3664        emit!(self, PseudoInstruction::Jump { delta: else_block });
3665
3666        // Exception handler entry
3667        self.switch_to_block(handler_block);
3668        // Stack: [exc] (from exception table)
3669
3670        // PUSH_EXC_INFO
3671        emit!(self, Instruction::PushExcInfo);
3672        // Stack: [prev_exc, exc]
3673
3674        // Push EXCEPTION_GROUP_HANDLER fblock
3675        let eg_dummy1 = self.new_block();
3676        let eg_dummy2 = self.new_block();
3677        self.push_fblock(FBlockType::ExceptionGroupHandler, eg_dummy1, eg_dummy2)?;
3678
3679        // Initialize handler stack before the loop
3680        // BUILD_LIST 0 + COPY 2 to set up [prev_exc, orig, list, rest]
3681        emit!(self, Instruction::BuildList { count: 0 });
3682        // Stack: [prev_exc, exc, []]
3683        emit!(self, Instruction::Copy { i: 2 });
3684        // Stack: [prev_exc, orig, list, rest]
3685
3686        let n = handlers.len();
3687        if n == 0 {
3688            // Empty handlers (invalid AST) - append rest to list and proceed
3689            // Stack: [prev_exc, orig, list, rest]
3690            emit!(self, Instruction::ListAppend { i: 1 });
3691            // Stack: [prev_exc, orig, list]
3692            emit!(
3693                self,
3694                PseudoInstruction::Jump {
3695                    delta: reraise_star_block
3696                }
3697            );
3698        }
3699        for (i, handler) in handlers.iter().enumerate() {
3700            let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
3701                type_,
3702                name,
3703                body,
3704                ..
3705            }) = handler;
3706
3707            let no_match_block = self.new_block();
3708            let next_block = self.new_block();
3709
3710            // Compile exception type
3711            if let Some(exc_type) = type_ {
3712                // Check for unparenthesized tuple
3713                if let ast::Expr::Tuple(ast::ExprTuple { elts, range, .. }) = exc_type.as_ref()
3714                    && let Some(first) = elts.first()
3715                    && range.start().to_u32() == first.range().start().to_u32()
3716                {
3717                    return Err(self.error(CodegenErrorType::SyntaxError(
3718                        "multiple exception types must be parenthesized".to_owned(),
3719                    )));
3720                }
3721                self.compile_expression(exc_type)?;
3722            } else {
3723                return Err(self.error(CodegenErrorType::SyntaxError(
3724                    "except* must specify an exception type".to_owned(),
3725                )));
3726            }
3727            // Stack: [prev_exc, orig, list, rest, type]
3728
3729            // ADDOP(c, loc, CHECK_EG_MATCH);
3730            emit!(self, Instruction::CheckEgMatch);
3731            // Stack: [prev_exc, orig, list, new_rest, match]
3732
3733            // ADDOP_I(c, loc, COPY, 1);
3734            // ADDOP_JUMP(c, loc, POP_JUMP_IF_NONE, no_match);
3735            emit!(self, Instruction::Copy { i: 1 });
3736            emit!(
3737                self,
3738                Instruction::PopJumpIfNone {
3739                    delta: no_match_block
3740                }
3741            );
3742
3743            // Handler matched
3744            // Stack: [prev_exc, orig, list, new_rest, match]
3745            // Note: CheckEgMatch already sets the matched exception as current exception
3746            let handler_except_block = self.new_block();
3747
3748            // Store match to name or pop
3749            if let Some(alias) = name {
3750                self.store_name(alias.as_str())?;
3751            } else {
3752                emit!(self, Instruction::PopTop); // pop match
3753            }
3754            // Stack: [prev_exc, orig, list, new_rest]
3755
3756            // HANDLER_CLEANUP fblock for handler body
3757            emit!(
3758                self,
3759                PseudoInstruction::SetupCleanup {
3760                    delta: handler_except_block
3761                }
3762            );
3763            self.push_fblock_full(
3764                FBlockType::HandlerCleanup,
3765                next_block,
3766                end_block,
3767                if let Some(alias) = name {
3768                    FBlockDatum::ExceptionName(alias.as_str().to_owned())
3769                } else {
3770                    FBlockDatum::None
3771                },
3772            )?;
3773
3774            // Execute handler body
3775            self.compile_statements(body)?;
3776
3777            // Handler body completed normally
3778            emit!(self, PseudoInstruction::PopBlock);
3779            self.pop_fblock(FBlockType::HandlerCleanup);
3780
3781            // Cleanup name binding
3782            if let Some(alias) = name {
3783                self.emit_load_const(ConstantData::None);
3784                self.store_name(alias.as_str())?;
3785                self.compile_name(alias.as_str(), NameUsage::Delete)?;
3786            }
3787
3788            // Jump to next handler
3789            emit!(self, PseudoInstruction::Jump { delta: next_block });
3790
3791            // Handler raised an exception (cleanup_end label)
3792            self.switch_to_block(handler_except_block);
3793            // Stack: [prev_exc, orig, list, new_rest, lasti, raised_exc]
3794            // (lasti is pushed because push_lasti=true in HANDLER_CLEANUP fblock)
3795
3796            // Cleanup name binding
3797            if let Some(alias) = name {
3798                self.emit_load_const(ConstantData::None);
3799                self.store_name(alias.as_str())?;
3800                self.compile_name(alias.as_str(), NameUsage::Delete)?;
3801            }
3802
3803            // LIST_APPEND(3) - append raised_exc to list
3804            // Stack: [prev_exc, orig, list, new_rest, lasti, raised_exc]
3805            // After pop: [prev_exc, orig, list, new_rest, lasti] (len=5)
3806            // nth_value(i) = stack[len - i - 1], we need stack[2] = list
3807            // stack[5 - i - 1] = 2 -> i = 2
3808            emit!(self, Instruction::ListAppend { i: 3 });
3809            // Stack: [prev_exc, orig, list, new_rest, lasti]
3810
3811            // POP_TOP - pop lasti
3812            emit!(self, Instruction::PopTop);
3813            // Stack: [prev_exc, orig, list, new_rest]
3814
3815            // JUMP except_with_error
3816            // We directly JUMP to next_block since no_match_block falls through to it
3817            emit!(self, PseudoInstruction::Jump { delta: next_block });
3818
3819            // No match - pop match (None)
3820            self.switch_to_block(no_match_block);
3821            emit!(self, Instruction::PopTop); // pop match (None)
3822            // Stack: [prev_exc, orig, list, new_rest]
3823            // Falls through to next_block
3824
3825            // except_with_error label
3826            // All paths merge here at next_block
3827            self.switch_to_block(next_block);
3828            // Stack: [prev_exc, orig, list, rest]
3829
3830            // After last handler, append rest to list
3831            if i == n - 1 {
3832                // Stack: [prev_exc, orig, list, rest]
3833                // ADDOP_I(c, NO_LOCATION, LIST_APPEND, 1);
3834                // PEEK(1) = stack[len-1] after pop
3835                // RustPython nth_value(i) = stack[len-i-1] after pop
3836                // For LIST_APPEND 1: stack[len-1] = stack[len-i-1] -> i = 0
3837                emit!(self, Instruction::ListAppend { i: 1 });
3838                // Stack: [prev_exc, orig, list]
3839                emit!(
3840                    self,
3841                    PseudoInstruction::Jump {
3842                        delta: reraise_star_block
3843                    }
3844                );
3845            }
3846        }
3847
3848        // Pop EXCEPTION_GROUP_HANDLER fblock
3849        self.pop_fblock(FBlockType::ExceptionGroupHandler);
3850
3851        // Reraise star block
3852        self.switch_to_block(reraise_star_block);
3853        // Stack: [prev_exc, orig, list]
3854
3855        // CALL_INTRINSIC_2 PREP_RERAISE_STAR
3856        // Takes 2 args (orig, list) and produces result
3857        emit!(
3858            self,
3859            Instruction::CallIntrinsic2 {
3860                func: bytecode::IntrinsicFunction2::PrepReraiseStar
3861            }
3862        );
3863        // Stack: [prev_exc, result]
3864
3865        // COPY 1
3866        emit!(self, Instruction::Copy { i: 1 });
3867        // Stack: [prev_exc, result, result]
3868
3869        // POP_JUMP_IF_NOT_NONE reraise
3870        emit!(
3871            self,
3872            Instruction::PopJumpIfNotNone {
3873                delta: reraise_block
3874            }
3875        );
3876        // Stack: [prev_exc, result]
3877
3878        // Nothing to reraise
3879        // POP_TOP - pop result (None)
3880        emit!(self, Instruction::PopTop);
3881        // Stack: [prev_exc]
3882
3883        // POP_BLOCK - no-op for us with exception tables (fblocks handle this)
3884        // POP_EXCEPT - restore previous exception context
3885        emit!(self, Instruction::PopExcept);
3886        // Stack: []
3887
3888        if !finalbody.is_empty() {
3889            emit!(self, PseudoInstruction::PopBlock);
3890            self.pop_fblock(FBlockType::FinallyTry);
3891        }
3892
3893        emit!(self, PseudoInstruction::Jump { delta: end_block });
3894
3895        // Reraise the result
3896        self.switch_to_block(reraise_block);
3897        // Stack: [prev_exc, result]
3898
3899        // POP_BLOCK - no-op for us
3900        // SWAP 2
3901        emit!(self, Instruction::Swap { i: 2 });
3902        // Stack: [result, prev_exc]
3903
3904        // POP_EXCEPT
3905        emit!(self, Instruction::PopExcept);
3906        // Stack: [result]
3907
3908        // RERAISE 0
3909        emit!(self, Instruction::Reraise { depth: 0 });
3910
3911        // try-else path
3912        // NOTE: When we reach here in compilation, the nothing-to-reraise path above
3913        // has already popped FinallyTry. But else_block is a different execution path
3914        // that branches from try body success (where FinallyTry is still active).
3915        // We need to re-push FinallyTry to reflect the correct fblock state for else path.
3916        if !finalbody.is_empty() {
3917            emit!(
3918                self,
3919                PseudoInstruction::SetupFinally {
3920                    delta: finally_block
3921                }
3922            );
3923            self.push_fblock_full(
3924                FBlockType::FinallyTry,
3925                finally_block,
3926                finally_block,
3927                FBlockDatum::FinallyBody(finalbody.to_vec()),
3928            )?;
3929        }
3930        self.switch_to_block(else_block);
3931        self.compile_statements(orelse)?;
3932
3933        if !finalbody.is_empty() {
3934            // Pop the FinallyTry fblock we just pushed for the else path
3935            emit!(self, PseudoInstruction::PopBlock);
3936            self.pop_fblock(FBlockType::FinallyTry);
3937        }
3938
3939        emit!(self, PseudoInstruction::Jump { delta: end_block });
3940
3941        self.switch_to_block(end_block);
3942        if !finalbody.is_empty() {
3943            // Snapshot sub_tables before first finally compilation
3944            let sub_table_cursor = self.symbol_table_stack.last().map(|t| t.next_sub_table);
3945
3946            // Compile finally body inline for normal path
3947            self.compile_statements(finalbody)?;
3948            emit!(self, PseudoInstruction::Jump { delta: exit_block });
3949
3950            // Restore sub_tables for exception path compilation
3951            if let Some(cursor) = sub_table_cursor
3952                && let Some(current_table) = self.symbol_table_stack.last_mut()
3953            {
3954                current_table.next_sub_table = cursor;
3955            }
3956
3957            // Exception handler path
3958            self.switch_to_block(finally_block);
3959            emit!(self, Instruction::PushExcInfo);
3960
3961            if let Some(cleanup) = finally_cleanup_block {
3962                emit!(self, PseudoInstruction::SetupCleanup { delta: cleanup });
3963                self.push_fblock(FBlockType::FinallyEnd, cleanup, cleanup)?;
3964            }
3965
3966            self.compile_statements(finalbody)?;
3967
3968            if finally_cleanup_block.is_some() {
3969                emit!(self, PseudoInstruction::PopBlock);
3970                self.pop_fblock(FBlockType::FinallyEnd);
3971            }
3972
3973            emit!(self, Instruction::Copy { i: 2 });
3974            emit!(self, Instruction::PopExcept);
3975            emit!(self, Instruction::Reraise { depth: 0 });
3976
3977            if let Some(cleanup) = finally_cleanup_block {
3978                self.switch_to_block(cleanup);
3979                emit!(self, Instruction::Copy { i: 3 });
3980                emit!(self, Instruction::PopExcept);
3981                emit!(self, Instruction::Reraise { depth: 1 });
3982            }
3983        }
3984
3985        self.switch_to_block(exit_block);
3986
3987        Ok(())
3988    }
3989
3990    /// Compile default arguments
3991    // = compiler_default_arguments
3992    fn compile_default_arguments(
3993        &mut self,
3994        parameters: &ast::Parameters,
3995    ) -> CompileResult<bytecode::MakeFunctionFlags> {
3996        let mut funcflags = bytecode::MakeFunctionFlags::new();
3997
3998        // Handle positional defaults
3999        let defaults: Vec<_> = core::iter::empty()
4000            .chain(&parameters.posonlyargs)
4001            .chain(&parameters.args)
4002            .filter_map(|x| x.default.as_deref())
4003            .collect();
4004
4005        if !defaults.is_empty() {
4006            // Compile defaults and build tuple
4007            for default in &defaults {
4008                self.compile_expression(default)?;
4009            }
4010            emit!(
4011                self,
4012                Instruction::BuildTuple {
4013                    count: defaults.len().to_u32()
4014                }
4015            );
4016            funcflags.insert(bytecode::MakeFunctionFlag::Defaults);
4017        }
4018
4019        // Handle keyword-only defaults
4020        let mut kw_with_defaults = vec![];
4021        for kwonlyarg in &parameters.kwonlyargs {
4022            if let Some(default) = &kwonlyarg.default {
4023                kw_with_defaults.push((&kwonlyarg.parameter, default));
4024            }
4025        }
4026
4027        if !kw_with_defaults.is_empty() {
4028            // Compile kwdefaults and build dict
4029            for (arg, default) in &kw_with_defaults {
4030                self.emit_load_const(ConstantData::Str {
4031                    value: self.mangle(arg.name.as_str()).into_owned().into(),
4032                });
4033                self.compile_expression(default)?;
4034            }
4035            emit!(
4036                self,
4037                Instruction::BuildMap {
4038                    count: kw_with_defaults.len().to_u32(),
4039                }
4040            );
4041            funcflags.insert(bytecode::MakeFunctionFlag::KwOnlyDefaults);
4042        }
4043
4044        Ok(funcflags)
4045    }
4046
4047    /// Compile function body and create function object
4048    // = compiler_function_body
4049    fn compile_function_body(
4050        &mut self,
4051        name: &str,
4052        parameters: &ast::Parameters,
4053        body: &[ast::Stmt],
4054        is_async: bool,
4055        funcflags: bytecode::MakeFunctionFlags,
4056    ) -> CompileResult<()> {
4057        // Save source range so MAKE_FUNCTION gets the `def` line, not the body's last line
4058        let saved_range = self.current_source_range;
4059
4060        // Always enter function scope
4061        self.enter_function(name, parameters)?;
4062        self.current_code_info()
4063            .flags
4064            .set(bytecode::CodeFlags::COROUTINE, is_async);
4065
4066        // Set up context
4067        let prev_ctx = self.ctx;
4068        self.ctx = CompileContext {
4069            loop_data: None,
4070            in_class: prev_ctx.in_class,
4071            func: if is_async {
4072                FunctionContext::AsyncFunction
4073            } else {
4074                FunctionContext::Function
4075            },
4076            // A function starts a new async scope only if it's async
4077            in_async_scope: is_async,
4078        };
4079
4080        // Set qualname
4081        self.set_qualname();
4082
4083        // PEP 479: Wrap generator/coroutine body with StopIteration handler
4084        let is_gen = is_async || self.current_symbol_table().is_generator;
4085        let stop_iteration_block = if is_gen {
4086            let handler_block = self.new_block();
4087            emit!(
4088                self,
4089                PseudoInstruction::SetupCleanup {
4090                    delta: handler_block
4091                }
4092            );
4093            self.set_no_location();
4094            self.push_fblock(FBlockType::StopIteration, handler_block, handler_block)?;
4095            Some(handler_block)
4096        } else {
4097            None
4098        };
4099
4100        // Handle docstring - store in co_consts[0] if present
4101        let (doc_str, body) = split_doc(body, &self.opts);
4102        if let Some(doc) = &doc_str {
4103            // Docstring present: store in co_consts[0] and set HAS_DOCSTRING flag
4104            self.current_code_info()
4105                .metadata
4106                .consts
4107                .insert_full(ConstantData::Str {
4108                    value: doc.to_string().into(),
4109                });
4110            self.current_code_info().flags |= bytecode::CodeFlags::HAS_DOCSTRING;
4111        }
4112        // Compile body statements
4113        self.compile_statements(body)?;
4114
4115        // Emit implicit `return None` if the body doesn't end with return.
4116        // Also ensure None is in co_consts even when not emitting return
4117        // (matching CPython: functions without explicit constants always
4118        // have None in co_consts).
4119        match body.last() {
4120            Some(ast::Stmt::Return(_)) => {}
4121            _ => {
4122                self.emit_return_const(ConstantData::None);
4123            }
4124        }
4125        // Functions with no other constants should still have None in co_consts
4126        if self.current_code_info().metadata.consts.is_empty() {
4127            self.arg_constant(ConstantData::None);
4128        }
4129
4130        // Close StopIteration handler and emit handler code
4131        if let Some(handler_block) = stop_iteration_block {
4132            emit!(self, PseudoInstruction::PopBlock);
4133            self.set_no_location();
4134            self.pop_fblock(FBlockType::StopIteration);
4135            self.switch_to_block(handler_block);
4136            emit!(
4137                self,
4138                Instruction::CallIntrinsic1 {
4139                    func: oparg::IntrinsicFunction1::StopIterationError
4140                }
4141            );
4142            self.set_no_location();
4143            emit!(self, Instruction::Reraise { depth: 1u32 });
4144            self.set_no_location();
4145        }
4146
4147        // Exit scope and create function object
4148        let code = self.exit_scope();
4149        self.ctx = prev_ctx;
4150
4151        self.set_source_range(saved_range);
4152
4153        // Create function object with closure
4154        self.make_closure(code, funcflags)?;
4155
4156        // Note: docstring is now retrieved from co_consts[0] by the VM
4157        // when HAS_DOCSTRING flag is set, so no runtime __doc__ assignment needed
4158
4159        Ok(())
4160    }
4161
4162    /// Compile function annotations as a closure (PEP 649)
4163    /// Returns true if an __annotate__ closure was created
4164    /// Uses symbol table's annotation_block for proper scoping.
4165    fn compile_annotations_closure(
4166        &mut self,
4167        func_name: &str,
4168        parameters: &ast::Parameters,
4169        returns: Option<&ast::Expr>,
4170    ) -> CompileResult<bool> {
4171        // Try to enter annotation scope - returns None if no annotation_block exists
4172        let Some(saved_ctx) = self.enter_annotation_scope(func_name)? else {
4173            return Ok(false);
4174        };
4175
4176        // Count annotations
4177        let parameters_iter = core::iter::empty()
4178            .chain(&parameters.posonlyargs)
4179            .chain(&parameters.args)
4180            .chain(&parameters.kwonlyargs)
4181            .map(|x| &x.parameter)
4182            .chain(parameters.vararg.as_deref())
4183            .chain(parameters.kwarg.as_deref());
4184
4185        let num_annotations: u32 =
4186            u32::try_from(parameters_iter.filter(|p| p.annotation.is_some()).count())
4187                .expect("too many annotations")
4188                + if returns.is_some() { 1 } else { 0 };
4189
4190        // Compile annotations inside the annotation scope
4191        let parameters_iter = core::iter::empty()
4192            .chain(&parameters.posonlyargs)
4193            .chain(&parameters.args)
4194            .chain(&parameters.kwonlyargs)
4195            .map(|x| &x.parameter)
4196            .chain(parameters.vararg.as_deref())
4197            .chain(parameters.kwarg.as_deref());
4198
4199        for param in parameters_iter {
4200            if let Some(annotation) = &param.annotation {
4201                self.emit_load_const(ConstantData::Str {
4202                    value: self.mangle(param.name.as_str()).into_owned().into(),
4203                });
4204                self.compile_annotation(annotation)?;
4205            }
4206        }
4207
4208        // Handle return annotation
4209        if let Some(annotation) = returns {
4210            self.emit_load_const(ConstantData::Str {
4211                value: "return".into(),
4212            });
4213            self.compile_annotation(annotation)?;
4214        }
4215
4216        // Build the map and return it
4217        emit!(
4218            self,
4219            Instruction::BuildMap {
4220                count: num_annotations,
4221            }
4222        );
4223        emit!(self, Instruction::ReturnValue);
4224
4225        // Exit the annotation scope and get the code object
4226        let annotate_code = self.exit_annotation_scope(saved_ctx);
4227
4228        // Make a closure from the code object
4229        self.make_closure(annotate_code, bytecode::MakeFunctionFlags::new())?;
4230
4231        Ok(true)
4232    }
4233
4234    /// Collect simple annotations from module body in AST order (including nested blocks)
4235    /// Returns list of (name, annotation_expr) pairs
4236    /// This must match the order that annotations are compiled to ensure
4237    /// conditional_annotation_index stays in sync with __annotate__ enumeration.
4238    fn collect_simple_annotations(body: &[ast::Stmt]) -> Vec<(&str, &ast::Expr)> {
4239        fn walk<'a>(stmts: &'a [ast::Stmt], out: &mut Vec<(&'a str, &'a ast::Expr)>) {
4240            for stmt in stmts {
4241                match stmt {
4242                    ast::Stmt::AnnAssign(ast::StmtAnnAssign {
4243                        target,
4244                        annotation,
4245                        simple,
4246                        ..
4247                    }) if *simple && matches!(target.as_ref(), ast::Expr::Name(_)) => {
4248                        if let ast::Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
4249                            out.push((id.as_str(), annotation.as_ref()));
4250                        }
4251                    }
4252                    ast::Stmt::If(ast::StmtIf {
4253                        body,
4254                        elif_else_clauses,
4255                        ..
4256                    }) => {
4257                        walk(body, out);
4258                        for clause in elif_else_clauses {
4259                            walk(&clause.body, out);
4260                        }
4261                    }
4262                    ast::Stmt::For(ast::StmtFor { body, orelse, .. })
4263                    | ast::Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
4264                        walk(body, out);
4265                        walk(orelse, out);
4266                    }
4267                    ast::Stmt::With(ast::StmtWith { body, .. }) => walk(body, out),
4268                    ast::Stmt::Try(ast::StmtTry {
4269                        body,
4270                        handlers,
4271                        orelse,
4272                        finalbody,
4273                        ..
4274                    }) => {
4275                        walk(body, out);
4276                        for handler in handlers {
4277                            let ast::ExceptHandler::ExceptHandler(
4278                                ast::ExceptHandlerExceptHandler { body, .. },
4279                            ) = handler;
4280                            walk(body, out);
4281                        }
4282                        walk(orelse, out);
4283                        walk(finalbody, out);
4284                    }
4285                    ast::Stmt::Match(ast::StmtMatch { cases, .. }) => {
4286                        for case in cases {
4287                            walk(&case.body, out);
4288                        }
4289                    }
4290                    _ => {}
4291                }
4292            }
4293        }
4294        let mut annotations = Vec::new();
4295        walk(body, &mut annotations);
4296        annotations
4297    }
4298
4299    /// Compile module-level __annotate__ function (PEP 649)
4300    /// Returns true if __annotate__ was created and stored
4301    fn compile_module_annotate(&mut self, body: &[ast::Stmt]) -> CompileResult<bool> {
4302        // Collect simple annotations from module body first
4303        let annotations = Self::collect_simple_annotations(body);
4304
4305        if annotations.is_empty() {
4306            return Ok(false);
4307        }
4308
4309        // Check if we have conditional annotations
4310        let has_conditional = self.current_symbol_table().has_conditional_annotations;
4311
4312        // Get parent scope type BEFORE pushing annotation symbol table
4313        let parent_scope_type = self.current_symbol_table().typ;
4314        // Try to push annotation symbol table from current scope
4315        if !self.push_current_annotation_symbol_table() {
4316            return Ok(false);
4317        }
4318
4319        // Annotation scopes are never async (even inside async functions)
4320        let saved_ctx = self.ctx;
4321        self.ctx = CompileContext {
4322            loop_data: None,
4323            in_class: saved_ctx.in_class,
4324            func: FunctionContext::Function,
4325            in_async_scope: false,
4326        };
4327
4328        // Enter annotation scope for code generation
4329        let key = self.symbol_table_stack.len() - 1;
4330        let lineno = self.get_source_line_number().get();
4331        self.enter_scope(
4332            "__annotate__",
4333            CompilerScope::Annotation,
4334            key,
4335            lineno.to_u32(),
4336        )?;
4337
4338        // Add 'format' parameter to varnames
4339        self.current_code_info()
4340            .metadata
4341            .varnames
4342            .insert("format".to_owned());
4343
4344        // Emit format validation: if format > VALUE_WITH_FAKE_GLOBALS: raise NotImplementedError
4345        self.emit_format_validation()?;
4346
4347        if has_conditional {
4348            // PEP 649: Build dict incrementally, checking conditional annotations
4349            // Start with empty dict
4350            emit!(self, Instruction::BuildMap { count: 0 });
4351
4352            // Process each annotation
4353            for (idx, (name, annotation)) in annotations.iter().enumerate() {
4354                // Check if index is in __conditional_annotations__
4355                let not_set_block = self.new_block();
4356
4357                // LOAD_CONST index
4358                self.emit_load_const(ConstantData::Integer { value: idx.into() });
4359                // Load __conditional_annotations__ from appropriate scope
4360                // Class scope: LoadDeref (freevars), Module scope: LoadGlobal
4361                if parent_scope_type == CompilerScope::Class {
4362                    let idx = self.get_free_var_index("__conditional_annotations__")?;
4363                    emit!(self, Instruction::LoadDeref { i: idx });
4364                } else {
4365                    let cond_annotations_name = self.name("__conditional_annotations__");
4366                    self.emit_load_global(cond_annotations_name, false);
4367                }
4368                // CONTAINS_OP (in)
4369                emit!(
4370                    self,
4371                    Instruction::ContainsOp {
4372                        invert: bytecode::Invert::No
4373                    }
4374                );
4375                // POP_JUMP_IF_FALSE not_set
4376                emit!(
4377                    self,
4378                    Instruction::PopJumpIfFalse {
4379                        delta: not_set_block
4380                    }
4381                );
4382
4383                // Annotation value
4384                self.compile_annotation(annotation)?;
4385                // COPY dict to TOS
4386                emit!(self, Instruction::Copy { i: 2 });
4387                // LOAD_CONST name
4388                self.emit_load_const(ConstantData::Str {
4389                    value: self.mangle(name).into_owned().into(),
4390                });
4391                // STORE_SUBSCR - dict[name] = value
4392                emit!(self, Instruction::StoreSubscr);
4393
4394                // not_set label
4395                self.switch_to_block(not_set_block);
4396            }
4397
4398            // Return the dict
4399            emit!(self, Instruction::ReturnValue);
4400        } else {
4401            // No conditional annotations - use simple BuildMap
4402            let num_annotations = u32::try_from(annotations.len()).expect("too many annotations");
4403
4404            // Compile annotations inside the annotation scope
4405            for (name, annotation) in annotations {
4406                self.emit_load_const(ConstantData::Str {
4407                    value: self.mangle(name).into_owned().into(),
4408                });
4409                self.compile_annotation(annotation)?;
4410            }
4411
4412            // Build the map and return it
4413            emit!(
4414                self,
4415                Instruction::BuildMap {
4416                    count: num_annotations,
4417                }
4418            );
4419            emit!(self, Instruction::ReturnValue);
4420        }
4421
4422        // Exit annotation scope - pop symbol table, restore to parent's annotation_block, and get code
4423        let annotation_table = self.pop_symbol_table();
4424        // Restore annotation_block to module's symbol table
4425        self.symbol_table_stack
4426            .last_mut()
4427            .expect("no module symbol table")
4428            .annotation_block = Some(Box::new(annotation_table));
4429        // Restore context
4430        self.ctx = saved_ctx;
4431        // Exit code scope
4432        let pop = self.code_stack.pop();
4433        let annotate_code = unwrap_internal(
4434            self,
4435            compiler_unwrap_option(self, pop).finalize_code(&self.opts),
4436        );
4437
4438        // Make a closure from the code object
4439        self.make_closure(annotate_code, bytecode::MakeFunctionFlags::new())?;
4440
4441        // Store as __annotate_func__ for classes, __annotate__ for modules
4442        let name = if parent_scope_type == CompilerScope::Class {
4443            "__annotate_func__"
4444        } else {
4445            "__annotate__"
4446        };
4447        self.store_name(name)?;
4448
4449        Ok(true)
4450    }
4451
4452    // = compiler_function
4453    #[allow(clippy::too_many_arguments)]
4454    fn compile_function_def(
4455        &mut self,
4456        name: &str,
4457        parameters: &ast::Parameters,
4458        body: &[ast::Stmt],
4459        decorator_list: &[ast::Decorator],
4460        returns: Option<&ast::Expr>, // TODO: use type hint somehow..
4461        is_async: bool,
4462        type_params: Option<&ast::TypeParams>,
4463    ) -> CompileResult<()> {
4464        // Save the source range of the `def` line before compiling decorators/defaults,
4465        // so that the function code object gets the correct co_firstlineno.
4466        let def_source_range = self.current_source_range;
4467
4468        self.prepare_decorators(decorator_list)?;
4469
4470        // compile defaults and return funcflags
4471        let funcflags = self.compile_default_arguments(parameters)?;
4472
4473        // Restore the `def` line range so that enter_function → push_output → get_source_line_number()
4474        // records the `def` keyword's line as co_firstlineno, not the last default-argument line.
4475        self.set_source_range(def_source_range);
4476
4477        let is_generic = type_params.is_some();
4478        let mut num_typeparam_args = 0;
4479
4480        // Save context before entering TypeParams scope
4481        let saved_ctx = self.ctx;
4482
4483        if is_generic {
4484            // Count args to pass to type params scope
4485            if funcflags.contains(&bytecode::MakeFunctionFlag::Defaults) {
4486                num_typeparam_args += 1;
4487            }
4488            if funcflags.contains(&bytecode::MakeFunctionFlag::KwOnlyDefaults) {
4489                num_typeparam_args += 1;
4490            }
4491
4492            // Enter type params scope
4493            let type_params_name = format!("<generic parameters of {name}>");
4494            self.push_output(
4495                bytecode::CodeFlags::OPTIMIZED | bytecode::CodeFlags::NEWLOCALS,
4496                0,
4497                num_typeparam_args as u32,
4498                0,
4499                type_params_name,
4500            )?;
4501
4502            // TypeParams scope is function-like
4503            self.ctx = CompileContext {
4504                loop_data: None,
4505                in_class: saved_ctx.in_class,
4506                func: FunctionContext::Function,
4507                in_async_scope: false,
4508            };
4509
4510            // Add parameter names to varnames for the type params scope
4511            // These will be passed as arguments when the closure is called
4512            let current_info = self.current_code_info();
4513            if funcflags.contains(&bytecode::MakeFunctionFlag::Defaults) {
4514                current_info
4515                    .metadata
4516                    .varnames
4517                    .insert(".defaults".to_owned());
4518            }
4519            if funcflags.contains(&bytecode::MakeFunctionFlag::KwOnlyDefaults) {
4520                current_info
4521                    .metadata
4522                    .varnames
4523                    .insert(".kwdefaults".to_owned());
4524            }
4525
4526            // Compile type parameters
4527            self.compile_type_params(type_params.unwrap())?;
4528
4529            // Load defaults/kwdefaults with LOAD_FAST
4530            for i in 0..num_typeparam_args {
4531                let var_num = oparg::VarNum::from(i as u32);
4532                emit!(self, Instruction::LoadFast { var_num });
4533            }
4534        }
4535
4536        // Compile annotations as closure (PEP 649)
4537        let mut annotations_flag = bytecode::MakeFunctionFlags::new();
4538        if self.compile_annotations_closure(name, parameters, returns)? {
4539            annotations_flag.insert(bytecode::MakeFunctionFlag::Annotate);
4540        }
4541
4542        // Compile function body
4543        let final_funcflags = funcflags | annotations_flag;
4544        self.compile_function_body(name, parameters, body, is_async, final_funcflags)?;
4545
4546        // Handle type params if present
4547        if is_generic {
4548            // SWAP to get function on top
4549            // Stack: [type_params_tuple, function] -> [function, type_params_tuple]
4550            emit!(self, Instruction::Swap { i: 2 });
4551
4552            // Call INTRINSIC_SET_FUNCTION_TYPE_PARAMS
4553            emit!(
4554                self,
4555                Instruction::CallIntrinsic2 {
4556                    func: bytecode::IntrinsicFunction2::SetFunctionTypeParams,
4557                }
4558            );
4559
4560            // Return the function object from type params scope
4561            emit!(self, Instruction::ReturnValue);
4562
4563            // Set argcount for type params scope
4564            self.current_code_info().metadata.argcount = num_typeparam_args as u32;
4565
4566            // Exit type params scope and create closure
4567            let type_params_code = self.exit_scope();
4568            self.ctx = saved_ctx;
4569
4570            // Make closure for type params code
4571            self.make_closure(type_params_code, bytecode::MakeFunctionFlags::new())?;
4572
4573            // Call the type params closure with defaults/kwdefaults as arguments.
4574            // Call protocol: [callable, self_or_null, arg1, ..., argN]
4575            // We need to reorder: [args..., closure] -> [closure, NULL, args...]
4576            // Using Swap operations to move closure down and insert NULL.
4577            // Note: num_typeparam_args is at most 2 (defaults tuple, kwdefaults dict).
4578            if num_typeparam_args > 0 {
4579                match num_typeparam_args {
4580                    1 => {
4581                        // Stack: [arg1, closure]
4582                        emit!(self, Instruction::Swap { i: 2 }); // [closure, arg1]
4583                        emit!(self, Instruction::PushNull); // [closure, arg1, NULL]
4584                        emit!(self, Instruction::Swap { i: 2 }); // [closure, NULL, arg1]
4585                    }
4586                    2 => {
4587                        // Stack: [arg1, arg2, closure]
4588                        emit!(self, Instruction::Swap { i: 3 }); // [closure, arg2, arg1]
4589                        emit!(self, Instruction::Swap { i: 2 }); // [closure, arg1, arg2]
4590                        emit!(self, Instruction::PushNull); // [closure, arg1, arg2, NULL]
4591                        emit!(self, Instruction::Swap { i: 3 }); // [closure, NULL, arg2, arg1]
4592                        emit!(self, Instruction::Swap { i: 2 }); // [closure, NULL, arg1, arg2]
4593                    }
4594                    _ => unreachable!("only defaults and kwdefaults are supported"),
4595                }
4596                emit!(
4597                    self,
4598                    Instruction::Call {
4599                        argc: num_typeparam_args as u32
4600                    }
4601                );
4602            } else {
4603                // Stack: [closure]
4604                emit!(self, Instruction::PushNull);
4605                // Stack: [closure, NULL]
4606                emit!(self, Instruction::Call { argc: 0 });
4607            }
4608        }
4609
4610        // Apply decorators
4611        self.apply_decorators(decorator_list);
4612
4613        // Store the function
4614        self.store_name(name)?;
4615
4616        Ok(())
4617    }
4618
4619    /// Determines if a variable should be CELL or FREE type
4620    // = get_ref_type
4621    fn get_ref_type(&self, name: &str) -> Result<SymbolScope, CodegenErrorType> {
4622        let table = self.symbol_table_stack.last().unwrap();
4623
4624        // Special handling for __class__, __classdict__, and __conditional_annotations__ in class scope
4625        // This should only apply when we're actually IN a class body,
4626        // not when we're in a method nested inside a class.
4627        if table.typ == CompilerScope::Class
4628            && (name == "__class__"
4629                || name == "__classdict__"
4630                || name == "__conditional_annotations__")
4631        {
4632            return Ok(SymbolScope::Cell);
4633        }
4634        match table.lookup(name) {
4635            Some(symbol) => match symbol.scope {
4636                SymbolScope::Cell => Ok(SymbolScope::Cell),
4637                SymbolScope::Free => Ok(SymbolScope::Free),
4638                _ if symbol.flags.contains(SymbolFlags::FREE_CLASS) => Ok(SymbolScope::Free),
4639                _ => Err(CodegenErrorType::SyntaxError(format!(
4640                    "get_ref_type: invalid scope for '{name}'"
4641                ))),
4642            },
4643            None => Err(CodegenErrorType::SyntaxError(format!(
4644                "get_ref_type: cannot find symbol '{name}'"
4645            ))),
4646        }
4647    }
4648
4649    /// Loads closure variables if needed and creates a function object
4650    // = compiler_make_closure
4651    fn make_closure(
4652        &mut self,
4653        code: CodeObject,
4654        flags: bytecode::MakeFunctionFlags,
4655    ) -> CompileResult<()> {
4656        // Handle free variables (closure)
4657        let has_freevars = !code.freevars.is_empty();
4658        if has_freevars {
4659            // Build closure tuple by loading free variables
4660
4661            for var in &code.freevars {
4662                // Special case: If a class contains a method with a
4663                // free variable that has the same name as a method,
4664                // the name will be considered free *and* local in the
4665                // class. It should be handled by the closure, as
4666                // well as by the normal name lookup logic.
4667
4668                // Get reference type using our get_ref_type function
4669                let ref_type = self.get_ref_type(var).map_err(|e| self.error(e))?;
4670
4671                // Get parent code info
4672                let parent_code = self.code_stack.last().unwrap();
4673                let cellvars_len = parent_code.metadata.cellvars.len();
4674
4675                // Look up the variable index based on reference type
4676                let idx = match ref_type {
4677                    SymbolScope::Cell => parent_code
4678                        .metadata
4679                        .cellvars
4680                        .get_index_of(var)
4681                        .or_else(|| {
4682                            parent_code
4683                                .metadata
4684                                .freevars
4685                                .get_index_of(var)
4686                                .map(|i| i + cellvars_len)
4687                        })
4688                        .ok_or_else(|| {
4689                            self.error(CodegenErrorType::SyntaxError(format!(
4690                                "compiler_make_closure: cannot find '{var}' in parent vars",
4691                            )))
4692                        })?,
4693                    SymbolScope::Free => parent_code
4694                        .metadata
4695                        .freevars
4696                        .get_index_of(var)
4697                        .map(|i| i + cellvars_len)
4698                        .or_else(|| parent_code.metadata.cellvars.get_index_of(var))
4699                        .ok_or_else(|| {
4700                            self.error(CodegenErrorType::SyntaxError(format!(
4701                                "compiler_make_closure: cannot find '{var}' in parent vars",
4702                            )))
4703                        })?,
4704                    _ => {
4705                        return Err(self.error(CodegenErrorType::SyntaxError(format!(
4706                            "compiler_make_closure: unexpected ref_type {ref_type:?} for '{var}'",
4707                        ))));
4708                    }
4709                };
4710
4711                emit!(self, PseudoInstruction::LoadClosure { i: idx.to_u32() });
4712            }
4713
4714            // Build tuple of closure variables
4715            emit!(
4716                self,
4717                Instruction::BuildTuple {
4718                    count: code.freevars.len().to_u32(),
4719                }
4720            );
4721        }
4722
4723        // load code object and create function
4724        self.emit_load_const(ConstantData::Code {
4725            code: Box::new(code),
4726        });
4727
4728        // Create function with no flags
4729        emit!(self, Instruction::MakeFunction);
4730
4731        // Now set attributes one by one using SET_FUNCTION_ATTRIBUTE
4732        // Note: The order matters! Values must be on stack before calling SET_FUNCTION_ATTRIBUTE
4733
4734        // Set closure if needed
4735        if has_freevars {
4736            emit!(
4737                self,
4738                Instruction::SetFunctionAttribute {
4739                    flag: bytecode::MakeFunctionFlag::Closure
4740                }
4741            );
4742        }
4743
4744        // Set annotations if present
4745        if flags.contains(&bytecode::MakeFunctionFlag::Annotations) {
4746            emit!(
4747                self,
4748                Instruction::SetFunctionAttribute {
4749                    flag: bytecode::MakeFunctionFlag::Annotations
4750                }
4751            );
4752        }
4753
4754        // Set __annotate__ closure if present (PEP 649)
4755        if flags.contains(&bytecode::MakeFunctionFlag::Annotate) {
4756            emit!(
4757                self,
4758                Instruction::SetFunctionAttribute {
4759                    flag: bytecode::MakeFunctionFlag::Annotate
4760                }
4761            );
4762        }
4763
4764        // Set kwdefaults if present
4765        if flags.contains(&bytecode::MakeFunctionFlag::KwOnlyDefaults) {
4766            emit!(
4767                self,
4768                Instruction::SetFunctionAttribute {
4769                    flag: bytecode::MakeFunctionFlag::KwOnlyDefaults
4770                }
4771            );
4772        }
4773
4774        // Set defaults if present
4775        if flags.contains(&bytecode::MakeFunctionFlag::Defaults) {
4776            emit!(
4777                self,
4778                Instruction::SetFunctionAttribute {
4779                    flag: bytecode::MakeFunctionFlag::Defaults
4780                }
4781            );
4782        }
4783
4784        // Set type_params if present
4785        if flags.contains(&bytecode::MakeFunctionFlag::TypeParams) {
4786            emit!(
4787                self,
4788                Instruction::SetFunctionAttribute {
4789                    flag: bytecode::MakeFunctionFlag::TypeParams
4790                }
4791            );
4792        }
4793
4794        Ok(())
4795    }
4796
4797    /// Collect attribute names assigned via `self.xxx = ...` in methods.
4798    /// These are stored as __static_attributes__ in the class dict.
4799    fn collect_static_attributes(body: &[ast::Stmt], attrs: Option<&mut IndexSet<String>>) {
4800        let Some(attrs) = attrs else { return };
4801        for stmt in body {
4802            let f = match stmt {
4803                ast::Stmt::FunctionDef(f) => f,
4804                _ => continue,
4805            };
4806            // Skip @staticmethod and @classmethod decorated functions
4807            let has_special_decorator = f.decorator_list.iter().any(|d| {
4808                matches!(&d.expression, ast::Expr::Name(n)
4809                    if n.id.as_str() == "staticmethod" || n.id.as_str() == "classmethod")
4810            });
4811            if has_special_decorator {
4812                continue;
4813            }
4814            // Skip implicit classmethods (__init_subclass__, __class_getitem__)
4815            let fname = f.name.as_str();
4816            if fname == "__init_subclass__" || fname == "__class_getitem__" {
4817                continue;
4818            }
4819            // For __new__, scan for "self" (not the first param "cls")
4820            if fname == "__new__" {
4821                Self::scan_store_attrs(&f.body, "self", attrs);
4822                continue;
4823            }
4824            let first_param = f
4825                .parameters
4826                .posonlyargs
4827                .first()
4828                .or(f.parameters.args.first())
4829                .map(|p| &p.parameter.name);
4830            let Some(self_name) = first_param else {
4831                continue;
4832            };
4833            Self::scan_store_attrs(&f.body, self_name.as_str(), attrs);
4834        }
4835    }
4836
4837    /// Extract self.attr patterns from an assignment target expression.
4838    fn scan_target_for_attrs(target: &ast::Expr, name: &str, attrs: &mut IndexSet<String>) {
4839        match target {
4840            ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
4841                if let ast::Expr::Name(n) = value.as_ref()
4842                    && n.id.as_str() == name
4843                {
4844                    attrs.insert(attr.to_string());
4845                }
4846            }
4847            ast::Expr::Tuple(t) => {
4848                for elt in &t.elts {
4849                    Self::scan_target_for_attrs(elt, name, attrs);
4850                }
4851            }
4852            ast::Expr::List(l) => {
4853                for elt in &l.elts {
4854                    Self::scan_target_for_attrs(elt, name, attrs);
4855                }
4856            }
4857            ast::Expr::Starred(s) => {
4858                Self::scan_target_for_attrs(&s.value, name, attrs);
4859            }
4860            _ => {}
4861        }
4862    }
4863
4864    /// Recursively scan statements for `name.attr = value` patterns.
4865    fn scan_store_attrs(stmts: &[ast::Stmt], name: &str, attrs: &mut IndexSet<String>) {
4866        for stmt in stmts {
4867            match stmt {
4868                ast::Stmt::Assign(a) => {
4869                    for target in &a.targets {
4870                        Self::scan_target_for_attrs(target, name, attrs);
4871                    }
4872                }
4873                ast::Stmt::AnnAssign(a) => {
4874                    Self::scan_target_for_attrs(&a.target, name, attrs);
4875                }
4876                ast::Stmt::AugAssign(a) => {
4877                    if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) =
4878                        a.target.as_ref()
4879                        && let ast::Expr::Name(n) = value.as_ref()
4880                        && n.id.as_str() == name
4881                    {
4882                        attrs.insert(attr.to_string());
4883                    }
4884                }
4885                ast::Stmt::If(s) => {
4886                    Self::scan_store_attrs(&s.body, name, attrs);
4887                    for clause in &s.elif_else_clauses {
4888                        Self::scan_store_attrs(&clause.body, name, attrs);
4889                    }
4890                }
4891                ast::Stmt::For(s) => {
4892                    Self::scan_store_attrs(&s.body, name, attrs);
4893                    Self::scan_store_attrs(&s.orelse, name, attrs);
4894                }
4895                ast::Stmt::While(s) => {
4896                    Self::scan_store_attrs(&s.body, name, attrs);
4897                    Self::scan_store_attrs(&s.orelse, name, attrs);
4898                }
4899                ast::Stmt::Try(s) => {
4900                    Self::scan_store_attrs(&s.body, name, attrs);
4901                    for handler in &s.handlers {
4902                        let ast::ExceptHandler::ExceptHandler(h) = handler;
4903                        Self::scan_store_attrs(&h.body, name, attrs);
4904                    }
4905                    Self::scan_store_attrs(&s.orelse, name, attrs);
4906                    Self::scan_store_attrs(&s.finalbody, name, attrs);
4907                }
4908                ast::Stmt::With(s) => {
4909                    Self::scan_store_attrs(&s.body, name, attrs);
4910                }
4911                ast::Stmt::Match(s) => {
4912                    for case in &s.cases {
4913                        Self::scan_store_attrs(&case.body, name, attrs);
4914                    }
4915                }
4916                _ => {}
4917            }
4918        }
4919    }
4920
4921    // Python/compile.c find_ann
4922    fn find_ann(body: &[ast::Stmt]) -> bool {
4923        for statement in body {
4924            let res = match &statement {
4925                ast::Stmt::AnnAssign(_) => true,
4926                ast::Stmt::For(ast::StmtFor { body, orelse, .. }) => {
4927                    Self::find_ann(body) || Self::find_ann(orelse)
4928                }
4929                ast::Stmt::If(ast::StmtIf {
4930                    body,
4931                    elif_else_clauses,
4932                    ..
4933                }) => {
4934                    Self::find_ann(body)
4935                        || elif_else_clauses.iter().any(|x| Self::find_ann(&x.body))
4936                }
4937                ast::Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
4938                    Self::find_ann(body) || Self::find_ann(orelse)
4939                }
4940                ast::Stmt::With(ast::StmtWith { body, .. }) => Self::find_ann(body),
4941                ast::Stmt::Match(ast::StmtMatch { cases, .. }) => {
4942                    cases.iter().any(|case| Self::find_ann(&case.body))
4943                }
4944                ast::Stmt::Try(ast::StmtTry {
4945                    body,
4946                    handlers,
4947                    orelse,
4948                    finalbody,
4949                    ..
4950                }) => {
4951                    Self::find_ann(body)
4952                        || handlers.iter().any(|h| {
4953                            let ast::ExceptHandler::ExceptHandler(
4954                                ast::ExceptHandlerExceptHandler { body, .. },
4955                            ) = h;
4956                            Self::find_ann(body)
4957                        })
4958                        || Self::find_ann(orelse)
4959                        || Self::find_ann(finalbody)
4960                }
4961                _ => false,
4962            };
4963            if res {
4964                return true;
4965            }
4966        }
4967        false
4968    }
4969
4970    /// Compile the class body into a code object
4971    // = compiler_class_body
4972    fn compile_class_body(
4973        &mut self,
4974        name: &str,
4975        body: &[ast::Stmt],
4976        type_params: Option<&ast::TypeParams>,
4977        firstlineno: u32,
4978    ) -> CompileResult<CodeObject> {
4979        // 1. Enter class scope
4980        let key = self.symbol_table_stack.len();
4981        self.push_symbol_table()?;
4982        self.enter_scope(name, CompilerScope::Class, key, firstlineno)?;
4983
4984        // Set qualname using the new method
4985        let qualname = self.set_qualname();
4986
4987        // For class scopes, set u_private to the class name for name mangling
4988        self.code_stack.last_mut().unwrap().private = Some(name.to_owned());
4989
4990        // 2. Set up class namespace
4991        let (doc_str, body) = split_doc(body, &self.opts);
4992
4993        // Load __name__ and store as __module__
4994        let dunder_name = self.name("__name__");
4995        emit!(self, Instruction::LoadName { namei: dunder_name });
4996        let dunder_module = self.name("__module__");
4997        emit!(
4998            self,
4999            Instruction::StoreName {
5000                namei: dunder_module
5001            }
5002        );
5003
5004        // Store __qualname__
5005        self.emit_load_const(ConstantData::Str {
5006            value: qualname.into(),
5007        });
5008        let qualname_name = self.name("__qualname__");
5009        emit!(
5010            self,
5011            Instruction::StoreName {
5012                namei: qualname_name
5013            }
5014        );
5015
5016        // Store __firstlineno__ before __doc__
5017        self.emit_load_const(ConstantData::Integer {
5018            value: BigInt::from(firstlineno),
5019        });
5020        let firstlineno_name = self.name("__firstlineno__");
5021        emit!(
5022            self,
5023            Instruction::StoreName {
5024                namei: firstlineno_name
5025            }
5026        );
5027
5028        // PEP 649: Initialize __classdict__ cell (before __doc__)
5029        if self.current_symbol_table().needs_classdict {
5030            emit!(self, Instruction::LoadLocals);
5031            let classdict_idx = self.get_cell_var_index("__classdict__")?;
5032            emit!(self, Instruction::StoreDeref { i: classdict_idx });
5033        }
5034
5035        // Store __doc__ only if there's an explicit docstring
5036        if let Some(doc) = doc_str {
5037            self.emit_load_const(ConstantData::Str { value: doc.into() });
5038            let doc_name = self.name("__doc__");
5039            emit!(self, Instruction::StoreName { namei: doc_name });
5040        }
5041
5042        // Set __type_params__ if we have type parameters
5043        if type_params.is_some() {
5044            // Load .type_params from enclosing scope
5045            let dot_type_params = self.name(".type_params");
5046            emit!(
5047                self,
5048                Instruction::LoadName {
5049                    namei: dot_type_params
5050                }
5051            );
5052
5053            // Store as __type_params__
5054            let dunder_type_params = self.name("__type_params__");
5055            emit!(
5056                self,
5057                Instruction::StoreName {
5058                    namei: dunder_type_params
5059                }
5060            );
5061        }
5062
5063        // Handle class annotations based on future_annotations flag
5064        if Self::find_ann(body) {
5065            if self.future_annotations {
5066                // PEP 563: Initialize __annotations__ dict for class
5067                emit!(self, Instruction::SetupAnnotations);
5068            } else {
5069                // PEP 649: Initialize __conditional_annotations__ set if needed for class
5070                if self.current_symbol_table().has_conditional_annotations {
5071                    emit!(self, Instruction::BuildSet { count: 0 });
5072                    self.store_name("__conditional_annotations__")?;
5073                }
5074
5075                // PEP 649: Generate __annotate__ function for class annotations
5076                self.compile_module_annotate(body)?;
5077            }
5078        }
5079
5080        // Collect __static_attributes__: scan methods for self.xxx = ... patterns
5081        Self::collect_static_attributes(
5082            body,
5083            self.code_stack
5084                .last_mut()
5085                .unwrap()
5086                .static_attributes
5087                .as_mut(),
5088        );
5089
5090        // 3. Compile the class body
5091        self.compile_statements(body)?;
5092
5093        // 4. Handle __classcell__ if needed
5094        let classcell_idx = self
5095            .code_stack
5096            .last_mut()
5097            .unwrap()
5098            .metadata
5099            .cellvars
5100            .iter()
5101            .position(|var| *var == "__class__");
5102
5103        // Emit __static_attributes__ tuple
5104        {
5105            let mut attrs: Vec<String> = self
5106                .code_stack
5107                .last()
5108                .unwrap()
5109                .static_attributes
5110                .as_ref()
5111                .map(|s| s.iter().cloned().collect())
5112                .unwrap_or_default();
5113            attrs.sort();
5114            self.emit_load_const(ConstantData::Tuple {
5115                elements: attrs
5116                    .into_iter()
5117                    .map(|s| ConstantData::Str { value: s.into() })
5118                    .collect(),
5119            });
5120            let static_attrs_name = self.name("__static_attributes__");
5121            emit!(
5122                self,
5123                Instruction::StoreName {
5124                    namei: static_attrs_name
5125                }
5126            );
5127        }
5128
5129        // Store __classdictcell__ if __classdict__ is a cell variable
5130        if self.current_symbol_table().needs_classdict {
5131            let classdict_idx = u32::from(self.get_cell_var_index("__classdict__")?);
5132            emit!(self, PseudoInstruction::LoadClosure { i: classdict_idx });
5133            let classdictcell = self.name("__classdictcell__");
5134            emit!(
5135                self,
5136                Instruction::StoreName {
5137                    namei: classdictcell
5138                }
5139            );
5140        }
5141
5142        if let Some(classcell_idx) = classcell_idx {
5143            emit!(
5144                self,
5145                PseudoInstruction::LoadClosure {
5146                    i: classcell_idx.to_u32()
5147                }
5148            );
5149            emit!(self, Instruction::Copy { i: 1 });
5150            let classcell = self.name("__classcell__");
5151            emit!(self, Instruction::StoreName { namei: classcell });
5152        } else {
5153            self.emit_load_const(ConstantData::None);
5154        }
5155
5156        // Return the class namespace
5157        self.emit_return_value();
5158
5159        // Exit scope and return the code object
5160        Ok(self.exit_scope())
5161    }
5162
5163    fn compile_class_def(
5164        &mut self,
5165        name: &str,
5166        body: &[ast::Stmt],
5167        decorator_list: &[ast::Decorator],
5168        type_params: Option<&ast::TypeParams>,
5169        arguments: Option<&ast::Arguments>,
5170    ) -> CompileResult<()> {
5171        self.prepare_decorators(decorator_list)?;
5172
5173        let is_generic = type_params.is_some();
5174        let firstlineno = self.get_source_line_number().get().to_u32();
5175
5176        // Save context before entering any scopes
5177        let saved_ctx = self.ctx;
5178
5179        // Step 1: If generic, enter type params scope and compile type params
5180        if is_generic {
5181            let type_params_name = format!("<generic parameters of {name}>");
5182            self.push_output(
5183                bytecode::CodeFlags::OPTIMIZED | bytecode::CodeFlags::NEWLOCALS,
5184                0,
5185                0,
5186                0,
5187                type_params_name,
5188            )?;
5189
5190            // Set private name for name mangling
5191            self.code_stack.last_mut().unwrap().private = Some(name.to_owned());
5192
5193            // TypeParams scope is function-like
5194            self.ctx = CompileContext {
5195                loop_data: None,
5196                in_class: saved_ctx.in_class,
5197                func: FunctionContext::Function,
5198                in_async_scope: false,
5199            };
5200
5201            // Compile type parameters and store as .type_params
5202            self.compile_type_params(type_params.unwrap())?;
5203            let dot_type_params = self.name(".type_params");
5204            emit!(
5205                self,
5206                Instruction::StoreName {
5207                    namei: dot_type_params
5208                }
5209            );
5210        }
5211
5212        // Step 2: Compile class body (always done, whether generic or not)
5213        let prev_ctx = self.ctx;
5214        self.ctx = CompileContext {
5215            func: FunctionContext::NoFunction,
5216            in_class: true,
5217            loop_data: None,
5218            in_async_scope: false,
5219        };
5220        let class_code = self.compile_class_body(name, body, type_params, firstlineno)?;
5221        self.ctx = prev_ctx;
5222
5223        // Step 3: Generate the rest of the code for the call
5224        if is_generic {
5225            // Still in type params scope
5226            let dot_type_params = self.name(".type_params");
5227            let dot_generic_base = self.name(".generic_base");
5228
5229            // Create .generic_base
5230            emit!(
5231                self,
5232                Instruction::LoadName {
5233                    namei: dot_type_params
5234                }
5235            );
5236            emit!(
5237                self,
5238                Instruction::CallIntrinsic1 {
5239                    func: bytecode::IntrinsicFunction1::SubscriptGeneric
5240                }
5241            );
5242            emit!(
5243                self,
5244                Instruction::StoreName {
5245                    namei: dot_generic_base
5246                }
5247            );
5248
5249            // Generate class creation code
5250            emit!(self, Instruction::LoadBuildClass);
5251            emit!(self, Instruction::PushNull);
5252
5253            // Set up the class function with type params
5254            let mut func_flags = bytecode::MakeFunctionFlags::new();
5255            emit!(
5256                self,
5257                Instruction::LoadName {
5258                    namei: dot_type_params
5259                }
5260            );
5261            func_flags.insert(bytecode::MakeFunctionFlag::TypeParams);
5262
5263            // Create class function with closure
5264            self.make_closure(class_code, func_flags)?;
5265            self.emit_load_const(ConstantData::Str { value: name.into() });
5266
5267            // Compile bases and call __build_class__
5268            // Check for starred bases or **kwargs
5269            let has_starred = arguments.is_some_and(|args| {
5270                args.args
5271                    .iter()
5272                    .any(|arg| matches!(arg, ast::Expr::Starred(_)))
5273            });
5274            let has_double_star =
5275                arguments.is_some_and(|args| args.keywords.iter().any(|kw| kw.arg.is_none()));
5276
5277            if has_starred || has_double_star {
5278                // Use CallFunctionEx for *bases or **kwargs
5279                // Stack has: [__build_class__, NULL, class_func, name]
5280                // Need to build: args tuple = (class_func, name, *bases, .generic_base)
5281
5282                // Build a list starting with class_func and name (2 elements already on stack)
5283                emit!(self, Instruction::BuildList { count: 2 });
5284
5285                // Add bases to the list
5286                if let Some(arguments) = arguments {
5287                    for arg in &arguments.args {
5288                        if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = arg {
5289                            // Starred: compile and extend
5290                            self.compile_expression(value)?;
5291                            emit!(self, Instruction::ListExtend { i: 1 });
5292                        } else {
5293                            // Non-starred: compile and append
5294                            self.compile_expression(arg)?;
5295                            emit!(self, Instruction::ListAppend { i: 1 });
5296                        }
5297                    }
5298                }
5299
5300                // Add .generic_base as final element
5301                emit!(
5302                    self,
5303                    Instruction::LoadName {
5304                        namei: dot_generic_base
5305                    }
5306                );
5307                emit!(self, Instruction::ListAppend { i: 1 });
5308
5309                // Convert list to tuple
5310                emit!(
5311                    self,
5312                    Instruction::CallIntrinsic1 {
5313                        func: IntrinsicFunction1::ListToTuple
5314                    }
5315                );
5316
5317                // Build kwargs if needed
5318                if arguments.is_some_and(|args| !args.keywords.is_empty()) {
5319                    self.compile_keywords(&arguments.unwrap().keywords)?;
5320                } else {
5321                    emit!(self, Instruction::PushNull);
5322                }
5323                emit!(self, Instruction::CallFunctionEx);
5324            } else {
5325                // Simple case: no starred bases, no **kwargs
5326                // Compile bases normally
5327                let base_count = if let Some(arguments) = arguments {
5328                    for arg in &arguments.args {
5329                        self.compile_expression(arg)?;
5330                    }
5331                    arguments.args.len()
5332                } else {
5333                    0
5334                };
5335
5336                // Load .generic_base as the last base
5337                emit!(
5338                    self,
5339                    Instruction::LoadName {
5340                        namei: dot_generic_base
5341                    }
5342                );
5343
5344                let nargs = 2 + u32::try_from(base_count).expect("too many base classes") + 1;
5345
5346                // Handle keyword arguments (no **kwargs here)
5347                if let Some(arguments) = arguments
5348                    && !arguments.keywords.is_empty()
5349                {
5350                    let mut kwarg_names = vec![];
5351                    for keyword in &arguments.keywords {
5352                        let name = keyword.arg.as_ref().expect(
5353                            "keyword argument name must be set (no **kwargs in this branch)",
5354                        );
5355                        kwarg_names.push(ConstantData::Str {
5356                            value: name.as_str().into(),
5357                        });
5358                        self.compile_expression(&keyword.value)?;
5359                    }
5360                    self.emit_load_const(ConstantData::Tuple {
5361                        elements: kwarg_names,
5362                    });
5363                    emit!(
5364                        self,
5365                        Instruction::CallKw {
5366                            argc: nargs
5367                                + u32::try_from(arguments.keywords.len())
5368                                    .expect("too many keyword arguments")
5369                        }
5370                    );
5371                } else {
5372                    emit!(self, Instruction::Call { argc: nargs });
5373                }
5374            }
5375
5376            // Return the created class
5377            self.emit_return_value();
5378
5379            // Exit type params scope and wrap in function
5380            let type_params_code = self.exit_scope();
5381            self.ctx = saved_ctx;
5382
5383            // Execute the type params function
5384            self.make_closure(type_params_code, bytecode::MakeFunctionFlags::new())?;
5385            emit!(self, Instruction::PushNull);
5386            emit!(self, Instruction::Call { argc: 0 });
5387        } else {
5388            // Non-generic class: standard path
5389            emit!(self, Instruction::LoadBuildClass);
5390            emit!(self, Instruction::PushNull);
5391
5392            // Create class function with closure
5393            self.make_closure(class_code, bytecode::MakeFunctionFlags::new())?;
5394            self.emit_load_const(ConstantData::Str { value: name.into() });
5395
5396            if let Some(arguments) = arguments {
5397                self.codegen_call_helper(2, arguments, self.current_source_range)?;
5398            } else {
5399                emit!(self, Instruction::Call { argc: 2 });
5400            }
5401        }
5402
5403        // Step 4: Apply decorators and store (common to both paths)
5404        self.apply_decorators(decorator_list);
5405        self.store_name(name)
5406    }
5407
5408    /// Compile an if statement with constant condition elimination.
5409    /// = compiler_if in CPython codegen.c
5410    fn compile_if(
5411        &mut self,
5412        test: &ast::Expr,
5413        body: &[ast::Stmt],
5414        elif_else_clauses: &[ast::ElifElseClause],
5415    ) -> CompileResult<()> {
5416        let constant = Self::expr_constant(test);
5417
5418        // If the test is constant false, walk the body (consuming sub_tables)
5419        // but don't emit bytecode
5420        if constant == Some(false) {
5421            self.emit_nop();
5422            self.do_not_emit_bytecode += 1;
5423            self.compile_statements(body)?;
5424            self.do_not_emit_bytecode -= 1;
5425            // Compile the elif/else chain (if any)
5426            match elif_else_clauses {
5427                [] => {}
5428                [first, rest @ ..] => {
5429                    if let Some(elif_test) = &first.test {
5430                        self.compile_if(elif_test, &first.body, rest)?;
5431                    } else {
5432                        self.compile_statements(&first.body)?;
5433                    }
5434                }
5435            }
5436            return Ok(());
5437        }
5438
5439        // If the test is constant true, compile body directly,
5440        // but walk elif/else without emitting (including elif tests to consume sub_tables)
5441        if constant == Some(true) {
5442            self.emit_nop();
5443            self.compile_statements(body)?;
5444            self.do_not_emit_bytecode += 1;
5445            for clause in elif_else_clauses {
5446                if let Some(elif_test) = &clause.test {
5447                    self.compile_expression(elif_test)?;
5448                }
5449                self.compile_statements(&clause.body)?;
5450            }
5451            self.do_not_emit_bytecode -= 1;
5452            return Ok(());
5453        }
5454
5455        // Non-constant test: normal compilation
5456        match elif_else_clauses {
5457            // Only if
5458            [] => {
5459                let after_block = self.new_block();
5460                self.compile_jump_if(test, false, after_block)?;
5461                self.compile_statements(body)?;
5462                self.switch_to_block(after_block);
5463            }
5464            // If, elif*, elif/else
5465            [rest @ .., tail] => {
5466                let after_block = self.new_block();
5467                let mut next_block = self.new_block();
5468
5469                self.compile_jump_if(test, false, next_block)?;
5470                self.compile_statements(body)?;
5471                emit!(self, PseudoInstruction::Jump { delta: after_block });
5472
5473                for clause in rest {
5474                    self.switch_to_block(next_block);
5475                    next_block = self.new_block();
5476                    if let Some(test) = &clause.test {
5477                        self.compile_jump_if(test, false, next_block)?;
5478                    } else {
5479                        unreachable!() // must be elif
5480                    }
5481                    self.compile_statements(&clause.body)?;
5482                    emit!(self, PseudoInstruction::Jump { delta: after_block });
5483                }
5484
5485                self.switch_to_block(next_block);
5486                if let Some(test) = &tail.test {
5487                    self.compile_jump_if(test, false, after_block)?;
5488                }
5489                self.compile_statements(&tail.body)?;
5490                self.switch_to_block(after_block);
5491            }
5492        }
5493        Ok(())
5494    }
5495
5496    fn compile_while(
5497        &mut self,
5498        test: &ast::Expr,
5499        body: &[ast::Stmt],
5500        orelse: &[ast::Stmt],
5501    ) -> CompileResult<()> {
5502        self.enter_conditional_block();
5503
5504        let constant = Self::expr_constant(test);
5505
5506        // while False: body → walk body (consuming sub_tables) but don't emit,
5507        // then compile orelse
5508        if constant == Some(false) {
5509            self.emit_nop();
5510            let while_block = self.new_block();
5511            let after_block = self.new_block();
5512            self.push_fblock(FBlockType::WhileLoop, while_block, after_block)?;
5513            self.do_not_emit_bytecode += 1;
5514            self.compile_statements(body)?;
5515            self.do_not_emit_bytecode -= 1;
5516            self.pop_fblock(FBlockType::WhileLoop);
5517            self.compile_statements(orelse)?;
5518            self.leave_conditional_block();
5519            return Ok(());
5520        }
5521
5522        let while_block = self.new_block();
5523        let else_block = self.new_block();
5524        let after_block = self.new_block();
5525
5526        self.switch_to_block(while_block);
5527        self.push_fblock(FBlockType::WhileLoop, while_block, after_block)?;
5528
5529        // while True: → no condition test, just NOP
5530        if constant == Some(true) {
5531            self.emit_nop();
5532        } else {
5533            self.compile_jump_if(test, false, else_block)?;
5534        }
5535
5536        let was_in_loop = self.ctx.loop_data.replace((while_block, after_block));
5537        self.compile_statements(body)?;
5538        self.ctx.loop_data = was_in_loop;
5539        emit!(self, PseudoInstruction::Jump { delta: while_block });
5540        self.switch_to_block(else_block);
5541
5542        self.pop_fblock(FBlockType::WhileLoop);
5543        self.compile_statements(orelse)?;
5544        self.switch_to_block(after_block);
5545
5546        self.leave_conditional_block();
5547        Ok(())
5548    }
5549
5550    fn compile_with(
5551        &mut self,
5552        items: &[ast::WithItem],
5553        body: &[ast::Stmt],
5554        is_async: bool,
5555    ) -> CompileResult<()> {
5556        self.enter_conditional_block();
5557
5558        // Python 3.12+ style with statement:
5559        //
5560        // BEFORE_WITH          # TOS: ctx_mgr -> [__exit__, __enter__ result]
5561        // L1: STORE_NAME f     # exception table: L1 to L2 -> L3 [1] lasti
5562        // L2: ... body ...
5563        //     LOAD_CONST None  # normal exit
5564        //     LOAD_CONST None
5565        //     LOAD_CONST None
5566        //     CALL 2           # __exit__(None, None, None)
5567        //     POP_TOP
5568        //     JUMP after
5569        // L3: PUSH_EXC_INFO    # exception handler
5570        //     WITH_EXCEPT_START # call __exit__(type, value, tb), push result
5571        //     TO_BOOL
5572        //     POP_JUMP_IF_TRUE suppress
5573        //     RERAISE 2
5574        // suppress:
5575        //     POP_TOP          # pop exit result
5576        // L5: POP_EXCEPT
5577        //     POP_TOP          # pop __exit__
5578        //     POP_TOP          # pop prev_exc (or lasti depending on layout)
5579        //     JUMP after
5580        // L6: COPY 3           # cleanup handler for reraise
5581        //     POP_EXCEPT
5582        //     RERAISE 1
5583        // after: ...
5584
5585        let with_range = self.current_source_range;
5586
5587        let Some((item, items)) = items.split_first() else {
5588            return Err(self.error(CodegenErrorType::EmptyWithItems));
5589        };
5590
5591        let exc_handler_block = self.new_block();
5592        let after_block = self.new_block();
5593
5594        // Compile context expression and load __enter__/__exit__ methods
5595        self.compile_expression(&item.context_expr)?;
5596        self.set_source_range(with_range);
5597
5598        // Stack: [cm]
5599        emit!(self, Instruction::Copy { i: 1 }); // [cm, cm]
5600
5601        if is_async {
5602            if self.ctx.func != FunctionContext::AsyncFunction {
5603                return Err(self.error(CodegenErrorType::InvalidAsyncWith));
5604            }
5605            // Load __aexit__ and __aenter__, then call __aenter__
5606            emit!(
5607                self,
5608                Instruction::LoadSpecial {
5609                    method: SpecialMethod::AExit
5610                }
5611            ); // [cm, aexit_func, self_ae]
5612            emit!(self, Instruction::Swap { i: 2 }); // [cm, self_ae, aexit_func]
5613            emit!(self, Instruction::Swap { i: 3 }); // [aexit_func, self_ae, cm]
5614            emit!(
5615                self,
5616                Instruction::LoadSpecial {
5617                    method: SpecialMethod::AEnter
5618                }
5619            ); // [aexit_func, self_ae, aenter_func, self_an]
5620            emit!(self, Instruction::Call { argc: 0 }); // [aexit_func, self_ae, awaitable]
5621            emit!(self, Instruction::GetAwaitable { r#where: 1 });
5622            self.emit_load_const(ConstantData::None);
5623            let _ = self.compile_yield_from_sequence(true)?;
5624        } else {
5625            // Load __exit__ and __enter__, then call __enter__
5626            emit!(
5627                self,
5628                Instruction::LoadSpecial {
5629                    method: SpecialMethod::Exit
5630                }
5631            ); // [cm, exit_func, self_exit]
5632            emit!(self, Instruction::Swap { i: 2 }); // [cm, self_exit, exit_func]
5633            emit!(self, Instruction::Swap { i: 3 }); // [exit_func, self_exit, cm]
5634            emit!(
5635                self,
5636                Instruction::LoadSpecial {
5637                    method: SpecialMethod::Enter
5638                }
5639            ); // [exit_func, self_exit, enter_func, self_enter]
5640            emit!(self, Instruction::Call { argc: 0 }); // [exit_func, self_exit, result]
5641        }
5642
5643        // Stack: [..., __exit__, enter_result]
5644        // Push fblock for exception table - handler goes to exc_handler_block
5645        // preserve_lasti=true for with statements
5646        emit!(
5647            self,
5648            PseudoInstruction::SetupWith {
5649                delta: exc_handler_block
5650            }
5651        );
5652        self.push_fblock(
5653            if is_async {
5654                FBlockType::AsyncWith
5655            } else {
5656                FBlockType::With
5657            },
5658            exc_handler_block, // block start (will become exit target after store)
5659            after_block,
5660        )?;
5661
5662        // Store or pop the enter result
5663        match &item.optional_vars {
5664            Some(var) => {
5665                self.set_source_range(var.range());
5666                self.compile_store(var)?;
5667            }
5668            None => {
5669                emit!(self, Instruction::PopTop);
5670            }
5671        }
5672        // Stack: [..., __exit__]
5673
5674        // Compile body or nested with
5675        if items.is_empty() {
5676            if body.is_empty() {
5677                return Err(self.error(CodegenErrorType::EmptyWithBody));
5678            }
5679            self.compile_statements(body)?;
5680        } else {
5681            self.set_source_range(with_range);
5682            self.compile_with(items, body, is_async)?;
5683        }
5684
5685        // Pop fblock before normal exit
5686        emit!(self, PseudoInstruction::PopBlock);
5687        self.pop_fblock(if is_async {
5688            FBlockType::AsyncWith
5689        } else {
5690            FBlockType::With
5691        });
5692
5693        // ===== Normal exit path =====
5694        // Stack: [..., exit_func, self_exit]
5695        // Call exit_func(self_exit, None, None, None)
5696        self.set_source_range(with_range);
5697        self.emit_load_const(ConstantData::None);
5698        self.emit_load_const(ConstantData::None);
5699        self.emit_load_const(ConstantData::None);
5700        emit!(self, Instruction::Call { argc: 3 });
5701        if is_async {
5702            emit!(self, Instruction::GetAwaitable { r#where: 2 });
5703            self.emit_load_const(ConstantData::None);
5704            let _ = self.compile_yield_from_sequence(true)?;
5705        }
5706        emit!(self, Instruction::PopTop); // Pop __exit__ result
5707        emit!(self, PseudoInstruction::Jump { delta: after_block });
5708
5709        // ===== Exception handler path =====
5710        // Stack at entry: [..., exit_func, self_exit, lasti, exc]
5711        // PUSH_EXC_INFO -> [..., exit_func, self_exit, lasti, prev_exc, exc]
5712        self.switch_to_block(exc_handler_block);
5713
5714        let cleanup_block = self.new_block();
5715        let suppress_block = self.new_block();
5716
5717        emit!(
5718            self,
5719            PseudoInstruction::SetupCleanup {
5720                delta: cleanup_block
5721            }
5722        );
5723        self.push_fblock(FBlockType::ExceptionHandler, exc_handler_block, after_block)?;
5724
5725        emit!(self, Instruction::PushExcInfo);
5726
5727        // WITH_EXCEPT_START: call exit_func(self_exit, type, value, tb)
5728        // Stack: [..., exit_func, self_exit, lasti, prev_exc, exc]
5729        emit!(self, Instruction::WithExceptStart);
5730
5731        if is_async {
5732            emit!(self, Instruction::GetAwaitable { r#where: 2 });
5733            self.emit_load_const(ConstantData::None);
5734            let _ = self.compile_yield_from_sequence(true)?;
5735        }
5736
5737        emit!(self, Instruction::ToBool);
5738        emit!(
5739            self,
5740            Instruction::PopJumpIfTrue {
5741                delta: suppress_block
5742            }
5743        );
5744
5745        emit!(self, PseudoInstruction::PopBlock);
5746        self.pop_fblock(FBlockType::ExceptionHandler);
5747
5748        emit!(self, Instruction::Reraise { depth: 2 });
5749
5750        // ===== Suppress block =====
5751        // Stack: [..., exit_func, self_exit, lasti, prev_exc, exc, True]
5752        self.switch_to_block(suppress_block);
5753        emit!(self, Instruction::PopTop); // pop True
5754        emit!(self, Instruction::PopExcept); // pop exc, restore prev_exc
5755        emit!(self, Instruction::PopTop); // pop lasti
5756        emit!(self, Instruction::PopTop); // pop self_exit
5757        emit!(self, Instruction::PopTop); // pop exit_func
5758        emit!(self, PseudoInstruction::Jump { delta: after_block });
5759
5760        // ===== Cleanup block (for nested exception during __exit__) =====
5761        // Stack: [..., __exit__, lasti, prev_exc, lasti2, exc2]
5762        // COPY 3: copy prev_exc to TOS
5763        // POP_EXCEPT: restore exception state
5764        // RERAISE 1: re-raise with lasti
5765        //
5766        // NOTE: We DON'T clear the fblock stack here because we want
5767        // outer exception handlers (e.g., try-except wrapping this with statement)
5768        // to be in the exception table for these instructions.
5769        // If we cleared fblock, exceptions here would propagate uncaught.
5770        self.switch_to_block(cleanup_block);
5771        emit!(self, Instruction::Copy { i: 3 });
5772        emit!(self, Instruction::PopExcept);
5773        emit!(self, Instruction::Reraise { depth: 1 });
5774
5775        // ===== After block =====
5776        self.switch_to_block(after_block);
5777
5778        self.leave_conditional_block();
5779        Ok(())
5780    }
5781
5782    fn compile_for(
5783        &mut self,
5784        target: &ast::Expr,
5785        iter: &ast::Expr,
5786        body: &[ast::Stmt],
5787        orelse: &[ast::Stmt],
5788        is_async: bool,
5789    ) -> CompileResult<()> {
5790        self.enter_conditional_block();
5791
5792        // Start loop
5793        let for_block = self.new_block();
5794        let else_block = self.new_block();
5795        let after_block = self.new_block();
5796        let mut end_async_for_target = BlockIdx::NULL;
5797
5798        // The thing iterated:
5799        // Optimize: `for x in [a, b, c]` → use tuple instead of list
5800        // Skip for async-for (GET_AITER expects the original type)
5801        if !is_async
5802            && let ast::Expr::List(ast::ExprList { elts, .. }) = iter
5803            && !elts.iter().any(|e| matches!(e, ast::Expr::Starred(_)))
5804        {
5805            for elt in elts {
5806                self.compile_expression(elt)?;
5807            }
5808            emit!(
5809                self,
5810                Instruction::BuildTuple {
5811                    count: u32::try_from(elts.len()).expect("too many elements"),
5812                }
5813            );
5814        } else {
5815            self.compile_expression(iter)?;
5816        }
5817
5818        if is_async {
5819            if self.ctx.func != FunctionContext::AsyncFunction {
5820                return Err(self.error(CodegenErrorType::InvalidAsyncFor));
5821            }
5822            emit!(self, Instruction::GetAIter);
5823
5824            self.switch_to_block(for_block);
5825
5826            // codegen_async_for: push fblock BEFORE SETUP_FINALLY
5827            self.push_fblock(FBlockType::ForLoop, for_block, after_block)?;
5828
5829            // SETUP_FINALLY to guard the __anext__ call
5830            emit!(self, PseudoInstruction::SetupFinally { delta: else_block });
5831            emit!(self, Instruction::GetANext);
5832            self.emit_load_const(ConstantData::None);
5833            end_async_for_target = self.compile_yield_from_sequence(true)?;
5834            // POP_BLOCK for SETUP_FINALLY - only GetANext/yield_from are protected
5835            emit!(self, PseudoInstruction::PopBlock);
5836            emit!(self, Instruction::NotTaken);
5837
5838            // Success block for __anext__
5839            self.compile_store(target)?;
5840        } else {
5841            // Retrieve Iterator
5842            emit!(self, Instruction::GetIter);
5843
5844            self.switch_to_block(for_block);
5845
5846            // Push fblock for for loop
5847            self.push_fblock(FBlockType::ForLoop, for_block, after_block)?;
5848
5849            emit!(self, Instruction::ForIter { delta: else_block });
5850
5851            // Start of loop iteration, set targets:
5852            self.compile_store(target)?;
5853        };
5854
5855        let was_in_loop = self.ctx.loop_data.replace((for_block, after_block));
5856        self.compile_statements(body)?;
5857        self.ctx.loop_data = was_in_loop;
5858        emit!(self, PseudoInstruction::Jump { delta: for_block });
5859
5860        self.switch_to_block(else_block);
5861
5862        // Except block for __anext__ / end of sync for
5863        // No PopBlock here - for async, POP_BLOCK is already in for_block
5864        self.pop_fblock(FBlockType::ForLoop);
5865
5866        // End-of-loop instructions are on the `for` line, not the body's last line
5867        let saved_range = self.current_source_range;
5868        self.set_source_range(iter.range());
5869        if is_async {
5870            self.emit_end_async_for(end_async_for_target);
5871        } else {
5872            emit!(self, Instruction::EndFor);
5873            emit!(self, Instruction::PopIter);
5874        }
5875        self.set_source_range(saved_range);
5876        self.compile_statements(orelse)?;
5877
5878        self.switch_to_block(after_block);
5879
5880        // Implicit return after for-loop should be attributed to the `for` line
5881        self.set_source_range(iter.range());
5882
5883        self.leave_conditional_block();
5884        Ok(())
5885    }
5886
5887    fn forbidden_name(&mut self, name: &str, ctx: NameUsage) -> CompileResult<bool> {
5888        if ctx == NameUsage::Store && name == "__debug__" {
5889            return Err(self.error(CodegenErrorType::Assign("__debug__")));
5890            // return Ok(true);
5891        }
5892        if ctx == NameUsage::Delete && name == "__debug__" {
5893            return Err(self.error(CodegenErrorType::Delete("__debug__")));
5894            // return Ok(true);
5895        }
5896        Ok(false)
5897    }
5898
5899    fn compile_error_forbidden_name(&mut self, name: &str) -> CodegenError {
5900        self.error(CodegenErrorType::SyntaxError(format!(
5901            "cannot use forbidden name '{name}' in pattern"
5902        )))
5903    }
5904
5905    /// Ensures that `pc.fail_pop` has at least `n + 1` entries.
5906    /// If not, new labels are generated and pushed until the required size is reached.
5907    fn ensure_fail_pop(&mut self, pc: &mut PatternContext, n: usize) -> CompileResult<()> {
5908        let required_size = n + 1;
5909        if required_size <= pc.fail_pop.len() {
5910            return Ok(());
5911        }
5912        while pc.fail_pop.len() < required_size {
5913            let new_block = self.new_block();
5914            pc.fail_pop.push(new_block);
5915        }
5916        Ok(())
5917    }
5918
5919    fn jump_to_fail_pop(&mut self, pc: &mut PatternContext, op: JumpOp) -> CompileResult<()> {
5920        // Compute the total number of items to pop:
5921        // items on top plus the captured objects.
5922        let pops = pc.on_top + pc.stores.len();
5923        // Ensure that the fail_pop vector has at least `pops + 1` elements.
5924        self.ensure_fail_pop(pc, pops)?;
5925        // Emit a jump using the jump target stored at index `pops`.
5926        match op {
5927            JumpOp::Jump => {
5928                emit!(
5929                    self,
5930                    PseudoInstruction::Jump {
5931                        delta: pc.fail_pop[pops]
5932                    }
5933                );
5934            }
5935            JumpOp::PopJumpIfFalse => {
5936                emit!(
5937                    self,
5938                    Instruction::PopJumpIfFalse {
5939                        delta: pc.fail_pop[pops]
5940                    }
5941                );
5942            }
5943        }
5944        Ok(())
5945    }
5946
5947    /// Emits the necessary POP instructions for all failure targets in the pattern context,
5948    /// then resets the fail_pop vector.
5949    fn emit_and_reset_fail_pop(&mut self, pc: &mut PatternContext) -> CompileResult<()> {
5950        // If the fail_pop vector is empty, nothing needs to be done.
5951        if pc.fail_pop.is_empty() {
5952            debug_assert!(pc.fail_pop.is_empty());
5953            return Ok(());
5954        }
5955        // Iterate over the fail_pop vector in reverse order, skipping the first label.
5956        for &label in pc.fail_pop.iter().skip(1).rev() {
5957            self.switch_to_block(label);
5958            // Emit the POP instruction.
5959            emit!(self, Instruction::PopTop);
5960        }
5961        // Finally, use the first label.
5962        self.switch_to_block(pc.fail_pop[0]);
5963        pc.fail_pop.clear();
5964        // Free the memory used by the vector.
5965        pc.fail_pop.shrink_to_fit();
5966        Ok(())
5967    }
5968
5969    /// Duplicate the effect of Python 3.10's ROT_* instructions using SWAPs.
5970    fn pattern_helper_rotate(&mut self, mut count: usize) -> CompileResult<()> {
5971        // Rotate TOS (top of stack) to position `count` down
5972        // This is done by a series of swaps
5973        // For count=1, no rotation needed (already at top)
5974        // For count=2, swap TOS with item 1 position down
5975        // For count=3, swap TOS with item 2 positions down, then with item 1 position down
5976        while count > 1 {
5977            // Emit a SWAP instruction with the current count.
5978            emit!(
5979                self,
5980                Instruction::Swap {
5981                    i: u32::try_from(count).unwrap()
5982                }
5983            );
5984            count -= 1;
5985        }
5986        Ok(())
5987    }
5988
5989    /// Helper to store a captured name for a star pattern.
5990    ///
5991    /// If `n` is `None`, it emits a POP_TOP instruction. Otherwise, it first
5992    /// checks that the name is allowed and not already stored. Then it rotates
5993    /// the object on the stack beneath any preserved items and appends the name
5994    /// to the list of captured names.
5995    fn pattern_helper_store_name(
5996        &mut self,
5997        n: Option<&ast::Identifier>,
5998        pc: &mut PatternContext,
5999    ) -> CompileResult<()> {
6000        match n {
6001            // If no name is provided, simply pop the top of the stack.
6002            None => {
6003                emit!(self, Instruction::PopTop);
6004                Ok(())
6005            }
6006            Some(name) => {
6007                // Check if the name is forbidden for storing.
6008                if self.forbidden_name(name.as_str(), NameUsage::Store)? {
6009                    return Err(self.compile_error_forbidden_name(name.as_str()));
6010                }
6011
6012                // Ensure we don't store the same name twice.
6013                // TODO: maybe pc.stores should be a set?
6014                if pc.stores.contains(&name.to_string()) {
6015                    return Err(
6016                        self.error(CodegenErrorType::DuplicateStore(name.as_str().to_string()))
6017                    );
6018                }
6019
6020                // Calculate how many items to rotate:
6021                let rotations = pc.on_top + pc.stores.len() + 1;
6022                self.pattern_helper_rotate(rotations)?;
6023
6024                // Append the name to the captured stores.
6025                pc.stores.push(name.to_string());
6026                Ok(())
6027            }
6028        }
6029    }
6030
6031    fn pattern_unpack_helper(&mut self, elts: &[ast::Pattern]) -> CompileResult<()> {
6032        let n = elts.len();
6033        let mut seen_star = false;
6034        for (i, elt) in elts.iter().enumerate() {
6035            if elt.is_match_star() {
6036                if !seen_star {
6037                    if i >= (1 << 8) || (n - i - 1) >= ((i32::MAX as usize) >> 8) {
6038                        todo!();
6039                        // return self.compiler_error(loc, "too many expressions in star-unpacking sequence pattern");
6040                    }
6041                    let counts = UnpackExArgs {
6042                        before: u8::try_from(i).unwrap(),
6043                        after: u8::try_from(n - i - 1).unwrap(),
6044                    };
6045                    emit!(self, Instruction::UnpackEx { counts });
6046                    seen_star = true;
6047                } else {
6048                    // TODO: Fix error msg
6049                    return Err(self.error(CodegenErrorType::MultipleStarArgs));
6050                    // return self.compiler_error(loc, "multiple starred expressions in sequence pattern");
6051                }
6052            }
6053        }
6054        if !seen_star {
6055            emit!(
6056                self,
6057                Instruction::UnpackSequence {
6058                    count: u32::try_from(n).unwrap()
6059                }
6060            );
6061        }
6062        Ok(())
6063    }
6064
6065    fn pattern_helper_sequence_unpack(
6066        &mut self,
6067        patterns: &[ast::Pattern],
6068        _star: Option<usize>,
6069        pc: &mut PatternContext,
6070    ) -> CompileResult<()> {
6071        // Unpack the sequence into individual subjects.
6072        self.pattern_unpack_helper(patterns)?;
6073        let size = patterns.len();
6074        // Increase the on_top counter for the newly unpacked subjects.
6075        pc.on_top += size;
6076        // For each unpacked subject, compile its subpattern.
6077        for pattern in patterns {
6078            // Decrement on_top for each subject as it is consumed.
6079            pc.on_top -= 1;
6080            self.compile_pattern_subpattern(pattern, pc)?;
6081        }
6082        Ok(())
6083    }
6084
6085    fn pattern_helper_sequence_subscr(
6086        &mut self,
6087        patterns: &[ast::Pattern],
6088        star: usize,
6089        pc: &mut PatternContext,
6090    ) -> CompileResult<()> {
6091        // Keep the subject around for extracting elements.
6092        pc.on_top += 1;
6093        for (i, pattern) in patterns.iter().enumerate() {
6094            // if pattern.is_wildcard() {
6095            // continue;
6096            // }
6097            if i == star {
6098                // This must be a starred wildcard.
6099                // assert!(pattern.is_star_wildcard());
6100                continue;
6101            }
6102            // Duplicate the subject.
6103            emit!(self, Instruction::Copy { i: 1 });
6104            if i < star {
6105                // For indices before the star, use a nonnegative index equal to i.
6106                self.emit_load_const(ConstantData::Integer { value: i.into() });
6107            } else {
6108                // For indices after the star, compute a nonnegative index:
6109                // index = len(subject) - (size - i)
6110                emit!(self, Instruction::GetLen);
6111                self.emit_load_const(ConstantData::Integer {
6112                    value: (patterns.len() - i).into(),
6113                });
6114                // Subtract to compute the correct index.
6115                emit!(
6116                    self,
6117                    Instruction::BinaryOp {
6118                        op: BinaryOperator::Subtract
6119                    }
6120                );
6121            }
6122            // Use BINARY_OP/NB_SUBSCR to extract the element.
6123            emit!(
6124                self,
6125                Instruction::BinaryOp {
6126                    op: BinaryOperator::Subscr
6127                }
6128            );
6129            // Compile the subpattern in irrefutable mode.
6130            self.compile_pattern_subpattern(pattern, pc)?;
6131        }
6132        // Pop the subject off the stack.
6133        pc.on_top -= 1;
6134        emit!(self, Instruction::PopTop);
6135        Ok(())
6136    }
6137
6138    fn compile_pattern_subpattern(
6139        &mut self,
6140        p: &ast::Pattern,
6141        pc: &mut PatternContext,
6142    ) -> CompileResult<()> {
6143        // Save the current allow_irrefutable state.
6144        let old_allow_irrefutable = pc.allow_irrefutable;
6145        // Temporarily allow irrefutable patterns.
6146        pc.allow_irrefutable = true;
6147        // Compile the pattern.
6148        self.compile_pattern(p, pc)?;
6149        // Restore the original state.
6150        pc.allow_irrefutable = old_allow_irrefutable;
6151        Ok(())
6152    }
6153
6154    fn compile_pattern_as(
6155        &mut self,
6156        p: &ast::PatternMatchAs,
6157        pc: &mut PatternContext,
6158    ) -> CompileResult<()> {
6159        // If there is no sub-pattern, then it's an irrefutable match.
6160        if p.pattern.is_none() {
6161            if !pc.allow_irrefutable {
6162                if let Some(_name) = p.name.as_ref() {
6163                    // TODO: This error message does not match cpython exactly
6164                    // A name capture makes subsequent patterns unreachable.
6165                    return Err(self.error(CodegenErrorType::UnreachablePattern(
6166                        PatternUnreachableReason::NameCapture,
6167                    )));
6168                } else {
6169                    // A wildcard makes remaining patterns unreachable.
6170                    return Err(self.error(CodegenErrorType::UnreachablePattern(
6171                        PatternUnreachableReason::Wildcard,
6172                    )));
6173                }
6174            }
6175            // If irrefutable matches are allowed, store the name (if any).
6176            return self.pattern_helper_store_name(p.name.as_ref(), pc);
6177        }
6178
6179        // Otherwise, there is a sub-pattern. Duplicate the object on top of the stack.
6180        pc.on_top += 1;
6181        emit!(self, Instruction::Copy { i: 1 });
6182        // Compile the sub-pattern.
6183        self.compile_pattern(p.pattern.as_ref().unwrap(), pc)?;
6184        // After success, decrement the on_top counter.
6185        pc.on_top -= 1;
6186        // Store the captured name (if any).
6187        self.pattern_helper_store_name(p.name.as_ref(), pc)?;
6188        Ok(())
6189    }
6190
6191    fn compile_pattern_star(
6192        &mut self,
6193        p: &ast::PatternMatchStar,
6194        pc: &mut PatternContext,
6195    ) -> CompileResult<()> {
6196        self.pattern_helper_store_name(p.name.as_ref(), pc)?;
6197        Ok(())
6198    }
6199
6200    /// Validates that keyword attributes in a class pattern are allowed
6201    /// and not duplicated.
6202    fn validate_kwd_attrs(
6203        &mut self,
6204        attrs: &[ast::Identifier],
6205        _patterns: &[ast::Pattern],
6206    ) -> CompileResult<()> {
6207        let n_attrs = attrs.len();
6208        for i in 0..n_attrs {
6209            let attr = attrs[i].as_str();
6210            // Check if the attribute name is forbidden in a Store context.
6211            if self.forbidden_name(attr, NameUsage::Store)? {
6212                // Return an error if the name is forbidden.
6213                return Err(self.compile_error_forbidden_name(attr));
6214            }
6215            // Check for duplicates: compare with every subsequent attribute.
6216            for ident in attrs.iter().take(n_attrs).skip(i + 1) {
6217                let other = ident.as_str();
6218                if attr == other {
6219                    return Err(self.error(CodegenErrorType::RepeatedAttributePattern));
6220                }
6221            }
6222        }
6223        Ok(())
6224    }
6225
6226    fn compile_pattern_class(
6227        &mut self,
6228        p: &ast::PatternMatchClass,
6229        pc: &mut PatternContext,
6230    ) -> CompileResult<()> {
6231        // Extract components from the MatchClass pattern.
6232        let match_class = p;
6233        let patterns = &match_class.arguments.patterns;
6234
6235        // Extract keyword attributes and patterns.
6236        // Capacity is pre-allocated based on the number of keyword arguments.
6237        let mut kwd_attrs = Vec::with_capacity(match_class.arguments.keywords.len());
6238        let mut kwd_patterns = Vec::with_capacity(match_class.arguments.keywords.len());
6239        for kwd in &match_class.arguments.keywords {
6240            kwd_attrs.push(kwd.attr.clone());
6241            kwd_patterns.push(kwd.pattern.clone());
6242        }
6243
6244        let nargs = patterns.len();
6245        let n_attrs = kwd_attrs.len();
6246
6247        // Check for too many sub-patterns.
6248        if nargs > u32::MAX as usize || (nargs + n_attrs).saturating_sub(1) > i32::MAX as usize {
6249            return Err(self.error(CodegenErrorType::SyntaxError(
6250                "too many sub-patterns in class pattern".to_owned(),
6251            )));
6252        }
6253
6254        // Validate keyword attributes if any.
6255        if n_attrs != 0 {
6256            self.validate_kwd_attrs(&kwd_attrs, &kwd_patterns)?;
6257        }
6258
6259        // Compile the class expression.
6260        self.compile_expression(&match_class.cls)?;
6261
6262        // Create a new tuple of attribute names.
6263        let mut attr_names = vec![];
6264        for name in &kwd_attrs {
6265            // Py_NewRef(name) is emulated by cloning the name into a PyObject.
6266            attr_names.push(ConstantData::Str {
6267                value: name.as_str().to_string().into(),
6268            });
6269        }
6270
6271        // Emit instructions:
6272        // 1. Load the new tuple of attribute names.
6273        self.emit_load_const(ConstantData::Tuple {
6274            elements: attr_names,
6275        });
6276        // 2. Emit MATCH_CLASS with nargs.
6277        emit!(
6278            self,
6279            Instruction::MatchClass {
6280                count: u32::try_from(nargs).unwrap()
6281            }
6282        );
6283        // 3. Duplicate the top of the stack.
6284        emit!(self, Instruction::Copy { i: 1 });
6285        // 4. Load None.
6286        self.emit_load_const(ConstantData::None);
6287        // 5. Compare with IS_OP 1.
6288        emit!(
6289            self,
6290            Instruction::IsOp {
6291                invert: Invert::Yes
6292            }
6293        );
6294
6295        // At this point the TOS is a tuple of (nargs + n_attrs) attributes (or None).
6296        pc.on_top += 1;
6297        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
6298
6299        // Unpack the tuple into (nargs + n_attrs) items.
6300        let total = nargs + n_attrs;
6301        emit!(
6302            self,
6303            Instruction::UnpackSequence {
6304                count: u32::try_from(total).unwrap()
6305            }
6306        );
6307        pc.on_top += total;
6308        pc.on_top -= 1;
6309
6310        // Process each sub-pattern.
6311        for subpattern in patterns.iter().chain(kwd_patterns.iter()) {
6312            // Check if this is a true wildcard (underscore pattern without name binding)
6313            let is_true_wildcard = match subpattern {
6314                ast::Pattern::MatchAs(match_as) => {
6315                    // Only consider it wildcard if both pattern and name are None (i.e., "_")
6316                    match_as.pattern.is_none() && match_as.name.is_none()
6317                }
6318                _ => subpattern.is_wildcard(),
6319            };
6320
6321            // Decrement the on_top counter for each sub-pattern
6322            pc.on_top -= 1;
6323
6324            if is_true_wildcard {
6325                emit!(self, Instruction::PopTop);
6326                continue; // Don't compile wildcard patterns
6327            }
6328
6329            // Compile the subpattern without irrefutability checks.
6330            self.compile_pattern_subpattern(subpattern, pc)?;
6331        }
6332        Ok(())
6333    }
6334
6335    fn compile_pattern_mapping(
6336        &mut self,
6337        p: &ast::PatternMatchMapping,
6338        pc: &mut PatternContext,
6339    ) -> CompileResult<()> {
6340        let mapping = p;
6341        let keys = &mapping.keys;
6342        let patterns = &mapping.patterns;
6343        let size = keys.len();
6344        let star_target = &mapping.rest;
6345
6346        // Validate pattern count matches key count
6347        if keys.len() != patterns.len() {
6348            return Err(self.error(CodegenErrorType::SyntaxError(format!(
6349                "keys ({}) / patterns ({}) length mismatch in mapping pattern",
6350                keys.len(),
6351                patterns.len()
6352            ))));
6353        }
6354
6355        // Validate rest pattern: '_' cannot be used as a rest target
6356        if let Some(rest) = star_target
6357            && rest.as_str() == "_"
6358        {
6359            return Err(self.error(CodegenErrorType::SyntaxError("invalid syntax".to_string())));
6360        }
6361
6362        // Step 1: Check if subject is a mapping
6363        // Stack: [subject]
6364        pc.on_top += 1;
6365
6366        emit!(self, Instruction::MatchMapping);
6367        // Stack: [subject, is_mapping]
6368
6369        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
6370        // Stack: [subject]
6371
6372        // Special case: empty pattern {} with no rest
6373        if size == 0 && star_target.is_none() {
6374            // If the pattern is just "{}", we're done! Pop the subject
6375            pc.on_top -= 1;
6376            emit!(self, Instruction::PopTop);
6377            return Ok(());
6378        }
6379
6380        // Length check for patterns with keys
6381        if size > 0 {
6382            // Check if the mapping has at least 'size' keys
6383            emit!(self, Instruction::GetLen);
6384            self.emit_load_const(ConstantData::Integer { value: size.into() });
6385            // Stack: [subject, len, size]
6386            emit!(
6387                self,
6388                Instruction::CompareOp {
6389                    opname: ComparisonOperator::GreaterOrEqual
6390                }
6391            );
6392            self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
6393            // Stack: [subject]
6394        }
6395
6396        // Check for overflow (INT_MAX < size - 1)
6397        if size > (i32::MAX as usize + 1) {
6398            return Err(self.error(CodegenErrorType::SyntaxError(
6399                "too many sub-patterns in mapping pattern".to_string(),
6400            )));
6401        }
6402        #[allow(clippy::cast_possible_truncation, reason = "checked right before")]
6403        let size = size as u32;
6404
6405        // Step 2: If we have keys to match
6406        if size > 0 {
6407            // Validate and compile keys
6408            let mut seen = IndexSet::default();
6409            for key in keys {
6410                let is_attribute = matches!(key, ast::Expr::Attribute(_));
6411                let is_literal = matches!(
6412                    key,
6413                    ast::Expr::NumberLiteral(_)
6414                        | ast::Expr::StringLiteral(_)
6415                        | ast::Expr::BytesLiteral(_)
6416                        | ast::Expr::BooleanLiteral(_)
6417                        | ast::Expr::NoneLiteral(_)
6418                );
6419                let key_repr = if is_literal {
6420                    UnparseExpr::new(key, &self.source_file).to_string()
6421                } else if is_attribute {
6422                    String::new()
6423                } else {
6424                    return Err(self.error(CodegenErrorType::SyntaxError(
6425                        "mapping pattern keys may only match literals and attribute lookups"
6426                            .to_string(),
6427                    )));
6428                };
6429
6430                if !key_repr.is_empty() && seen.contains(&key_repr) {
6431                    return Err(self.error(CodegenErrorType::SyntaxError(format!(
6432                        "mapping pattern checks duplicate key ({key_repr})"
6433                    ))));
6434                }
6435                if !key_repr.is_empty() {
6436                    seen.insert(key_repr);
6437                }
6438
6439                self.compile_expression(key)?;
6440            }
6441        }
6442        // Stack: [subject, key1, key2, ..., key_n]
6443
6444        // Build tuple of keys (empty tuple if size==0)
6445        emit!(self, Instruction::BuildTuple { count: size });
6446        // Stack: [subject, keys_tuple]
6447
6448        // Match keys
6449        emit!(self, Instruction::MatchKeys);
6450        // Stack: [subject, keys_tuple, values_or_none]
6451        pc.on_top += 2; // subject and keys_tuple are underneath
6452
6453        // Check if match succeeded
6454        emit!(self, Instruction::Copy { i: 1 });
6455        // Stack: [subject, keys_tuple, values_tuple, values_tuple_copy]
6456
6457        // Check if copy is None (consumes the copy like POP_JUMP_IF_NONE)
6458        self.emit_load_const(ConstantData::None);
6459        emit!(
6460            self,
6461            Instruction::IsOp {
6462                invert: Invert::Yes
6463            }
6464        );
6465
6466        // Stack: [subject, keys_tuple, values_tuple, bool]
6467        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
6468        // Stack: [subject, keys_tuple, values_tuple]
6469
6470        // Unpack values (the original values_tuple)
6471        emit!(self, Instruction::UnpackSequence { count: size });
6472        // Stack after unpack: [subject, keys_tuple, ...unpacked values...]
6473        pc.on_top += size as usize; // Unpacked size values, tuple replaced by values
6474        pc.on_top -= 1;
6475
6476        // Step 3: Process matched values
6477        for i in 0..size {
6478            pc.on_top -= 1;
6479            self.compile_pattern_subpattern(&patterns[i as usize], pc)?;
6480        }
6481
6482        // After processing subpatterns, adjust on_top
6483        // "Whatever happens next should consume the tuple of keys and the subject"
6484        // Stack currently: [subject, keys_tuple, ...any captured values...]
6485        pc.on_top -= 2;
6486
6487        // Step 4: Handle rest pattern or cleanup
6488        if let Some(rest_name) = star_target {
6489            // Build rest dict for **rest pattern
6490            // Stack: [subject, keys_tuple]
6491
6492            // Build rest dict exactly
6493            emit!(self, Instruction::BuildMap { count: 0 });
6494            // Stack: [subject, keys_tuple, {}]
6495            emit!(self, Instruction::Swap { i: 3 });
6496            // Stack: [{}, keys_tuple, subject]
6497            emit!(self, Instruction::DictUpdate { i: 2 });
6498            // Stack after DICT_UPDATE: [rest_dict, keys_tuple]
6499            // DICT_UPDATE consumes source (subject) and leaves dict in place
6500
6501            // Unpack keys and delete from rest_dict
6502            emit!(self, Instruction::UnpackSequence { count: size });
6503            // Stack: [rest_dict, k1, k2, ..., kn] (if size==0, nothing pushed)
6504
6505            // Delete each key from rest_dict (skipped when size==0)
6506            // while (size) { COPY(1 + size--); SWAP(2); DELETE_SUBSCR }
6507            let mut remaining = size;
6508            while remaining > 0 {
6509                // Copy rest_dict which is at position (1 + remaining) from TOS
6510                emit!(self, Instruction::Copy { i: 1 + remaining });
6511                // Stack: [rest_dict, k1, ..., kn, rest_dict]
6512                emit!(self, Instruction::Swap { i: 2 });
6513                // Stack: [rest_dict, k1, ..., kn-1, rest_dict, kn]
6514                emit!(self, Instruction::DeleteSubscr);
6515                // Stack: [rest_dict, k1, ..., kn-1] (removed kn from rest_dict)
6516                remaining -= 1;
6517            }
6518            // Stack: [rest_dict] (plus any previously stored values)
6519            // pattern_helper_store_name will handle the rotation correctly
6520
6521            // Store the rest dict
6522            self.pattern_helper_store_name(Some(rest_name), pc)?;
6523
6524            // After storing all values, pc.on_top should be 0
6525            // The values are rotated to the bottom for later storage
6526            pc.on_top = 0;
6527        } else {
6528            // Non-rest pattern: just clean up the stack
6529
6530            // Pop them as we're not using them
6531            emit!(self, Instruction::PopTop); // Pop keys_tuple
6532            emit!(self, Instruction::PopTop); // Pop subject
6533        }
6534
6535        Ok(())
6536    }
6537
6538    fn compile_pattern_or(
6539        &mut self,
6540        p: &ast::PatternMatchOr,
6541        pc: &mut PatternContext,
6542    ) -> CompileResult<()> {
6543        // Ensure the pattern is a MatchOr.
6544        let end = self.new_block(); // Create a new jump target label.
6545        let size = p.patterns.len();
6546        if size <= 1 {
6547            return Err(self.error(CodegenErrorType::SyntaxError(
6548                "MatchOr requires at least 2 patterns".to_owned(),
6549            )));
6550        }
6551
6552        // Save the current pattern context.
6553        let old_pc = pc.clone();
6554        // Simulate Py_INCREF on pc.stores by cloning it.
6555        pc.stores = pc.stores.clone();
6556        let mut control: Option<Vec<String>> = None; // Will hold the capture list of the first alternative.
6557
6558        // Process each alternative.
6559        for (i, alt) in p.patterns.iter().enumerate() {
6560            // Create a fresh empty store for this alternative.
6561            pc.stores = Vec::new();
6562            // An irrefutable subpattern must be last (if allowed).
6563            pc.allow_irrefutable = (i == size - 1) && old_pc.allow_irrefutable;
6564            // Reset failure targets and the on_top counter.
6565            pc.fail_pop.clear();
6566            pc.on_top = 0;
6567            // Emit a COPY(1) instruction before compiling the alternative.
6568            emit!(self, Instruction::Copy { i: 1 });
6569            self.compile_pattern(alt, pc)?;
6570
6571            let n_stores = pc.stores.len();
6572            if i == 0 {
6573                // Save the captured names from the first alternative.
6574                control = Some(pc.stores.clone());
6575            } else {
6576                let control_vec = control.as_ref().unwrap();
6577                if n_stores != control_vec.len() {
6578                    return Err(self.error(CodegenErrorType::ConflictingNameBindPattern));
6579                } else if n_stores > 0 {
6580                    // Check that the names occur in the same order.
6581                    for i_control in (0..n_stores).rev() {
6582                        let name = &control_vec[i_control];
6583                        // Find the index of `name` in the current stores.
6584                        let i_stores =
6585                            pc.stores.iter().position(|n| n == name).ok_or_else(|| {
6586                                self.error(CodegenErrorType::ConflictingNameBindPattern)
6587                            })?;
6588                        if i_control != i_stores {
6589                            // The orders differ; we must reorder.
6590                            assert!(i_stores < i_control, "expected i_stores < i_control");
6591                            let rotations = i_stores + 1;
6592                            // Rotate pc.stores: take a slice of the first `rotations` items...
6593                            let rotated = pc.stores[0..rotations].to_vec();
6594                            // Remove those elements.
6595                            for _ in 0..rotations {
6596                                pc.stores.remove(0);
6597                            }
6598                            // Insert the rotated slice at the appropriate index.
6599                            let insert_pos = i_control - i_stores;
6600                            for (j, elem) in rotated.into_iter().enumerate() {
6601                                pc.stores.insert(insert_pos + j, elem);
6602                            }
6603                            // Also perform the same rotation on the evaluation stack.
6604                            for _ in 0..=i_stores {
6605                                self.pattern_helper_rotate(i_control + 1)?;
6606                            }
6607                        }
6608                    }
6609                }
6610            }
6611            // Emit a jump to the common end label and reset any failure jump targets.
6612            emit!(self, PseudoInstruction::Jump { delta: end });
6613            self.emit_and_reset_fail_pop(pc)?;
6614        }
6615
6616        // Restore the original pattern context.
6617        *pc = old_pc.clone();
6618        // Simulate Py_INCREF on pc.stores.
6619        pc.stores = pc.stores.clone();
6620        // In C, old_pc.fail_pop is set to NULL to avoid freeing it later.
6621        // In Rust, old_pc is a local clone, so we need not worry about that.
6622
6623        // No alternative matched: pop the subject and fail.
6624        emit!(self, Instruction::PopTop);
6625        self.jump_to_fail_pop(pc, JumpOp::Jump)?;
6626
6627        // Use the label "end".
6628        self.switch_to_block(end);
6629
6630        // Adjust the final captures.
6631        let n_stores = control.as_ref().unwrap().len();
6632        let n_rots = n_stores + 1 + pc.on_top + pc.stores.len();
6633        for i in 0..n_stores {
6634            // Rotate the capture to its proper place.
6635            self.pattern_helper_rotate(n_rots)?;
6636            let name = &control.as_ref().unwrap()[i];
6637            // Check for duplicate binding.
6638            if pc.stores.contains(name) {
6639                return Err(self.error(CodegenErrorType::DuplicateStore(name.to_string())));
6640            }
6641            pc.stores.push(name.clone());
6642        }
6643
6644        // Old context and control will be dropped automatically.
6645        // Finally, pop the copy of the subject.
6646        emit!(self, Instruction::PopTop);
6647        Ok(())
6648    }
6649
6650    fn compile_pattern_sequence(
6651        &mut self,
6652        p: &ast::PatternMatchSequence,
6653        pc: &mut PatternContext,
6654    ) -> CompileResult<()> {
6655        // Ensure the pattern is a MatchSequence.
6656        let patterns = &p.patterns; // a slice of ast::Pattern
6657        let size = patterns.len();
6658        let mut star: Option<usize> = None;
6659        let mut only_wildcard = true;
6660        let mut star_wildcard = false;
6661
6662        // Find a starred pattern, if it exists. There may be at most one.
6663        for (i, pattern) in patterns.iter().enumerate() {
6664            if pattern.is_match_star() {
6665                if star.is_some() {
6666                    // TODO: Fix error msg
6667                    return Err(self.error(CodegenErrorType::MultipleStarArgs));
6668                }
6669                // star wildcard check
6670                star_wildcard = pattern
6671                    .as_match_star()
6672                    .map(|m| m.name.is_none())
6673                    .unwrap_or(false);
6674                only_wildcard &= star_wildcard;
6675                star = Some(i);
6676                continue;
6677            }
6678            // wildcard check
6679            only_wildcard &= pattern
6680                .as_match_as()
6681                .map(|m| m.name.is_none())
6682                .unwrap_or(false);
6683        }
6684
6685        // Keep the subject on top during the sequence and length checks.
6686        pc.on_top += 1;
6687        emit!(self, Instruction::MatchSequence);
6688        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
6689
6690        if star.is_none() {
6691            // No star: len(subject) == size
6692            emit!(self, Instruction::GetLen);
6693            self.emit_load_const(ConstantData::Integer { value: size.into() });
6694            emit!(
6695                self,
6696                Instruction::CompareOp {
6697                    opname: ComparisonOperator::Equal
6698                }
6699            );
6700            self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
6701        } else if size > 1 {
6702            // Star exists: len(subject) >= size - 1
6703            emit!(self, Instruction::GetLen);
6704            self.emit_load_const(ConstantData::Integer {
6705                value: (size - 1).into(),
6706            });
6707            emit!(
6708                self,
6709                Instruction::CompareOp {
6710                    opname: ComparisonOperator::GreaterOrEqual
6711                }
6712            );
6713            self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
6714        }
6715
6716        // Whatever comes next should consume the subject.
6717        pc.on_top -= 1;
6718        if only_wildcard {
6719            // ast::Patterns like: [] / [_] / [_, _] / [*_] / [_, *_] / [_, _, *_] / etc.
6720            emit!(self, Instruction::PopTop);
6721        } else if star_wildcard {
6722            self.pattern_helper_sequence_subscr(patterns, star.unwrap(), pc)?;
6723        } else {
6724            self.pattern_helper_sequence_unpack(patterns, star, pc)?;
6725        }
6726        Ok(())
6727    }
6728
6729    fn compile_pattern_value(
6730        &mut self,
6731        p: &ast::PatternMatchValue,
6732        pc: &mut PatternContext,
6733    ) -> CompileResult<()> {
6734        // TODO: ensure literal or attribute lookup
6735        self.compile_expression(&p.value)?;
6736        emit!(
6737            self,
6738            Instruction::CompareOp {
6739                opname: bytecode::ComparisonOperator::Equal
6740            }
6741        );
6742        // emit!(self, Instruction::ToBool);
6743        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
6744        Ok(())
6745    }
6746
6747    fn compile_pattern_singleton(
6748        &mut self,
6749        p: &ast::PatternMatchSingleton,
6750        pc: &mut PatternContext,
6751    ) -> CompileResult<()> {
6752        // Load the singleton constant value.
6753        self.emit_load_const(match p.value {
6754            ast::Singleton::None => ConstantData::None,
6755            ast::Singleton::False => ConstantData::Boolean { value: false },
6756            ast::Singleton::True => ConstantData::Boolean { value: true },
6757        });
6758        // Compare using the "Is" operator.
6759        emit!(self, Instruction::IsOp { invert: Invert::No });
6760        // Jump to the failure label if the comparison is false.
6761        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
6762        Ok(())
6763    }
6764
6765    fn compile_pattern(
6766        &mut self,
6767        pattern_type: &ast::Pattern,
6768        pattern_context: &mut PatternContext,
6769    ) -> CompileResult<()> {
6770        match &pattern_type {
6771            ast::Pattern::MatchValue(pattern_type) => {
6772                self.compile_pattern_value(pattern_type, pattern_context)
6773            }
6774            ast::Pattern::MatchSingleton(pattern_type) => {
6775                self.compile_pattern_singleton(pattern_type, pattern_context)
6776            }
6777            ast::Pattern::MatchSequence(pattern_type) => {
6778                self.compile_pattern_sequence(pattern_type, pattern_context)
6779            }
6780            ast::Pattern::MatchMapping(pattern_type) => {
6781                self.compile_pattern_mapping(pattern_type, pattern_context)
6782            }
6783            ast::Pattern::MatchClass(pattern_type) => {
6784                self.compile_pattern_class(pattern_type, pattern_context)
6785            }
6786            ast::Pattern::MatchStar(pattern_type) => {
6787                self.compile_pattern_star(pattern_type, pattern_context)
6788            }
6789            ast::Pattern::MatchAs(pattern_type) => {
6790                self.compile_pattern_as(pattern_type, pattern_context)
6791            }
6792            ast::Pattern::MatchOr(pattern_type) => {
6793                self.compile_pattern_or(pattern_type, pattern_context)
6794            }
6795        }
6796    }
6797
6798    fn compile_match_inner(
6799        &mut self,
6800        subject: &ast::Expr,
6801        cases: &[ast::MatchCase],
6802        pattern_context: &mut PatternContext,
6803    ) -> CompileResult<()> {
6804        self.compile_expression(subject)?;
6805        let end = self.new_block();
6806
6807        let num_cases = cases.len();
6808        assert!(num_cases > 0);
6809        let has_default = cases.iter().last().unwrap().pattern.is_match_star() && num_cases > 1;
6810
6811        let case_count = num_cases - if has_default { 1 } else { 0 };
6812        for (i, m) in cases.iter().enumerate().take(case_count) {
6813            // Only copy the subject if not on the last case
6814            if i != case_count - 1 {
6815                emit!(self, Instruction::Copy { i: 1 });
6816            }
6817
6818            pattern_context.stores = Vec::with_capacity(1);
6819            pattern_context.allow_irrefutable = m.guard.is_some() || i == case_count - 1;
6820            pattern_context.fail_pop.clear();
6821            pattern_context.on_top = 0;
6822
6823            self.compile_pattern(&m.pattern, pattern_context)?;
6824            assert_eq!(pattern_context.on_top, 0);
6825
6826            for name in &pattern_context.stores {
6827                self.compile_name(name, NameUsage::Store)?;
6828            }
6829
6830            if let Some(ref guard) = m.guard {
6831                self.ensure_fail_pop(pattern_context, 0)?;
6832                // Compile the guard expression
6833                self.compile_expression(guard)?;
6834                emit!(self, Instruction::ToBool);
6835                emit!(
6836                    self,
6837                    Instruction::PopJumpIfFalse {
6838                        delta: pattern_context.fail_pop[0]
6839                    }
6840                );
6841            }
6842
6843            if i != case_count - 1 {
6844                emit!(self, Instruction::PopTop);
6845            }
6846
6847            self.compile_statements(&m.body)?;
6848            emit!(self, PseudoInstruction::Jump { delta: end });
6849            self.emit_and_reset_fail_pop(pattern_context)?;
6850        }
6851
6852        if has_default {
6853            let m = &cases[num_cases - 1];
6854            if num_cases == 1 {
6855                emit!(self, Instruction::PopTop);
6856            } else {
6857                emit!(self, Instruction::Nop);
6858            }
6859            if let Some(ref guard) = m.guard {
6860                // Compile guard and jump to end if false
6861                self.compile_expression(guard)?;
6862                emit!(self, Instruction::Copy { i: 1 });
6863                emit!(self, Instruction::PopJumpIfFalse { delta: end });
6864                emit!(self, Instruction::PopTop);
6865            }
6866            self.compile_statements(&m.body)?;
6867        }
6868        self.switch_to_block(end);
6869        Ok(())
6870    }
6871
6872    fn compile_match(
6873        &mut self,
6874        subject: &ast::Expr,
6875        cases: &[ast::MatchCase],
6876    ) -> CompileResult<()> {
6877        self.enter_conditional_block();
6878        let mut pattern_context = PatternContext::new();
6879        self.compile_match_inner(subject, cases, &mut pattern_context)?;
6880        self.leave_conditional_block();
6881        Ok(())
6882    }
6883
6884    /// [CPython `compiler_addcompare`](https://github.com/python/cpython/blob/627894459a84be3488a1789919679c997056a03c/Python/compile.c#L2880-L2924)
6885    fn compile_addcompare(&mut self, op: &ast::CmpOp) {
6886        use bytecode::ComparisonOperator::*;
6887        match op {
6888            ast::CmpOp::Eq => emit!(self, Instruction::CompareOp { opname: Equal }),
6889            ast::CmpOp::NotEq => emit!(self, Instruction::CompareOp { opname: NotEqual }),
6890            ast::CmpOp::Lt => emit!(self, Instruction::CompareOp { opname: Less }),
6891            ast::CmpOp::LtE => emit!(
6892                self,
6893                Instruction::CompareOp {
6894                    opname: LessOrEqual
6895                }
6896            ),
6897            ast::CmpOp::Gt => emit!(self, Instruction::CompareOp { opname: Greater }),
6898            ast::CmpOp::GtE => {
6899                emit!(
6900                    self,
6901                    Instruction::CompareOp {
6902                        opname: GreaterOrEqual
6903                    }
6904                )
6905            }
6906            ast::CmpOp::In => emit!(self, Instruction::ContainsOp { invert: Invert::No }),
6907            ast::CmpOp::NotIn => emit!(
6908                self,
6909                Instruction::ContainsOp {
6910                    invert: Invert::Yes
6911                }
6912            ),
6913            ast::CmpOp::Is => emit!(self, Instruction::IsOp { invert: Invert::No }),
6914            ast::CmpOp::IsNot => emit!(
6915                self,
6916                Instruction::IsOp {
6917                    invert: Invert::Yes
6918                }
6919            ),
6920        }
6921    }
6922
6923    /// Compile a chained comparison.
6924    ///
6925    /// ```py
6926    /// a == b == c == d
6927    /// ```
6928    ///
6929    /// Will compile into (pseudo code):
6930    ///
6931    /// ```py
6932    /// result = a == b
6933    /// if result:
6934    ///   result = b == c
6935    ///   if result:
6936    ///     result = c == d
6937    /// ```
6938    ///
6939    /// # See Also
6940    /// - [CPython `compiler_compare`](https://github.com/python/cpython/blob/627894459a84be3488a1789919679c997056a03c/Python/compile.c#L4678-L4717)
6941    fn compile_compare(
6942        &mut self,
6943        left: &ast::Expr,
6944        ops: &[ast::CmpOp],
6945        comparators: &[ast::Expr],
6946    ) -> CompileResult<()> {
6947        // Save the full Compare expression range for COMPARE_OP positions
6948        let compare_range = self.current_source_range;
6949        let (last_op, mid_ops) = ops.split_last().unwrap();
6950        let (last_comparator, mid_comparators) = comparators.split_last().unwrap();
6951
6952        // initialize lhs outside of loop
6953        self.compile_expression(left)?;
6954
6955        if mid_comparators.is_empty() {
6956            self.compile_expression(last_comparator)?;
6957            self.set_source_range(compare_range);
6958            self.compile_addcompare(last_op);
6959
6960            return Ok(());
6961        }
6962
6963        let cleanup = self.new_block();
6964
6965        // for all comparisons except the last (as the last one doesn't need a conditional jump)
6966        for (op, comparator) in mid_ops.iter().zip(mid_comparators) {
6967            self.compile_expression(comparator)?;
6968
6969            // store rhs for the next comparison in chain
6970            self.set_source_range(compare_range);
6971            emit!(self, Instruction::Swap { i: 2 });
6972            emit!(self, Instruction::Copy { i: 2 });
6973
6974            self.compile_addcompare(op);
6975
6976            // if comparison result is false, we break with this value; if true, try the next one.
6977            emit!(self, Instruction::Copy { i: 1 });
6978            emit!(self, Instruction::PopJumpIfFalse { delta: cleanup });
6979            emit!(self, Instruction::PopTop);
6980        }
6981
6982        self.compile_expression(last_comparator)?;
6983        self.set_source_range(compare_range);
6984        self.compile_addcompare(last_op);
6985
6986        let end = self.new_block();
6987        emit!(self, PseudoInstruction::Jump { delta: end });
6988
6989        // early exit left us with stack: `rhs, comparison_result`. We need to clean up rhs.
6990        self.switch_to_block(cleanup);
6991        emit!(self, Instruction::Swap { i: 2 });
6992        emit!(self, Instruction::PopTop);
6993
6994        self.switch_to_block(end);
6995        Ok(())
6996    }
6997
6998    fn compile_jump_if_compare(
6999        &mut self,
7000        left: &ast::Expr,
7001        ops: &[ast::CmpOp],
7002        comparators: &[ast::Expr],
7003        condition: bool,
7004        target_block: BlockIdx,
7005    ) -> CompileResult<()> {
7006        let compare_range = self.current_source_range;
7007        let (last_op, mid_ops) = ops.split_last().unwrap();
7008        let (last_comparator, mid_comparators) = comparators.split_last().unwrap();
7009
7010        self.compile_expression(left)?;
7011
7012        if mid_comparators.is_empty() {
7013            self.compile_expression(last_comparator)?;
7014            self.set_source_range(compare_range);
7015            self.compile_addcompare(last_op);
7016            self.emit_pop_jump_by_condition(condition, target_block);
7017            return Ok(());
7018        }
7019
7020        let cleanup = self.new_block();
7021        let end = self.new_block();
7022
7023        for (op, comparator) in mid_ops.iter().zip(mid_comparators) {
7024            self.compile_expression(comparator)?;
7025            self.set_source_range(compare_range);
7026            emit!(self, Instruction::Swap { i: 2 });
7027            emit!(self, Instruction::Copy { i: 2 });
7028            self.compile_addcompare(op);
7029            emit!(self, Instruction::PopJumpIfFalse { delta: cleanup });
7030        }
7031
7032        self.compile_expression(last_comparator)?;
7033        self.set_source_range(compare_range);
7034        self.compile_addcompare(last_op);
7035        self.emit_pop_jump_by_condition(condition, target_block);
7036        emit!(self, PseudoInstruction::Jump { delta: end });
7037
7038        self.switch_to_block(cleanup);
7039        emit!(self, Instruction::PopTop);
7040        if !condition {
7041            emit!(
7042                self,
7043                PseudoInstruction::Jump {
7044                    delta: target_block
7045                }
7046            );
7047        }
7048
7049        self.switch_to_block(end);
7050        Ok(())
7051    }
7052
7053    fn emit_pop_jump_by_condition(&mut self, condition: bool, target_block: BlockIdx) {
7054        if condition {
7055            emit!(
7056                self,
7057                Instruction::PopJumpIfTrue {
7058                    delta: target_block
7059                }
7060            );
7061        } else {
7062            emit!(
7063                self,
7064                Instruction::PopJumpIfFalse {
7065                    delta: target_block,
7066                }
7067            );
7068        }
7069    }
7070
7071    fn compile_annotation(&mut self, annotation: &ast::Expr) -> CompileResult<()> {
7072        if self.future_annotations {
7073            self.emit_load_const(ConstantData::Str {
7074                value: UnparseExpr::new(annotation, &self.source_file)
7075                    .to_string()
7076                    .into(),
7077            });
7078        } else {
7079            let was_in_annotation = self.in_annotation;
7080            self.in_annotation = true;
7081
7082            // Special handling for starred annotations (*Ts -> Unpack[Ts])
7083            let result = match annotation {
7084                ast::Expr::Starred(ast::ExprStarred { value, .. }) => {
7085                    // *args: *Ts (where Ts is a TypeVarTuple).
7086                    // Do [annotation_value] = [*Ts].
7087                    self.compile_expression(value)?;
7088                    emit!(self, Instruction::UnpackSequence { count: 1 });
7089                    Ok(())
7090                }
7091                _ => self.compile_expression(annotation),
7092            };
7093
7094            self.in_annotation = was_in_annotation;
7095            result?;
7096        }
7097        Ok(())
7098    }
7099
7100    fn compile_annotated_assign(
7101        &mut self,
7102        target: &ast::Expr,
7103        annotation: &ast::Expr,
7104        value: Option<&ast::Expr>,
7105        simple: bool,
7106    ) -> CompileResult<()> {
7107        // Perform the actual assignment first
7108        if let Some(value) = value {
7109            self.compile_expression(value)?;
7110            self.compile_store(target)?;
7111        }
7112
7113        // If we have a simple name in module or class scope, store annotation
7114        if simple
7115            && !self.ctx.in_func()
7116            && let ast::Expr::Name(ast::ExprName { id, .. }) = target
7117        {
7118            if self.future_annotations {
7119                // PEP 563: Store stringified annotation directly to __annotations__
7120                // Compile annotation as string
7121                self.compile_annotation(annotation)?;
7122                // Load __annotations__
7123                let annotations_name = self.name("__annotations__");
7124                emit!(
7125                    self,
7126                    Instruction::LoadName {
7127                        namei: annotations_name
7128                    }
7129                );
7130                // Load the variable name
7131                self.emit_load_const(ConstantData::Str {
7132                    value: self.mangle(id.as_str()).into_owned().into(),
7133                });
7134                // Store: __annotations__[name] = annotation
7135                emit!(self, Instruction::StoreSubscr);
7136            } else {
7137                // PEP 649: Handle conditional annotations
7138                if self.current_symbol_table().has_conditional_annotations {
7139                    // Allocate an index for every annotation when has_conditional_annotations
7140                    // This keeps indices aligned with compile_module_annotate's enumeration
7141                    let code_info = self.current_code_info();
7142                    let annotation_index = code_info.next_conditional_annotation_index;
7143                    code_info.next_conditional_annotation_index += 1;
7144
7145                    // Determine if this annotation is conditional
7146                    // Module and Class scopes both need all annotations tracked
7147                    let scope_type = self.current_symbol_table().typ;
7148                    let in_conditional_block = self.current_code_info().in_conditional_block > 0;
7149                    let is_conditional =
7150                        matches!(scope_type, CompilerScope::Module | CompilerScope::Class)
7151                            || in_conditional_block;
7152
7153                    // Only add to __conditional_annotations__ set if actually conditional
7154                    if is_conditional {
7155                        self.load_name("__conditional_annotations__")?;
7156                        self.emit_load_const(ConstantData::Integer {
7157                            value: annotation_index.into(),
7158                        });
7159                        emit!(self, Instruction::SetAdd { i: 1 });
7160                        emit!(self, Instruction::PopTop);
7161                    }
7162                }
7163            }
7164        }
7165
7166        Ok(())
7167    }
7168
7169    fn compile_store(&mut self, target: &ast::Expr) -> CompileResult<()> {
7170        match &target {
7171            ast::Expr::Name(ast::ExprName { id, .. }) => self.store_name(id.as_str())?,
7172            ast::Expr::Subscript(ast::ExprSubscript {
7173                value, slice, ctx, ..
7174            }) => {
7175                self.compile_subscript(value, slice, *ctx)?;
7176            }
7177            ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
7178                self.compile_expression(value)?;
7179                let namei = self.name(attr.as_str());
7180                emit!(self, Instruction::StoreAttr { namei });
7181            }
7182            ast::Expr::List(ast::ExprList { elts, .. })
7183            | ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
7184                let mut seen_star = false;
7185
7186                // Scan for star args:
7187                for (i, element) in elts.iter().enumerate() {
7188                    if let ast::Expr::Starred(_) = &element {
7189                        if seen_star {
7190                            return Err(self.error(CodegenErrorType::MultipleStarArgs));
7191                        } else {
7192                            seen_star = true;
7193                            let before = i;
7194                            let after = elts.len() - i - 1;
7195                            let (before, after) = (|| Some((before.to_u8()?, after.to_u8()?)))()
7196                                .ok_or_else(|| {
7197                                    self.error_ranged(
7198                                        CodegenErrorType::TooManyStarUnpack,
7199                                        target.range(),
7200                                    )
7201                                })?;
7202                            let counts = bytecode::UnpackExArgs { before, after };
7203                            emit!(self, Instruction::UnpackEx { counts });
7204                        }
7205                    }
7206                }
7207
7208                if !seen_star {
7209                    emit!(
7210                        self,
7211                        Instruction::UnpackSequence {
7212                            count: elts.len().to_u32(),
7213                        }
7214                    );
7215                }
7216
7217                for element in elts {
7218                    if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = &element {
7219                        self.compile_store(value)?;
7220                    } else {
7221                        self.compile_store(element)?;
7222                    }
7223                }
7224            }
7225            _ => {
7226                return Err(self.error(match target {
7227                    ast::Expr::Starred(_) => CodegenErrorType::SyntaxError(
7228                        "starred assignment target must be in a list or tuple".to_owned(),
7229                    ),
7230                    _ => CodegenErrorType::Assign(target.python_name()),
7231                }));
7232            }
7233        }
7234
7235        Ok(())
7236    }
7237
7238    fn compile_augassign(
7239        &mut self,
7240        target: &ast::Expr,
7241        op: &ast::Operator,
7242        value: &ast::Expr,
7243    ) -> CompileResult<()> {
7244        enum AugAssignKind<'a> {
7245            Name { id: &'a str },
7246            Subscript,
7247            Attr { idx: bytecode::NameIdx },
7248        }
7249
7250        let kind = match &target {
7251            ast::Expr::Name(ast::ExprName { id, .. }) => {
7252                let id = id.as_str();
7253                self.compile_name(id, NameUsage::Load)?;
7254                AugAssignKind::Name { id }
7255            }
7256            ast::Expr::Subscript(ast::ExprSubscript {
7257                value,
7258                slice,
7259                ctx: _,
7260                ..
7261            }) => {
7262                // For augmented assignment, we need to load the value first
7263                // But we can't use compile_subscript directly because we need DUP_TOP2
7264                self.compile_expression(value)?;
7265                self.compile_expression(slice)?;
7266                emit!(self, Instruction::Copy { i: 2 });
7267                emit!(self, Instruction::Copy { i: 2 });
7268                emit!(
7269                    self,
7270                    Instruction::BinaryOp {
7271                        op: BinaryOperator::Subscr
7272                    }
7273                );
7274                AugAssignKind::Subscript
7275            }
7276            ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
7277                let attr = attr.as_str();
7278                self.compile_expression(value)?;
7279                emit!(self, Instruction::Copy { i: 1 });
7280                let idx = self.name(attr);
7281                self.emit_load_attr(idx);
7282                AugAssignKind::Attr { idx }
7283            }
7284            _ => {
7285                return Err(self.error(CodegenErrorType::Assign(target.python_name())));
7286            }
7287        };
7288
7289        self.compile_expression(value)?;
7290        self.compile_op(op, true);
7291
7292        match kind {
7293            AugAssignKind::Name { id } => {
7294                // stack: RESULT
7295                self.compile_name(id, NameUsage::Store)?;
7296            }
7297            AugAssignKind::Subscript => {
7298                // stack: CONTAINER SLICE RESULT
7299                emit!(self, Instruction::Swap { i: 3 });
7300                emit!(self, Instruction::Swap { i: 2 });
7301                emit!(self, Instruction::StoreSubscr);
7302            }
7303            AugAssignKind::Attr { idx } => {
7304                // stack: CONTAINER RESULT
7305                emit!(self, Instruction::Swap { i: 2 });
7306                emit!(self, Instruction::StoreAttr { namei: idx });
7307            }
7308        }
7309
7310        Ok(())
7311    }
7312
7313    fn compile_op(&mut self, op: &ast::Operator, inplace: bool) {
7314        let bin_op = match op {
7315            ast::Operator::Add => BinaryOperator::Add,
7316            ast::Operator::Sub => BinaryOperator::Subtract,
7317            ast::Operator::Mult => BinaryOperator::Multiply,
7318            ast::Operator::MatMult => BinaryOperator::MatrixMultiply,
7319            ast::Operator::Div => BinaryOperator::TrueDivide,
7320            ast::Operator::FloorDiv => BinaryOperator::FloorDivide,
7321            ast::Operator::Mod => BinaryOperator::Remainder,
7322            ast::Operator::Pow => BinaryOperator::Power,
7323            ast::Operator::LShift => BinaryOperator::Lshift,
7324            ast::Operator::RShift => BinaryOperator::Rshift,
7325            ast::Operator::BitOr => BinaryOperator::Or,
7326            ast::Operator::BitXor => BinaryOperator::Xor,
7327            ast::Operator::BitAnd => BinaryOperator::And,
7328        };
7329
7330        let op = if inplace { bin_op.as_inplace() } else { bin_op };
7331        emit!(self, Instruction::BinaryOp { op })
7332    }
7333
7334    /// Implement boolean short circuit evaluation logic.
7335    /// https://en.wikipedia.org/wiki/Short-circuit_evaluation
7336    ///
7337    /// This means, in a boolean statement 'x and y' the variable y will
7338    /// not be evaluated when x is false.
7339    ///
7340    /// The idea is to jump to a label if the expression is either true or false
7341    /// (indicated by the condition parameter).
7342    fn compile_jump_if(
7343        &mut self,
7344        expression: &ast::Expr,
7345        condition: bool,
7346        target_block: BlockIdx,
7347    ) -> CompileResult<()> {
7348        let prev_source_range = self.current_source_range;
7349        self.set_source_range(expression.range());
7350
7351        // Compile expression for test, and jump to label if false
7352        let result = match &expression {
7353            ast::Expr::BoolOp(ast::ExprBoolOp { op, values, .. }) => {
7354                match op {
7355                    ast::BoolOp::And => {
7356                        if condition {
7357                            // If all values are true.
7358                            let end_block = self.new_block();
7359                            let (last_value, values) = values.split_last().unwrap();
7360
7361                            // If any of the values is false, we can short-circuit.
7362                            for value in values {
7363                                self.compile_jump_if(value, false, end_block)?;
7364                            }
7365
7366                            // It depends upon the last value now: will it be true?
7367                            self.compile_jump_if(last_value, true, target_block)?;
7368                            self.switch_to_block(end_block);
7369                        } else {
7370                            // If any value is false, the whole condition is false.
7371                            for value in values {
7372                                self.compile_jump_if(value, false, target_block)?;
7373                            }
7374                        }
7375                    }
7376                    ast::BoolOp::Or => {
7377                        if condition {
7378                            // If any of the values is true.
7379                            for value in values {
7380                                self.compile_jump_if(value, true, target_block)?;
7381                            }
7382                        } else {
7383                            // If all of the values are false.
7384                            let end_block = self.new_block();
7385                            let (last_value, values) = values.split_last().unwrap();
7386
7387                            // If any value is true, we can short-circuit:
7388                            for value in values {
7389                                self.compile_jump_if(value, true, end_block)?;
7390                            }
7391
7392                            // It all depends upon the last value now!
7393                            self.compile_jump_if(last_value, false, target_block)?;
7394                            self.switch_to_block(end_block);
7395                        }
7396                    }
7397                }
7398                Ok(())
7399            }
7400            ast::Expr::UnaryOp(ast::ExprUnaryOp {
7401                op: ast::UnaryOp::Not,
7402                operand,
7403                ..
7404            }) => self.compile_jump_if(operand, !condition, target_block),
7405            ast::Expr::Compare(ast::ExprCompare {
7406                left,
7407                ops,
7408                comparators,
7409                ..
7410            }) if ops.len() > 1 => {
7411                self.compile_jump_if_compare(left, ops, comparators, condition, target_block)
7412            }
7413            // `x is None` / `x is not None` → POP_JUMP_IF_NONE / POP_JUMP_IF_NOT_NONE
7414            ast::Expr::Compare(ast::ExprCompare {
7415                left,
7416                ops,
7417                comparators,
7418                ..
7419            }) if ops.len() == 1
7420                && matches!(ops[0], ast::CmpOp::Is | ast::CmpOp::IsNot)
7421                && comparators.len() == 1
7422                && matches!(&comparators[0], ast::Expr::NoneLiteral(_)) =>
7423            {
7424                self.compile_expression(left)?;
7425                let is_not = matches!(ops[0], ast::CmpOp::IsNot);
7426                // is None + jump_if_false → POP_JUMP_IF_NOT_NONE
7427                // is None + jump_if_true → POP_JUMP_IF_NONE
7428                // is not None + jump_if_false → POP_JUMP_IF_NONE
7429                // is not None + jump_if_true → POP_JUMP_IF_NOT_NONE
7430                let jump_if_none = condition != is_not;
7431                if jump_if_none {
7432                    emit!(
7433                        self,
7434                        Instruction::PopJumpIfNone {
7435                            delta: target_block,
7436                        }
7437                    );
7438                } else {
7439                    emit!(
7440                        self,
7441                        Instruction::PopJumpIfNotNone {
7442                            delta: target_block,
7443                        }
7444                    );
7445                }
7446                Ok(())
7447            }
7448            _ => {
7449                // Fall back case which always will work!
7450                self.compile_expression(expression)?;
7451                // Compare already produces a bool; everything else needs TO_BOOL
7452                if !matches!(expression, ast::Expr::Compare(_)) {
7453                    emit!(self, Instruction::ToBool);
7454                }
7455                if condition {
7456                    emit!(
7457                        self,
7458                        Instruction::PopJumpIfTrue {
7459                            delta: target_block,
7460                        }
7461                    );
7462                } else {
7463                    emit!(
7464                        self,
7465                        Instruction::PopJumpIfFalse {
7466                            delta: target_block,
7467                        }
7468                    );
7469                }
7470                Ok(())
7471            }
7472        };
7473
7474        self.set_source_range(prev_source_range);
7475        result
7476    }
7477
7478    /// Compile a boolean operation as an expression.
7479    /// This means, that the last value remains on the stack.
7480    fn compile_bool_op(&mut self, op: &ast::BoolOp, values: &[ast::Expr]) -> CompileResult<()> {
7481        self.compile_bool_op_with_target(op, values, None)
7482    }
7483
7484    /// Compile a boolean operation as an expression, with an optional
7485    /// short-circuit target override. When `short_circuit_target` is `Some`,
7486    /// the short-circuit jumps go to that block instead of the default
7487    /// `after_block`, enabling jump threading to avoid redundant `__bool__` calls.
7488    fn compile_bool_op_with_target(
7489        &mut self,
7490        op: &ast::BoolOp,
7491        values: &[ast::Expr],
7492        short_circuit_target: Option<BlockIdx>,
7493    ) -> CompileResult<()> {
7494        let after_block = self.new_block();
7495        let (last_value, values) = values.split_last().unwrap();
7496        let jump_target = short_circuit_target.unwrap_or(after_block);
7497
7498        for value in values {
7499            // Optimization: when a non-last value is a BoolOp with the opposite
7500            // operator, redirect its short-circuit exits to skip the outer's
7501            // redundant __bool__ test (jump threading).
7502            if short_circuit_target.is_none()
7503                && let ast::Expr::BoolOp(ast::ExprBoolOp {
7504                    op: inner_op,
7505                    values: inner_values,
7506                    ..
7507                }) = value
7508                && inner_op != op
7509            {
7510                let pop_block = self.new_block();
7511                self.compile_bool_op_with_target(inner_op, inner_values, Some(pop_block))?;
7512                self.emit_short_circuit_test(op, after_block);
7513                self.switch_to_block(pop_block);
7514                emit!(self, Instruction::PopTop);
7515                continue;
7516            }
7517
7518            self.compile_expression(value)?;
7519            self.emit_short_circuit_test(op, jump_target);
7520            emit!(self, Instruction::PopTop);
7521        }
7522
7523        // If all values did not qualify, take the value of the last value:
7524        self.compile_expression(last_value)?;
7525        self.switch_to_block(after_block);
7526        Ok(())
7527    }
7528
7529    /// Emit `Copy 1` + conditional jump for short-circuit evaluation.
7530    /// For `And`, emits `PopJumpIfFalse`; for `Or`, emits `PopJumpIfTrue`.
7531    fn emit_short_circuit_test(&mut self, op: &ast::BoolOp, target: BlockIdx) {
7532        emit!(self, Instruction::Copy { i: 1 });
7533        emit!(self, Instruction::ToBool);
7534        match op {
7535            ast::BoolOp::And => {
7536                emit!(self, Instruction::PopJumpIfFalse { delta: target });
7537            }
7538            ast::BoolOp::Or => {
7539                emit!(self, Instruction::PopJumpIfTrue { delta: target });
7540            }
7541        }
7542    }
7543
7544    fn compile_dict(&mut self, items: &[ast::DictItem]) -> CompileResult<()> {
7545        let has_unpacking = items.iter().any(|item| item.key.is_none());
7546
7547        if !has_unpacking {
7548            // Match CPython's compiler_subdict chunking strategy:
7549            // - n≤15: BUILD_MAP n (all pairs on stack)
7550            // - n>15: BUILD_MAP 0 + MAP_ADD chunks of 17, last chunk uses
7551            //   BUILD_MAP n (if ≤15) or BUILD_MAP 0 + MAP_ADD
7552            const STACK_LIMIT: usize = 15;
7553            const BIG_MAP_CHUNK: usize = 17;
7554
7555            if items.len() <= STACK_LIMIT {
7556                for item in items {
7557                    self.compile_expression(item.key.as_ref().unwrap())?;
7558                    self.compile_expression(&item.value)?;
7559                }
7560                emit!(
7561                    self,
7562                    Instruction::BuildMap {
7563                        count: u32::try_from(items.len()).expect("too many dict items"),
7564                    }
7565                );
7566            } else {
7567                // Split: leading full chunks of BIG_MAP_CHUNK via MAP_ADD,
7568                // remainder via BUILD_MAP n or MAP_ADD depending on size
7569                let n = items.len();
7570                let remainder = n % BIG_MAP_CHUNK;
7571                let n_big_chunks = n / BIG_MAP_CHUNK;
7572                // If remainder fits on stack (≤15), use BUILD_MAP n for it.
7573                // Otherwise it becomes another MAP_ADD chunk.
7574                let (big_count, tail_count) = if remainder > 0 && remainder <= STACK_LIMIT {
7575                    (n_big_chunks, remainder)
7576                } else {
7577                    // remainder is 0 or >15: all chunks are MAP_ADD chunks
7578                    let total_map_add = if remainder == 0 {
7579                        n_big_chunks
7580                    } else {
7581                        n_big_chunks + 1
7582                    };
7583                    (total_map_add, 0usize)
7584                };
7585
7586                emit!(self, Instruction::BuildMap { count: 0 });
7587
7588                let mut idx = 0;
7589                for chunk_i in 0..big_count {
7590                    if chunk_i > 0 {
7591                        emit!(self, Instruction::BuildMap { count: 0 });
7592                    }
7593                    let chunk_size = if idx + BIG_MAP_CHUNK <= n - tail_count {
7594                        BIG_MAP_CHUNK
7595                    } else {
7596                        n - tail_count - idx
7597                    };
7598                    for item in &items[idx..idx + chunk_size] {
7599                        self.compile_expression(item.key.as_ref().unwrap())?;
7600                        self.compile_expression(&item.value)?;
7601                        emit!(self, Instruction::MapAdd { i: 1 });
7602                    }
7603                    if chunk_i > 0 {
7604                        emit!(self, Instruction::DictUpdate { i: 1 });
7605                    }
7606                    idx += chunk_size;
7607                }
7608
7609                // Tail: remaining pairs via BUILD_MAP n + DICT_UPDATE
7610                if tail_count > 0 {
7611                    for item in &items[idx..idx + tail_count] {
7612                        self.compile_expression(item.key.as_ref().unwrap())?;
7613                        self.compile_expression(&item.value)?;
7614                    }
7615                    emit!(
7616                        self,
7617                        Instruction::BuildMap {
7618                            count: tail_count.to_u32(),
7619                        }
7620                    );
7621                    emit!(self, Instruction::DictUpdate { i: 1 });
7622                }
7623            }
7624            return Ok(());
7625        }
7626
7627        // Complex case with ** unpacking: preserve insertion order.
7628        // Collect runs of regular k:v pairs and emit BUILD_MAP + DICT_UPDATE
7629        // for each run, and DICT_UPDATE for each ** entry.
7630        let mut have_dict = false;
7631        let mut elements: u32 = 0;
7632
7633        // Flush pending regular pairs as a BUILD_MAP, merging into the
7634        // accumulator dict via DICT_UPDATE when one already exists.
7635        macro_rules! flush_pending {
7636            () => {
7637                #[allow(unused_assignments)]
7638                if elements > 0 {
7639                    emit!(self, Instruction::BuildMap { count: elements });
7640                    if have_dict {
7641                        emit!(self, Instruction::DictUpdate { i: 1 });
7642                    } else {
7643                        have_dict = true;
7644                    }
7645                    elements = 0;
7646                }
7647            };
7648        }
7649
7650        for item in items {
7651            if let Some(key) = &item.key {
7652                // Regular key: value pair
7653                self.compile_expression(key)?;
7654                self.compile_expression(&item.value)?;
7655                elements += 1;
7656            } else {
7657                // ** unpacking entry
7658                flush_pending!();
7659                if !have_dict {
7660                    emit!(self, Instruction::BuildMap { count: 0 });
7661                    have_dict = true;
7662                }
7663                self.compile_expression(&item.value)?;
7664                emit!(self, Instruction::DictUpdate { i: 1 });
7665            }
7666        }
7667
7668        flush_pending!();
7669        if !have_dict {
7670            emit!(self, Instruction::BuildMap { count: 0 });
7671        }
7672
7673        Ok(())
7674    }
7675
7676    /// Compile the yield-from/await sequence using SEND/END_SEND/CLEANUP_THROW.
7677    /// compiler_add_yield_from
7678    /// This generates:
7679    ///   send:
7680    ///     SEND exit
7681    ///     SETUP_FINALLY fail (via exception table)
7682    ///     YIELD_VALUE 1
7683    ///     POP_BLOCK (implicit)
7684    ///     RESUME
7685    ///     JUMP send
7686    ///   fail:
7687    ///     CLEANUP_THROW
7688    ///   exit:
7689    ///     END_SEND
7690    fn compile_yield_from_sequence(&mut self, is_await: bool) -> CompileResult<BlockIdx> {
7691        let send_block = self.new_block();
7692        let fail_block = self.new_block();
7693        let exit_block = self.new_block();
7694
7695        // send:
7696        self.switch_to_block(send_block);
7697        emit!(self, Instruction::Send { delta: exit_block });
7698
7699        // SETUP_FINALLY fail - set up exception handler for YIELD_VALUE
7700        emit!(self, PseudoInstruction::SetupFinally { delta: fail_block });
7701        self.push_fblock(
7702            FBlockType::TryExcept, // Use TryExcept for exception handler
7703            send_block,
7704            exit_block,
7705        )?;
7706
7707        // YIELD_VALUE with arg=1 (yield-from/await mode - not wrapped for async gen)
7708        emit!(self, Instruction::YieldValue { arg: 1 });
7709
7710        // POP_BLOCK before RESUME
7711        emit!(self, PseudoInstruction::PopBlock);
7712        self.pop_fblock(FBlockType::TryExcept);
7713
7714        // RESUME
7715        emit!(
7716            self,
7717            Instruction::Resume {
7718                context: if is_await {
7719                    oparg::ResumeContext::from(oparg::ResumeLocation::AfterAwait)
7720                } else {
7721                    oparg::ResumeContext::from(oparg::ResumeLocation::AfterYieldFrom)
7722                }
7723            }
7724        );
7725
7726        // JUMP_BACKWARD_NO_INTERRUPT send
7727        emit!(
7728            self,
7729            PseudoInstruction::JumpNoInterrupt { delta: send_block }
7730        );
7731
7732        // fail: CLEANUP_THROW
7733        // Stack when exception: [receiver, yielded_value, exc]
7734        // CLEANUP_THROW: [sub_iter, last_sent_val, exc] -> [None, value]
7735        // After: stack is [None, value], fall through to exit
7736        self.switch_to_block(fail_block);
7737        emit!(self, Instruction::CleanupThrow);
7738        // Fall through to exit block
7739
7740        // exit: END_SEND
7741        // Stack: [receiver, value] (from SEND) or [None, value] (from CLEANUP_THROW)
7742        // END_SEND: [receiver/None, value] -> [value]
7743        self.switch_to_block(exit_block);
7744        emit!(self, Instruction::EndSend);
7745
7746        Ok(send_block)
7747    }
7748
7749    /// Returns true if the expression is a constant with no side effects.
7750    fn is_const_expression(expr: &ast::Expr) -> bool {
7751        matches!(
7752            expr,
7753            ast::Expr::StringLiteral(_)
7754                | ast::Expr::BytesLiteral(_)
7755                | ast::Expr::NumberLiteral(_)
7756                | ast::Expr::BooleanLiteral(_)
7757                | ast::Expr::NoneLiteral(_)
7758                | ast::Expr::EllipsisLiteral(_)
7759        ) || matches!(expr, ast::Expr::FString(fstring) if Self::fstring_value_is_const(&fstring.value))
7760    }
7761
7762    fn fstring_value_is_const(fstring: &ast::FStringValue) -> bool {
7763        for part in fstring {
7764            if !Self::fstring_part_is_const(part) {
7765                return false;
7766            }
7767        }
7768        true
7769    }
7770
7771    fn fstring_part_is_const(part: &ast::FStringPart) -> bool {
7772        match part {
7773            ast::FStringPart::Literal(_) => true,
7774            ast::FStringPart::FString(fstring) => fstring
7775                .elements
7776                .iter()
7777                .all(|element| matches!(element, ast::InterpolatedStringElement::Literal(_))),
7778        }
7779    }
7780
7781    fn compile_expression(&mut self, expression: &ast::Expr) -> CompileResult<()> {
7782        trace!("Compiling {expression:?}");
7783        let range = expression.range();
7784        self.set_source_range(range);
7785
7786        match &expression {
7787            ast::Expr::Call(ast::ExprCall {
7788                func, arguments, ..
7789            }) => self.compile_call(func, arguments)?,
7790            ast::Expr::BoolOp(ast::ExprBoolOp { op, values, .. }) => {
7791                self.compile_bool_op(op, values)?
7792            }
7793            ast::Expr::BinOp(ast::ExprBinOp {
7794                left, op, right, ..
7795            }) => {
7796                // optimize_format_str: 'format' % (args,) → f-string bytecode
7797                if matches!(op, ast::Operator::Mod)
7798                    && let ast::Expr::StringLiteral(s) = left.as_ref()
7799                    && let ast::Expr::Tuple(ast::ExprTuple { elts, .. }) = right.as_ref()
7800                    && !elts.iter().any(|e| matches!(e, ast::Expr::Starred(_)))
7801                    && self.try_optimize_format_str(s.value.to_str(), elts, range)?
7802                {
7803                    return Ok(());
7804                }
7805                self.compile_expression(left)?;
7806                self.compile_expression(right)?;
7807
7808                // Restore full expression range before emitting the operation
7809                self.set_source_range(range);
7810                self.compile_op(op, false);
7811            }
7812            ast::Expr::Subscript(ast::ExprSubscript {
7813                value, slice, ctx, ..
7814            }) => {
7815                self.compile_subscript(value, slice, *ctx)?;
7816            }
7817            ast::Expr::UnaryOp(ast::ExprUnaryOp { op, operand, .. }) => {
7818                self.compile_expression(operand)?;
7819
7820                // Restore full expression range before emitting the operation
7821                self.set_source_range(range);
7822                match op {
7823                    ast::UnaryOp::UAdd => emit!(
7824                        self,
7825                        Instruction::CallIntrinsic1 {
7826                            func: bytecode::IntrinsicFunction1::UnaryPositive
7827                        }
7828                    ),
7829                    ast::UnaryOp::USub => emit!(self, Instruction::UnaryNegative),
7830                    ast::UnaryOp::Not => {
7831                        emit!(self, Instruction::ToBool);
7832                        emit!(self, Instruction::UnaryNot);
7833                    }
7834                    ast::UnaryOp::Invert => emit!(self, Instruction::UnaryInvert),
7835                };
7836            }
7837            ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
7838                // Check for super() attribute access optimization
7839                if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
7840                    // super().attr or super(cls, self).attr optimization
7841                    // Stack: [global_super, class, self] → LOAD_SUPER_ATTR → [attr]
7842                    // Set source range to super() call for arg-loading instructions
7843                    let super_range = value.range();
7844                    self.set_source_range(super_range);
7845                    self.load_args_for_super(&super_type)?;
7846                    self.set_source_range(super_range);
7847                    let idx = self.name(attr.as_str());
7848                    match super_type {
7849                        SuperCallType::TwoArg { .. } => {
7850                            self.emit_load_super_attr(idx);
7851                        }
7852                        SuperCallType::ZeroArg => {
7853                            self.emit_load_zero_super_attr(idx);
7854                        }
7855                    }
7856                } else {
7857                    // Normal attribute access
7858                    self.compile_expression(value)?;
7859                    let idx = self.name(attr.as_str());
7860                    self.emit_load_attr(idx);
7861                }
7862            }
7863            ast::Expr::Compare(ast::ExprCompare {
7864                left,
7865                ops,
7866                comparators,
7867                ..
7868            }) => {
7869                self.compile_compare(left, ops, comparators)?;
7870            }
7871            // ast::Expr::Constant(ExprConstant { value, .. }) => {
7872            //     self.emit_load_const(compile_constant(value));
7873            // }
7874            ast::Expr::List(ast::ExprList { elts, .. }) => {
7875                self.starunpack_helper(elts, 0, CollectionType::List)?;
7876            }
7877            ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
7878                self.starunpack_helper(elts, 0, CollectionType::Tuple)?;
7879            }
7880            ast::Expr::Set(ast::ExprSet { elts, .. }) => {
7881                self.starunpack_helper(elts, 0, CollectionType::Set)?;
7882            }
7883            ast::Expr::Dict(ast::ExprDict { items, .. }) => {
7884                self.compile_dict(items)?;
7885            }
7886            ast::Expr::Slice(ast::ExprSlice {
7887                lower, upper, step, ..
7888            }) => {
7889                // Try constant slice folding first
7890                if self.try_fold_constant_slice(
7891                    lower.as_deref(),
7892                    upper.as_deref(),
7893                    step.as_deref(),
7894                )? {
7895                    return Ok(());
7896                }
7897                let mut compile_bound = |bound: Option<&ast::Expr>| match bound {
7898                    Some(exp) => self.compile_expression(exp),
7899                    None => {
7900                        self.emit_load_const(ConstantData::None);
7901                        Ok(())
7902                    }
7903                };
7904                compile_bound(lower.as_deref())?;
7905                compile_bound(upper.as_deref())?;
7906                if let Some(step) = step {
7907                    self.compile_expression(step)?;
7908                }
7909                let argc = match step {
7910                    Some(_) => BuildSliceArgCount::Three,
7911                    None => BuildSliceArgCount::Two,
7912                };
7913                emit!(self, Instruction::BuildSlice { argc });
7914            }
7915            ast::Expr::Yield(ast::ExprYield { value, .. }) => {
7916                if !self.ctx.in_func() {
7917                    return Err(self.error(CodegenErrorType::InvalidYield));
7918                }
7919                self.mark_generator();
7920                match value {
7921                    Some(expression) => self.compile_expression(expression)?,
7922                    Option::None => self.emit_load_const(ConstantData::None),
7923                };
7924                if self.ctx.func == FunctionContext::AsyncFunction {
7925                    emit!(
7926                        self,
7927                        Instruction::CallIntrinsic1 {
7928                            func: bytecode::IntrinsicFunction1::AsyncGenWrap
7929                        }
7930                    );
7931                }
7932                // arg=0: direct yield (wrapped for async generators)
7933                emit!(self, Instruction::YieldValue { arg: 0 });
7934                emit!(
7935                    self,
7936                    Instruction::Resume {
7937                        context: oparg::ResumeContext::from(oparg::ResumeLocation::AfterYield)
7938                    }
7939                );
7940            }
7941            ast::Expr::Await(ast::ExprAwait { value, .. }) => {
7942                if self.ctx.func != FunctionContext::AsyncFunction {
7943                    return Err(self.error(CodegenErrorType::InvalidAwait));
7944                }
7945                self.compile_expression(value)?;
7946                emit!(self, Instruction::GetAwaitable { r#where: 0 });
7947                self.emit_load_const(ConstantData::None);
7948                let _ = self.compile_yield_from_sequence(true)?;
7949            }
7950            ast::Expr::YieldFrom(ast::ExprYieldFrom { value, .. }) => {
7951                match self.ctx.func {
7952                    FunctionContext::NoFunction => {
7953                        return Err(self.error(CodegenErrorType::InvalidYieldFrom));
7954                    }
7955                    FunctionContext::AsyncFunction => {
7956                        return Err(self.error(CodegenErrorType::AsyncYieldFrom));
7957                    }
7958                    FunctionContext::Function => {}
7959                }
7960                self.mark_generator();
7961                self.compile_expression(value)?;
7962                emit!(self, Instruction::GetYieldFromIter);
7963                self.emit_load_const(ConstantData::None);
7964                let _ = self.compile_yield_from_sequence(false)?;
7965            }
7966            ast::Expr::Name(ast::ExprName { id, .. }) => self.load_name(id.as_str())?,
7967            ast::Expr::Lambda(ast::ExprLambda {
7968                parameters, body, ..
7969            }) => {
7970                let default_params = ast::Parameters::default();
7971                let params = parameters.as_deref().unwrap_or(&default_params);
7972                validate_duplicate_params(params).map_err(|e| self.error(e))?;
7973
7974                let prev_ctx = self.ctx;
7975                let name = "<lambda>".to_owned();
7976
7977                // Prepare defaults before entering function
7978                let defaults: Vec<_> = core::iter::empty()
7979                    .chain(&params.posonlyargs)
7980                    .chain(&params.args)
7981                    .filter_map(|x| x.default.as_deref())
7982                    .collect();
7983                let have_defaults = !defaults.is_empty();
7984
7985                if have_defaults {
7986                    let size = defaults.len().to_u32();
7987                    for element in &defaults {
7988                        self.compile_expression(element)?;
7989                    }
7990                    emit!(self, Instruction::BuildTuple { count: size });
7991                }
7992
7993                // Prepare keyword-only defaults
7994                let mut kw_with_defaults = vec![];
7995                for kwonlyarg in &params.kwonlyargs {
7996                    if let Some(default) = &kwonlyarg.default {
7997                        kw_with_defaults.push((&kwonlyarg.parameter, default));
7998                    }
7999                }
8000
8001                let have_kwdefaults = !kw_with_defaults.is_empty();
8002                if have_kwdefaults {
8003                    let default_kw_count = kw_with_defaults.len();
8004                    for (arg, default) in &kw_with_defaults {
8005                        self.emit_load_const(ConstantData::Str {
8006                            value: self.mangle(arg.name.as_str()).into_owned().into(),
8007                        });
8008                        self.compile_expression(default)?;
8009                    }
8010                    emit!(
8011                        self,
8012                        Instruction::BuildMap {
8013                            count: default_kw_count.to_u32(),
8014                        }
8015                    );
8016                }
8017
8018                self.enter_function(&name, params)?;
8019                let mut func_flags = bytecode::MakeFunctionFlags::new();
8020                if have_defaults {
8021                    func_flags.insert(bytecode::MakeFunctionFlag::Defaults);
8022                }
8023                if have_kwdefaults {
8024                    func_flags.insert(bytecode::MakeFunctionFlag::KwOnlyDefaults);
8025                }
8026
8027                // Set qualname for lambda
8028                self.set_qualname();
8029
8030                self.ctx = CompileContext {
8031                    loop_data: Option::None,
8032                    in_class: prev_ctx.in_class,
8033                    func: FunctionContext::Function,
8034                    // Lambda is never async, so new scope is not async
8035                    in_async_scope: false,
8036                };
8037
8038                // Lambda cannot have docstrings, so no None is added to co_consts
8039
8040                self.compile_expression(body)?;
8041                self.emit_return_value();
8042                let code = self.exit_scope();
8043
8044                // Create lambda function with closure
8045                self.make_closure(code, func_flags)?;
8046
8047                self.ctx = prev_ctx;
8048            }
8049            ast::Expr::ListComp(ast::ExprListComp {
8050                elt, generators, ..
8051            }) => {
8052                self.compile_comprehension(
8053                    "<listcomp>",
8054                    Some(
8055                        Instruction::BuildList {
8056                            count: OpArgMarker::marker(),
8057                        }
8058                        .into(),
8059                    ),
8060                    generators,
8061                    &|compiler| {
8062                        compiler.compile_comprehension_element(elt)?;
8063                        emit!(
8064                            compiler,
8065                            Instruction::ListAppend {
8066                                i: (generators.len() + 1).to_u32(),
8067                            }
8068                        );
8069                        Ok(())
8070                    },
8071                    ComprehensionType::List,
8072                    Self::contains_await(elt) || Self::generators_contain_await(generators),
8073                )?;
8074            }
8075            ast::Expr::SetComp(ast::ExprSetComp {
8076                elt, generators, ..
8077            }) => {
8078                self.compile_comprehension(
8079                    "<setcomp>",
8080                    Some(
8081                        Instruction::BuildSet {
8082                            count: OpArgMarker::marker(),
8083                        }
8084                        .into(),
8085                    ),
8086                    generators,
8087                    &|compiler| {
8088                        compiler.compile_comprehension_element(elt)?;
8089                        emit!(
8090                            compiler,
8091                            Instruction::SetAdd {
8092                                i: (generators.len() + 1).to_u32(),
8093                            }
8094                        );
8095                        Ok(())
8096                    },
8097                    ComprehensionType::Set,
8098                    Self::contains_await(elt) || Self::generators_contain_await(generators),
8099                )?;
8100            }
8101            ast::Expr::DictComp(ast::ExprDictComp {
8102                key,
8103                value,
8104                generators,
8105                ..
8106            }) => {
8107                self.compile_comprehension(
8108                    "<dictcomp>",
8109                    Some(
8110                        Instruction::BuildMap {
8111                            count: OpArgMarker::marker(),
8112                        }
8113                        .into(),
8114                    ),
8115                    generators,
8116                    &|compiler| {
8117                        // changed evaluation order for Py38 named expression PEP 572
8118                        compiler.compile_expression(key)?;
8119                        compiler.compile_expression(value)?;
8120
8121                        emit!(
8122                            compiler,
8123                            Instruction::MapAdd {
8124                                i: (generators.len() + 1).to_u32(),
8125                            }
8126                        );
8127
8128                        Ok(())
8129                    },
8130                    ComprehensionType::Dict,
8131                    Self::contains_await(key)
8132                        || Self::contains_await(value)
8133                        || Self::generators_contain_await(generators),
8134                )?;
8135            }
8136            ast::Expr::Generator(ast::ExprGenerator {
8137                elt, generators, ..
8138            }) => {
8139                // Check if element or generators contain async content
8140                // This makes the generator expression into an async generator
8141                let element_contains_await =
8142                    Self::contains_await(elt) || Self::generators_contain_await(generators);
8143                self.compile_comprehension(
8144                    "<genexpr>",
8145                    None,
8146                    generators,
8147                    &|compiler| {
8148                        // Compile the element expression
8149                        // Note: if element is an async comprehension, compile_expression
8150                        // already handles awaiting it, so we don't need to await again here
8151                        compiler.compile_comprehension_element(elt)?;
8152
8153                        compiler.mark_generator();
8154                        if compiler.ctx.func == FunctionContext::AsyncFunction {
8155                            emit!(
8156                                compiler,
8157                                Instruction::CallIntrinsic1 {
8158                                    func: bytecode::IntrinsicFunction1::AsyncGenWrap
8159                                }
8160                            );
8161                        }
8162                        // arg=0: direct yield (wrapped for async generators)
8163                        emit!(compiler, Instruction::YieldValue { arg: 0 });
8164                        emit!(
8165                            compiler,
8166                            Instruction::Resume {
8167                                context: oparg::ResumeContext::from(
8168                                    oparg::ResumeLocation::AfterYield
8169                                )
8170                            }
8171                        );
8172                        emit!(compiler, Instruction::PopTop);
8173
8174                        Ok(())
8175                    },
8176                    ComprehensionType::Generator,
8177                    element_contains_await,
8178                )?;
8179            }
8180            ast::Expr::Starred(ast::ExprStarred { value, .. }) => {
8181                if self.in_annotation {
8182                    // In annotation context, starred expressions are allowed (PEP 646)
8183                    // For now, just compile the inner value without wrapping with Unpack
8184                    // This is a temporary solution until we figure out how to properly import typing
8185                    self.compile_expression(value)?;
8186                } else {
8187                    return Err(self.error(CodegenErrorType::InvalidStarExpr));
8188                }
8189            }
8190            ast::Expr::If(ast::ExprIf {
8191                test, body, orelse, ..
8192            }) => {
8193                let else_block = self.new_block();
8194                let after_block = self.new_block();
8195                self.compile_jump_if(test, false, else_block)?;
8196
8197                // True case
8198                self.compile_expression(body)?;
8199                emit!(self, PseudoInstruction::Jump { delta: after_block });
8200
8201                // False case
8202                self.switch_to_block(else_block);
8203                self.compile_expression(orelse)?;
8204
8205                // End
8206                self.switch_to_block(after_block);
8207            }
8208
8209            ast::Expr::Named(ast::ExprNamed {
8210                target,
8211                value,
8212                node_index: _,
8213                range: _,
8214            }) => {
8215                // Walrus targets in inlined comps should NOT be hidden from locals()
8216                if self.current_code_info().in_inlined_comp
8217                    && let ast::Expr::Name(ast::ExprName { id, .. }) = target.as_ref()
8218                {
8219                    let name = self.mangle(id.as_str());
8220                    let info = self.code_stack.last_mut().unwrap();
8221                    info.metadata.fast_hidden.insert(name.to_string(), false);
8222                }
8223                self.compile_expression(value)?;
8224                emit!(self, Instruction::Copy { i: 1 });
8225                self.compile_store(target)?;
8226            }
8227            ast::Expr::FString(fstring) => {
8228                self.compile_expr_fstring(fstring)?;
8229            }
8230            ast::Expr::TString(tstring) => {
8231                self.compile_expr_tstring(tstring)?;
8232            }
8233            ast::Expr::StringLiteral(string) => {
8234                let value = self.compile_string_value(string);
8235                self.emit_load_const(ConstantData::Str { value });
8236            }
8237            ast::Expr::BytesLiteral(bytes) => {
8238                let iter = bytes.value.iter().flat_map(|x| x.iter().copied());
8239                let v: Vec<u8> = iter.collect();
8240                self.emit_load_const(ConstantData::Bytes { value: v });
8241            }
8242            ast::Expr::NumberLiteral(number) => match &number.value {
8243                ast::Number::Int(int) => {
8244                    let value = ruff_int_to_bigint(int).map_err(|e| self.error(e))?;
8245                    self.emit_load_const(ConstantData::Integer { value });
8246                }
8247                ast::Number::Float(float) => {
8248                    self.emit_load_const(ConstantData::Float { value: *float });
8249                }
8250                ast::Number::Complex { real, imag } => {
8251                    self.emit_load_const(ConstantData::Complex {
8252                        value: Complex::new(*real, *imag),
8253                    });
8254                }
8255            },
8256            ast::Expr::BooleanLiteral(b) => {
8257                self.emit_load_const(ConstantData::Boolean { value: b.value });
8258            }
8259            ast::Expr::NoneLiteral(_) => {
8260                self.emit_load_const(ConstantData::None);
8261            }
8262            ast::Expr::EllipsisLiteral(_) => {
8263                self.emit_load_const(ConstantData::Ellipsis);
8264            }
8265            ast::Expr::IpyEscapeCommand(_) => {
8266                panic!("unexpected ipy escape command");
8267            }
8268        }
8269        Ok(())
8270    }
8271
8272    fn compile_keywords(&mut self, keywords: &[ast::Keyword]) -> CompileResult<()> {
8273        let mut size = 0;
8274        let groupby = keywords.iter().chunk_by(|e| e.arg.is_none());
8275        for (is_unpacking, sub_keywords) in &groupby {
8276            if is_unpacking {
8277                for keyword in sub_keywords {
8278                    self.compile_expression(&keyword.value)?;
8279                    size += 1;
8280                }
8281            } else {
8282                let mut sub_size = 0;
8283                for keyword in sub_keywords {
8284                    if let Some(name) = &keyword.arg {
8285                        self.emit_load_const(ConstantData::Str {
8286                            value: name.as_str().into(),
8287                        });
8288                        self.compile_expression(&keyword.value)?;
8289                        sub_size += 1;
8290                    }
8291                }
8292                emit!(self, Instruction::BuildMap { count: sub_size });
8293                size += 1;
8294            }
8295        }
8296        if size > 1 {
8297            // Merge all dicts: first dict is accumulator, merge rest into it
8298            for _ in 1..size {
8299                emit!(self, Instruction::DictMerge { i: 1 });
8300            }
8301        }
8302        Ok(())
8303    }
8304
8305    fn detect_builtin_generator_call(
8306        &self,
8307        func: &ast::Expr,
8308        args: &ast::Arguments,
8309    ) -> Option<BuiltinGeneratorCallKind> {
8310        let ast::Expr::Name(ast::ExprName { id, .. }) = func else {
8311            return None;
8312        };
8313        if args.args.len() != 1
8314            || !args.keywords.is_empty()
8315            || !matches!(args.args[0], ast::Expr::Generator(_))
8316        {
8317            return None;
8318        }
8319        match id.as_str() {
8320            "tuple" => Some(BuiltinGeneratorCallKind::Tuple),
8321            "list" => Some(BuiltinGeneratorCallKind::List),
8322            "set" => Some(BuiltinGeneratorCallKind::Set),
8323            "all" => Some(BuiltinGeneratorCallKind::All),
8324            "any" => Some(BuiltinGeneratorCallKind::Any),
8325            _ => None,
8326        }
8327    }
8328
8329    /// Emit the optimized inline loop for builtin(genexpr) calls.
8330    ///
8331    /// Stack on entry: `[func, iter]` where `iter` is the already-compiled
8332    /// generator iterator and `func` is the builtin candidate.
8333    /// On return the compiler is positioned at the fallback block with
8334    /// `[func, iter]` still on the stack (for the normal CALL path).
8335    fn optimize_builtin_generator_call(
8336        &mut self,
8337        kind: BuiltinGeneratorCallKind,
8338        end: BlockIdx,
8339    ) -> CompileResult<()> {
8340        let common_constant = match kind {
8341            BuiltinGeneratorCallKind::Tuple => bytecode::CommonConstant::BuiltinTuple,
8342            BuiltinGeneratorCallKind::List => bytecode::CommonConstant::BuiltinList,
8343            BuiltinGeneratorCallKind::Set => bytecode::CommonConstant::BuiltinSet,
8344            BuiltinGeneratorCallKind::All => bytecode::CommonConstant::BuiltinAll,
8345            BuiltinGeneratorCallKind::Any => bytecode::CommonConstant::BuiltinAny,
8346        };
8347
8348        let loop_block = self.new_block();
8349        let cleanup = self.new_block();
8350        let fallback = self.new_block();
8351        let result = matches!(
8352            kind,
8353            BuiltinGeneratorCallKind::All | BuiltinGeneratorCallKind::Any
8354        )
8355        .then(|| self.new_block());
8356
8357        // Stack: [func, iter] — copy func (TOS1) for identity check
8358        emit!(self, Instruction::Copy { i: 2 });
8359        emit!(
8360            self,
8361            Instruction::LoadCommonConstant {
8362                idx: common_constant
8363            }
8364        );
8365        emit!(self, Instruction::IsOp { invert: Invert::No });
8366        emit!(self, Instruction::PopJumpIfFalse { delta: fallback });
8367        emit!(self, Instruction::NotTaken);
8368        // Remove func from [func, iter] → [iter]
8369        emit!(self, Instruction::Swap { i: 2 });
8370        emit!(self, Instruction::PopTop);
8371
8372        if matches!(
8373            kind,
8374            BuiltinGeneratorCallKind::Tuple | BuiltinGeneratorCallKind::List
8375        ) {
8376            // [iter] → [iter, list] → [list, iter]
8377            emit!(self, Instruction::BuildList { count: 0 });
8378            emit!(self, Instruction::Swap { i: 2 });
8379        } else if matches!(kind, BuiltinGeneratorCallKind::Set) {
8380            // [iter] → [iter, set] → [set, iter]
8381            emit!(self, Instruction::BuildSet { count: 0 });
8382            emit!(self, Instruction::Swap { i: 2 });
8383        }
8384
8385        self.switch_to_block(loop_block);
8386        emit!(self, Instruction::ForIter { delta: cleanup });
8387
8388        match kind {
8389            BuiltinGeneratorCallKind::Tuple | BuiltinGeneratorCallKind::List => {
8390                emit!(self, Instruction::ListAppend { i: 2 });
8391                emit!(self, PseudoInstruction::Jump { delta: loop_block });
8392            }
8393            BuiltinGeneratorCallKind::Set => {
8394                emit!(self, Instruction::SetAdd { i: 2 });
8395                emit!(self, PseudoInstruction::Jump { delta: loop_block });
8396            }
8397            BuiltinGeneratorCallKind::All => {
8398                let result = result.expect("all() optimization should have a result block");
8399                emit!(self, Instruction::ToBool);
8400                emit!(self, Instruction::PopJumpIfFalse { delta: result });
8401                emit!(self, Instruction::NotTaken);
8402                emit!(self, PseudoInstruction::Jump { delta: loop_block });
8403            }
8404            BuiltinGeneratorCallKind::Any => {
8405                let result = result.expect("any() optimization should have a result block");
8406                emit!(self, Instruction::ToBool);
8407                emit!(self, Instruction::PopJumpIfTrue { delta: result });
8408                emit!(self, Instruction::NotTaken);
8409                emit!(self, PseudoInstruction::Jump { delta: loop_block });
8410            }
8411        }
8412
8413        if let Some(result_block) = result {
8414            self.switch_to_block(result_block);
8415            emit!(self, Instruction::PopIter);
8416            self.emit_load_const(ConstantData::Boolean {
8417                value: matches!(kind, BuiltinGeneratorCallKind::Any),
8418            });
8419            emit!(self, PseudoInstruction::Jump { delta: end });
8420        }
8421
8422        self.switch_to_block(cleanup);
8423        emit!(self, Instruction::EndFor);
8424        emit!(self, Instruction::PopIter);
8425        match kind {
8426            BuiltinGeneratorCallKind::Tuple => {
8427                emit!(
8428                    self,
8429                    Instruction::CallIntrinsic1 {
8430                        func: IntrinsicFunction1::ListToTuple
8431                    }
8432                );
8433            }
8434            BuiltinGeneratorCallKind::List | BuiltinGeneratorCallKind::Set => {}
8435            BuiltinGeneratorCallKind::All => {
8436                self.emit_load_const(ConstantData::Boolean { value: true });
8437            }
8438            BuiltinGeneratorCallKind::Any => {
8439                self.emit_load_const(ConstantData::Boolean { value: false });
8440            }
8441        }
8442        emit!(self, PseudoInstruction::Jump { delta: end });
8443
8444        self.switch_to_block(fallback);
8445        Ok(())
8446    }
8447
8448    fn compile_call(&mut self, func: &ast::Expr, args: &ast::Arguments) -> CompileResult<()> {
8449        // Save the call expression's source range so CALL instructions use the
8450        // call start line, not the last argument's line.
8451        let call_range = self.current_source_range;
8452        let uses_ex_call = self.call_uses_ex_call(args);
8453
8454        // Method call: obj → LOAD_ATTR_METHOD → [method, self_or_null] → args → CALL
8455        // Regular call: func → PUSH_NULL → args → CALL
8456        if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = &func {
8457            // Check for super() method call optimization
8458            if !uses_ex_call
8459                && let Some(super_type) = self.can_optimize_super_call(value, attr.as_str())
8460            {
8461                // super().method() or super(cls, self).method() optimization
8462                // Stack: [global_super, class, self] → LOAD_SUPER_METHOD → [method, self]
8463                // Set source range to the super() call for LOAD_GLOBAL/LOAD_DEREF/etc.
8464                let super_range = value.range();
8465                self.set_source_range(super_range);
8466                self.load_args_for_super(&super_type)?;
8467                self.set_source_range(super_range);
8468                let idx = self.name(attr.as_str());
8469                match super_type {
8470                    SuperCallType::TwoArg { .. } => {
8471                        self.emit_load_super_method(idx);
8472                    }
8473                    SuperCallType::ZeroArg => {
8474                        self.emit_load_zero_super_method(idx);
8475                    }
8476                }
8477                // NOP for line tracking at .method( line
8478                self.set_source_range(attr.range());
8479                emit!(self, Instruction::Nop);
8480                // CALL at .method( line (not the full expression line)
8481                self.codegen_call_helper(0, args, attr.range())?;
8482            } else {
8483                self.compile_expression(value)?;
8484                let idx = self.name(attr.as_str());
8485                // Imported names and CALL_FUNCTION_EX-style calls use plain
8486                // LOAD_ATTR + PUSH_NULL; other names use method-call mode.
8487                // Check current scope and enclosing scopes for IMPORTED flag.
8488                let is_import = matches!(value.as_ref(), ast::Expr::Name(ast::ExprName { id, .. })
8489                    if self.is_name_imported(id.as_str()));
8490                if is_import || uses_ex_call {
8491                    self.emit_load_attr(idx);
8492                    emit!(self, Instruction::PushNull);
8493                } else {
8494                    self.emit_load_attr_method(idx);
8495                }
8496                self.codegen_call_helper(0, args, call_range)?;
8497            }
8498        } else if let Some(kind) = (!uses_ex_call)
8499            .then(|| self.detect_builtin_generator_call(func, args))
8500            .flatten()
8501        {
8502            // Optimized builtin(genexpr) path: compile the genexpr only once
8503            // so its code object appears exactly once in co_consts.
8504            let end = self.new_block();
8505            self.compile_expression(func)?;
8506            self.compile_expression(&args.args[0])?;
8507            // Stack: [func, iter]
8508            self.optimize_builtin_generator_call(kind, end)?;
8509            // Fallback block: [func, iter] → [func, null, iter] → CALL
8510            emit!(self, Instruction::PushNull);
8511            emit!(self, Instruction::Swap { i: 2 });
8512            self.set_source_range(call_range);
8513            emit!(self, Instruction::Call { argc: 1 });
8514            self.switch_to_block(end);
8515        } else {
8516            // Regular call: push func, then NULL for self_or_null slot
8517            // Stack layout: [func, NULL, args...] - same as method call [func, self, args...]
8518            self.compile_expression(func)?;
8519            emit!(self, Instruction::PushNull);
8520            self.codegen_call_helper(0, args, call_range)?;
8521        }
8522        Ok(())
8523    }
8524
8525    fn call_uses_ex_call(&self, arguments: &ast::Arguments) -> bool {
8526        let has_starred = arguments
8527            .args
8528            .iter()
8529            .any(|arg| matches!(arg, ast::Expr::Starred(_)));
8530        let has_double_star = arguments.keywords.iter().any(|k| k.arg.is_none());
8531        let too_big = arguments.args.len() + arguments.keywords.len() > 15;
8532        has_starred || has_double_star || too_big
8533    }
8534
8535    /// Compile subkwargs: emit key-value pairs for BUILD_MAP
8536    fn codegen_subkwargs(
8537        &mut self,
8538        keywords: &[ast::Keyword],
8539        begin: usize,
8540        end: usize,
8541    ) -> CompileResult<()> {
8542        let n = end - begin;
8543        assert!(n > 0);
8544
8545        // For large kwargs, use BUILD_MAP(0) + MAP_ADD to avoid stack overflow
8546        let big = n * 2 > 8; // STACK_USE_GUIDELINE approximation
8547
8548        if big {
8549            emit!(self, Instruction::BuildMap { count: 0 });
8550        }
8551
8552        for kw in &keywords[begin..end] {
8553            // Key first, then value - this is critical!
8554            self.emit_load_const(ConstantData::Str {
8555                value: kw.arg.as_ref().unwrap().as_str().into(),
8556            });
8557            self.compile_expression(&kw.value)?;
8558
8559            if big {
8560                emit!(self, Instruction::MapAdd { i: 1 });
8561            }
8562        }
8563
8564        if !big {
8565            emit!(self, Instruction::BuildMap { count: n.to_u32() });
8566        }
8567
8568        Ok(())
8569    }
8570
8571    /// Compile call arguments and emit the appropriate CALL instruction.
8572    /// `call_range` is the source range of the call expression, used to set
8573    /// the correct line number on the CALL instruction.
8574    fn codegen_call_helper(
8575        &mut self,
8576        additional_positional: u32,
8577        arguments: &ast::Arguments,
8578        call_range: TextRange,
8579    ) -> CompileResult<()> {
8580        let nelts = arguments.args.len();
8581        let nkwelts = arguments.keywords.len();
8582
8583        // Check if we have starred args or **kwargs
8584        let has_starred = arguments
8585            .args
8586            .iter()
8587            .any(|arg| matches!(arg, ast::Expr::Starred(_)));
8588        let has_double_star = arguments.keywords.iter().any(|k| k.arg.is_none());
8589
8590        // Check if exceeds stack guideline (STACK_USE_GUIDELINE / 2 = 15)
8591        // With CALL_KW, kwargs values go on stack but keys go in a const tuple,
8592        // so stack usage is: func + null + positional_args + kwarg_values + kwnames_tuple
8593        let too_big = nelts + nkwelts > 15;
8594
8595        if !has_starred && !has_double_star && !too_big {
8596            // Simple call path: no * or ** args
8597            for arg in &arguments.args {
8598                self.compile_expression(arg)?;
8599            }
8600
8601            if nkwelts > 0 {
8602                // Compile keyword values and build kwnames tuple
8603                let mut kwarg_names = Vec::with_capacity(nkwelts);
8604                for keyword in &arguments.keywords {
8605                    kwarg_names.push(ConstantData::Str {
8606                        value: keyword.arg.as_ref().unwrap().as_str().into(),
8607                    });
8608                    self.compile_expression(&keyword.value)?;
8609                }
8610
8611                // Restore call expression range for kwnames and CALL_KW
8612                self.set_source_range(call_range);
8613                self.emit_load_const(ConstantData::Tuple {
8614                    elements: kwarg_names,
8615                });
8616
8617                let argc = additional_positional + nelts.to_u32() + nkwelts.to_u32();
8618                emit!(self, Instruction::CallKw { argc });
8619            } else {
8620                self.set_source_range(call_range);
8621                let argc = additional_positional + nelts.to_u32();
8622                emit!(self, Instruction::Call { argc });
8623            }
8624        } else {
8625            // ex_call path: has * or ** args
8626
8627            // Compile positional arguments
8628            if additional_positional == 0
8629                && nelts == 1
8630                && matches!(arguments.args[0], ast::Expr::Starred(_))
8631            {
8632                // Single starred arg: pass value directly to CallFunctionEx.
8633                // Runtime will convert to tuple and validate with function name.
8634                if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = &arguments.args[0] {
8635                    self.compile_expression(value)?;
8636                }
8637            } else if !has_starred {
8638                for arg in &arguments.args {
8639                    self.compile_expression(arg)?;
8640                }
8641                self.set_source_range(call_range);
8642                let positional_count = additional_positional + nelts.to_u32();
8643                if positional_count == 0 {
8644                    self.emit_load_const(ConstantData::Tuple { elements: vec![] });
8645                } else {
8646                    emit!(
8647                        self,
8648                        Instruction::BuildTuple {
8649                            count: positional_count
8650                        }
8651                    );
8652                }
8653            } else {
8654                // Use starunpack_helper to build a list, then convert to tuple
8655                self.starunpack_helper(
8656                    &arguments.args,
8657                    additional_positional,
8658                    CollectionType::List,
8659                )?;
8660                emit!(
8661                    self,
8662                    Instruction::CallIntrinsic1 {
8663                        func: IntrinsicFunction1::ListToTuple
8664                    }
8665                );
8666            }
8667
8668            // Compile keyword arguments
8669            if nkwelts > 0 {
8670                let mut have_dict = false;
8671                let mut nseen = 0usize;
8672
8673                for (i, keyword) in arguments.keywords.iter().enumerate() {
8674                    if keyword.arg.is_none() {
8675                        // **kwargs unpacking
8676                        if nseen > 0 {
8677                            // Pack up preceding keywords using codegen_subkwargs
8678                            self.codegen_subkwargs(&arguments.keywords, i - nseen, i)?;
8679                            if have_dict {
8680                                emit!(self, Instruction::DictMerge { i: 1 });
8681                            }
8682                            have_dict = true;
8683                            nseen = 0;
8684                        }
8685
8686                        if !have_dict {
8687                            emit!(self, Instruction::BuildMap { count: 0 });
8688                            have_dict = true;
8689                        }
8690
8691                        self.compile_expression(&keyword.value)?;
8692                        emit!(self, Instruction::DictMerge { i: 1 });
8693                    } else {
8694                        nseen += 1;
8695                    }
8696                }
8697
8698                // Pack up any trailing keyword arguments
8699                if nseen > 0 {
8700                    self.codegen_subkwargs(&arguments.keywords, nkwelts - nseen, nkwelts)?;
8701                    if have_dict {
8702                        emit!(self, Instruction::DictMerge { i: 1 });
8703                    }
8704                    have_dict = true;
8705                }
8706
8707                assert!(have_dict);
8708            } else {
8709                emit!(self, Instruction::PushNull);
8710            }
8711
8712            self.set_source_range(call_range);
8713            emit!(self, Instruction::CallFunctionEx);
8714        }
8715
8716        Ok(())
8717    }
8718
8719    fn compile_comprehension_element(&mut self, element: &ast::Expr) -> CompileResult<()> {
8720        self.compile_expression(element).map_err(|e| {
8721            if let CodegenErrorType::InvalidStarExpr = e.error {
8722                self.error(CodegenErrorType::SyntaxError(
8723                    "iterable unpacking cannot be used in comprehension".to_owned(),
8724                ))
8725            } else {
8726                e
8727            }
8728        })
8729    }
8730
8731    fn consume_next_sub_table(&mut self) -> CompileResult<()> {
8732        {
8733            let _ = self.push_symbol_table()?;
8734        }
8735        let _ = self.pop_symbol_table();
8736        Ok(())
8737    }
8738
8739    fn consume_skipped_nested_scopes_in_expr(
8740        &mut self,
8741        expression: &ast::Expr,
8742    ) -> CompileResult<()> {
8743        use ast::visitor::Visitor;
8744
8745        struct SkippedScopeVisitor<'a> {
8746            compiler: &'a mut Compiler,
8747            error: Option<CodegenError>,
8748        }
8749
8750        impl SkippedScopeVisitor<'_> {
8751            fn consume_scope(&mut self) {
8752                if self.error.is_none() {
8753                    self.error = self.compiler.consume_next_sub_table().err();
8754                }
8755            }
8756        }
8757
8758        impl ast::visitor::Visitor<'_> for SkippedScopeVisitor<'_> {
8759            fn visit_expr(&mut self, expr: &ast::Expr) {
8760                if self.error.is_some() {
8761                    return;
8762                }
8763
8764                match expr {
8765                    ast::Expr::Lambda(ast::ExprLambda { parameters, .. }) => {
8766                        // Defaults are scanned before enter_scope in the
8767                        // symbol table builder, so their nested scopes
8768                        // precede the lambda scope in sub_tables.
8769                        if let Some(params) = parameters.as_deref() {
8770                            for default in params
8771                                .posonlyargs
8772                                .iter()
8773                                .chain(&params.args)
8774                                .chain(&params.kwonlyargs)
8775                                .filter_map(|p| p.default.as_deref())
8776                            {
8777                                self.visit_expr(default);
8778                            }
8779                        }
8780                        self.consume_scope();
8781                    }
8782                    ast::Expr::ListComp(ast::ExprListComp { generators, .. })
8783                    | ast::Expr::SetComp(ast::ExprSetComp { generators, .. })
8784                    | ast::Expr::Generator(ast::ExprGenerator { generators, .. }) => {
8785                        // leave_scope runs before the first iterator is
8786                        // scanned, so the comprehension scope comes first
8787                        // in sub_tables, then any nested scopes from the
8788                        // first iterator.
8789                        self.consume_scope();
8790                        if let Some(first) = generators.first() {
8791                            self.visit_expr(&first.iter);
8792                        }
8793                    }
8794                    ast::Expr::DictComp(ast::ExprDictComp { generators, .. }) => {
8795                        self.consume_scope();
8796                        if let Some(first) = generators.first() {
8797                            self.visit_expr(&first.iter);
8798                        }
8799                    }
8800                    _ => ast::visitor::walk_expr(self, expr),
8801                }
8802            }
8803        }
8804
8805        let mut visitor = SkippedScopeVisitor {
8806            compiler: self,
8807            error: None,
8808        };
8809        visitor.visit_expr(expression);
8810        if let Some(err) = visitor.error {
8811            Err(err)
8812        } else {
8813            Ok(())
8814        }
8815    }
8816
8817    fn compile_comprehension(
8818        &mut self,
8819        name: &str,
8820        init_collection: Option<AnyInstruction>,
8821        generators: &[ast::Comprehension],
8822        compile_element: &dyn Fn(&mut Self) -> CompileResult<()>,
8823        comprehension_type: ComprehensionType,
8824        element_contains_await: bool,
8825    ) -> CompileResult<()> {
8826        let prev_ctx = self.ctx;
8827        let has_an_async_gen = generators.iter().any(|g| g.is_async);
8828
8829        // Check for async comprehension outside async function (list/set/dict only, not generator expressions)
8830        // Use in_async_scope to allow nested async comprehensions inside an async function
8831        if comprehension_type != ComprehensionType::Generator
8832            && (has_an_async_gen || element_contains_await)
8833            && !prev_ctx.in_async_scope
8834        {
8835            return Err(self.error(CodegenErrorType::InvalidAsyncComprehension));
8836        }
8837
8838        // Check if this comprehension should be inlined (PEP 709)
8839        let is_inlined = self.is_inlined_comprehension_context(comprehension_type);
8840
8841        // async comprehensions are allowed in various contexts:
8842        // - list/set/dict comprehensions in async functions (or nested within)
8843        // - always for generator expressions
8844        let is_async_list_set_dict_comprehension = comprehension_type
8845            != ComprehensionType::Generator
8846            && (has_an_async_gen || element_contains_await)
8847            && prev_ctx.in_async_scope;
8848
8849        let is_async_generator_comprehension = comprehension_type == ComprehensionType::Generator
8850            && (has_an_async_gen || element_contains_await);
8851
8852        debug_assert!(!(is_async_list_set_dict_comprehension && is_async_generator_comprehension));
8853
8854        let is_async = is_async_list_set_dict_comprehension || is_async_generator_comprehension;
8855
8856        // We must have at least one generator:
8857        assert!(!generators.is_empty());
8858
8859        if is_inlined && !has_an_async_gen && !element_contains_await {
8860            // PEP 709: Inlined comprehension - compile inline without new scope
8861            let was_in_inlined_comp = self.current_code_info().in_inlined_comp;
8862            self.current_code_info().in_inlined_comp = true;
8863            let result = self.compile_inlined_comprehension(
8864                init_collection,
8865                generators,
8866                compile_element,
8867                has_an_async_gen,
8868            );
8869            self.current_code_info().in_inlined_comp = was_in_inlined_comp;
8870            return result;
8871        }
8872
8873        // Non-inlined path: create a new code object (generator expressions, etc.)
8874        self.ctx = CompileContext {
8875            loop_data: None,
8876            in_class: prev_ctx.in_class,
8877            func: if is_async {
8878                FunctionContext::AsyncFunction
8879            } else {
8880                FunctionContext::Function
8881            },
8882            // Inherit in_async_scope from parent - nested async comprehensions are allowed
8883            // if we're anywhere inside an async function
8884            in_async_scope: prev_ctx.in_async_scope || is_async,
8885        };
8886
8887        let flags = bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED;
8888        let flags = if is_async {
8889            flags | bytecode::CodeFlags::COROUTINE
8890        } else {
8891            flags
8892        };
8893
8894        // Create magnificent function <listcomp>:
8895        self.push_output(flags, 1, 1, 0, name.to_owned())?;
8896
8897        // Set qualname for comprehension
8898        self.set_qualname();
8899
8900        let arg0 = self.varname(".0")?;
8901
8902        let return_none = init_collection.is_none();
8903
8904        // PEP 479: Wrap generator/coroutine body with StopIteration handler
8905        let is_gen_scope = self.current_symbol_table().is_generator || is_async;
8906        let stop_iteration_block = if is_gen_scope {
8907            let handler_block = self.new_block();
8908            emit!(
8909                self,
8910                PseudoInstruction::SetupCleanup {
8911                    delta: handler_block
8912                }
8913            );
8914            self.set_no_location();
8915            self.push_fblock(FBlockType::StopIteration, handler_block, handler_block)?;
8916            Some(handler_block)
8917        } else {
8918            None
8919        };
8920
8921        // Create empty object of proper type:
8922        if let Some(init_collection) = init_collection {
8923            self._emit(init_collection, OpArg::new(0), BlockIdx::NULL)
8924        }
8925
8926        let mut loop_labels = vec![];
8927        for generator in generators {
8928            let loop_block = self.new_block();
8929            let if_cleanup_block = self.new_block();
8930            let after_block = self.new_block();
8931
8932            if loop_labels.is_empty() {
8933                // Load iterator onto stack (passed as first argument):
8934                emit!(self, Instruction::LoadFast { var_num: arg0 });
8935            } else {
8936                // Evaluate iterated item:
8937                self.compile_expression(&generator.iter)?;
8938
8939                // Get iterator / turn item into an iterator
8940                if generator.is_async {
8941                    emit!(self, Instruction::GetAIter);
8942                } else {
8943                    emit!(self, Instruction::GetIter);
8944                }
8945            }
8946
8947            self.switch_to_block(loop_block);
8948            let mut end_async_for_target = BlockIdx::NULL;
8949            if generator.is_async {
8950                emit!(self, PseudoInstruction::SetupFinally { delta: after_block });
8951                emit!(self, Instruction::GetANext);
8952                self.push_fblock(
8953                    FBlockType::AsyncComprehensionGenerator,
8954                    loop_block,
8955                    after_block,
8956                )?;
8957                self.emit_load_const(ConstantData::None);
8958                end_async_for_target = self.compile_yield_from_sequence(true)?;
8959                // POP_BLOCK before store: only __anext__/yield_from are
8960                // protected by SetupFinally targeting END_ASYNC_FOR.
8961                emit!(self, PseudoInstruction::PopBlock);
8962                self.pop_fblock(FBlockType::AsyncComprehensionGenerator);
8963                self.compile_store(&generator.target)?;
8964            } else {
8965                emit!(self, Instruction::ForIter { delta: after_block });
8966                self.compile_store(&generator.target)?;
8967            }
8968            loop_labels.push((
8969                loop_block,
8970                if_cleanup_block,
8971                after_block,
8972                generator.is_async,
8973                end_async_for_target,
8974            ));
8975
8976            // Now evaluate the ifs:
8977            for if_condition in &generator.ifs {
8978                self.compile_jump_if(if_condition, false, if_cleanup_block)?
8979            }
8980        }
8981
8982        compile_element(self)?;
8983
8984        for (loop_block, if_cleanup_block, after_block, is_async, end_async_for_target) in
8985            loop_labels.iter().rev().copied()
8986        {
8987            emit!(self, PseudoInstruction::Jump { delta: loop_block });
8988
8989            self.switch_to_block(if_cleanup_block);
8990            emit!(self, PseudoInstruction::Jump { delta: loop_block });
8991
8992            self.switch_to_block(after_block);
8993            if is_async {
8994                // EndAsyncFor pops both the exception and the aiter
8995                // (handler depth is before GetANext, so aiter is at handler depth)
8996                self.emit_end_async_for(end_async_for_target);
8997            } else {
8998                // END_FOR + POP_ITER pattern (CPython 3.14)
8999                emit!(self, Instruction::EndFor);
9000                emit!(self, Instruction::PopIter);
9001            }
9002        }
9003
9004        if return_none {
9005            self.emit_load_const(ConstantData::None)
9006        }
9007
9008        self.emit_return_value();
9009
9010        // Close StopIteration handler and emit handler code
9011        if let Some(handler_block) = stop_iteration_block {
9012            emit!(self, PseudoInstruction::PopBlock);
9013            self.set_no_location();
9014            self.pop_fblock(FBlockType::StopIteration);
9015            self.switch_to_block(handler_block);
9016            emit!(
9017                self,
9018                Instruction::CallIntrinsic1 {
9019                    func: oparg::IntrinsicFunction1::StopIterationError
9020                }
9021            );
9022            self.set_no_location();
9023            emit!(self, Instruction::Reraise { depth: 1u32 });
9024            self.set_no_location();
9025        }
9026
9027        let code = self.exit_scope();
9028
9029        self.ctx = prev_ctx;
9030
9031        // Create comprehension function with closure
9032        self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
9033
9034        // Evaluate iterated item:
9035        self.compile_expression(&generators[0].iter)?;
9036
9037        // Get iterator / turn item into an iterator
9038        // Use is_async from the first generator, not has_an_async_gen which covers ALL generators
9039        if generators[0].is_async {
9040            emit!(self, Instruction::GetAIter);
9041        } else {
9042            emit!(self, Instruction::GetIter);
9043        };
9044
9045        // Call just created <listcomp> function:
9046        emit!(self, Instruction::Call { argc: 0 });
9047        if is_async_list_set_dict_comprehension {
9048            emit!(self, Instruction::GetAwaitable { r#where: 0 });
9049            self.emit_load_const(ConstantData::None);
9050            let _ = self.compile_yield_from_sequence(true)?;
9051        }
9052
9053        Ok(())
9054    }
9055
9056    /// Compile an inlined comprehension (PEP 709)
9057    /// This generates bytecode inline without creating a new code object
9058    fn compile_inlined_comprehension(
9059        &mut self,
9060        init_collection: Option<AnyInstruction>,
9061        generators: &[ast::Comprehension],
9062        compile_element: &dyn Fn(&mut Self) -> CompileResult<()>,
9063        has_async: bool,
9064    ) -> CompileResult<()> {
9065        // PEP 709: Consume the comprehension's sub_table.
9066        // The symbols are already merged into parent scope by analyze_symbol_table.
9067        let current_table = self
9068            .symbol_table_stack
9069            .last_mut()
9070            .expect("no current symbol table");
9071        let comp_table = current_table.sub_tables[current_table.next_sub_table].clone();
9072        current_table.next_sub_table += 1;
9073
9074        // Compile the outermost iterator first. Its expression may reference
9075        // nested scopes (e.g. lambdas) whose sub_tables sit at the current
9076        // position in the parent's list. Those must be consumed before we
9077        // splice in the comprehension's own children.
9078        self.compile_expression(&generators[0].iter)?;
9079
9080        // Splice the comprehension's children (e.g. nested inlined
9081        // comprehensions) into the parent so the compiler can find them.
9082        if !comp_table.sub_tables.is_empty() {
9083            let current_table = self
9084                .symbol_table_stack
9085                .last_mut()
9086                .expect("no current symbol table");
9087            let insert_pos = current_table.next_sub_table;
9088            for (i, st) in comp_table.sub_tables.iter().enumerate() {
9089                current_table.sub_tables.insert(insert_pos + i, st.clone());
9090            }
9091        }
9092        if has_async && generators[0].is_async {
9093            emit!(self, Instruction::GetAIter);
9094        } else {
9095            emit!(self, Instruction::GetIter);
9096        }
9097
9098        // Collect local variables that need to be saved/restored.
9099        // All DEF_LOCAL && !DEF_NONLOCAL names from the comp table, plus class block names.
9100        let in_class_block = {
9101            let ct = self.current_symbol_table();
9102            ct.typ == CompilerScope::Class && !self.current_code_info().in_inlined_comp
9103        };
9104        let mut pushed_locals: Vec<String> = Vec::new();
9105        for (name, sym) in &comp_table.symbols {
9106            if sym.flags.contains(SymbolFlags::PARAMETER) {
9107                continue; // skip .0
9108            }
9109            // Walrus operator targets (ASSIGNED_IN_COMPREHENSION without ITER)
9110            // are not local to the comprehension; they leak to the outer scope.
9111            let is_walrus = sym.flags.contains(SymbolFlags::ASSIGNED_IN_COMPREHENSION)
9112                && !sym.flags.contains(SymbolFlags::ITER);
9113            let is_local = sym
9114                .flags
9115                .intersects(SymbolFlags::ASSIGNED | SymbolFlags::ITER)
9116                && !sym.flags.contains(SymbolFlags::NONLOCAL)
9117                && !is_walrus;
9118            if is_local || in_class_block {
9119                pushed_locals.push(name.clone());
9120            }
9121        }
9122
9123        // TweakInlinedComprehensionScopes: temporarily override parent symbols
9124        // with comp scopes where they differ.
9125        let mut temp_symbols: IndexMap<String, Symbol> = IndexMap::default();
9126        for (name, comp_sym) in &comp_table.symbols {
9127            if comp_sym.flags.contains(SymbolFlags::PARAMETER) {
9128                continue; // skip .0
9129            }
9130            let comp_scope = comp_sym.scope;
9131
9132            let current_table = self.symbol_table_stack.last().expect("no symbol table");
9133            if let Some(outer_sym) = current_table.symbols.get(name) {
9134                let outer_scope = outer_sym.scope;
9135                if (comp_scope != outer_scope
9136                    && comp_scope != SymbolScope::Free
9137                    && !(comp_scope == SymbolScope::Cell && outer_scope == SymbolScope::Free))
9138                    || in_class_block
9139                {
9140                    temp_symbols.insert(name.clone(), outer_sym.clone());
9141                    let current_table =
9142                        self.symbol_table_stack.last_mut().expect("no symbol table");
9143                    current_table.symbols.insert(name.clone(), comp_sym.clone());
9144                }
9145            }
9146        }
9147
9148        // Step 2: Save local variables that will be shadowed by the comprehension.
9149        // For each variable, we push the fast local value via LoadFastAndClear.
9150        // For merged CELL variables, LoadFastAndClear saves the cell object from
9151        // the merged slot, and MAKE_CELL creates a new empty cell in-place.
9152        // MAKE_CELL has no stack effect (operates only on fastlocals).
9153        let mut total_stack_items: usize = 0;
9154        for name in &pushed_locals {
9155            let var_num = self.varname(name)?;
9156            emit!(self, Instruction::LoadFastAndClear { var_num });
9157            total_stack_items += 1;
9158            // If the comp symbol is CELL, emit MAKE_CELL to create fresh cell
9159            if let Some(comp_sym) = comp_table.symbols.get(name)
9160                && comp_sym.scope == SymbolScope::Cell
9161            {
9162                let i = if self
9163                    .current_symbol_table()
9164                    .symbols
9165                    .get(name)
9166                    .is_some_and(|s| s.scope == SymbolScope::Free)
9167                {
9168                    self.get_free_var_index(name)?
9169                } else {
9170                    self.get_cell_var_index(name)?
9171                };
9172                emit!(self, Instruction::MakeCell { i });
9173            }
9174        }
9175
9176        // Step 3: SWAP iterator to TOS (above saved locals + cell values)
9177        if total_stack_items > 0 {
9178            emit!(
9179                self,
9180                Instruction::Swap {
9181                    i: u32::try_from(total_stack_items + 1).unwrap()
9182                }
9183            );
9184        }
9185
9186        // Step 4: Create the collection (list/set/dict)
9187        if let Some(init_collection) = init_collection {
9188            self._emit(init_collection, OpArg::new(0), BlockIdx::NULL);
9189            // SWAP to get iterator on top
9190            emit!(self, Instruction::Swap { i: 2 });
9191        }
9192
9193        // Set up exception handler for cleanup on exception
9194        let cleanup_block = self.new_block();
9195        let end_block = self.new_block();
9196
9197        if !pushed_locals.is_empty() {
9198            emit!(
9199                self,
9200                PseudoInstruction::SetupFinally {
9201                    delta: cleanup_block
9202                }
9203            );
9204            self.push_fblock(FBlockType::TryExcept, cleanup_block, end_block)?;
9205        }
9206
9207        // Step 5: Compile the comprehension loop(s)
9208        let mut loop_labels: Vec<(BlockIdx, BlockIdx, BlockIdx, bool, BlockIdx)> = vec![];
9209        for (i, generator) in generators.iter().enumerate() {
9210            let loop_block = self.new_block();
9211            let if_cleanup_block = self.new_block();
9212            let after_block = self.new_block();
9213
9214            if i > 0 {
9215                self.compile_expression(&generator.iter)?;
9216                if generator.is_async {
9217                    emit!(self, Instruction::GetAIter);
9218                } else {
9219                    emit!(self, Instruction::GetIter);
9220                }
9221            }
9222
9223            self.switch_to_block(loop_block);
9224
9225            let mut end_async_for_target = BlockIdx::NULL;
9226            if generator.is_async {
9227                emit!(self, PseudoInstruction::SetupFinally { delta: after_block });
9228                emit!(self, Instruction::GetANext);
9229                self.push_fblock(
9230                    FBlockType::AsyncComprehensionGenerator,
9231                    loop_block,
9232                    after_block,
9233                )?;
9234                self.emit_load_const(ConstantData::None);
9235                end_async_for_target = self.compile_yield_from_sequence(true)?;
9236                emit!(self, PseudoInstruction::PopBlock);
9237                self.pop_fblock(FBlockType::AsyncComprehensionGenerator);
9238                self.compile_store(&generator.target)?;
9239            } else {
9240                emit!(self, Instruction::ForIter { delta: after_block });
9241                self.compile_store(&generator.target)?;
9242            }
9243
9244            loop_labels.push((
9245                loop_block,
9246                if_cleanup_block,
9247                after_block,
9248                generator.is_async,
9249                end_async_for_target,
9250            ));
9251
9252            // Evaluate the if conditions
9253            for if_condition in &generator.ifs {
9254                self.compile_jump_if(if_condition, false, if_cleanup_block)?;
9255            }
9256        }
9257
9258        // Step 6: Compile the element expression and append to collection
9259        compile_element(self)?;
9260
9261        // Step 7: Close all loops
9262        for &(loop_block, if_cleanup_block, after_block, is_async, end_async_for_target) in
9263            loop_labels.iter().rev()
9264        {
9265            emit!(self, PseudoInstruction::Jump { delta: loop_block });
9266
9267            self.switch_to_block(if_cleanup_block);
9268            emit!(self, PseudoInstruction::Jump { delta: loop_block });
9269
9270            self.switch_to_block(after_block);
9271            if is_async {
9272                self.emit_end_async_for(end_async_for_target);
9273            } else {
9274                emit!(self, Instruction::EndFor);
9275                emit!(self, Instruction::PopIter);
9276            }
9277        }
9278
9279        // Step 8: Clean up - restore saved locals (and cell values)
9280        if total_stack_items > 0 {
9281            emit!(self, PseudoInstruction::PopBlock);
9282            self.pop_fblock(FBlockType::TryExcept);
9283
9284            // Normal path: jump past cleanup
9285            emit!(self, PseudoInstruction::Jump { delta: end_block });
9286
9287            // Exception cleanup path
9288            self.switch_to_block(cleanup_block);
9289            // Stack: [saved_values..., collection, exception]
9290            emit!(self, Instruction::Swap { i: 2 });
9291            emit!(self, Instruction::PopTop); // Pop incomplete collection
9292
9293            // Restore locals and cell values
9294            emit!(
9295                self,
9296                Instruction::Swap {
9297                    i: u32::try_from(total_stack_items + 1).unwrap()
9298                }
9299            );
9300            for name in pushed_locals.iter().rev() {
9301                let var_num = self.varname(name)?;
9302                emit!(self, Instruction::StoreFast { var_num });
9303            }
9304            // Re-raise the exception
9305            emit!(self, Instruction::Reraise { depth: 0 });
9306
9307            // Normal end path
9308            self.switch_to_block(end_block);
9309        }
9310
9311        // SWAP result to TOS (above saved values)
9312        if total_stack_items > 0 {
9313            emit!(
9314                self,
9315                Instruction::Swap {
9316                    i: u32::try_from(total_stack_items + 1).unwrap()
9317                }
9318            );
9319        }
9320
9321        // Restore saved locals (StoreFast restores the saved cell object for merged cells)
9322        for name in pushed_locals.iter().rev() {
9323            let var_num = self.varname(name)?;
9324            emit!(self, Instruction::StoreFast { var_num });
9325        }
9326
9327        // RevertInlinedComprehensionScopes: restore original symbols
9328        let current_table = self.symbol_table_stack.last_mut().expect("no symbol table");
9329        for (name, original_sym) in temp_symbols {
9330            current_table.symbols.insert(name, original_sym);
9331        }
9332
9333        Ok(())
9334    }
9335
9336    fn compile_future_features(&mut self, features: &[ast::Alias]) -> Result<(), CodegenError> {
9337        if let DoneWithFuture::Yes = self.done_with_future_stmts {
9338            return Err(self.error(CodegenErrorType::InvalidFuturePlacement));
9339        }
9340        self.done_with_future_stmts = DoneWithFuture::DoneWithDoc;
9341        for feature in features {
9342            match feature.name.as_str() {
9343                // Python 3 features; we've already implemented them by default
9344                "nested_scopes" | "generators" | "division" | "absolute_import"
9345                | "with_statement" | "print_function" | "unicode_literals" | "generator_stop" => {}
9346                "annotations" => self.future_annotations = true,
9347                other => {
9348                    return Err(
9349                        self.error(CodegenErrorType::InvalidFutureFeature(other.to_owned()))
9350                    );
9351                }
9352            }
9353        }
9354        Ok(())
9355    }
9356
9357    // Low level helper functions:
9358    fn _emit<I: Into<AnyInstruction>>(&mut self, instr: I, arg: OpArg, target: BlockIdx) {
9359        if self.do_not_emit_bytecode > 0 {
9360            return;
9361        }
9362        let range = self.current_source_range;
9363        let source = self.source_file.to_source_code();
9364        let location = source.source_location(range.start(), PositionEncoding::Utf8);
9365        let end_location = source.source_location(range.end(), PositionEncoding::Utf8);
9366        let except_handler = None;
9367        self.current_block().instructions.push(ir::InstructionInfo {
9368            instr: instr.into(),
9369            arg,
9370            target,
9371            location,
9372            end_location,
9373            except_handler,
9374            lineno_override: None,
9375            cache_entries: 0,
9376        });
9377    }
9378
9379    /// Mark the last emitted instruction as having no source location.
9380    /// Prevents it from triggering LINE events in sys.monitoring.
9381    fn set_no_location(&mut self) {
9382        if let Some(last) = self.current_block().instructions.last_mut() {
9383            last.lineno_override = Some(-1);
9384        }
9385    }
9386
9387    fn emit_no_arg<I: Into<AnyInstruction>>(&mut self, ins: I) {
9388        self._emit(ins, OpArg::NULL, BlockIdx::NULL)
9389    }
9390
9391    fn emit_arg<A: OpArgType, T: EmitArg<A>, I: Into<AnyInstruction>>(
9392        &mut self,
9393        arg: T,
9394        f: impl FnOnce(OpArgMarker<A>) -> I,
9395    ) {
9396        let (op, arg, target) = arg.emit(f);
9397        self._emit(op, arg, target)
9398    }
9399
9400    // fn block_done()
9401
9402    /// Convert a string literal AST node to Wtf8Buf, handling surrogate literals correctly.
9403    fn compile_string_value(&self, string: &ast::ExprStringLiteral) -> Wtf8Buf {
9404        let value = string.value.to_str();
9405        if value.contains(char::REPLACEMENT_CHARACTER) {
9406            // Might have a surrogate literal; reparse from source to preserve them.
9407            string
9408                .value
9409                .iter()
9410                .map(|lit| {
9411                    let source = self.source_file.slice(lit.range);
9412                    crate::string_parser::parse_string_literal(source, lit.flags.into())
9413                })
9414                .collect()
9415        } else {
9416            value.into()
9417        }
9418    }
9419
9420    fn compile_fstring_literal_value(
9421        &self,
9422        string: &ast::InterpolatedStringLiteralElement,
9423        flags: ast::FStringFlags,
9424    ) -> Wtf8Buf {
9425        if string.value.contains(char::REPLACEMENT_CHARACTER) {
9426            let source = self.source_file.slice(string.range);
9427            crate::string_parser::parse_fstring_literal_element(source.into(), flags.into()).into()
9428        } else {
9429            string.value.to_string().into()
9430        }
9431    }
9432
9433    fn compile_fstring_part_literal_value(&self, string: &ast::StringLiteral) -> Wtf8Buf {
9434        if string.value.contains(char::REPLACEMENT_CHARACTER) {
9435            let source = self.source_file.slice(string.range);
9436            crate::string_parser::parse_string_literal(source, string.flags.into()).into()
9437        } else {
9438            string.value.to_string().into()
9439        }
9440    }
9441
9442    fn arg_constant(&mut self, constant: ConstantData) -> oparg::ConstIdx {
9443        let info = self.current_code_info();
9444        info.metadata.consts.insert_full(constant).0.to_u32().into()
9445    }
9446
9447    /// Try to fold a collection of constant expressions into a single ConstantData::Tuple.
9448    /// Returns None if any element cannot be folded.
9449    fn try_fold_constant_collection(
9450        &mut self,
9451        elts: &[ast::Expr],
9452    ) -> CompileResult<Option<ConstantData>> {
9453        let mut constants = Vec::with_capacity(elts.len());
9454        for elt in elts {
9455            let Some(constant) = self.try_fold_constant_expr(elt)? else {
9456                return Ok(None);
9457            };
9458            constants.push(constant);
9459        }
9460        Ok(Some(ConstantData::Tuple {
9461            elements: constants,
9462        }))
9463    }
9464
9465    fn try_fold_constant_expr(&mut self, expr: &ast::Expr) -> CompileResult<Option<ConstantData>> {
9466        Ok(Some(match expr {
9467            ast::Expr::NumberLiteral(num) => match &num.value {
9468                ast::Number::Int(int) => ConstantData::Integer {
9469                    value: ruff_int_to_bigint(int).map_err(|e| self.error(e))?,
9470                },
9471                ast::Number::Float(f) => ConstantData::Float { value: *f },
9472                ast::Number::Complex { real, imag } => ConstantData::Complex {
9473                    value: Complex::new(*real, *imag),
9474                },
9475            },
9476            ast::Expr::StringLiteral(s) => ConstantData::Str {
9477                value: self.compile_string_value(s),
9478            },
9479            ast::Expr::BytesLiteral(b) => ConstantData::Bytes {
9480                value: b.value.bytes().collect(),
9481            },
9482            ast::Expr::BooleanLiteral(b) => ConstantData::Boolean { value: b.value },
9483            ast::Expr::NoneLiteral(_) => ConstantData::None,
9484            ast::Expr::EllipsisLiteral(_) => ConstantData::Ellipsis,
9485            ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
9486                let mut elements = Vec::with_capacity(elts.len());
9487                for elt in elts {
9488                    let Some(constant) = self.try_fold_constant_expr(elt)? else {
9489                        return Ok(None);
9490                    };
9491                    elements.push(constant);
9492                }
9493                ConstantData::Tuple { elements }
9494            }
9495            _ => return Ok(None),
9496        }))
9497    }
9498
9499    fn emit_load_const(&mut self, constant: ConstantData) {
9500        let idx = self.arg_constant(constant);
9501        self.emit_arg(idx, |consti| Instruction::LoadConst { consti })
9502    }
9503
9504    /// Fold constant slice: if all parts are compile-time constants, emit LOAD_CONST(slice).
9505    fn try_fold_constant_slice(
9506        &mut self,
9507        lower: Option<&ast::Expr>,
9508        upper: Option<&ast::Expr>,
9509        step: Option<&ast::Expr>,
9510    ) -> CompileResult<bool> {
9511        let to_const = |expr: Option<&ast::Expr>, this: &mut Self| -> CompileResult<_> {
9512            match expr {
9513                None => Ok(Some(ConstantData::None)),
9514                Some(expr) => this.try_fold_constant_expr(expr),
9515            }
9516        };
9517
9518        let (Some(start), Some(stop), Some(step_val)) = (
9519            to_const(lower, self)?,
9520            to_const(upper, self)?,
9521            to_const(step, self)?,
9522        ) else {
9523            return Ok(false);
9524        };
9525
9526        self.emit_load_const(ConstantData::Slice {
9527            elements: Box::new([start, stop, step_val]),
9528        });
9529        Ok(true)
9530    }
9531
9532    fn emit_return_const(&mut self, constant: ConstantData) {
9533        self.emit_load_const(constant);
9534        emit!(self, Instruction::ReturnValue)
9535    }
9536
9537    fn emit_end_async_for(&mut self, send_target: BlockIdx) {
9538        self._emit(Instruction::EndAsyncFor, OpArg::NULL, send_target);
9539    }
9540
9541    /// Emit LOAD_ATTR for attribute access (method=false).
9542    /// Encodes: (name_idx << 1) | 0
9543    fn emit_load_attr(&mut self, name_idx: u32) {
9544        let encoded = LoadAttr::new(name_idx, false);
9545        self.emit_arg(encoded, |namei| Instruction::LoadAttr { namei })
9546    }
9547
9548    /// Emit LOAD_ATTR with method flag set (for method calls).
9549    /// Encodes: (name_idx << 1) | 1
9550    fn emit_load_attr_method(&mut self, name_idx: u32) {
9551        let encoded = LoadAttr::new(name_idx, true);
9552        self.emit_arg(encoded, |namei| Instruction::LoadAttr { namei })
9553    }
9554
9555    /// Emit LOAD_GLOBAL.
9556    /// Encodes: (name_idx << 1) | push_null_bit
9557    fn emit_load_global(&mut self, name_idx: u32, push_null: bool) {
9558        let encoded = (name_idx << 1) | u32::from(push_null);
9559        self.emit_arg(encoded, |namei| Instruction::LoadGlobal { namei });
9560    }
9561
9562    /// Emit LOAD_SUPER_ATTR for 2-arg super().attr access.
9563    /// Encodes: (name_idx << 2) | 0b10 (method=0, class=1)
9564    fn emit_load_super_attr(&mut self, name_idx: u32) {
9565        let encoded = LoadSuperAttr::new(name_idx, false, true);
9566        self.emit_arg(encoded, |namei| Instruction::LoadSuperAttr { namei })
9567    }
9568
9569    /// Emit LOAD_SUPER_ATTR for 2-arg super().method() call.
9570    /// Encodes: (name_idx << 2) | 0b11 (method=1, class=1)
9571    fn emit_load_super_method(&mut self, name_idx: u32) {
9572        let encoded = LoadSuperAttr::new(name_idx, true, true);
9573        self.emit_arg(encoded, |namei| Instruction::LoadSuperAttr { namei })
9574    }
9575
9576    /// Emit LOAD_SUPER_ATTR for 0-arg super().attr access.
9577    /// Encodes: (name_idx << 2) | 0b00 (method=0, class=0)
9578    fn emit_load_zero_super_attr(&mut self, name_idx: u32) {
9579        let encoded = LoadSuperAttr::new(name_idx, false, false);
9580        self.emit_arg(encoded, |namei| Instruction::LoadSuperAttr { namei })
9581    }
9582
9583    /// Emit LOAD_SUPER_ATTR for 0-arg super().method() call.
9584    /// Encodes: (name_idx << 2) | 0b01 (method=1, class=0)
9585    fn emit_load_zero_super_method(&mut self, name_idx: u32) {
9586        let encoded = LoadSuperAttr::new(name_idx, true, false);
9587        self.emit_arg(encoded, |namei| Instruction::LoadSuperAttr { namei })
9588    }
9589
9590    fn emit_return_value(&mut self) {
9591        emit!(self, Instruction::ReturnValue)
9592    }
9593
9594    fn current_code_info(&mut self) -> &mut ir::CodeInfo {
9595        self.code_stack.last_mut().expect("no code on stack")
9596    }
9597
9598    /// Evaluate whether an expression is a compile-time constant boolean.
9599    /// Returns Some(true) for truthy constants, Some(false) for falsy constants,
9600    /// None for non-constant expressions.
9601    /// = expr_constant in CPython compile.c
9602    fn expr_constant(expr: &ast::Expr) -> Option<bool> {
9603        match expr {
9604            ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => Some(*value),
9605            ast::Expr::NoneLiteral(_) => Some(false),
9606            ast::Expr::EllipsisLiteral(_) => Some(true),
9607            ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => match value {
9608                ast::Number::Int(i) => {
9609                    let n: i64 = i.as_i64().unwrap_or(1);
9610                    Some(n != 0)
9611                }
9612                ast::Number::Float(f) => Some(*f != 0.0),
9613                ast::Number::Complex { real, imag, .. } => Some(*real != 0.0 || *imag != 0.0),
9614            },
9615            ast::Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
9616                Some(!value.to_str().is_empty())
9617            }
9618            ast::Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
9619                Some(value.bytes().next().is_some())
9620            }
9621            ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
9622                if elts.is_empty() {
9623                    Some(false)
9624                } else {
9625                    None // non-empty tuples may have side effects in elements
9626                }
9627            }
9628            _ => None,
9629        }
9630    }
9631
9632    fn emit_nop(&mut self) {
9633        emit!(self, Instruction::Nop);
9634    }
9635
9636    /// Enter a conditional block (if/for/while/match/try/with)
9637    /// PEP 649: Track conditional annotation context
9638    fn enter_conditional_block(&mut self) {
9639        self.current_code_info().in_conditional_block += 1;
9640    }
9641
9642    /// Leave a conditional block
9643    fn leave_conditional_block(&mut self) {
9644        let code_info = self.current_code_info();
9645        debug_assert!(code_info.in_conditional_block > 0);
9646        code_info.in_conditional_block -= 1;
9647    }
9648
9649    /// Compile break or continue statement with proper fblock cleanup.
9650    /// compiler_break, compiler_continue
9651    /// This handles unwinding through With blocks and exception handlers.
9652    fn compile_break_continue(
9653        &mut self,
9654        range: ruff_text_size::TextRange,
9655        is_break: bool,
9656    ) -> CompileResult<()> {
9657        if self.do_not_emit_bytecode > 0 {
9658            // Still validate that we're inside a loop even in dead code
9659            let code = self.current_code_info();
9660            let mut found_loop = false;
9661            for i in (0..code.fblock.len()).rev() {
9662                match code.fblock[i].fb_type {
9663                    FBlockType::WhileLoop | FBlockType::ForLoop => {
9664                        found_loop = true;
9665                        break;
9666                    }
9667                    FBlockType::ExceptionGroupHandler => {
9668                        return Err(self.error_ranged(
9669                            CodegenErrorType::BreakContinueReturnInExceptStar,
9670                            range,
9671                        ));
9672                    }
9673                    _ => {}
9674                }
9675            }
9676            if !found_loop {
9677                if is_break {
9678                    return Err(self.error_ranged(CodegenErrorType::InvalidBreak, range));
9679                } else {
9680                    return Err(self.error_ranged(CodegenErrorType::InvalidContinue, range));
9681                }
9682            }
9683            return Ok(());
9684        }
9685
9686        // unwind_fblock_stack
9687        // We need to unwind fblocks and compile cleanup code. For FinallyTry blocks,
9688        // we need to compile the finally body inline, but we must temporarily pop
9689        // the fblock so that nested break/continue in the finally body don't see it.
9690
9691        // First, find the loop
9692        let code = self.current_code_info();
9693        let mut loop_idx = None;
9694        let mut is_for_loop = false;
9695
9696        for i in (0..code.fblock.len()).rev() {
9697            match code.fblock[i].fb_type {
9698                FBlockType::WhileLoop => {
9699                    loop_idx = Some(i);
9700                    is_for_loop = false;
9701                    break;
9702                }
9703                FBlockType::ForLoop => {
9704                    loop_idx = Some(i);
9705                    is_for_loop = true;
9706                    break;
9707                }
9708                FBlockType::ExceptionGroupHandler => {
9709                    return Err(
9710                        self.error_ranged(CodegenErrorType::BreakContinueReturnInExceptStar, range)
9711                    );
9712                }
9713                _ => {}
9714            }
9715        }
9716
9717        let Some(loop_idx) = loop_idx else {
9718            if is_break {
9719                return Err(self.error_ranged(CodegenErrorType::InvalidBreak, range));
9720            } else {
9721                return Err(self.error_ranged(CodegenErrorType::InvalidContinue, range));
9722            }
9723        };
9724
9725        let loop_block = code.fblock[loop_idx].fb_block;
9726        let exit_block = code.fblock[loop_idx].fb_exit;
9727
9728        // Collect the fblocks we need to unwind through, from top down to (but not including) the loop
9729        #[derive(Clone)]
9730        enum UnwindAction {
9731            With {
9732                is_async: bool,
9733            },
9734            HandlerCleanup {
9735                name: Option<String>,
9736            },
9737            TryExcept,
9738            FinallyTry {
9739                body: Vec<ruff_python_ast::Stmt>,
9740                fblock_idx: usize,
9741            },
9742            FinallyEnd,
9743            PopValue, // Pop return value when continue/break cancels a return
9744        }
9745        let mut unwind_actions = Vec::new();
9746
9747        {
9748            let code = self.current_code_info();
9749            for i in (loop_idx + 1..code.fblock.len()).rev() {
9750                match code.fblock[i].fb_type {
9751                    FBlockType::With => {
9752                        unwind_actions.push(UnwindAction::With { is_async: false });
9753                    }
9754                    FBlockType::AsyncWith => {
9755                        unwind_actions.push(UnwindAction::With { is_async: true });
9756                    }
9757                    FBlockType::HandlerCleanup => {
9758                        let name = match &code.fblock[i].fb_datum {
9759                            FBlockDatum::ExceptionName(name) => Some(name.clone()),
9760                            _ => None,
9761                        };
9762                        unwind_actions.push(UnwindAction::HandlerCleanup { name });
9763                    }
9764                    FBlockType::TryExcept => {
9765                        unwind_actions.push(UnwindAction::TryExcept);
9766                    }
9767                    FBlockType::FinallyTry => {
9768                        // Need to execute finally body before break/continue
9769                        if let FBlockDatum::FinallyBody(ref body) = code.fblock[i].fb_datum {
9770                            unwind_actions.push(UnwindAction::FinallyTry {
9771                                body: body.clone(),
9772                                fblock_idx: i,
9773                            });
9774                        }
9775                    }
9776                    FBlockType::FinallyEnd => {
9777                        // Inside finally block reached via exception - need to pop exception
9778                        unwind_actions.push(UnwindAction::FinallyEnd);
9779                    }
9780                    FBlockType::PopValue => {
9781                        // Pop the return value that was saved on stack
9782                        unwind_actions.push(UnwindAction::PopValue);
9783                    }
9784                    _ => {}
9785                }
9786            }
9787        }
9788
9789        // Emit cleanup for each fblock
9790        for action in unwind_actions {
9791            match action {
9792                UnwindAction::With { is_async } => {
9793                    // Stack: [..., exit_func, self_exit]
9794                    emit!(self, PseudoInstruction::PopBlock);
9795                    self.emit_load_const(ConstantData::None);
9796                    self.emit_load_const(ConstantData::None);
9797                    self.emit_load_const(ConstantData::None);
9798                    emit!(self, Instruction::Call { argc: 3 });
9799
9800                    if is_async {
9801                        emit!(self, Instruction::GetAwaitable { r#where: 2 });
9802                        self.emit_load_const(ConstantData::None);
9803                        let _ = self.compile_yield_from_sequence(true)?;
9804                    }
9805
9806                    emit!(self, Instruction::PopTop);
9807                }
9808                UnwindAction::HandlerCleanup { ref name } => {
9809                    // codegen_unwind_fblock(HANDLER_CLEANUP)
9810                    if name.is_some() {
9811                        // Named handler: PopBlock for inner SETUP_CLEANUP
9812                        emit!(self, PseudoInstruction::PopBlock);
9813                    }
9814                    // PopBlock for outer SETUP_CLEANUP (ExceptionHandler)
9815                    emit!(self, PseudoInstruction::PopBlock);
9816                    emit!(self, Instruction::PopExcept);
9817                    if let Some(name) = name {
9818                        self.emit_load_const(ConstantData::None);
9819                        self.store_name(name)?;
9820                        self.compile_name(name, NameUsage::Delete)?;
9821                    }
9822                }
9823                UnwindAction::TryExcept => {
9824                    // codegen_unwind_fblock(TRY_EXCEPT)
9825                    emit!(self, PseudoInstruction::PopBlock);
9826                }
9827                UnwindAction::FinallyTry { body, fblock_idx } => {
9828                    // codegen_unwind_fblock(FINALLY_TRY)
9829                    emit!(self, PseudoInstruction::PopBlock);
9830
9831                    // compile finally body inline
9832                    // Temporarily pop the FinallyTry fblock so nested break/continue
9833                    // in the finally body won't see it again.
9834                    let code = self.current_code_info();
9835                    let saved_fblock = code.fblock.remove(fblock_idx);
9836
9837                    self.compile_statements(&body)?;
9838
9839                    // Restore the fblock (though this break/continue will jump away,
9840                    // this keeps the fblock stack consistent for error checking)
9841                    let code = self.current_code_info();
9842                    code.fblock.insert(fblock_idx, saved_fblock);
9843                }
9844                UnwindAction::FinallyEnd => {
9845                    // codegen_unwind_fblock(FINALLY_END)
9846                    emit!(self, Instruction::PopTop); // exc_value
9847                    emit!(self, PseudoInstruction::PopBlock);
9848                    emit!(self, Instruction::PopExcept);
9849                }
9850                UnwindAction::PopValue => {
9851                    // Pop the return value - continue/break cancels the pending return
9852                    emit!(self, Instruction::PopTop);
9853                }
9854            }
9855        }
9856
9857        // For break in a for loop, pop the iterator
9858        if is_break && is_for_loop {
9859            emit!(self, Instruction::PopIter);
9860        }
9861
9862        // Jump to target
9863        let target = if is_break { exit_block } else { loop_block };
9864        emit!(self, PseudoInstruction::Jump { delta: target });
9865
9866        Ok(())
9867    }
9868
9869    fn current_block(&mut self) -> &mut ir::Block {
9870        let info = self.current_code_info();
9871        &mut info.blocks[info.current_block]
9872    }
9873
9874    fn new_block(&mut self) -> BlockIdx {
9875        let code = self.current_code_info();
9876        let idx = BlockIdx::new(code.blocks.len().to_u32());
9877        code.blocks.push(ir::Block::default());
9878        idx
9879    }
9880
9881    fn switch_to_block(&mut self, block: BlockIdx) {
9882        let code = self.current_code_info();
9883        let prev = code.current_block;
9884        assert_ne!(prev, block, "recursive switching {prev:?} -> {block:?}");
9885        assert_eq!(
9886            code.blocks[block].next,
9887            BlockIdx::NULL,
9888            "switching {prev:?} -> {block:?} to completed block"
9889        );
9890        let prev_block = &mut code.blocks[prev.idx()];
9891        assert_eq!(
9892            u32::from(prev_block.next),
9893            u32::MAX,
9894            "switching {prev:?} -> {block:?} from block that's already got a next"
9895        );
9896        prev_block.next = block;
9897        code.current_block = block;
9898    }
9899
9900    const fn set_source_range(&mut self, range: TextRange) {
9901        self.current_source_range = range;
9902    }
9903
9904    fn get_source_line_number(&mut self) -> OneIndexed {
9905        self.source_file
9906            .to_source_code()
9907            .line_index(self.current_source_range.start())
9908    }
9909
9910    fn mark_generator(&mut self) {
9911        self.current_code_info().flags |= bytecode::CodeFlags::GENERATOR
9912    }
9913
9914    /// Whether the expression contains an await expression and
9915    /// thus requires the function to be async.
9916    ///
9917    /// Both:
9918    /// ```py
9919    /// async with: ...
9920    /// async for: ...
9921    /// ```
9922    /// are statements, so we won't check for them here
9923    fn contains_await(expression: &ast::Expr) -> bool {
9924        use ast::visitor::Visitor;
9925
9926        #[derive(Default)]
9927        struct AwaitVisitor {
9928            found: bool,
9929        }
9930
9931        impl ast::visitor::Visitor<'_> for AwaitVisitor {
9932            fn visit_expr(&mut self, expr: &ast::Expr) {
9933                if self.found {
9934                    return;
9935                }
9936
9937                match expr {
9938                    ast::Expr::Await(_) => self.found = true,
9939                    // Note: We do NOT check for async comprehensions here.
9940                    // Async list/set/dict comprehensions are handled by compile_comprehension
9941                    // which already awaits the result. A generator expression containing
9942                    // an async comprehension as its element does NOT become an async generator,
9943                    // because the async comprehension is awaited when evaluating the element.
9944                    _ => ast::visitor::walk_expr(self, expr),
9945                }
9946            }
9947        }
9948
9949        let mut visitor = AwaitVisitor::default();
9950        visitor.visit_expr(expression);
9951        visitor.found
9952    }
9953
9954    /// Check if any of the generators (except the first one's iter) contains an await expression.
9955    /// The first generator's iter is evaluated outside the comprehension scope.
9956    fn generators_contain_await(generators: &[ast::Comprehension]) -> bool {
9957        for (i, generator) in generators.iter().enumerate() {
9958            // First generator's iter is evaluated outside the comprehension
9959            if i > 0 && Self::contains_await(&generator.iter) {
9960                return true;
9961            }
9962            // Check ifs in all generators
9963            for if_expr in &generator.ifs {
9964                if Self::contains_await(if_expr) {
9965                    return true;
9966                }
9967            }
9968        }
9969        false
9970    }
9971
9972    fn compile_expr_fstring(&mut self, fstring: &ast::ExprFString) -> CompileResult<()> {
9973        let fstring = &fstring.value;
9974        let mut element_count = 0;
9975        let mut pending_literal = None;
9976        for part in fstring {
9977            self.compile_fstring_part_into(part, &mut pending_literal, &mut element_count)?;
9978        }
9979        self.finish_fstring(pending_literal, element_count)
9980    }
9981
9982    fn compile_fstring_part_into(
9983        &mut self,
9984        part: &ast::FStringPart,
9985        pending_literal: &mut Option<Wtf8Buf>,
9986        element_count: &mut u32,
9987    ) -> CompileResult<()> {
9988        match part {
9989            ast::FStringPart::Literal(string) => {
9990                let value = self.compile_fstring_part_literal_value(string);
9991                if let Some(pending) = pending_literal.as_mut() {
9992                    pending.push_wtf8(value.as_ref());
9993                } else {
9994                    *pending_literal = Some(value);
9995                }
9996                Ok(())
9997            }
9998            ast::FStringPart::FString(fstring) => self.compile_fstring_elements_into(
9999                fstring.flags,
10000                &fstring.elements,
10001                pending_literal,
10002                element_count,
10003            ),
10004        }
10005    }
10006
10007    fn finish_fstring(
10008        &mut self,
10009        mut pending_literal: Option<Wtf8Buf>,
10010        mut element_count: u32,
10011    ) -> CompileResult<()> {
10012        let keep_empty = element_count == 0;
10013        self.emit_pending_fstring_literal(&mut pending_literal, &mut element_count, keep_empty);
10014
10015        if element_count == 0 {
10016            self.emit_load_const(ConstantData::Str {
10017                value: Wtf8Buf::new(),
10018            });
10019        } else if element_count > 1 {
10020            emit!(
10021                self,
10022                Instruction::BuildString {
10023                    count: element_count
10024                }
10025            );
10026        }
10027
10028        Ok(())
10029    }
10030
10031    fn emit_pending_fstring_literal(
10032        &mut self,
10033        pending_literal: &mut Option<Wtf8Buf>,
10034        element_count: &mut u32,
10035        keep_empty: bool,
10036    ) {
10037        let Some(value) = pending_literal.take() else {
10038            return;
10039        };
10040
10041        // CPython drops empty literal fragments when they are adjacent to
10042        // formatted values, but still emits an empty string for a fully-empty
10043        // f-string.
10044        if value.is_empty() && (!keep_empty || *element_count > 0) {
10045            return;
10046        }
10047
10048        self.emit_load_const(ConstantData::Str { value });
10049        *element_count += 1;
10050    }
10051
10052    /// Optimize `'format_str' % (args,)` into f-string bytecode.
10053    /// Returns true if optimization was applied, false to fall back to normal BINARY_OP %.
10054    /// Matches CPython's codegen.c `compiler_formatted_value` optimization.
10055    fn try_optimize_format_str(
10056        &mut self,
10057        format_str: &str,
10058        args: &[ast::Expr],
10059        range: ruff_text_size::TextRange,
10060    ) -> CompileResult<bool> {
10061        // Parse format string into segments
10062        let Some(segments) = Self::parse_percent_format(format_str) else {
10063            return Ok(false);
10064        };
10065
10066        // Verify arg count matches specifier count
10067        let spec_count = segments.iter().filter(|s| s.conversion.is_some()).count();
10068        if spec_count != args.len() {
10069            return Ok(false);
10070        }
10071
10072        self.set_source_range(range);
10073
10074        // Special case: no specifiers, just %% escaping → constant fold
10075        if spec_count == 0 {
10076            let folded: String = segments.iter().map(|s| s.literal.as_str()).collect();
10077            self.emit_load_const(ConstantData::Str {
10078                value: folded.into(),
10079            });
10080            return Ok(true);
10081        }
10082
10083        // Emit f-string style bytecode
10084        let mut part_count: u32 = 0;
10085        let mut arg_idx = 0;
10086
10087        for seg in &segments {
10088            if !seg.literal.is_empty() {
10089                self.emit_load_const(ConstantData::Str {
10090                    value: seg.literal.clone().into(),
10091                });
10092                part_count += 1;
10093            }
10094            if let Some(conv) = seg.conversion {
10095                self.compile_expression(&args[arg_idx])?;
10096                self.set_source_range(range);
10097                emit!(self, Instruction::ConvertValue { oparg: conv });
10098                emit!(self, Instruction::FormatSimple);
10099                part_count += 1;
10100                arg_idx += 1;
10101            }
10102        }
10103
10104        if part_count == 0 {
10105            self.emit_load_const(ConstantData::Str {
10106                value: String::new().into(),
10107            });
10108        } else if part_count > 1 {
10109            emit!(self, Instruction::BuildString { count: part_count });
10110        }
10111
10112        Ok(true)
10113    }
10114
10115    /// Parse a %-format string into segments of (literal_prefix, optional conversion).
10116    /// Returns None if the format string contains unsupported specifiers.
10117    fn parse_percent_format(format_str: &str) -> Option<Vec<FormatSegment>> {
10118        let mut segments = Vec::new();
10119        let mut chars = format_str.chars().peekable();
10120        let mut current_literal = String::new();
10121
10122        while let Some(ch) = chars.next() {
10123            if ch == '%' {
10124                match chars.peek() {
10125                    Some('%') => {
10126                        chars.next();
10127                        current_literal.push('%');
10128                    }
10129                    Some('s') => {
10130                        chars.next();
10131                        segments.push(FormatSegment {
10132                            literal: core::mem::take(&mut current_literal),
10133                            conversion: Some(oparg::ConvertValueOparg::Str),
10134                        });
10135                    }
10136                    Some('r') => {
10137                        chars.next();
10138                        segments.push(FormatSegment {
10139                            literal: core::mem::take(&mut current_literal),
10140                            conversion: Some(oparg::ConvertValueOparg::Repr),
10141                        });
10142                    }
10143                    Some('a') => {
10144                        chars.next();
10145                        segments.push(FormatSegment {
10146                            literal: core::mem::take(&mut current_literal),
10147                            conversion: Some(oparg::ConvertValueOparg::Ascii),
10148                        });
10149                    }
10150                    _ => {
10151                        // Unsupported: %d, %f, %(name)s, %10s, etc.
10152                        return None;
10153                    }
10154                }
10155            } else {
10156                current_literal.push(ch);
10157            }
10158        }
10159
10160        // Trailing literal
10161        if !current_literal.is_empty() {
10162            segments.push(FormatSegment {
10163                literal: current_literal,
10164                conversion: None,
10165            });
10166        }
10167
10168        Some(segments)
10169    }
10170
10171    fn compile_fstring_elements(
10172        &mut self,
10173        flags: ast::FStringFlags,
10174        fstring_elements: &ast::InterpolatedStringElements,
10175    ) -> CompileResult<()> {
10176        let mut element_count = 0;
10177        let mut pending_literal: Option<Wtf8Buf> = None;
10178        self.compile_fstring_elements_into(
10179            flags,
10180            fstring_elements,
10181            &mut pending_literal,
10182            &mut element_count,
10183        )?;
10184        self.finish_fstring(pending_literal, element_count)
10185    }
10186
10187    fn compile_fstring_elements_into(
10188        &mut self,
10189        flags: ast::FStringFlags,
10190        fstring_elements: &ast::InterpolatedStringElements,
10191        pending_literal: &mut Option<Wtf8Buf>,
10192        element_count: &mut u32,
10193    ) -> CompileResult<()> {
10194        for element in fstring_elements {
10195            match element {
10196                ast::InterpolatedStringElement::Literal(string) => {
10197                    let value = self.compile_fstring_literal_value(string, flags);
10198                    if let Some(pending) = pending_literal.as_mut() {
10199                        pending.push_wtf8(value.as_ref());
10200                    } else {
10201                        *pending_literal = Some(value);
10202                    }
10203                }
10204                ast::InterpolatedStringElement::Interpolation(fstring_expr) => {
10205                    let mut conversion = match fstring_expr.conversion {
10206                        ast::ConversionFlag::None => ConvertValueOparg::None,
10207                        ast::ConversionFlag::Str => ConvertValueOparg::Str,
10208                        ast::ConversionFlag::Repr => ConvertValueOparg::Repr,
10209                        ast::ConversionFlag::Ascii => ConvertValueOparg::Ascii,
10210                    };
10211
10212                    if let Some(ast::DebugText { leading, trailing }) = &fstring_expr.debug_text {
10213                        let range = fstring_expr.expression.range();
10214                        let source = self.source_file.slice(range);
10215                        let text = [
10216                            strip_fstring_debug_comments(leading).as_str(),
10217                            source,
10218                            strip_fstring_debug_comments(trailing).as_str(),
10219                        ]
10220                        .concat();
10221
10222                        let text: Wtf8Buf = text.into();
10223                        pending_literal
10224                            .get_or_insert_with(Wtf8Buf::new)
10225                            .push_wtf8(text.as_ref());
10226
10227                        // If debug text is present, apply repr conversion when no `format_spec` specified.
10228                        // See action_helpers.c: fstring_find_expr_replacement
10229                        if matches!(
10230                            (conversion, &fstring_expr.format_spec),
10231                            (ConvertValueOparg::None, None)
10232                        ) {
10233                            conversion = ConvertValueOparg::Repr;
10234                        }
10235                    }
10236
10237                    self.emit_pending_fstring_literal(pending_literal, element_count, false);
10238
10239                    self.compile_expression(&fstring_expr.expression)?;
10240
10241                    match conversion {
10242                        ConvertValueOparg::None => {}
10243                        ConvertValueOparg::Str
10244                        | ConvertValueOparg::Repr
10245                        | ConvertValueOparg::Ascii => {
10246                            emit!(self, Instruction::ConvertValue { oparg: conversion })
10247                        }
10248                    }
10249
10250                    match &fstring_expr.format_spec {
10251                        Some(format_spec) => {
10252                            self.compile_fstring_elements(flags, &format_spec.elements)?;
10253
10254                            emit!(self, Instruction::FormatWithSpec);
10255                        }
10256                        None => {
10257                            emit!(self, Instruction::FormatSimple);
10258                        }
10259                    }
10260
10261                    *element_count += 1;
10262                }
10263            }
10264        }
10265
10266        Ok(())
10267    }
10268
10269    fn compile_expr_tstring(&mut self, expr_tstring: &ast::ExprTString) -> CompileResult<()> {
10270        // ast::TStringValue can contain multiple ast::TString parts (implicit concatenation)
10271        // Each ast::TString part should be compiled and the results merged into a single Template
10272        let tstring_value = &expr_tstring.value;
10273
10274        // Collect all strings and compile all interpolations
10275        let mut all_strings: Vec<Wtf8Buf> = Vec::new();
10276        let mut current_string = Wtf8Buf::new();
10277        let mut interp_count: u32 = 0;
10278
10279        for tstring in tstring_value.iter() {
10280            self.compile_tstring_into(
10281                tstring,
10282                &mut all_strings,
10283                &mut current_string,
10284                &mut interp_count,
10285            )?;
10286        }
10287
10288        // Add trailing string
10289        all_strings.push(core::mem::take(&mut current_string));
10290
10291        // Now build the Template:
10292        // Stack currently has all interpolations from compile_tstring_into calls
10293
10294        // 1. Build interpolations tuple from the interpolations on the stack
10295        emit!(
10296            self,
10297            Instruction::BuildTuple {
10298                count: interp_count
10299            }
10300        );
10301
10302        // 2. Load all string parts
10303        let string_count: u32 = all_strings
10304            .len()
10305            .try_into()
10306            .expect("t-string string count overflowed");
10307        for s in &all_strings {
10308            self.emit_load_const(ConstantData::Str { value: s.clone() });
10309        }
10310
10311        // 3. Build strings tuple
10312        emit!(
10313            self,
10314            Instruction::BuildTuple {
10315                count: string_count
10316            }
10317        );
10318
10319        // 4. Swap so strings is below interpolations: [interps, strings] -> [strings, interps]
10320        emit!(self, Instruction::Swap { i: 2 });
10321
10322        // 5. Build the Template
10323        emit!(self, Instruction::BuildTemplate);
10324
10325        Ok(())
10326    }
10327
10328    fn compile_tstring_into(
10329        &mut self,
10330        tstring: &ast::TString,
10331        strings: &mut Vec<Wtf8Buf>,
10332        current_string: &mut Wtf8Buf,
10333        interp_count: &mut u32,
10334    ) -> CompileResult<()> {
10335        for element in &tstring.elements {
10336            match element {
10337                ast::InterpolatedStringElement::Literal(lit) => {
10338                    // Accumulate literal parts into current_string
10339                    current_string.push_str(&lit.value);
10340                }
10341                ast::InterpolatedStringElement::Interpolation(interp) => {
10342                    // Finish current string segment
10343                    strings.push(core::mem::take(current_string));
10344
10345                    // Compile the interpolation value
10346                    self.compile_expression(&interp.expression)?;
10347
10348                    // Load the expression source string, including any
10349                    // whitespace between '{' and the expression start
10350                    let expr_range = interp.expression.range();
10351                    let expr_source = if interp.range.start() < expr_range.start()
10352                        && interp.range.end() >= expr_range.end()
10353                    {
10354                        let after_brace = interp.range.start() + TextSize::new(1);
10355                        self.source_file
10356                            .slice(TextRange::new(after_brace, expr_range.end()))
10357                    } else {
10358                        // Fallback for programmatically constructed ASTs with dummy ranges
10359                        self.source_file.slice(expr_range)
10360                    };
10361                    self.emit_load_const(ConstantData::Str {
10362                        value: expr_source.to_string().into(),
10363                    });
10364
10365                    // Determine conversion code
10366                    let conversion: u32 = match interp.conversion {
10367                        ast::ConversionFlag::None => 0,
10368                        ast::ConversionFlag::Str => 1,
10369                        ast::ConversionFlag::Repr => 2,
10370                        ast::ConversionFlag::Ascii => 3,
10371                    };
10372
10373                    // Handle format_spec
10374                    let has_format_spec = interp.format_spec.is_some();
10375                    if let Some(format_spec) = &interp.format_spec {
10376                        // Compile format_spec as a string using fstring element compilation
10377                        // Use default ast::FStringFlags since format_spec syntax is independent of t-string flags
10378                        self.compile_fstring_elements(
10379                            ast::FStringFlags::empty(),
10380                            &format_spec.elements,
10381                        )?;
10382                    }
10383
10384                    // Emit BUILD_INTERPOLATION
10385                    // oparg encoding: (conversion << 2) | has_format_spec
10386                    let format = (conversion << 2) | u32::from(has_format_spec);
10387                    emit!(self, Instruction::BuildInterpolation { format });
10388
10389                    *interp_count += 1;
10390                }
10391            }
10392        }
10393
10394        Ok(())
10395    }
10396}
10397
10398trait EmitArg<Arg: OpArgType> {
10399    fn emit<I: Into<AnyInstruction>>(
10400        self,
10401        f: impl FnOnce(OpArgMarker<Arg>) -> I,
10402    ) -> (AnyInstruction, OpArg, BlockIdx);
10403}
10404
10405impl<T: OpArgType> EmitArg<T> for T {
10406    fn emit<I: Into<AnyInstruction>>(
10407        self,
10408        f: impl FnOnce(OpArgMarker<T>) -> I,
10409    ) -> (AnyInstruction, OpArg, BlockIdx) {
10410        let (marker, arg) = OpArgMarker::new(self);
10411        (f(marker).into(), arg, BlockIdx::NULL)
10412    }
10413}
10414
10415impl EmitArg<bytecode::Label> for BlockIdx {
10416    fn emit<I: Into<AnyInstruction>>(
10417        self,
10418        f: impl FnOnce(OpArgMarker<bytecode::Label>) -> I,
10419    ) -> (AnyInstruction, OpArg, BlockIdx) {
10420        (f(OpArgMarker::marker()).into(), OpArg::NULL, self)
10421    }
10422}
10423
10424/// Strips leading whitespace from a docstring.
10425///
10426/// `inspect.cleandoc` is a good reference, but has a few incompatibilities.
10427// = _PyCompile_CleanDoc
10428fn clean_doc(doc: &str) -> String {
10429    let doc = expandtabs(doc, 8);
10430    // First pass: find minimum indentation of non-blank lines AFTER the first line.
10431    // A "blank line" is one containing only spaces (or empty).
10432    let margin = doc
10433        .split('\n')
10434        .skip(1) // skip first line
10435        .filter(|line| line.chars().any(|c| c != ' ')) // non-blank lines only
10436        .map(|line| line.chars().take_while(|c| *c == ' ').count())
10437        .min()
10438        .unwrap_or(0);
10439
10440    let mut cleaned = String::with_capacity(doc.len());
10441    // Strip all leading spaces from the first line
10442    if let Some(first_line) = doc.split('\n').next() {
10443        let trimmed = first_line.trim_start();
10444        // Early exit: no leading spaces on first line AND margin == 0
10445        if trimmed.len() == first_line.len() && margin == 0 {
10446            return doc.to_owned();
10447        }
10448        cleaned.push_str(trimmed);
10449    }
10450    // Subsequent lines: skip up to `margin` leading spaces
10451    for line in doc.split('\n').skip(1) {
10452        cleaned.push('\n');
10453        let skip = line.chars().take(margin).take_while(|c| *c == ' ').count();
10454        cleaned.push_str(&line[skip..]);
10455    }
10456
10457    cleaned
10458}
10459
10460// copied from rustpython_common::str, so we don't have to depend on it just for this function
10461fn expandtabs(input: &str, tab_size: usize) -> String {
10462    let tab_stop = tab_size;
10463    let mut expanded_str = String::with_capacity(input.len());
10464    let mut tab_size = tab_stop;
10465    let mut col_count = 0usize;
10466    for ch in input.chars() {
10467        match ch {
10468            '\t' => {
10469                let num_spaces = tab_size - col_count;
10470                col_count += num_spaces;
10471                let expand = " ".repeat(num_spaces);
10472                expanded_str.push_str(&expand);
10473            }
10474            '\r' | '\n' => {
10475                expanded_str.push(ch);
10476                col_count = 0;
10477                tab_size = 0;
10478            }
10479            _ => {
10480                expanded_str.push(ch);
10481                col_count += 1;
10482            }
10483        }
10484        if col_count >= tab_size {
10485            tab_size += tab_stop;
10486        }
10487    }
10488    expanded_str
10489}
10490
10491fn split_doc<'a>(body: &'a [ast::Stmt], opts: &CompileOpts) -> (Option<String>, &'a [ast::Stmt]) {
10492    if let Some((ast::Stmt::Expr(expr), body_rest)) = body.split_first() {
10493        let doc_comment = match &*expr.value {
10494            ast::Expr::StringLiteral(value) => Some(&value.value),
10495            // f-strings are not allowed in Python doc comments.
10496            ast::Expr::FString(_) => None,
10497            _ => None,
10498        };
10499        if let Some(doc) = doc_comment {
10500            return if opts.optimize < 2 {
10501                (Some(clean_doc(doc.to_str())), body_rest)
10502            } else {
10503                (None, body_rest)
10504            };
10505        }
10506    }
10507    (None, body)
10508}
10509
10510pub fn ruff_int_to_bigint(int: &ast::Int) -> Result<BigInt, CodegenErrorType> {
10511    if let Some(small) = int.as_u64() {
10512        Ok(BigInt::from(small))
10513    } else {
10514        parse_big_integer(int)
10515    }
10516}
10517
10518/// Converts a `ruff` ast integer into a `BigInt`.
10519/// Unlike small integers, big integers may be stored in one of four possible radix representations.
10520fn parse_big_integer(int: &ast::Int) -> Result<BigInt, CodegenErrorType> {
10521    // TODO: Improve ruff API
10522    // Can we avoid this copy?
10523    let s = format!("{int}");
10524    let mut s = s.as_str();
10525    // See: https://peps.python.org/pep-0515/#literal-grammar
10526    let radix = match s.get(0..2) {
10527        Some("0b" | "0B") => {
10528            s = s.get(2..).unwrap_or(s);
10529            2
10530        }
10531        Some("0o" | "0O") => {
10532            s = s.get(2..).unwrap_or(s);
10533            8
10534        }
10535        Some("0x" | "0X") => {
10536            s = s.get(2..).unwrap_or(s);
10537            16
10538        }
10539        _ => 10,
10540    };
10541
10542    BigInt::from_str_radix(s, radix).map_err(|e| {
10543        CodegenErrorType::SyntaxError(format!(
10544            "unparsed integer literal (radix {radix}): {s} ({e})"
10545        ))
10546    })
10547}
10548
10549// Note: Not a good practice in general. Keep this trait private only for compiler
10550trait ToU32 {
10551    fn to_u32(self) -> u32;
10552}
10553
10554impl ToU32 for usize {
10555    fn to_u32(self) -> u32 {
10556        self.try_into().unwrap()
10557    }
10558}
10559
10560/// Strip Python comments from f-string debug text (leading/trailing around `=`).
10561/// A comment starts with `#` and extends to the end of the line.
10562/// The newline character itself is preserved.
10563fn strip_fstring_debug_comments(text: &str) -> String {
10564    let mut result = String::with_capacity(text.len());
10565    let mut in_comment = false;
10566    for ch in text.chars() {
10567        if in_comment {
10568            if ch == '\n' {
10569                in_comment = false;
10570                result.push(ch);
10571            }
10572        } else if ch == '#' {
10573            in_comment = true;
10574        } else {
10575            result.push(ch);
10576        }
10577    }
10578    result
10579}
10580
10581#[cfg(test)]
10582mod ruff_tests {
10583    use super::*;
10584    use ast::name::Name;
10585
10586    /// Test if the compiler can correctly identify fstrings containing an `await` expression.
10587    #[test]
10588    fn test_fstring_contains_await() {
10589        let range = TextRange::default();
10590        let flags = ast::FStringFlags::empty();
10591
10592        // f'{x}'
10593        let expr_x = ast::Expr::Name(ast::ExprName {
10594            node_index: ast::AtomicNodeIndex::NONE,
10595            range,
10596            id: Name::new("x"),
10597            ctx: ast::ExprContext::Load,
10598        });
10599        let not_present = &ast::Expr::FString(ast::ExprFString {
10600            node_index: ast::AtomicNodeIndex::NONE,
10601            range,
10602            value: ast::FStringValue::single(ast::FString {
10603                node_index: ast::AtomicNodeIndex::NONE,
10604                range,
10605                elements: vec![ast::InterpolatedStringElement::Interpolation(
10606                    ast::InterpolatedElement {
10607                        node_index: ast::AtomicNodeIndex::NONE,
10608                        range,
10609                        expression: Box::new(expr_x),
10610                        debug_text: None,
10611                        conversion: ast::ConversionFlag::None,
10612                        format_spec: None,
10613                    },
10614                )]
10615                .into(),
10616                flags,
10617            }),
10618        });
10619        assert!(!Compiler::contains_await(not_present));
10620
10621        // f'{await x}'
10622        let expr_await_x = ast::Expr::Await(ast::ExprAwait {
10623            node_index: ast::AtomicNodeIndex::NONE,
10624            range,
10625            value: Box::new(ast::Expr::Name(ast::ExprName {
10626                node_index: ast::AtomicNodeIndex::NONE,
10627                range,
10628                id: Name::new("x"),
10629                ctx: ast::ExprContext::Load,
10630            })),
10631        });
10632        let present = &ast::Expr::FString(ast::ExprFString {
10633            node_index: ast::AtomicNodeIndex::NONE,
10634            range,
10635            value: ast::FStringValue::single(ast::FString {
10636                node_index: ast::AtomicNodeIndex::NONE,
10637                range,
10638                elements: vec![ast::InterpolatedStringElement::Interpolation(
10639                    ast::InterpolatedElement {
10640                        node_index: ast::AtomicNodeIndex::NONE,
10641                        range,
10642                        expression: Box::new(expr_await_x),
10643                        debug_text: None,
10644                        conversion: ast::ConversionFlag::None,
10645                        format_spec: None,
10646                    },
10647                )]
10648                .into(),
10649                flags,
10650            }),
10651        });
10652        assert!(Compiler::contains_await(present));
10653
10654        // f'{x:{await y}}'
10655        let expr_x = ast::Expr::Name(ast::ExprName {
10656            node_index: ast::AtomicNodeIndex::NONE,
10657            range,
10658            id: Name::new("x"),
10659            ctx: ast::ExprContext::Load,
10660        });
10661        let expr_await_y = ast::Expr::Await(ast::ExprAwait {
10662            node_index: ast::AtomicNodeIndex::NONE,
10663            range,
10664            value: Box::new(ast::Expr::Name(ast::ExprName {
10665                node_index: ast::AtomicNodeIndex::NONE,
10666                range,
10667                id: Name::new("y"),
10668                ctx: ast::ExprContext::Load,
10669            })),
10670        });
10671        let present = &ast::Expr::FString(ast::ExprFString {
10672            node_index: ast::AtomicNodeIndex::NONE,
10673            range,
10674            value: ast::FStringValue::single(ast::FString {
10675                node_index: ast::AtomicNodeIndex::NONE,
10676                range,
10677                elements: vec![ast::InterpolatedStringElement::Interpolation(
10678                    ast::InterpolatedElement {
10679                        node_index: ast::AtomicNodeIndex::NONE,
10680                        range,
10681                        expression: Box::new(expr_x),
10682                        debug_text: None,
10683                        conversion: ast::ConversionFlag::None,
10684                        format_spec: Some(Box::new(ast::InterpolatedStringFormatSpec {
10685                            node_index: ast::AtomicNodeIndex::NONE,
10686                            range,
10687                            elements: vec![ast::InterpolatedStringElement::Interpolation(
10688                                ast::InterpolatedElement {
10689                                    node_index: ast::AtomicNodeIndex::NONE,
10690                                    range,
10691                                    expression: Box::new(expr_await_y),
10692                                    debug_text: None,
10693                                    conversion: ast::ConversionFlag::None,
10694                                    format_spec: None,
10695                                },
10696                            )]
10697                            .into(),
10698                        })),
10699                    },
10700                )]
10701                .into(),
10702                flags,
10703            }),
10704        });
10705        assert!(Compiler::contains_await(present));
10706    }
10707}
10708
10709#[cfg(test)]
10710mod tests {
10711    use super::*;
10712    use rustpython_compiler_core::{SourceFileBuilder, bytecode::OpArg};
10713
10714    fn assert_scope_exit_locations(code: &CodeObject) {
10715        for (instr, (location, _)) in code.instructions.iter().zip(code.locations.iter()) {
10716            if matches!(
10717                instr.op,
10718                Instruction::ReturnValue
10719                    | Instruction::RaiseVarargs { .. }
10720                    | Instruction::Reraise { .. }
10721            ) {
10722                assert!(
10723                    location.line.get() > 0,
10724                    "scope-exit instruction {instr:?} is missing a line number"
10725                );
10726            }
10727        }
10728        for constant in code.constants.iter() {
10729            if let ConstantData::Code { code } = constant {
10730                assert_scope_exit_locations(code);
10731            }
10732        }
10733    }
10734
10735    fn compile_exec(source: &str) -> CodeObject {
10736        let opts = CompileOpts::default();
10737        compile_exec_with_options(source, opts)
10738    }
10739
10740    fn compile_exec_optimized(source: &str) -> CodeObject {
10741        let opts = CompileOpts {
10742            optimize: 1,
10743            ..CompileOpts::default()
10744        };
10745        compile_exec_with_options(source, opts)
10746    }
10747
10748    fn compile_exec_with_options(source: &str, opts: CompileOpts) -> CodeObject {
10749        let source_file = SourceFileBuilder::new("source_path", source).finish();
10750        let parsed = ruff_python_parser::parse(
10751            source_file.source_text(),
10752            ruff_python_parser::Mode::Module.into(),
10753        )
10754        .unwrap();
10755        let ast = parsed.into_syntax();
10756        let ast = match ast {
10757            ruff_python_ast::Mod::Module(stmts) => stmts,
10758            _ => unreachable!(),
10759        };
10760        let symbol_table = SymbolTable::scan_program(&ast, source_file.clone())
10761            .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))
10762            .unwrap();
10763        let mut compiler = Compiler::new(opts, source_file, "<module>".to_owned());
10764        compiler.compile_program(&ast, symbol_table).unwrap();
10765        compiler.exit_scope()
10766    }
10767
10768    fn find_code<'a>(code: &'a CodeObject, name: &str) -> Option<&'a CodeObject> {
10769        if code.obj_name == name {
10770            return Some(code);
10771        }
10772        code.constants.iter().find_map(|constant| {
10773            if let ConstantData::Code { code } = constant {
10774                find_code(code, name)
10775            } else {
10776                None
10777            }
10778        })
10779    }
10780
10781    fn has_common_constant(code: &CodeObject, expected: bytecode::CommonConstant) -> bool {
10782        code.instructions.iter().any(|unit| match unit.op {
10783            Instruction::LoadCommonConstant { idx } => {
10784                idx.get(OpArg::new(u32::from(u8::from(unit.arg)))) == expected
10785            }
10786            _ => false,
10787        })
10788    }
10789
10790    fn has_intrinsic_1(code: &CodeObject, expected: IntrinsicFunction1) -> bool {
10791        code.instructions.iter().any(|unit| match unit.op {
10792            Instruction::CallIntrinsic1 { func } => {
10793                func.get(OpArg::new(u32::from(u8::from(unit.arg)))) == expected
10794            }
10795            _ => false,
10796        })
10797    }
10798
10799    macro_rules! assert_dis_snapshot {
10800        ($value:expr) => {
10801            insta::assert_snapshot!(
10802                insta::internals::AutoName,
10803                $value.display_expand_code_objects().to_string(),
10804                stringify!($value)
10805            )
10806        };
10807    }
10808
10809    #[test]
10810    fn test_if_ors() {
10811        assert_dis_snapshot!(compile_exec(
10812            "\
10813if True or False or False:
10814    pass
10815"
10816        ));
10817    }
10818
10819    #[test]
10820    fn test_if_ands() {
10821        assert_dis_snapshot!(compile_exec(
10822            "\
10823if True and False and False:
10824    pass
10825"
10826        ));
10827    }
10828
10829    #[test]
10830    fn test_if_mixed() {
10831        assert_dis_snapshot!(compile_exec(
10832            "\
10833if (True and False) or (False and True):
10834    pass
10835"
10836        ));
10837    }
10838
10839    #[test]
10840    fn test_nested_bool_op() {
10841        assert_dis_snapshot!(compile_exec(
10842            "\
10843x = Test() and False or False
10844"
10845        ));
10846    }
10847
10848    #[test]
10849    fn test_const_bool_not_op() {
10850        assert_dis_snapshot!(compile_exec_optimized(
10851            "\
10852x = not True
10853"
10854        ));
10855    }
10856
10857    #[test]
10858    fn test_nested_double_async_with() {
10859        assert_dis_snapshot!(compile_exec(
10860            "\
10861async def test():
10862    for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):
10863        with self.subTest(type=type(stop_exc)):
10864            try:
10865                async with egg():
10866                    raise stop_exc
10867            except Exception as ex:
10868                self.assertIs(ex, stop_exc)
10869            else:
10870                self.fail(f'{stop_exc} was suppressed')
10871"
10872        ));
10873    }
10874
10875    #[test]
10876    fn test_scope_exit_instructions_keep_line_numbers() {
10877        let code = compile_exec(
10878            "\
10879async def test():
10880    for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):
10881        with self.subTest(type=type(stop_exc)):
10882            try:
10883                async with egg():
10884                    raise stop_exc
10885            except Exception as ex:
10886                self.assertIs(ex, stop_exc)
10887            else:
10888                self.fail(f'{stop_exc} was suppressed')
10889",
10890        );
10891        assert_scope_exit_locations(&code);
10892    }
10893
10894    #[test]
10895    fn test_attribute_ex_call_uses_plain_load_attr() {
10896        let code = compile_exec(
10897            "\
10898def f(cls, args, kwargs):
10899    cls.__new__(cls, *args)
10900    cls.__new__(cls, *args, **kwargs)
10901",
10902        );
10903        let f = find_code(&code, "f").expect("missing function code");
10904
10905        let ex_call_count = f
10906            .instructions
10907            .iter()
10908            .filter(|unit| matches!(unit.op, Instruction::CallFunctionEx))
10909            .count();
10910        let load_attr_count = f
10911            .instructions
10912            .iter()
10913            .filter(|unit| matches!(unit.op, Instruction::LoadAttr { .. }))
10914            .count();
10915
10916        assert_eq!(ex_call_count, 2);
10917        assert_eq!(load_attr_count, 2);
10918
10919        for unit in f.instructions.iter() {
10920            if let Instruction::LoadAttr { namei } = unit.op {
10921                let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
10922                assert!(
10923                    !load_attr.is_method(),
10924                    "CALL_FUNCTION_EX should use plain LOAD_ATTR"
10925                );
10926            }
10927        }
10928    }
10929
10930    #[test]
10931    fn test_simple_attribute_call_keeps_method_load() {
10932        let code = compile_exec(
10933            "\
10934def f(obj, arg):
10935    return obj.method(arg)
10936",
10937        );
10938        let f = find_code(&code, "f").expect("missing function code");
10939        let load_attr = f
10940            .instructions
10941            .iter()
10942            .find_map(|unit| match unit.op {
10943                Instruction::LoadAttr { namei } => {
10944                    Some(namei.get(OpArg::new(u32::from(u8::from(unit.arg)))))
10945                }
10946                _ => None,
10947            })
10948            .expect("missing LOAD_ATTR");
10949
10950        assert!(
10951            load_attr.is_method(),
10952            "simple method calls should stay optimized"
10953        );
10954    }
10955
10956    #[test]
10957    fn test_builtin_any_genexpr_call_is_optimized() {
10958        let code = compile_exec(
10959            "\
10960def f(xs):
10961    return any(x for x in xs)
10962",
10963        );
10964        let f = find_code(&code, "f").expect("missing function code");
10965
10966        assert!(has_common_constant(f, bytecode::CommonConstant::BuiltinAny));
10967        assert!(
10968            f.instructions
10969                .iter()
10970                .any(|unit| matches!(unit.op, Instruction::PopJumpIfTrue { .. }))
10971        );
10972        assert!(
10973            f.instructions
10974                .iter()
10975                .any(|unit| matches!(unit.op, Instruction::NotTaken))
10976        );
10977        assert_eq!(
10978            f.instructions
10979                .iter()
10980                .filter(|unit| matches!(unit.op, Instruction::PushNull))
10981                .count(),
10982            1,
10983            "fallback call path should remain for shadowed any()"
10984        );
10985    }
10986
10987    #[test]
10988    fn test_builtin_tuple_list_set_genexpr_calls_are_optimized() {
10989        let code = compile_exec(
10990            "\
10991def tuple_f(xs):
10992    return tuple(x for x in xs)
10993
10994def list_f(xs):
10995    return list(x for x in xs)
10996
10997def set_f(xs):
10998    return set(x for x in xs)
10999",
11000        );
11001
11002        let tuple_f = find_code(&code, "tuple_f").expect("missing tuple_f code");
11003        assert!(has_common_constant(
11004            tuple_f,
11005            bytecode::CommonConstant::BuiltinTuple
11006        ));
11007        assert!(has_intrinsic_1(tuple_f, IntrinsicFunction1::ListToTuple));
11008        let tuple_list_append = tuple_f
11009            .instructions
11010            .iter()
11011            .find_map(|unit| match unit.op {
11012                Instruction::ListAppend { .. } => Some(u32::from(u8::from(unit.arg))),
11013                _ => None,
11014            })
11015            .expect("tuple(genexpr) fast path should emit LIST_APPEND");
11016        assert_eq!(tuple_list_append, 2);
11017
11018        let list_f = find_code(&code, "list_f").expect("missing list_f code");
11019        assert!(has_common_constant(
11020            list_f,
11021            bytecode::CommonConstant::BuiltinList
11022        ));
11023        assert!(
11024            list_f
11025                .instructions
11026                .iter()
11027                .any(|unit| matches!(unit.op, Instruction::ListAppend { .. }))
11028        );
11029
11030        let set_f = find_code(&code, "set_f").expect("missing set_f code");
11031        assert!(has_common_constant(
11032            set_f,
11033            bytecode::CommonConstant::BuiltinSet
11034        ));
11035        assert!(
11036            set_f
11037                .instructions
11038                .iter()
11039                .any(|unit| matches!(unit.op, Instruction::SetAdd { .. }))
11040        );
11041    }
11042
11043    #[test]
11044    fn test_module_store_uses_store_global_when_nested_scope_declares_global() {
11045        let code = compile_exec(
11046            "\
11047_address_fmt_re = None
11048
11049class C:
11050    def f(self):
11051        global _address_fmt_re
11052        if _address_fmt_re is None:
11053            _address_fmt_re = 1
11054",
11055        );
11056
11057        assert!(code.instructions.iter().any(|unit| match unit.op {
11058            Instruction::StoreGlobal { namei } => {
11059                let idx = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
11060                code.names[usize::try_from(idx).unwrap()].as_str() == "_address_fmt_re"
11061            }
11062            _ => false,
11063        }));
11064    }
11065
11066    #[test]
11067    fn test_conditional_return_epilogue_is_duplicated() {
11068        let code = compile_exec(
11069            "\
11070def f(base, cls, state):
11071    if base is object:
11072        obj = object.__new__(cls)
11073    else:
11074        obj = base.__new__(cls, state)
11075    return obj
11076",
11077        );
11078        let f = find_code(&code, "f").expect("missing function code");
11079        let return_count = f
11080            .instructions
11081            .iter()
11082            .filter(|unit| matches!(unit.op, Instruction::ReturnValue))
11083            .count();
11084
11085        assert_eq!(return_count, 2);
11086    }
11087
11088    #[test]
11089    fn test_assert_without_message_raises_class_directly() {
11090        let code = compile_exec(
11091            "\
11092def f(x):
11093    assert x
11094",
11095        );
11096        let f = find_code(&code, "f").expect("missing function code");
11097        let call_count = f
11098            .instructions
11099            .iter()
11100            .filter(|unit| matches!(unit.op, Instruction::Call { .. }))
11101            .count();
11102        let push_null_count = f
11103            .instructions
11104            .iter()
11105            .filter(|unit| matches!(unit.op, Instruction::PushNull))
11106            .count();
11107
11108        assert_eq!(call_count, 0);
11109        assert_eq!(push_null_count, 0);
11110    }
11111
11112    #[test]
11113    fn test_chained_compare_jump_uses_single_cleanup_copy() {
11114        let code = compile_exec(
11115            "\
11116def f(code):
11117    if not 1 <= code <= 2147483647:
11118        raise ValueError('x')
11119",
11120        );
11121        let f = find_code(&code, "f").expect("missing function code");
11122        let copy_count = f
11123            .instructions
11124            .iter()
11125            .filter(|unit| matches!(unit.op, Instruction::Copy { .. }))
11126            .count();
11127        let pop_top_count = f
11128            .instructions
11129            .iter()
11130            .filter(|unit| matches!(unit.op, Instruction::PopTop))
11131            .count();
11132
11133        assert_eq!(copy_count, 1);
11134        assert_eq!(pop_top_count, 1);
11135    }
11136
11137    #[test]
11138    fn test_constant_slice_folding_handles_string_and_bigint_bounds() {
11139        let code = compile_exec(
11140            "\
11141def f(obj):
11142    return obj['a':123456789012345678901234567890]
11143",
11144        );
11145        let f = find_code(&code, "f").expect("missing function code");
11146        let slice = f
11147            .constants
11148            .iter()
11149            .find_map(|constant| match constant {
11150                ConstantData::Slice { elements } => Some(elements),
11151                _ => None,
11152            })
11153            .expect("missing folded slice constant");
11154
11155        assert!(matches!(slice[0], ConstantData::Str { .. }));
11156        assert!(matches!(slice[1], ConstantData::Integer { .. }));
11157        assert!(matches!(slice[2], ConstantData::None));
11158    }
11159
11160    #[test]
11161    fn test_exception_cleanup_jump_to_return_is_inlined() {
11162        let code = compile_exec(
11163            "\
11164def f(names, cls):
11165    try:
11166        cls.attr = names
11167    except:
11168        pass
11169    return names
11170",
11171        );
11172        let f = find_code(&code, "f").expect("missing function code");
11173        let return_count = f
11174            .instructions
11175            .iter()
11176            .filter(|unit| matches!(unit.op, Instruction::ReturnValue))
11177            .count();
11178
11179        assert_eq!(return_count, 2);
11180    }
11181
11182    #[test]
11183    fn test_fstring_adjacent_literals_are_merged() {
11184        let code = compile_exec(
11185            "\
11186def f(cls, proto):
11187    raise TypeError(
11188        f\"cannot pickle {cls.__name__!r} object: \"
11189        f\"a class that defines __slots__ without \"
11190        f\"defining __getstate__ cannot be pickled \"
11191        f\"with protocol {proto}\"
11192    )
11193",
11194        );
11195        let f = find_code(&code, "f").expect("missing function code");
11196        let string_consts = f
11197            .instructions
11198            .iter()
11199            .filter_map(|unit| match unit.op {
11200                Instruction::LoadConst { consti } => {
11201                    Some(&f.constants[consti.get(OpArg::new(u32::from(u8::from(unit.arg))))])
11202                }
11203                _ => None,
11204            })
11205            .filter_map(|constant| match constant {
11206                ConstantData::Str { value } => Some(value.to_string()),
11207                _ => None,
11208            })
11209            .collect::<Vec<_>>();
11210
11211        assert!(
11212            string_consts.iter().any(|value| {
11213                value
11214                    == " object: a class that defines __slots__ without defining __getstate__ cannot be pickled with protocol "
11215            }),
11216            "expected merged trailing f-string literal, got {string_consts:?}"
11217        );
11218        assert!(
11219            !string_consts.iter().any(|value| value == " object: "),
11220            "did not expect split trailing literal, got {string_consts:?}"
11221        );
11222    }
11223
11224    #[test]
11225    fn test_literal_only_fstring_statement_is_optimized_away() {
11226        let code = compile_exec(
11227            "\
11228def f():
11229    f'''Not a docstring'''
11230",
11231        );
11232        let f = find_code(&code, "f").expect("missing function code");
11233
11234        assert!(
11235            !f.instructions
11236                .iter()
11237                .any(|unit| matches!(unit.op, Instruction::PopTop)),
11238            "literal-only f-string statement should be removed"
11239        );
11240        assert!(
11241            !f.constants.iter().any(|constant| matches!(
11242                constant,
11243                ConstantData::Str { value } if value.to_string() == "Not a docstring"
11244            )),
11245            "literal-only f-string should not survive in constants"
11246        );
11247    }
11248
11249    #[test]
11250    fn test_empty_fstring_literals_are_elided_around_interpolation() {
11251        let code = compile_exec(
11252            "\
11253def f(x):
11254    if '' f'{x}':
11255        return 1
11256    return 2
11257",
11258        );
11259        let f = find_code(&code, "f").expect("missing function code");
11260
11261        let empty_string_loads = f
11262            .instructions
11263            .iter()
11264            .filter_map(|unit| match unit.op {
11265                Instruction::LoadConst { consti } => {
11266                    Some(&f.constants[consti.get(OpArg::new(u32::from(u8::from(unit.arg))))])
11267                }
11268                _ => None,
11269            })
11270            .filter(|constant| {
11271                matches!(
11272                    constant,
11273                    ConstantData::Str { value } if value.is_empty()
11274                )
11275            })
11276            .count();
11277        let build_string_count = f
11278            .instructions
11279            .iter()
11280            .filter(|unit| matches!(unit.op, Instruction::BuildString { .. }))
11281            .count();
11282
11283        assert_eq!(empty_string_loads, 0);
11284        assert_eq!(build_string_count, 0);
11285    }
11286
11287    #[test]
11288    fn test_large_power_is_not_constant_folded() {
11289        let code = compile_exec("x = 2**100\n");
11290
11291        assert!(code.instructions.iter().any(|unit| match unit.op {
11292            Instruction::BinaryOp { op } => {
11293                op.get(OpArg::new(u32::from(u8::from(unit.arg)))) == oparg::BinaryOperator::Power
11294            }
11295            _ => false,
11296        }));
11297    }
11298
11299    #[test]
11300    fn test_list_of_constant_tuples_uses_list_extend() {
11301        let code = compile_exec(
11302            "\
11303deprecated_cases = [('a', 'b'), ('c', 'd'), ('e', 'f'), ('g', 'h'), ('i', 'j')]
11304",
11305        );
11306
11307        assert!(
11308            code.instructions
11309                .iter()
11310                .any(|unit| matches!(unit.op, Instruction::ListExtend { .. })),
11311            "expected constant tuple list folding"
11312        );
11313    }
11314
11315    #[test]
11316    fn test_constant_list_iterable_uses_tuple() {
11317        let code = compile_exec(
11318            "\
11319def f():
11320    return {x: y for x, y in [(1, 2), ]}
11321",
11322        );
11323        let f = find_code(&code, "f").expect("missing function code");
11324
11325        assert!(
11326            !f.instructions
11327                .iter()
11328                .any(|unit| matches!(unit.op, Instruction::BuildList { .. })),
11329            "constant list iterable should avoid BUILD_LIST before GET_ITER"
11330        );
11331        assert!(f.constants.iter().any(|constant| matches!(
11332            constant,
11333            ConstantData::Tuple { elements }
11334                if matches!(
11335                    elements.as_slice(),
11336                    [ConstantData::Tuple { elements: inner }]
11337                        if matches!(
11338                            inner.as_slice(),
11339                            [
11340                                ConstantData::Integer { .. },
11341                                ConstantData::Integer { .. }
11342                            ]
11343                        )
11344                )
11345        )));
11346    }
11347
11348    #[test]
11349    fn test_constant_set_iterable_keeps_runtime_set_build() {
11350        let code = compile_exec(
11351            "\
11352def f():
11353    return [x for x in {1, 2, 3}]
11354",
11355        );
11356        let f = find_code(&code, "f").expect("missing function code");
11357
11358        assert!(
11359            f.instructions
11360                .iter()
11361                .any(|unit| matches!(unit.op, Instruction::BuildSet { .. })),
11362            "constant set iterable should keep BUILD_SET before GET_ITER"
11363        );
11364        assert!(f.constants.iter().any(|constant| matches!(
11365            constant,
11366            ConstantData::Tuple { elements }
11367                if matches!(
11368                    elements.as_slice(),
11369                    [
11370                        ConstantData::Integer { .. },
11371                        ConstantData::Integer { .. },
11372                        ConstantData::Integer { .. }
11373                    ]
11374                )
11375        )));
11376    }
11377
11378    #[test]
11379    fn test_optimized_assert_preserves_nested_scope_order() {
11380        compile_exec_optimized(
11381            "\
11382class S:
11383    def f(self, sequence):
11384        _formats = [self._types_mapping[type(item)] for item in sequence]
11385        _list_len = len(_formats)
11386        assert sum(len(fmt) <= 8 for fmt in _formats) == _list_len
11387        _recreation_codes = [self._extract_recreation_code(item) for item in sequence]
11388",
11389        );
11390    }
11391
11392    #[test]
11393    fn test_optimized_assert_with_nested_scope_in_first_iter() {
11394        // First iterator of a comprehension is evaluated in the enclosing
11395        // scope, so nested scopes inside it (the generator here) must also
11396        // be consumed when the assert is optimized away.
11397        compile_exec_optimized(
11398            "\
11399def f(items):
11400    assert [x for x in (y for y in items)]
11401    return [x for x in items]
11402",
11403        );
11404    }
11405
11406    #[test]
11407    fn test_optimized_assert_with_lambda_defaults() {
11408        // Lambda default values are evaluated in the enclosing scope,
11409        // so nested scopes inside defaults must be consumed.
11410        compile_exec_optimized(
11411            "\
11412def f(items):
11413    assert (lambda x=[i for i in items]: x)()
11414    return [x for x in items]
11415",
11416        );
11417    }
11418}