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, ast_constant_value_to_constant_data,
14    error::{CodegenError, CodegenErrorType, InternalError},
15    interpolated_string_literal_value, interpolation_debug_text,
16    ir::{self, Block, BlockIdx, Blocks},
17    preprocess, string_literal_part_value, string_literal_value,
18    symboltable::{self, CompilerScope, Symbol, SymbolFlags, SymbolScope, SymbolTable},
19    unparse::UnparseExpr,
20};
21use alloc::borrow::Cow;
22use core::{mem, slice};
23use malachite_bigint::BigInt;
24use num_complex::Complex;
25use num_traits::{Num, ToPrimitive, Zero};
26use ruff_python_ast::{self as ast, name::Name};
27use ruff_text_size::{Ranged, TextRange, TextSize, TextSlice};
28use rustpython_compiler_core::{
29    Mode, OneIndexed, PositionEncoding, SourceFile, SourceLocation,
30    bytecode::{
31        self, AnyInstruction, AnyOpcode, Arg as OpArgMarker, BinaryOperator, BuildSliceArgCount,
32        CodeObject, ComparisonOperator, ConstantData, ConvertValueOparg, Instruction,
33        IntrinsicFunction1, Invert, LoadAttr, LoadSuperAttr, OpArg, OpArgType, PseudoInstruction,
34        SpecialMethod, UnpackExArgs, oparg,
35    },
36};
37use rustpython_literal::{
38    complex as literal_complex,
39    escape::{AsciiEscape, UnicodeEscape},
40    float as literal_float,
41};
42use rustpython_wtf8::Wtf8Buf;
43
44/// Extension trait for `ast::Expr` to add constant checking methods
45trait ExprExt {
46    /// Returns true if the expression is a constant literal with no side effects.
47    fn is_constant(&self) -> bool;
48}
49
50impl ExprExt for ast::Expr {
51    fn is_constant(&self) -> bool {
52        matches!(
53            self,
54            Self::NumberLiteral(_)
55                | Self::StringLiteral(_)
56                | Self::BytesLiteral(_)
57                | Self::Constant(_)
58                | Self::NoneLiteral(_)
59                | Self::BooleanLiteral(_)
60                | Self::EllipsisLiteral(_)
61        )
62    }
63}
64
65const CO_MAXBLOCKS: usize = 21;
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum FBlockType {
69    WhileLoop,
70    ForLoop,
71    TryExcept,
72    FinallyTry,
73    FinallyEnd,
74    With,
75    AsyncWith,
76    HandlerCleanup,
77    PopValue,
78    ExceptionHandler,
79    ExceptionGroupHandler,
80    AsyncComprehensionGenerator,
81    StopIteration,
82}
83
84/// Stores additional data for fblock unwinding
85// fb_datum
86#[derive(Debug, Clone)]
87pub enum FBlockDatum {
88    None,
89    /// For FinallyTry: stores the finally body statements to compile during unwind
90    FinallyBody(Vec<ast::Stmt>),
91    /// For HandlerCleanup: stores the exception variable name (e.g., "e" in "except X as e")
92    ExceptionName(String),
93}
94
95/// Type of super() call optimization detected by can_optimize_super_call()
96#[derive(Debug, Clone)]
97enum SuperCallType<'a> {
98    /// super(class, self) - explicit 2-argument form
99    TwoArg {
100        class_arg: &'a ast::Expr,
101        self_arg: &'a ast::Expr,
102    },
103    /// super() - implicit 0-argument form (uses __class__ cell)
104    ZeroArg,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108enum BuiltinGeneratorCallKind {
109    Tuple,
110    All,
111    Any,
112}
113
114#[derive(Debug, Clone)]
115pub struct FBlockInfo {
116    pub fb_type: FBlockType,
117    // CPython _PyCompile_FBlockInfo stores jump_target_label values here.
118    pub(crate) fb_block: ir::InstructionSequenceLabel,
119    // CPython's optional type-specific exit or cleanup jump_target_label.
120    pub(crate) fb_exit: ir::InstructionSequenceLabel,
121    pub fb_range: TextRange,
122    // additional data for fblock unwinding
123    pub fb_datum: FBlockDatum,
124}
125
126pub(crate) type InternalResult<T> = Result<T, InternalError>;
127type CompileResult<T> = Result<T, CodegenError>;
128pub type SyntaxWarningHandler<'a> =
129    dyn FnMut(SourceLocation, String) -> Result<(), CodegenError> + 'a;
130
131fn warn_ast_preprocess_syntax(
132    source_file: &SourceFile,
133    handler: &mut SyntaxWarningHandler<'_>,
134    range: TextRange,
135    message: String,
136) -> CompileResult<()> {
137    let location = source_file
138        .to_source_code()
139        .source_location(range.start(), PositionEncoding::Utf8);
140    handler(location, message)
141}
142
143fn checked_future_features(
144    ast: &ruff_python_ast::Mod,
145    source_file: &SourceFile,
146) -> CompileResult<bytecode::CodeFlags> {
147    preprocess::checked_future_features(ast).map_err(|err| {
148        let source_code = source_file.to_source_code();
149        let location = source_code.source_location(err.range.start(), PositionEncoding::Utf8);
150        let end_location = source_code.source_location(err.range.end(), PositionEncoding::Utf8);
151        let error = match err.kind {
152            preprocess::FutureFeatureErrorKind::InvalidFeature(feature) => {
153                CodegenErrorType::InvalidFutureFeature(feature)
154            }
155            preprocess::FutureFeatureErrorKind::InvalidBraces => {
156                CodegenErrorType::InvalidFutureBraces
157            }
158        };
159        CodegenError {
160            location: Some(location),
161            end_location: Some(end_location),
162            error,
163            source_path: source_file.name().to_owned(),
164        }
165    })
166}
167
168#[derive(PartialEq, Eq, Clone, Copy)]
169enum NameUsage {
170    Load,
171    Store,
172    Delete,
173}
174/// Main structure holding the state of compilation.
175struct Compiler<'a> {
176    code_stack: Vec<ir::CodeInfo>,
177    symbol_table_stack: Vec<SymbolTable>,
178    source_file: SourceFile,
179    // current_source_location: SourceLocation,
180    current_source_range: TextRange,
181    future_features: bytecode::CodeFlags,
182    future_annotations: bool,
183    ctx: CompileContext,
184    opts: CompileOpts,
185    in_annotation: bool,
186    /// True when compiling in "single" (interactive) mode.
187    /// Expression statements at module scope emit CALL_INTRINSIC_1(Print).
188    interactive: bool,
189    /// Counter for dead-code elimination during constant folding.
190    /// When > 0, the compiler walks AST (consuming sub_tables) but emits no bytecode.
191    /// Mirrors CPython's `c_do_not_emit_bytecode`.
192    do_not_emit_bytecode: u32,
193    /// Mirrors `c_disable_warning` while compiling FINALLY_END copies.
194    disable_warning: u32,
195    syntax_warning_handler: Option<&'a mut SyntaxWarningHandler<'a>>,
196    /// If true, keep unoptimized nested instruction sequences on the parent.
197    save_nested_seqs: bool,
198}
199
200/// A Python `__future__` feature flag imported via `from __future__ import <feature>`.
201///
202/// # See Also
203///
204/// - [Python documentation on `__future__`](https://docs.python.org/3.14/library/__future__.html)
205#[derive(Clone, Copy, Debug, Eq, PartialEq)]
206pub enum FutureFeature {
207    /// ```py
208    /// from __future__ import absolute_import
209    /// ```
210    AbsoluteImport,
211
212    /// ```py
213    /// from __future__ import annotations
214    /// ```
215    Annotations,
216
217    /// ```py
218    /// from __future__ import barry_as_FLUFL
219    /// ```
220    BarryAsFLUFL,
221
222    /// ```py
223    /// from __future__ import braces
224    /// ```
225    Braces,
226
227    /// ```py
228    /// from __future__ import division
229    /// ```
230    Division,
231
232    /// ```py
233    /// from __future__ import generator_stop
234    /// ```
235    GeneratorStop,
236
237    /// ```py
238    /// from __future__ import generators
239    /// ```
240    Generators,
241
242    /// ```py
243    /// from __future__ import nested_scopes
244    /// ```
245    NestedScopes,
246
247    /// ```py
248    /// from __future__ import print_function
249    /// ```
250    PrintFunction,
251
252    /// ```py
253    /// from __future__ import unicode_literals
254    /// ```
255    UnicodeLiterals,
256
257    /// ```py
258    /// from __future__ import with_statement
259    /// ```
260    WithStatement,
261}
262
263impl TryFrom<&str> for FutureFeature {
264    type Error = String;
265
266    fn try_from(name: &str) -> Result<Self, Self::Error> {
267        Ok(match name {
268            "absolute_import" => Self::AbsoluteImport,
269            "annotations" => Self::Annotations,
270            "barry_as_FLUFL" => Self::BarryAsFLUFL,
271            "braces" => Self::Braces,
272            "division" => Self::Division,
273            "generator_stop" => Self::GeneratorStop,
274            "generators" => Self::Generators,
275            "nested_scopes" => Self::NestedScopes,
276            "print_function" => Self::PrintFunction,
277            "unicode_literals" => Self::UnicodeLiterals,
278            "with_statement" => Self::WithStatement,
279            _ => return Err(name.into()),
280        })
281    }
282}
283
284#[derive(Clone, Copy)]
285enum ComprehensionSymbolSource {
286    Child,
287    Inlined,
288}
289
290#[derive(Clone, Copy)]
291struct SymbolTableCursors {
292    sub_table: usize,
293    hidden_annotation_block: usize,
294    inlined_comprehension_block: usize,
295}
296
297#[derive(Clone, Debug)]
298pub struct CompileOpts {
299    /// How optimized the bytecode output should be; any optimize > 0 does
300    /// not emit assert statements
301    pub optimize: u8,
302    /// Include column info in bytecode (-X no_debug_ranges disables)
303    pub debug_ranges: bool,
304    /// Maximum decimal integer literal digits, matching sys.int_info/default.
305    pub int_max_str_digits: usize,
306    /// Allow module-level await/async-for/async-with, matching PyCF_ALLOW_TOP_LEVEL_AWAIT.
307    pub allow_top_level_await: bool,
308    /// Future compiler flags passed explicitly to compile(), matching cf_flags merge.
309    pub future_features: bytecode::CodeFlags,
310    /// Keep single-input blocks incomplete until a terminating newline is seen.
311    pub dont_imply_dedent: bool,
312    /// Recursion limit used by compiler tree walks, matching Py_EnterRecursiveCall.
313    pub recursion_limit: usize,
314}
315
316impl Default for CompileOpts {
317    fn default() -> Self {
318        Self {
319            optimize: 0,
320            debug_ranges: true,
321            int_max_str_digits: 4300,
322            allow_top_level_await: false,
323            future_features: bytecode::CodeFlags::empty(),
324            dont_imply_dedent: false,
325            recursion_limit: 1000,
326        }
327    }
328}
329
330#[derive(Debug, Clone, Copy)]
331struct CompileContext {
332    in_class: bool,
333    func: FunctionContext,
334    /// True if we're anywhere inside an async function (even inside nested comprehensions)
335    in_async_scope: bool,
336}
337
338#[derive(Debug, Clone, Copy, PartialEq)]
339enum FunctionContext {
340    NoFunction,
341    Function,
342    AsyncFunction,
343}
344
345impl CompileContext {
346    fn in_func(self) -> bool {
347        self.func != FunctionContext::NoFunction
348    }
349}
350
351#[derive(Debug, Clone, Copy, PartialEq)]
352enum ComprehensionType {
353    Generator,
354    List,
355    Set,
356    Dict,
357}
358
359#[derive(Debug, Clone, Copy)]
360enum ComprehensionLoopControl {
361    Iteration {
362        loop_block: BlockIdx,
363        if_cleanup_block: BlockIdx,
364        after_block: BlockIdx,
365        backedge_range: TextRange,
366        is_async: bool,
367        end_async_for_target: BlockIdx,
368    },
369    IfCleanupOnly {
370        if_cleanup_block: BlockIdx,
371    },
372}
373
374fn validate_duplicate_params(params: &ast::Parameters) -> Result<(), CodegenErrorType> {
375    let mut seen_params = IndexSet::default();
376    for param in params {
377        let param_name = param.name().as_str();
378        if !seen_params.insert(param_name) {
379            return Err(CodegenErrorType::SyntaxError(format!(
380                r#"Duplicate parameter "{param_name}""#
381            )));
382        }
383    }
384
385    Ok(())
386}
387
388/// Compile an Mod produced from ruff parser
389pub fn compile_top(
390    ast: ruff_python_ast::Mod,
391    source_file: SourceFile,
392    mode: Mode,
393    opts: CompileOpts,
394) -> CompileResult<CodeObject> {
395    compile_top_with_syntax_warning_handler(ast, source_file, mode, opts, None)
396}
397
398pub fn compile_top_with_syntax_warning_handler<'a>(
399    mut ast: ruff_python_ast::Mod,
400    source_file: SourceFile,
401    mode: Mode,
402    mut opts: CompileOpts,
403    mut syntax_warning_handler: Option<&'a mut SyntaxWarningHandler<'a>>,
404) -> CompileResult<CodeObject> {
405    opts.future_features |= checked_future_features(&ast, &source_file)?;
406    let future_annotations = opts
407        .future_features
408        .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
409    if let Some(handler) = syntax_warning_handler.as_deref_mut() {
410        preprocess::warn_control_flow_in_finally(&ast, |range, message| {
411            warn_ast_preprocess_syntax(&source_file, handler, range, message)
412        })?;
413    }
414    if matches!(mode, Mode::Single)
415        && let ruff_python_ast::Mod::Module(module) = &mut ast
416    {
417        preprocess::preprocess_statements(
418            &mut module.body,
419            opts.optimize,
420            future_annotations,
421            false,
422        );
423    } else {
424        preprocess::preprocess_mod(&mut ast, opts.optimize, future_annotations, false);
425    }
426    match ast {
427        ruff_python_ast::Mod::Module(module) => match mode {
428            Mode::Exec | Mode::Eval => compile_program_with_syntax_warning_handler(
429                &module,
430                source_file,
431                opts,
432                syntax_warning_handler,
433            ),
434            Mode::Single => compile_program_single_with_syntax_warning_handler(
435                &module,
436                source_file,
437                opts,
438                syntax_warning_handler,
439            ),
440            Mode::BlockExpr => compile_block_expression_with_syntax_warning_handler(
441                &module,
442                source_file,
443                opts,
444                syntax_warning_handler,
445            ),
446        },
447        ruff_python_ast::Mod::Expression(expr) => compile_expression_with_syntax_warning_handler(
448            &expr,
449            source_file,
450            opts,
451            syntax_warning_handler,
452        ),
453    }
454}
455
456/// Unoptimized instruction sequence and the metadata needed by optimize_cfg.
457pub struct CodegenOutput {
458    pub seq: ir::InstructionSequence,
459    pub argcount: u32,
460    pub posonlyargcount: u32,
461    pub kwonlyargcount: u32,
462    pub consts: Vec<ConstantData>,
463}
464
465/// compile.c _PyCompile_CodeGen
466pub fn compile_codegen(
467    ast: ruff_python_ast::Mod,
468    source_file: SourceFile,
469    mode: Mode,
470    opts: CompileOpts,
471) -> CompileResult<CodegenOutput> {
472    compile_codegen_with_syntax_warning_handler(ast, source_file, mode, opts, None)
473}
474
475fn compile_codegen_with_syntax_warning_handler<'a>(
476    mut ast: ruff_python_ast::Mod,
477    source_file: SourceFile,
478    mode: Mode,
479    mut opts: CompileOpts,
480    mut syntax_warning_handler: Option<&'a mut SyntaxWarningHandler<'a>>,
481) -> CompileResult<CodegenOutput> {
482    opts.future_features |= checked_future_features(&ast, &source_file)?;
483    let future_annotations = opts
484        .future_features
485        .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
486    if let Some(handler) = syntax_warning_handler.as_deref_mut() {
487        preprocess::warn_control_flow_in_finally(&ast, |range, message| {
488            warn_ast_preprocess_syntax(&source_file, handler, range, message)
489        })?;
490    }
491    if matches!(mode, Mode::Single)
492        && let ruff_python_ast::Mod::Module(module) = &mut ast
493    {
494        preprocess::preprocess_statements(
495            &mut module.body,
496            opts.optimize,
497            future_annotations,
498            false,
499        );
500    } else {
501        preprocess::preprocess_mod(&mut ast, opts.optimize, future_annotations, false);
502    }
503
504    let mut compiler = Compiler::new_with_syntax_warning_handler(
505        opts,
506        source_file.clone(),
507        "<module>",
508        syntax_warning_handler,
509    );
510    compiler.save_nested_seqs = true;
511    match ast {
512        ruff_python_ast::Mod::Module(module) => match mode {
513            Mode::Exec | Mode::Eval => {
514                let symbol_table = scan_module_symbols(&module, &source_file, &compiler.opts)?;
515                compiler.compile_program(&module, symbol_table)?;
516            }
517            Mode::Single => {
518                let symbol_table = scan_module_symbols(&module, &source_file, &compiler.opts)?;
519                compiler.compile_program_single(&module.body, symbol_table)?;
520            }
521            Mode::BlockExpr => {
522                let symbol_table = scan_module_symbols(&module, &source_file, &compiler.opts)?;
523                compiler.compile_block_expr(&module.body, symbol_table)?;
524            }
525        },
526        ruff_python_ast::Mod::Expression(expr) => {
527            let symbol_table = scan_expr_symbols(&expr, &source_file, &compiler.opts)?;
528            compiler.compile_eval(&expr, symbol_table)?;
529        }
530    }
531
532    let mut unit = compiler
533        .code_stack
534        .pop()
535        .expect("codegen leaves the top-level unit on the stack");
536    unit.instr_sequence.apply_label_map();
537    Ok(CodegenOutput {
538        seq: unit.instr_sequence,
539        argcount: unit.metadata.argcount,
540        posonlyargcount: unit.metadata.posonlyargcount,
541        kwonlyargcount: unit.metadata.kwonlyargcount,
542        consts: unit.metadata.consts.into_vec(),
543    })
544}
545
546/// Compile a standard Python program to bytecode
547pub fn compile_program(
548    ast: &ast::ModModule,
549    source_file: SourceFile,
550    opts: CompileOpts,
551) -> CompileResult<CodeObject> {
552    compile_program_with_syntax_warning_handler(ast, source_file, opts, None)
553}
554
555fn scan_module_symbols(
556    ast: &ast::ModModule,
557    source_file: &SourceFile,
558    opts: &CompileOpts,
559) -> CompileResult<SymbolTable> {
560    SymbolTable::scan_program_with_options(
561        ast,
562        source_file.clone(),
563        opts.allow_top_level_await,
564        opts.future_features
565            .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS),
566        opts.recursion_limit,
567    )
568    .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))
569}
570
571fn scan_expr_symbols(
572    ast: &ast::ModExpression,
573    source_file: &SourceFile,
574    opts: &CompileOpts,
575) -> CompileResult<SymbolTable> {
576    SymbolTable::scan_expr_with_options(
577        ast,
578        source_file.clone(),
579        opts.allow_top_level_await,
580        opts.future_features
581            .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS),
582        opts.recursion_limit,
583    )
584    .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))
585}
586
587fn compile_program_with_syntax_warning_handler<'a>(
588    ast: &ast::ModModule,
589    source_file: SourceFile,
590    opts: CompileOpts,
591    syntax_warning_handler: Option<&'a mut SyntaxWarningHandler<'a>>,
592) -> CompileResult<CodeObject> {
593    let symbol_table = scan_module_symbols(ast, &source_file, &opts)?;
594    let mut compiler = Compiler::new_with_syntax_warning_handler(
595        opts,
596        source_file,
597        "<module>",
598        syntax_warning_handler,
599    );
600    compiler.compile_program(ast, symbol_table)?;
601    let code = compiler.exit_scope();
602    trace!("Compilation completed: {code:?}");
603    Ok(code)
604}
605
606/// Compile a Python program to bytecode for the context of a REPL
607pub fn compile_program_single(
608    ast: &ast::ModModule,
609    source_file: SourceFile,
610    opts: CompileOpts,
611) -> CompileResult<CodeObject> {
612    compile_program_single_with_syntax_warning_handler(ast, source_file, opts, None)
613}
614
615fn compile_program_single_with_syntax_warning_handler<'a>(
616    ast: &ast::ModModule,
617    source_file: SourceFile,
618    opts: CompileOpts,
619    syntax_warning_handler: Option<&'a mut SyntaxWarningHandler<'a>>,
620) -> CompileResult<CodeObject> {
621    let symbol_table = scan_module_symbols(ast, &source_file, &opts)?;
622    let mut compiler = Compiler::new_with_syntax_warning_handler(
623        opts,
624        source_file,
625        "<module>",
626        syntax_warning_handler,
627    );
628    compiler.compile_program_single(&ast.body, symbol_table)?;
629    let code = compiler.exit_scope();
630    trace!("Compilation completed: {code:?}");
631    Ok(code)
632}
633
634pub fn compile_block_expression(
635    ast: &ast::ModModule,
636    source_file: SourceFile,
637    opts: CompileOpts,
638) -> CompileResult<CodeObject> {
639    compile_block_expression_with_syntax_warning_handler(ast, source_file, opts, None)
640}
641
642fn compile_block_expression_with_syntax_warning_handler<'a>(
643    ast: &ast::ModModule,
644    source_file: SourceFile,
645    opts: CompileOpts,
646    syntax_warning_handler: Option<&'a mut SyntaxWarningHandler<'a>>,
647) -> CompileResult<CodeObject> {
648    let symbol_table = scan_module_symbols(ast, &source_file, &opts)?;
649    let mut compiler = Compiler::new_with_syntax_warning_handler(
650        opts,
651        source_file,
652        "<module>",
653        syntax_warning_handler,
654    );
655    compiler.compile_block_expr(&ast.body, symbol_table)?;
656    let code = compiler.exit_scope();
657    trace!("Compilation completed: {code:?}");
658    Ok(code)
659}
660
661pub fn compile_expression(
662    ast: &ast::ModExpression,
663    source_file: SourceFile,
664    opts: CompileOpts,
665) -> CompileResult<CodeObject> {
666    compile_expression_with_syntax_warning_handler(ast, source_file, opts, None)
667}
668
669fn compile_expression_with_syntax_warning_handler<'a>(
670    ast: &ast::ModExpression,
671    source_file: SourceFile,
672    opts: CompileOpts,
673    syntax_warning_handler: Option<&'a mut SyntaxWarningHandler<'a>>,
674) -> CompileResult<CodeObject> {
675    let symbol_table = scan_expr_symbols(ast, &source_file, &opts)?;
676    let mut compiler = Compiler::new_with_syntax_warning_handler(
677        opts,
678        source_file,
679        "<module>",
680        syntax_warning_handler,
681    );
682    compiler.compile_eval(ast, symbol_table)?;
683    let code = compiler.exit_scope();
684    Ok(code)
685}
686
687macro_rules! emit {
688    // Struct variant with single identifier (e.g., Foo::A { arg })
689    ($c:expr, $enum:ident :: $op:ident { $arg:ident $(,)? } $(,)?) => {
690        $c.emit_arg($arg, |x| $enum::$op { $arg: x })
691    };
692
693    // Struct variant with explicit value (e.g., Foo::A { arg: 42 })
694    ($c:expr, $enum:ident :: $op:ident { $arg:ident : $arg_val:expr $(,)? } $(,)?) => {
695        $c.emit_arg($arg_val, |x| $enum::$op { $arg: x })
696    };
697
698    // Tuple variant (e.g., Foo::B(42)). Should never be reached, here for validation.
699    ($c:expr, $enum:ident :: $op:ident($arg_val:expr $(,)? ) $(,)?) => {
700        panic!("No instruction should be defined as `Instruction::Foo(value)` use `Instruction::Foo { x: value }` instead")
701    };
702
703    // No-arg variant (e.g., Foo::C)
704    ($c:expr, $enum:ident :: $op:ident $(,)?) => {
705        $c.emit_no_arg($enum::$op)
706    };
707}
708
709fn eprint_location(zelf: &Compiler<'_>) {
710    let start = zelf
711        .source_file
712        .to_source_code()
713        .source_location(zelf.current_source_range.start(), PositionEncoding::Utf8);
714    let end = zelf
715        .source_file
716        .to_source_code()
717        .source_location(zelf.current_source_range.end(), PositionEncoding::Utf8);
718    eprintln!(
719        "LOCATION: {} from {}:{} to {}:{}",
720        zelf.source_file.name(),
721        start.line,
722        start.character_offset,
723        end.line,
724        end.character_offset
725    );
726}
727
728/// Better traceback for internal error
729#[track_caller]
730fn unwrap_internal<T>(zelf: &Compiler<'_>, r: InternalResult<T>) -> T {
731    if let Err(ref r_err) = r {
732        eprintln!("=== CODEGEN PANIC INFO ===");
733        eprintln!("This IS an internal error: {r_err}");
734        eprint_location(zelf);
735        eprintln!("=== END PANIC INFO ===");
736    }
737    r.unwrap()
738}
739
740fn compiler_unwrap_option<T>(zelf: &Compiler<'_>, o: Option<T>) -> T {
741    if o.is_none() {
742        eprintln!("=== CODEGEN PANIC INFO ===");
743        eprintln!("This IS an internal error, an option was unwrapped during codegen");
744        eprint_location(zelf);
745        eprintln!("=== END PANIC INFO ===");
746    }
747    o.unwrap()
748}
749
750// fn compiler_result_unwrap<T, E: core::fmt::Debug>(zelf: &Compiler, result: Result<T, E>) -> T {
751//     if result.is_err() {
752//         eprintln!("=== CODEGEN PANIC INFO ===");
753//         eprintln!("This IS an internal error, an result was unwrapped during codegen");
754//         eprint_location(zelf);
755//         eprintln!("=== END PANIC INFO ===");
756//     }
757//     result.unwrap()
758// }
759
760/// The pattern context holds information about captured names and jump targets.
761#[derive(Clone)]
762pub struct PatternContext {
763    /// A list of names captured by the pattern.
764    pub stores: Vec<Name>,
765    /// If false, then any name captures against our subject will raise.
766    pub allow_irrefutable: bool,
767    /// A list of jump target labels used on pattern failure.
768    pub fail_pop: Vec<BlockIdx>,
769    /// The number of items on top of the stack that should remain.
770    pub on_top: usize,
771}
772
773impl Default for PatternContext {
774    fn default() -> Self {
775        Self::new()
776    }
777}
778
779impl PatternContext {
780    #[must_use]
781    pub const fn new() -> Self {
782        Self {
783            stores: Vec::new(),
784            allow_irrefutable: false,
785            fail_pop: Vec::new(),
786            on_top: 0,
787        }
788    }
789
790    #[must_use]
791    pub fn fail_pop_size(&self) -> usize {
792        self.fail_pop.len()
793    }
794}
795
796#[derive(Clone, Copy, Eq, PartialEq)]
797enum JumpOp {
798    Jump,
799    PopJumpIfFalse,
800}
801
802/// Type of collection to build in starunpack_helper
803#[derive(Clone, Copy, Debug, Eq, PartialEq)]
804enum CollectionType {
805    Tuple,
806    List,
807    Set,
808}
809
810#[derive(Clone, Copy, Eq, PartialEq)]
811enum InferredType {
812    Tuple,
813    List,
814    Dict,
815    Set,
816    FrozenSet,
817    Generator,
818    Function,
819    Template,
820    Str,
821    Bytes,
822    Int,
823    Float,
824    Complex,
825    Bool,
826    NoneType,
827    Ellipsis,
828    Slice,
829}
830
831impl InferredType {
832    const fn name(self) -> &'static str {
833        match self {
834            Self::Tuple => "tuple",
835            Self::List => "list",
836            Self::Dict => "dict",
837            Self::Set => "set",
838            Self::FrozenSet => "frozenset",
839            Self::Generator => "generator",
840            Self::Function => "function",
841            Self::Template => "string.templatelib.Template",
842            Self::Str => "str",
843            Self::Bytes => "bytes",
844            Self::Int => "int",
845            Self::Float => "float",
846            Self::Complex => "complex",
847            Self::Bool => "bool",
848            Self::NoneType => "NoneType",
849            Self::Ellipsis => "ellipsis",
850            Self::Slice => "slice",
851        }
852    }
853
854    const fn is_long_subclass(self) -> bool {
855        matches!(self, Self::Int | Self::Bool)
856    }
857}
858
859const STACK_USE_GUIDELINE: u32 = 30;
860
861impl<'warnings> Compiler<'warnings> {
862    fn constant_truthiness(constant: &ConstantData) -> bool {
863        match constant {
864            ConstantData::Tuple { elements } | ConstantData::Frozenset { elements } => {
865                !elements.is_empty()
866            }
867            ConstantData::Integer { value } => !value.is_zero(),
868            ConstantData::Float { value } => *value != 0.0,
869            ConstantData::Complex { value } => value.re != 0.0 || value.im != 0.0,
870            ConstantData::Boolean { value } => *value,
871            ConstantData::Str { value } => !value.is_empty(),
872            ConstantData::Bytes { value } => !value.is_empty(),
873            ConstantData::Code { .. } | ConstantData::Slice { .. } | ConstantData::Ellipsis => true,
874            ConstantData::None => false,
875        }
876    }
877
878    fn infer_type_constant(constant: &ConstantData) -> Option<InferredType> {
879        match constant {
880            ConstantData::Tuple { .. } => Some(InferredType::Tuple),
881            ConstantData::Frozenset { .. } => Some(InferredType::FrozenSet),
882            ConstantData::Integer { .. } => Some(InferredType::Int),
883            ConstantData::Float { .. } => Some(InferredType::Float),
884            ConstantData::Complex { .. } => Some(InferredType::Complex),
885            ConstantData::Boolean { .. } => Some(InferredType::Bool),
886            ConstantData::Str { .. } => Some(InferredType::Str),
887            ConstantData::Bytes { .. } => Some(InferredType::Bytes),
888            ConstantData::None => Some(InferredType::NoneType),
889            ConstantData::Ellipsis => Some(InferredType::Ellipsis),
890            ConstantData::Slice { .. } => Some(InferredType::Slice),
891            ConstantData::Code { .. } => None,
892        }
893    }
894
895    fn infer_type(&self, expr: &ast::Expr) -> Option<InferredType> {
896        if let Some(constant) = self.ast_constant_value(expr) {
897            return Self::infer_type_constant(&constant);
898        }
899        match expr {
900            ast::Expr::Tuple(_) => Some(InferredType::Tuple),
901            ast::Expr::List(_) | ast::Expr::ListComp(_) => Some(InferredType::List),
902            ast::Expr::Dict(_) | ast::Expr::DictComp(_) => Some(InferredType::Dict),
903            ast::Expr::Set(_) | ast::Expr::SetComp(_) => Some(InferredType::Set),
904            ast::Expr::Generator(_) => Some(InferredType::Generator),
905            ast::Expr::Lambda(_) => Some(InferredType::Function),
906            ast::Expr::TString(_) => Some(InferredType::Template),
907            ast::Expr::FString(_) | ast::Expr::StringLiteral(_) => Some(InferredType::Str),
908            ast::Expr::BytesLiteral(_) => Some(InferredType::Bytes),
909            ast::Expr::NumberLiteral(number) => match number.value {
910                ast::Number::Int(_) => Some(InferredType::Int),
911                ast::Number::Float(_) => Some(InferredType::Float),
912                ast::Number::Complex { .. } => Some(InferredType::Complex),
913            },
914            ast::Expr::BooleanLiteral(_) => Some(InferredType::Bool),
915            ast::Expr::NoneLiteral(_) => Some(InferredType::NoneType),
916            ast::Expr::EllipsisLiteral(_) => Some(InferredType::Ellipsis),
917            ast::Expr::Slice(_) => Some(InferredType::Slice),
918            _ => None,
919        }
920    }
921
922    fn is_constant_expr(&self, expr: &ast::Expr) -> bool {
923        if self.ast_constant_value(expr).is_some() {
924            return true;
925        }
926        matches!(
927            expr,
928            ast::Expr::StringLiteral(_)
929                | ast::Expr::BytesLiteral(_)
930                | ast::Expr::NumberLiteral(_)
931                | ast::Expr::BooleanLiteral(_)
932                | ast::Expr::NoneLiteral(_)
933                | ast::Expr::EllipsisLiteral(_)
934        )
935    }
936
937    fn is_constant_slice(&self, slice: &ast::Expr) -> bool {
938        match slice {
939            ast::Expr::Slice(s) => {
940                let lower_const = s.lower.is_none()
941                    || s.lower.as_deref().is_some_and(|e| self.is_constant_expr(e));
942                let upper_const = s.upper.is_none()
943                    || s.upper.as_deref().is_some_and(|e| self.is_constant_expr(e));
944                let step_const =
945                    s.step.is_none() || s.step.as_deref().is_some_and(|e| self.is_constant_expr(e));
946                lower_const && upper_const && step_const
947            }
948            _ => false,
949        }
950    }
951
952    fn should_apply_two_element_slice_optimization(&self, slice: &ast::Expr) -> bool {
953        !self.is_constant_slice(slice) && matches!(slice, ast::Expr::Slice(s) if s.step.is_none())
954    }
955
956    fn check_is_arg(&self, expr: &ast::Expr) -> bool {
957        if let Some(constant) = self.ast_constant_value(expr) {
958            return matches!(
959                constant,
960                ConstantData::None | ConstantData::Boolean { .. } | ConstantData::Ellipsis
961            );
962        }
963        if let ast::Expr::Tuple(tuple) = expr {
964            return !tuple.elts.iter().all(|expr| self.is_constant_expr(expr));
965        }
966        if !self.is_constant_expr(expr) {
967            return true;
968        }
969        matches!(
970            expr,
971            ast::Expr::NoneLiteral(_)
972                | ast::Expr::BooleanLiteral(_)
973                | ast::Expr::EllipsisLiteral(_)
974        )
975    }
976
977    fn warn_syntax(&mut self, range: TextRange, message: String) -> CompileResult<()> {
978        if self.disable_warning > 0 {
979            return Ok(());
980        }
981        let Some(handler) = self.syntax_warning_handler.as_deref_mut() else {
982            return Ok(());
983        };
984        let location = self
985            .source_file
986            .to_source_code()
987            .source_location(range.start(), PositionEncoding::Utf8);
988        handler(location, message)
989    }
990
991    fn check_caller(&mut self, func: &ast::Expr) -> CompileResult<()> {
992        let warns = self.ast_constant_value(func).is_some()
993            || matches!(
994                func,
995                ast::Expr::StringLiteral(_)
996                    | ast::Expr::BytesLiteral(_)
997                    | ast::Expr::NumberLiteral(_)
998                    | ast::Expr::BooleanLiteral(_)
999                    | ast::Expr::NoneLiteral(_)
1000                    | ast::Expr::EllipsisLiteral(_)
1001                    | ast::Expr::Tuple(_)
1002                    | ast::Expr::List(_)
1003                    | ast::Expr::ListComp(_)
1004                    | ast::Expr::Dict(_)
1005                    | ast::Expr::DictComp(_)
1006                    | ast::Expr::Set(_)
1007                    | ast::Expr::SetComp(_)
1008                    | ast::Expr::Generator(_)
1009                    | ast::Expr::FString(_)
1010                    | ast::Expr::TString(_)
1011            );
1012        if warns && let Some(inferred) = self.infer_type(func) {
1013            self.warn_syntax(
1014                func.range(),
1015                format!(
1016                    "'{}' object is not callable; perhaps you missed a comma?",
1017                    inferred.name()
1018                ),
1019            )?;
1020        }
1021        Ok(())
1022    }
1023
1024    fn check_compare(
1025        &mut self,
1026        range: TextRange,
1027        left: &ast::Expr,
1028        ops: &[ast::CmpOp],
1029        comparators: &[ast::Expr],
1030    ) -> CompileResult<()> {
1031        let mut left_is_arg = self.check_is_arg(left);
1032        let mut left_expr = left;
1033        for (op, right_expr) in ops.iter().zip(comparators.iter()) {
1034            let right_is_arg = self.check_is_arg(right_expr);
1035            if matches!(op, ast::CmpOp::Is | ast::CmpOp::IsNot) && (!right_is_arg || !left_is_arg) {
1036                let literal = if !left_is_arg { left_expr } else { right_expr };
1037                if let Some(inferred) = self.infer_type(literal) {
1038                    let is_op = matches!(op, ast::CmpOp::Is);
1039                    let op = if is_op { "\"is\"" } else { "\"is not\"" };
1040                    let replacement = if is_op { "==" } else { "!=" };
1041                    self.warn_syntax(
1042                        range,
1043                        format!(
1044                            "{op} with '{}' literal. Did you mean \"{replacement}\"?",
1045                            inferred.name()
1046                        ),
1047                    )?;
1048                    return Ok(());
1049                }
1050            }
1051            left_is_arg = right_is_arg;
1052            left_expr = right_expr;
1053        }
1054        Ok(())
1055    }
1056
1057    fn constant_warns_as_subscripter(constant: &ConstantData) -> bool {
1058        matches!(
1059            constant,
1060            ConstantData::None
1061                | ConstantData::Ellipsis
1062                | ConstantData::Integer { .. }
1063                | ConstantData::Float { .. }
1064                | ConstantData::Complex { .. }
1065                | ConstantData::Boolean { .. }
1066                | ConstantData::Frozenset { .. }
1067        )
1068    }
1069
1070    fn check_subscripter(&mut self, value: &ast::Expr) -> CompileResult<()> {
1071        let warns = self
1072            .ast_constant_value(value)
1073            .is_some_and(|constant| Self::constant_warns_as_subscripter(&constant))
1074            || matches!(
1075                value,
1076                ast::Expr::NoneLiteral(_)
1077                    | ast::Expr::EllipsisLiteral(_)
1078                    | ast::Expr::NumberLiteral(_)
1079                    | ast::Expr::BooleanLiteral(_)
1080                    | ast::Expr::Set(_)
1081                    | ast::Expr::SetComp(_)
1082                    | ast::Expr::Generator(_)
1083                    | ast::Expr::TString(_)
1084                    | ast::Expr::Lambda(_)
1085            );
1086        if warns && let Some(inferred) = self.infer_type(value) {
1087            self.warn_syntax(
1088                value.range(),
1089                format!(
1090                    "'{}' object is not subscriptable; perhaps you missed a comma?",
1091                    inferred.name()
1092                ),
1093            )?;
1094        }
1095        Ok(())
1096    }
1097
1098    fn check_index(&mut self, value: &ast::Expr, slice: &ast::Expr) -> CompileResult<()> {
1099        let Some(index_type) = self.infer_type(slice) else {
1100            return Ok(());
1101        };
1102        if index_type.is_long_subclass() || index_type == InferredType::Slice {
1103            return Ok(());
1104        }
1105
1106        let constant_warns = self.ast_constant_value(value).is_some_and(|constant| {
1107            matches!(
1108                constant,
1109                ConstantData::Str { .. } | ConstantData::Bytes { .. } | ConstantData::Tuple { .. }
1110            )
1111        });
1112        let warns = constant_warns
1113            || matches!(
1114                value,
1115                ast::Expr::StringLiteral(_)
1116                    | ast::Expr::BytesLiteral(_)
1117                    | ast::Expr::Tuple(_)
1118                    | ast::Expr::List(_)
1119                    | ast::Expr::ListComp(_)
1120                    | ast::Expr::FString(_)
1121            );
1122        if warns && let Some(value_type) = self.infer_type(value) {
1123            self.warn_syntax(
1124                value.range(),
1125                format!(
1126                    "{} indices must be integers or slices, not {}; perhaps you missed a comma?",
1127                    value_type.name(),
1128                    index_type.name()
1129                ),
1130            )?;
1131        }
1132        Ok(())
1133    }
1134
1135    fn check_assert(&mut self, assert_stmt: &ast::StmtAssert) -> CompileResult<()> {
1136        let warns = match &*assert_stmt.test {
1137            ast::Expr::Tuple(tuple) => !tuple.elts.is_empty(),
1138            _ => matches!(
1139                self.ast_constant_value(&assert_stmt.test),
1140                Some(ConstantData::Tuple { ref elements }) if !elements.is_empty()
1141            ),
1142        };
1143        if warns {
1144            self.warn_syntax(
1145                assert_stmt.range,
1146                "assertion is always true, perhaps remove parentheses?".to_owned(),
1147            )?;
1148        }
1149        Ok(())
1150    }
1151
1152    fn new_with_syntax_warning_handler(
1153        opts: CompileOpts,
1154        source_file: SourceFile,
1155        code_name: &str,
1156        syntax_warning_handler: Option<&'warnings mut SyntaxWarningHandler<'warnings>>,
1157    ) -> Self {
1158        let module_code = ir::CodeInfo {
1159            // CPython convention: top-level module / interactive /
1160            // expression code does not carry CO_NEWLOCALS or CO_OPTIMIZED.
1161            // (See `Python/compile.c compiler_enter_scope` for module
1162            // scope.)  This matches the per-scope mapping at
1163            // enter_scope::CompilerScope::Module below, which also returns
1164            // empty flags.  frame.rs:725-731 then binds locals to globals
1165            // for module/REPL frames whose `scope.locals` is None - the
1166            // correct semantics for `exec(code, globals)` and module init.
1167            flags: bytecode::CodeFlags::empty(),
1168            source_path: source_file.name().to_owned(),
1169            private: None,
1170            blocks: Blocks::from([Block::default()]),
1171            current_block: BlockIdx::new(0),
1172            instr_sequence: ir::InstructionSequence::new(),
1173            instr_sequence_label_map: ir::InstructionSequenceLabelMap::new(),
1174            annotations_instr_sequence: None,
1175            metadata: ir::CodeUnitMetadata {
1176                name: code_name.to_string(),
1177                qualname: Some(code_name.to_string()),
1178                consts: Default::default(),
1179                names: IndexSet::default(),
1180                varnames: IndexSet::default(),
1181                cellvars: IndexSet::default(),
1182                freevars: IndexSet::default(),
1183                fast_hidden: IndexMap::default(),
1184                fast_hidden_final: IndexSet::default(),
1185                argcount: 0,
1186                posonlyargcount: 0,
1187                kwonlyargcount: 0,
1188                firstlineno: OneIndexed::MIN,
1189            },
1190            static_attributes: None,
1191            in_inlined_comp: false,
1192            fblock: Vec::with_capacity(CO_MAXBLOCKS),
1193            symbol_table_index: 0, // Module is always the first symbol table
1194            nparams: 0,
1195            in_conditional_block: 0,
1196            next_conditional_annotation_index: 0,
1197        };
1198        Self {
1199            code_stack: vec![module_code],
1200            symbol_table_stack: Vec::new(),
1201            source_file,
1202            // current_source_location: SourceLocation::default(),
1203            current_source_range: TextRange::default(),
1204            future_features: opts.future_features,
1205            future_annotations: false,
1206            ctx: CompileContext {
1207                in_class: false,
1208                func: FunctionContext::NoFunction,
1209                in_async_scope: false,
1210            },
1211            opts,
1212            in_annotation: false,
1213            interactive: false,
1214            do_not_emit_bytecode: 0,
1215            disable_warning: 0,
1216            syntax_warning_handler,
1217            save_nested_seqs: false,
1218        }
1219    }
1220
1221    fn compile_module_annotation_setup_sequence(
1222        &mut self,
1223        body: &[ast::Stmt],
1224        loc: TextRange,
1225    ) -> CompileResult<()> {
1226        let (
1227            saved_blocks,
1228            saved_current_block,
1229            saved_instr_sequence,
1230            saved_instr_sequence_label_map,
1231            saved_annotations_instr_sequence,
1232        ) = {
1233            let code = self.current_code_info();
1234
1235            (
1236                mem::replace(&mut code.blocks, Blocks::from([Block::default()])),
1237                mem::replace(&mut code.current_block, BlockIdx::new(0)),
1238                mem::replace(&mut code.instr_sequence, ir::InstructionSequence::new()),
1239                mem::replace(
1240                    &mut code.instr_sequence_label_map,
1241                    ir::InstructionSequenceLabelMap::new(),
1242                ),
1243                code.annotations_instr_sequence.take(),
1244            )
1245        };
1246
1247        let result = self.compile_module_annotate(body, Some(loc));
1248
1249        {
1250            let code = self.current_code_info();
1251            code.blocks = saved_blocks;
1252            let annotations_instr_sequence =
1253                mem::replace(&mut code.instr_sequence, saved_instr_sequence);
1254            code.current_block = saved_current_block;
1255            code.instr_sequence_label_map = saved_instr_sequence_label_map;
1256            debug_assert!(saved_annotations_instr_sequence.is_none());
1257            if matches!(result, Ok(true)) {
1258                code.annotations_instr_sequence = Some(annotations_instr_sequence);
1259            } else {
1260                code.annotations_instr_sequence = saved_annotations_instr_sequence;
1261            }
1262        };
1263
1264        result.map(|_| ())
1265    }
1266
1267    /// Compile just start and stop of a slice (for BINARY_SLICE/STORE_SLICE)
1268    // = codegen_slice_two_parts
1269    fn compile_slice_two_parts(&mut self, s: &ast::ExprSlice) -> CompileResult<()> {
1270        // Compile lower (or None)
1271        if let Some(lower) = &s.lower {
1272            self.compile_expression(lower)?;
1273        } else {
1274            self.set_source_range(s.range);
1275            self.emit_load_const(ConstantData::None);
1276        }
1277
1278        // Compile upper (or None)
1279        if let Some(upper) = &s.upper {
1280            self.compile_expression(upper)?;
1281        } else {
1282            self.set_source_range(s.range);
1283            self.emit_load_const(ConstantData::None);
1284        }
1285
1286        Ok(())
1287    }
1288    /// Compile a subscript expression
1289    // = compiler_subscript
1290    fn compile_subscript(
1291        &mut self,
1292        value: &ast::Expr,
1293        slice: &ast::Expr,
1294        ctx: ast::ExprContext,
1295    ) -> CompileResult<()> {
1296        // Save full subscript expression range (set by compile_expression before this call)
1297        let subscript_range = self.current_source_range;
1298        if matches!(ctx, ast::ExprContext::Load) {
1299            self.check_subscripter(value)?;
1300            self.check_index(value, slice)?;
1301        }
1302
1303        // VISIT(c, expr, e->v.Subscript.value)
1304        self.compile_expression(value)?;
1305
1306        // Handle two-element non-constant slice with BINARY_SLICE/STORE_SLICE
1307        let use_slice_opt = matches!(ctx, ast::ExprContext::Load | ast::ExprContext::Store)
1308            && self.should_apply_two_element_slice_optimization(slice);
1309        if use_slice_opt {
1310            match slice {
1311                ast::Expr::Slice(s) => self.compile_slice_two_parts(s)?,
1312                _ => unreachable!(
1313                    "should_use_slice_optimization should only return true for ast::Expr::Slice"
1314                ),
1315            };
1316        } else {
1317            // VISIT(c, expr, e->v.Subscript.slice)
1318            self.compile_expression(slice)?;
1319        }
1320
1321        // Restore full subscript expression range before emitting
1322        self.set_source_range(subscript_range);
1323
1324        match (use_slice_opt, ctx) {
1325            (true, ast::ExprContext::Load) => emit!(self, Instruction::BinarySlice),
1326            (true, ast::ExprContext::Store) => emit!(self, Instruction::StoreSlice),
1327            (true, _) => unreachable!(),
1328            (false, ast::ExprContext::Load) => emit!(
1329                self,
1330                Instruction::BinaryOp {
1331                    op: BinaryOperator::Subscr
1332                }
1333            ),
1334            (false, ast::ExprContext::Store) => emit!(self, Instruction::StoreSubscr),
1335            (false, ast::ExprContext::Del) => emit!(self, Instruction::DeleteSubscr),
1336            (false, ast::ExprContext::Invalid) => {
1337                return Err(self.error(CodegenErrorType::SyntaxError(
1338                    "Invalid expression context".to_owned(),
1339                )));
1340            }
1341        }
1342
1343        Ok(())
1344    }
1345
1346    /// Helper function for compiling tuples/lists/sets with starred expressions
1347    ///
1348    /// ast::Parameters:
1349    /// - elts: The elements to compile
1350    /// - pushed: Number of items already on the stack
1351    /// - collection_type: What type of collection to build (tuple, list, set)
1352    ///
1353    // = starunpack_helper in compile.c
1354    fn starunpack_helper_impl(
1355        &mut self,
1356        elts: &[ast::Expr],
1357        injected_arg: Option<&Name>,
1358        pushed: u32,
1359        collection_type: CollectionType,
1360    ) -> CompileResult<()> {
1361        let collection_range = self.current_source_range;
1362        let n = elts.len().to_u32();
1363        let seen_star = elts.iter().any(|e| matches!(e, ast::Expr::Starred(_)));
1364
1365        let injected_count = u32::from(injected_arg.is_some());
1366        let big = n + pushed + injected_count > STACK_USE_GUIDELINE;
1367
1368        // Constant collections are not folded here: the late flowgraph
1369        // optimization passes introduce tuple-backed constants after their
1370        // operands have first been emitted, matching the constant ordering.
1371
1372        // If no stars and not too big, compile all elements and build once
1373        if !seen_star && !big {
1374            for elt in elts {
1375                self.compile_expression(elt)?;
1376            }
1377            if let Some(injected_arg) = injected_arg {
1378                self.set_source_range(collection_range);
1379                self.load_name(injected_arg)?;
1380            }
1381            let total_size = n + injected_count + pushed;
1382            self.set_source_range(collection_range);
1383            match collection_type {
1384                CollectionType::List => {
1385                    emit!(self, Instruction::BuildList { count: total_size });
1386                }
1387                CollectionType::Set => {
1388                    emit!(self, Instruction::BuildSet { count: total_size });
1389                }
1390                CollectionType::Tuple => {
1391                    emit!(self, Instruction::BuildTuple { count: total_size });
1392                }
1393            }
1394            return Ok(());
1395        }
1396
1397        // Has stars or too big: use streaming approach.
1398        let mut sequence_built = false;
1399        let mut i = 0u32;
1400
1401        if big {
1402            self.set_source_range(collection_range);
1403            match collection_type {
1404                CollectionType::List => {
1405                    emit!(self, Instruction::BuildList { count: pushed });
1406                    sequence_built = true;
1407                }
1408                CollectionType::Set => {
1409                    emit!(self, Instruction::BuildSet { count: pushed });
1410                    sequence_built = true;
1411                }
1412                CollectionType::Tuple => {
1413                    emit!(self, Instruction::BuildList { count: pushed });
1414                    sequence_built = true;
1415                }
1416            }
1417        }
1418
1419        for elt in elts {
1420            if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = elt {
1421                // When we hit first star, build sequence with elements so far
1422                if !sequence_built {
1423                    self.set_source_range(collection_range);
1424                    match collection_type {
1425                        CollectionType::List => {
1426                            emit!(self, Instruction::BuildList { count: i + pushed });
1427                        }
1428                        CollectionType::Set => {
1429                            emit!(self, Instruction::BuildSet { count: i + pushed });
1430                        }
1431                        CollectionType::Tuple => {
1432                            emit!(self, Instruction::BuildList { count: i + pushed });
1433                        }
1434                    }
1435                    sequence_built = true;
1436                }
1437
1438                // Compile the starred expression and extend
1439                self.compile_expression(value)?;
1440                self.set_source_range(collection_range);
1441                match collection_type {
1442                    CollectionType::List => {
1443                        emit!(self, Instruction::ListExtend { i: 1 });
1444                    }
1445                    CollectionType::Set => {
1446                        emit!(self, Instruction::SetUpdate { i: 1 });
1447                    }
1448                    CollectionType::Tuple => {
1449                        emit!(self, Instruction::ListExtend { i: 1 });
1450                    }
1451                }
1452            } else {
1453                // Non-starred element
1454                self.compile_expression(elt)?;
1455
1456                if sequence_built {
1457                    // Sequence already exists, append to it
1458                    self.set_source_range(collection_range);
1459                    match collection_type {
1460                        CollectionType::List => {
1461                            emit!(self, Instruction::ListAppend { i: 1 });
1462                        }
1463                        CollectionType::Set => {
1464                            emit!(self, Instruction::SetAdd { i: 1 });
1465                        }
1466                        CollectionType::Tuple => {
1467                            emit!(self, Instruction::ListAppend { i: 1 });
1468                        }
1469                    }
1470                } else {
1471                    // Still collecting elements before first star
1472                    i += 1;
1473                }
1474            }
1475        }
1476
1477        debug_assert!(sequence_built);
1478        if let Some(injected_arg) = injected_arg {
1479            self.set_source_range(collection_range);
1480            self.load_name(injected_arg)?;
1481            self.set_source_range(collection_range);
1482            match collection_type {
1483                CollectionType::List | CollectionType::Tuple => {
1484                    emit!(self, Instruction::ListAppend { i: 1 });
1485                }
1486                CollectionType::Set => {
1487                    emit!(self, Instruction::SetAdd { i: 1 });
1488                }
1489            }
1490        }
1491
1492        if collection_type == CollectionType::Tuple {
1493            self.set_source_range(collection_range);
1494            emit!(
1495                self,
1496                Instruction::CallIntrinsic1 {
1497                    func: IntrinsicFunction1::ListToTuple
1498                }
1499            );
1500        }
1501
1502        Ok(())
1503    }
1504
1505    fn starunpack_helper(
1506        &mut self,
1507        elts: &[ast::Expr],
1508        pushed: u32,
1509        collection_type: CollectionType,
1510    ) -> CompileResult<()> {
1511        self.starunpack_helper_impl(elts, None, pushed, collection_type)
1512    }
1513
1514    fn error(&mut self, error: CodegenErrorType) -> CodegenError {
1515        self.error_ranged(error, self.current_source_range)
1516    }
1517
1518    fn error_ranged(&mut self, error: CodegenErrorType, range: TextRange) -> CodegenError {
1519        let source_code = self.source_file.to_source_code();
1520        let location = source_code.source_location(range.start(), PositionEncoding::Utf8);
1521        let end_location = source_code.source_location(range.end(), PositionEncoding::Utf8);
1522        CodegenError {
1523            error,
1524            location: Some(location),
1525            end_location: Some(end_location),
1526            source_path: self.source_file.name().to_owned(),
1527        }
1528    }
1529
1530    fn error_optional_range(
1531        &mut self,
1532        error: CodegenErrorType,
1533        range: Option<TextRange>,
1534    ) -> CodegenError {
1535        match range {
1536            Some(range) => self.error_ranged(error, range),
1537            None => CodegenError {
1538                error,
1539                location: None,
1540                end_location: None,
1541                source_path: self.source_file.name().to_owned(),
1542            },
1543        }
1544    }
1545
1546    /// Get the SymbolTable for the current scope.
1547    fn current_symbol_table(&self) -> &SymbolTable {
1548        self.symbol_table_stack
1549            .last()
1550            .expect("symbol_table_stack is empty! This is a compiler bug.")
1551    }
1552
1553    fn has_enclosing_non_module_code_scope(&self) -> bool {
1554        self.code_stack.len() > 1
1555    }
1556
1557    /// Match CPython's `is_import_originated()`: only imports recorded in the
1558    /// module-level symbol table suppress method-call optimization.
1559    fn is_name_imported(&self, name: &str) -> bool {
1560        self.symbol_table_stack
1561            .first()
1562            .and_then(|table| table.symbols.get(name))
1563            .is_some_and(|sym| sym.flags.contains(SymbolFlags::DEF_IMPORT))
1564    }
1565
1566    /// Get the cell-relative index of a free variable.
1567    /// Returns ncells + freevar_idx. Fixed up to localsplus index during finalize.
1568    fn get_free_var_index(&mut self, name: &str) -> oparg::VarNum {
1569        let info = self.code_stack.last_mut().unwrap();
1570        let idx = info
1571            .metadata
1572            .freevars
1573            .get_index_of(name)
1574            .unwrap_or_else(|| info.metadata.freevars.insert_full(name.to_owned()).0);
1575        (idx + info.metadata.cellvars.len()).to_u32().into()
1576    }
1577
1578    /// Get the cell-relative index of a cell variable.
1579    /// Returns cellvar_idx. Fixed up to localsplus index during finalize.
1580    fn get_cell_var_index(&mut self, name: &str) -> oparg::VarNum {
1581        let info = self.code_stack.last_mut().unwrap();
1582        let idx = info
1583            .metadata
1584            .cellvars
1585            .get_index_of(name)
1586            .unwrap_or_else(|| info.metadata.cellvars.insert_full(name.to_owned()).0);
1587        idx.to_u32().into()
1588    }
1589
1590    /// Get the index of a local variable.
1591    fn get_local_var_index(&mut self, name: &str) -> oparg::VarNum {
1592        let info = self.code_stack.last_mut().unwrap();
1593        let idx = info
1594            .metadata
1595            .varnames
1596            .get_index_of(name)
1597            .unwrap_or_else(|| info.metadata.varnames.insert_full(name.to_owned()).0);
1598        idx.to_u32().into()
1599    }
1600
1601    /// Get the index of a global name.
1602    fn get_global_name_index(&mut self, name: &str) -> u32 {
1603        let info = self.code_stack.last_mut().unwrap();
1604        let idx = info
1605            .metadata
1606            .names
1607            .get_index_of(name)
1608            .unwrap_or_else(|| info.metadata.names.insert_full(name.to_owned()).0);
1609        idx.to_u32()
1610    }
1611
1612    /// Push the next symbol table on to the stack
1613    fn push_symbol_table(&mut self) -> CompileResult<&SymbolTable> {
1614        // Look up the next table contained in the scope of the current table
1615        let current_table = self
1616            .symbol_table_stack
1617            .last_mut()
1618            .expect("no current symbol table");
1619
1620        if current_table.next_sub_table >= current_table.sub_tables.len() {
1621            let name = current_table.name.clone();
1622            let typ = current_table.typ;
1623            return Err(self.error(CodegenErrorType::SyntaxError(format!(
1624                "no symbol table available in {name} (type: {typ:?})"
1625            ))));
1626        }
1627
1628        while current_table.next_sub_table < current_table.sub_tables.len()
1629            && current_table.sub_tables[current_table.next_sub_table].typ
1630                == CompilerScope::Annotation
1631        {
1632            current_table.next_sub_table += 1;
1633        }
1634        if current_table.next_sub_table >= current_table.sub_tables.len() {
1635            let name = current_table.name.clone();
1636            let typ = current_table.typ;
1637            return Err(self.error(CodegenErrorType::SyntaxError(format!(
1638                "no symbol table available in {name} (type: {typ:?})"
1639            ))));
1640        }
1641
1642        let idx = current_table.next_sub_table;
1643        current_table.next_sub_table += 1;
1644        let table = current_table.sub_tables[idx].clone();
1645
1646        // Push the next table onto the stack
1647        self.symbol_table_stack.push(table);
1648        Ok(self.current_symbol_table())
1649    }
1650
1651    fn push_symbol_table_matching(
1652        &mut self,
1653        typ: CompilerScope,
1654        table_name: &str,
1655    ) -> CompileResult<&SymbolTable> {
1656        let current_table = self
1657            .symbol_table_stack
1658            .last_mut()
1659            .expect("no current symbol table");
1660
1661        while current_table.next_sub_table < current_table.sub_tables.len()
1662            && current_table.sub_tables[current_table.next_sub_table].typ
1663                == CompilerScope::Annotation
1664        {
1665            current_table.next_sub_table += 1;
1666        }
1667
1668        let start = current_table.next_sub_table;
1669        let Some(idx) = current_table.sub_tables[start..]
1670            .iter()
1671            .position(|table| table.typ == typ && table.name == table_name)
1672            .map(|idx| start + idx)
1673        else {
1674            let name = current_table.name.clone();
1675            let current_typ = current_table.typ;
1676            return Err(self.error(CodegenErrorType::SyntaxError(format!(
1677                "no matching symbol table {table_name} ({typ:?}) available in {name} (type: {current_typ:?})"
1678            ))));
1679        };
1680
1681        let table = current_table.sub_tables[idx].clone();
1682        current_table.next_sub_table = idx + 1;
1683        self.symbol_table_stack.push(table);
1684        Ok(self.current_symbol_table())
1685    }
1686
1687    /// Push the function annotation symbol table.
1688    /// Signature annotation blocks are stored in st_blocks keyed by the
1689    /// arguments AST node. Without future annotations they are also children;
1690    /// with future annotations they are hidden from children and consumed here.
1691    fn push_annotation_symbol_table(&mut self) -> bool {
1692        let Some(annotation_table) = ({
1693            let current_table = self
1694                .symbol_table_stack
1695                .last_mut()
1696                .expect("no current symbol table");
1697
1698            let next_idx = current_table.next_sub_table;
1699            if next_idx < current_table.sub_tables.len()
1700                && current_table.sub_tables[next_idx].typ == CompilerScope::Annotation
1701            {
1702                let next_table = current_table.sub_tables[next_idx].clone();
1703                current_table.next_sub_table += 1;
1704                Some(next_table)
1705            } else if current_table.next_hidden_annotation_block
1706                < current_table.hidden_annotation_blocks.len()
1707            {
1708                let idx = current_table.next_hidden_annotation_block;
1709                current_table.next_hidden_annotation_block += 1;
1710                Some(current_table.hidden_annotation_blocks[idx].clone())
1711            } else {
1712                None
1713            }
1714        }) else {
1715            return false;
1716        };
1717
1718        self.symbol_table_stack.push(annotation_table);
1719        true
1720    }
1721
1722    fn next_function_annotation_symbol_table_uses_annotations(&self) -> bool {
1723        let current_table = self
1724            .symbol_table_stack
1725            .last()
1726            .expect("no current symbol table");
1727        let next_idx = current_table.next_sub_table;
1728        if next_idx < current_table.sub_tables.len()
1729            && current_table.sub_tables[next_idx].typ == CompilerScope::Annotation
1730        {
1731            return current_table.sub_tables[next_idx].annotations_used;
1732        }
1733
1734        let hidden_idx = current_table.next_hidden_annotation_block;
1735        current_table
1736            .hidden_annotation_blocks
1737            .get(hidden_idx)
1738            .is_some_and(|table| table.annotations_used)
1739    }
1740
1741    /// Push the annotation symbol table for module/class level annotations
1742    /// This takes annotation_block from the current symbol table (not sub_tables)
1743    fn push_current_annotation_symbol_table(&mut self) -> bool {
1744        let current_table = self
1745            .symbol_table_stack
1746            .last_mut()
1747            .expect("no current symbol table");
1748
1749        // For modules/classes, annotation_block is directly in the current table
1750        if let Some(annotation_block) = current_table.annotation_block.take() {
1751            self.symbol_table_stack.push(*annotation_block);
1752            true
1753        } else {
1754            false
1755        }
1756    }
1757
1758    /// Pop the annotation symbol table.
1759    fn pop_annotation_symbol_table(&mut self) {
1760        self.symbol_table_stack.pop().expect("compiler bug");
1761    }
1762
1763    /// Pop the current symbol table off the stack
1764    fn pop_symbol_table(&mut self) -> SymbolTable {
1765        self.symbol_table_stack.pop().expect("compiler bug")
1766    }
1767
1768    /// Check if a super() call can be optimized
1769    /// Returns Some(SuperCallType) if optimization is possible, None otherwise
1770    fn can_optimize_super_call<'a>(
1771        &self,
1772        value: &'a ast::Expr,
1773        attr: &str,
1774    ) -> Option<SuperCallType<'a>> {
1775        // 1. value must be a Call expression
1776        let ast::Expr::Call(ast::ExprCall {
1777            func, arguments, ..
1778        }) = value
1779        else {
1780            return None;
1781        };
1782
1783        // 2. func must be Name("super")
1784        let ast::Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else {
1785            return None;
1786        };
1787        if id.as_str() != "super" {
1788            return None;
1789        }
1790
1791        // 3. attr must not be "__class__"
1792        if attr == "__class__" {
1793            return None;
1794        }
1795
1796        // 4. No keyword arguments
1797        if !arguments.keywords.is_empty() {
1798            return None;
1799        }
1800
1801        // 5. "super" must be GlobalImplicit in the current scope.
1802        let table = self.current_symbol_table();
1803        if let Some(symbol) = table.lookup(&"super".into())
1804            && symbol.scope != SymbolScope::GlobalImplicit
1805        {
1806            return None;
1807        }
1808        // Then check the top-level scope and reject any statically
1809        // visible symbol for "super", not just local bindings.
1810        if let Some(top_table) = self.symbol_table_stack.first()
1811            && top_table.lookup(&"super".into()).is_some()
1812        {
1813            return None;
1814        }
1815
1816        // 6. Check argument pattern
1817        let args = &arguments.args;
1818
1819        // No starred expressions allowed
1820        if args.iter().any(|arg| matches!(arg, ast::Expr::Starred(_))) {
1821            return None;
1822        }
1823
1824        match args.len() {
1825            2 => {
1826                // 2-arg: super(class, self)
1827                Some(SuperCallType::TwoArg {
1828                    class_arg: &args[0],
1829                    self_arg: &args[1],
1830                })
1831            }
1832            0 => {
1833                // 0-arg: super() - need __class__ cell and first parameter
1834                // Enclosing function should have at least one positional argument
1835                let info = self.code_stack.last()?;
1836                if info.metadata.argcount == 0 && info.metadata.posonlyargcount == 0 {
1837                    return None;
1838                }
1839
1840                // Check if __class__ is available as a cell/free variable
1841                // The scope must be Free (from enclosing class) or have DEF_FREE_CLASS flag
1842                {
1843                    let symbol = table.lookup(&"__class__".into())?;
1844                    if symbol.scope != SymbolScope::Free
1845                        && !symbol.flags.contains(SymbolFlags::DEF_FREE_CLASS)
1846                    {
1847                        return None;
1848                    }
1849                }
1850
1851                Some(SuperCallType::ZeroArg)
1852            }
1853            _ => None, // 1 or 3+ args - not optimizable
1854        }
1855    }
1856
1857    /// Load arguments for super() optimization onto the stack
1858    /// Stack result: [global_super, class, self]
1859    fn load_args_for_super(
1860        &mut self,
1861        super_type: &SuperCallType<'_>,
1862        super_name_range: TextRange,
1863        super_call_range: TextRange,
1864    ) -> CompileResult<()> {
1865        // 1. Load global super
1866        self.set_source_range(super_name_range);
1867        self.compile_name(&"super".into(), NameUsage::Load)?;
1868
1869        match super_type {
1870            SuperCallType::TwoArg {
1871                class_arg,
1872                self_arg,
1873            } => {
1874                // 2-arg: load provided arguments
1875                self.compile_expression(class_arg)?;
1876                self.compile_expression(self_arg)?;
1877            }
1878            SuperCallType::ZeroArg => {
1879                // 0-arg: load __class__ cell and first parameter
1880                // Load __class__ from cell/free variable
1881                self.set_source_range(super_call_range);
1882                let scope = self
1883                    .get_ref_type(&"__class__".into())
1884                    .map_err(|e| self.error(e))?;
1885                let idx = match scope {
1886                    SymbolScope::Cell => self.get_cell_var_index("__class__"),
1887                    SymbolScope::Free => self.get_free_var_index("__class__"),
1888                    _ => {
1889                        return Err(self.error(CodegenErrorType::SyntaxError(
1890                            "super(): __class__ cell not found".to_owned(),
1891                        )));
1892                    }
1893                };
1894                emit!(self, Instruction::LoadDeref { i: idx });
1895
1896                // Load first parameter (typically 'self').
1897                // Safety: can_optimize_super_call() ensures argcount > 0, and
1898                // parameters are always added to varnames first (see symboltable.rs).
1899                let first_param = {
1900                    let info = self.code_stack.last().unwrap();
1901                    info.metadata.varnames.first().cloned()
1902                };
1903                let first_param = first_param.ok_or_else(|| {
1904                    self.error(CodegenErrorType::SyntaxError(
1905                        "super(): no arguments and no first parameter".to_owned(),
1906                    ))
1907                })?;
1908                self.set_source_range(super_call_range);
1909                self.compile_name(&first_param.into(), NameUsage::Load)?;
1910            }
1911        }
1912        Ok(())
1913    }
1914
1915    /// Check if this is an inlined comprehension context (PEP 709).
1916    /// Generator expressions are never inlined.
1917    fn is_inlined_comprehension_context(
1918        &self,
1919        comprehension_type: ComprehensionType,
1920        comp_table: &SymbolTable,
1921    ) -> bool {
1922        if comprehension_type == ComprehensionType::Generator {
1923            return false;
1924        }
1925        comp_table.comp_inlined
1926    }
1927
1928    /// Enter a new scope
1929    // = compiler_enter_scope
1930    fn enter_scope(
1931        &mut self,
1932        name: &str,
1933        scope_type: CompilerScope,
1934        key: usize, // Symbol table stack index used like CPython's scope key.
1935        lineno: u32,
1936    ) -> CompileResult<()> {
1937        // Allocate a new compiler unit
1938
1939        // In Rust, we'll create the structure directly
1940        let source_path = self.source_file.name().to_owned();
1941
1942        // Lookup symbol table entry using key (_PySymtable_Lookup)
1943        let ste = match self.symbol_table_stack.get(key) {
1944            Some(v) => v,
1945            None => {
1946                return Err(self.error(CodegenErrorType::SyntaxError(
1947                    "unknown symbol table entry".to_owned(),
1948                )));
1949            }
1950        };
1951
1952        // Use varnames from symbol table (already collected in definition order)
1953        let varname_cache: IndexSet<Name> = ste.varnames.iter().cloned().collect();
1954        let nparams = ste.varnames.len();
1955
1956        // Build cellvars using dictbytype (CELL scope or COMP_CELL flag, sorted)
1957        let mut cellvar_cache = IndexSet::default();
1958        let mut cell_names: Vec<_> = ste
1959            .symbols
1960            .iter()
1961            .filter(|(_, s)| {
1962                s.scope == SymbolScope::Cell || s.flags.contains(SymbolFlags::DEF_COMP_CELL)
1963            })
1964            .map(|(name, _)| name.clone())
1965            .collect();
1966        cell_names.sort();
1967        for name in cell_names {
1968            cellvar_cache.insert(name.into());
1969        }
1970
1971        // Handle implicit __class__ cell if needed
1972        if ste.needs_class_closure {
1973            // Cook up an implicit __class__ cell
1974            debug_assert_eq!(scope_type, CompilerScope::Class);
1975            cellvar_cache.insert("__class__".to_string());
1976        }
1977
1978        // Handle implicit __classdict__ cell if needed
1979        if ste.needs_classdict {
1980            // Cook up an implicit __classdict__ cell
1981            debug_assert_eq!(scope_type, CompilerScope::Class);
1982            cellvar_cache.insert("__classdict__".to_string());
1983        }
1984
1985        // Handle implicit __conditional_annotations__ cell if needed.
1986        if Self::scope_needs_conditional_annotations_cell(ste) {
1987            cellvar_cache.insert("__conditional_annotations__".to_string());
1988        }
1989
1990        // Build freevars using dictbytype (FREE scope, offset by cellvars size)
1991        let mut freevar_cache = IndexSet::default();
1992        let annotation_free_names: IndexSet<Name> = ste
1993            .annotation_block
1994            .as_ref()
1995            .map(|annotation| {
1996                annotation
1997                    .symbols
1998                    .iter()
1999                    .filter(|(_, s)| s.scope == SymbolScope::Free)
2000                    .map(|(name, _)| name.clone())
2001                    .collect()
2002            })
2003            .unwrap_or_default();
2004        let mut free_names: Vec<_> = ste
2005            .symbols
2006            .iter()
2007            .filter(|(_, s)| {
2008                s.scope == SymbolScope::Free
2009                    || (scope_type != CompilerScope::Class
2010                        && s.flags.contains(SymbolFlags::DEF_FREE_CLASS))
2011                    || (scope_type == CompilerScope::Class
2012                        && s.flags.contains(SymbolFlags::DEF_FREE_CLASS)
2013                        && self.has_enclosing_non_module_code_scope())
2014            })
2015            .filter(|(name, symbol)| {
2016                if !matches!(
2017                    scope_type,
2018                    CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda
2019                ) {
2020                    return true;
2021                }
2022                !(annotation_free_names.contains(*name) && symbol.flags.is_empty())
2023            })
2024            .map(|(name, _)| name.clone())
2025            .collect();
2026        free_names.sort();
2027        for name in free_names {
2028            freevar_cache.insert(name.into());
2029        }
2030
2031        // The `__conditional_annotations__` cell an annotation scope reads is
2032        // cooked up here rather than carried by the symbol table, so it lands
2033        // after the names the symbol table did supply.
2034        if scope_type == CompilerScope::Annotation && ste.has_conditional_annotations {
2035            freevar_cache.insert("__conditional_annotations__".to_string());
2036        }
2037
2038        // Initialize u_metadata fields
2039        let (mut flags, posonlyarg_count, arg_count, kwonlyarg_count) = match scope_type {
2040            CompilerScope::Module => (bytecode::CodeFlags::empty(), 0, 0, 0),
2041            CompilerScope::Class => (bytecode::CodeFlags::empty(), 0, 0, 0),
2042            CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda => (
2043                bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
2044                0, // Will be set later in enter_function
2045                0, // Will be set later in enter_function
2046                0, // Will be set later in enter_function
2047            ),
2048            CompilerScope::Comprehension => (
2049                bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
2050                0,
2051                1, // comprehensions take one argument (.0)
2052                0,
2053            ),
2054            CompilerScope::TypeParams => (
2055                bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
2056                0,
2057                0,
2058                0,
2059            ),
2060            CompilerScope::Annotation => (
2061                bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
2062                1, // format is positional-only
2063                0,
2064                0,
2065            ),
2066            CompilerScope::TypeAlias | CompilerScope::TypeVariable => (
2067                bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
2068                1, // format is positional-only
2069                0,
2070                0,
2071            ),
2072        };
2073
2074        if ste.is_method {
2075            flags |= bytecode::CodeFlags::METHOD;
2076        }
2077
2078        // CPython sets CO_NESTED from symtable's ste_nested, not merely
2079        // from lexical depth: module-level class methods are CO_METHOD but
2080        // not CO_NESTED.
2081        let mut flags = if ste.is_nested
2082            && matches!(
2083                scope_type,
2084                CompilerScope::Function
2085                    | CompilerScope::AsyncFunction
2086                    | CompilerScope::Lambda
2087                    | CompilerScope::Comprehension
2088                    | CompilerScope::Annotation
2089                    | CompilerScope::TypeAlias
2090                    | CompilerScope::TypeVariable
2091                    | CompilerScope::TypeParams
2092            ) {
2093            flags | bytecode::CodeFlags::NESTED
2094        } else {
2095            flags
2096        };
2097        flags |= self.future_features;
2098
2099        // Get private name from parent scope
2100        let private = if !self.code_stack.is_empty() {
2101            self.code_stack.last().unwrap().private.clone()
2102        } else {
2103            None
2104        };
2105
2106        // Create the new compilation unit
2107        let code_info = ir::CodeInfo {
2108            flags,
2109            source_path,
2110            private,
2111            blocks: Blocks::from([Block::default()]),
2112            current_block: BlockIdx::new(0),
2113            instr_sequence: ir::InstructionSequence::new(),
2114            instr_sequence_label_map: ir::InstructionSequenceLabelMap::new(),
2115            annotations_instr_sequence: None,
2116            metadata: ir::CodeUnitMetadata {
2117                name: name.to_owned(),
2118                qualname: None, // Will be set below
2119                consts: Default::default(),
2120                names: IndexSet::default(),
2121                varnames: varname_cache.into_iter().map(Into::into).collect(),
2122                cellvars: cellvar_cache,
2123                freevars: freevar_cache,
2124                fast_hidden: IndexMap::default(),
2125                fast_hidden_final: IndexSet::default(),
2126                argcount: arg_count,
2127                posonlyargcount: posonlyarg_count,
2128                kwonlyargcount: kwonlyarg_count,
2129                firstlineno: OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN),
2130            },
2131            static_attributes: if scope_type == CompilerScope::Class {
2132                Some(IndexSet::default())
2133            } else {
2134                None
2135            },
2136            in_inlined_comp: false,
2137            fblock: Vec::with_capacity(CO_MAXBLOCKS),
2138            symbol_table_index: key,
2139            nparams,
2140            in_conditional_block: 0,
2141            next_conditional_annotation_index: 0,
2142        };
2143
2144        // Push the old compiler unit on the stack (like PyCapsule)
2145        // This happens before setting qualname
2146        self.code_stack.push(code_info);
2147
2148        // Set qualname after pushing (uses compiler_set_qualname logic)
2149        if scope_type != CompilerScope::Module {
2150            self.set_qualname();
2151        }
2152
2153        // Emit RESUME (handles async preamble and module lineno 0)
2154        // CPython: LOCATION(lineno, lineno, 0, 0), then loc.lineno = 0 for module
2155        self.emit_resume_for_scope(scope_type, lineno);
2156
2157        Ok(())
2158    }
2159
2160    /// Emit RESUME instruction with proper handling for module lineno.
2161    /// codegen_enter_scope equivalent for RESUME emission.
2162    fn emit_resume_for_scope(&mut self, scope_type: CompilerScope, lineno: u32) {
2163        // CPython: LOCATION(lineno, lineno, 0, 0)
2164        // Module scope: loc.lineno = 0 (before the first line)
2165        let lineno_override = if scope_type == CompilerScope::Module {
2166            Some(0)
2167        } else {
2168            None
2169        };
2170
2171        // Use lineno for location (col = 0 as in CPython)
2172        let location = SourceLocation {
2173            line: OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN),
2174            character_offset: OneIndexed::MIN, // col = 0
2175        };
2176        let end_location = location; // end_lineno = lineno, end_col = 0
2177        let except_handler = None;
2178
2179        self.cpython_cfg_builder_addop(ir::InstructionInfo {
2180            instr: Instruction::Resume {
2181                context: OpArgMarker::marker(),
2182            }
2183            .into(),
2184            arg: OpArg::new(oparg::ResumeLocation::AtFuncStart.into()),
2185            target: BlockIdx::NULL,
2186            location,
2187            end_location,
2188            except_handler,
2189            lineno_override,
2190        });
2191    }
2192
2193    fn push_output(
2194        &mut self,
2195        flags: bytecode::CodeFlags,
2196        posonlyarg_count: u32,
2197        arg_count: u32,
2198        kwonlyarg_count: u32,
2199        obj_name: &str,
2200    ) -> CompileResult<()> {
2201        // First push the symbol table
2202        let table = self.push_symbol_table()?;
2203        let scope_type = table.typ;
2204
2205        // The key is the current position in the symbol table stack
2206        let key = self.symbol_table_stack.len() - 1;
2207
2208        // Get the line number
2209        let lineno = self.get_source_line_number().get();
2210
2211        // Call enter_scope which does most of the work
2212        self.enter_scope(obj_name, scope_type, key, lineno.to_u32())?;
2213
2214        // Override the values that push_output sets explicitly
2215        // enter_scope sets default values based on scope_type, but push_output
2216        // allows callers to specify exact values
2217        if let Some(info) = self.code_stack.last_mut() {
2218            // Preserve flags computed from the symbol-table context.
2219            info.flags = flags
2220                | (info.flags
2221                    & (bytecode::CodeFlags::NESTED
2222                        | bytecode::CodeFlags::METHOD
2223                        | bytecode::CodeFlags::FUTURE_DIVISION
2224                        | bytecode::CodeFlags::FUTURE_ABSOLUTE_IMPORT
2225                        | bytecode::CodeFlags::FUTURE_WITH_STATEMENT
2226                        | bytecode::CodeFlags::FUTURE_PRINT_FUNCTION
2227                        | bytecode::CodeFlags::FUTURE_UNICODE_LITERALS
2228                        | bytecode::CodeFlags::FUTURE_BARRY_AS_BDFL
2229                        | bytecode::CodeFlags::FUTURE_GENERATOR_STOP
2230                        | bytecode::CodeFlags::FUTURE_ANNOTATIONS));
2231            info.metadata.argcount = arg_count;
2232            info.metadata.posonlyargcount = posonlyarg_count;
2233            info.metadata.kwonlyargcount = kwonlyarg_count;
2234        }
2235        Ok(())
2236    }
2237
2238    // compiler_exit_scope
2239    fn exit_scope(&mut self) -> CodeObject {
2240        self.pop_symbol_table();
2241        // Various scopes can have sub_tables:
2242        // - ast::TypeParams scope can have sub_tables (the function body's symbol table)
2243        // - Module scope can have sub_tables (for TypeAlias scopes, nested functions, classes)
2244        // - Function scope can have sub_tables (for nested functions, classes)
2245        // - Class scope can have sub_tables (for nested classes, methods)
2246
2247        let pop = self.code_stack.pop();
2248        let stack_top = compiler_unwrap_option(self, pop);
2249        let nested = self
2250            .save_nested_seqs
2251            .then(|| stack_top.instr_sequence.clone());
2252        let code = unwrap_internal(self, stack_top.finalize_code(&self.opts));
2253        if let (Some(nested), Some(parent)) = (nested, self.code_stack.last_mut()) {
2254            parent.instr_sequence.add_nested(nested);
2255        }
2256        code
2257    }
2258
2259    fn expose_annotation_format_parameter(code: &mut CodeObject) {
2260        if let Some(first) = code.varnames.first_mut() {
2261            *first = String::from("format");
2262        }
2263    }
2264
2265    fn configure_annotation_format_parameter(&mut self) {
2266        let info = self.current_code_info();
2267        info.metadata.varnames.insert(".format".to_owned());
2268        info.nparams = 1;
2269    }
2270
2271    /// Exit a function signature annotation scope.
2272    fn exit_annotation_scope(&mut self, saved_ctx: CompileContext) -> CodeObject {
2273        self.pop_annotation_symbol_table();
2274        self.ctx = saved_ctx;
2275        let pop = self.code_stack.pop();
2276        let stack_top = compiler_unwrap_option(self, pop);
2277        let nested = self
2278            .save_nested_seqs
2279            .then(|| stack_top.instr_sequence.clone());
2280        let mut code = unwrap_internal(self, stack_top.finalize_code(&self.opts));
2281        if let (Some(nested), Some(parent)) = (nested, self.code_stack.last_mut()) {
2282            parent.instr_sequence.add_nested(nested);
2283        }
2284        Self::expose_annotation_format_parameter(&mut code);
2285        code
2286    }
2287
2288    /// Enter a function signature annotation scope.
2289    /// Returns None if no matching annotation symbol table exists.
2290    /// On success, returns the saved CompileContext to pass to exit_annotation_scope.
2291    fn enter_annotation_scope(
2292        &mut self,
2293        func_name: &str,
2294        loc: TextRange,
2295    ) -> CompileResult<Option<CompileContext>> {
2296        if !self.push_annotation_symbol_table() {
2297            return Ok(None);
2298        }
2299
2300        // Annotation scopes are never async (even inside async functions)
2301        let saved_ctx = self.ctx;
2302        self.ctx = CompileContext {
2303            in_class: saved_ctx.in_class,
2304            func: FunctionContext::Function,
2305            in_async_scope: false,
2306        };
2307
2308        self.set_source_range(loc);
2309        let key = self.symbol_table_stack.len() - 1;
2310        let lineno = self.get_source_line_number().get();
2311        self.enter_scope(
2312            "__annotate__",
2313            CompilerScope::Annotation,
2314            key,
2315            lineno.to_u32(),
2316        )?;
2317
2318        // enter_scope() qualified the scope by the enclosing scope only; redo it
2319        // now that the annotated function is known. Only signature annotations
2320        // get this treatment - deferred class and module annotations are
2321        // compiled inside the scope they belong to and are already qualified.
2322        self.set_annotation_qualname(func_name);
2323
2324        // Keep the internal ".format" name; exit_annotation_scope()
2325        // renames it to "format" on the final code object.
2326        self.configure_annotation_format_parameter();
2327
2328        // Emit format validation: if format > VALUE_WITH_FAKE_GLOBALS: raise NotImplementedError
2329        // VALUE_WITH_FAKE_GLOBALS = 2 (from annotationlib.Format)
2330        self.emit_format_validation();
2331
2332        Ok(Some(saved_ctx))
2333    }
2334
2335    /// Emit format parameter validation for annotation scope
2336    /// if format > VALUE_WITH_FAKE_GLOBALS (2): raise NotImplementedError
2337    fn emit_format_validation(&mut self) {
2338        // Load format parameter (first local variable, index 0)
2339        emit!(
2340            self,
2341            Instruction::LoadFast {
2342                var_num: oparg::VarNum::from_u32(0)
2343            }
2344        );
2345
2346        // Load VALUE_WITH_FAKE_GLOBALS constant (2)
2347        self.emit_load_const(ConstantData::Integer { value: 2.into() });
2348
2349        // Compare: format > 2
2350        emit!(
2351            self,
2352            Instruction::CompareOp {
2353                opname: ComparisonOperator::Greater
2354            }
2355        );
2356
2357        // Jump to body if format <= 2 (comparison is false)
2358        let body_block = self.new_block();
2359        emit!(self, Instruction::PopJumpIfFalse { delta: body_block });
2360
2361        // Raise NotImplementedError
2362        emit!(
2363            self,
2364            Instruction::LoadCommonConstant {
2365                idx: bytecode::CommonConstant::NotImplementedError
2366            }
2367        );
2368        emit!(
2369            self,
2370            Instruction::RaiseVarargs {
2371                argc: bytecode::RaiseKind::Raise
2372            }
2373        );
2374
2375        // Body label - continue with annotation evaluation
2376        self.use_cpython_label_block(body_block);
2377    }
2378
2379    /// CPython `_PyCompile_PushFBlock()`: store the active label targets on the
2380    /// fblock stack.
2381    fn push_fblock_labels(
2382        &mut self,
2383        fb_type: FBlockType,
2384        fb_block: ir::InstructionSequenceLabel,
2385        fb_exit: ir::InstructionSequenceLabel,
2386        fb_datum: FBlockDatum,
2387    ) -> CompileResult<()> {
2388        let fb_range = self.current_source_range;
2389        if self.current_code_info().fblock.len() >= CO_MAXBLOCKS {
2390            return Err(self.error(CodegenErrorType::SyntaxError(
2391                "too many statically nested blocks".to_owned(),
2392            )));
2393        }
2394        if matches!(fb_type, FBlockType::FinallyEnd) {
2395            self.disable_warning += 1;
2396        }
2397        let code = self.current_code_info();
2398        code.fblock.push(FBlockInfo {
2399            fb_type,
2400            fb_block,
2401            fb_exit,
2402            fb_range,
2403            fb_datum,
2404        });
2405        Ok(())
2406    }
2407
2408    /// CPython `_PyCompile_PopFBlock()`: assert the popped type and label.
2409    fn pop_fblock_label(
2410        &mut self,
2411        expected_type: FBlockType,
2412        expected_block: ir::InstructionSequenceLabel,
2413    ) -> FBlockInfo {
2414        let fblock = {
2415            let code = self.current_code_info();
2416            code.fblock.pop().expect("fblock stack underflow")
2417        };
2418        debug_assert_eq!(fblock.fb_type, expected_type);
2419        debug_assert_eq!(
2420            fblock.fb_block, expected_block,
2421            "CPython _PyCompile_PopFBlock asserts the popped fb_block label"
2422        );
2423        if matches!(expected_type, FBlockType::FinallyEnd) {
2424            self.disable_warning -= 1;
2425        }
2426        fblock
2427    }
2428
2429    /// `_PyCompile_PushFBlock()` call used by
2430    /// `codegen_unwind_fblock_stack()` to restore the copied fblock after
2431    /// recursive unwinding.
2432    fn restore_fblock_info(&mut self, fblock: FBlockInfo) -> CompileResult<()> {
2433        let FBlockInfo {
2434            fb_type,
2435            fb_block,
2436            fb_exit,
2437            fb_range,
2438            fb_datum,
2439        } = fblock;
2440        let code = self.current_code_info();
2441        if code.fblock.len() >= CO_MAXBLOCKS {
2442            return Err(self.error_ranged(
2443                CodegenErrorType::SyntaxError("too many statically nested blocks".to_owned()),
2444                fb_range,
2445            ));
2446        }
2447        code.fblock.push(FBlockInfo {
2448            fb_type,
2449            fb_block,
2450            fb_exit,
2451            fb_range,
2452            fb_datum,
2453        });
2454        Ok(())
2455    }
2456
2457    fn set_unwind_source_range(&mut self, loc: Option<TextRange>) {
2458        if let Some(range) = loc {
2459            self.set_source_range(range);
2460        }
2461    }
2462
2463    fn mark_unwind_no_location(&mut self, loc: Option<TextRange>) {
2464        if loc.is_none() {
2465            self.set_no_location();
2466        }
2467    }
2468
2469    /// Unwind a single fblock, emitting cleanup code
2470    /// preserve_tos: if true, preserve the top of stack (e.g., return value)
2471    fn unwind_fblock(
2472        &mut self,
2473        info: &FBlockInfo,
2474        preserve_tos: bool,
2475        loc: &mut Option<TextRange>,
2476    ) -> CompileResult<()> {
2477        match info.fb_type {
2478            FBlockType::WhileLoop
2479            | FBlockType::ExceptionHandler
2480            | FBlockType::ExceptionGroupHandler
2481            | FBlockType::AsyncComprehensionGenerator
2482            | FBlockType::StopIteration => {
2483                // No cleanup needed
2484            }
2485
2486            FBlockType::ForLoop => {
2487                // When returning from a for-loop, CPython swaps the preserved
2488                // value with the iterator and uses POP_TOP for loop cleanup.
2489                if preserve_tos {
2490                    self.set_unwind_source_range(*loc);
2491                    emit!(self, Instruction::Swap { i: 2 });
2492                    self.mark_unwind_no_location(*loc);
2493                }
2494                self.set_unwind_source_range(*loc);
2495                emit!(self, Instruction::PopTop);
2496                self.mark_unwind_no_location(*loc);
2497            }
2498
2499            FBlockType::TryExcept => {
2500                self.set_unwind_source_range(*loc);
2501                emit!(self, PseudoInstruction::PopBlock);
2502                self.mark_unwind_no_location(*loc);
2503            }
2504
2505            FBlockType::FinallyTry => {
2506                // codegen_unwind_fblock(FINALLY_TRY)
2507                self.set_unwind_source_range(*loc);
2508                emit!(self, PseudoInstruction::PopBlock);
2509                self.mark_unwind_no_location(*loc);
2510
2511                if preserve_tos {
2512                    self.push_fblock_labels(
2513                        FBlockType::PopValue,
2514                        ir::InstructionSequenceLabel::NO_LABEL,
2515                        ir::InstructionSequenceLabel::NO_LABEL,
2516                        FBlockDatum::None,
2517                    )?;
2518                }
2519
2520                if let FBlockDatum::FinallyBody(ref body) = info.fb_datum {
2521                    // This is an extra copy of the finally body, emitted for the
2522                    // path that leaves the try block early. Nested scopes are
2523                    // handed out by position, and the cursors are still inside
2524                    // the try block's own run of scopes here, so seek them to the
2525                    // ones this body opened before compiling it. The try
2526                    // statement emits its own copies from the same place
2527                    // afterwards, so put the cursors back when the copy is done.
2528                    let symbol_table_cursors = self.current_symbol_table_cursors();
2529                    if let Some(first) = body.first() {
2530                        let line = self
2531                            .source_file
2532                            .to_source_code()
2533                            .line_index(first.range().start())
2534                            .get()
2535                            .to_u32();
2536                        self.seek_symbol_table_cursors_to_line(line);
2537                    }
2538                    self.compile_statements(body)?;
2539                    self.set_symbol_table_cursors(symbol_table_cursors);
2540                }
2541
2542                if preserve_tos {
2543                    self.pop_fblock_label(
2544                        FBlockType::PopValue,
2545                        ir::InstructionSequenceLabel::NO_LABEL,
2546                    );
2547                }
2548
2549                *loc = None;
2550            }
2551
2552            FBlockType::FinallyEnd => {
2553                // codegen_unwind_fblock(FINALLY_END)
2554                if preserve_tos {
2555                    self.set_unwind_source_range(*loc);
2556                    emit!(self, Instruction::Swap { i: 2 });
2557                    self.mark_unwind_no_location(*loc);
2558                }
2559                self.set_unwind_source_range(*loc);
2560                emit!(self, Instruction::PopTop); // exc_value
2561                self.mark_unwind_no_location(*loc);
2562                if preserve_tos {
2563                    self.set_unwind_source_range(*loc);
2564                    emit!(self, Instruction::Swap { i: 2 });
2565                    self.mark_unwind_no_location(*loc);
2566                }
2567                self.set_unwind_source_range(*loc);
2568                emit!(self, PseudoInstruction::PopBlock);
2569                self.mark_unwind_no_location(*loc);
2570                self.set_unwind_source_range(*loc);
2571                emit!(self, Instruction::PopExcept);
2572                self.mark_unwind_no_location(*loc);
2573            }
2574
2575            FBlockType::With | FBlockType::AsyncWith => {
2576                // Stack: [..., exit_func, self_exit, return_value (if preserve_tos)]
2577                // CPython codegen_unwind_fblock() assigns *ploc = info->fb_loc
2578                // for WITH/ASYNC_WITH cleanup and then makes following unwind
2579                // instructions artificial with *ploc = NO_LOCATION.
2580                *loc = Some(info.fb_range);
2581                self.set_unwind_source_range(*loc);
2582                emit!(self, PseudoInstruction::PopBlock);
2583
2584                if preserve_tos {
2585                    // Rotate return value below the exit pair
2586                    // [exit_func, self_exit, value] → [value, exit_func, self_exit]
2587                    self.set_unwind_source_range(*loc);
2588                    emit!(self, Instruction::Swap { i: 3 }); // [value, self_exit, exit_func]
2589                    self.set_unwind_source_range(*loc);
2590                    emit!(self, Instruction::Swap { i: 2 }); // [value, exit_func, self_exit]
2591                }
2592
2593                self.set_unwind_source_range(*loc);
2594                self.compile_call_exit_with_nones();
2595
2596                // For async with, await the result
2597                if matches!(info.fb_type, FBlockType::AsyncWith) {
2598                    self.set_unwind_source_range(*loc);
2599                    emit!(self, Instruction::GetAwaitable { r#where: 2 });
2600                    self.set_unwind_source_range(*loc);
2601                    self.emit_load_const(ConstantData::None);
2602                    let _ = self.compile_yield_from_sequence(true);
2603                }
2604
2605                // Pop the __exit__ result
2606                self.set_unwind_source_range(*loc);
2607                emit!(self, Instruction::PopTop);
2608                *loc = None;
2609            }
2610
2611            FBlockType::HandlerCleanup => {
2612                // codegen_unwind_fblock(HANDLER_CLEANUP)
2613                if let FBlockDatum::ExceptionName(_) = info.fb_datum {
2614                    // Named handler: PopBlock for inner SETUP_CLEANUP
2615                    self.set_unwind_source_range(*loc);
2616                    emit!(self, PseudoInstruction::PopBlock);
2617                    self.mark_unwind_no_location(*loc);
2618                }
2619                if preserve_tos {
2620                    self.set_unwind_source_range(*loc);
2621                    emit!(self, Instruction::Swap { i: 2 });
2622                    self.mark_unwind_no_location(*loc);
2623                }
2624                // PopBlock for outer SETUP_CLEANUP (ExceptionHandler)
2625                self.set_unwind_source_range(*loc);
2626                emit!(self, PseudoInstruction::PopBlock);
2627                self.mark_unwind_no_location(*loc);
2628                self.set_unwind_source_range(*loc);
2629                emit!(self, Instruction::PopExcept);
2630                self.mark_unwind_no_location(*loc);
2631
2632                // If there's an exception name, clean it up
2633                if let FBlockDatum::ExceptionName(ref name) = info.fb_datum {
2634                    self.set_unwind_source_range(*loc);
2635                    self.emit_load_const(ConstantData::None);
2636                    self.mark_unwind_no_location(*loc);
2637                    self.set_unwind_source_range(*loc);
2638                    self.store_name(&name.into())?;
2639                    self.mark_unwind_no_location(*loc);
2640                    self.set_unwind_source_range(*loc);
2641                    self.compile_name(&name.into(), NameUsage::Delete)?;
2642                    self.mark_unwind_no_location(*loc);
2643                }
2644            }
2645
2646            FBlockType::PopValue => {
2647                if preserve_tos {
2648                    self.set_unwind_source_range(*loc);
2649                    emit!(self, Instruction::Swap { i: 2 });
2650                    self.mark_unwind_no_location(*loc);
2651                }
2652                self.set_unwind_source_range(*loc);
2653                emit!(self, Instruction::PopTop);
2654                self.mark_unwind_no_location(*loc);
2655            }
2656        }
2657        Ok(())
2658    }
2659
2660    /// Unwind the fblock stack, emitting cleanup code for each block
2661    /// preserve_tos: if true, preserve the top of stack (e.g., return value)
2662    /// stop_at_loop: if true, stop when encountering a loop (for break/continue)
2663    fn unwind_fblock_stack(
2664        &mut self,
2665        preserve_tos: bool,
2666        stop_at_loop: bool,
2667    ) -> CompileResult<Option<TextRange>> {
2668        let (unwind_loc, _loop_fblock) =
2669            self.unwind_fblock_stack_with_loop(preserve_tos, stop_at_loop)?;
2670        Ok(unwind_loc)
2671    }
2672
2673    /// CPython `codegen_unwind_fblock_stack()`: unwind frame blocks and, when
2674    /// requested by break/continue codegen, return the first loop fblock instead
2675    /// of unwinding it.
2676    fn unwind_fblock_stack_with_loop(
2677        &mut self,
2678        preserve_tos: bool,
2679        stop_at_loop: bool,
2680    ) -> CompileResult<(Option<TextRange>, Option<FBlockInfo>)> {
2681        let mut unwind_loc = Some(self.current_source_range);
2682        let loop_fblock =
2683            self.unwind_fblock_stack_inner(preserve_tos, stop_at_loop, &mut unwind_loc)?;
2684        Ok((unwind_loc, loop_fblock))
2685    }
2686
2687    fn unwind_fblock_stack_inner(
2688        &mut self,
2689        preserve_tos: bool,
2690        stop_at_loop: bool,
2691        unwind_loc: &mut Option<TextRange>,
2692    ) -> CompileResult<Option<FBlockInfo>> {
2693        let Some(top) = self.current_code_info().fblock.last().cloned() else {
2694            return Ok(None);
2695        };
2696        if matches!(top.fb_type, FBlockType::ExceptionGroupHandler) {
2697            return Err(self.error_optional_range(
2698                CodegenErrorType::BreakContinueReturnInExceptStar,
2699                *unwind_loc,
2700            ));
2701        }
2702        if stop_at_loop && matches!(top.fb_type, FBlockType::WhileLoop | FBlockType::ForLoop) {
2703            return Ok(Some(top));
2704        }
2705
2706        let copy = self
2707            .current_code_info()
2708            .fblock
2709            .pop()
2710            .expect("fblock stack underflow");
2711        self.unwind_fblock(&copy, preserve_tos, unwind_loc)?;
2712        let loop_fblock = self.unwind_fblock_stack_inner(preserve_tos, stop_at_loop, unwind_loc)?;
2713        self.restore_fblock_info(copy)?;
2714        Ok(loop_fblock)
2715    }
2716
2717    // could take impl Into<Cow<str>>, but everything is borrowed from ast structs; we never
2718    // actually have a `String` to pass
2719    fn name(&mut self, name: &str) -> bytecode::NameIdx {
2720        self._name_inner(name, |i| &mut i.metadata.names)
2721    }
2722
2723    fn varname(&mut self, name: &str) -> oparg::VarNum {
2724        // NOTE: __debug__ checks are now handled in symboltable phase
2725        oparg::VarNum::from_u32(self._name_inner(name, |i| &mut i.metadata.varnames))
2726    }
2727
2728    fn _name_inner(
2729        &mut self,
2730        name: &str,
2731        cache: impl FnOnce(&mut ir::CodeInfo) -> &mut IndexSet<String>,
2732    ) -> u32 {
2733        let target = name.into();
2734        let name = self.mangle(&target);
2735        let cache = cache(self.current_code_info());
2736        cache
2737            .get_index_of(name.as_str())
2738            .unwrap_or_else(|| cache.insert_full(name.to_string()).0)
2739            .to_u32()
2740    }
2741
2742    /// Set the qualified name for the current code object
2743    // = compiler_set_qualname
2744    fn set_qualname(&mut self) -> String {
2745        self.set_qualname_for_function(None)
2746    }
2747
2748    /// Set the qualname of an annotation scope, qualified by the function whose
2749    /// signature it annotates. The annotation block's symbol table entry records
2750    /// that name (`ste_function_name`) and folds it into the qualname, so `f`'s
2751    /// annotation scope is named `f.__annotate__`.
2752    fn set_annotation_qualname(&mut self, function_name: &str) {
2753        self.set_qualname_for_function(Some(function_name));
2754    }
2755
2756    fn set_qualname_for_function(&mut self, function_name: Option<&str>) -> String {
2757        let qualname = self.make_qualname(function_name);
2758        self.current_code_info().metadata.qualname = Some(qualname.clone());
2759        qualname
2760    }
2761
2762    fn make_qualname(&mut self, function_name: Option<&str>) -> String {
2763        let stack_size = self.code_stack.len();
2764        assert!(stack_size >= 1);
2765
2766        let current_obj_name = self.current_code_info().metadata.name.clone();
2767
2768        // If we're at the module level (stack_size == 1), qualname is just the name
2769        if stack_size <= 1 {
2770            return current_obj_name;
2771        }
2772
2773        // Check parent scope
2774        let mut parent_idx = stack_size - 2;
2775        let mut parent = &self.code_stack[parent_idx];
2776
2777        let mut parent_scope = self
2778            .symbol_table_stack
2779            .get(parent_idx)
2780            .map(|table| table.typ);
2781
2782        // CPython skips both generic-parameter scopes and annotation scopes
2783        // when building qualnames for the contained function/class code object.
2784        if matches!(
2785            parent_scope,
2786            Some(
2787                CompilerScope::TypeParams
2788                    | CompilerScope::Annotation
2789                    | CompilerScope::TypeAlias
2790                    | CompilerScope::TypeVariable,
2791            )
2792        ) || parent.metadata.name.starts_with("<generic parameters of ")
2793        {
2794            if stack_size == 2 {
2795                // If we're immediately within the module, qualname is just the name
2796                return current_obj_name;
2797            }
2798            // Use grandparent
2799            parent_idx = stack_size - 3;
2800            parent = &self.code_stack[parent_idx];
2801            parent_scope = self
2802                .symbol_table_stack
2803                .get(parent_idx)
2804                .map(|table| table.typ);
2805        }
2806
2807        // Check if this is a global class/function.
2808        // CPython compiler_set_qualname() only applies this GLOBAL_EXPLICIT
2809        // shortcut to function, async-function, and class scopes. Annotation
2810        // scopes, including type-alias value scopes, still inherit the parent
2811        // function's .<locals> qualname.
2812        let mut force_global = false;
2813        let current_scope = self
2814            .code_stack
2815            .last()
2816            .map(|code| self.symbol_table_stack[code.symbol_table_index].typ);
2817        if matches!(
2818            current_scope,
2819            Some(CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Class)
2820        ) {
2821            if stack_size > self.symbol_table_stack.len() {
2822                // We might be in a situation where symbol table isn't pushed yet
2823                // In this case, check the parent symbol table
2824                if let Some(parent_table) = self.symbol_table_stack.last()
2825                    && let Some(symbol) = parent_table.lookup(&current_obj_name.clone().into())
2826                    && symbol.scope == SymbolScope::GlobalExplicit
2827                {
2828                    force_global = true;
2829                }
2830            } else if let Some(_current_table) = self.symbol_table_stack.last() {
2831                // Mangle the name if necessary (for private names in classes)
2832                let target = &current_obj_name.clone().into();
2833                let mangled_name = self.mangle(target);
2834
2835                // Look up in parent symbol table to check scope
2836                if self.symbol_table_stack.len() >= 2 {
2837                    let parent_table = &self.symbol_table_stack[self.symbol_table_stack.len() - 2];
2838                    if let Some(symbol) = parent_table.lookup(&mangled_name)
2839                        && symbol.scope == SymbolScope::GlobalExplicit
2840                    {
2841                        force_global = true;
2842                    }
2843                }
2844            }
2845        }
2846
2847        // Build the prefix the current name is qualified by, if any
2848        let base = if force_global {
2849            // For global symbols, qualname is just the name
2850            None
2851        } else {
2852            // Check parent scope type
2853            let parent_obj_name = &parent.metadata.name;
2854
2855            // Determine if parent is a function-like scope
2856            let is_function_parent = matches!(
2857                parent_scope,
2858                Some(
2859                    CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda
2860                )
2861            );
2862
2863            // Use parent's qualname if available, otherwise use parent_obj_name
2864            let parent_qualname = parent.metadata.qualname.as_ref().unwrap_or(parent_obj_name);
2865
2866            if is_function_parent {
2867                // For functions, append .<locals> to parent qualname
2868                Some(format!("{parent_qualname}.<locals>"))
2869            } else if parent_qualname == "<module>" {
2870                // Module level, nothing to qualify by
2871                None
2872            } else {
2873                // For classes and other scopes, use parent's qualname directly
2874                Some(parent_qualname.clone())
2875            }
2876        };
2877
2878        // An annotation scope is compiled in the scope enclosing the function it
2879        // annotates, so the function itself is missing from the prefix above.
2880        let base = match (base, function_name) {
2881            (Some(base), Some(function_name)) => Some(format!("{base}.{function_name}")),
2882            (None, Some(function_name)) => Some(function_name.to_owned()),
2883            (base, None) => base,
2884        };
2885
2886        match base {
2887            Some(base) => format!("{base}.{current_obj_name}"),
2888            None => current_obj_name,
2889        }
2890    }
2891
2892    fn compile_program(
2893        &mut self,
2894        body: &ast::ModModule,
2895        symbol_table: SymbolTable,
2896    ) -> CompileResult<()> {
2897        let size_before = self.code_stack.len();
2898        // Set future_annotations from symbol table (detected during symbol table scan)
2899        self.future_annotations = symbol_table.future_annotations;
2900        let future_features = self.future_features;
2901        self.current_code_info().flags |= future_features;
2902        if self.future_annotations {
2903            self.future_features
2904                .insert(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
2905            self.current_code_info()
2906                .flags
2907                .insert(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
2908        }
2909        if symbol_table.is_coroutine {
2910            self.current_code_info()
2911                .flags
2912                .insert(bytecode::CodeFlags::COROUTINE);
2913        }
2914
2915        // Module-level __conditional_annotations__ cell
2916        let has_module_cond_ann = Self::scope_needs_conditional_annotations_cell(&symbol_table);
2917        if has_module_cond_ann {
2918            self.current_code_info()
2919                .metadata
2920                .cellvars
2921                .insert("__conditional_annotations__".to_string());
2922        }
2923
2924        self.symbol_table_stack.push(symbol_table);
2925
2926        self.emit_resume_for_scope(CompilerScope::Module, 1);
2927        emit!(self, PseudoInstruction::AnnotationsPlaceholder);
2928
2929        let (doc, statements) = split_doc_with_range(&body.body, &self.opts);
2930        let module_start_loc = self.module_start_location(&body.body);
2931        let annotations_used = self.current_symbol_table().annotations_used;
2932        // Handle annotation bookkeeping before the docstring assignment, as
2933        // codegen_body() does after _PyCodegen_Module() inserts the prefix set.
2934        if Self::scope_needs_conditional_annotations_cell(self.current_symbol_table()) {
2935            self.set_source_range(module_start_loc);
2936            emit!(self, Instruction::BuildSet { count: 0 });
2937            self.store_name(&"__conditional_annotations__".into())?;
2938        }
2939
2940        if self.future_annotations && annotations_used {
2941            self.set_source_range(module_start_loc);
2942            emit!(self, Instruction::SetupAnnotations);
2943        }
2944
2945        if let Some((value, range)) = doc {
2946            let saved_range = self.current_source_range;
2947            self.set_source_range(range);
2948            self.emit_load_const(ConstantData::Str {
2949                value: value.into(),
2950            });
2951            let doc = self.name("__doc__");
2952            emit!(self, Instruction::StoreName { namei: doc });
2953            self.set_no_location();
2954            self.set_source_range(saved_range);
2955        }
2956
2957        // Compile all statements
2958        self.compile_statements(statements)?;
2959
2960        if annotations_used && !self.future_annotations {
2961            self.compile_module_annotation_setup_sequence(statements, module_start_loc)?;
2962        }
2963
2964        assert_eq!(self.code_stack.len(), size_before);
2965
2966        // Match _PyCodegen_AddReturnAtEnd(): implicit scope epilogues start
2967        // without a source location and receive one later via CFG line
2968        // propagation.
2969        self.emit_return_const_no_location(ConstantData::None);
2970        Ok(())
2971    }
2972
2973    fn compile_program_single(
2974        &mut self,
2975        body: &[ast::Stmt],
2976        symbol_table: SymbolTable,
2977    ) -> CompileResult<()> {
2978        self.interactive = true;
2979        // Set future_annotations from symbol table (detected during symbol table scan)
2980        self.future_annotations = symbol_table.future_annotations;
2981        let future_features = self.future_features;
2982        self.current_code_info().flags |= future_features;
2983        if self.future_annotations {
2984            self.future_features
2985                .insert(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
2986            self.current_code_info()
2987                .flags
2988                .insert(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
2989        }
2990        if symbol_table.is_coroutine {
2991            self.current_code_info()
2992                .flags
2993                .insert(bytecode::CodeFlags::COROUTINE);
2994        }
2995
2996        // Module-level __conditional_annotations__ cell
2997        if Self::scope_needs_conditional_annotations_cell(&symbol_table) {
2998            self.current_code_info()
2999                .metadata
3000                .cellvars
3001                .insert("__conditional_annotations__".to_string());
3002        }
3003
3004        self.symbol_table_stack.push(symbol_table);
3005        let module_start_loc = self.module_start_location(body);
3006
3007        self.emit_resume_for_scope(CompilerScope::Module, 1);
3008        emit!(self, PseudoInstruction::AnnotationsPlaceholder);
3009
3010        let annotations_used = self.current_symbol_table().annotations_used;
3011        // Handle annotations based on future_annotations flag
3012        if self.current_symbol_table().has_conditional_annotations {
3013            self.set_source_range(module_start_loc);
3014            emit!(self, Instruction::BuildSet { count: 0 });
3015            self.store_name(&"__conditional_annotations__".into())?;
3016        }
3017
3018        if self.future_annotations && annotations_used {
3019            self.set_source_range(module_start_loc);
3020            // PEP 563: Initialize __annotations__ dict
3021            emit!(self, Instruction::SetupAnnotations);
3022        }
3023
3024        self.compile_statements(body)?;
3025
3026        if annotations_used && !self.future_annotations {
3027            self.compile_module_annotation_setup_sequence(body, module_start_loc)?;
3028        }
3029
3030        self.emit_return_const_no_location(ConstantData::None);
3031        Ok(())
3032    }
3033
3034    fn compile_block_expr(
3035        &mut self,
3036        body: &[ast::Stmt],
3037        symbol_table: SymbolTable,
3038    ) -> CompileResult<()> {
3039        let future_features = self.future_features;
3040        self.current_code_info().flags |= future_features;
3041        if symbol_table.is_coroutine {
3042            self.current_code_info()
3043                .flags
3044                .insert(bytecode::CodeFlags::COROUTINE);
3045        }
3046        self.symbol_table_stack.push(symbol_table);
3047        self.emit_resume_for_scope(CompilerScope::Module, 1);
3048
3049        if let Some((last_statement, statements)) = body.split_last() {
3050            self.compile_statements(statements)?;
3051            match last_statement {
3052                ast::Stmt::Expr(ast::StmtExpr { value, .. }) => {
3053                    self.compile_expression(value)?;
3054                }
3055                ast::Stmt::FunctionDef(ast::StmtFunctionDef {
3056                    name,
3057                    parameters,
3058                    body,
3059                    decorator_list,
3060                    returns,
3061                    type_params,
3062                    is_async,
3063                    ..
3064                }) => {
3065                    validate_duplicate_params(parameters).map_err(|e| self.error(e))?;
3066                    self.compile_function_def(
3067                        name.id(),
3068                        parameters,
3069                        body,
3070                        decorator_list,
3071                        returns.as_deref(),
3072                        *is_async,
3073                        type_params.as_deref(),
3074                        true,
3075                    )?;
3076                }
3077                ast::Stmt::ClassDef(ast::StmtClassDef {
3078                    name,
3079                    body,
3080                    decorator_list,
3081                    type_params,
3082                    arguments,
3083                    ..
3084                }) => {
3085                    self.compile_class_def(
3086                        name.id(),
3087                        body,
3088                        decorator_list,
3089                        type_params.as_deref(),
3090                        arguments.as_deref(),
3091                        true,
3092                    )?;
3093                }
3094                _ => {
3095                    self.compile_statement(last_statement)?;
3096                    self.emit_load_const(ConstantData::None);
3097                }
3098            }
3099        } else {
3100            self.emit_load_const(ConstantData::None);
3101        }
3102        self.emit_return_value();
3103
3104        Ok(())
3105    }
3106
3107    // Compile statement in eval mode:
3108    fn compile_eval(
3109        &mut self,
3110        expression: &ast::ModExpression,
3111        symbol_table: SymbolTable,
3112    ) -> CompileResult<()> {
3113        let future_features = self.future_features;
3114        self.current_code_info().flags |= future_features;
3115        if symbol_table.is_coroutine {
3116            self.current_code_info()
3117                .flags
3118                .insert(bytecode::CodeFlags::COROUTINE);
3119        }
3120        self.symbol_table_stack.push(symbol_table);
3121        self.emit_resume_for_scope(CompilerScope::Module, 1);
3122
3123        self.compile_expression(&expression.body)?;
3124        self.emit_return_value();
3125        // The return belongs to no expression, so the exit block can take its
3126        // location from whichever path reaches it.
3127        self.set_no_location();
3128        Ok(())
3129    }
3130
3131    fn compile_statements(&mut self, statements: &[ast::Stmt]) -> CompileResult<()> {
3132        for statement in statements {
3133            self.compile_statement(statement)?;
3134        }
3135        Ok(())
3136    }
3137
3138    fn compile_with_body_statements(&mut self, statements: &[ast::Stmt]) -> CompileResult<()> {
3139        for statement in statements {
3140            self.compile_statement(statement)?;
3141        }
3142        Ok(())
3143    }
3144
3145    /// CPython `codegen_call_exit_with_nones()`.
3146    fn compile_call_exit_with_nones(&mut self) {
3147        self.emit_load_const(ConstantData::None);
3148        self.emit_load_const(ConstantData::None);
3149        self.emit_load_const(ConstantData::None);
3150        emit!(self, Instruction::Call { argc: 3 });
3151    }
3152
3153    /// CPython `codegen_with_except_finish()`.
3154    fn compile_with_except_finish(&mut self, cleanup_block: BlockIdx) {
3155        let suppress_block = self.new_block();
3156
3157        emit!(self, Instruction::ToBool);
3158        self.set_no_location();
3159        emit!(
3160            self,
3161            Instruction::PopJumpIfTrue {
3162                delta: suppress_block
3163            }
3164        );
3165        self.set_no_location();
3166        emit!(self, Instruction::Reraise { depth: 2 });
3167        self.set_no_location();
3168
3169        self.use_cpython_label_block(suppress_block);
3170        emit!(self, Instruction::PopTop);
3171        self.set_no_location();
3172        emit!(self, PseudoInstruction::PopBlock);
3173        self.set_no_location();
3174        emit!(self, Instruction::PopExcept);
3175        self.set_no_location();
3176        emit!(self, Instruction::PopTop);
3177        self.set_no_location();
3178        emit!(self, Instruction::PopTop);
3179        self.set_no_location();
3180        emit!(self, Instruction::PopTop);
3181        self.set_no_location();
3182        let exit_block = self.new_block();
3183        emit!(
3184            self,
3185            PseudoInstruction::JumpNoInterrupt { delta: exit_block }
3186        );
3187        self.set_no_location();
3188
3189        self.use_cpython_label_block(cleanup_block);
3190        emit!(self, Instruction::Copy { i: 3 });
3191        self.set_no_location();
3192        emit!(self, Instruction::PopExcept);
3193        self.set_no_location();
3194        emit!(self, Instruction::Reraise { depth: 1 });
3195        self.set_no_location();
3196
3197        self.use_cpython_label_block(exit_block);
3198    }
3199
3200    fn compile_loop_body_statements(&mut self, statements: &[ast::Stmt]) -> CompileResult<()> {
3201        self.compile_statements(statements)
3202    }
3203
3204    fn scope_needs_conditional_annotations_cell(symbol_table: &SymbolTable) -> bool {
3205        match symbol_table.typ {
3206            CompilerScope::Module | CompilerScope::Class => {
3207                symbol_table.has_conditional_annotations
3208            }
3209            _ => false,
3210        }
3211    }
3212
3213    fn load_name(&mut self, name: &Name) -> CompileResult<()> {
3214        self.compile_name(name, NameUsage::Load)
3215    }
3216
3217    fn store_name(&mut self, name: &Name) -> CompileResult<()> {
3218        self.compile_name(name, NameUsage::Store)
3219    }
3220
3221    fn emit_no_location_exception_name_cleanup(&mut self, name: &Name) -> CompileResult<()> {
3222        // CPython codegen_try_except() emits `name = None; del name`
3223        // with NO_LOCATION for `except ... as name` cleanup.
3224        self.emit_load_const(ConstantData::None);
3225        self.set_no_location();
3226        self.store_name(name)?;
3227        self.set_no_location();
3228        self.compile_name(name, NameUsage::Delete)?;
3229        self.set_no_location();
3230        Ok(())
3231    }
3232
3233    fn mangle<'a>(&self, name: &'a Name) -> Cow<'a, Name> {
3234        // Use private from current code unit for name mangling
3235        let private = self
3236            .code_stack
3237            .last()
3238            .and_then(|info| info.private.as_ref());
3239        let mangled_names = self.current_symbol_table().mangled_names.as_ref();
3240        symboltable::maybe_mangle_name(private.map(Name::from).as_ref(), mangled_names, name)
3241    }
3242
3243    fn module_name_declared_global_in_nested_scope(table: &SymbolTable, name: &Name) -> bool {
3244        table.sub_tables.iter().any(|subtable| {
3245            (!subtable.comp_inlined
3246                && subtable
3247                    .lookup(name)
3248                    .is_some_and(|symbol| symbol.scope == SymbolScope::GlobalExplicit))
3249                || Self::module_name_declared_global_in_nested_scope(subtable, name)
3250        })
3251    }
3252
3253    // = compiler_nameop
3254    fn compile_name(&mut self, name: &Name, usage: NameUsage) -> CompileResult<()> {
3255        enum NameOp {
3256            Fast,
3257            Global,
3258            Deref,
3259            Name,
3260            DictOrGlobals, // PEP 649: can_see_class_scope
3261        }
3262
3263        let name = self.mangle(name);
3264
3265        // Special handling for __debug__
3266        if NameUsage::Load == usage && name.as_str() == "__debug__" {
3267            self.emit_load_const(ConstantData::Boolean {
3268                value: self.opts.optimize == 0,
3269            });
3270            return Ok(());
3271        }
3272
3273        // Determine the operation type based on symbol scope
3274        let is_function_like = self.ctx.in_func();
3275
3276        // Look up the symbol, handling ast::TypeParams and Annotation scopes specially
3277        let (symbol_scope, can_see_class_scope, class_declared_global) = {
3278            let current_idx = self.symbol_table_stack.len() - 1;
3279            let current_table = &self.symbol_table_stack[current_idx];
3280            let is_typeparams = current_table.typ == CompilerScope::TypeParams;
3281            let is_annotation = matches!(
3282                current_table.typ,
3283                CompilerScope::Annotation | CompilerScope::TypeAlias | CompilerScope::TypeVariable
3284            );
3285            let can_see_class = current_table.can_see_class_scope;
3286
3287            // First try to find in current table
3288            let symbol = current_table.lookup(&name);
3289
3290            // If not found and we're in ast::TypeParams or Annotation scope, try parent scope
3291            let symbol = if symbol.is_none() && (is_typeparams || is_annotation) {
3292                self.symbol_table_stack
3293                    .get(self.symbol_table_stack.len() - 2) // Try to get parent index
3294                    .expect("Symbol has no parent! This is a compiler bug.")
3295                    .lookup(&name)
3296            } else {
3297                symbol
3298            };
3299            let class_declared_global = can_see_class
3300                && self.symbol_table_stack[..current_idx]
3301                    .iter()
3302                    .rev()
3303                    .find(|table| table.typ == CompilerScope::Class)
3304                    .and_then(|table| table.lookup(&name))
3305                    .is_some_and(|symbol| symbol.flags.contains(SymbolFlags::DEF_GLOBAL));
3306
3307            (
3308                symbol.map(|s| s.scope),
3309                can_see_class,
3310                class_declared_global,
3311            )
3312        };
3313
3314        // Special handling for class scope implicit cell variables.
3315        // __classdict__: only LOAD uses Cell (stores go to class namespace)
3316        // __conditional_annotations__: both LOAD and STORE use Cell (it's a mutable set
3317        // that the annotation scope accesses through the closure)
3318        let symbol_scope = {
3319            let current_table = self.current_symbol_table();
3320            if current_table.typ == CompilerScope::Class
3321                && !self.current_code_info().in_inlined_comp
3322                && ((usage == NameUsage::Load
3323                    && (matches!(
3324                        name.as_str(),
3325                        "__classdict__" | "__conditional_annotations__"
3326                    )))
3327                    || (name.as_str() == "__conditional_annotations__"
3328                        && usage == NameUsage::Store))
3329            {
3330                Some(SymbolScope::Cell)
3331            } else {
3332                symbol_scope
3333            }
3334        };
3335
3336        // In annotation or type params scope, missing symbols are treated as global implicit
3337        // This allows referencing global names like Union, Optional, etc. that are imported
3338        // at module level but not explicitly bound in the function scope
3339        let actual_scope = match symbol_scope {
3340            Some(scope) => scope,
3341            None => {
3342                let current_table = self.current_symbol_table();
3343                if matches!(
3344                    current_table.typ,
3345                    CompilerScope::Annotation
3346                        | CompilerScope::TypeAlias
3347                        | CompilerScope::TypeVariable
3348                        | CompilerScope::TypeParams
3349                ) {
3350                    SymbolScope::GlobalImplicit
3351                } else if matches!(
3352                    name.as_str(),
3353                    "__name__"
3354                        | "__module__"
3355                        | "__qualname__"
3356                        | "__firstlineno__"
3357                        | "__doc__"
3358                        | "__static_attributes__"
3359                        | "__annotate__"
3360                        | "__annotate_func__"
3361                        | "__classdictcell__"
3362                        | "__classcell__"
3363                ) {
3364                    SymbolScope::Unknown
3365                } else {
3366                    return Err(self.error(CodegenErrorType::SyntaxError(format!(
3367                        "the symbol '{name}' must be present in the symbol table"
3368                    ))));
3369                }
3370            }
3371        };
3372
3373        let module_global_from_nested_scope = {
3374            let current_table = self.current_symbol_table();
3375            current_table.typ == CompilerScope::Module
3376                && Self::module_name_declared_global_in_nested_scope(current_table, name.as_ref())
3377        };
3378
3379        // Determine operation type based on scope
3380        let op_type = match actual_scope {
3381            SymbolScope::Free => NameOp::Deref,
3382            SymbolScope::Cell => NameOp::Deref,
3383            SymbolScope::Local => {
3384                if module_global_from_nested_scope {
3385                    NameOp::Global
3386                } else if is_function_like
3387                    || self
3388                        .current_code_info()
3389                        .metadata
3390                        .fast_hidden
3391                        .get(name.as_str())
3392                        .is_some_and(|&hidden| hidden)
3393                {
3394                    NameOp::Fast
3395                } else {
3396                    NameOp::Name
3397                }
3398            }
3399            SymbolScope::GlobalImplicit => {
3400                // PEP 649: In annotation scope with class visibility, use DictOrGlobals
3401                // to check classdict first before globals
3402                if class_declared_global {
3403                    NameOp::Global
3404                } else if can_see_class_scope && usage == NameUsage::Load {
3405                    NameOp::DictOrGlobals
3406                } else if is_function_like {
3407                    NameOp::Global
3408                } else {
3409                    NameOp::Name
3410                }
3411            }
3412            SymbolScope::GlobalExplicit => {
3413                // A global declared in the owning class body must bypass the
3414                // classdict, but an explicit global inherited from an outer
3415                // function still participates in DictOrGlobals lookup.
3416                if can_see_class_scope && !class_declared_global && usage == NameUsage::Load {
3417                    NameOp::DictOrGlobals
3418                } else {
3419                    NameOp::Global
3420                }
3421            }
3422            SymbolScope::Unknown => {
3423                if module_global_from_nested_scope {
3424                    NameOp::Global
3425                } else {
3426                    NameOp::Name
3427                }
3428            }
3429        };
3430
3431        // Generate appropriate instructions based on operation type
3432        match op_type {
3433            NameOp::Deref => {
3434                let i = match actual_scope {
3435                    SymbolScope::Free => self.get_free_var_index(&name),
3436                    SymbolScope::Cell => self.get_cell_var_index(&name),
3437                    _ => unreachable!("Invalid scope for Deref operation"),
3438                };
3439
3440                match usage {
3441                    NameUsage::Load => {
3442                        // ClassBlock (not inlined comp): LOAD_LOCALS first, then LOAD_FROM_DICT_OR_DEREF
3443                        if self.ctx.in_class
3444                            && !self.ctx.in_func()
3445                            && !self.current_code_info().in_inlined_comp
3446                        {
3447                            emit!(self, Instruction::LoadLocals);
3448                            emit!(self, Instruction::LoadFromDictOrDeref { i });
3449                        // can_see_class_scope: LOAD_DEREF(__classdict__) first
3450                        } else if can_see_class_scope {
3451                            let classdict_idx = self.get_free_var_index("__classdict__");
3452                            emit!(self, Instruction::LoadDeref { i: classdict_idx });
3453                            emit!(self, Instruction::LoadFromDictOrDeref { i });
3454                        } else {
3455                            emit!(self, Instruction::LoadDeref { i });
3456                        }
3457                    }
3458                    NameUsage::Store => emit!(self, Instruction::StoreDeref { i }),
3459                    NameUsage::Delete => emit!(self, Instruction::DeleteDeref { i }),
3460                };
3461            }
3462            NameOp::Fast => {
3463                let var_num = self.get_local_var_index(&name);
3464                match usage {
3465                    NameUsage::Load => emit!(self, Instruction::LoadFast { var_num }),
3466                    NameUsage::Store => emit!(self, Instruction::StoreFast { var_num }),
3467                    NameUsage::Delete => emit!(self, Instruction::DeleteFast { var_num }),
3468                };
3469            }
3470            NameOp::Global => {
3471                let namei = self.get_global_name_index(&name);
3472                match usage {
3473                    NameUsage::Load => {
3474                        self.emit_load_global(namei, false);
3475                        return Ok(());
3476                    }
3477                    NameUsage::Store => emit!(self, Instruction::StoreGlobal { namei }),
3478                    NameUsage::Delete => emit!(self, Instruction::DeleteGlobal { namei }),
3479                };
3480            }
3481            NameOp::Name => {
3482                let namei = self.get_global_name_index(&name);
3483                match usage {
3484                    NameUsage::Load => {
3485                        if self.current_symbol_table().typ == CompilerScope::Class
3486                            && self.current_code_info().in_inlined_comp
3487                        {
3488                            self.emit_load_global(namei, false);
3489                        } else {
3490                            emit!(self, Instruction::LoadName { namei });
3491                        }
3492                    }
3493                    NameUsage::Store => emit!(self, Instruction::StoreName { namei }),
3494                    NameUsage::Delete => emit!(self, Instruction::DeleteName { namei }),
3495                };
3496            }
3497            NameOp::DictOrGlobals => {
3498                // PEP 649: First check classdict (from __classdict__ freevar), then globals
3499                let idx = self.get_global_name_index(&name);
3500                debug_assert!(usage == NameUsage::Load);
3501                let classdict_idx = self.get_free_var_index("__classdict__");
3502                emit!(self, Instruction::LoadDeref { i: classdict_idx });
3503                emit!(self, Instruction::LoadFromDictOrGlobals { i: idx });
3504            }
3505        }
3506
3507        Ok(())
3508    }
3509
3510    fn compile_statement(&mut self, statement: &ast::Stmt) -> CompileResult<()> {
3511        trace!("Compiling {statement:?}");
3512        let prev_source_range = self.current_source_range;
3513        self.set_source_range(statement.range());
3514
3515        // `from __future__` still executes that `from` statement at runtime, so the
3516        // ImportFrom is compiled down below as well.
3517        if let ast::Stmt::ImportFrom(ast::StmtImportFrom {
3518            module,
3519            names,
3520            level,
3521            ..
3522        }) = &statement
3523            && *level == 0
3524            && module.as_ref().map(|id| id.as_str()) == Some("__future__")
3525        {
3526            self.compile_future_features(names)?;
3527        }
3528
3529        match &statement {
3530            ast::Stmt::Import(ast::StmtImport { names, .. }) => {
3531                // import a, b, c as d
3532                for name in names {
3533                    let name = &name;
3534                    self.emit_load_const(ConstantData::Integer {
3535                        value: num_traits::Zero::zero(),
3536                    });
3537                    self.emit_load_const(ConstantData::None);
3538                    let namei = self.name(&name.name);
3539                    emit!(self, Instruction::ImportName { namei });
3540                    if let Some(alias) = &name.asname {
3541                        let parts: Vec<&str> = name.name.split('.').skip(1).collect();
3542                        for (i, part) in parts.iter().enumerate() {
3543                            let namei = self.name(part);
3544                            emit!(self, Instruction::ImportFrom { namei });
3545                            if i < parts.len() - 1 {
3546                                emit!(self, Instruction::Swap { i: 2 });
3547                                emit!(self, Instruction::PopTop);
3548                            }
3549                        }
3550                        self.store_name(alias.id())?;
3551                        if !parts.is_empty() {
3552                            emit!(self, Instruction::PopTop);
3553                        }
3554                    } else {
3555                        self.store_name(&name.name.split('.').next().unwrap().into())?
3556                    }
3557                }
3558            }
3559            ast::Stmt::ImportFrom(ast::StmtImportFrom {
3560                level,
3561                module,
3562                names,
3563                ..
3564            }) => {
3565                let import_star = names.first().is_some_and(|n| &n.name == "*");
3566
3567                let from_list = names
3568                    .iter()
3569                    .map(|n| ConstantData::Str {
3570                        value: n.name.as_str().into(),
3571                    })
3572                    .collect();
3573
3574                // from .... import (*fromlist)
3575                self.emit_load_const(ConstantData::Integer {
3576                    value: (*level).into(),
3577                });
3578                self.emit_load_const(ConstantData::Tuple {
3579                    elements: from_list,
3580                });
3581
3582                let module_name = module.as_ref().map_or("", |s| s.as_str());
3583                let module_idx = self.name(module_name);
3584                emit!(self, Instruction::ImportName { namei: module_idx });
3585
3586                if import_star {
3587                    // from .... import *
3588                    emit!(
3589                        self,
3590                        Instruction::CallIntrinsic1 {
3591                            func: bytecode::IntrinsicFunction1::ImportStar
3592                        }
3593                    );
3594                    emit!(self, Instruction::PopTop);
3595                    self.set_no_location();
3596                } else {
3597                    // from mod import a, b as c
3598
3599                    for name in names {
3600                        let name = &name;
3601                        let idx = self.name(name.name.as_str());
3602                        // import symbol from module:
3603                        emit!(self, Instruction::ImportFrom { namei: idx });
3604
3605                        // Store module under proper name:
3606                        if let Some(alias) = &name.asname {
3607                            self.store_name(alias.id())?
3608                        } else {
3609                            self.store_name(name.name.id())?
3610                        }
3611                    }
3612
3613                    // Pop module from stack:
3614                    emit!(self, Instruction::PopTop);
3615                }
3616            }
3617            ast::Stmt::Expr(ast::StmtExpr { value, .. }) => {
3618                // Optimize away constant expressions with no side effects.
3619                // In interactive mode, always compile (to print the result).
3620                let dominated_by_interactive =
3621                    self.interactive && !self.ctx.in_func() && !self.ctx.in_class;
3622                if !dominated_by_interactive && value.is_constant() {
3623                    emit!(self, Instruction::Nop);
3624                } else {
3625                    let statement_range = self.current_source_range;
3626                    self.compile_expression(value)?;
3627
3628                    if dominated_by_interactive {
3629                        // The printing belongs to the statement, not to whatever
3630                        // the expression left behind.
3631                        self.set_source_range(statement_range);
3632                        emit!(
3633                            self,
3634                            Instruction::CallIntrinsic1 {
3635                                func: bytecode::IntrinsicFunction1::Print
3636                            }
3637                        );
3638                    }
3639
3640                    emit!(self, Instruction::PopTop);
3641                    self.set_no_location();
3642                }
3643            }
3644            ast::Stmt::Global(_) | ast::Stmt::Nonlocal(_) => {
3645                // Handled during symbol table construction.
3646            }
3647            ast::Stmt::If(ast::StmtIf {
3648                test,
3649                body,
3650                elif_else_clauses,
3651                ..
3652            }) => {
3653                self.enter_conditional_block();
3654                self.compile_if(test, body, elif_else_clauses, statement.range())?;
3655                self.leave_conditional_block();
3656                self.set_source_range(statement.range());
3657            }
3658            ast::Stmt::While(ast::StmtWhile {
3659                test,
3660                body,
3661                orelse,
3662                range,
3663                ..
3664            }) => self.compile_while(test, body, orelse, *range)?,
3665            ast::Stmt::With(ast::StmtWith {
3666                items,
3667                body,
3668                is_async,
3669                ..
3670            }) => self.compile_with(items, body, *is_async)?,
3671            ast::Stmt::For(ast::StmtFor {
3672                target,
3673                iter,
3674                body,
3675                orelse,
3676                is_async,
3677                range,
3678                ..
3679            }) => self.compile_for(target, iter, body, orelse, *is_async, *range)?,
3680            ast::Stmt::Match(ast::StmtMatch { subject, cases, .. }) => {
3681                self.compile_match(subject, cases)?
3682            }
3683            ast::Stmt::Raise(ast::StmtRaise {
3684                exc, cause, range, ..
3685            }) => {
3686                let kind = match exc {
3687                    Some(value) => {
3688                        self.compile_expression(value)?;
3689                        match cause {
3690                            Some(cause) => {
3691                                self.compile_expression(cause)?;
3692                                bytecode::RaiseKind::RaiseCause
3693                            }
3694                            None => bytecode::RaiseKind::Raise,
3695                        }
3696                    }
3697                    None => bytecode::RaiseKind::BareRaise,
3698                };
3699                self.set_source_range(*range);
3700                emit!(self, Instruction::RaiseVarargs { argc: kind });
3701            }
3702            ast::Stmt::Try(ast::StmtTry {
3703                body,
3704                handlers,
3705                orelse,
3706                finalbody,
3707                is_star,
3708                ..
3709            }) => {
3710                self.enter_conditional_block();
3711                if *is_star {
3712                    self.compile_try_star_statement(body, handlers, orelse, finalbody)?
3713                } else {
3714                    self.compile_try_statement(body, handlers, orelse, finalbody)?
3715                }
3716                self.leave_conditional_block();
3717            }
3718            ast::Stmt::FunctionDef(ast::StmtFunctionDef {
3719                name,
3720                parameters,
3721                body,
3722                decorator_list,
3723                returns,
3724                type_params,
3725                is_async,
3726                ..
3727            }) => {
3728                validate_duplicate_params(parameters).map_err(|e| self.error(e))?;
3729
3730                self.compile_function_def(
3731                    name.id(),
3732                    parameters,
3733                    body,
3734                    decorator_list,
3735                    returns.as_deref(),
3736                    *is_async,
3737                    type_params.as_deref(),
3738                    false,
3739                )?
3740            }
3741            ast::Stmt::ClassDef(ast::StmtClassDef {
3742                name,
3743                body,
3744                decorator_list,
3745                type_params,
3746                arguments,
3747                ..
3748            }) => self.compile_class_def(
3749                name.id(),
3750                body,
3751                decorator_list,
3752                type_params.as_deref(),
3753                arguments.as_deref(),
3754                false,
3755            )?,
3756            ast::Stmt::Assert(assert_stmt) => {
3757                let ast::StmtAssert {
3758                    test, msg, range, ..
3759                } = assert_stmt;
3760                self.check_assert(assert_stmt)?;
3761                // if some flag, ignore all assert statements!
3762                if self.opts.optimize == 0 {
3763                    let after_block = self.new_block();
3764                    self.compile_jump_if_inner(test, true, after_block, Some(*range))?;
3765                    self.set_source_range(*range);
3766                    emit!(
3767                        self,
3768                        Instruction::LoadCommonConstant {
3769                            idx: bytecode::CommonConstant::AssertionError
3770                        }
3771                    );
3772                    if let Some(e) = msg {
3773                        self.compile_expression(e)?;
3774                        self.set_source_range(*range);
3775                        emit!(self, Instruction::Call { argc: 0 });
3776                    }
3777                    self.set_source_range(test.range());
3778                    emit!(
3779                        self,
3780                        Instruction::RaiseVarargs {
3781                            argc: bytecode::RaiseKind::Raise,
3782                        }
3783                    );
3784                    self.use_cpython_label_block(after_block);
3785                } else {
3786                    // Optimized-out asserts still need to consume any nested
3787                    // scope symbol tables they contain so later nested scopes
3788                    // stay aligned with AST traversal order.
3789                    self.consume_skipped_nested_scopes_in_expr(test)?;
3790                    if let Some(expr) = msg {
3791                        self.consume_skipped_nested_scopes_in_expr(expr)?;
3792                    }
3793                }
3794            }
3795            ast::Stmt::Break(_) => {
3796                // Unwind fblock stack until we find a loop, emitting cleanup for each fblock
3797                self.compile_break_continue(statement.range(), true)?;
3798            }
3799            ast::Stmt::Continue(_) => {
3800                // Unwind fblock stack until we find a loop, emitting cleanup for each fblock
3801                self.compile_break_continue(statement.range(), false)?;
3802            }
3803            ast::Stmt::Return(ast::StmtReturn { value, .. }) => {
3804                if !self.ctx.in_func() {
3805                    return Err(
3806                        self.error_ranged(CodegenErrorType::InvalidReturn, statement.range())
3807                    );
3808                }
3809
3810                let prev_source_range = self.current_source_range;
3811                let stmt_range = statement.range();
3812                match value {
3813                    Some(v) => {
3814                        if self.ctx.func == FunctionContext::AsyncFunction
3815                            && self.current_symbol_table().is_generator
3816                        {
3817                            return Err(self.error_ranged(
3818                                CodegenErrorType::AsyncReturnValue,
3819                                statement.range(),
3820                            ));
3821                        }
3822                        let debug_constant = matches!(
3823                            &**v,
3824                            ast::Expr::Name(ast::ExprName { id, ctx, .. })
3825                                if matches!(ctx, ast::ExprContext::Load)
3826                                    && id.as_str() == "__debug__"
3827                        );
3828                        let folded_constant = if self.is_constant_expr(v) || debug_constant {
3829                            self.try_fold_constant_expr(v)?
3830                        } else {
3831                            None
3832                        };
3833                        let preserve_tos = folded_constant.is_none();
3834                        let mut return_range = stmt_range;
3835                        if preserve_tos {
3836                            self.compile_expression(v)?;
3837                        } else {
3838                            return_range = v.range();
3839                            self.set_source_range(v.range());
3840                            emit!(self, Instruction::Nop);
3841                        }
3842
3843                        let source = self.source_file.to_source_code();
3844                        if source.line_index(v.range().start())
3845                            != source.line_index(stmt_range.start())
3846                        {
3847                            return_range = stmt_range;
3848                            self.set_source_range(stmt_range);
3849                            emit!(self, Instruction::Nop);
3850                        }
3851                        self.set_source_range(return_range);
3852                        let unwind_loc = self.unwind_fblock_stack(preserve_tos, false)?;
3853                        if let Some(loc) = unwind_loc {
3854                            self.set_source_range(loc);
3855                            match folded_constant {
3856                                Some(constant) => self.emit_return_const(constant),
3857                                None => {
3858                                    self.emit_return_value();
3859                                }
3860                            }
3861                        } else {
3862                            match folded_constant {
3863                                Some(constant) => self.emit_return_const_no_location(constant),
3864                                None => {
3865                                    self.emit_return_value();
3866                                    self.set_no_location();
3867                                }
3868                            }
3869                        }
3870                    }
3871                    None => {
3872                        self.set_source_range(stmt_range);
3873                        emit!(self, Instruction::Nop);
3874                        // Unwind fblock stack with preserve_tos=false (no value to preserve)
3875                        let unwind_loc = self.unwind_fblock_stack(false, false)?;
3876                        if let Some(loc) = unwind_loc {
3877                            self.set_source_range(loc);
3878                            self.emit_return_const(ConstantData::None);
3879                        } else {
3880                            self.emit_return_const_no_location(ConstantData::None);
3881                        }
3882                    }
3883                }
3884                self.set_source_range(prev_source_range);
3885            }
3886            ast::Stmt::Assign(ast::StmtAssign {
3887                targets,
3888                value,
3889                range,
3890                ..
3891            }) => {
3892                self.compile_expression(value)?;
3893
3894                for (i, target) in targets.iter().enumerate() {
3895                    if i + 1 != targets.len() {
3896                        self.set_source_range(*range);
3897                        emit!(self, Instruction::Copy { i: 1 });
3898                    }
3899                    self.compile_store(target)?;
3900                }
3901            }
3902            ast::Stmt::AugAssign(ast::StmtAugAssign {
3903                target, op, value, ..
3904            }) => self.compile_augassign(target, *op, value)?,
3905            ast::Stmt::AnnAssign(ast::StmtAnnAssign {
3906                target,
3907                annotation,
3908                value,
3909                simple,
3910                range,
3911                ..
3912            }) => {
3913                self.compile_annotated_assign(
3914                    target,
3915                    annotation,
3916                    value.as_deref(),
3917                    *simple,
3918                    *range,
3919                )?;
3920                // Bare annotations in function scope emit no code; restore
3921                // source range so subsequent instructions keep the correct line.
3922                if value.is_none() && self.ctx.in_func() {
3923                    self.set_source_range(prev_source_range);
3924                }
3925            }
3926            ast::Stmt::Delete(ast::StmtDelete { targets, .. }) => {
3927                for target in targets {
3928                    self.compile_delete(target)?;
3929                }
3930            }
3931            ast::Stmt::Pass(_) => {
3932                emit!(self, Instruction::Nop); // NOP for line tracing
3933            }
3934            ast::Stmt::TypeAlias(ast::StmtTypeAlias {
3935                name,
3936                type_params,
3937                value,
3938                range,
3939                ..
3940            }) => {
3941                let Some(name) = name.as_name_expr() else {
3942                    return Err(self.error(CodegenErrorType::SyntaxError(
3943                        "type alias expect name".to_owned(),
3944                    )));
3945                };
3946                let name_string = name.id();
3947
3948                if let Some(type_params) = type_params {
3949                    self.set_source_range(*range);
3950                    self.push_symbol_table()?;
3951                    let key = self.symbol_table_stack.len() - 1;
3952                    let lineno = self.get_source_line_number().get().to_u32();
3953                    let scope_name = format!("<generic parameters of {name_string}>");
3954                    self.enter_scope(&scope_name, CompilerScope::TypeParams, key, lineno)?;
3955
3956                    // TypeParams scope is function-like
3957                    let prev_ctx = self.ctx;
3958                    self.ctx = CompileContext {
3959                        in_class: prev_ctx.in_class,
3960                        func: FunctionContext::Function,
3961                        in_async_scope: false,
3962                    };
3963
3964                    self.set_source_range(*range);
3965                    self.emit_load_const(ConstantData::Str {
3966                        value: name_string.as_str().into(),
3967                    });
3968                    self.compile_type_params(type_params)?;
3969                    self.compile_typealias_value_closure(name_string, value, *range)?;
3970                    self.set_source_range(*range);
3971                    emit!(self, Instruction::BuildTuple { count: 3 });
3972                    emit!(
3973                        self,
3974                        Instruction::CallIntrinsic1 {
3975                            func: bytecode::IntrinsicFunction1::TypeAlias
3976                        }
3977                    );
3978                    emit!(self, Instruction::ReturnValue);
3979
3980                    let code = self.exit_scope();
3981                    self.ctx = prev_ctx;
3982                    self.set_source_range(*range);
3983                    self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
3984                    self.set_source_range(*range);
3985                    emit!(self, Instruction::PushNull);
3986                    emit!(self, Instruction::Call { argc: 0 });
3987                } else {
3988                    self.set_source_range(*range);
3989                    self.emit_load_const(ConstantData::Str {
3990                        value: name_string.as_str().into(),
3991                    });
3992                    self.emit_load_const(ConstantData::None);
3993                    self.compile_typealias_value_closure(name_string, value, *range)?;
3994                    self.set_source_range(*range);
3995                    emit!(self, Instruction::BuildTuple { count: 3 });
3996                    emit!(
3997                        self,
3998                        Instruction::CallIntrinsic1 {
3999                            func: bytecode::IntrinsicFunction1::TypeAlias
4000                        }
4001                    );
4002                }
4003
4004                self.set_source_range(*range);
4005                self.store_name(name_string)?;
4006            }
4007            ast::Stmt::IpyEscapeCommand(stmt) => {
4008                return Err(self.error_ranged(
4009                    CodegenErrorType::SyntaxError("invalid syntax".to_owned()),
4010                    stmt.range,
4011                ));
4012            }
4013        }
4014        Ok(())
4015    }
4016
4017    fn compile_delete(&mut self, expression: &ast::Expr) -> CompileResult<()> {
4018        let prev_source_range = self.current_source_range;
4019        self.set_source_range(expression.range());
4020        let result = (|| -> CompileResult<()> {
4021            match &expression {
4022                ast::Expr::Name(ast::ExprName { id, .. }) => {
4023                    self.compile_name(id, NameUsage::Delete)?
4024                }
4025                ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
4026                    self.compile_expression(value)?;
4027                    let namei = self.name(attr.as_str());
4028                    self.set_source_range(self.update_start_location_to_match_attr(
4029                        expression.range(),
4030                        expression.range(),
4031                        attr.as_str(),
4032                    ));
4033                    emit!(self, Instruction::DeleteAttr { namei });
4034                }
4035                ast::Expr::Subscript(ast::ExprSubscript {
4036                    value, slice, ctx, ..
4037                }) => {
4038                    self.compile_subscript(value, slice, *ctx)?;
4039                }
4040                ast::Expr::Tuple(ast::ExprTuple { elts, .. })
4041                | ast::Expr::List(ast::ExprList { elts, .. }) => {
4042                    for element in elts {
4043                        self.compile_delete(element)?;
4044                    }
4045                }
4046                ast::Expr::BinOp(_) | ast::Expr::UnaryOp(_) => {
4047                    return Err(self.error(CodegenErrorType::Delete("expression")));
4048                }
4049                _ => return Err(self.error(CodegenErrorType::Delete(expression.python_name()))),
4050            }
4051            Ok(())
4052        })();
4053        self.set_source_range(prev_source_range);
4054        result
4055    }
4056
4057    fn enter_function(&mut self, name: &str, parameters: &ast::Parameters) -> CompileResult<()> {
4058        self.push_output(
4059            bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
4060            parameters.posonlyargs.len().to_u32(),
4061            parameters.args.len().to_u32(),
4062            parameters.kwonlyargs.len().to_u32(),
4063            name,
4064        )?;
4065
4066        let args_iter = core::iter::empty()
4067            .chain(&parameters.posonlyargs)
4068            .chain(&parameters.args)
4069            .map(|arg| &arg.parameter)
4070            .chain(parameters.kwonlyargs.iter().map(|arg| &arg.parameter));
4071        for name in args_iter {
4072            self.varname(name.name.as_str());
4073        }
4074
4075        if let Some(name) = parameters.vararg.as_deref() {
4076            self.current_code_info().flags |= bytecode::CodeFlags::VARARGS;
4077            self.varname(name.name.as_str());
4078        }
4079        if let Some(name) = parameters.kwarg.as_deref() {
4080            self.current_code_info().flags |= bytecode::CodeFlags::VARKEYWORDS;
4081            self.varname(name.name.as_str());
4082        }
4083
4084        Ok(())
4085    }
4086
4087    /// Push decorators onto the stack in source order.
4088    /// For @dec1 @dec2 def foo(): stack becomes [dec1, dec2]
4089    fn prepare_decorators(&mut self, decorator_list: &[ast::Decorator]) -> CompileResult<()> {
4090        for decorator in decorator_list {
4091            self.compile_expression(&decorator.expression)?;
4092        }
4093        Ok(())
4094    }
4095
4096    /// Apply decorators: each decorator calls the function below it.
4097    /// Stack: [dec1, dec2, func] → CALL 0 → [dec1, dec2(func)] → CALL 0 → [dec1(dec2(func))]
4098    fn apply_decorators(&mut self, decorator_list: &[ast::Decorator]) {
4099        for decorator in decorator_list.iter().rev() {
4100            self.set_source_range(decorator.expression.range());
4101            emit!(self, Instruction::Call { argc: 0 });
4102        }
4103    }
4104
4105    /// Compile type parameter bound or default in a separate scope and return closure
4106    fn compile_type_param_bound_or_default(
4107        &mut self,
4108        expr: &ast::Expr,
4109        name: &str,
4110        allow_starred: bool,
4111    ) -> CompileResult<()> {
4112        let expr_range = expr.range();
4113        self.set_source_range(expr_range);
4114        self.emit_load_const(ConstantData::Tuple {
4115            elements: vec![ConstantData::Integer { value: 1.into() }],
4116        });
4117
4118        // Push the next symbol table onto the stack
4119        self.push_symbol_table()?;
4120
4121        // Get the current symbol table
4122        let key = self.symbol_table_stack.len() - 1;
4123        let lineno = self.get_source_line_number().get().to_u32();
4124
4125        // Enter scope with the type parameter name
4126        self.enter_scope(name, CompilerScope::TypeVariable, key, lineno)?;
4127
4128        self.configure_annotation_format_parameter();
4129
4130        self.emit_format_validation();
4131
4132        // TypeParams scope is function-like
4133        let prev_ctx = self.ctx;
4134        self.ctx = CompileContext {
4135            in_class: prev_ctx.in_class,
4136            func: FunctionContext::Function,
4137            in_async_scope: false,
4138        };
4139
4140        // Compile the expression
4141        if allow_starred && matches!(expr, ast::Expr::Starred(_)) {
4142            if let ast::Expr::Starred(starred) = expr {
4143                self.compile_expression(&starred.value)?;
4144                self.set_source_range(expr_range);
4145                emit!(self, Instruction::UnpackSequence { count: 1 });
4146            }
4147        } else {
4148            self.compile_expression(expr)?;
4149        }
4150
4151        // Return value
4152        self.set_source_range(expr_range);
4153        emit!(self, Instruction::ReturnValue);
4154        self.emit_return_const_no_location(ConstantData::None);
4155
4156        // Exit scope and create closure
4157        let code = self.exit_scope();
4158        self.ctx = prev_ctx;
4159
4160        self.set_source_range(expr_range);
4161        self.make_closure(
4162            code,
4163            bytecode::MakeFunctionFlags::from([bytecode::MakeFunctionFlag::Defaults]),
4164        )?;
4165
4166        Ok(())
4167    }
4168
4169    fn compile_typealias_value_closure(
4170        &mut self,
4171        alias_name: &str,
4172        value: &ast::Expr,
4173        alias_range: TextRange,
4174    ) -> CompileResult<()> {
4175        self.set_source_range(alias_range);
4176        self.emit_load_const(ConstantData::Tuple {
4177            elements: vec![ConstantData::Integer { value: 1.into() }],
4178        });
4179
4180        self.push_symbol_table()?;
4181        let key = self.symbol_table_stack.len() - 1;
4182        let lineno = self.get_source_line_number().get().to_u32();
4183        self.enter_scope(alias_name, CompilerScope::TypeAlias, key, lineno)?;
4184        self.configure_annotation_format_parameter();
4185        self.emit_format_validation();
4186
4187        let prev_ctx = self.ctx;
4188        self.ctx = CompileContext {
4189            in_class: prev_ctx.in_class,
4190            func: FunctionContext::Function,
4191            in_async_scope: false,
4192        };
4193
4194        self.compile_expression(value)?;
4195        self.set_source_range(alias_range);
4196        emit!(self, Instruction::ReturnValue);
4197
4198        let code = self.exit_scope();
4199        self.ctx = prev_ctx;
4200        self.set_source_range(alias_range);
4201        self.make_closure(
4202            code,
4203            bytecode::MakeFunctionFlags::from([bytecode::MakeFunctionFlag::Defaults]),
4204        )?;
4205
4206        Ok(())
4207    }
4208
4209    /// Store each type parameter so it is accessible to the current scope, and leave a tuple of
4210    /// all the type parameters on the stack. Handles default values per PEP 695.
4211    fn compile_type_params(&mut self, type_params: &ast::TypeParams) -> CompileResult<()> {
4212        let mut seen_default = false;
4213        // First, compile each type parameter and store it
4214        for type_param in &type_params.type_params {
4215            match type_param {
4216                ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
4217                    name,
4218                    bound,
4219                    default,
4220                    range,
4221                    ..
4222                }) => {
4223                    self.set_source_range(*range);
4224                    self.emit_load_const(ConstantData::Str {
4225                        value: name.as_str().into(),
4226                    });
4227
4228                    if let Some(expr) = &bound {
4229                        self.compile_type_param_bound_or_default(expr, name.as_str(), false)?;
4230
4231                        self.set_source_range(*range);
4232                        let intrinsic = if expr.is_tuple_expr() {
4233                            bytecode::IntrinsicFunction2::TypeVarWithConstraint
4234                        } else {
4235                            bytecode::IntrinsicFunction2::TypeVarWithBound
4236                        };
4237                        emit!(self, Instruction::CallIntrinsic2 { func: intrinsic });
4238                    } else {
4239                        emit!(
4240                            self,
4241                            Instruction::CallIntrinsic1 {
4242                                func: bytecode::IntrinsicFunction1::TypeVar
4243                            }
4244                        );
4245                    }
4246
4247                    if let Some(default_expr) = default {
4248                        seen_default = true;
4249                        self.compile_type_param_bound_or_default(
4250                            default_expr,
4251                            name.as_str(),
4252                            false,
4253                        )?;
4254                        self.set_source_range(*range);
4255                        emit!(
4256                            self,
4257                            Instruction::CallIntrinsic2 {
4258                                func: bytecode::IntrinsicFunction2::SetTypeparamDefault
4259                            }
4260                        );
4261                    } else if seen_default {
4262                        return Err(self.error_ranged(
4263                            CodegenErrorType::SyntaxError(format!(
4264                                "non-default type parameter '{name}' follows default type parameter"
4265                            )),
4266                            *range,
4267                        ));
4268                    }
4269
4270                    self.set_source_range(*range);
4271                    emit!(self, Instruction::Copy { i: 1 });
4272                    self.store_name(name.id())?;
4273                }
4274                ast::TypeParam::ParamSpec(ast::TypeParamParamSpec {
4275                    name,
4276                    default,
4277                    range,
4278                    ..
4279                }) => {
4280                    self.set_source_range(*range);
4281                    self.emit_load_const(ConstantData::Str {
4282                        value: name.as_str().into(),
4283                    });
4284                    emit!(
4285                        self,
4286                        Instruction::CallIntrinsic1 {
4287                            func: bytecode::IntrinsicFunction1::ParamSpec
4288                        }
4289                    );
4290
4291                    if let Some(default_expr) = default {
4292                        seen_default = true;
4293                        self.compile_type_param_bound_or_default(
4294                            default_expr,
4295                            name.as_str(),
4296                            false,
4297                        )?;
4298                        self.set_source_range(*range);
4299                        emit!(
4300                            self,
4301                            Instruction::CallIntrinsic2 {
4302                                func: bytecode::IntrinsicFunction2::SetTypeparamDefault
4303                            }
4304                        );
4305                    } else if seen_default {
4306                        return Err(self.error_ranged(
4307                            CodegenErrorType::SyntaxError(format!(
4308                                "non-default type parameter '{name}' follows default type parameter"
4309                            )),
4310                            *range,
4311                        ));
4312                    }
4313
4314                    self.set_source_range(*range);
4315                    emit!(self, Instruction::Copy { i: 1 });
4316                    self.store_name(name.id())?;
4317                }
4318                ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple {
4319                    name,
4320                    default,
4321                    range,
4322                    ..
4323                }) => {
4324                    self.set_source_range(*range);
4325                    self.emit_load_const(ConstantData::Str {
4326                        value: name.as_str().into(),
4327                    });
4328                    emit!(
4329                        self,
4330                        Instruction::CallIntrinsic1 {
4331                            func: bytecode::IntrinsicFunction1::TypeVarTuple
4332                        }
4333                    );
4334
4335                    if let Some(default_expr) = default {
4336                        // TypeVarTuple allows starred expressions
4337                        self.compile_type_param_bound_or_default(
4338                            default_expr,
4339                            name.as_str(),
4340                            true,
4341                        )?;
4342                        self.set_source_range(*range);
4343                        emit!(
4344                            self,
4345                            Instruction::CallIntrinsic2 {
4346                                func: bytecode::IntrinsicFunction2::SetTypeparamDefault
4347                            }
4348                        );
4349                        seen_default = true;
4350                    } else if seen_default {
4351                        return Err(self.error_ranged(
4352                            CodegenErrorType::SyntaxError(format!(
4353                                "non-default type parameter '{name}' follows default type parameter"
4354                            )),
4355                            *range,
4356                        ));
4357                    }
4358
4359                    self.set_source_range(*range);
4360                    emit!(self, Instruction::Copy { i: 1 });
4361                    self.store_name(name.id())?;
4362                }
4363            };
4364        }
4365        if let Some(first) = type_params.type_params.first() {
4366            self.set_source_range(first.range());
4367        }
4368        emit!(
4369            self,
4370            Instruction::BuildTuple {
4371                count: u32::try_from(type_params.len()).unwrap(),
4372            }
4373        );
4374        Ok(())
4375    }
4376
4377    fn compile_try_statement(
4378        &mut self,
4379        body: &[ast::Stmt],
4380        handlers: &[ast::ExceptHandler],
4381        orelse: &[ast::Stmt],
4382        finalbody: &[ast::Stmt],
4383    ) -> CompileResult<()> {
4384        if finalbody.is_empty() {
4385            return self.compile_try_except_no_finally(body, handlers, orelse);
4386        }
4387
4388        let body_block = self.new_block();
4389        let finally_except_block = self.new_block();
4390        let exit_block = self.new_block();
4391        let finally_cleanup_block = self.new_block();
4392
4393        emit!(
4394            self,
4395            PseudoInstruction::SetupFinally {
4396                delta: finally_except_block
4397            }
4398        );
4399        self.use_cpython_label_block(body_block);
4400        let body_label = self.instr_sequence_label_for_block(body_block);
4401        let finally_except_label = self.instr_sequence_label_for_block(finally_except_block);
4402        self.push_fblock_labels(
4403            FBlockType::FinallyTry,
4404            body_label,
4405            finally_except_label,
4406            FBlockDatum::FinallyBody(finalbody.to_vec()),
4407        )?;
4408
4409        if handlers.is_empty() {
4410            self.compile_statements(body)?;
4411        } else {
4412            self.compile_try_except_no_finally(body, handlers, orelse)?;
4413        }
4414
4415        emit!(self, PseudoInstruction::PopBlock);
4416        self.set_no_location();
4417        self.pop_fblock_label(FBlockType::FinallyTry, body_label);
4418
4419        let symbol_table_cursors = self.current_symbol_table_cursors();
4420
4421        self.compile_statements(finalbody)?;
4422        emit!(
4423            self,
4424            PseudoInstruction::JumpNoInterrupt { delta: exit_block }
4425        );
4426        self.set_no_location();
4427
4428        self.set_symbol_table_cursors(symbol_table_cursors);
4429
4430        self.use_cpython_label_block(finally_except_block);
4431        emit!(
4432            self,
4433            PseudoInstruction::SetupCleanup {
4434                delta: finally_cleanup_block
4435            }
4436        );
4437        self.set_no_location();
4438        emit!(self, Instruction::PushExcInfo);
4439        self.set_no_location();
4440        self.push_fblock_labels(
4441            FBlockType::FinallyEnd,
4442            finally_except_label,
4443            ir::InstructionSequenceLabel::NO_LABEL,
4444            FBlockDatum::None,
4445        )?;
4446        self.compile_statements(finalbody)?;
4447        self.pop_fblock_label(FBlockType::FinallyEnd, finally_except_label);
4448        emit!(self, Instruction::Reraise { depth: 0 });
4449        self.set_no_location();
4450
4451        self.use_cpython_label_block(finally_cleanup_block);
4452        emit!(self, Instruction::Copy { i: 3 });
4453        self.set_no_location();
4454        emit!(self, Instruction::PopExcept);
4455        self.set_no_location();
4456        emit!(self, Instruction::Reraise { depth: 1 });
4457        self.set_no_location();
4458
4459        self.use_cpython_label_block(exit_block);
4460
4461        Ok(())
4462    }
4463
4464    fn compile_try_except_no_finally(
4465        &mut self,
4466        body: &[ast::Stmt],
4467        handlers: &[ast::ExceptHandler],
4468        orelse: &[ast::Stmt],
4469    ) -> CompileResult<()> {
4470        let body_block = self.new_block();
4471        let handler_block = self.new_block();
4472        let end_block = self.new_block();
4473        let cleanup_block = self.new_block();
4474        emit!(
4475            self,
4476            PseudoInstruction::SetupFinally {
4477                delta: handler_block
4478            }
4479        );
4480        self.use_cpython_label_block(body_block);
4481        let body_label = self.instr_sequence_label_for_block(body_block);
4482        self.push_fblock_labels(
4483            FBlockType::TryExcept,
4484            body_label,
4485            ir::InstructionSequenceLabel::NO_LABEL,
4486            FBlockDatum::None,
4487        )?;
4488        self.compile_statements(body)?;
4489        self.pop_fblock_label(FBlockType::TryExcept, body_label);
4490        emit!(self, PseudoInstruction::PopBlock);
4491        self.set_no_location();
4492
4493        // The symtable stores child scopes in AST visit order
4494        // (body, handlers, orelse), while codegen_try_except() emits orelse
4495        // before the exception handlers. Keep the symbol table in symtable order
4496        // and only move the codegen cursor while compiling orelse.
4497        let handler_symbol_table_cursors = self.current_symbol_table_cursors();
4498        self.consume_skipped_nested_scopes_in_except_handlers(handlers)?;
4499        self.compile_statements(orelse)?;
4500        let after_orelse_symbol_table_cursors = self.current_symbol_table_cursors();
4501        self.set_symbol_table_cursors(handler_symbol_table_cursors);
4502        emit!(
4503            self,
4504            PseudoInstruction::JumpNoInterrupt { delta: end_block }
4505        );
4506        self.set_no_location();
4507
4508        self.use_cpython_label_block(handler_block);
4509        emit!(
4510            self,
4511            PseudoInstruction::SetupCleanup {
4512                delta: cleanup_block
4513            }
4514        );
4515        self.set_no_location();
4516        emit!(self, Instruction::PushExcInfo);
4517        self.set_no_location();
4518        self.push_fblock_labels(
4519            FBlockType::ExceptionHandler,
4520            ir::InstructionSequenceLabel::NO_LABEL,
4521            ir::InstructionSequenceLabel::NO_LABEL,
4522            FBlockDatum::None,
4523        )?;
4524
4525        for (i, handler) in handlers.iter().enumerate() {
4526            let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
4527                type_,
4528                name,
4529                body,
4530                range: handler_range,
4531                ..
4532            }) = handler;
4533            self.set_source_range(*handler_range);
4534            if type_.is_none() && i < handlers.len() - 1 {
4535                return Err(self.error(CodegenErrorType::SyntaxError(
4536                    "default 'except:' must be last".to_owned(),
4537                )));
4538            }
4539            let next_handler = self.new_block();
4540
4541            if let Some(exc_type) = type_ {
4542                self.compile_expression(exc_type)?;
4543                self.set_source_range(*handler_range);
4544                emit!(self, Instruction::CheckExcMatch);
4545                emit!(
4546                    self,
4547                    Instruction::PopJumpIfFalse {
4548                        delta: next_handler
4549                    }
4550                );
4551            }
4552
4553            if let Some(alias) = name {
4554                let cleanup_end = self.new_block();
4555                let cleanup_body = self.new_block();
4556
4557                self.store_name(alias.id())?;
4558
4559                emit!(self, PseudoInstruction::SetupCleanup { delta: cleanup_end });
4560                self.use_cpython_label_block(cleanup_body);
4561                let cleanup_body_label = self.instr_sequence_label_for_block(cleanup_body);
4562                self.push_fblock_labels(
4563                    FBlockType::HandlerCleanup,
4564                    cleanup_body_label,
4565                    ir::InstructionSequenceLabel::NO_LABEL,
4566                    FBlockDatum::ExceptionName(alias.as_str().to_owned()),
4567                )?;
4568
4569                self.compile_statements(body)?;
4570
4571                self.pop_fblock_label(FBlockType::HandlerCleanup, cleanup_body_label);
4572                emit!(self, PseudoInstruction::PopBlock);
4573                self.set_no_location();
4574                emit!(self, PseudoInstruction::PopBlock);
4575                self.set_no_location();
4576                emit!(self, Instruction::PopExcept);
4577                self.set_no_location();
4578
4579                self.emit_no_location_exception_name_cleanup(alias.id())?;
4580
4581                emit!(
4582                    self,
4583                    PseudoInstruction::JumpNoInterrupt { delta: end_block }
4584                );
4585                self.set_no_location();
4586
4587                self.use_cpython_label_block(cleanup_end);
4588                self.emit_no_location_exception_name_cleanup(alias.id())?;
4589                emit!(self, Instruction::Reraise { depth: 1 });
4590                self.set_no_location();
4591            } else {
4592                let cleanup_body = self.new_block();
4593
4594                emit!(self, Instruction::PopTop);
4595                self.use_cpython_label_block(cleanup_body);
4596                let cleanup_body_label = self.instr_sequence_label_for_block(cleanup_body);
4597                self.push_fblock_labels(
4598                    FBlockType::HandlerCleanup,
4599                    cleanup_body_label,
4600                    ir::InstructionSequenceLabel::NO_LABEL,
4601                    FBlockDatum::None,
4602                )?;
4603
4604                self.compile_statements(body)?;
4605
4606                self.pop_fblock_label(FBlockType::HandlerCleanup, cleanup_body_label);
4607                emit!(self, PseudoInstruction::PopBlock);
4608                self.set_no_location();
4609                emit!(self, Instruction::PopExcept);
4610                self.set_no_location();
4611                emit!(
4612                    self,
4613                    PseudoInstruction::JumpNoInterrupt { delta: end_block }
4614                );
4615                self.set_no_location();
4616            }
4617
4618            self.use_cpython_label_block(next_handler);
4619        }
4620        self.set_symbol_table_cursors(after_orelse_symbol_table_cursors);
4621
4622        self.pop_fblock_label(
4623            FBlockType::ExceptionHandler,
4624            ir::InstructionSequenceLabel::NO_LABEL,
4625        );
4626        emit!(self, Instruction::Reraise { depth: 0 });
4627        self.set_no_location();
4628
4629        self.use_cpython_label_block(cleanup_block);
4630        emit!(self, Instruction::Copy { i: 3 });
4631        self.set_no_location();
4632        emit!(self, Instruction::PopExcept);
4633        self.set_no_location();
4634        emit!(self, Instruction::Reraise { depth: 1 });
4635        self.set_no_location();
4636
4637        self.use_cpython_label_block(end_block);
4638        Ok(())
4639    }
4640
4641    fn compile_try_star_statement(
4642        &mut self,
4643        body: &[ast::Stmt],
4644        handlers: &[ast::ExceptHandler],
4645        orelse: &[ast::Stmt],
4646        finalbody: &[ast::Stmt],
4647    ) -> CompileResult<()> {
4648        if finalbody.is_empty() {
4649            return self.compile_try_star_except(body, handlers, orelse);
4650        }
4651
4652        let body_block = self.new_block();
4653        let finally_except_block = self.new_block();
4654        let exit_block = self.new_block();
4655        let finally_cleanup_block = self.new_block();
4656
4657        emit!(
4658            self,
4659            PseudoInstruction::SetupFinally {
4660                delta: finally_except_block
4661            }
4662        );
4663        self.use_cpython_label_block(body_block);
4664        let body_label = self.instr_sequence_label_for_block(body_block);
4665        let finally_except_label = self.instr_sequence_label_for_block(finally_except_block);
4666        self.push_fblock_labels(
4667            FBlockType::FinallyTry,
4668            body_label,
4669            finally_except_label,
4670            FBlockDatum::FinallyBody(finalbody.to_vec()),
4671        )?;
4672
4673        if handlers.is_empty() {
4674            self.compile_statements(body)?;
4675        } else {
4676            self.compile_try_star_except(body, handlers, orelse)?;
4677        }
4678
4679        emit!(self, PseudoInstruction::PopBlock);
4680        self.set_no_location();
4681        self.pop_fblock_label(FBlockType::FinallyTry, body_label);
4682
4683        let symbol_table_cursors = self.current_symbol_table_cursors();
4684        self.compile_statements(finalbody)?;
4685
4686        emit!(
4687            self,
4688            PseudoInstruction::JumpNoInterrupt { delta: exit_block }
4689        );
4690        self.set_no_location();
4691
4692        self.set_symbol_table_cursors(symbol_table_cursors);
4693
4694        self.use_cpython_label_block(finally_except_block);
4695        emit!(
4696            self,
4697            PseudoInstruction::SetupCleanup {
4698                delta: finally_cleanup_block
4699            }
4700        );
4701        self.set_no_location();
4702        emit!(self, Instruction::PushExcInfo);
4703        self.set_no_location();
4704        self.push_fblock_labels(
4705            FBlockType::FinallyEnd,
4706            finally_except_label,
4707            ir::InstructionSequenceLabel::NO_LABEL,
4708            FBlockDatum::None,
4709        )?;
4710        self.compile_statements(finalbody)?;
4711        self.pop_fblock_label(FBlockType::FinallyEnd, finally_except_label);
4712        emit!(self, Instruction::Reraise { depth: 0 });
4713        self.set_no_location();
4714
4715        self.use_cpython_label_block(finally_cleanup_block);
4716        emit!(self, Instruction::Copy { i: 3 });
4717        self.set_no_location();
4718        emit!(self, Instruction::PopExcept);
4719        self.set_no_location();
4720        emit!(self, Instruction::Reraise { depth: 1 });
4721        self.set_no_location();
4722
4723        self.use_cpython_label_block(exit_block);
4724
4725        Ok(())
4726    }
4727
4728    fn compile_try_star_except(
4729        &mut self,
4730        body: &[ast::Stmt],
4731        handlers: &[ast::ExceptHandler],
4732        orelse: &[ast::Stmt],
4733    ) -> CompileResult<()> {
4734        // compiler_try_star_except
4735        // Stack layout during handler processing: [prev_exc, orig, list, rest]
4736        let body_block = self.new_block();
4737        let handler_block = self.new_block();
4738        let else_block = self.new_block();
4739        let end_block = self.new_block();
4740        let cleanup_block = self.new_block();
4741        let reraise_star_block = self.new_block();
4742
4743        // SETUP_FINALLY for try body
4744        emit!(
4745            self,
4746            PseudoInstruction::SetupFinally {
4747                delta: handler_block
4748            }
4749        );
4750        self.use_cpython_label_block(body_block);
4751        let body_label = self.instr_sequence_label_for_block(body_block);
4752        self.push_fblock_labels(
4753            FBlockType::TryExcept,
4754            body_label,
4755            ir::InstructionSequenceLabel::NO_LABEL,
4756            FBlockDatum::None,
4757        )?;
4758        self.compile_statements(body)?;
4759        self.pop_fblock_label(FBlockType::TryExcept, body_label);
4760        emit!(self, PseudoInstruction::PopBlock);
4761        self.set_no_location();
4762        emit!(
4763            self,
4764            PseudoInstruction::JumpNoInterrupt { delta: else_block }
4765        );
4766        self.set_no_location();
4767
4768        // Exception handler entry
4769        self.use_cpython_label_block(handler_block);
4770        // Stack: [exc] (from exception table)
4771
4772        emit!(
4773            self,
4774            PseudoInstruction::SetupCleanup {
4775                delta: cleanup_block
4776            }
4777        );
4778        self.set_no_location();
4779
4780        // PUSH_EXC_INFO
4781        emit!(self, Instruction::PushExcInfo);
4782        self.set_no_location();
4783        // Stack: [prev_exc, exc]
4784
4785        // Push EXCEPTION_GROUP_HANDLER fblock
4786        self.push_fblock_labels(
4787            FBlockType::ExceptionGroupHandler,
4788            ir::InstructionSequenceLabel::NO_LABEL,
4789            ir::InstructionSequenceLabel::NO_LABEL,
4790            FBlockDatum::None,
4791        )?;
4792
4793        let n = handlers.len();
4794        for (i, handler) in handlers.iter().enumerate() {
4795            let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
4796                type_,
4797                name,
4798                body,
4799                range: handler_range,
4800                ..
4801            }) = handler;
4802            let is_last_handler = i == n - 1;
4803            self.set_source_range(*handler_range);
4804
4805            let next_handler_block = self.new_block();
4806            let except_with_error_block = self.new_block();
4807            let no_match_block = self.new_block();
4808
4809            if i == 0 {
4810                // CPython initializes the except* work stack inside the first
4811                // handler iteration in codegen_try_star_except().
4812                emit!(self, Instruction::BuildList { count: 0 });
4813                emit!(self, Instruction::Copy { i: 2 });
4814            }
4815
4816            // Compile exception type. The public-AST validator allows a
4817            // NULL type here, so codegen only emits CHECK_EG_MATCH when present.
4818            if let Some(exc_type) = type_ {
4819                self.compile_expression(exc_type)?;
4820                self.set_source_range(*handler_range);
4821            }
4822
4823            if type_.is_some() {
4824                // Stack: [prev_exc, orig, list, rest, type]
4825                // ADDOP(c, loc, CHECK_EG_MATCH);
4826                emit!(self, Instruction::CheckEgMatch);
4827                // Stack: [prev_exc, orig, list, new_rest, match]
4828
4829                // ADDOP_I(c, loc, COPY, 1);
4830                // ADDOP_JUMP(c, loc, POP_JUMP_IF_NONE, no_match);
4831                emit!(self, Instruction::Copy { i: 1 });
4832                emit!(
4833                    self,
4834                    Instruction::PopJumpIfNone {
4835                        delta: no_match_block
4836                    }
4837                );
4838            }
4839
4840            // Handler matched
4841            // Stack: [prev_exc, orig, list, new_rest, match]
4842            // Note: CheckEgMatch already sets the matched exception as current exception
4843            let cleanup_end_block = self.new_block();
4844            let cleanup_body_block = self.new_block();
4845
4846            // Store match to name or pop
4847            if let Some(alias) = name {
4848                self.store_name(alias.id())?;
4849            } else {
4850                emit!(self, Instruction::PopTop); // pop match
4851            }
4852            // Stack: [prev_exc, orig, list, new_rest]
4853
4854            // HANDLER_CLEANUP fblock for handler body
4855            emit!(
4856                self,
4857                PseudoInstruction::SetupCleanup {
4858                    delta: cleanup_end_block
4859                }
4860            );
4861            self.use_cpython_label_block(cleanup_body_block);
4862            let cleanup_body_label = self.instr_sequence_label_for_block(cleanup_body_block);
4863            self.push_fblock_labels(
4864                FBlockType::HandlerCleanup,
4865                cleanup_body_label,
4866                ir::InstructionSequenceLabel::NO_LABEL,
4867                if let Some(alias) = name {
4868                    FBlockDatum::ExceptionName(alias.as_str().to_owned())
4869                } else {
4870                    FBlockDatum::None
4871                },
4872            )?;
4873
4874            // Execute handler body
4875            self.compile_statements(body)?;
4876
4877            // Handler body completed normally
4878            self.pop_fblock_label(FBlockType::HandlerCleanup, cleanup_body_label);
4879            emit!(self, PseudoInstruction::PopBlock);
4880            self.set_no_location();
4881
4882            // Cleanup name binding
4883            if let Some(alias) = name {
4884                self.emit_no_location_exception_name_cleanup(alias.id())?;
4885            }
4886
4887            emit!(
4888                self,
4889                PseudoInstruction::JumpNoInterrupt {
4890                    delta: next_handler_block
4891                }
4892            );
4893            self.set_no_location();
4894
4895            // Handler raised an exception (cleanup_end label)
4896            self.use_cpython_label_block(cleanup_end_block);
4897            // Stack: [prev_exc, orig, list, new_rest, lasti, raised_exc]
4898            // (lasti is pushed because push_lasti=true in HANDLER_CLEANUP fblock)
4899
4900            // Cleanup name binding
4901            if let Some(alias) = name {
4902                self.emit_no_location_exception_name_cleanup(alias.id())?;
4903            }
4904
4905            // LIST_APPEND(3) - append raised_exc to list
4906            // Stack: [prev_exc, orig, list, new_rest, lasti, raised_exc]
4907            // After pop: [prev_exc, orig, list, new_rest, lasti] (len=5)
4908            // nth_value(i) = stack[len - i - 1], we need stack[2] = list
4909            // stack[5 - i - 1] = 2 -> i = 2
4910            emit!(self, Instruction::ListAppend { i: 3 });
4911            self.set_no_location();
4912            // Stack: [prev_exc, orig, list, new_rest, lasti]
4913
4914            // POP_TOP - pop lasti
4915            emit!(self, Instruction::PopTop);
4916            self.set_no_location();
4917            // Stack: [prev_exc, orig, list, new_rest]
4918
4919            emit!(
4920                self,
4921                PseudoInstruction::JumpNoInterrupt {
4922                    delta: except_with_error_block
4923                }
4924            );
4925            self.set_no_location();
4926
4927            self.use_cpython_label_block(next_handler_block);
4928            emit!(self, Instruction::Nop);
4929            self.set_no_location();
4930            emit!(
4931                self,
4932                PseudoInstruction::JumpNoInterrupt {
4933                    delta: except_with_error_block
4934                }
4935            );
4936            self.set_no_location();
4937
4938            self.use_cpython_label_block(no_match_block);
4939            self.set_source_range(*handler_range);
4940            emit!(self, Instruction::PopTop); // pop match (None)
4941            // Stack: [prev_exc, orig, list, new_rest]
4942
4943            self.use_cpython_label_block(except_with_error_block);
4944
4945            if is_last_handler {
4946                emit!(self, Instruction::ListAppend { i: 1 });
4947                self.set_no_location();
4948                emit!(
4949                    self,
4950                    PseudoInstruction::JumpNoInterrupt {
4951                        delta: reraise_star_block
4952                    }
4953                );
4954                self.set_no_location();
4955            }
4956        }
4957
4958        // Pop EXCEPTION_GROUP_HANDLER fblock
4959        self.pop_fblock_label(
4960            FBlockType::ExceptionGroupHandler,
4961            ir::InstructionSequenceLabel::NO_LABEL,
4962        );
4963        let reraise_block = self.new_block();
4964
4965        // Reraise star block
4966        self.use_cpython_label_block(reraise_star_block);
4967        // Stack: [prev_exc, orig, list]
4968
4969        // CALL_INTRINSIC_2 PREP_RERAISE_STAR
4970        // Takes 2 args (orig, list) and produces result
4971        emit!(
4972            self,
4973            Instruction::CallIntrinsic2 {
4974                func: bytecode::IntrinsicFunction2::PrepReraiseStar
4975            }
4976        );
4977        self.set_no_location();
4978        // Stack: [prev_exc, result]
4979
4980        // COPY 1
4981        emit!(self, Instruction::Copy { i: 1 });
4982        self.set_no_location();
4983        // Stack: [prev_exc, result, result]
4984
4985        // POP_JUMP_IF_NOT_NONE reraise
4986        emit!(
4987            self,
4988            Instruction::PopJumpIfNotNone {
4989                delta: reraise_block
4990            }
4991        );
4992        self.set_no_location();
4993        // Stack: [prev_exc, result]
4994
4995        // Nothing to reraise
4996        // POP_TOP - pop result (None)
4997        emit!(self, Instruction::PopTop);
4998        self.set_no_location();
4999        // Stack: [prev_exc]
5000
5001        emit!(self, PseudoInstruction::PopBlock);
5002        self.set_no_location();
5003        // POP_EXCEPT - restore previous exception context
5004        emit!(self, Instruction::PopExcept);
5005        self.set_no_location();
5006        // Stack: []
5007
5008        emit!(
5009            self,
5010            PseudoInstruction::JumpNoInterrupt { delta: end_block }
5011        );
5012        self.set_no_location();
5013
5014        // Reraise the result
5015        self.use_cpython_label_block(reraise_block);
5016
5017        // Stack: [prev_exc, result]
5018        emit!(self, PseudoInstruction::PopBlock);
5019        self.set_no_location();
5020        emit!(self, Instruction::Swap { i: 2 });
5021        self.set_no_location();
5022        // Stack: [result, prev_exc]
5023
5024        // POP_EXCEPT
5025        emit!(self, Instruction::PopExcept);
5026        self.set_no_location();
5027        // Stack: [result]
5028
5029        // RERAISE 0
5030        emit!(self, Instruction::Reraise { depth: 0 });
5031        self.set_no_location();
5032
5033        self.use_cpython_label_block(cleanup_block);
5034        emit!(self, Instruction::Copy { i: 3 });
5035        self.set_no_location();
5036        emit!(self, Instruction::PopExcept);
5037        self.set_no_location();
5038        emit!(self, Instruction::Reraise { depth: 1 });
5039        self.set_no_location();
5040
5041        self.use_cpython_label_block(else_block);
5042        self.compile_statements(orelse)?;
5043
5044        self.use_cpython_label_block(end_block);
5045
5046        Ok(())
5047    }
5048
5049    /// Compile default arguments
5050    // = compiler_default_arguments
5051    fn compile_default_arguments(
5052        &mut self,
5053        parameters: &ast::Parameters,
5054        loc: TextRange,
5055    ) -> CompileResult<bytecode::MakeFunctionFlags> {
5056        let mut funcflags = bytecode::MakeFunctionFlags::new();
5057
5058        // Handle positional defaults
5059        let defaults: Vec<_> = core::iter::empty()
5060            .chain(&parameters.posonlyargs)
5061            .chain(&parameters.args)
5062            .filter_map(|x| x.default.as_deref())
5063            .collect();
5064
5065        if !defaults.is_empty() {
5066            // Compile defaults and build tuple
5067            for default in &defaults {
5068                self.compile_expression(default)?;
5069            }
5070            self.set_source_range(loc);
5071            emit!(
5072                self,
5073                Instruction::BuildTuple {
5074                    count: defaults.len().to_u32()
5075                }
5076            );
5077            funcflags.insert(bytecode::MakeFunctionFlag::Defaults);
5078        }
5079
5080        // Handle keyword-only defaults
5081        let mut kw_with_defaults = vec![];
5082        for kwonlyarg in &parameters.kwonlyargs {
5083            if let Some(default) = &kwonlyarg.default {
5084                kw_with_defaults.push((&kwonlyarg.parameter, default));
5085            }
5086        }
5087
5088        if !kw_with_defaults.is_empty() {
5089            // Compile kwdefaults and build dict
5090            for (arg, default) in &kw_with_defaults {
5091                self.set_source_range(loc);
5092                self.emit_load_const(ConstantData::Str {
5093                    value: self.mangle(arg.name().id()).as_str().into(),
5094                });
5095                self.compile_expression(default)?;
5096            }
5097            self.set_source_range(loc);
5098            emit!(
5099                self,
5100                Instruction::BuildMap {
5101                    count: kw_with_defaults.len().to_u32(),
5102                }
5103            );
5104            funcflags.insert(bytecode::MakeFunctionFlag::KwOnlyDefaults);
5105        }
5106
5107        Ok(funcflags)
5108    }
5109
5110    /// Compile function body and create function object
5111    // = compiler_function_body
5112    fn compile_function_body(
5113        &mut self,
5114        name: &str,
5115        parameters: &ast::Parameters,
5116        body: &[ast::Stmt],
5117        is_async: bool,
5118        funcflags: bytecode::MakeFunctionFlags,
5119        closure_range: TextRange,
5120    ) -> CompileResult<()> {
5121        // Always enter function scope
5122        self.enter_function(name, parameters)?;
5123        self.current_code_info()
5124            .flags
5125            .set(bytecode::CodeFlags::COROUTINE, is_async);
5126
5127        // Set up context
5128        let prev_ctx = self.ctx;
5129        self.ctx = CompileContext {
5130            in_class: prev_ctx.in_class,
5131            func: if is_async {
5132                FunctionContext::AsyncFunction
5133            } else {
5134                FunctionContext::Function
5135            },
5136            // A function starts a new async scope only if it's async
5137            in_async_scope: is_async,
5138        };
5139
5140        // Set qualname
5141        self.set_qualname();
5142
5143        // Handle docstring - store in co_consts[0] if present
5144        let (doc_info, body) = split_doc_with_range(body, &self.opts);
5145        let doc_str = doc_info.as_ref().map(|(doc, _)| doc);
5146        if let Some(doc) = &doc_str {
5147            // Docstring present: store in co_consts[0] and set HAS_DOCSTRING flag
5148            self.current_code_info()
5149                .metadata
5150                .consts
5151                .insert_full(ConstantData::Str {
5152                    value: (*doc).to_string().into(),
5153                });
5154            self.current_code_info().flags |= bytecode::CodeFlags::HAS_DOCSTRING;
5155        }
5156
5157        let start_label = self.use_cpython_function_start_label();
5158
5159        // PEP 479: Wrap generator/coroutine body with StopIteration handler
5160        let is_gen = is_async || self.current_symbol_table().is_generator;
5161        let stop_iteration_block = if is_gen {
5162            let handler_block = self.new_block();
5163            self.insert_cpython_stopiteration_setup_cleanup(handler_block);
5164            self.push_fblock_labels(
5165                FBlockType::StopIteration,
5166                start_label,
5167                ir::InstructionSequenceLabel::NO_LABEL,
5168                FBlockDatum::None,
5169            )?;
5170            Some(handler_block)
5171        } else {
5172            None
5173        };
5174        // Compile body statements
5175        self.compile_statements(body)?;
5176
5177        if stop_iteration_block.is_some() {
5178            self.emit_return_const_no_location(ConstantData::None);
5179        }
5180
5181        // Close StopIteration handler and emit handler code
5182        if let Some(handler_block) = stop_iteration_block {
5183            self.pop_fblock_label(FBlockType::StopIteration, start_label);
5184            self.use_cpython_label_block(handler_block);
5185            emit!(
5186                self,
5187                Instruction::CallIntrinsic1 {
5188                    func: oparg::IntrinsicFunction1::StopIterationError
5189                }
5190            );
5191            self.set_no_location();
5192            emit!(self, Instruction::Reraise { depth: 1u32 });
5193            self.set_no_location();
5194        }
5195        self.emit_return_const_no_location(ConstantData::None);
5196
5197        // Exit scope and create function object
5198        let code = self.exit_scope();
5199        self.ctx = prev_ctx;
5200
5201        self.set_source_range(closure_range);
5202
5203        // Create function object with closure
5204        self.make_closure(code, funcflags)?;
5205
5206        // Note: docstring is now retrieved from co_consts[0] by the VM
5207        // when HAS_DOCSTRING flag is set, so no runtime __doc__ assignment needed
5208
5209        Ok(())
5210    }
5211
5212    /// Compile function annotations as a closure (PEP 649)
5213    /// Returns true if an __annotate__ closure was created
5214    /// Uses the matching annotation symbol table for proper scoping.
5215    fn compile_annotations_closure(
5216        &mut self,
5217        func_name: &str,
5218        parameters: &ast::Parameters,
5219        returns: Option<&ast::Expr>,
5220        func_range: TextRange,
5221    ) -> CompileResult<bool> {
5222        if !self.next_function_annotation_symbol_table_uses_annotations() {
5223            // CPython creates a hidden AnnotationBlock for every function
5224            // signature under `from __future__ import annotations`, including
5225            // an unannotated one.  It still belongs to this function: consume
5226            // it so the next function sees its own block rather than remaining
5227            // pinned to this unused entry.
5228            if self.push_annotation_symbol_table() {
5229                self.pop_annotation_symbol_table();
5230            }
5231            return Ok(false);
5232        }
5233
5234        // Try to enter annotation scope - returns None if no matching symbol table exists.
5235        let Some(saved_ctx) = self.enter_annotation_scope(func_name, func_range)? else {
5236            return Ok(false);
5237        };
5238
5239        // Count annotations
5240        let parameters_iter = parameters
5241            .args
5242            .iter()
5243            .map(|x| &x.parameter)
5244            .chain(parameters.posonlyargs.iter().map(|x| &x.parameter))
5245            .chain(parameters.vararg.as_deref())
5246            .chain(parameters.kwonlyargs.iter().map(|x| &x.parameter))
5247            .chain(parameters.kwarg.as_deref());
5248
5249        let num_annotations =
5250            u32::try_from(parameters_iter.filter(|p| p.annotation.is_some()).count())
5251                .expect("too many annotations")
5252                + u32::from(returns.is_some());
5253
5254        // Compile annotations inside the annotation scope
5255        let parameters_iter = parameters
5256            .args
5257            .iter()
5258            .map(|x| &x.parameter)
5259            .chain(parameters.posonlyargs.iter().map(|x| &x.parameter))
5260            .chain(parameters.vararg.as_deref())
5261            .chain(parameters.kwonlyargs.iter().map(|x| &x.parameter))
5262            .chain(parameters.kwarg.as_deref());
5263
5264        for param in parameters_iter {
5265            if let Some(annotation) = &param.annotation {
5266                self.set_source_range(func_range);
5267                self.emit_load_const(ConstantData::Str {
5268                    value: self.mangle(param.name.id()).as_str().into(),
5269                });
5270                self.compile_annotation(annotation)?;
5271            }
5272        }
5273
5274        // Handle return annotation
5275        if let Some(annotation) = returns {
5276            self.set_source_range(func_range);
5277            self.emit_load_const(ConstantData::Str {
5278                value: "return".into(),
5279            });
5280            self.compile_annotation(annotation)?;
5281        }
5282
5283        // Build the map and return it
5284        self.set_source_range(func_range);
5285        emit!(
5286            self,
5287            Instruction::BuildMap {
5288                count: num_annotations,
5289            }
5290        );
5291        emit!(self, Instruction::ReturnValue);
5292        self.emit_return_const_no_location(ConstantData::None);
5293
5294        // Exit the annotation scope and get the code object
5295        let annotate_code = self.exit_annotation_scope(saved_ctx);
5296
5297        // Make a closure from the code object
5298        self.set_source_range(func_range);
5299        self.make_closure(annotate_code, bytecode::MakeFunctionFlags::new())?;
5300
5301        Ok(true)
5302    }
5303
5304    /// Collect annotated assignments from module/class body in AST order
5305    /// (including nested conditional blocks). This preserves the same walk
5306    /// order as symbol-table construction so the annotation scope's
5307    /// `sub_tables` cursor stays aligned.
5308    fn collect_annotations(
5309        body: &[ast::Stmt],
5310        parent_scope_type: CompilerScope,
5311    ) -> Vec<(&ast::StmtAnnAssign, bool)> {
5312        fn walk<'a>(
5313            stmts: &'a [ast::Stmt],
5314            out: &mut Vec<(&'a ast::StmtAnnAssign, bool)>,
5315            in_conditional_block: bool,
5316            module_scope: bool,
5317        ) {
5318            for stmt in stmts {
5319                match stmt {
5320                    ast::Stmt::AnnAssign(stmt) => {
5321                        out.push((stmt, module_scope || in_conditional_block));
5322                    }
5323                    ast::Stmt::If(ast::StmtIf {
5324                        body,
5325                        elif_else_clauses,
5326                        ..
5327                    }) => {
5328                        walk(body, out, true, module_scope);
5329                        for clause in elif_else_clauses {
5330                            walk(&clause.body, out, true, module_scope);
5331                        }
5332                    }
5333                    ast::Stmt::For(ast::StmtFor { body, orelse, .. })
5334                    | ast::Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
5335                        walk(body, out, true, module_scope);
5336                        walk(orelse, out, true, module_scope);
5337                    }
5338                    ast::Stmt::With(ast::StmtWith { body, .. }) => {
5339                        walk(body, out, true, module_scope);
5340                    }
5341                    ast::Stmt::Try(ast::StmtTry {
5342                        body,
5343                        handlers,
5344                        orelse,
5345                        finalbody,
5346                        ..
5347                    }) => {
5348                        walk(body, out, true, module_scope);
5349                        for handler in handlers {
5350                            let ast::ExceptHandler::ExceptHandler(
5351                                ast::ExceptHandlerExceptHandler { body, .. },
5352                            ) = handler;
5353                            walk(body, out, true, module_scope);
5354                        }
5355                        walk(orelse, out, true, module_scope);
5356                        walk(finalbody, out, true, module_scope);
5357                    }
5358                    ast::Stmt::Match(ast::StmtMatch { cases, .. }) => {
5359                        for case in cases {
5360                            walk(&case.body, out, true, module_scope);
5361                        }
5362                    }
5363                    _ => {}
5364                }
5365            }
5366        }
5367        let mut annotations = Vec::new();
5368        walk(
5369            body,
5370            &mut annotations,
5371            false,
5372            parent_scope_type == CompilerScope::Module,
5373        );
5374        annotations
5375    }
5376
5377    fn compile_annotation_for_symbol_cursor_only(
5378        &mut self,
5379        annotation: &ast::Expr,
5380    ) -> CompileResult<()> {
5381        self.consume_skipped_nested_scopes_in_expr(annotation)
5382    }
5383
5384    /// Compile module-level __annotate__ function (PEP 649)
5385    /// Returns true if __annotate__ was created and stored
5386    fn compile_module_annotate(
5387        &mut self,
5388        body: &[ast::Stmt],
5389        loc: Option<TextRange>,
5390    ) -> CompileResult<bool> {
5391        let loc = loc.unwrap_or(self.current_source_range);
5392        // Get parent scope type BEFORE pushing annotation symbol table.
5393        let parent_scope_type = self.current_symbol_table().typ;
5394        let annotations = Self::collect_annotations(body, parent_scope_type);
5395        let simple_annotation_count = annotations
5396            .iter()
5397            .filter(|(stmt, _)| stmt.simple && matches!(stmt.target.as_ref(), ast::Expr::Name(_)))
5398            .count();
5399
5400        if simple_annotation_count == 0 {
5401            return Ok(false);
5402        }
5403
5404        // Check if we have conditional annotations
5405        let has_conditional = self.current_symbol_table().has_conditional_annotations;
5406
5407        // Try to push annotation symbol table from current scope
5408        if !self.push_current_annotation_symbol_table() {
5409            return Ok(false);
5410        }
5411
5412        // Annotation scopes are never async (even inside async functions)
5413        let saved_ctx = self.ctx;
5414        self.ctx = CompileContext {
5415            in_class: saved_ctx.in_class,
5416            func: FunctionContext::Function,
5417            in_async_scope: false,
5418        };
5419
5420        // Enter annotation scope for code generation
5421        self.set_source_range(loc);
5422        let key = self.symbol_table_stack.len() - 1;
5423        let lineno = self.get_source_line_number().get();
5424        self.enter_scope(
5425            "__annotate__",
5426            CompilerScope::Annotation,
5427            key,
5428            lineno.to_u32(),
5429        )?;
5430
5431        // Keep the internal ".format" name; the final code object
5432        // exposes this parameter as "format".
5433        self.configure_annotation_format_parameter();
5434
5435        // Emit format validation: if format > VALUE_WITH_FAKE_GLOBALS: raise NotImplementedError
5436        self.emit_format_validation();
5437
5438        self.set_source_range(loc);
5439        emit!(self, Instruction::BuildMap { count: 0 });
5440
5441        let mut conditional_idx = 0usize;
5442        for (stmt, is_conditional) in annotations {
5443            let ast::StmtAnnAssign {
5444                target,
5445                annotation,
5446                simple,
5447                range,
5448                ..
5449            } = stmt;
5450            let simple_name = if *simple {
5451                match target.as_ref() {
5452                    ast::Expr::Name(ast::ExprName { id, .. }) => Some(id),
5453                    _ => None,
5454                }
5455            } else {
5456                None
5457            };
5458
5459            if simple_name.is_none() {
5460                if !self.future_annotations {
5461                    self.compile_annotation_for_symbol_cursor_only(annotation)?;
5462                }
5463                continue;
5464            }
5465
5466            let not_set_block = (has_conditional && is_conditional).then(|| self.new_block());
5467            let not_set_label = (!has_conditional || !is_conditional)
5468                .then(|| self.current_code_info().new_instr_sequence_label());
5469            let name = simple_name.expect("missing simple annotation name");
5470
5471            if let Some(not_set_block) = not_set_block {
5472                self.set_source_range(*range);
5473                self.emit_load_const(ConstantData::Integer {
5474                    value: conditional_idx.into(),
5475                });
5476                conditional_idx += 1;
5477                if parent_scope_type == CompilerScope::Class {
5478                    let idx = self.get_free_var_index("__conditional_annotations__");
5479                    emit!(self, Instruction::LoadDeref { i: idx });
5480                } else {
5481                    let cond_annotations_name = self.name("__conditional_annotations__");
5482                    self.emit_load_global(cond_annotations_name, false);
5483                }
5484                emit!(
5485                    self,
5486                    Instruction::ContainsOp {
5487                        invert: bytecode::Invert::No
5488                    }
5489                );
5490                emit!(
5491                    self,
5492                    Instruction::PopJumpIfFalse {
5493                        delta: not_set_block
5494                    }
5495                );
5496            }
5497
5498            self.compile_annotation(annotation)?;
5499            self.set_source_range(*range);
5500            emit!(self, Instruction::Copy { i: 2 });
5501            self.emit_load_const(ConstantData::Str {
5502                value: self.mangle(name).as_str().into(),
5503            });
5504            self.set_source_range(loc);
5505            emit!(self, Instruction::StoreSubscr);
5506
5507            if let Some(not_set_block) = not_set_block {
5508                self.use_cpython_label_block(not_set_block);
5509            } else if let Some(not_set_label) = not_set_label {
5510                let result = self
5511                    .current_code_info()
5512                    .use_raw_instr_sequence_label(not_set_label);
5513                unwrap_internal(self, result);
5514            }
5515        }
5516
5517        self.set_source_range(loc);
5518        emit!(self, Instruction::ReturnValue);
5519        self.emit_return_const_no_location(ConstantData::None);
5520
5521        // Exit annotation scope - pop symbol table, restore to parent's annotation_block, and get code
5522        let annotation_table = self.pop_symbol_table();
5523        // Restore annotation_block to module's symbol table
5524        self.symbol_table_stack
5525            .last_mut()
5526            .expect("no module symbol table")
5527            .annotation_block = Some(Box::new(annotation_table));
5528        // Restore context
5529        self.ctx = saved_ctx;
5530        // Exit code scope
5531        let pop = self.code_stack.pop();
5532        let mut annotate_code = unwrap_internal(
5533            self,
5534            compiler_unwrap_option(self, pop).finalize_code(&self.opts),
5535        );
5536        Self::expose_annotation_format_parameter(&mut annotate_code);
5537
5538        // Make a closure from the code object
5539        self.set_source_range(loc);
5540        self.make_closure(annotate_code, bytecode::MakeFunctionFlags::new())?;
5541
5542        // Store as __annotate_func__ for classes, __annotate__ for modules
5543        let name = if parent_scope_type == CompilerScope::Class {
5544            "__annotate_func__"
5545        } else {
5546            "__annotate__"
5547        }
5548        .into();
5549        self.set_source_range(loc);
5550        self.store_name(&name)?;
5551
5552        Ok(true)
5553    }
5554
5555    // = compiler_function
5556    #[expect(clippy::too_many_arguments, reason = "ignore warning for now")]
5557    fn compile_function_def(
5558        &mut self,
5559        name: &Name,
5560        parameters: &ast::Parameters,
5561        body: &[ast::Stmt],
5562        decorator_list: &[ast::Decorator],
5563        returns: Option<&ast::Expr>, // TODO: use type hint somehow..
5564        is_async: bool,
5565        type_params: Option<&ast::TypeParams>,
5566        preserve_value_before_store: bool,
5567    ) -> CompileResult<()> {
5568        // CPython's FunctionDef/AsyncFunctionDef LOC(s) starts at the
5569        // definition line even when decorators are present.
5570        let stmt_source_range = self.current_source_range;
5571        let def_source_range = crate::decorated_definition_range(
5572            &self.source_file,
5573            stmt_source_range,
5574            decorator_list,
5575            if is_async { "async def " } else { "def " },
5576        );
5577
5578        // The symtable visits defaults before decorators, but
5579        // codegen_function() emits decorators first. Keep the symbol table in
5580        // symtable order and only move the codegen cursor while compiling
5581        // decorators.
5582        let defaults_symbol_table_cursors = self.current_symbol_table_cursors();
5583        self.consume_skipped_nested_scopes_in_parameter_defaults(parameters)?;
5584        self.prepare_decorators(decorator_list)?;
5585        let after_decorators_symbol_table_cursors = self.current_symbol_table_cursors();
5586        self.set_symbol_table_cursors(defaults_symbol_table_cursors);
5587
5588        // The first decorator line is used for code objects created by
5589        // this definition, but LOC(s) for the surrounding instructions.
5590        let firstlineno_range = decorator_list
5591            .first()
5592            .map_or(stmt_source_range, |decorator| decorator.expression.range());
5593
5594        // compile defaults and return funcflags
5595        let funcflags = self.compile_default_arguments(parameters, def_source_range)?;
5596        self.set_symbol_table_cursors(after_decorators_symbol_table_cursors);
5597
5598        let is_generic = type_params.is_some();
5599        let mut num_typeparam_args = 0u32;
5600
5601        // Save context before entering TypeParams scope
5602        let saved_ctx = self.ctx;
5603
5604        if is_generic {
5605            // Count args to pass to type params scope
5606            if funcflags.contains(&bytecode::MakeFunctionFlag::Defaults) {
5607                num_typeparam_args += 1;
5608            }
5609            if funcflags.contains(&bytecode::MakeFunctionFlag::KwOnlyDefaults) {
5610                num_typeparam_args += 1;
5611            }
5612            if num_typeparam_args == 2 {
5613                self.set_source_range(def_source_range);
5614                emit!(self, Instruction::Swap { i: 2 });
5615            }
5616
5617            // Enter type params scope
5618            let type_params_name = format!("<generic parameters of {name}>");
5619            self.set_source_range(firstlineno_range);
5620            self.push_output(
5621                bytecode::CodeFlags::OPTIMIZED | bytecode::CodeFlags::NEWLOCALS,
5622                0,
5623                num_typeparam_args,
5624                0,
5625                &type_params_name,
5626            )?;
5627
5628            // TypeParams scope is function-like
5629            self.ctx = CompileContext {
5630                in_class: saved_ctx.in_class,
5631                func: FunctionContext::Function,
5632                in_async_scope: false,
5633            };
5634
5635            // Add parameter names to varnames for the type params scope
5636            // These will be passed as arguments when the closure is called.
5637            // `.defaults` is there whether or not the function has any: the
5638            // symbol table gives every generic function's type params scope
5639            // one. `.kwdefaults` only appears when it is really passed.
5640            let current_info = self.current_code_info();
5641            current_info
5642                .metadata
5643                .varnames
5644                .insert(".defaults".to_owned());
5645            if funcflags.contains(&bytecode::MakeFunctionFlag::KwOnlyDefaults) {
5646                current_info
5647                    .metadata
5648                    .varnames
5649                    .insert(".kwdefaults".to_owned());
5650            }
5651
5652            // Compile type parameters
5653            self.compile_type_params(type_params.unwrap())?;
5654
5655            // Load defaults/kwdefaults with LOAD_FAST
5656            self.set_source_range(def_source_range);
5657            for i in 0..num_typeparam_args {
5658                let var_num = oparg::VarNum::from(i);
5659                emit!(self, Instruction::LoadFast { var_num });
5660            }
5661        }
5662
5663        // Compile annotations as closure (PEP 649)
5664        let mut annotations_flag = bytecode::MakeFunctionFlags::new();
5665        if self.compile_annotations_closure(name, parameters, returns, def_source_range)? {
5666            annotations_flag.insert(bytecode::MakeFunctionFlag::Annotate);
5667        }
5668
5669        // Compile function body. codegen_function() uses the first
5670        // decorator line for co_firstlineno, but LOC(s) for MAKE_FUNCTION.
5671        self.set_source_range(firstlineno_range);
5672        let final_funcflags = funcflags | annotations_flag;
5673        self.compile_function_body(
5674            name,
5675            parameters,
5676            body,
5677            is_async,
5678            final_funcflags,
5679            def_source_range,
5680        )?;
5681
5682        // Handle type params if present
5683        if is_generic {
5684            // SWAP to get function on top
5685            // Stack: [type_params_tuple, function] -> [function, type_params_tuple]
5686            self.set_source_range(def_source_range);
5687            emit!(self, Instruction::Swap { i: 2 });
5688
5689            // Call INTRINSIC_SET_FUNCTION_TYPE_PARAMS
5690            self.set_source_range(def_source_range);
5691            emit!(
5692                self,
5693                Instruction::CallIntrinsic2 {
5694                    func: bytecode::IntrinsicFunction2::SetFunctionTypeParams,
5695                }
5696            );
5697
5698            // Return the function object from type params scope
5699            emit!(self, Instruction::ReturnValue);
5700            self.set_no_location();
5701
5702            // Set argcount for type params scope
5703            self.current_code_info().metadata.argcount = num_typeparam_args;
5704            self.current_code_info().nparams = num_typeparam_args as usize;
5705
5706            // Exit type params scope and create closure
5707            let type_params_code = self.exit_scope();
5708            self.ctx = saved_ctx;
5709
5710            // Make closure for type params code
5711            self.set_source_range(def_source_range);
5712            self.make_closure(type_params_code, bytecode::MakeFunctionFlags::new())?;
5713
5714            if num_typeparam_args > 0 {
5715                self.set_source_range(def_source_range);
5716                emit!(
5717                    self,
5718                    Instruction::Swap {
5719                        i: num_typeparam_args + 1
5720                    }
5721                );
5722                self.set_source_range(def_source_range);
5723                emit!(
5724                    self,
5725                    Instruction::Call {
5726                        argc: num_typeparam_args - 1
5727                    }
5728                );
5729            } else {
5730                // Stack: [closure]
5731                self.set_source_range(def_source_range);
5732                emit!(self, Instruction::PushNull);
5733                // Stack: [closure, NULL]
5734                self.set_source_range(def_source_range);
5735                emit!(self, Instruction::Call { argc: 0 });
5736            }
5737        }
5738
5739        // Apply decorators
5740        self.apply_decorators(decorator_list);
5741
5742        // Store the function
5743        self.set_source_range(def_source_range);
5744        if preserve_value_before_store {
5745            emit!(self, Instruction::Copy { i: 1 });
5746        }
5747        self.store_name(name)?;
5748
5749        Ok(())
5750    }
5751
5752    /// Determines if a variable should be CELL or FREE type
5753    // = get_ref_type
5754    fn get_ref_type(&self, name: &Name) -> Result<SymbolScope, CodegenErrorType> {
5755        let table = self.symbol_table_stack.last().unwrap();
5756
5757        // Special handling for __class__, __classdict__, and __conditional_annotations__ in class scope
5758        // This should only apply when we're actually IN a class body,
5759        // not when we're in a method nested inside a class.
5760        if table.typ == CompilerScope::Class
5761            && (name == "__class__"
5762                || name == "__classdict__"
5763                || name == "__conditional_annotations__")
5764        {
5765            return Ok(SymbolScope::Cell);
5766        }
5767        match table.lookup(name) {
5768            Some(symbol) => match symbol.scope {
5769                SymbolScope::Cell => Ok(SymbolScope::Cell),
5770                SymbolScope::Free => Ok(SymbolScope::Free),
5771                _ if symbol.flags.contains(SymbolFlags::DEF_FREE_CLASS) => Ok(SymbolScope::Free),
5772                _ => Err(CodegenErrorType::SyntaxError(format!(
5773                    "get_ref_type: invalid scope for '{name}'"
5774                ))),
5775            },
5776            None => Err(CodegenErrorType::SyntaxError(format!(
5777                "get_ref_type: cannot find symbol '{name}'"
5778            ))),
5779        }
5780    }
5781
5782    /// Loads closure variables if needed and creates a function object
5783    // = compiler_make_closure
5784    fn make_closure(
5785        &mut self,
5786        code: CodeObject,
5787        flags: bytecode::MakeFunctionFlags,
5788    ) -> CompileResult<()> {
5789        // Handle free variables (closure)
5790        let has_freevars = !code.freevars.is_empty();
5791        if has_freevars {
5792            // Build closure tuple by loading free variables
5793
5794            for var in &code.freevars {
5795                // Special case: If a class contains a method with a
5796                // free variable that has the same name as a method,
5797                // the name will be considered free *and* local in the
5798                // class. It should be handled by the closure, as
5799                // well as by the normal name lookup logic.
5800
5801                // Get reference type using our get_ref_type function
5802                let ref_type = self
5803                    .get_ref_type(&var.as_str().into())
5804                    .map_err(|e| self.error(e))?;
5805
5806                // Get parent code info
5807                let parent_code = self.code_stack.last().unwrap();
5808                let cellvars_len = parent_code.metadata.cellvars.len();
5809
5810                // Look up the variable index based on reference type
5811                let idx = match ref_type {
5812                    SymbolScope::Cell => parent_code
5813                        .metadata
5814                        .cellvars
5815                        .get_index_of(var)
5816                        .or_else(|| {
5817                            parent_code
5818                                .metadata
5819                                .freevars
5820                                .get_index_of(var)
5821                                .map(|i| i + cellvars_len)
5822                        })
5823                        .ok_or_else(|| {
5824                            self.error(CodegenErrorType::SyntaxError(format!(
5825                                "compiler_make_closure: cannot find '{var}' in parent vars",
5826                            )))
5827                        })?,
5828                    SymbolScope::Free => parent_code
5829                        .metadata
5830                        .freevars
5831                        .get_index_of(var)
5832                        .map(|i| i + cellvars_len)
5833                        .or_else(|| parent_code.metadata.cellvars.get_index_of(var))
5834                        .ok_or_else(|| {
5835                            self.error(CodegenErrorType::SyntaxError(format!(
5836                                "compiler_make_closure: cannot find '{var}' in parent vars",
5837                            )))
5838                        })?,
5839                    _ => {
5840                        return Err(self.error(CodegenErrorType::SyntaxError(format!(
5841                            "compiler_make_closure: unexpected ref_type {ref_type:?} for '{var}'",
5842                        ))));
5843                    }
5844                };
5845
5846                emit!(self, PseudoInstruction::LoadClosure { i: idx.to_u32() });
5847            }
5848
5849            // Build tuple of closure variables
5850            emit!(
5851                self,
5852                Instruction::BuildTuple {
5853                    count: code.freevars.len().to_u32(),
5854                }
5855            );
5856        }
5857
5858        // load code object and create function
5859        self.emit_load_const(ConstantData::Code {
5860            code: Box::new(code),
5861        });
5862
5863        // Create function with no flags
5864        emit!(self, Instruction::MakeFunction);
5865
5866        // Now set attributes one by one using SET_FUNCTION_ATTRIBUTE
5867        // Note: The order matters! Values must be on stack before calling SET_FUNCTION_ATTRIBUTE
5868
5869        // Set closure if needed
5870        if has_freevars {
5871            emit!(
5872                self,
5873                Instruction::SetFunctionAttribute {
5874                    flag: bytecode::MakeFunctionFlag::Closure
5875                }
5876            );
5877        }
5878
5879        // Set annotations if present
5880        if flags.contains(&bytecode::MakeFunctionFlag::Annotations) {
5881            emit!(
5882                self,
5883                Instruction::SetFunctionAttribute {
5884                    flag: bytecode::MakeFunctionFlag::Annotations
5885                }
5886            );
5887        }
5888
5889        // Set __annotate__ closure if present (PEP 649)
5890        if flags.contains(&bytecode::MakeFunctionFlag::Annotate) {
5891            emit!(
5892                self,
5893                Instruction::SetFunctionAttribute {
5894                    flag: bytecode::MakeFunctionFlag::Annotate
5895                }
5896            );
5897        }
5898
5899        // Set kwdefaults if present
5900        if flags.contains(&bytecode::MakeFunctionFlag::KwOnlyDefaults) {
5901            emit!(
5902                self,
5903                Instruction::SetFunctionAttribute {
5904                    flag: bytecode::MakeFunctionFlag::KwOnlyDefaults
5905                }
5906            );
5907        }
5908
5909        // Set defaults if present
5910        if flags.contains(&bytecode::MakeFunctionFlag::Defaults) {
5911            emit!(
5912                self,
5913                Instruction::SetFunctionAttribute {
5914                    flag: bytecode::MakeFunctionFlag::Defaults
5915                }
5916            );
5917        }
5918
5919        Ok(())
5920    }
5921
5922    // Python/compile.c _PyCompile_MaybeAddStaticAttributeToClass
5923    fn maybe_add_static_attribute_to_class(&mut self, value: &ast::Expr, attr: &str) {
5924        if !matches!(value, ast::Expr::Name(n) if n.id.as_str() == "self") {
5925            return;
5926        }
5927        if let Some(class_unit) = self
5928            .code_stack
5929            .iter_mut()
5930            .rev()
5931            .find(|unit| unit.static_attributes.is_some())
5932        {
5933            class_unit
5934                .static_attributes
5935                .as_mut()
5936                .unwrap()
5937                .insert(attr.to_owned());
5938        }
5939    }
5940
5941    /// Compile the class body into a code object
5942    // = compiler_class_body
5943    fn compile_class_body(
5944        &mut self,
5945        name: &str,
5946        body: &[ast::Stmt],
5947        type_params: Option<&ast::TypeParams>,
5948        firstlineno: u32,
5949    ) -> CompileResult<CodeObject> {
5950        // 1. Enter class scope
5951        let key = self.symbol_table_stack.len();
5952        self.push_symbol_table_matching(CompilerScope::Class, name)?;
5953        self.enter_scope(name, CompilerScope::Class, key, firstlineno)?;
5954
5955        // Set qualname using the new method
5956        let qualname = self.set_qualname();
5957
5958        // For class scopes, set u_private to the class name for name mangling
5959        self.code_stack.last_mut().unwrap().private = Some(name.to_owned());
5960
5961        // 2. Set up class namespace
5962        let (doc_str, body) = split_doc_with_range(body, &self.opts);
5963        let class_body_prefix_range = self.source_line_start_range(firstlineno);
5964        self.set_source_range(class_body_prefix_range);
5965
5966        // Load __name__ and store as __module__
5967        self.load_name(&"__name__".into())?;
5968        self.store_name(&"__module__".into())?;
5969
5970        // Store __qualname__
5971        self.emit_load_const(ConstantData::Str {
5972            value: qualname.into(),
5973        });
5974        self.store_name(&"__qualname__".into())?;
5975
5976        // Store __firstlineno__ before __doc__
5977        self.emit_load_const(ConstantData::Integer {
5978            value: BigInt::from(firstlineno),
5979        });
5980        self.store_name(&"__firstlineno__".into())?;
5981
5982        // Set __type_params__ from the enclosing type-params closure when
5983        // compiling a generic class body.
5984        if type_params.is_some() {
5985            self.load_name(&".type_params".into())?;
5986            self.store_name(&"__type_params__".into())?;
5987        }
5988
5989        // PEP 649: Initialize __classdict__ after synthetic generic-class
5990        // setup so nested generic classes match CPython's prologue order.
5991        if self.current_symbol_table().needs_classdict {
5992            emit!(self, Instruction::LoadLocals);
5993            let classdict_idx = self.get_cell_var_index("__classdict__");
5994            emit!(self, Instruction::StoreDeref { i: classdict_idx });
5995        }
5996
5997        // Handle class annotation bookkeeping in CPython order.
5998        let annotations_used = self.current_symbol_table().annotations_used;
5999        if Self::scope_needs_conditional_annotations_cell(self.current_symbol_table()) {
6000            emit!(self, Instruction::BuildSet { count: 0 });
6001            self.store_name(&"__conditional_annotations__".into())?;
6002        }
6003
6004        if self.future_annotations && annotations_used {
6005            emit!(self, Instruction::SetupAnnotations);
6006        }
6007
6008        // Store __doc__ only if there's an explicit docstring.
6009        if let Some((doc, range)) = doc_str {
6010            let saved_range = self.current_source_range;
6011            self.set_source_range(range);
6012            self.emit_load_const(ConstantData::Str { value: doc.into() });
6013            self.store_name(&"__doc__".into())?;
6014            self.set_no_location();
6015            self.set_source_range(saved_range);
6016        }
6017
6018        // 3. Compile the class body
6019        self.compile_statements(body)?;
6020
6021        if annotations_used && !self.future_annotations {
6022            self.compile_module_annotate(body, Some(class_body_prefix_range))?;
6023        }
6024
6025        // 4. Handle __classcell__ if needed
6026        let classcell_idx = self
6027            .code_stack
6028            .last_mut()
6029            .unwrap()
6030            .metadata
6031            .cellvars
6032            .iter()
6033            .position(|var| *var == "__class__");
6034
6035        // Emit __static_attributes__ tuple
6036        {
6037            let mut attrs: Vec<String> = self
6038                .code_stack
6039                .last()
6040                .unwrap()
6041                .static_attributes
6042                .as_ref()
6043                .map(|s| s.iter().cloned().collect())
6044                .unwrap_or_default();
6045            attrs.sort();
6046            self.emit_load_const(ConstantData::Tuple {
6047                elements: attrs
6048                    .into_iter()
6049                    .map(|s| ConstantData::Str { value: s.into() })
6050                    .collect(),
6051            });
6052            self.set_no_location();
6053            self.store_name(&"__static_attributes__".into())?;
6054            self.set_no_location();
6055        }
6056
6057        // Store __classdictcell__ if __classdict__ is a cell variable
6058        if self.current_symbol_table().needs_classdict {
6059            let classdict_idx = u32::from(self.get_cell_var_index("__classdict__"));
6060            emit!(self, PseudoInstruction::LoadClosure { i: classdict_idx });
6061            self.set_no_location();
6062            self.store_name(&"__classdictcell__".into())?;
6063            self.set_no_location();
6064        }
6065
6066        if let Some(classcell_idx) = classcell_idx {
6067            emit!(
6068                self,
6069                PseudoInstruction::LoadClosure {
6070                    i: classcell_idx.to_u32()
6071                }
6072            );
6073            self.set_no_location();
6074            emit!(self, Instruction::Copy { i: 1 });
6075            self.set_no_location();
6076            self.store_name(&"__classcell__".into())?;
6077            self.set_no_location();
6078        } else {
6079            self.emit_load_const(ConstantData::None);
6080            self.set_no_location();
6081        }
6082
6083        // Return the class namespace
6084        self.emit_return_value();
6085        self.set_no_location();
6086        self.emit_return_const_no_location(ConstantData::None);
6087
6088        // Exit scope and return the code object
6089        Ok(self.exit_scope())
6090    }
6091
6092    fn compile_class_def(
6093        &mut self,
6094        name: &Name,
6095        body: &[ast::Stmt],
6096        decorator_list: &[ast::Decorator],
6097        type_params: Option<&ast::TypeParams>,
6098        arguments: Option<&ast::Arguments>,
6099        preserve_value_before_store: bool,
6100    ) -> CompileResult<()> {
6101        // CPython's ClassDef LOC(s) starts at the class line even when
6102        // decorators are present.
6103        let stmt_source_range = self.current_source_range;
6104        let class_source_range = crate::decorated_definition_range(
6105            &self.source_file,
6106            stmt_source_range,
6107            decorator_list,
6108            "class ",
6109        );
6110        self.prepare_decorators(decorator_list)?;
6111
6112        let is_generic = type_params.is_some();
6113        let firstlineno_range = decorator_list
6114            .first()
6115            .map_or(stmt_source_range, |decorator| decorator.expression.range());
6116        #[expect(clippy::map_unwrap_or, reason = "Changing this will not compile")]
6117        let firstlineno = decorator_list
6118            .first()
6119            .map(|decorator| {
6120                self.source_file
6121                    .to_source_code()
6122                    .line_index(decorator.expression.range().start())
6123                    .get()
6124                    .to_u32()
6125            })
6126            .unwrap_or_else(|| self.get_source_line_number().get().to_u32());
6127
6128        // Save context before entering any scopes
6129        let saved_ctx = self.ctx;
6130
6131        // Step 1: If generic, enter type params scope and compile type params
6132        if is_generic {
6133            let type_params_name = format!("<generic parameters of {name}>");
6134            self.set_source_range(firstlineno_range);
6135            self.push_output(
6136                bytecode::CodeFlags::OPTIMIZED | bytecode::CodeFlags::NEWLOCALS,
6137                0,
6138                0,
6139                0,
6140                &type_params_name,
6141            )?;
6142
6143            // Set private name for name mangling
6144            self.code_stack.last_mut().unwrap().private = Some(name.as_str().to_owned());
6145
6146            // TypeParams scope is function-like
6147            self.ctx = CompileContext {
6148                in_class: saved_ctx.in_class,
6149                func: FunctionContext::Function,
6150                in_async_scope: false,
6151            };
6152
6153            // Compile type parameters and store them in the synthetic cell that
6154            // generic class bodies close over.
6155            self.compile_type_params(type_params.unwrap())?;
6156            self.set_source_range(class_source_range);
6157            self.store_name(&".type_params".into())?;
6158        }
6159
6160        // Step 2: Compile class body (always done, whether generic or not)
6161        let prev_ctx = self.ctx;
6162        self.ctx = CompileContext {
6163            func: FunctionContext::NoFunction,
6164            in_class: true,
6165            in_async_scope: false,
6166        };
6167        let pre_class_body_symbol_table_cursors = self.current_symbol_table_cursors();
6168        let class_code = self.compile_class_body(name, body, type_params, firstlineno)?;
6169        let post_class_body_symbol_table_cursors = self.current_symbol_table_cursors();
6170        self.set_symbol_table_cursors(pre_class_body_symbol_table_cursors);
6171        self.ctx = prev_ctx;
6172        self.set_source_range(class_source_range);
6173
6174        // Step 3: Generate the rest of the code for the call
6175        if is_generic {
6176            // Generate class creation code
6177            emit!(self, Instruction::LoadBuildClass);
6178            emit!(self, Instruction::PushNull);
6179
6180            // Create the class body function with the .type_params closure
6181            // captured through the class code object's freevars.
6182            self.make_closure(class_code, bytecode::MakeFunctionFlags::new())?;
6183            self.emit_load_const(ConstantData::Str {
6184                value: name.as_str().into(),
6185            });
6186
6187            // Create .generic_base after the class function and name are on the
6188            // stack so the remaining call shape matches CPython's ordering.
6189            self.set_source_range(class_source_range);
6190            self.load_name(&".type_params".into())?;
6191            emit!(
6192                self,
6193                Instruction::CallIntrinsic1 {
6194                    func: bytecode::IntrinsicFunction1::SubscriptGeneric
6195                }
6196            );
6197            self.set_source_range(class_source_range);
6198            self.store_name(&".generic_base".into())?;
6199
6200            let (bases, keywords) = arguments.map_or((&[][..], &[][..]), |args| {
6201                (&args.args[..], &args.keywords[..])
6202            });
6203            self.codegen_call_helper_impl(
6204                2,
6205                bases,
6206                keywords,
6207                class_source_range,
6208                None,
6209                Some(&".generic_base".into()),
6210            )?;
6211
6212            // Return the created class
6213            self.emit_return_value();
6214            self.set_no_location();
6215
6216            // Exit type params scope and wrap in function
6217            let type_params_code = self.exit_scope();
6218            self.ctx = saved_ctx;
6219
6220            // Execute the type params function
6221            self.set_source_range(class_source_range);
6222            self.make_closure(type_params_code, bytecode::MakeFunctionFlags::new())?;
6223            self.set_source_range(class_source_range);
6224            emit!(self, Instruction::PushNull);
6225            self.set_source_range(class_source_range);
6226            emit!(self, Instruction::Call { argc: 0 });
6227        } else {
6228            // Non-generic class: standard path
6229            emit!(self, Instruction::LoadBuildClass);
6230            emit!(self, Instruction::PushNull);
6231
6232            // Create class function with closure
6233            self.make_closure(class_code, bytecode::MakeFunctionFlags::new())?;
6234            self.emit_load_const(ConstantData::Str {
6235                value: name.as_str().into(),
6236            });
6237
6238            if let Some(arguments) = arguments {
6239                self.codegen_call_helper(2, arguments, class_source_range, None)?;
6240            } else {
6241                self.set_source_range(class_source_range);
6242                emit!(self, Instruction::Call { argc: 2 });
6243            }
6244            self.set_symbol_table_cursors(post_class_body_symbol_table_cursors);
6245        }
6246
6247        // Step 4: Apply decorators and store (common to both paths)
6248        self.apply_decorators(decorator_list);
6249        self.set_source_range(class_source_range);
6250        if preserve_value_before_store {
6251            emit!(self, Instruction::Copy { i: 1 });
6252        }
6253        self.store_name(name)
6254    }
6255
6256    /// Compile an if statement with constant condition elimination.
6257    /// = compiler_if in CPython codegen.c
6258    fn compile_if(
6259        &mut self,
6260        test: &ast::Expr,
6261        body: &[ast::Stmt],
6262        elif_else_clauses: &[ast::ElifElseClause],
6263        stmt_range: TextRange,
6264    ) -> CompileResult<()> {
6265        let end_block = self.new_block();
6266        let next_block = if elif_else_clauses.is_empty() {
6267            end_block
6268        } else {
6269            self.new_block()
6270        };
6271
6272        self.compile_jump_if_inner(test, false, next_block, Some(stmt_range))?;
6273        self.compile_statements(body)?;
6274
6275        let Some((clause, rest)) = elif_else_clauses.split_first() else {
6276            self.use_cpython_label_block(end_block);
6277            return Ok(());
6278        };
6279
6280        emit!(
6281            self,
6282            PseudoInstruction::JumpNoInterrupt { delta: end_block }
6283        );
6284        self.set_no_location();
6285        self.use_cpython_label_block(next_block);
6286
6287        if let Some(test) = &clause.test {
6288            self.compile_if(test, &clause.body, rest, clause.range)?;
6289        } else {
6290            debug_assert!(rest.is_empty());
6291            self.compile_statements(&clause.body)?;
6292        }
6293        self.use_cpython_label_block(end_block);
6294        Ok(())
6295    }
6296
6297    fn compile_while(
6298        &mut self,
6299        test: &ast::Expr,
6300        body: &[ast::Stmt],
6301        orelse: &[ast::Stmt],
6302        while_range: TextRange,
6303    ) -> CompileResult<()> {
6304        self.enter_conditional_block();
6305
6306        let loop_block = self.new_block();
6307        let end_block = self.new_block();
6308        let anchor_block = self.new_block();
6309        let loop_label = self.instr_sequence_label_for_block(loop_block);
6310        let end_label = self.instr_sequence_label_for_block(end_block);
6311        self.use_cpython_label_block(loop_block);
6312        self.push_fblock_labels(
6313            FBlockType::WhileLoop,
6314            loop_label,
6315            end_label,
6316            FBlockDatum::None,
6317        )?;
6318        self.compile_jump_if_inner(test, false, anchor_block, Some(while_range))?;
6319
6320        self.compile_loop_body_statements(body)?;
6321        emit!(self, PseudoInstruction::Jump { delta: loop_block });
6322        self.set_no_location();
6323
6324        self.pop_fblock_label(FBlockType::WhileLoop, loop_label);
6325        self.use_cpython_label_block(anchor_block);
6326        self.compile_statements(orelse)?;
6327        self.use_cpython_label_block(end_block);
6328
6329        self.leave_conditional_block();
6330        Ok(())
6331    }
6332
6333    fn compile_with(
6334        &mut self,
6335        items: &[ast::WithItem],
6336        body: &[ast::Stmt],
6337        is_async: bool,
6338    ) -> CompileResult<()> {
6339        self.enter_conditional_block();
6340        let result = self.compile_with_inner(items, body, is_async);
6341        self.leave_conditional_block();
6342        result
6343    }
6344
6345    fn compile_with_inner(
6346        &mut self,
6347        items: &[ast::WithItem],
6348        body: &[ast::Stmt],
6349        is_async: bool,
6350    ) -> CompileResult<()> {
6351        // Python 3.12+ style with statement:
6352        //
6353        // BEFORE_WITH          # TOS: ctx_mgr -> [__exit__, __enter__ result]
6354        // L1: STORE_NAME f     # exception table: L1 to L2 -> L3 [1] lasti
6355        // L2: ... body ...
6356        //     LOAD_CONST None  # normal exit
6357        //     LOAD_CONST None
6358        //     LOAD_CONST None
6359        //     CALL 2           # __exit__(None, None, None)
6360        //     POP_TOP
6361        //     JUMP after
6362        // L3: PUSH_EXC_INFO    # exception handler
6363        //     WITH_EXCEPT_START # call __exit__(type, value, tb), push result
6364        //     TO_BOOL
6365        //     POP_JUMP_IF_TRUE suppress
6366        //     RERAISE 2
6367        // suppress:
6368        //     POP_TOP          # pop exit result
6369        // L5: POP_EXCEPT
6370        //     POP_TOP          # pop __exit__
6371        //     POP_TOP          # pop prev_exc (or lasti depending on layout)
6372        //     JUMP after
6373        // L6: COPY 3           # cleanup handler for reraise
6374        //     POP_EXCEPT
6375        //     RERAISE 1
6376        // after: ...
6377
6378        let Some((item, items)) = items.split_first() else {
6379            return Err(self.error(CodegenErrorType::EmptyWithItems));
6380        };
6381        let with_range = item.context_expr.range();
6382
6383        let body_block = self.new_block();
6384        let exc_handler_block = self.new_block();
6385        let after_block = self.new_block();
6386        let cleanup_block = self.new_block();
6387
6388        // Compile context expression and load __enter__/__exit__ methods
6389        self.compile_expression(&item.context_expr)?;
6390        self.set_source_range(with_range);
6391
6392        // Stack: [cm]
6393        emit!(self, Instruction::Copy { i: 1 }); // [cm, cm]
6394
6395        if is_async {
6396            if self.ctx.func != FunctionContext::AsyncFunction
6397                && !self.allows_top_level_await_in_current_context()
6398            {
6399                return Err(self.error(CodegenErrorType::InvalidAsyncWith));
6400            }
6401            // Load __aexit__ and __aenter__, then call __aenter__
6402            emit!(
6403                self,
6404                Instruction::LoadSpecial {
6405                    method: SpecialMethod::AExit
6406                }
6407            ); // [cm, aexit_func, self_ae]
6408            emit!(self, Instruction::Swap { i: 2 }); // [cm, self_ae, aexit_func]
6409            emit!(self, Instruction::Swap { i: 3 }); // [aexit_func, self_ae, cm]
6410            emit!(
6411                self,
6412                Instruction::LoadSpecial {
6413                    method: SpecialMethod::AEnter
6414                }
6415            ); // [aexit_func, self_ae, aenter_func, self_an]
6416            emit!(self, Instruction::Call { argc: 0 }); // [aexit_func, self_ae, awaitable]
6417            emit!(self, Instruction::GetAwaitable { r#where: 1 });
6418            self.emit_load_const(ConstantData::None);
6419            let _ = self.compile_yield_from_sequence(true);
6420        } else {
6421            // Load __exit__ and __enter__, then call __enter__
6422            emit!(
6423                self,
6424                Instruction::LoadSpecial {
6425                    method: SpecialMethod::Exit
6426                }
6427            ); // [cm, exit_func, self_exit]
6428            emit!(self, Instruction::Swap { i: 2 }); // [cm, self_exit, exit_func]
6429            emit!(self, Instruction::Swap { i: 3 }); // [exit_func, self_exit, cm]
6430            emit!(
6431                self,
6432                Instruction::LoadSpecial {
6433                    method: SpecialMethod::Enter
6434                }
6435            ); // [exit_func, self_exit, enter_func, self_enter]
6436            emit!(self, Instruction::Call { argc: 0 }); // [exit_func, self_exit, result]
6437        }
6438
6439        // Stack: [..., __exit__, enter_result]
6440        // Push fblock for exception table - handler goes to exc_handler_block
6441        // preserve_lasti=true for with statements
6442        emit!(
6443            self,
6444            PseudoInstruction::SetupWith {
6445                delta: exc_handler_block
6446            }
6447        );
6448        self.use_cpython_label_block(body_block);
6449        let fblock_type = if is_async {
6450            FBlockType::AsyncWith
6451        } else {
6452            FBlockType::With
6453        };
6454        let body_label = self.instr_sequence_label_for_block(body_block);
6455        let exc_handler_label = self.instr_sequence_label_for_block(exc_handler_block);
6456        self.push_fblock_labels(
6457            fblock_type,
6458            body_label,
6459            exc_handler_label,
6460            FBlockDatum::None,
6461        )?;
6462
6463        // Store or pop the enter result
6464        match &item.optional_vars {
6465            Some(var) => {
6466                self.set_source_range(var.range());
6467                self.compile_store(var)?;
6468            }
6469            None => {
6470                emit!(self, Instruction::PopTop);
6471            }
6472        }
6473        // Stack: [..., __exit__]
6474
6475        // Compile body or nested with
6476        if items.is_empty() {
6477            if body.is_empty() {
6478                return Err(self.error(CodegenErrorType::EmptyWithBody));
6479            }
6480            self.compile_with_body_statements(body)?;
6481        } else {
6482            self.set_source_range(items[0].context_expr.range());
6483            self.compile_with_inner(items, body, is_async)?;
6484        }
6485
6486        // CPython pops the async-with fblock before emitting POP_BLOCK, but
6487        // sync with emits the artificial POP_BLOCK before popping the fblock.
6488        if is_async {
6489            self.pop_fblock_label(fblock_type, body_label);
6490            self.set_source_range(with_range);
6491            emit!(self, PseudoInstruction::PopBlock);
6492        } else {
6493            emit!(self, PseudoInstruction::PopBlock);
6494            self.set_no_location();
6495            self.set_source_range(with_range);
6496            self.pop_fblock_label(fblock_type, body_label);
6497        }
6498
6499        self.compile_call_exit_with_nones();
6500        if is_async {
6501            emit!(self, Instruction::GetAwaitable { r#where: 2 });
6502            self.emit_load_const(ConstantData::None);
6503            let _ = self.compile_yield_from_sequence(true);
6504        }
6505        emit!(self, Instruction::PopTop); // Pop __exit__ result
6506        emit!(self, PseudoInstruction::Jump { delta: after_block });
6507
6508        // ===== Exception handler path =====
6509        // Stack at entry: [..., exit_func, self_exit, lasti, exc]
6510        // PUSH_EXC_INFO -> [..., exit_func, self_exit, lasti, prev_exc, exc]
6511        self.use_cpython_label_block(exc_handler_block);
6512
6513        emit!(
6514            self,
6515            PseudoInstruction::SetupCleanup {
6516                delta: cleanup_block
6517            }
6518        );
6519
6520        emit!(self, Instruction::PushExcInfo);
6521
6522        // WITH_EXCEPT_START: call exit_func(self_exit, type, value, tb)
6523        // Stack: [..., exit_func, self_exit, lasti, prev_exc, exc]
6524        emit!(self, Instruction::WithExceptStart);
6525
6526        if is_async {
6527            emit!(self, Instruction::GetAwaitable { r#where: 2 });
6528            self.emit_load_const(ConstantData::None);
6529            let _ = self.compile_yield_from_sequence(true);
6530        }
6531
6532        self.compile_with_except_finish(cleanup_block);
6533
6534        self.use_cpython_label_block(after_block);
6535
6536        Ok(())
6537    }
6538
6539    fn compile_for(
6540        &mut self,
6541        target: &ast::Expr,
6542        iter: &ast::Expr,
6543        body: &[ast::Stmt],
6544        orelse: &[ast::Stmt],
6545        is_async: bool,
6546        for_range: TextRange,
6547    ) -> CompileResult<()> {
6548        self.enter_conditional_block();
6549
6550        // Start loop
6551        let for_block = self.new_block();
6552        let (body_label, send_block) = if is_async {
6553            (None, self.new_block())
6554        } else {
6555            let body_label = self.current_code_info().new_instr_sequence_label();
6556            (Some(body_label), BlockIdx::NULL)
6557        };
6558        let else_block = self.new_block();
6559        let after_block = self.new_block();
6560        let for_label = self.instr_sequence_label_for_block(for_block);
6561        let after_label = self.instr_sequence_label_for_block(after_block);
6562        let mut end_async_for_target = BlockIdx::NULL;
6563
6564        if !is_async {
6565            // CPython codegen_for() pushes the loop fblock before compiling
6566            // the iterable expression.
6567            self.push_fblock_labels(
6568                FBlockType::ForLoop,
6569                for_label,
6570                after_label,
6571                FBlockDatum::None,
6572            )?;
6573        }
6574
6575        // The thing iterated:
6576        self.compile_expression(iter)?;
6577
6578        if is_async {
6579            if self.ctx.func != FunctionContext::AsyncFunction
6580                && !self.allows_top_level_await_in_current_context()
6581            {
6582                return Err(self.error(CodegenErrorType::InvalidAsyncFor));
6583            }
6584            self.set_source_range(iter.range());
6585            emit!(self, Instruction::GetAiter);
6586
6587            self.use_cpython_label_block(for_block);
6588            self.set_source_range(for_range);
6589
6590            // codegen_async_for: push fblock BEFORE SETUP_FINALLY
6591            self.push_fblock_labels(
6592                FBlockType::ForLoop,
6593                for_label,
6594                after_label,
6595                FBlockDatum::None,
6596            )?;
6597
6598            // SETUP_FINALLY to guard the __anext__ call
6599            emit!(self, PseudoInstruction::SetupFinally { delta: else_block });
6600            emit!(self, Instruction::GetAnext);
6601            self.emit_load_const(ConstantData::None);
6602            self.use_cpython_label_block(send_block);
6603            let _ = self.compile_yield_from_sequence(true);
6604            end_async_for_target = send_block;
6605            // POP_BLOCK for SETUP_FINALLY - only GetANext/yield_from are protected
6606            emit!(self, PseudoInstruction::PopBlock);
6607            emit!(self, Instruction::NotTaken);
6608
6609            // Success block for __anext__
6610            self.compile_store(target)?;
6611        } else {
6612            // Retrieve Iterator
6613            self.set_source_range(iter.range());
6614            emit!(self, Instruction::GetIter);
6615
6616            self.use_cpython_label_block(for_block);
6617
6618            emit!(self, Instruction::ForIter { delta: else_block });
6619
6620            let saved_range = self.current_source_range;
6621            self.set_source_range(target.range());
6622            emit!(self, Instruction::Nop);
6623            let result = self
6624                .current_code_info()
6625                .use_raw_instr_sequence_label(body_label.expect("sync for must have body label"));
6626            unwrap_internal(self, result);
6627            self.compile_store(target)?;
6628            self.set_source_range(saved_range);
6629        };
6630
6631        self.compile_loop_body_statements(body)?;
6632        emit!(self, PseudoInstruction::Jump { delta: for_block });
6633        self.set_no_location();
6634
6635        if is_async {
6636            // codegen_async_for() pops the loop fblock before the
6637            // END_ASYNC_FOR exception block. Sync codegen_for() keeps the
6638            // fblock through END_FOR/POP_ITER and pops below.
6639            self.pop_fblock_label(FBlockType::ForLoop, for_label);
6640        }
6641
6642        self.use_cpython_label_block(else_block);
6643
6644        // Except block for __anext__ / end of sync for
6645        if is_async {
6646            // codegen_async_for emits END_ASYNC_FOR at the iterator location,
6647            // after the for-loop fblock has already been popped.
6648            let saved_range = self.current_source_range;
6649            self.set_source_range(iter.range());
6650            self.emit_end_async_for(end_async_for_target);
6651            self.set_source_range(saved_range);
6652        } else {
6653            // codegen_for emits END_FOR/POP_ITER with NO_LOCATION. Line numbers
6654            // are propagated later by flowgraph.c::resolve_line_numbers().
6655            emit!(self, Instruction::EndFor);
6656            self.set_no_location();
6657            emit!(self, Instruction::PopIter);
6658            self.set_no_location();
6659            self.pop_fblock_label(FBlockType::ForLoop, for_label);
6660        }
6661        self.compile_statements(orelse)?;
6662
6663        self.use_cpython_label_block(after_block);
6664
6665        // Implicit return after for-loop should be attributed to the `for` line
6666        self.set_source_range(iter.range());
6667
6668        self.leave_conditional_block();
6669        Ok(())
6670    }
6671
6672    fn compile_comprehension_iter(&mut self, generator: &ast::Comprehension) -> CompileResult<()> {
6673        let saved_range = self.current_source_range;
6674        self.compile_expression(&generator.iter)?;
6675        self.set_source_range(generator.iter.range());
6676        if generator.is_async {
6677            emit!(self, Instruction::GetAiter);
6678        } else {
6679            emit!(self, Instruction::GetIter);
6680        }
6681        self.set_source_range(saved_range);
6682        Ok(())
6683    }
6684
6685    fn singleton_comprehension_assignment_iter(iter: &ast::Expr) -> Option<&ast::Expr> {
6686        let elts = match iter {
6687            ast::Expr::List(ast::ExprList { elts, .. }) => elts,
6688            ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => elts,
6689            _ => return None,
6690        };
6691        match elts.as_slice() {
6692            [elt] if !matches!(elt, ast::Expr::Starred(_)) => Some(elt),
6693            _ => None,
6694        }
6695    }
6696
6697    /// Ensures that `pc.fail_pop` has at least `n + 1` entries.
6698    /// If not, new labels are generated and pushed until the required size is reached.
6699    fn ensure_fail_pop(&mut self, pc: &mut PatternContext, n: usize) {
6700        let required_size = n + 1;
6701        if required_size <= pc.fail_pop.len() {
6702            return;
6703        }
6704        while pc.fail_pop.len() < required_size {
6705            let new_block = self.new_block();
6706            pc.fail_pop.push(new_block);
6707        }
6708    }
6709
6710    fn jump_to_fail_pop(&mut self, pc: &mut PatternContext, op: JumpOp) {
6711        // Compute the total number of items to pop:
6712        // items on top plus the captured objects.
6713        let pops = pc.on_top + pc.stores.len();
6714        // Ensure that the fail_pop vector has at least `pops + 1` elements.
6715        self.ensure_fail_pop(pc, pops);
6716        // Emit a jump using the jump target stored at index `pops`.
6717        match op {
6718            JumpOp::Jump => {
6719                emit!(
6720                    self,
6721                    PseudoInstruction::Jump {
6722                        delta: pc.fail_pop[pops]
6723                    }
6724                )
6725            }
6726            JumpOp::PopJumpIfFalse => {
6727                emit!(
6728                    self,
6729                    Instruction::PopJumpIfFalse {
6730                        delta: pc.fail_pop[pops]
6731                    }
6732                )
6733            }
6734        };
6735    }
6736
6737    /// Emits the necessary POP instructions for all failure targets in the pattern context,
6738    /// then resets the fail_pop vector.
6739    fn emit_and_reset_fail_pop(&mut self, pc: &mut PatternContext, loc: TextRange) {
6740        // If the fail_pop vector is empty, nothing needs to be done.
6741        if pc.fail_pop.is_empty() {
6742            debug_assert!(pc.fail_pop.is_empty());
6743            return;
6744        }
6745        // Iterate over the fail_pop vector in reverse order, skipping the first label.
6746        for &label in pc.fail_pop.iter().skip(1).rev() {
6747            // CPython emit_and_reset_fail_pop() uses USE_LABEL here.
6748            self.use_cpython_label_block(label);
6749            // Emit the POP instruction.
6750            self.set_source_range(loc);
6751            emit!(self, Instruction::PopTop);
6752        }
6753        // Finally, use the first label.
6754        // CPython emit_and_reset_fail_pop() uses USE_LABEL here too.
6755        self.use_cpython_label_block(pc.fail_pop[0]);
6756        pc.fail_pop.clear();
6757        // Free the memory used by the vector.
6758        pc.fail_pop.shrink_to_fit();
6759    }
6760
6761    /// Duplicate the effect of Python 3.10's ROT_* instructions using SWAPs.
6762    fn pattern_helper_rotate(&mut self, loc: TextRange, mut count: usize) {
6763        // Rotate TOS (top of stack) to position `count` down
6764        // This is done by a series of swaps
6765        // For count=1, no rotation needed (already at top)
6766        // For count=2, swap TOS with item 1 position down
6767        // For count=3, swap TOS with item 2 positions down, then with item 1 position down
6768        while count > 1 {
6769            // Emit a SWAP instruction with the current count.
6770            self.set_source_range(loc);
6771            emit!(
6772                self,
6773                Instruction::Swap {
6774                    i: u32::try_from(count).unwrap()
6775                }
6776            );
6777            count -= 1;
6778        }
6779    }
6780
6781    /// Helper to store a captured name for a star pattern.
6782    ///
6783    /// If `n` is `None`, it emits a POP_TOP instruction. Otherwise, it first
6784    /// checks that the name is allowed and not already stored. Then it rotates
6785    /// the object on the stack beneath any preserved items and appends the name
6786    /// to the list of captured names.
6787    fn pattern_helper_store_name(
6788        &mut self,
6789        loc: TextRange,
6790        n: Option<&ast::Identifier>,
6791        pc: &mut PatternContext,
6792    ) -> CompileResult<()> {
6793        match n {
6794            // If no name is provided, simply pop the top of the stack.
6795            None => {
6796                self.set_source_range(loc);
6797                emit!(self, Instruction::PopTop);
6798                Ok(())
6799            }
6800            Some(name) => {
6801                // Ensure we don't store the same name twice.
6802                // TODO: maybe pc.stores should be a set?
6803                if pc.stores.contains(name.id()) {
6804                    return Err(self.error_ranged(
6805                        CodegenErrorType::DuplicateStore(name.as_str().to_string()),
6806                        loc,
6807                    ));
6808                }
6809
6810                // Calculate how many items to rotate:
6811                let rotations = pc.on_top + pc.stores.len() + 1;
6812                self.pattern_helper_rotate(loc, rotations);
6813
6814                // Append the name to the captured stores.
6815                pc.stores.push(name.id().clone());
6816                Ok(())
6817            }
6818        }
6819    }
6820
6821    fn pattern_wildcard_check(pattern: &ast::Pattern) -> bool {
6822        matches!(
6823            pattern,
6824            ast::Pattern::MatchAs(ast::PatternMatchAs { name: None, .. })
6825        )
6826    }
6827
6828    fn pattern_wildcard_star_check(pattern: &ast::Pattern) -> bool {
6829        matches!(
6830            pattern,
6831            ast::Pattern::MatchStar(ast::PatternMatchStar { name: None, .. })
6832        )
6833    }
6834
6835    fn pattern_unpack_helper(
6836        &mut self,
6837        loc: TextRange,
6838        elts: &[ast::Pattern],
6839    ) -> CompileResult<()> {
6840        let n = elts.len();
6841        let mut seen_star = false;
6842        for (i, elt) in elts.iter().enumerate() {
6843            if elt.is_match_star() && !seen_star {
6844                if i >= (1 << 8) || (n - i - 1) >= ((i32::MAX as usize) >> 8) {
6845                    return Err(self.error_ranged(
6846                        CodegenErrorType::TooManyExpressionsInStarUnpackingSequencePattern,
6847                        loc,
6848                    ));
6849                }
6850                let counts = UnpackExArgs {
6851                    before: u8::try_from(i).unwrap(),
6852                    after: u32::try_from(n - i - 1).unwrap(),
6853                };
6854                self.set_source_range(loc);
6855                emit!(self, Instruction::UnpackEx { counts });
6856                seen_star = true;
6857            } else if elt.is_match_star() {
6858                return Err(self.error_ranged(
6859                    CodegenErrorType::MultipleStarredExpressionsInSequencePattern,
6860                    loc,
6861                ));
6862            }
6863        }
6864        if !seen_star {
6865            self.set_source_range(loc);
6866            emit!(
6867                self,
6868                Instruction::UnpackSequence {
6869                    count: u32::try_from(n).unwrap()
6870                }
6871            );
6872        }
6873        Ok(())
6874    }
6875
6876    fn pattern_helper_sequence_unpack(
6877        &mut self,
6878        loc: TextRange,
6879        patterns: &[ast::Pattern],
6880        _star: Option<usize>,
6881        pc: &mut PatternContext,
6882    ) -> CompileResult<()> {
6883        // Unpack the sequence into individual subjects.
6884        self.pattern_unpack_helper(loc, patterns)?;
6885        let size = patterns.len();
6886        // Increase the on_top counter for the newly unpacked subjects.
6887        pc.on_top += size;
6888        // For each unpacked subject, compile its subpattern.
6889        for pattern in patterns {
6890            // Decrement on_top for each subject as it is consumed.
6891            pc.on_top -= 1;
6892            self.compile_pattern_subpattern(pattern, pc)?;
6893        }
6894        Ok(())
6895    }
6896
6897    fn pattern_helper_sequence_subscr(
6898        &mut self,
6899        loc: TextRange,
6900        patterns: &[ast::Pattern],
6901        star: usize,
6902        pc: &mut PatternContext,
6903    ) -> CompileResult<()> {
6904        // Keep the subject around for extracting elements.
6905        pc.on_top += 1;
6906        for (i, pattern) in patterns.iter().enumerate() {
6907            if Self::pattern_wildcard_check(pattern) {
6908                continue;
6909            }
6910            if i == star {
6911                // This must be a starred wildcard.
6912                debug_assert!(Self::pattern_wildcard_star_check(pattern));
6913                continue;
6914            }
6915            // Duplicate the subject.
6916            self.set_source_range(loc);
6917            emit!(self, Instruction::Copy { i: 1 });
6918            if i < star {
6919                // For indices before the star, use a nonnegative index equal to i.
6920                self.set_source_range(loc);
6921                self.emit_load_const(ConstantData::Integer { value: i.into() });
6922            } else {
6923                // For indices after the star, compute a nonnegative index:
6924                // index = len(subject) - (size - i)
6925                self.set_source_range(loc);
6926                emit!(self, Instruction::GetLen);
6927                self.set_source_range(loc);
6928                self.emit_load_const(ConstantData::Integer {
6929                    value: (patterns.len() - i).into(),
6930                });
6931                // Subtract to compute the correct index.
6932                self.set_source_range(loc);
6933                emit!(
6934                    self,
6935                    Instruction::BinaryOp {
6936                        op: BinaryOperator::Subtract
6937                    }
6938                );
6939            }
6940            // Use BINARY_OP/NB_SUBSCR to extract the element.
6941            self.set_source_range(loc);
6942            emit!(
6943                self,
6944                Instruction::BinaryOp {
6945                    op: BinaryOperator::Subscr
6946                }
6947            );
6948            // Compile the subpattern in irrefutable mode.
6949            self.compile_pattern_subpattern(pattern, pc)?;
6950        }
6951        // Pop the subject off the stack.
6952        pc.on_top -= 1;
6953        self.set_source_range(loc);
6954        emit!(self, Instruction::PopTop);
6955        Ok(())
6956    }
6957
6958    fn compile_pattern_subpattern(
6959        &mut self,
6960        p: &ast::Pattern,
6961        pc: &mut PatternContext,
6962    ) -> CompileResult<()> {
6963        // Save the current allow_irrefutable state.
6964        let old_allow_irrefutable = pc.allow_irrefutable;
6965        // Temporarily allow irrefutable patterns.
6966        pc.allow_irrefutable = true;
6967        // Compile the pattern.
6968        self.compile_pattern(p, pc)?;
6969        // Restore the original state.
6970        pc.allow_irrefutable = old_allow_irrefutable;
6971        Ok(())
6972    }
6973
6974    fn compile_pattern_as(
6975        &mut self,
6976        p: &ast::PatternMatchAs,
6977        pc: &mut PatternContext,
6978    ) -> CompileResult<()> {
6979        // If there is no sub-pattern, then it's an irrefutable match.
6980        if p.pattern.is_none() {
6981            if !pc.allow_irrefutable {
6982                if let Some(name) = p.name.as_ref() {
6983                    return Err(self.error_ranged(
6984                        CodegenErrorType::UnreachableNameCapturePattern(name.to_string()),
6985                        p.range,
6986                    ));
6987                }
6988                // A wildcard makes remaining patterns unreachable.
6989                return Err(
6990                    self.error_ranged(CodegenErrorType::UnreachableWildcardPattern, p.range)
6991                );
6992            }
6993            // If irrefutable matches are allowed, store the name (if any).
6994            return self.pattern_helper_store_name(p.range, p.name.as_ref(), pc);
6995        }
6996
6997        // Otherwise, there is a sub-pattern. Duplicate the object on top of the stack.
6998        pc.on_top += 1;
6999        self.set_source_range(p.range);
7000        emit!(self, Instruction::Copy { i: 1 });
7001        // Compile the sub-pattern.
7002        self.compile_pattern(p.pattern.as_ref().unwrap(), pc)?;
7003        // After success, decrement the on_top counter.
7004        pc.on_top -= 1;
7005        // Store the captured name (if any).
7006        self.pattern_helper_store_name(p.range, p.name.as_ref(), pc)?;
7007        Ok(())
7008    }
7009
7010    fn compile_pattern_star(
7011        &mut self,
7012        p: &ast::PatternMatchStar,
7013        pc: &mut PatternContext,
7014    ) -> CompileResult<()> {
7015        self.pattern_helper_store_name(p.range, p.name.as_ref(), pc)?;
7016        Ok(())
7017    }
7018
7019    /// Validates that keyword attributes in a class pattern are allowed
7020    /// and not duplicated.
7021    fn validate_kwd_attrs(
7022        &mut self,
7023        attrs: &[ast::Identifier],
7024        patterns: &[ast::Pattern],
7025    ) -> CompileResult<()> {
7026        let n_attrs = attrs.len();
7027        for i in 0..n_attrs {
7028            let attr = attrs[i].as_str();
7029            // Check for duplicates: compare with every subsequent attribute.
7030            for (j, ident) in attrs.iter().enumerate().take(n_attrs).skip(i + 1) {
7031                let other = ident.as_str();
7032                if attr == other {
7033                    return Err(self.error_ranged(
7034                        CodegenErrorType::RepeatedAttributePattern(attr.to_owned()),
7035                        patterns[j].range(),
7036                    ));
7037                }
7038            }
7039        }
7040        Ok(())
7041    }
7042
7043    fn compile_pattern_class(
7044        &mut self,
7045        p: &ast::PatternMatchClass,
7046        pc: &mut PatternContext,
7047    ) -> CompileResult<()> {
7048        // Extract components from the MatchClass pattern.
7049        let match_class = p;
7050        let patterns = &match_class.arguments.patterns;
7051
7052        // Extract keyword attributes and patterns.
7053        // Capacity is pre-allocated based on the number of keyword arguments.
7054        let mut kwd_attrs = Vec::with_capacity(match_class.arguments.keywords.len());
7055        let mut kwd_patterns = Vec::with_capacity(match_class.arguments.keywords.len());
7056        for kwd in &match_class.arguments.keywords {
7057            kwd_attrs.push(kwd.attr.clone());
7058            kwd_patterns.push(kwd.pattern.clone());
7059        }
7060
7061        let nargs = patterns.len();
7062        let n_attrs = kwd_attrs.len();
7063        let n_kwd_patterns = kwd_patterns.len();
7064        if n_attrs != n_kwd_patterns {
7065            return Err(self.error_ranged(
7066                CodegenErrorType::SyntaxError(format!(
7067                    "kwd_attrs ({n_attrs}) / kwd_patterns ({n_kwd_patterns}) length mismatch in class pattern"
7068                )),
7069                p.range,
7070            ));
7071        }
7072
7073        // Check for too many sub-patterns.
7074        if nargs > i32::MAX as usize
7075            || nargs.saturating_add(n_attrs).saturating_sub(1) > i32::MAX as usize
7076        {
7077            return Err(self.error_ranged(
7078                CodegenErrorType::SyntaxError(format!(
7079                    "too many sub-patterns in class pattern {}",
7080                    UnparseExpr::new(&match_class.cls, &self.source_file)
7081                )),
7082                p.range,
7083            ));
7084        }
7085
7086        // Validate keyword attributes if any.
7087        if n_attrs != 0 {
7088            self.validate_kwd_attrs(&kwd_attrs, &kwd_patterns)?;
7089        }
7090
7091        // Compile the class expression.
7092        self.compile_expression(&match_class.cls)?;
7093        self.set_source_range(p.range);
7094
7095        // Create a new tuple of attribute names.
7096        let mut attr_names = vec![];
7097        for name in &kwd_attrs {
7098            // Py_NewRef(name) is emulated by cloning the name into a PyObject.
7099            attr_names.push(ConstantData::Str {
7100                value: name.as_str().to_string().into(),
7101            });
7102        }
7103
7104        // Emit instructions:
7105        // 1. Load the new tuple of attribute names.
7106        self.emit_load_const(ConstantData::Tuple {
7107            elements: attr_names,
7108        });
7109        // 2. Emit MATCH_CLASS with nargs.
7110        emit!(
7111            self,
7112            Instruction::MatchClass {
7113                count: u32::try_from(nargs).unwrap()
7114            }
7115        );
7116        // 3. Duplicate the top of the stack.
7117        emit!(self, Instruction::Copy { i: 1 });
7118        // 4. Load None.
7119        self.emit_load_const(ConstantData::None);
7120        // 5. Compare with IS_OP 1.
7121        emit!(
7122            self,
7123            Instruction::IsOp {
7124                invert: Invert::Yes
7125            }
7126        );
7127
7128        // At this point the TOS is a tuple of (nargs + n_attrs) attributes (or None).
7129        pc.on_top += 1;
7130        self.set_source_range(p.range);
7131        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7132
7133        // Unpack the tuple into (nargs + n_attrs) items.
7134        let total = nargs + n_attrs;
7135        emit!(
7136            self,
7137            Instruction::UnpackSequence {
7138                count: u32::try_from(total).unwrap()
7139            }
7140        );
7141        if total == 0 {
7142            pc.on_top -= 1;
7143        } else {
7144            pc.on_top += total - 1;
7145        }
7146
7147        // Process each sub-pattern.
7148        for subpattern in patterns.iter().chain(kwd_patterns.iter()) {
7149            // Decrement the on_top counter for each sub-pattern
7150            pc.on_top -= 1;
7151
7152            if Self::pattern_wildcard_check(subpattern) {
7153                self.set_source_range(p.range);
7154                emit!(self, Instruction::PopTop);
7155                continue; // Don't compile wildcard patterns
7156            }
7157
7158            // Compile the subpattern without irrefutability checks.
7159            self.compile_pattern_subpattern(subpattern, pc)?;
7160        }
7161        Ok(())
7162    }
7163
7164    fn compile_pattern_mapping(
7165        &mut self,
7166        p: &ast::PatternMatchMapping,
7167        pc: &mut PatternContext,
7168    ) -> CompileResult<()> {
7169        let mapping = p;
7170        let keys = &mapping.keys;
7171        let patterns = &mapping.patterns;
7172        let size = keys.len();
7173        let star_target = &mapping.rest;
7174
7175        // Validate pattern count matches key count
7176        if keys.len() != patterns.len() {
7177            return Err(self.error_ranged(
7178                CodegenErrorType::SyntaxError(format!(
7179                    "keys ({}) / patterns ({}) length mismatch in mapping pattern",
7180                    keys.len(),
7181                    patterns.len()
7182                )),
7183                p.range,
7184            ));
7185        }
7186
7187        // `case {**_}:` is rejected before codegen. RustPython's parser
7188        // currently lets it through, so keep the compiler boundary equivalent.
7189        if let Some(rest) = star_target
7190            && rest.as_str() == "_"
7191        {
7192            return Err(self.error_ranged(
7193                CodegenErrorType::SyntaxError("invalid syntax".to_string()),
7194                rest.range,
7195            ));
7196        }
7197
7198        // Step 1: Check if subject is a mapping
7199        // Stack: [subject]
7200        pc.on_top += 1;
7201
7202        self.set_source_range(p.range);
7203        emit!(self, Instruction::MatchMapping);
7204        // Stack: [subject, is_mapping]
7205
7206        self.set_source_range(p.range);
7207        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7208        // Stack: [subject]
7209
7210        // Special case: empty pattern {} with no rest
7211        if size == 0 && star_target.is_none() {
7212            // If the pattern is just "{}", we're done! Pop the subject
7213            pc.on_top -= 1;
7214            emit!(self, Instruction::PopTop);
7215            return Ok(());
7216        }
7217
7218        // Length check for patterns with keys
7219        if size > 0 {
7220            // Check if the mapping has at least 'size' keys
7221            self.set_source_range(p.range);
7222            emit!(self, Instruction::GetLen);
7223            self.set_source_range(p.range);
7224            self.emit_load_const(ConstantData::Integer { value: size.into() });
7225            // Stack: [subject, len, size]
7226            self.set_source_range(p.range);
7227            emit!(
7228                self,
7229                Instruction::CompareOp {
7230                    opname: ComparisonOperator::GreaterOrEqual
7231                }
7232            );
7233            self.set_source_range(p.range);
7234            self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7235            // Stack: [subject]
7236        }
7237
7238        // Check for overflow (INT_MAX < size - 1)
7239        if size.saturating_sub(1) > i32::MAX as usize {
7240            return Err(self.error_ranged(
7241                CodegenErrorType::SyntaxError(
7242                    "too many sub-patterns in mapping pattern".to_string(),
7243                ),
7244                p.range,
7245            ));
7246        }
7247        let size = size.to_u32();
7248
7249        // Step 2: Validate and compile all keys.
7250        let mut seen = Vec::new();
7251        for key in keys {
7252            self.compile_pattern_mapping_key(&mut seen, p.range, key)?;
7253        }
7254        self.set_source_range(p.range);
7255        // Stack: [subject, key1, key2, ..., key_n]
7256
7257        // Build tuple of keys (empty tuple if size==0)
7258        emit!(self, Instruction::BuildTuple { count: size });
7259        // Stack: [subject, keys_tuple]
7260
7261        // Match keys
7262        emit!(self, Instruction::MatchKeys);
7263        // Stack: [subject, keys_tuple, values_or_none]
7264        pc.on_top += 2; // subject and keys_tuple are underneath
7265
7266        // Check if match succeeded
7267        self.set_source_range(p.range);
7268        emit!(self, Instruction::Copy { i: 1 });
7269        // Stack: [subject, keys_tuple, values_tuple, values_tuple_copy]
7270
7271        // Check if copy is None (consumes the copy like POP_JUMP_IF_NONE)
7272        self.set_source_range(p.range);
7273        self.emit_load_const(ConstantData::None);
7274        self.set_source_range(p.range);
7275        emit!(
7276            self,
7277            Instruction::IsOp {
7278                invert: Invert::Yes
7279            }
7280        );
7281
7282        // Stack: [subject, keys_tuple, values_tuple, bool]
7283        self.set_source_range(p.range);
7284        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7285        // Stack: [subject, keys_tuple, values_tuple]
7286
7287        // Unpack values (the original values_tuple)
7288        emit!(self, Instruction::UnpackSequence { count: size });
7289        // Stack after unpack: [subject, keys_tuple, ...unpacked values...]
7290        if size == 0 {
7291            pc.on_top -= 1;
7292        } else {
7293            pc.on_top += size as usize - 1;
7294        }
7295
7296        // Step 3: Process matched values
7297        for i in 0..size {
7298            pc.on_top -= 1;
7299            self.compile_pattern_subpattern(&patterns[i as usize], pc)?;
7300        }
7301
7302        // After processing subpatterns, adjust on_top
7303        // "Whatever happens next should consume the tuple of keys and the subject"
7304        // Stack currently: [subject, keys_tuple, ...any captured values...]
7305        pc.on_top -= 2;
7306
7307        // Step 4: Handle rest pattern or cleanup
7308        if let Some(rest_name) = star_target {
7309            // Build rest dict for **rest pattern
7310            // Stack: [subject, keys_tuple]
7311
7312            // Build rest dict exactly
7313            self.set_source_range(p.range);
7314            emit!(self, Instruction::BuildMap { count: 0 });
7315            // Stack: [subject, keys_tuple, {}]
7316            self.set_source_range(p.range);
7317            emit!(self, Instruction::Swap { i: 3 });
7318            // Stack: [{}, keys_tuple, subject]
7319            self.set_source_range(p.range);
7320            emit!(self, Instruction::DictUpdate { i: 2 });
7321            // Stack after DICT_UPDATE: [rest_dict, keys_tuple]
7322            // DICT_UPDATE consumes source (subject) and leaves dict in place
7323
7324            // Unpack keys and delete from rest_dict
7325            self.set_source_range(p.range);
7326            emit!(self, Instruction::UnpackSequence { count: size });
7327            // Stack: [rest_dict, k1, k2, ..., kn] (if size==0, nothing pushed)
7328
7329            // Delete each key from rest_dict (skipped when size==0)
7330            // while (size) { COPY(1 + size--); SWAP(2); DELETE_SUBSCR }
7331            let mut remaining = size;
7332            while remaining > 0 {
7333                // Copy rest_dict which is at position (1 + remaining) from TOS
7334                self.set_source_range(p.range);
7335                emit!(self, Instruction::Copy { i: 1 + remaining });
7336                // Stack: [rest_dict, k1, ..., kn, rest_dict]
7337                self.set_source_range(p.range);
7338                emit!(self, Instruction::Swap { i: 2 });
7339                // Stack: [rest_dict, k1, ..., kn-1, rest_dict, kn]
7340                self.set_source_range(p.range);
7341                emit!(self, Instruction::DeleteSubscr);
7342                // Stack: [rest_dict, k1, ..., kn-1] (removed kn from rest_dict)
7343                remaining -= 1;
7344            }
7345            // Stack: [rest_dict] (plus any previously stored values)
7346            // pattern_helper_store_name will handle the rotation correctly
7347
7348            // Store the rest dict
7349            self.pattern_helper_store_name(p.range, Some(rest_name), pc)?;
7350        } else {
7351            // Non-rest pattern: just clean up the stack
7352
7353            // Pop them as we're not using them
7354            self.set_source_range(p.range);
7355            emit!(self, Instruction::PopTop); // Pop keys_tuple
7356            self.set_source_range(p.range);
7357            emit!(self, Instruction::PopTop); // Pop subject
7358        }
7359
7360        Ok(())
7361    }
7362
7363    fn compile_pattern_mapping_key(
7364        &mut self,
7365        seen: &mut Vec<ConstantData>,
7366        pattern_range: TextRange,
7367        key: &ast::Expr,
7368    ) -> CompileResult<()> {
7369        let is_attribute = matches!(key, ast::Expr::Attribute(_));
7370        let constant = match self.try_compile_match_mapping_key_constant(key)? {
7371            Some(constant) => Some(constant),
7372            None if is_attribute => None,
7373            None => {
7374                if Self::is_unexpected_match_literal_constant(key) {
7375                    return Err(self.error_ranged(
7376                        CodegenErrorType::SyntaxError(
7377                            "unexpected constant inside of a literal pattern".to_string(),
7378                        ),
7379                        pattern_range,
7380                    ));
7381                }
7382                return Err(self.error_ranged(
7383                    CodegenErrorType::SyntaxError(
7384                        "mapping pattern keys may only match literals and attribute lookups"
7385                            .to_string(),
7386                    ),
7387                    pattern_range,
7388                ));
7389            }
7390        };
7391
7392        if let Some(constant) = constant {
7393            if seen
7394                .iter()
7395                .any(|seen| Self::match_mapping_keys_equal(seen, &constant))
7396            {
7397                let key_repr = Self::match_mapping_key_repr(&constant);
7398                return Err(self.error_ranged(
7399                    CodegenErrorType::SyntaxError(format!(
7400                        "mapping pattern checks duplicate key ({key_repr})"
7401                    )),
7402                    pattern_range,
7403                ));
7404            }
7405            seen.push(constant);
7406        }
7407
7408        self.compile_match_pattern_expr(key)
7409    }
7410
7411    fn try_compile_match_mapping_key_constant(
7412        &mut self,
7413        key: &ast::Expr,
7414    ) -> CompileResult<Option<ConstantData>> {
7415        if let Some(constant) = self.try_fold_match_pattern_const_expr(key)? {
7416            return Ok(Some(constant));
7417        }
7418        self.try_compile_match_mapping_key_direct_constant(key)
7419    }
7420
7421    fn try_compile_match_value_constant(
7422        &mut self,
7423        value: &ast::Expr,
7424    ) -> CompileResult<Option<ConstantData>> {
7425        if let Some(constant) = self.try_fold_match_pattern_const_expr(value)? {
7426            return Ok(Some(constant));
7427        }
7428        self.try_compile_match_pattern_direct_literal(value)
7429    }
7430
7431    fn match_mapping_keys_equal(left: &ConstantData, right: &ConstantData) -> bool {
7432        use ConstantData::{Bytes, Ellipsis, None, Str};
7433
7434        if Self::match_mapping_numeric_keys_equal(left, right).unwrap_or(false) {
7435            return true;
7436        }
7437
7438        match (left, right) {
7439            (Str { value: left }, Str { value: right }) => left == right,
7440            (Bytes { value: left }, Bytes { value: right }) => left == right,
7441            (None, None) | (Ellipsis, Ellipsis) => true,
7442            _ => false,
7443        }
7444    }
7445
7446    fn match_mapping_key_repr(key: &ConstantData) -> String {
7447        match key {
7448            ConstantData::Integer { value } => value.to_string(),
7449            ConstantData::Float { value } => literal_float::to_string(*value),
7450            ConstantData::Complex { value } => literal_complex::to_string(value.re, value.im),
7451            ConstantData::Boolean { value } => {
7452                if *value {
7453                    "True".to_owned()
7454                } else {
7455                    "False".to_owned()
7456                }
7457            }
7458            ConstantData::Str { value } => UnicodeEscape::new_repr(value.as_ref())
7459                .str_repr()
7460                .to_string()
7461                .unwrap_or_else(|| value.to_string()),
7462            ConstantData::Bytes { value } => AsciiEscape::new_repr(value)
7463                .bytes_repr()
7464                .to_string()
7465                .unwrap_or_else(|| format!(r#"b"{}""#, value.escape_ascii())),
7466            ConstantData::None => "None".to_owned(),
7467            ConstantData::Ellipsis => "...".to_owned(),
7468            other => other.to_string(),
7469        }
7470    }
7471
7472    fn match_mapping_numeric_keys_equal(left: &ConstantData, right: &ConstantData) -> Option<bool> {
7473        use ConstantData::{Boolean, Complex, Float, Integer};
7474
7475        match (left, right) {
7476            (Integer { value: left }, Integer { value: right }) => Some(left == right),
7477            (Boolean { value: left }, Boolean { value: right }) => Some(left == right),
7478            (Boolean { value }, Integer { value: int })
7479            | (Integer { value: int }, Boolean { value }) => {
7480                Some(BigInt::from(u8::from(*value)) == *int)
7481            }
7482            (Float { value: left }, Float { value: right }) => Some(left == right),
7483            (Integer { value: int }, Float { value: float })
7484            | (Float { value: float }, Integer { value: int }) => {
7485                Some(Self::match_mapping_float_integer_equal(*float, int))
7486            }
7487            (Boolean { value }, Float { value: float })
7488            | (Float { value: float }, Boolean { value }) => Some(
7489                Self::match_mapping_float_integer_equal(*float, &BigInt::from(u8::from(*value))),
7490            ),
7491            (Complex { value: left }, Complex { value: right }) => {
7492                Some(left.re == right.re && left.im == right.im)
7493            }
7494            (Complex { value: complex }, other) | (other, Complex { value: complex }) => Some(
7495                complex.im == 0.0
7496                    && Self::match_mapping_float_real_constant_equal(complex.re, other)
7497                        .unwrap_or(false),
7498            ),
7499            _ => Option::None,
7500        }
7501    }
7502
7503    fn match_mapping_float_real_constant_equal(float: f64, other: &ConstantData) -> Option<bool> {
7504        match other {
7505            ConstantData::Integer { value } => {
7506                Some(Self::match_mapping_float_integer_equal(float, value))
7507            }
7508            ConstantData::Boolean { value } => Some(Self::match_mapping_float_integer_equal(
7509                float,
7510                &BigInt::from(u8::from(*value)),
7511            )),
7512            ConstantData::Float { value } => Some(float == *value),
7513            _ => None,
7514        }
7515    }
7516
7517    fn match_mapping_float_integer_equal(float: f64, int: &BigInt) -> bool {
7518        Self::match_mapping_float_to_integer(float).is_some_and(|float_int| &float_int == int)
7519    }
7520
7521    fn match_mapping_float_to_integer(value: f64) -> Option<BigInt> {
7522        if !value.is_finite() {
7523            return None;
7524        }
7525        if value == 0.0 {
7526            return Some(BigInt::from(0));
7527        }
7528
7529        let bits = value.to_bits();
7530        let negative = (bits >> 63) != 0;
7531        let exponent_bits = i32::try_from((bits >> 52) & 0x7ff).ok()?;
7532        let fraction = bits & ((1_u64 << 52) - 1);
7533        let (mantissa, exponent) = if exponent_bits == 0 {
7534            (fraction, -1074)
7535        } else {
7536            ((1_u64 << 52) | fraction, exponent_bits - 1023 - 52)
7537        };
7538
7539        let mut integer = if exponent >= 0 {
7540            BigInt::from(mantissa) << u32::try_from(exponent).ok()?
7541        } else {
7542            let shift = u32::try_from(-exponent).ok()?;
7543            if shift >= u64::BITS {
7544                return None;
7545            }
7546            let mask = (1_u64 << shift) - 1;
7547            if mantissa & mask != 0 {
7548                return None;
7549            }
7550            BigInt::from(mantissa >> shift)
7551        };
7552
7553        if negative {
7554            integer = -integer;
7555        }
7556        Some(integer)
7557    }
7558
7559    fn compile_pattern_or(
7560        &mut self,
7561        p: &ast::PatternMatchOr,
7562        pc: &mut PatternContext,
7563    ) -> CompileResult<()> {
7564        // Ensure the pattern is a MatchOr.
7565        let end = self.new_block(); // Create a new jump target label.
7566        let size = p.patterns.len();
7567        if size <= 1 {
7568            return Err(self.error(CodegenErrorType::SyntaxError(
7569                "MatchOr requires at least 2 patterns".to_owned(),
7570            )));
7571        }
7572
7573        // Save the current pattern context.
7574        let old_pc = pc.clone();
7575        // Simulate Py_INCREF on pc.stores by cloning it.
7576        pc.stores = pc.stores.clone();
7577        let mut control: Option<Vec<Name>> = None; // Will hold the capture list of the first alternative.
7578
7579        // Process each alternative.
7580        for (i, alt) in p.patterns.iter().enumerate() {
7581            // Create a fresh empty store for this alternative.
7582            pc.stores = Vec::new();
7583            // An irrefutable subpattern must be last (if allowed).
7584            pc.allow_irrefutable = (i == size - 1) && old_pc.allow_irrefutable;
7585            // Reset failure targets and the on_top counter.
7586            pc.fail_pop.clear();
7587            pc.on_top = 0;
7588            // Emit a COPY(1) instruction before compiling the alternative.
7589            self.set_source_range(alt.range());
7590            emit!(self, Instruction::Copy { i: 1 });
7591            self.compile_pattern(alt, pc)?;
7592
7593            let n_stores = pc.stores.len();
7594            if i == 0 {
7595                // Save the captured names from the first alternative.
7596                control = Some(pc.stores.clone());
7597            } else {
7598                let control_vec = control.as_ref().unwrap();
7599                if n_stores != control_vec.len() {
7600                    return Err(
7601                        self.error_ranged(CodegenErrorType::ConflictingNameBindPattern, p.range())
7602                    );
7603                } else if n_stores > 0 {
7604                    // Check that the names occur in the same order.
7605                    for i_control in (0..n_stores).rev() {
7606                        let name = &control_vec[i_control];
7607                        // Find the index of `name` in the current stores.
7608                        let i_stores =
7609                            pc.stores.iter().position(|n| n == name).ok_or_else(|| {
7610                                self.error_ranged(
7611                                    CodegenErrorType::ConflictingNameBindPattern,
7612                                    p.range(),
7613                                )
7614                            })?;
7615                        if i_control != i_stores {
7616                            // The orders differ; we must reorder.
7617                            assert!(i_stores < i_control, "expected i_stores < i_control");
7618                            let rotations = i_stores + 1;
7619                            // Rotate pc.stores: take a slice of the first `rotations` items...
7620                            let rotated = pc.stores[0..rotations].to_vec();
7621                            // Remove those elements.
7622                            for _ in 0..rotations {
7623                                pc.stores.remove(0);
7624                            }
7625                            // Insert the rotated slice at the appropriate index.
7626                            let insert_pos = i_control - i_stores;
7627                            for (j, elem) in rotated.into_iter().enumerate() {
7628                                pc.stores.insert(insert_pos + j, elem);
7629                            }
7630                            // Also perform the same rotation on the evaluation stack.
7631                            self.set_source_range(alt.range());
7632                            for _ in 0..=i_stores {
7633                                self.pattern_helper_rotate(alt.range(), i_control + 1);
7634                            }
7635                        }
7636                    }
7637                }
7638            }
7639            // Emit a jump to the common end label and reset any failure jump targets.
7640            self.set_source_range(alt.range());
7641            emit!(self, PseudoInstruction::Jump { delta: end });
7642            self.set_source_range(alt.range());
7643            self.emit_and_reset_fail_pop(pc, alt.range());
7644        }
7645
7646        // Restore the original pattern context.
7647        *pc = old_pc;
7648        // Simulate Py_INCREF on pc.stores.
7649        pc.stores = pc.stores.clone();
7650        // In C, old_pc.fail_pop is set to NULL to avoid freeing it later.
7651        // In Rust, old_pc is a local clone, so we need not worry about that.
7652
7653        // No alternative matched: pop the subject and fail.
7654        self.set_source_range(p.range());
7655        emit!(self, Instruction::PopTop);
7656        self.jump_to_fail_pop(pc, JumpOp::Jump);
7657
7658        // Use the label "end".
7659        // CPython codegen_pattern_or() emits USE_LABEL(c, end).
7660        self.use_cpython_label_block(end);
7661
7662        // Adjust the final captures.
7663        let n_stores = control.as_ref().unwrap().len();
7664        let n_rots = n_stores + 1 + pc.on_top + pc.stores.len();
7665        for i in 0..n_stores {
7666            // Rotate the capture to its proper place.
7667            self.set_source_range(p.range());
7668            self.pattern_helper_rotate(p.range(), n_rots);
7669            let name = &control.as_ref().unwrap()[i];
7670            // Check for duplicate binding.
7671            if pc.stores.contains(name) {
7672                return Err(self.error_ranged(
7673                    CodegenErrorType::DuplicateStore(name.to_string()),
7674                    p.range(),
7675                ));
7676            }
7677            pc.stores.push(name.clone());
7678        }
7679
7680        // Old context and control will be dropped automatically.
7681        // Finally, pop the copy of the subject.
7682        self.set_source_range(p.range());
7683        emit!(self, Instruction::PopTop);
7684        Ok(())
7685    }
7686
7687    fn compile_pattern_sequence(
7688        &mut self,
7689        p: &ast::PatternMatchSequence,
7690        pc: &mut PatternContext,
7691    ) -> CompileResult<()> {
7692        // Ensure the pattern is a MatchSequence.
7693        let patterns = &p.patterns; // a slice of ast::Pattern
7694        let size = patterns.len();
7695        let mut star: Option<usize> = None;
7696        let mut only_wildcard = true;
7697        let mut star_wildcard = false;
7698
7699        // Find a starred pattern, if it exists. There may be at most one.
7700        for (i, pattern) in patterns.iter().enumerate() {
7701            if pattern.is_match_star() {
7702                if star.is_some() {
7703                    return Err(self.error_ranged(
7704                        CodegenErrorType::MultipleStarredNamesInSequencePattern,
7705                        p.range,
7706                    ));
7707                }
7708                // star wildcard check
7709                star_wildcard = Self::pattern_wildcard_star_check(pattern);
7710                only_wildcard &= star_wildcard;
7711                star = Some(i);
7712                continue;
7713            }
7714            // wildcard check
7715            only_wildcard &= Self::pattern_wildcard_check(pattern);
7716        }
7717
7718        // Keep the subject on top during the sequence and length checks.
7719        pc.on_top += 1;
7720        self.set_source_range(p.range);
7721        emit!(self, Instruction::MatchSequence);
7722        self.set_source_range(p.range);
7723        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7724
7725        if star.is_none() {
7726            // No star: len(subject) == size
7727            self.set_source_range(p.range);
7728            emit!(self, Instruction::GetLen);
7729            self.set_source_range(p.range);
7730            self.emit_load_const(ConstantData::Integer { value: size.into() });
7731            self.set_source_range(p.range);
7732            emit!(
7733                self,
7734                Instruction::CompareOp {
7735                    opname: ComparisonOperator::Equal
7736                }
7737            );
7738            self.set_source_range(p.range);
7739            self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7740        } else if size > 1 {
7741            // Star exists: len(subject) >= size - 1
7742            self.set_source_range(p.range);
7743            emit!(self, Instruction::GetLen);
7744            self.set_source_range(p.range);
7745            self.emit_load_const(ConstantData::Integer {
7746                value: (size - 1).into(),
7747            });
7748            self.set_source_range(p.range);
7749            emit!(
7750                self,
7751                Instruction::CompareOp {
7752                    opname: ComparisonOperator::GreaterOrEqual
7753                }
7754            );
7755            self.set_source_range(p.range);
7756            self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7757        }
7758
7759        // Whatever comes next should consume the subject.
7760        pc.on_top -= 1;
7761        if only_wildcard {
7762            // ast::Patterns like: [] / [_] / [_, _] / [*_] / [_, *_] / [_, _, *_] / etc.
7763            self.set_source_range(p.range);
7764            emit!(self, Instruction::PopTop);
7765        } else if star_wildcard {
7766            self.pattern_helper_sequence_subscr(p.range, patterns, star.unwrap(), pc)?;
7767        } else {
7768            self.pattern_helper_sequence_unpack(p.range, patterns, star, pc)?;
7769        }
7770        Ok(())
7771    }
7772
7773    fn compile_pattern_value(
7774        &mut self,
7775        p: &ast::PatternMatchValue,
7776        pc: &mut PatternContext,
7777    ) -> CompileResult<()> {
7778        // Match CPython codegen_pattern_value(): compare, then normalize to bool
7779        // before the fail jump. Late IR folding will collapse COMPARE_OP+TO_BOOL
7780        // into COMPARE_OP bool(...) when applicable.
7781        if let Some(constant) = self.try_compile_match_value_constant(&p.value)? {
7782            self.set_source_range(p.value.range());
7783            self.emit_load_const(constant);
7784        } else if matches!(*p.value, ast::Expr::Attribute(_)) {
7785            self.compile_expression(&p.value)?;
7786        } else {
7787            if Self::is_unexpected_match_literal_constant(&p.value) {
7788                return Err(self.error_ranged(
7789                    CodegenErrorType::SyntaxError(
7790                        "unexpected constant inside of a literal pattern".to_string(),
7791                    ),
7792                    p.range,
7793                ));
7794            }
7795            return Err(self.error_ranged(
7796                CodegenErrorType::SyntaxError(
7797                    "patterns may only match literals and attribute lookups".to_string(),
7798                ),
7799                p.range,
7800            ));
7801        }
7802        self.set_source_range(p.range);
7803        emit!(
7804            self,
7805            Instruction::CompareOp {
7806                opname: bytecode::ComparisonOperator::Equal
7807            }
7808        );
7809        self.set_source_range(p.range);
7810        emit!(self, Instruction::ToBool);
7811        self.set_source_range(p.range);
7812        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7813        Ok(())
7814    }
7815
7816    fn compile_pattern_singleton(
7817        &mut self,
7818        p: &ast::PatternMatchSingleton,
7819        pc: &mut PatternContext,
7820    ) {
7821        // Load the singleton constant value.
7822        self.set_source_range(p.range);
7823        self.emit_load_const(match p.value {
7824            ast::Singleton::None => ConstantData::None,
7825            ast::Singleton::False => ConstantData::Boolean { value: false },
7826            ast::Singleton::True => ConstantData::Boolean { value: true },
7827        });
7828        // Compare using the "Is" operator.
7829        self.set_source_range(p.range);
7830        emit!(self, Instruction::IsOp { invert: Invert::No });
7831        // Jump to the failure label if the comparison is false.
7832        self.set_source_range(p.range);
7833        self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7834    }
7835
7836    fn compile_pattern(
7837        &mut self,
7838        pattern_type: &ast::Pattern,
7839        pattern_context: &mut PatternContext,
7840    ) -> CompileResult<()> {
7841        let prev_source_range = self.current_source_range;
7842        self.set_source_range(pattern_type.range());
7843        let result = match &pattern_type {
7844            ast::Pattern::MatchValue(pattern_type) => {
7845                self.compile_pattern_value(pattern_type, pattern_context)
7846            }
7847            ast::Pattern::MatchSingleton(pattern_type) => {
7848                self.compile_pattern_singleton(pattern_type, pattern_context);
7849                Ok(())
7850            }
7851            ast::Pattern::MatchSequence(pattern_type) => {
7852                self.compile_pattern_sequence(pattern_type, pattern_context)
7853            }
7854            ast::Pattern::MatchMapping(pattern_type) => {
7855                self.compile_pattern_mapping(pattern_type, pattern_context)
7856            }
7857            ast::Pattern::MatchClass(pattern_type) => {
7858                self.compile_pattern_class(pattern_type, pattern_context)
7859            }
7860            ast::Pattern::MatchStar(pattern_type) => {
7861                self.compile_pattern_star(pattern_type, pattern_context)
7862            }
7863            ast::Pattern::MatchAs(pattern_type) => {
7864                self.compile_pattern_as(pattern_type, pattern_context)
7865            }
7866            ast::Pattern::MatchOr(pattern_type) => {
7867                self.compile_pattern_or(pattern_type, pattern_context)
7868            }
7869        };
7870        self.set_source_range(prev_source_range);
7871        result
7872    }
7873
7874    fn compile_match_inner(
7875        &mut self,
7876        subject: &ast::Expr,
7877        cases: &[ast::MatchCase],
7878        pattern_context: &mut PatternContext,
7879    ) -> CompileResult<()> {
7880        self.compile_expression(subject)?;
7881        let end = self.new_block();
7882
7883        let num_cases = cases.len();
7884        assert!(num_cases > 0);
7885        let has_default =
7886            num_cases > 1 && Self::pattern_wildcard_check(&cases.last().unwrap().pattern);
7887
7888        let case_count = num_cases - usize::from(has_default);
7889        for (i, m) in cases.iter().enumerate().take(case_count) {
7890            // Only copy the subject if not on the last case
7891            if i != case_count - 1 {
7892                self.set_source_range(m.pattern.range());
7893                emit!(self, Instruction::Copy { i: 1 });
7894            }
7895
7896            pattern_context.stores = Vec::with_capacity(1);
7897            pattern_context.allow_irrefutable = m.guard.is_some() || i == num_cases - 1;
7898            pattern_context.fail_pop.clear();
7899            pattern_context.on_top = 0;
7900
7901            self.compile_pattern(&m.pattern, pattern_context)?;
7902            assert_eq!(pattern_context.on_top, 0);
7903
7904            for name in &pattern_context.stores {
7905                self.set_source_range(m.pattern.range());
7906                self.compile_name(name, NameUsage::Store)?;
7907            }
7908
7909            if let Some(ref guard) = m.guard {
7910                self.ensure_fail_pop(pattern_context, 0);
7911                self.compile_jump_if_inner(
7912                    guard,
7913                    false,
7914                    pattern_context.fail_pop[0],
7915                    Some(m.pattern.range()),
7916                )?;
7917            }
7918
7919            if i != case_count - 1 {
7920                if let Some(first_stmt) = m.body.first() {
7921                    self.set_source_range(first_stmt.range());
7922                }
7923                emit!(self, Instruction::PopTop);
7924                // CPython emits NEXT_LOCATION here; resolve it after redundant
7925                // NOP removal so a following pass NOP survives.
7926                self.set_last_emitted_lineno_override(ir::NEXT_LOCATION_OVERRIDE);
7927            }
7928
7929            self.compile_statements(&m.body)?;
7930            emit!(self, PseudoInstruction::Jump { delta: end });
7931            self.set_no_location();
7932            self.set_source_range(m.pattern.range());
7933            self.emit_and_reset_fail_pop(pattern_context, m.pattern.range());
7934        }
7935
7936        if has_default {
7937            let m = &cases[num_cases - 1];
7938            self.set_source_range(m.pattern.range());
7939            if num_cases == 1 {
7940                emit!(self, Instruction::PopTop);
7941            } else {
7942                emit!(self, Instruction::Nop);
7943            }
7944            if let Some(ref guard) = m.guard {
7945                self.compile_jump_if_inner(guard, false, end, Some(m.pattern.range()))?;
7946            }
7947            self.compile_statements(&m.body)?;
7948        }
7949        // CPython codegen_match_inner() emits USE_LABEL(c, end).
7950        self.use_cpython_label_block(end);
7951        Ok(())
7952    }
7953
7954    fn compile_match(
7955        &mut self,
7956        subject: &ast::Expr,
7957        cases: &[ast::MatchCase],
7958    ) -> CompileResult<()> {
7959        self.enter_conditional_block();
7960        let mut pattern_context = PatternContext::new();
7961        self.compile_match_inner(subject, cases, &mut pattern_context)?;
7962        self.leave_conditional_block();
7963        Ok(())
7964    }
7965
7966    /// [CPython `compiler_addcompare`](https://github.com/python/cpython/blob/627894459a84be3488a1789919679c997056a03c/Python/compile.c#L2880-L2924)
7967    fn compile_addcompare(&mut self, op: ast::CmpOp) {
7968        match op {
7969            ast::CmpOp::Eq => emit!(
7970                self,
7971                Instruction::CompareOp {
7972                    opname: ComparisonOperator::Equal
7973                }
7974            ),
7975            ast::CmpOp::NotEq => emit!(
7976                self,
7977                Instruction::CompareOp {
7978                    opname: ComparisonOperator::NotEqual
7979                }
7980            ),
7981            ast::CmpOp::Lt => emit!(
7982                self,
7983                Instruction::CompareOp {
7984                    opname: ComparisonOperator::Less
7985                }
7986            ),
7987            ast::CmpOp::LtE => emit!(
7988                self,
7989                Instruction::CompareOp {
7990                    opname: ComparisonOperator::LessOrEqual
7991                }
7992            ),
7993            ast::CmpOp::Gt => emit!(
7994                self,
7995                Instruction::CompareOp {
7996                    opname: ComparisonOperator::Greater
7997                }
7998            ),
7999            ast::CmpOp::GtE => {
8000                emit!(
8001                    self,
8002                    Instruction::CompareOp {
8003                        opname: ComparisonOperator::GreaterOrEqual
8004                    }
8005                )
8006            }
8007            ast::CmpOp::In => emit!(self, Instruction::ContainsOp { invert: Invert::No }),
8008            ast::CmpOp::NotIn => emit!(
8009                self,
8010                Instruction::ContainsOp {
8011                    invert: Invert::Yes
8012                }
8013            ),
8014            ast::CmpOp::Is => emit!(self, Instruction::IsOp { invert: Invert::No }),
8015            ast::CmpOp::IsNot => emit!(
8016                self,
8017                Instruction::IsOp {
8018                    invert: Invert::Yes
8019                }
8020            ),
8021        }
8022    }
8023
8024    /// Compile a chained comparison.
8025    ///
8026    /// ```py
8027    /// a == b == c == d
8028    /// ```
8029    ///
8030    /// Will compile into (pseudo code):
8031    ///
8032    /// ```py
8033    /// result = a == b
8034    /// if result:
8035    ///   result = b == c
8036    ///   if result:
8037    ///     result = c == d
8038    /// ```
8039    ///
8040    /// # See Also
8041    /// - [CPython `compiler_compare`](https://github.com/python/cpython/blob/627894459a84be3488a1789919679c997056a03c/Python/compile.c#L4678-L4717)
8042    fn compile_compare(
8043        &mut self,
8044        left: &ast::Expr,
8045        ops: &[ast::CmpOp],
8046        comparators: &[ast::Expr],
8047    ) -> CompileResult<()> {
8048        // Save the full Compare expression range for COMPARE_OP positions
8049        let compare_range = self.current_source_range;
8050        self.check_compare(compare_range, left, ops, comparators)?;
8051        let (last_op, mid_ops) = ops.split_last().unwrap();
8052        let (last_comparator, mid_comparators) = comparators.split_last().unwrap();
8053
8054        // initialize lhs outside of loop
8055        self.compile_expression(left)?;
8056
8057        if mid_comparators.is_empty() {
8058            self.compile_expression(last_comparator)?;
8059            self.set_source_range(compare_range);
8060            self.compile_addcompare(*last_op);
8061
8062            return Ok(());
8063        }
8064
8065        let cleanup = self.new_block();
8066
8067        // for all comparisons except the last (as the last one doesn't need a conditional jump)
8068        for (op, comparator) in mid_ops.iter().zip(mid_comparators) {
8069            self.compile_expression(comparator)?;
8070
8071            // store rhs for the next comparison in chain
8072            self.set_source_range(compare_range);
8073            emit!(self, Instruction::Swap { i: 2 });
8074            emit!(self, Instruction::Copy { i: 2 });
8075
8076            self.compile_addcompare(*op);
8077
8078            // if comparison result is false, we break with this value; if true, try the next one.
8079            emit!(self, Instruction::Copy { i: 1 });
8080            emit!(self, Instruction::ToBool);
8081            emit!(self, Instruction::PopJumpIfFalse { delta: cleanup });
8082            emit!(self, Instruction::PopTop);
8083        }
8084
8085        self.compile_expression(last_comparator)?;
8086        self.set_source_range(compare_range);
8087        self.compile_addcompare(*last_op);
8088
8089        let end = self.new_block();
8090        emit!(self, PseudoInstruction::JumpNoInterrupt { delta: end });
8091        self.set_no_location();
8092
8093        // early exit left us with stack: `rhs, comparison_result`. We need to clean up rhs.
8094        self.use_cpython_label_block(cleanup);
8095        emit!(self, Instruction::Swap { i: 2 });
8096        emit!(self, Instruction::PopTop);
8097
8098        self.use_cpython_label_block(end);
8099        Ok(())
8100    }
8101
8102    fn compile_jump_if_compare(
8103        &mut self,
8104        left: &ast::Expr,
8105        ops: &[ast::CmpOp],
8106        comparators: &[ast::Expr],
8107        condition: bool,
8108        target_block: BlockIdx,
8109    ) -> CompileResult<()> {
8110        let compare_range = self.current_source_range;
8111        self.check_compare(compare_range, left, ops, comparators)?;
8112        let (last_op, mid_ops) = ops.split_last().unwrap();
8113        let (last_comparator, mid_comparators) = comparators.split_last().unwrap();
8114
8115        if mid_comparators.is_empty() {
8116            self.compile_expression(left)?;
8117            self.compile_expression(last_comparator)?;
8118            self.set_source_range(compare_range);
8119            self.compile_addcompare(*last_op);
8120            self.emit_pop_jump_by_condition(condition, target_block);
8121            return Ok(());
8122        }
8123
8124        let cleanup = self.new_block();
8125        self.compile_expression(left)?;
8126
8127        for (op, comparator) in mid_ops.iter().zip(mid_comparators) {
8128            self.compile_expression(comparator)?;
8129            self.set_source_range(compare_range);
8130            emit!(self, Instruction::Swap { i: 2 });
8131            emit!(self, Instruction::Copy { i: 2 });
8132            self.compile_addcompare(*op);
8133            emit!(self, Instruction::ToBool);
8134            emit!(self, Instruction::PopJumpIfFalse { delta: cleanup });
8135        }
8136
8137        self.compile_expression(last_comparator)?;
8138        self.set_source_range(compare_range);
8139        self.compile_addcompare(*last_op);
8140        emit!(self, Instruction::ToBool);
8141        self.emit_pop_jump_by_condition(condition, target_block);
8142        let end = self.new_block();
8143        emit!(self, PseudoInstruction::JumpNoInterrupt { delta: end });
8144        self.set_no_location();
8145
8146        self.use_cpython_label_block(cleanup);
8147        emit!(self, Instruction::PopTop);
8148        if !condition {
8149            emit!(
8150                self,
8151                PseudoInstruction::JumpNoInterrupt {
8152                    delta: target_block
8153                }
8154            );
8155            self.set_no_location();
8156        }
8157
8158        self.use_cpython_label_block(end);
8159        Ok(())
8160    }
8161
8162    fn emit_pop_jump_by_condition(&mut self, condition: bool, target_block: BlockIdx) {
8163        if condition {
8164            emit!(
8165                self,
8166                Instruction::PopJumpIfTrue {
8167                    delta: target_block
8168                }
8169            );
8170        } else {
8171            emit!(
8172                self,
8173                Instruction::PopJumpIfFalse {
8174                    delta: target_block,
8175                }
8176            );
8177        }
8178    }
8179
8180    fn compile_annotation(&mut self, annotation: &ast::Expr) -> CompileResult<()> {
8181        if self.future_annotations {
8182            self.set_source_range(annotation.range());
8183            self.emit_load_const(ConstantData::Str {
8184                value: UnparseExpr::new(annotation, &self.source_file)
8185                    .to_string()
8186                    .into(),
8187            });
8188        } else {
8189            let was_in_annotation = self.in_annotation;
8190            self.in_annotation = true;
8191
8192            // Special handling for starred annotations (*Ts -> Unpack[Ts])
8193            let result = match annotation {
8194                ast::Expr::Starred(ast::ExprStarred { value, .. }) => {
8195                    // *args: *Ts (where Ts is a TypeVarTuple).
8196                    // Do [annotation_value] = [*Ts].
8197                    let saved_range = self.current_source_range;
8198                    self.compile_expression(value)?;
8199                    self.set_source_range(saved_range);
8200                    emit!(self, Instruction::UnpackSequence { count: 1 });
8201                    Ok(())
8202                }
8203                _ => self.compile_expression(annotation),
8204            };
8205
8206            self.in_annotation = was_in_annotation;
8207            result?;
8208        }
8209        Ok(())
8210    }
8211
8212    fn compile_check_annotation_expression(&mut self, expression: &ast::Expr) -> CompileResult<()> {
8213        self.compile_expression(expression)?;
8214        self.set_source_range(expression.range());
8215        emit!(self, Instruction::PopTop);
8216        Ok(())
8217    }
8218
8219    fn compile_check_annotation_subscript(&mut self, expression: &ast::Expr) -> CompileResult<()> {
8220        match expression {
8221            ast::Expr::Slice(ast::ExprSlice {
8222                lower, upper, step, ..
8223            }) => {
8224                if let Some(lower) = lower {
8225                    self.compile_check_annotation_expression(lower)?;
8226                }
8227                if let Some(upper) = upper {
8228                    self.compile_check_annotation_expression(upper)?;
8229                }
8230                if let Some(step) = step {
8231                    self.compile_check_annotation_expression(step)?;
8232                }
8233            }
8234            ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
8235                for element in elts {
8236                    self.compile_check_annotation_subscript(element)?;
8237                }
8238            }
8239            _ => self.compile_check_annotation_expression(expression)?,
8240        }
8241        Ok(())
8242    }
8243
8244    fn compile_annotated_assign(
8245        &mut self,
8246        target: &ast::Expr,
8247        annotation: &ast::Expr,
8248        value: Option<&ast::Expr>,
8249        simple: bool,
8250        loc: TextRange,
8251    ) -> CompileResult<()> {
8252        // Perform the actual assignment first
8253        if let Some(value) = value {
8254            self.compile_expression(value)?;
8255            self.compile_store(target)?;
8256        }
8257
8258        // If we have a simple name in module or class scope, store annotation
8259        if simple
8260            && !self.ctx.in_func()
8261            && let ast::Expr::Name(ast::ExprName { id, .. }) = target
8262        {
8263            if self.future_annotations {
8264                // PEP 563: Store stringified annotation directly to __annotations__
8265                // Compile annotation as string
8266                self.compile_annotation(annotation)?;
8267                self.set_source_range(loc);
8268                // Load __annotations__
8269                let annotations_name = self.name("__annotations__");
8270                emit!(
8271                    self,
8272                    Instruction::LoadName {
8273                        namei: annotations_name
8274                    }
8275                );
8276                // Load the variable name
8277                self.set_source_range(loc);
8278                self.emit_load_const(ConstantData::Str {
8279                    value: self.mangle(id).as_str().into(),
8280                });
8281                // Store: __annotations__[name] = annotation
8282                self.set_source_range(loc);
8283                emit!(self, Instruction::StoreSubscr);
8284            } else {
8285                // PEP 649: Handle conditional annotations
8286                if self.current_symbol_table().has_conditional_annotations {
8287                    let scope_type = self.current_symbol_table().typ;
8288                    let in_conditional_block = self.current_code_info().in_conditional_block > 0;
8289                    let is_conditional =
8290                        matches!(scope_type, CompilerScope::Module) || in_conditional_block;
8291
8292                    if is_conditional {
8293                        let code_info = self.current_code_info();
8294                        let annotation_index = code_info.next_conditional_annotation_index;
8295                        code_info.next_conditional_annotation_index += 1;
8296
8297                        self.set_source_range(loc);
8298                        if matches!(scope_type, CompilerScope::Class) {
8299                            let i = self.get_cell_var_index("__conditional_annotations__");
8300                            emit!(self, Instruction::LoadDeref { i });
8301                        } else {
8302                            let namei = self.name("__conditional_annotations__");
8303                            emit!(self, Instruction::LoadName { namei });
8304                        }
8305                        self.emit_load_const(ConstantData::Integer {
8306                            value: annotation_index.into(),
8307                        });
8308                        emit!(self, Instruction::SetAdd { i: 1 });
8309                        emit!(self, Instruction::PopTop);
8310                    }
8311                }
8312            }
8313        }
8314
8315        if value.is_none() {
8316            match target {
8317                ast::Expr::Attribute(ast::ExprAttribute { value, .. }) => {
8318                    self.compile_check_annotation_expression(value)?;
8319                }
8320                ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
8321                    self.compile_check_annotation_expression(value)?;
8322                    self.compile_check_annotation_subscript(slice)?;
8323                }
8324                _ => {}
8325            }
8326        }
8327
8328        Ok(())
8329    }
8330
8331    fn compile_store(&mut self, target: &ast::Expr) -> CompileResult<()> {
8332        let prev_source_range = self.current_source_range;
8333        self.set_source_range(target.range());
8334        let result = (|| -> CompileResult<()> {
8335            match &target {
8336                ast::Expr::Name(ast::ExprName { id, .. }) => self.store_name(id)?,
8337                ast::Expr::Subscript(ast::ExprSubscript {
8338                    value, slice, ctx, ..
8339                }) => {
8340                    self.compile_subscript(value, slice, *ctx)?;
8341                }
8342                ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
8343                    self.maybe_add_static_attribute_to_class(value, attr.as_str());
8344                    self.compile_expression(value)?;
8345                    self.set_source_range(self.update_start_location_to_match_attr(
8346                        target.range(),
8347                        target.range(),
8348                        attr.as_str(),
8349                    ));
8350                    let namei = self.name(attr.as_str());
8351                    emit!(self, Instruction::StoreAttr { namei });
8352                }
8353                ast::Expr::List(ast::ExprList { elts, .. })
8354                | ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
8355                    let mut seen_star = false;
8356
8357                    // Scan for star args:
8358                    for (i, element) in elts.iter().enumerate() {
8359                        if matches!(element, ast::Expr::Starred(_)) && !seen_star {
8360                            let before = i;
8361                            let after = elts.len() - i - 1;
8362                            if before >= (1 << 8) || after >= ((i32::MAX as usize) >> 8) {
8363                                return Err(self.error_ranged(
8364                                    CodegenErrorType::TooManyStarUnpack,
8365                                    target.range(),
8366                                ));
8367                            }
8368                            let before = before.to_u8().ok_or_else(|| {
8369                                self.error_ranged(
8370                                    CodegenErrorType::TooManyStarUnpack,
8371                                    target.range(),
8372                                )
8373                            })?;
8374                            let after = after.to_u32();
8375                            let counts = bytecode::UnpackExArgs { before, after };
8376                            emit!(self, Instruction::UnpackEx { counts });
8377                            seen_star = true;
8378                        } else if matches!(element, ast::Expr::Starred(_)) {
8379                            return Err(self.error(CodegenErrorType::MultipleStarArgs));
8380                        }
8381                    }
8382
8383                    if !seen_star {
8384                        emit!(
8385                            self,
8386                            Instruction::UnpackSequence {
8387                                count: elts.len().to_u32(),
8388                            }
8389                        );
8390                    }
8391
8392                    for element in elts {
8393                        if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = &element {
8394                            self.compile_store(value)?;
8395                        } else {
8396                            self.compile_store(element)?;
8397                        }
8398                    }
8399                }
8400                _ => {
8401                    return Err(self.error(match target {
8402                        ast::Expr::Starred(_) => CodegenErrorType::SyntaxError(
8403                            "starred assignment target must be in a list or tuple".to_owned(),
8404                        ),
8405                        _ => CodegenErrorType::Assign(target.python_name()),
8406                    }));
8407                }
8408            }
8409            Ok(())
8410        })();
8411
8412        self.set_source_range(prev_source_range);
8413        result
8414    }
8415
8416    fn compile_augassign(
8417        &mut self,
8418        target: &ast::Expr,
8419        op: ast::Operator,
8420        value: &ast::Expr,
8421    ) -> CompileResult<()> {
8422        let stmt_range = self.current_source_range;
8423        let target_range = target.range();
8424        enum AugAssignKind<'a> {
8425            Name {
8426                id: &'a Name,
8427            },
8428            Subscript {
8429                use_slice_opt: bool,
8430            },
8431            Attr {
8432                idx: bytecode::NameIdx,
8433                attr_range: TextRange,
8434            },
8435        }
8436
8437        let kind = match &target {
8438            ast::Expr::Name(ast::ExprName { id, .. }) => {
8439                self.set_source_range(target_range);
8440                self.compile_name(id, NameUsage::Load)?;
8441                AugAssignKind::Name { id }
8442            }
8443            ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
8444                let use_slice_opt = self.should_apply_two_element_slice_optimization(slice);
8445                self.compile_expression(value)?;
8446                self.set_source_range(target_range);
8447                if use_slice_opt {
8448                    let ast::Expr::Slice(slice_expr) = slice.as_ref() else {
8449                        unreachable!(
8450                            "should_use_slice_optimization should only return true for ast::Expr::Slice"
8451                        );
8452                    };
8453                    self.compile_slice_two_parts(slice_expr)?;
8454                    self.set_source_range(target_range);
8455                    emit!(self, Instruction::Copy { i: 3 });
8456                    emit!(self, Instruction::Copy { i: 3 });
8457                    emit!(self, Instruction::Copy { i: 3 });
8458                    emit!(self, Instruction::BinarySlice);
8459                } else {
8460                    self.compile_expression(slice)?;
8461                    self.set_source_range(target_range);
8462                    emit!(self, Instruction::Copy { i: 2 });
8463                    emit!(self, Instruction::Copy { i: 2 });
8464                    emit!(
8465                        self,
8466                        Instruction::BinaryOp {
8467                            op: BinaryOperator::Subscr
8468                        }
8469                    );
8470                }
8471                AugAssignKind::Subscript { use_slice_opt }
8472            }
8473            ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
8474                let attr = attr.as_str();
8475                self.compile_expression(value)?;
8476                let attr_range =
8477                    self.update_start_location_to_match_attr(target_range, target_range, attr);
8478                self.set_source_range(target_range);
8479                emit!(self, Instruction::Copy { i: 1 });
8480                let idx = self.name(attr);
8481                self.set_source_range(attr_range);
8482                self.emit_load_attr(idx);
8483                AugAssignKind::Attr { idx, attr_range }
8484            }
8485            _ => {
8486                return Err(self.error(CodegenErrorType::Assign(target.python_name())));
8487            }
8488        };
8489
8490        self.compile_expression(value)?;
8491        self.set_source_range(stmt_range);
8492        self.compile_op(op, true);
8493
8494        match kind {
8495            AugAssignKind::Name { id } => {
8496                // stack: RESULT
8497                self.set_source_range(target_range);
8498                self.compile_name(id, NameUsage::Store)?;
8499            }
8500            AugAssignKind::Subscript { use_slice_opt } => {
8501                self.set_source_range(target_range);
8502                if use_slice_opt {
8503                    // stack: CONTAINER START STOP RESULT
8504                    emit!(self, Instruction::Swap { i: 4 });
8505                    emit!(self, Instruction::Swap { i: 3 });
8506                    emit!(self, Instruction::Swap { i: 2 });
8507                    emit!(self, Instruction::StoreSlice);
8508                } else {
8509                    // stack: CONTAINER SLICE RESULT
8510                    emit!(self, Instruction::Swap { i: 3 });
8511                    emit!(self, Instruction::Swap { i: 2 });
8512                    emit!(self, Instruction::StoreSubscr);
8513                }
8514            }
8515            AugAssignKind::Attr { idx, attr_range } => {
8516                // stack: CONTAINER RESULT
8517                self.set_source_range(attr_range);
8518                emit!(self, Instruction::Swap { i: 2 });
8519                emit!(self, Instruction::StoreAttr { namei: idx });
8520            }
8521        }
8522
8523        Ok(())
8524    }
8525
8526    fn compile_op(&mut self, op: ast::Operator, inplace: bool) {
8527        let bin_op = match op {
8528            ast::Operator::Add => BinaryOperator::Add,
8529            ast::Operator::Sub => BinaryOperator::Subtract,
8530            ast::Operator::Mult => BinaryOperator::Multiply,
8531            ast::Operator::MatMult => BinaryOperator::MatrixMultiply,
8532            ast::Operator::Div => BinaryOperator::TrueDivide,
8533            ast::Operator::FloorDiv => BinaryOperator::FloorDivide,
8534            ast::Operator::Mod => BinaryOperator::Remainder,
8535            ast::Operator::Pow => BinaryOperator::Power,
8536            ast::Operator::LShift => BinaryOperator::Lshift,
8537            ast::Operator::RShift => BinaryOperator::Rshift,
8538            ast::Operator::BitOr => BinaryOperator::Or,
8539            ast::Operator::BitXor => BinaryOperator::Xor,
8540            ast::Operator::BitAnd => BinaryOperator::And,
8541        };
8542
8543        let op = if inplace { bin_op.as_inplace() } else { bin_op };
8544        emit!(self, Instruction::BinaryOp { op })
8545    }
8546
8547    /// Implement boolean short circuit evaluation logic.
8548    /// https://en.wikipedia.org/wiki/Short-circuit_evaluation
8549    ///
8550    /// This means, in a boolean statement 'x and y' the variable y will
8551    /// not be evaluated when x is false.
8552    ///
8553    /// The idea is to jump to a label if the expression is either true or false
8554    /// (indicated by the condition parameter).
8555    fn compile_jump_if_inner(
8556        &mut self,
8557        expression: &ast::Expr,
8558        condition: bool,
8559        target_block: BlockIdx,
8560        source_range: Option<TextRange>,
8561    ) -> CompileResult<()> {
8562        let prev_source_range = self.current_source_range;
8563        self.set_source_range(source_range.unwrap_or_else(|| expression.range()));
8564
8565        // Compile expression for test, and jump to label if false
8566        let result = match &expression {
8567            ast::Expr::BoolOp(ast::ExprBoolOp { op, values, .. }) => {
8568                let (last_value, prefix_values) = values.split_last().unwrap();
8569                let cond2 = matches!(op, ast::BoolOp::Or);
8570                let next2 = if cond2 != condition {
8571                    self.new_block()
8572                } else {
8573                    target_block
8574                };
8575
8576                for value in prefix_values {
8577                    self.compile_jump_if_inner(value, cond2, next2, source_range)?;
8578                }
8579                self.compile_jump_if_inner(last_value, condition, target_block, source_range)?;
8580
8581                if next2 != target_block {
8582                    self.use_cpython_label_block(next2);
8583                }
8584                Ok(())
8585            }
8586            ast::Expr::UnaryOp(ast::ExprUnaryOp {
8587                op: ast::UnaryOp::Not,
8588                operand,
8589                ..
8590            }) => self.compile_jump_if_inner(operand, !condition, target_block, source_range),
8591            ast::Expr::If(ast::ExprIf {
8592                test, body, orelse, ..
8593            }) => {
8594                let end = self.new_block();
8595                let next2 = self.new_block();
8596                self.compile_jump_if_inner(test, false, next2, source_range)?;
8597                self.compile_jump_if_inner(body, condition, target_block, source_range)?;
8598                emit!(self, PseudoInstruction::JumpNoInterrupt { delta: end });
8599                self.set_no_location();
8600
8601                self.use_cpython_label_block(next2);
8602                self.compile_jump_if_inner(orelse, condition, target_block, source_range)?;
8603
8604                self.use_cpython_label_block(end);
8605                Ok(())
8606            }
8607            ast::Expr::Compare(ast::ExprCompare {
8608                left,
8609                ops,
8610                comparators,
8611                ..
8612            }) if ops.len() > 1 => {
8613                self.set_source_range(expression.range());
8614                self.compile_jump_if_compare(left, ops, comparators, condition, target_block)
8615            }
8616            _ => {
8617                // Fall back case which always will work!
8618                self.compile_expression(expression)?;
8619                self.set_source_range(expression.range());
8620                emit!(self, Instruction::ToBool);
8621                if condition {
8622                    emit!(
8623                        self,
8624                        Instruction::PopJumpIfTrue {
8625                            delta: target_block,
8626                        }
8627                    );
8628                } else {
8629                    emit!(
8630                        self,
8631                        Instruction::PopJumpIfFalse {
8632                            delta: target_block,
8633                        }
8634                    );
8635                }
8636                Ok(())
8637            }
8638        };
8639
8640        self.set_source_range(prev_source_range);
8641        result
8642    }
8643
8644    fn compile_jump_if(
8645        &mut self,
8646        expression: &ast::Expr,
8647        condition: bool,
8648        target_block: BlockIdx,
8649    ) -> CompileResult<()> {
8650        self.compile_jump_if_inner(expression, condition, target_block, None)
8651    }
8652
8653    /// Compile a boolean operation as an expression.
8654    /// This means, that the last value remains on the stack.
8655    fn compile_bool_op(&mut self, op: ast::BoolOp, values: &[ast::Expr]) -> CompileResult<()> {
8656        let boolop_range = self.current_source_range;
8657        let after_block = self.new_block();
8658        let (last_value, prefix_values) = values.split_last().unwrap();
8659
8660        for value in prefix_values {
8661            self.compile_expression(value)?;
8662            self.set_source_range(boolop_range);
8663            self.emit_short_circuit_test(op, after_block);
8664            self.set_source_range(boolop_range);
8665            emit!(self, Instruction::PopTop);
8666        }
8667
8668        self.compile_expression(last_value)?;
8669        self.use_cpython_label_block(after_block);
8670        Ok(())
8671    }
8672
8673    /// Emit CPython-style pseudo conditional jump for short-circuit evaluation.
8674    /// flowgraph.c lowers it to `COPY 1; TO_BOOL; POP_JUMP_IF_*`.
8675    fn emit_short_circuit_test(&mut self, op: ast::BoolOp, target: BlockIdx) {
8676        match op {
8677            ast::BoolOp::And => {
8678                emit!(self, PseudoInstruction::JumpIfFalse { delta: target });
8679            }
8680            ast::BoolOp::Or => {
8681                emit!(self, PseudoInstruction::JumpIfTrue { delta: target });
8682            }
8683        }
8684    }
8685
8686    fn compile_subdict(
8687        &mut self,
8688        items: &[ast::DictItem],
8689        begin: usize,
8690        end: usize,
8691        range: TextRange,
8692    ) -> CompileResult<()> {
8693        let n = end - begin;
8694        let big = n * 2 > STACK_USE_GUIDELINE as usize;
8695        if big {
8696            self.set_source_range(range);
8697            emit!(self, Instruction::BuildMap { count: 0 });
8698        }
8699        for item in &items[begin..end] {
8700            self.compile_expression(item.key.as_ref().unwrap())?;
8701            self.compile_expression(&item.value)?;
8702            if big {
8703                self.set_source_range(range);
8704                emit!(self, Instruction::MapAdd { i: 1 });
8705            }
8706        }
8707        if !big {
8708            self.set_source_range(range);
8709            emit!(self, Instruction::BuildMap { count: n.to_u32() });
8710        }
8711        Ok(())
8712    }
8713
8714    fn compile_dict(&mut self, items: &[ast::DictItem], range: TextRange) -> CompileResult<()> {
8715        let n = items.len();
8716        let mut have_dict = false;
8717        let mut elements = 0usize;
8718
8719        for (i, item) in items.iter().enumerate() {
8720            if item.key.is_none() {
8721                if elements != 0 {
8722                    self.compile_subdict(items, i - elements, i, range)?;
8723                    if have_dict {
8724                        self.set_source_range(range);
8725                        emit!(self, Instruction::DictUpdate { i: 1 });
8726                    }
8727                    have_dict = true;
8728                    elements = 0;
8729                }
8730                if !have_dict {
8731                    self.set_source_range(range);
8732                    emit!(self, Instruction::BuildMap { count: 0 });
8733                    have_dict = true;
8734                }
8735                self.compile_expression(&item.value)?;
8736                self.set_source_range(range);
8737                emit!(self, Instruction::DictUpdate { i: 1 });
8738            } else if elements * 2 > STACK_USE_GUIDELINE as usize {
8739                self.compile_subdict(items, i - elements, i + 1, range)?;
8740                if have_dict {
8741                    self.set_source_range(range);
8742                    emit!(self, Instruction::DictUpdate { i: 1 });
8743                }
8744                have_dict = true;
8745                elements = 0;
8746            } else {
8747                elements += 1;
8748            }
8749        }
8750
8751        if elements != 0 {
8752            self.compile_subdict(items, n - elements, n, range)?;
8753            if have_dict {
8754                self.set_source_range(range);
8755                emit!(self, Instruction::DictUpdate { i: 1 });
8756            }
8757            have_dict = true;
8758        }
8759        if !have_dict {
8760            self.set_source_range(range);
8761            emit!(self, Instruction::BuildMap { count: 0 });
8762        }
8763
8764        Ok(())
8765    }
8766
8767    /// Compile the yield-from/await sequence using SEND/END_SEND/CLEANUP_THROW.
8768    /// compiler_add_yield_from
8769    /// This generates:
8770    ///   send:
8771    ///     SEND exit
8772    ///     SETUP_FINALLY fail (via exception table)
8773    ///     YIELD_VALUE 1
8774    ///     POP_BLOCK (NO_LOCATION)
8775    ///     RESUME
8776    ///     JUMP send
8777    ///   fail:
8778    ///     CLEANUP_THROW
8779    ///     JUMP exit
8780    ///   exit:
8781    ///     END_SEND
8782    fn compile_yield_from_sequence(&mut self, is_await: bool) -> BlockIdx {
8783        let send_block = self.new_block();
8784        let fail_block = self.new_block();
8785        let exit_block = self.new_block();
8786
8787        // send:
8788        self.use_cpython_label_block(send_block);
8789        emit!(self, Instruction::Send { delta: exit_block });
8790
8791        // SETUP_FINALLY fail - set up exception handler for YIELD_VALUE
8792        emit!(self, PseudoInstruction::SetupFinally { delta: fail_block });
8793
8794        // YIELD_VALUE with arg=1 (yield-from/await mode - not wrapped for async gen)
8795        emit!(self, Instruction::YieldValue { arg: 1 });
8796
8797        // POP_BLOCK before RESUME
8798        emit!(self, PseudoInstruction::PopBlock);
8799        self.set_no_location();
8800
8801        // RESUME
8802        emit!(
8803            self,
8804            Instruction::Resume {
8805                context: if is_await {
8806                    oparg::ResumeContext::from(oparg::ResumeLocation::AfterAwait)
8807                } else {
8808                    oparg::ResumeContext::from(oparg::ResumeLocation::AfterYieldFrom)
8809                }
8810            }
8811        );
8812
8813        // JUMP_BACKWARD_NO_INTERRUPT send
8814        emit!(
8815            self,
8816            PseudoInstruction::JumpNoInterrupt { delta: send_block }
8817        );
8818
8819        // fail: CLEANUP_THROW
8820        // Stack when exception: [receiver, yielded_value, exc]
8821        // CLEANUP_THROW: [sub_iter, last_sent_val, exc] -> [None, value]
8822        // CPython lets this block fall through to END_SEND during codegen;
8823        // push_cold_blocks_to_end later inserts the no-interrupt jump after
8824        // moving the cold fail block behind the warm exit path.
8825        self.use_cpython_label_block(fail_block);
8826        emit!(self, Instruction::CleanupThrow);
8827
8828        // exit: END_SEND
8829        // Stack: [receiver, value] (from SEND) or [None, value] (from CLEANUP_THROW)
8830        // END_SEND: [receiver/None, value] -> [value]
8831        self.use_cpython_label_block(exit_block);
8832        emit!(self, Instruction::EndSend);
8833
8834        send_block
8835    }
8836
8837    fn ast_constant_value(&self, expr: &ast::Expr) -> Option<ConstantData> {
8838        expr.as_constant_expr()
8839            .map(|expr| ast_constant_value_to_constant_data(expr.value.clone()))
8840    }
8841
8842    fn single_runtime_interpolation(
8843        expr_tstring: &ast::ExprTString,
8844    ) -> Option<(&ast::ConstantValue, Option<&ast::Expr>)> {
8845        let tstring = expr_tstring.as_single_part_tstring()?;
8846        let interpolation = tstring.elements.first()?.as_interpolation()?;
8847        Some((
8848            interpolation.runtime_str.as_ref()?,
8849            interpolation.runtime_interpolation_format_spec.as_deref(),
8850        ))
8851    }
8852
8853    fn compile_expression(&mut self, expression: &ast::Expr) -> CompileResult<()> {
8854        trace!("Compiling {expression:?}");
8855        let range = expression.range();
8856        self.set_source_range(range);
8857
8858        if let Some(constant) = self.ast_constant_value(expression) {
8859            self.emit_load_const(constant);
8860            return Ok(());
8861        }
8862
8863        match &expression {
8864            ast::Expr::Call(ast::ExprCall {
8865                func, arguments, ..
8866            }) => self.compile_call(func, arguments)?,
8867            ast::Expr::BoolOp(ast::ExprBoolOp { op, values, .. }) => {
8868                self.compile_bool_op(*op, values)?
8869            }
8870            ast::Expr::BinOp(ast::ExprBinOp {
8871                left, op, right, ..
8872            }) => {
8873                self.compile_expression(left)?;
8874                self.compile_expression(right)?;
8875
8876                // Restore full expression range before emitting the operation
8877                self.set_source_range(range);
8878                self.compile_op(*op, false);
8879            }
8880            ast::Expr::Subscript(ast::ExprSubscript {
8881                value, slice, ctx, ..
8882            }) => {
8883                self.compile_subscript(value, slice, *ctx)?;
8884            }
8885            ast::Expr::UnaryOp(ast::ExprUnaryOp { op, operand, .. }) => {
8886                self.compile_expression(operand)?;
8887
8888                // Restore full expression range before emitting the operation
8889                self.set_source_range(range);
8890                match op {
8891                    ast::UnaryOp::UAdd => emit!(
8892                        self,
8893                        Instruction::CallIntrinsic1 {
8894                            func: bytecode::IntrinsicFunction1::UnaryPositive
8895                        }
8896                    ),
8897                    ast::UnaryOp::USub => emit!(self, Instruction::UnaryNegative),
8898                    ast::UnaryOp::Not => {
8899                        emit!(self, Instruction::ToBool);
8900                        emit!(self, Instruction::UnaryNot);
8901                    }
8902                    ast::UnaryOp::Invert => emit!(self, Instruction::UnaryInvert),
8903                };
8904            }
8905            ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
8906                // Check for super() attribute access optimization
8907                if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
8908                    // super().attr or super(cls, self).attr optimization
8909                    // Stack: [global_super, class, self] → LOAD_SUPER_ATTR → [attr]
8910                    let ast::Expr::Call(ast::ExprCall {
8911                        func: super_func, ..
8912                    }) = value.as_ref()
8913                    else {
8914                        unreachable!("can_optimize_super_call only accepts calls");
8915                    };
8916                    self.load_args_for_super(&super_type, super_func.range(), value.range())?;
8917                    let attr_access_range =
8918                        self.update_start_location_to_match_attr(range, range, attr.as_str());
8919                    self.set_source_range(range);
8920                    let idx = self.name(attr.as_str());
8921                    match super_type {
8922                        SuperCallType::TwoArg { .. } => {
8923                            self.emit_load_super_attr(idx);
8924                        }
8925                        SuperCallType::ZeroArg => {
8926                            self.emit_load_zero_super_attr(idx);
8927                        }
8928                    }
8929                    self.set_source_range(attr_access_range);
8930                    emit!(self, Instruction::Nop);
8931                } else {
8932                    // Normal attribute access
8933                    self.compile_expression(value)?;
8934                    self.set_source_range(self.update_start_location_to_match_attr(
8935                        range,
8936                        range,
8937                        attr.as_str(),
8938                    ));
8939                    let idx = self.name(attr.as_str());
8940                    self.emit_load_attr(idx);
8941                }
8942            }
8943            ast::Expr::Compare(ast::ExprCompare {
8944                left,
8945                ops,
8946                comparators,
8947                ..
8948            }) => {
8949                self.compile_compare(left, ops, comparators)?;
8950            }
8951            ast::Expr::Constant(ast::ExprConstant { value, .. }) => {
8952                self.emit_load_const(ast_constant_value_to_constant_data(value.clone()));
8953            }
8954            ast::Expr::List(ast::ExprList { elts, range, .. }) => {
8955                self.set_source_range(*range);
8956                self.starunpack_helper(elts, 0, CollectionType::List)?;
8957            }
8958            ast::Expr::Tuple(ast::ExprTuple { elts, range, .. }) => {
8959                self.set_source_range(*range);
8960                self.starunpack_helper(elts, 0, CollectionType::Tuple)?;
8961            }
8962            ast::Expr::Set(ast::ExprSet { elts, range, .. }) => {
8963                self.set_source_range(*range);
8964                self.starunpack_helper(elts, 0, CollectionType::Set)?;
8965            }
8966            ast::Expr::Dict(ast::ExprDict { items, range, .. }) => {
8967                self.compile_dict(items, *range)?;
8968            }
8969            ast::Expr::Slice(ast::ExprSlice {
8970                lower,
8971                upper,
8972                step,
8973                range,
8974                ..
8975            }) => {
8976                if let Some(folded_const) = self.try_fold_constant_slice(
8977                    lower.as_deref(),
8978                    upper.as_deref(),
8979                    step.as_deref(),
8980                )? {
8981                    self.set_source_range(*range);
8982                    self.emit_load_const(folded_const);
8983                    return Ok(());
8984                }
8985                if let Some(lower) = lower {
8986                    self.compile_expression(lower)?;
8987                } else {
8988                    self.set_source_range(*range);
8989                    self.emit_load_const(ConstantData::None);
8990                }
8991                if let Some(upper) = upper {
8992                    self.compile_expression(upper)?;
8993                } else {
8994                    self.set_source_range(*range);
8995                    self.emit_load_const(ConstantData::None);
8996                }
8997                if let Some(step) = step {
8998                    self.compile_expression(step)?;
8999                }
9000                let argc = match step {
9001                    Some(_) => BuildSliceArgCount::Three,
9002                    None => BuildSliceArgCount::Two,
9003                };
9004                self.set_source_range(*range);
9005                emit!(self, Instruction::BuildSlice { argc });
9006            }
9007            ast::Expr::Yield(ast::ExprYield { value, .. }) => {
9008                if !self.ctx.in_func() {
9009                    return Err(self.error(CodegenErrorType::InvalidYield));
9010                }
9011                self.mark_generator();
9012                match value {
9013                    Some(expression) => self.compile_expression(expression)?,
9014                    Option::None => self.emit_load_const(ConstantData::None),
9015                };
9016                self.set_source_range(range);
9017                if self.ctx.func == FunctionContext::AsyncFunction {
9018                    emit!(
9019                        self,
9020                        Instruction::CallIntrinsic1 {
9021                            func: bytecode::IntrinsicFunction1::AsyncGenWrap
9022                        }
9023                    );
9024                }
9025                // arg=0: direct yield (wrapped for async generators)
9026                emit!(self, Instruction::YieldValue { arg: 0 });
9027                emit!(
9028                    self,
9029                    Instruction::Resume {
9030                        context: oparg::ResumeContext::from(oparg::ResumeLocation::AfterYield)
9031                    }
9032                );
9033            }
9034            ast::Expr::Await(ast::ExprAwait { value, .. }) => {
9035                if self.ctx.func != FunctionContext::AsyncFunction
9036                    && !self.allows_top_level_await_in_current_context()
9037                {
9038                    return Err(self.error(CodegenErrorType::InvalidAwait));
9039                }
9040                self.compile_expression(value)?;
9041                self.set_source_range(range);
9042                emit!(self, Instruction::GetAwaitable { r#where: 0 });
9043                self.emit_load_const(ConstantData::None);
9044                let _ = self.compile_yield_from_sequence(true);
9045            }
9046            ast::Expr::YieldFrom(ast::ExprYieldFrom { value, .. }) => {
9047                match self.ctx.func {
9048                    FunctionContext::NoFunction => {
9049                        return Err(self.error(CodegenErrorType::InvalidYieldFrom));
9050                    }
9051                    FunctionContext::AsyncFunction => {
9052                        return Err(self.error(CodegenErrorType::AsyncYieldFrom));
9053                    }
9054                    FunctionContext::Function => {}
9055                }
9056                self.mark_generator();
9057                self.compile_expression(value)?;
9058                self.set_source_range(range);
9059                emit!(self, Instruction::GetYieldFromIter);
9060                self.emit_load_const(ConstantData::None);
9061                let _ = self.compile_yield_from_sequence(false);
9062            }
9063            ast::Expr::Name(ast::ExprName { id, .. }) => self.load_name(id)?,
9064            ast::Expr::Lambda(ast::ExprLambda {
9065                parameters,
9066                body,
9067                range,
9068                ..
9069            }) => {
9070                let default_params = ast::Parameters::default();
9071                let params = parameters.as_deref().unwrap_or(&default_params);
9072                validate_duplicate_params(params).map_err(|e| self.error(e))?;
9073
9074                let prev_ctx = self.ctx;
9075                let name = "<lambda>".to_owned();
9076
9077                // Prepare defaults before entering function
9078                let defaults: Vec<_> = core::iter::empty()
9079                    .chain(&params.posonlyargs)
9080                    .chain(&params.args)
9081                    .filter_map(|x| x.default.as_deref())
9082                    .collect();
9083                let have_defaults = !defaults.is_empty();
9084
9085                if have_defaults {
9086                    let size = defaults.len().to_u32();
9087                    for element in &defaults {
9088                        self.compile_expression(element)?;
9089                    }
9090                    self.set_source_range(*range);
9091                    emit!(self, Instruction::BuildTuple { count: size });
9092                }
9093
9094                // Prepare keyword-only defaults
9095                let mut kw_with_defaults = vec![];
9096                for kwonlyarg in &params.kwonlyargs {
9097                    if let Some(default) = &kwonlyarg.default {
9098                        kw_with_defaults.push((&kwonlyarg.parameter, default));
9099                    }
9100                }
9101
9102                let have_kwdefaults = !kw_with_defaults.is_empty();
9103                if have_kwdefaults {
9104                    let default_kw_count = kw_with_defaults.len();
9105                    for (arg, default) in &kw_with_defaults {
9106                        self.set_source_range(*range);
9107                        self.emit_load_const(ConstantData::Str {
9108                            value: self.mangle(arg.name().id()).as_str().into(),
9109                        });
9110                        self.compile_expression(default)?;
9111                    }
9112                    self.set_source_range(*range);
9113                    emit!(
9114                        self,
9115                        Instruction::BuildMap {
9116                            count: default_kw_count.to_u32(),
9117                        }
9118                    );
9119                }
9120
9121                self.enter_function(&name, params)?;
9122                let mut func_flags = bytecode::MakeFunctionFlags::new();
9123                if have_defaults {
9124                    func_flags.insert(bytecode::MakeFunctionFlag::Defaults);
9125                }
9126                if have_kwdefaults {
9127                    func_flags.insert(bytecode::MakeFunctionFlag::KwOnlyDefaults);
9128                }
9129
9130                // Set qualname for lambda
9131                self.set_qualname();
9132
9133                self.ctx = CompileContext {
9134                    in_class: prev_ctx.in_class,
9135                    func: FunctionContext::Function,
9136                    // Lambda is never async, so new scope is not async
9137                    in_async_scope: false,
9138                };
9139
9140                self.compile_expression(body)?;
9141                let is_generator = self
9142                    .current_code_info()
9143                    .flags
9144                    .contains(bytecode::CodeFlags::GENERATOR);
9145                if is_generator {
9146                    // codegen_lambda() calls OptimizeAndAssemble with
9147                    // addNone=0, so AddReturnAtEnd appends RETURN_VALUE without
9148                    // adding None to co_consts.
9149                    emit!(self, Instruction::ReturnValue);
9150                    self.set_no_location();
9151                } else {
9152                    self.set_source_range(body.range());
9153                    self.emit_return_value();
9154                    self.emit_return_const_no_location(ConstantData::None);
9155                }
9156                let code = self.exit_scope();
9157
9158                // Create lambda function with closure
9159                self.set_source_range(*range);
9160                self.make_closure(code, func_flags)?;
9161
9162                self.ctx = prev_ctx;
9163            }
9164            ast::Expr::ListComp(ast::ExprListComp {
9165                elt,
9166                generators,
9167                range,
9168                ..
9169            }) => {
9170                self.compile_comprehension(
9171                    "<listcomp>",
9172                    Some(
9173                        Instruction::BuildList {
9174                            count: OpArgMarker::marker(),
9175                        }
9176                        .into(),
9177                    ),
9178                    generators,
9179                    &|compiler, collection_add_i| {
9180                        compiler.compile_comprehension_element(elt)?;
9181                        compiler.set_source_range(elt.range());
9182                        emit!(
9183                            compiler,
9184                            Instruction::ListAppend {
9185                                i: collection_add_i.to_u32(),
9186                            }
9187                        );
9188                        Ok(())
9189                    },
9190                    ComprehensionType::List,
9191                    Self::contains_await(elt) || Self::generators_contain_await(generators),
9192                    *range,
9193                    elt.range(),
9194                    elt.range(),
9195                )?;
9196            }
9197            ast::Expr::SetComp(ast::ExprSetComp {
9198                elt,
9199                generators,
9200                range,
9201                ..
9202            }) => {
9203                self.compile_comprehension(
9204                    "<setcomp>",
9205                    Some(
9206                        Instruction::BuildSet {
9207                            count: OpArgMarker::marker(),
9208                        }
9209                        .into(),
9210                    ),
9211                    generators,
9212                    &|compiler, collection_add_i| {
9213                        compiler.compile_comprehension_element(elt)?;
9214                        compiler.set_source_range(elt.range());
9215                        emit!(
9216                            compiler,
9217                            Instruction::SetAdd {
9218                                i: collection_add_i.to_u32(),
9219                            }
9220                        );
9221                        Ok(())
9222                    },
9223                    ComprehensionType::Set,
9224                    Self::contains_await(elt) || Self::generators_contain_await(generators),
9225                    *range,
9226                    elt.range(),
9227                    elt.range(),
9228                )?;
9229            }
9230            ast::Expr::DictComp(ast::ExprDictComp {
9231                key,
9232                value,
9233                generators,
9234                range,
9235                ..
9236            }) => {
9237                let Some(key) = key.as_deref() else {
9238                    self.set_source_range(*range);
9239                    return Err(self.error(CodegenErrorType::SyntaxError(
9240                        "dict unpacking cannot be used in dict comprehension".to_owned(),
9241                    )));
9242                };
9243                self.compile_comprehension(
9244                    "<dictcomp>",
9245                    Some(
9246                        Instruction::BuildMap {
9247                            count: OpArgMarker::marker(),
9248                        }
9249                        .into(),
9250                    ),
9251                    generators,
9252                    &|compiler, collection_add_i| {
9253                        // changed evaluation order for Py38 named expression PEP 572
9254                        compiler.compile_expression(key)?;
9255                        compiler.compile_expression(value)?;
9256
9257                        compiler.set_source_range(TextRange::new(
9258                            key.range().start(),
9259                            value.range().end(),
9260                        ));
9261                        emit!(
9262                            compiler,
9263                            Instruction::MapAdd {
9264                                i: collection_add_i.to_u32(),
9265                            }
9266                        );
9267
9268                        Ok(())
9269                    },
9270                    ComprehensionType::Dict,
9271                    Self::contains_await(key)
9272                        || Self::contains_await(value)
9273                        || Self::generators_contain_await(generators),
9274                    *range,
9275                    TextRange::new(key.range().start(), value.range().end()),
9276                    key.range(),
9277                )?;
9278            }
9279            ast::Expr::Generator(ast::ExprGenerator {
9280                elt,
9281                generators,
9282                range,
9283                ..
9284            }) => {
9285                self.compile_generator_expression(elt, generators, *range)?;
9286            }
9287            ast::Expr::Starred(ast::ExprStarred { value, .. }) => {
9288                if self.in_annotation {
9289                    // In annotation context, starred expressions are allowed (PEP 646)
9290                    // For now, just compile the inner value without wrapping with Unpack
9291                    // This is a temporary solution until we figure out how to properly import typing
9292                    self.compile_expression(value)?;
9293                } else {
9294                    return Err(self.error(CodegenErrorType::InvalidStarExpr));
9295                }
9296            }
9297            ast::Expr::If(ast::ExprIf {
9298                test, body, orelse, ..
9299            }) => {
9300                let after_block = self.new_block();
9301                let else_block = self.new_block();
9302                self.compile_jump_if(test, false, else_block)?;
9303
9304                // True case
9305                self.compile_expression(body)?;
9306                emit!(
9307                    self,
9308                    PseudoInstruction::JumpNoInterrupt { delta: after_block }
9309                );
9310                self.set_no_location();
9311
9312                // False case
9313                self.use_cpython_label_block(else_block);
9314                self.compile_expression(orelse)?;
9315
9316                // End
9317                self.use_cpython_label_block(after_block);
9318            }
9319
9320            ast::Expr::Named(ast::ExprNamed {
9321                target,
9322                value,
9323                node_index: _,
9324                range,
9325            }) => {
9326                // Walrus targets in inlined comps should NOT be hidden from locals()
9327                if self.current_code_info().in_inlined_comp
9328                    && let ast::Expr::Name(ast::ExprName { id, .. }) = target.as_ref()
9329                {
9330                    let name = self.mangle(id);
9331                    let info = self.code_stack.last_mut().unwrap();
9332                    info.metadata.fast_hidden.insert(name.to_string(), false);
9333                    info.metadata
9334                        .fast_hidden_final
9335                        .swap_remove(name.into_owned().as_str());
9336                }
9337                self.compile_expression(value)?;
9338                self.set_source_range(*range);
9339                emit!(self, Instruction::Copy { i: 1 });
9340                self.compile_store(target)?;
9341                self.set_source_range(target.range());
9342            }
9343            ast::Expr::FString(fstring) => {
9344                if let Some(joined_str) = fstring.runtime_joined_str.as_ref() {
9345                    return self.compile_runtime_joined_str(fstring, joined_str);
9346                }
9347                self.compile_expr_fstring(fstring)?;
9348            }
9349            ast::Expr::TString(tstring) => {
9350                if let Some(template_str) = tstring.runtime_template_str.as_ref() {
9351                    return self.compile_runtime_template_str(tstring, template_str);
9352                }
9353                if let Some(interpolation) = Self::single_runtime_interpolation(tstring)
9354                    && self.compile_runtime_interpolation(tstring, interpolation)?
9355                {
9356                    return Ok(());
9357                }
9358                self.compile_expr_tstring(tstring)?;
9359            }
9360            ast::Expr::StringLiteral(string) => {
9361                let value = string_literal_value(&self.source_file, &string.value);
9362                self.emit_load_const(ConstantData::Str { value });
9363            }
9364            ast::Expr::BytesLiteral(bytes) => {
9365                let iter = bytes.value.iter().flat_map(|x| x.iter().copied());
9366                let v: Vec<u8> = iter.collect();
9367                self.emit_load_const(ConstantData::Bytes { value: v });
9368            }
9369            ast::Expr::NumberLiteral(number) => match &number.value {
9370                ast::Number::Int(int) => {
9371                    let value = ruff_int_to_bigint(int).map_err(|e| self.error(e))?;
9372                    self.emit_load_const(ConstantData::Integer { value });
9373                }
9374                ast::Number::Float(float) => {
9375                    self.emit_load_const(ConstantData::Float { value: *float });
9376                }
9377                ast::Number::Complex { real, imag } => {
9378                    self.emit_load_const(ConstantData::Complex {
9379                        value: Complex::new(*real, *imag),
9380                    });
9381                }
9382            },
9383            ast::Expr::BooleanLiteral(b) => {
9384                self.emit_load_const(ConstantData::Boolean { value: b.value });
9385            }
9386            ast::Expr::NoneLiteral(_) => {
9387                self.emit_load_const(ConstantData::None);
9388            }
9389            ast::Expr::EllipsisLiteral(_) => {
9390                self.emit_load_const(ConstantData::Ellipsis);
9391            }
9392            ast::Expr::IpyEscapeCommand(expr) => {
9393                return Err(self.error_ranged(
9394                    CodegenErrorType::SyntaxError("invalid syntax".to_owned()),
9395                    expr.range,
9396                ));
9397            }
9398        }
9399        Ok(())
9400    }
9401
9402    /// The called name of a `name(genexpr)` call, the shape
9403    /// `maybe_optimize_function_call()` reserves a `skip_optimization` label
9404    /// for. An `await` or an `async for` inside the generator does not
9405    /// disqualify it: the inlined loop raises the same `TypeError` that
9406    /// calling the builtin on an async generator would.
9407    fn cpython_genexpr_call_name<'a>(
9408        &self,
9409        func: &'a ast::Expr,
9410        args: &ast::Arguments,
9411    ) -> Option<&'a str> {
9412        let ast::Expr::Name(ast::ExprName { id, .. }) = func else {
9413            return None;
9414        };
9415        let [ast::Expr::Generator(ast::ExprGenerator { .. })] = &args.args[..] else {
9416            return None;
9417        };
9418        if !args.keywords.is_empty() {
9419            return None;
9420        }
9421        let table = self.current_symbol_table();
9422        table.sub_tables.get(table.next_sub_table)?;
9423        Some(id.as_str())
9424    }
9425
9426    fn detect_builtin_generator_call(
9427        &self,
9428        func: &ast::Expr,
9429        args: &ast::Arguments,
9430    ) -> Option<BuiltinGeneratorCallKind> {
9431        match self.cpython_genexpr_call_name(func, args)? {
9432            "tuple" => Some(BuiltinGeneratorCallKind::Tuple),
9433            "all" => Some(BuiltinGeneratorCallKind::All),
9434            "any" => Some(BuiltinGeneratorCallKind::Any),
9435            _ => None,
9436        }
9437    }
9438
9439    /// Emit the optimized inline loop for builtin(genexpr) calls.
9440    ///
9441    /// Stack on entry: `[func]` where `func` is the builtin candidate.
9442    /// On return the compiler is positioned at the skip-optimization block so the
9443    /// normal call path can compile the original generator argument again.
9444    fn optimize_builtin_generator_call(
9445        &mut self,
9446        kind: BuiltinGeneratorCallKind,
9447        generator_expr: &ast::Expr,
9448        loc: TextRange,
9449        end: BlockIdx,
9450    ) -> CompileResult<()> {
9451        let common_constant = match kind {
9452            BuiltinGeneratorCallKind::Tuple => bytecode::CommonConstant::BuiltinTuple,
9453            BuiltinGeneratorCallKind::All => bytecode::CommonConstant::BuiltinAll,
9454            BuiltinGeneratorCallKind::Any => bytecode::CommonConstant::BuiltinAny,
9455        };
9456
9457        let skip_optimization = self.new_block();
9458
9459        // Stack: [func] - copy function for identity check
9460        self.set_source_range(loc);
9461        emit!(self, Instruction::Copy { i: 1 });
9462        emit!(
9463            self,
9464            Instruction::LoadCommonConstant {
9465                idx: common_constant
9466            }
9467        );
9468        emit!(self, Instruction::IsOp { invert: Invert::No });
9469        emit!(
9470            self,
9471            Instruction::PopJumpIfFalse {
9472                delta: skip_optimization
9473            }
9474        );
9475        emit!(self, Instruction::PopTop);
9476
9477        if matches!(kind, BuiltinGeneratorCallKind::Tuple) {
9478            self.set_source_range(loc);
9479            emit!(self, Instruction::BuildList { count: 0 });
9480        }
9481
9482        let symbol_table_cursors = self.current_symbol_table_cursors();
9483        self.compile_expression(generator_expr)?;
9484        self.set_symbol_table_cursors(symbol_table_cursors);
9485
9486        let loop_block = self.new_block();
9487        let cleanup = self.new_block();
9488        self.use_cpython_label_block(loop_block);
9489        self.set_source_range(loc);
9490        emit!(self, Instruction::ForIter { delta: cleanup });
9491
9492        match kind {
9493            BuiltinGeneratorCallKind::Tuple => {
9494                self.set_source_range(loc);
9495                emit!(self, Instruction::ListAppend { i: 2 });
9496                self.set_source_range(loc);
9497                emit!(self, PseudoInstruction::Jump { delta: loop_block });
9498            }
9499            BuiltinGeneratorCallKind::All => {
9500                self.set_source_range(loc);
9501                emit!(self, Instruction::ToBool);
9502                emit!(self, Instruction::PopJumpIfTrue { delta: loop_block });
9503                emit!(self, Instruction::PopIter);
9504                self.set_no_location();
9505                self.set_source_range(loc);
9506                self.emit_load_const(ConstantData::Boolean { value: false });
9507                self.set_source_range(loc);
9508                emit!(self, PseudoInstruction::Jump { delta: end });
9509            }
9510            BuiltinGeneratorCallKind::Any => {
9511                self.set_source_range(loc);
9512                emit!(self, Instruction::ToBool);
9513                emit!(self, Instruction::PopJumpIfFalse { delta: loop_block });
9514                emit!(self, Instruction::PopIter);
9515                self.set_no_location();
9516                self.set_source_range(loc);
9517                self.emit_load_const(ConstantData::Boolean { value: true });
9518                self.set_source_range(loc);
9519                emit!(self, PseudoInstruction::Jump { delta: end });
9520            }
9521        }
9522
9523        self.use_cpython_label_block(cleanup);
9524        emit!(self, Instruction::EndFor);
9525        self.set_no_location();
9526        emit!(self, Instruction::PopIter);
9527        self.set_no_location();
9528        match kind {
9529            BuiltinGeneratorCallKind::Tuple => {
9530                self.set_source_range(loc);
9531                emit!(
9532                    self,
9533                    Instruction::CallIntrinsic1 {
9534                        func: IntrinsicFunction1::ListToTuple
9535                    }
9536                );
9537            }
9538            BuiltinGeneratorCallKind::All => {
9539                self.set_source_range(loc);
9540                self.emit_load_const(ConstantData::Boolean { value: true });
9541            }
9542            BuiltinGeneratorCallKind::Any => {
9543                self.set_source_range(loc);
9544                self.emit_load_const(ConstantData::Boolean { value: false });
9545            }
9546        }
9547        self.set_source_range(loc);
9548        emit!(self, PseudoInstruction::Jump { delta: end });
9549
9550        self.use_cpython_label_block(skip_optimization);
9551        Ok(())
9552    }
9553
9554    fn can_use_cpython_method_call(&self, value: &ast::Expr, args: &ast::Arguments) -> bool {
9555        let is_import = matches!(value, ast::Expr::Name(ast::ExprName { id, .. })
9556            if self.is_name_imported(id.as_str()));
9557        if is_import {
9558            return false;
9559        }
9560
9561        if args.args.len() + args.keywords.len() + usize::from(!args.keywords.is_empty())
9562            >= STACK_USE_GUIDELINE as usize
9563        {
9564            return false;
9565        }
9566
9567        !args
9568            .args
9569            .iter()
9570            .any(|arg| matches!(arg, ast::Expr::Starred(_)))
9571            && args.keywords.iter().all(|kw| kw.arg.is_some())
9572    }
9573
9574    fn compile_method_call_arguments(
9575        &mut self,
9576        args: &ast::Arguments,
9577        call_range: TextRange,
9578        kw_names_range: TextRange,
9579    ) -> CompileResult<()> {
9580        for arg in &args.args {
9581            self.compile_expression(arg)?;
9582        }
9583
9584        if args.keywords.is_empty() {
9585            self.set_source_range(call_range);
9586            emit!(
9587                self,
9588                Instruction::Call {
9589                    argc: args.args.len().to_u32()
9590                }
9591            );
9592            return Ok(());
9593        }
9594
9595        let mut kwarg_names = Vec::with_capacity(args.keywords.len());
9596        for keyword in &args.keywords {
9597            kwarg_names.push(ConstantData::Str {
9598                value: keyword.arg.as_ref().unwrap().as_str().into(),
9599            });
9600            self.compile_expression(&keyword.value)?;
9601        }
9602        self.set_source_range(kw_names_range);
9603        self.emit_load_const(ConstantData::Tuple {
9604            elements: kwarg_names,
9605        });
9606        self.set_source_range(call_range);
9607        emit!(
9608            self,
9609            Instruction::CallKw {
9610                argc: (args.args.len() + args.keywords.len()).to_u32()
9611            }
9612        );
9613        Ok(())
9614    }
9615
9616    fn compile_call(&mut self, func: &ast::Expr, args: &ast::Arguments) -> CompileResult<()> {
9617        // Save the call expression's source range so CALL instructions use the
9618        // call start line, not the last argument's line.
9619        let call_range = self.current_source_range;
9620        self.validate_keywords(&args.keywords)?;
9621        let uses_ex_call = self.call_uses_ex_call(args);
9622
9623        // Method call: obj → LOAD_ATTR_METHOD → [method, self_or_null] → args → CALL
9624        // Regular call: func → PUSH_NULL → args → CALL
9625        if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = &func {
9626            if !self.can_use_cpython_method_call(value, args) {
9627                self.check_caller(func)?;
9628                self.compile_expression(func)?;
9629                self.set_source_range(func.range());
9630                emit!(self, Instruction::PushNull);
9631                self.codegen_call_helper(0, args, call_range, None)?;
9632                return Ok(());
9633            }
9634
9635            // Check for super() method call optimization
9636            if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
9637                // super().method() or super(cls, self).method() optimization
9638                // CALL path: [global_super, class, self] → LOAD_SUPER_METHOD → [method, self]
9639                let ast::Expr::Call(ast::ExprCall {
9640                    func: super_func, ..
9641                }) = value.as_ref()
9642                else {
9643                    unreachable!("can_optimize_super_call only accepts calls");
9644                };
9645                self.load_args_for_super(&super_type, super_func.range(), value.range())?;
9646                let attr_access_range = self.update_start_location_to_match_attr(
9647                    func.range(),
9648                    func.range(),
9649                    attr.as_str(),
9650                );
9651                let method_call_range = self.update_start_location_to_match_attr(
9652                    call_range,
9653                    func.range(),
9654                    attr.as_str(),
9655                );
9656                self.set_source_range(func.range());
9657                let idx = self.name(attr.as_str());
9658                match super_type {
9659                    SuperCallType::TwoArg { .. } => {
9660                        self.emit_load_super_method(idx);
9661                    }
9662                    SuperCallType::ZeroArg => {
9663                        self.emit_load_zero_super_method(idx);
9664                    }
9665                }
9666                // NOP for line tracking at .method( line
9667                self.set_source_range(attr_access_range);
9668                emit!(self, Instruction::Nop);
9669                // CALL at .method( line (not the full expression line)
9670                self.compile_method_call_arguments(args, method_call_range, attr_access_range)?;
9671            } else {
9672                self.compile_expression(value)?;
9673                let idx = self.name(attr.as_str());
9674                let attr_access_range = self.update_start_location_to_match_attr(
9675                    func.range(),
9676                    func.range(),
9677                    attr.as_str(),
9678                );
9679                let method_call_range = self.update_start_location_to_match_attr(
9680                    call_range,
9681                    func.range(),
9682                    attr.as_str(),
9683                );
9684                self.set_source_range(attr_access_range);
9685                self.emit_load_attr_method(idx);
9686                self.compile_method_call_arguments(args, method_call_range, attr_access_range)?;
9687            }
9688        } else if let Some(kind) = (!uses_ex_call)
9689            .then(|| self.detect_builtin_generator_call(func, args))
9690            .flatten()
9691        {
9692            let skip_normal_call = self.new_block();
9693            self.check_caller(func)?;
9694            self.compile_expression(func)?;
9695            self.optimize_builtin_generator_call(
9696                kind,
9697                &args.args[0],
9698                func.range(),
9699                skip_normal_call,
9700            )?;
9701            self.set_source_range(func.range());
9702            emit!(self, Instruction::PushNull);
9703            self.codegen_call_helper(0, args, call_range, None)?;
9704            self.use_cpython_label_block(skip_normal_call);
9705        } else {
9706            // Regular call: push func, then NULL for self_or_null slot
9707            // Stack layout: [func, NULL, args...] - same as method call [func, self, args...]
9708            // CPython `codegen_call()` always creates and uses
9709            // `skip_normal_call`, even when `maybe_optimize_function_call()`
9710            // leaves it untargeted.
9711            let skip_normal_call = self.current_code_info().new_instr_sequence_label();
9712            let genexpr_call_name = (!uses_ex_call)
9713                .then(|| self.cpython_genexpr_call_name(func, args))
9714                .flatten()
9715                .is_some();
9716            self.check_caller(func)?;
9717            self.compile_expression(func)?;
9718            if genexpr_call_name {
9719                // CPython `maybe_optimize_function_call()` creates and uses
9720                // `skip_optimization` for every name(genexpr) shape after
9721                // loading the function, even when the name is not all/any/tuple.
9722                let skip_optimization = self.current_code_info().new_instr_sequence_label();
9723                let result = self
9724                    .current_code_info()
9725                    .use_raw_instr_sequence_label(skip_optimization);
9726                unwrap_internal(self, result);
9727            }
9728            self.set_source_range(func.range());
9729            emit!(self, Instruction::PushNull);
9730            self.codegen_call_helper(0, args, call_range, None)?;
9731            let result = self
9732                .current_code_info()
9733                .use_raw_instr_sequence_label(skip_normal_call);
9734            unwrap_internal(self, result);
9735        }
9736        Ok(())
9737    }
9738
9739    fn call_uses_ex_call(&self, arguments: &ast::Arguments) -> bool {
9740        let has_starred = arguments
9741            .args
9742            .iter()
9743            .any(|arg| matches!(arg, ast::Expr::Starred(_)));
9744        let has_double_star = arguments.keywords.iter().any(|k| k.arg.is_none());
9745        let too_big =
9746            arguments.args.len() + arguments.keywords.len() * 2 > STACK_USE_GUIDELINE as usize;
9747        has_starred || has_double_star || too_big
9748    }
9749
9750    /// Reject duplicate keyword-argument names in a call.
9751    fn validate_keywords(&mut self, keywords: &[ast::Keyword]) -> CompileResult<()> {
9752        for (i, keyword) in keywords.iter().enumerate() {
9753            let Some(arg) = &keyword.arg else {
9754                continue;
9755            };
9756            for other in &keywords[i + 1..] {
9757                if other
9758                    .arg
9759                    .as_ref()
9760                    .is_some_and(|other| other.as_str() == arg.as_str())
9761                {
9762                    return Err(self.error_ranged(
9763                        CodegenErrorType::SyntaxError(format!("keyword argument repeated: {arg}")),
9764                        other.range,
9765                    ));
9766                }
9767            }
9768        }
9769        Ok(())
9770    }
9771
9772    /// Compile subkwargs: emit key-value pairs for BUILD_MAP
9773    fn codegen_subkwargs(
9774        &mut self,
9775        keywords: &[ast::Keyword],
9776        begin: usize,
9777        end: usize,
9778        call_range: TextRange,
9779    ) -> CompileResult<()> {
9780        let n = end - begin;
9781        assert!(n > 0);
9782
9783        // For large kwargs, use BUILD_MAP(0) + MAP_ADD to avoid stack overflow.
9784        let big = n * 2 > STACK_USE_GUIDELINE as usize;
9785
9786        if big {
9787            emit!(self, Instruction::BuildMap { count: 0 });
9788            self.set_no_location();
9789        }
9790
9791        for kw in &keywords[begin..end] {
9792            // Key first, then value - this is critical!
9793            self.set_source_range(call_range);
9794            self.emit_load_const(ConstantData::Str {
9795                value: kw.arg.as_ref().unwrap().as_str().into(),
9796            });
9797            self.compile_expression(&kw.value)?;
9798
9799            if big {
9800                emit!(self, Instruction::MapAdd { i: 1 });
9801                self.set_no_location();
9802            }
9803        }
9804
9805        if !big {
9806            self.set_source_range(call_range);
9807            emit!(self, Instruction::BuildMap { count: n.to_u32() });
9808        }
9809
9810        Ok(())
9811    }
9812
9813    /// Compile call arguments and emit the appropriate CALL instruction.
9814    /// `call_range` is the source range of the call expression, used to set
9815    /// the correct line number on the CALL instruction.
9816    fn codegen_call_helper(
9817        &mut self,
9818        additional_positional: u32,
9819        arguments: &ast::Arguments,
9820        call_range: TextRange,
9821        kw_names_range: Option<TextRange>,
9822    ) -> CompileResult<()> {
9823        self.codegen_call_helper_impl(
9824            additional_positional,
9825            &arguments.args,
9826            &arguments.keywords,
9827            call_range,
9828            kw_names_range,
9829            None,
9830        )
9831    }
9832
9833    fn codegen_call_helper_impl(
9834        &mut self,
9835        additional_positional: u32,
9836        args: &[ast::Expr],
9837        keywords: &[ast::Keyword],
9838        call_range: TextRange,
9839        kw_names_range: Option<TextRange>,
9840        injected_arg: Option<&Name>,
9841    ) -> CompileResult<()> {
9842        self.validate_keywords(keywords)?;
9843
9844        let nelts = args.len();
9845        let nkwelts = keywords.len();
9846
9847        // Check if we have starred args or **kwargs
9848        let has_starred = args.iter().any(|arg| matches!(arg, ast::Expr::Starred(_)));
9849        let has_double_star = keywords.iter().any(|k| k.arg.is_none());
9850
9851        // Check if exceeds CPython's stack-use guideline.
9852        // With CALL_KW, kwargs values go on stack but keys go in a const tuple,
9853        // so stack usage is: func + null + positional_args + kwarg_values + kwnames_tuple
9854        let too_big = nelts + nkwelts * 2 > STACK_USE_GUIDELINE as usize;
9855
9856        if !has_starred && !has_double_star && !too_big {
9857            // Simple call path: no * or ** args
9858            for arg in args {
9859                self.compile_expression(arg)?;
9860            }
9861            let injected_count = if let Some(injected_arg) = injected_arg {
9862                self.set_source_range(call_range);
9863                self.load_name(injected_arg)?;
9864                1
9865            } else {
9866                0
9867            };
9868
9869            if nkwelts > 0 {
9870                // Compile keyword values and build kwnames tuple
9871                let mut kwarg_names = Vec::with_capacity(nkwelts);
9872                for keyword in keywords {
9873                    kwarg_names.push(ConstantData::Str {
9874                        value: keyword.arg.as_ref().unwrap().as_str().into(),
9875                    });
9876                    self.compile_expression(&keyword.value)?;
9877                }
9878
9879                // Restore call expression range for kwnames and CALL_KW
9880                self.set_source_range(kw_names_range.unwrap_or(call_range));
9881                self.emit_load_const(ConstantData::Tuple {
9882                    elements: kwarg_names,
9883                });
9884
9885                self.set_source_range(call_range);
9886                let argc =
9887                    additional_positional + nelts.to_u32() + injected_count + nkwelts.to_u32();
9888                emit!(self, Instruction::CallKw { argc });
9889            } else {
9890                self.set_source_range(call_range);
9891                let argc = additional_positional + nelts.to_u32() + injected_count;
9892                emit!(self, Instruction::Call { argc });
9893            }
9894        } else {
9895            // ex_call path: has * or ** args
9896
9897            // Compile positional arguments
9898            if additional_positional == 0 && nelts == 1 && matches!(args[0], ast::Expr::Starred(_))
9899            {
9900                // Single starred arg: pass value directly to CallFunctionEx.
9901                // Runtime will convert to tuple and validate with function name.
9902                if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = &args[0] {
9903                    self.compile_expression(value)?;
9904                }
9905            } else {
9906                // CPython `codegen_call_helper_impl()` sends every other
9907                // CALL_FUNCTION_EX positional shape through
9908                // `starunpack_helper_impl(..., BUILD_LIST, LIST_APPEND,
9909                // LIST_EXTEND, tuple=1)`, even when the only reason for the
9910                // ex-call path is too many non-starred positional arguments.
9911                self.set_source_range(call_range);
9912                self.starunpack_helper_impl(
9913                    args,
9914                    injected_arg,
9915                    additional_positional,
9916                    CollectionType::Tuple,
9917                )?;
9918            }
9919
9920            self.compile_call_function_ex_keywords(keywords, call_range)?;
9921
9922            self.set_source_range(call_range);
9923            emit!(self, Instruction::CallFunctionEx);
9924        }
9925
9926        Ok(())
9927    }
9928
9929    fn compile_call_function_ex_keywords(
9930        &mut self,
9931        keywords: &[ast::Keyword],
9932        call_range: TextRange,
9933    ) -> CompileResult<()> {
9934        if keywords.is_empty() {
9935            self.set_source_range(call_range);
9936            emit!(self, Instruction::PushNull);
9937            return Ok(());
9938        }
9939
9940        let mut have_dict = false;
9941        let mut nseen = 0usize;
9942
9943        for (i, keyword) in keywords.iter().enumerate() {
9944            if keyword.arg.is_none() {
9945                if nseen > 0 {
9946                    self.codegen_subkwargs(keywords, i - nseen, i, call_range)?;
9947                    if have_dict {
9948                        self.set_source_range(call_range);
9949                        emit!(self, Instruction::DictMerge { i: 1 });
9950                    }
9951                    have_dict = true;
9952                    nseen = 0;
9953                }
9954
9955                if !have_dict {
9956                    self.set_source_range(call_range);
9957                    emit!(self, Instruction::BuildMap { count: 0 });
9958                    have_dict = true;
9959                }
9960
9961                self.compile_expression(&keyword.value)?;
9962                self.set_source_range(call_range);
9963                emit!(self, Instruction::DictMerge { i: 1 });
9964            } else {
9965                nseen += 1;
9966            }
9967        }
9968
9969        if nseen > 0 {
9970            self.codegen_subkwargs(keywords, keywords.len() - nseen, keywords.len(), call_range)?;
9971            if have_dict {
9972                self.set_source_range(call_range);
9973                emit!(self, Instruction::DictMerge { i: 1 });
9974            }
9975            have_dict = true;
9976        }
9977
9978        debug_assert!(have_dict);
9979        Ok(())
9980    }
9981
9982    fn compile_comprehension_element(&mut self, element: &ast::Expr) -> CompileResult<()> {
9983        self.compile_expression(element).map_err(|e| {
9984            if let CodegenErrorType::InvalidStarExpr = e.error {
9985                self.error(CodegenErrorType::SyntaxError(
9986                    "iterable unpacking cannot be used in comprehension".to_owned(),
9987                ))
9988            } else {
9989                e
9990            }
9991        })
9992    }
9993
9994    fn compile_generator_expression(
9995        &mut self,
9996        elt: &ast::Expr,
9997        generators: &[ast::Comprehension],
9998        range: TextRange,
9999    ) -> CompileResult<()> {
10000        // Check if element or generators contain async content
10001        // This makes the generator expression into an async generator
10002        let element_contains_await =
10003            Self::contains_await(elt) || Self::generators_contain_await(generators);
10004        self.compile_comprehension(
10005            "<genexpr>",
10006            None,
10007            generators,
10008            &|compiler, _collection_add_i| {
10009                // Compile the element expression
10010                // Note: if element is an async comprehension, compile_expression
10011                // already handles awaiting it, so we don't need to await again here
10012                compiler.compile_comprehension_element(elt)?;
10013
10014                compiler.mark_generator();
10015                if compiler.ctx.func == FunctionContext::AsyncFunction {
10016                    compiler.set_source_range(elt.range());
10017                    emit!(
10018                        compiler,
10019                        Instruction::CallIntrinsic1 {
10020                            func: bytecode::IntrinsicFunction1::AsyncGenWrap
10021                        }
10022                    );
10023                }
10024                // arg=0: direct yield (wrapped for async generators)
10025                compiler.set_source_range(elt.range());
10026                emit!(compiler, Instruction::YieldValue { arg: 0 });
10027                emit!(
10028                    compiler,
10029                    Instruction::Resume {
10030                        context: oparg::ResumeContext::from(oparg::ResumeLocation::AfterYield)
10031                    }
10032                );
10033                emit!(compiler, Instruction::PopTop);
10034
10035                Ok(())
10036            },
10037            ComprehensionType::Generator,
10038            element_contains_await,
10039            range,
10040            elt.range(),
10041            elt.range(),
10042        )
10043    }
10044
10045    fn consume_next_sub_table(&mut self) -> CompileResult<()> {
10046        {
10047            let _ = self.push_symbol_table()?;
10048        }
10049        self.pop_symbol_table();
10050        Ok(())
10051    }
10052
10053    fn consume_function_annotation_symbol_table_if_used(&mut self) -> CompileResult<()> {
10054        if !self.next_function_annotation_symbol_table_uses_annotations() {
10055            return Ok(());
10056        }
10057        if !self.push_annotation_symbol_table() {
10058            let current_table = self.current_symbol_table();
10059            return Err(self.error(CodegenErrorType::SyntaxError(format!(
10060                "no annotation symbol table available in {} (type: {:?})",
10061                current_table.name, current_table.typ
10062            ))));
10063        }
10064        self.pop_annotation_symbol_table();
10065        Ok(())
10066    }
10067
10068    fn consume_skipped_nested_scopes_in_expr(
10069        &mut self,
10070        expression: &ast::Expr,
10071    ) -> CompileResult<()> {
10072        use ast::visitor::Visitor;
10073
10074        struct SkippedScopeVisitor<'a, 'warnings> {
10075            compiler: &'a mut Compiler<'warnings>,
10076            error: Option<CodegenError>,
10077        }
10078
10079        impl SkippedScopeVisitor<'_, '_> {
10080            fn consume_scope(&mut self) {
10081                if self.error.is_none() {
10082                    self.error = self.compiler.consume_next_sub_table().err();
10083                }
10084            }
10085
10086            fn consume_inlined_comprehension_scope(&mut self) -> bool {
10087                if self.error.is_some() {
10088                    return false;
10089                }
10090                let Some(current_table) = self.compiler.symbol_table_stack.last_mut() else {
10091                    return false;
10092                };
10093                if current_table.next_inlined_comprehension_block
10094                    < current_table.inlined_comprehension_blocks.len()
10095                {
10096                    current_table.next_inlined_comprehension_block += 1;
10097                    true
10098                } else {
10099                    false
10100                }
10101            }
10102
10103            fn visit_comprehension_tail(
10104                &mut self,
10105                elt1: &ast::Expr,
10106                elt2: Option<&ast::Expr>,
10107                generators: &[ast::Comprehension],
10108            ) {
10109                if let Some(outermost) = generators.first() {
10110                    self.visit_expr(&outermost.target);
10111                    for if_expr in &outermost.ifs {
10112                        self.visit_expr(if_expr);
10113                    }
10114                }
10115                for generator in generators.iter().skip(1) {
10116                    self.visit_expr(&generator.target);
10117                    self.visit_expr(&generator.iter);
10118                    for if_expr in &generator.ifs {
10119                        self.visit_expr(if_expr);
10120                    }
10121                }
10122                if let Some(elt2) = elt2 {
10123                    self.visit_expr(elt2);
10124                }
10125                self.visit_expr(elt1);
10126            }
10127        }
10128
10129        impl ast::visitor::Visitor<'_> for SkippedScopeVisitor<'_, '_> {
10130            fn visit_expr(&mut self, expr: &ast::Expr) {
10131                if self.error.is_some() {
10132                    return;
10133                }
10134
10135                match expr {
10136                    ast::Expr::Lambda(ast::ExprLambda { parameters, .. }) => {
10137                        // Defaults are scanned before enter_scope in the
10138                        // symbol table builder, so their nested scopes
10139                        // precede the lambda scope in sub_tables.
10140                        if let Some(params) = parameters.as_deref() {
10141                            for default in params
10142                                .posonlyargs
10143                                .iter()
10144                                .chain(&params.args)
10145                                .chain(&params.kwonlyargs)
10146                                .filter_map(|p| p.default.as_deref())
10147                            {
10148                                self.visit_expr(default);
10149                            }
10150                        }
10151                        self.consume_scope();
10152                    }
10153                    ast::Expr::Generator(ast::ExprGenerator { generators, .. }) => {
10154                        if let Some(first) = generators.first() {
10155                            self.visit_expr(&first.iter);
10156                        }
10157                        self.consume_scope();
10158                    }
10159                    ast::Expr::ListComp(ast::ExprListComp {
10160                        elt, generators, ..
10161                    })
10162                    | ast::Expr::SetComp(ast::ExprSetComp {
10163                        elt, generators, ..
10164                    }) => {
10165                        if let Some(first) = generators.first() {
10166                            self.visit_expr(&first.iter);
10167                        }
10168                        if self.consume_inlined_comprehension_scope() {
10169                            self.visit_comprehension_tail(elt, None, generators);
10170                        } else {
10171                            self.consume_scope();
10172                        }
10173                    }
10174                    ast::Expr::DictComp(ast::ExprDictComp {
10175                        key,
10176                        value,
10177                        generators,
10178                        ..
10179                    }) => {
10180                        if let Some(first) = generators.first() {
10181                            self.visit_expr(&first.iter);
10182                        }
10183                        if self.consume_inlined_comprehension_scope() {
10184                            if let Some(key) = key.as_deref() {
10185                                self.visit_comprehension_tail(key, Some(value), generators);
10186                            } else {
10187                                self.visit_comprehension_tail(value, None, generators);
10188                            }
10189                        } else {
10190                            self.consume_scope();
10191                        }
10192                    }
10193                    _ => ast::visitor::walk_expr(self, expr),
10194                }
10195            }
10196        }
10197
10198        let mut visitor = SkippedScopeVisitor {
10199            compiler: self,
10200            error: None,
10201        };
10202        visitor.visit_expr(expression);
10203        if let Some(err) = visitor.error {
10204            Err(err)
10205        } else {
10206            Ok(())
10207        }
10208    }
10209
10210    fn consume_skipped_nested_scopes_in_parameter_defaults(
10211        &mut self,
10212        parameters: &ast::Parameters,
10213    ) -> CompileResult<()> {
10214        for default in parameters
10215            .posonlyargs
10216            .iter()
10217            .chain(&parameters.args)
10218            .chain(&parameters.kwonlyargs)
10219            .filter_map(|arg| arg.default.as_deref())
10220        {
10221            self.consume_skipped_nested_scopes_in_expr(default)?;
10222        }
10223        Ok(())
10224    }
10225
10226    fn consume_skipped_nested_scopes_in_statements(
10227        &mut self,
10228        statements: &[ast::Stmt],
10229    ) -> CompileResult<()> {
10230        use ast::visitor::Visitor;
10231
10232        struct SkippedStatementScopeVisitor<'a, 'warnings> {
10233            compiler: &'a mut Compiler<'warnings>,
10234            error: Option<CodegenError>,
10235        }
10236
10237        impl SkippedStatementScopeVisitor<'_, '_> {
10238            fn consume_scope(&mut self) {
10239                if self.error.is_none() {
10240                    self.error = self.compiler.consume_next_sub_table().err();
10241                }
10242            }
10243
10244            fn consume_function_annotation_scope_if_used(&mut self) {
10245                if self.error.is_none() {
10246                    self.error = self
10247                        .compiler
10248                        .consume_function_annotation_symbol_table_if_used()
10249                        .err();
10250                }
10251            }
10252
10253            fn visit_parameter_defaults(&mut self, parameters: &ast::Parameters) {
10254                for default in parameters
10255                    .posonlyargs
10256                    .iter()
10257                    .chain(&parameters.args)
10258                    .chain(&parameters.kwonlyargs)
10259                    .filter_map(|arg| arg.default.as_deref())
10260                {
10261                    self.visit_expr(default);
10262                }
10263            }
10264
10265            fn visit_decorators(&mut self, decorators: &[ast::Decorator]) {
10266                for decorator in decorators {
10267                    self.visit_expr(&decorator.expression);
10268                }
10269            }
10270
10271            fn visit_arguments(&mut self, arguments: &ast::Arguments) {
10272                for arg in &arguments.args {
10273                    self.visit_expr(arg);
10274                }
10275                for keyword in &arguments.keywords {
10276                    self.visit_expr(&keyword.value);
10277                }
10278            }
10279        }
10280
10281        impl ast::visitor::Visitor<'_> for SkippedStatementScopeVisitor<'_, '_> {
10282            fn visit_stmt(&mut self, stmt: &ast::Stmt) {
10283                if self.error.is_some() {
10284                    return;
10285                }
10286
10287                match stmt {
10288                    ast::Stmt::FunctionDef(ast::StmtFunctionDef {
10289                        parameters,
10290                        decorator_list,
10291                        type_params,
10292                        ..
10293                    }) => {
10294                        self.visit_parameter_defaults(parameters);
10295                        self.visit_decorators(decorator_list);
10296                        if type_params.is_some() {
10297                            self.consume_scope();
10298                        } else {
10299                            self.consume_function_annotation_scope_if_used();
10300                            self.consume_scope();
10301                        }
10302                    }
10303                    ast::Stmt::ClassDef(ast::StmtClassDef {
10304                        arguments,
10305                        decorator_list,
10306                        type_params,
10307                        ..
10308                    }) => {
10309                        self.visit_decorators(decorator_list);
10310                        if type_params.is_some() {
10311                            self.consume_scope();
10312                        }
10313                        if let Some(arguments) = arguments {
10314                            self.visit_arguments(arguments);
10315                        }
10316                        self.consume_scope();
10317                    }
10318                    ast::Stmt::TypeAlias(ast::StmtTypeAlias { type_params, .. }) => {
10319                        if type_params.is_some() {
10320                            self.consume_scope();
10321                        }
10322                        self.consume_scope();
10323                    }
10324                    ast::Stmt::AnnAssign(ast::StmtAnnAssign { target, value, .. }) => {
10325                        self.visit_expr(target);
10326                        if let Some(value) = value {
10327                            self.visit_expr(value);
10328                        }
10329                    }
10330                    ast::Stmt::If(ast::StmtIf {
10331                        test,
10332                        body,
10333                        elif_else_clauses,
10334                        ..
10335                    }) => {
10336                        self.visit_expr(test);
10337                        for stmt in body {
10338                            self.visit_stmt(stmt);
10339                        }
10340                        for clause in elif_else_clauses {
10341                            if let Some(test) = &clause.test {
10342                                self.visit_expr(test);
10343                            }
10344                            for stmt in &clause.body {
10345                                self.visit_stmt(stmt);
10346                            }
10347                        }
10348                    }
10349                    ast::Stmt::Try(ast::StmtTry {
10350                        body,
10351                        handlers,
10352                        orelse,
10353                        finalbody,
10354                        ..
10355                    }) => {
10356                        for stmt in body {
10357                            self.visit_stmt(stmt);
10358                        }
10359                        for handler in handlers {
10360                            self.visit_except_handler(handler);
10361                        }
10362                        for stmt in orelse {
10363                            self.visit_stmt(stmt);
10364                        }
10365                        for stmt in finalbody {
10366                            self.visit_stmt(stmt);
10367                        }
10368                    }
10369                    _ => ast::visitor::walk_stmt(self, stmt),
10370                }
10371            }
10372
10373            fn visit_expr(&mut self, expr: &ast::Expr) {
10374                if self.error.is_some() {
10375                    return;
10376                }
10377                self.error = self
10378                    .compiler
10379                    .consume_skipped_nested_scopes_in_expr(expr)
10380                    .err();
10381            }
10382
10383            fn visit_except_handler(&mut self, handler: &ast::ExceptHandler) {
10384                if self.error.is_some() {
10385                    return;
10386                }
10387                let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
10388                    type_,
10389                    body,
10390                    ..
10391                }) = handler;
10392                if let Some(type_) = type_ {
10393                    self.visit_expr(type_);
10394                }
10395                for stmt in body {
10396                    self.visit_stmt(stmt);
10397                }
10398            }
10399        }
10400
10401        let mut visitor = SkippedStatementScopeVisitor {
10402            compiler: self,
10403            error: None,
10404        };
10405        for statement in statements {
10406            visitor.visit_stmt(statement);
10407        }
10408        if let Some(err) = visitor.error {
10409            Err(err)
10410        } else {
10411            Ok(())
10412        }
10413    }
10414
10415    fn consume_skipped_nested_scopes_in_except_handlers(
10416        &mut self,
10417        handlers: &[ast::ExceptHandler],
10418    ) -> CompileResult<()> {
10419        use ast::visitor::Visitor;
10420
10421        struct SkippedHandlerScopeVisitor<'a, 'warnings> {
10422            compiler: &'a mut Compiler<'warnings>,
10423            error: Option<CodegenError>,
10424        }
10425
10426        impl ast::visitor::Visitor<'_> for SkippedHandlerScopeVisitor<'_, '_> {
10427            fn visit_expr(&mut self, expr: &ast::Expr) {
10428                if self.error.is_some() {
10429                    return;
10430                }
10431                self.error = self
10432                    .compiler
10433                    .consume_skipped_nested_scopes_in_expr(expr)
10434                    .err();
10435            }
10436
10437            fn visit_stmt(&mut self, stmt: &ast::Stmt) {
10438                if self.error.is_some() {
10439                    return;
10440                }
10441                self.error = self
10442                    .compiler
10443                    .consume_skipped_nested_scopes_in_statements(slice::from_ref(stmt))
10444                    .err();
10445            }
10446        }
10447
10448        let mut visitor = SkippedHandlerScopeVisitor {
10449            compiler: self,
10450            error: None,
10451        };
10452        for handler in handlers {
10453            visitor.visit_except_handler(handler);
10454            if visitor.error.is_some() {
10455                break;
10456            }
10457        }
10458        if let Some(err) = visitor.error {
10459            Err(err)
10460        } else {
10461            Ok(())
10462        }
10463    }
10464
10465    fn current_symbol_table_cursors(&self) -> SymbolTableCursors {
10466        let table = self
10467            .symbol_table_stack
10468            .last()
10469            .expect("no current symbol table");
10470        SymbolTableCursors {
10471            sub_table: table.next_sub_table,
10472            hidden_annotation_block: table.next_hidden_annotation_block,
10473            inlined_comprehension_block: table.next_inlined_comprehension_block,
10474        }
10475    }
10476
10477    fn set_symbol_table_cursors(&mut self, cursors: SymbolTableCursors) {
10478        let table = self
10479            .symbol_table_stack
10480            .last_mut()
10481            .expect("no current symbol table");
10482        table.next_sub_table = cursors.sub_table;
10483        table.next_hidden_annotation_block = cursors.hidden_annotation_block;
10484        table.next_inlined_comprehension_block = cursors.inlined_comprehension_block;
10485    }
10486
10487    /// Advance the nested-scope cursors to the first scope that begins on or
10488    /// after `line_number`.
10489    ///
10490    /// A statement list can be compiled more than once — `finally` bodies are
10491    /// re-emitted on every path that leaves the try block early — and each copy
10492    /// has to be handed the same scopes as the last. Positioning by line works
10493    /// because `finally` is the last clause of its statement: every scope the
10494    /// preceding clauses opened begins on an earlier line than the body being
10495    /// re-compiled, and every scope that body opens begins on its own line or
10496    /// later.
10497    fn seek_symbol_table_cursors_to_line(&mut self, line_number: u32) {
10498        fn seek(tables: &[SymbolTable], cursor: &mut usize, line_number: u32) {
10499            while tables
10500                .get(*cursor)
10501                .is_some_and(|table| table.line_number < line_number)
10502            {
10503                *cursor += 1;
10504            }
10505        }
10506
10507        let table = self
10508            .symbol_table_stack
10509            .last_mut()
10510            .expect("no current symbol table");
10511        seek(&table.sub_tables, &mut table.next_sub_table, line_number);
10512        seek(
10513            &table.hidden_annotation_blocks,
10514            &mut table.next_hidden_annotation_block,
10515            line_number,
10516        );
10517        seek(
10518            &table.inlined_comprehension_blocks,
10519            &mut table.next_inlined_comprehension_block,
10520            line_number,
10521        );
10522    }
10523
10524    fn lookup_comprehension_symbol_table_after_skipped_nested_scopes_in_expr(
10525        &mut self,
10526        expression: &ast::Expr,
10527        comprehension_type: ComprehensionType,
10528    ) -> CompileResult<(SymbolTable, ComprehensionSymbolSource)> {
10529        let saved_cursor = self
10530            .symbol_table_stack
10531            .last()
10532            .expect("no current symbol table")
10533            .next_sub_table;
10534        let saved_inlined_cursor = self
10535            .symbol_table_stack
10536            .last()
10537            .expect("no current symbol table")
10538            .next_inlined_comprehension_block;
10539        let result = (|| {
10540            self.consume_skipped_nested_scopes_in_expr(expression)?;
10541            let current_table = self
10542                .symbol_table_stack
10543                .last()
10544                .expect("no current symbol table");
10545            if comprehension_type != ComprehensionType::Generator
10546                && let Some(table) = current_table
10547                    .inlined_comprehension_blocks
10548                    .get(current_table.next_inlined_comprehension_block)
10549            {
10550                return Ok((table.clone(), ComprehensionSymbolSource::Inlined));
10551            }
10552            if let Some(table) = current_table.sub_tables.get(current_table.next_sub_table) {
10553                Ok((table.clone(), ComprehensionSymbolSource::Child))
10554            } else {
10555                let name = current_table.name.clone();
10556                let typ = current_table.typ;
10557                Err(self.error(CodegenErrorType::SyntaxError(format!(
10558                    "no symbol table available in {name} (type: {typ:?})"
10559                ))))
10560            }
10561        })();
10562        self.symbol_table_stack
10563            .last_mut()
10564            .expect("no current symbol table")
10565            .next_sub_table = saved_cursor;
10566        self.symbol_table_stack
10567            .last_mut()
10568            .expect("no current symbol table")
10569            .next_inlined_comprehension_block = saved_inlined_cursor;
10570        result
10571    }
10572
10573    fn push_output_with_symbol_table(
10574        &mut self,
10575        table: SymbolTable,
10576        flags: bytecode::CodeFlags,
10577        posonlyarg_count: u32,
10578        arg_count: u32,
10579        kwonlyarg_count: u32,
10580        obj_name: &str,
10581    ) -> CompileResult<()> {
10582        let scope_type = table.typ;
10583        self.symbol_table_stack.push(table);
10584
10585        let key = self.symbol_table_stack.len() - 1;
10586        let lineno = self.get_source_line_number().get();
10587        self.enter_scope(obj_name, scope_type, key, lineno.to_u32())?;
10588
10589        if let Some(info) = self.code_stack.last_mut() {
10590            info.flags = flags
10591                | (info.flags
10592                    & (bytecode::CodeFlags::NESTED
10593                        | bytecode::CodeFlags::METHOD
10594                        | bytecode::CodeFlags::FUTURE_DIVISION
10595                        | bytecode::CodeFlags::FUTURE_ABSOLUTE_IMPORT
10596                        | bytecode::CodeFlags::FUTURE_WITH_STATEMENT
10597                        | bytecode::CodeFlags::FUTURE_PRINT_FUNCTION
10598                        | bytecode::CodeFlags::FUTURE_UNICODE_LITERALS
10599                        | bytecode::CodeFlags::FUTURE_BARRY_AS_BDFL
10600                        | bytecode::CodeFlags::FUTURE_GENERATOR_STOP
10601                        | bytecode::CodeFlags::FUTURE_ANNOTATIONS));
10602            info.metadata.argcount = arg_count;
10603            info.metadata.posonlyargcount = posonlyarg_count;
10604            info.metadata.kwonlyargcount = kwonlyarg_count;
10605        }
10606        Ok(())
10607    }
10608
10609    #[expect(clippy::too_many_arguments, reason = "ignore warning for now")]
10610    fn compile_comprehension(
10611        &mut self,
10612        name: &str,
10613        init_collection: Option<AnyInstruction>,
10614        generators: &[ast::Comprehension],
10615        compile_element: &dyn Fn(&mut Self, usize) -> CompileResult<()>,
10616        comprehension_type: ComprehensionType,
10617        element_contains_await: bool,
10618        comprehension_range: TextRange,
10619        element_range: TextRange,
10620        outer_backedge_range: TextRange,
10621    ) -> CompileResult<()> {
10622        let prev_ctx = self.ctx;
10623        let has_an_async_gen = generators.iter().any(|g| g.is_async);
10624        let is_top_level_await_context = self.opts.allow_top_level_await
10625            && prev_ctx.func == FunctionContext::NoFunction
10626            && !prev_ctx.in_class;
10627
10628        // Check for async comprehension outside async function (list/set/dict only, not generator expressions)
10629        // Use in_async_scope to allow nested async comprehensions inside an async function
10630        if comprehension_type != ComprehensionType::Generator
10631            && (has_an_async_gen || element_contains_await)
10632            && !prev_ctx.in_async_scope
10633            && !is_top_level_await_context
10634        {
10635            return Err(self.error(CodegenErrorType::InvalidAsyncComprehension));
10636        }
10637
10638        // async comprehensions are allowed in various contexts:
10639        // - list/set/dict comprehensions in async functions (or nested within)
10640        // - always for generator expressions
10641        let is_async_list_set_dict_comprehension = comprehension_type
10642            != ComprehensionType::Generator
10643            && (has_an_async_gen || element_contains_await)
10644            && (prev_ctx.in_async_scope || is_top_level_await_context);
10645
10646        let is_async_generator_comprehension = comprehension_type == ComprehensionType::Generator
10647            && (has_an_async_gen || element_contains_await);
10648
10649        debug_assert!(!(is_async_list_set_dict_comprehension && is_async_generator_comprehension));
10650
10651        let is_async = is_async_list_set_dict_comprehension || is_async_generator_comprehension;
10652
10653        // We must have at least one generator:
10654        assert!(!generators.is_empty());
10655        let outermost = &generators[0];
10656        let (comp_table, comp_source) = self
10657            .lookup_comprehension_symbol_table_after_skipped_nested_scopes_in_expr(
10658                &outermost.iter,
10659                comprehension_type,
10660            )?;
10661
10662        let is_inlined = self.is_inlined_comprehension_context(comprehension_type, &comp_table);
10663
10664        if is_inlined {
10665            // CPython inlines every non-generator comprehension that the
10666            // symtable marked as comp_inlined, including async variants.
10667            // codegen_comprehension() only branches on ste_comp_inlined here
10668            // and relies on the inlined path itself to handle GET_AITER /
10669            // async-comprehension cleanup.
10670            return self.compile_inlined_comprehension(
10671                &comp_table,
10672                init_collection,
10673                generators,
10674                compile_element,
10675                (comprehension_range, element_range, outer_backedge_range),
10676                comp_source,
10677            );
10678        }
10679
10680        // Non-inlined path: create a new code object (generator expressions, etc.)
10681        self.ctx = CompileContext {
10682            in_class: prev_ctx.in_class,
10683            func: if is_async {
10684                FunctionContext::AsyncFunction
10685            } else {
10686                FunctionContext::Function
10687            },
10688            // Inherit in_async_scope from parent - nested async comprehensions are allowed
10689            // if we're anywhere inside an async function
10690            in_async_scope: prev_ctx.in_async_scope || is_async,
10691        };
10692
10693        let flags = bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED;
10694        let flags = if is_async {
10695            flags | bytecode::CodeFlags::COROUTINE
10696        } else {
10697            flags
10698        };
10699
10700        // The symbol table follows CPython's symtable walk: nested scopes
10701        // in the outermost iterator are recorded before the comprehension
10702        // scope itself. Peek past those nested scopes so we can enter the
10703        // correct comprehension table here, then let the real outermost
10704        // iterator compile consume its nested scopes later in parent scope.
10705        self.push_output_with_symbol_table(comp_table, flags, 0, 1, 0, name)?;
10706
10707        // Set qualname for comprehension
10708        self.set_qualname();
10709        self.set_source_range(comprehension_range);
10710
10711        let arg0 = self.varname(".0");
10712
10713        let return_none = init_collection.is_none();
10714
10715        // CPython codegen_comprehension() wraps only generator expressions
10716        // with codegen_wrap_in_stopiteration_handler(); unlike function bodies,
10717        // it does not push a COMPILE_FBLOCK_STOP_ITERATION fblock.
10718        let stop_iteration_block = if comprehension_type == ComprehensionType::Generator {
10719            let handler_block = self.new_block();
10720            self.insert_cpython_stopiteration_setup_cleanup(handler_block);
10721            Some(handler_block)
10722        } else {
10723            None
10724        };
10725
10726        // Create empty object of proper type:
10727        if let Some(init_collection) = init_collection {
10728            self._emit(init_collection, OpArg::new(0), BlockIdx::NULL)
10729        }
10730
10731        let mut loop_labels = vec![];
10732        let mut real_loop_depth = 0;
10733        for (gen_index, generator) in generators.iter().enumerate() {
10734            if gen_index > 0
10735                && !generator.is_async
10736                && let Some(singleton_iter) =
10737                    Self::singleton_comprehension_assignment_iter(&generator.iter)
10738            {
10739                // CPython allocates start/if_cleanup/anchor labels before the
10740                // singleton sub-iterator fast path sets start = NO_LABEL.
10741                let _start_label = self.current_code_info().new_instr_sequence_label();
10742                let if_cleanup_block = self.new_block();
10743                let _anchor_label = self.current_code_info().new_instr_sequence_label();
10744                self.compile_expression(singleton_iter)?;
10745                self.compile_store(&generator.target)?;
10746
10747                if !generator.ifs.is_empty() {
10748                    for if_condition in &generator.ifs {
10749                        self.compile_jump_if(if_condition, false, if_cleanup_block)?;
10750                    }
10751                }
10752                loop_labels.push(ComprehensionLoopControl::IfCleanupOnly { if_cleanup_block });
10753                continue;
10754            }
10755
10756            let loop_block = self.new_block();
10757            let (send_block, after_block, if_cleanup_block) = if generator.is_async {
10758                let send_block = self.new_block();
10759                let after_block = self.new_block();
10760                let if_cleanup_block = self.new_block();
10761                (send_block, after_block, if_cleanup_block)
10762            } else {
10763                let if_cleanup_block = self.new_block();
10764                let after_block = self.new_block();
10765                (BlockIdx::NULL, after_block, if_cleanup_block)
10766            };
10767
10768            if gen_index == 0 {
10769                // Load iterator onto stack (passed as first argument):
10770                emit!(self, Instruction::LoadFast { var_num: arg0 });
10771            } else {
10772                // Evaluate iterated item:
10773                self.compile_comprehension_iter(generator)?;
10774            }
10775
10776            self.use_cpython_label_block(loop_block);
10777            let mut end_async_for_target = BlockIdx::NULL;
10778            if generator.is_async {
10779                let loop_label = self.instr_sequence_label_for_block(loop_block);
10780                self.push_fblock_labels(
10781                    FBlockType::AsyncComprehensionGenerator,
10782                    loop_label,
10783                    ir::InstructionSequenceLabel::NO_LABEL,
10784                    FBlockDatum::None,
10785                )?;
10786                emit!(self, PseudoInstruction::SetupFinally { delta: after_block });
10787                emit!(self, Instruction::GetAnext);
10788                self.emit_load_const(ConstantData::None);
10789                self.use_cpython_label_block(send_block);
10790                let _ = self.compile_yield_from_sequence(true);
10791                end_async_for_target = send_block;
10792                // POP_BLOCK before store: only __anext__/yield_from are
10793                // protected by SetupFinally targeting END_ASYNC_FOR.
10794                emit!(self, PseudoInstruction::PopBlock);
10795                self.compile_store(&generator.target)?;
10796            } else {
10797                let saved_range = self.current_source_range;
10798                self.set_source_range(generator.iter.range());
10799                emit!(self, Instruction::ForIter { delta: after_block });
10800                self.set_source_range(saved_range);
10801                self.compile_store(&generator.target)?;
10802            }
10803            real_loop_depth += 1;
10804            let backedge_range = if gen_index + 1 == generators.len() {
10805                element_range
10806            } else {
10807                outer_backedge_range
10808            };
10809            loop_labels.push(ComprehensionLoopControl::Iteration {
10810                loop_block,
10811                if_cleanup_block,
10812                after_block,
10813                backedge_range,
10814                is_async: generator.is_async,
10815                end_async_for_target,
10816            });
10817
10818            // CPython always lowers comprehension guards through codegen_jump_if
10819            // and leaves constant-folding to later CFG optimization passes.
10820            for if_condition in &generator.ifs {
10821                self.compile_jump_if(if_condition, false, if_cleanup_block)?;
10822            }
10823        }
10824
10825        compile_element(self, real_loop_depth + 1)?;
10826
10827        for loop_control in loop_labels.iter().rev().copied() {
10828            match loop_control {
10829                ComprehensionLoopControl::Iteration {
10830                    loop_block,
10831                    if_cleanup_block,
10832                    after_block,
10833                    backedge_range,
10834                    is_async,
10835                    end_async_for_target,
10836                } => {
10837                    self.use_cpython_label_block(if_cleanup_block);
10838                    self.set_source_range(backedge_range);
10839                    emit!(self, PseudoInstruction::Jump { delta: loop_block });
10840
10841                    if is_async {
10842                        let loop_label = self.instr_sequence_label_for_block(loop_block);
10843                        self.pop_fblock_label(FBlockType::AsyncComprehensionGenerator, loop_label);
10844                    }
10845
10846                    self.use_cpython_label_block(after_block);
10847                    if is_async {
10848                        self.set_source_range(comprehension_range);
10849                        // EndAsyncFor pops both the exception and the aiter
10850                        // (handler depth is before GetANext, so aiter is at handler depth)
10851                        self.emit_end_async_for(end_async_for_target);
10852                    } else {
10853                        self.emit_sync_comprehension_end_for();
10854                    }
10855                }
10856                ComprehensionLoopControl::IfCleanupOnly { if_cleanup_block } => {
10857                    self.use_cpython_label_block(if_cleanup_block);
10858                }
10859            }
10860        }
10861
10862        if return_none {
10863            self.emit_return_const_no_location(ConstantData::None);
10864        } else {
10865            self.set_source_range(comprehension_range);
10866            self.emit_return_value();
10867        }
10868
10869        // Close StopIteration handler and emit handler code
10870        if let Some(handler_block) = stop_iteration_block {
10871            self.use_cpython_label_block(handler_block);
10872            emit!(
10873                self,
10874                Instruction::CallIntrinsic1 {
10875                    func: oparg::IntrinsicFunction1::StopIterationError
10876                }
10877            );
10878            self.set_no_location();
10879            emit!(self, Instruction::Reraise { depth: 1u32 });
10880            self.set_no_location();
10881        }
10882        self.emit_return_const_no_location(ConstantData::None);
10883
10884        let code = self.exit_scope();
10885
10886        self.ctx = prev_ctx;
10887
10888        // Create comprehension function with closure
10889        self.set_source_range(comprehension_range);
10890        self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
10891
10892        // Evaluate iterated item and get its iterator.
10893        self.compile_comprehension_iter(outermost)?;
10894        self.symbol_table_stack
10895            .last_mut()
10896            .expect("no current symbol table")
10897            .next_sub_table += 1;
10898
10899        // Call just created <listcomp> function:
10900        self.set_source_range(comprehension_range);
10901        emit!(self, Instruction::Call { argc: 0 });
10902        if is_async_list_set_dict_comprehension {
10903            emit!(self, Instruction::GetAwaitable { r#where: 0 });
10904            self.emit_load_const(ConstantData::None);
10905            let _ = self.compile_yield_from_sequence(true);
10906        }
10907
10908        Ok(())
10909    }
10910
10911    /// Compile an inlined comprehension (PEP 709)
10912    /// This generates bytecode inline without creating a new code object
10913    fn compile_inlined_comprehension(
10914        &mut self,
10915        comp_table: &SymbolTable,
10916        init_collection: Option<AnyInstruction>,
10917        generators: &[ast::Comprehension],
10918        compile_element: &dyn Fn(&mut Self, usize) -> CompileResult<()>,
10919        ranges: (TextRange, TextRange, TextRange),
10920        comp_source: ComprehensionSymbolSource,
10921    ) -> CompileResult<()> {
10922        let (comprehension_range, element_range, outer_backedge_range) = ranges;
10923        // Compile the outermost iterator first. Its expression may reference
10924        // nested scopes (e.g. lambdas) whose sub_tables sit at the current
10925        // position in the parent's list. Those must be consumed before we
10926        // splice in the comprehension's own children.
10927        self.compile_comprehension_iter(&generators[0])?;
10928        match comp_source {
10929            ComprehensionSymbolSource::Child => {
10930                self.symbol_table_stack
10931                    .last_mut()
10932                    .expect("no current symbol table")
10933                    .next_sub_table += 1;
10934            }
10935            ComprehensionSymbolSource::Inlined => {
10936                self.symbol_table_stack
10937                    .last_mut()
10938                    .expect("no current symbol table")
10939                    .next_inlined_comprehension_block += 1;
10940            }
10941        }
10942
10943        let was_in_inlined_comp = self.current_code_info().in_inlined_comp;
10944        let saved_source_range = self.current_source_range;
10945        let tweak_in_class_block = {
10946            let ct = self.current_symbol_table();
10947            ct.typ == CompilerScope::Class && !was_in_inlined_comp
10948        };
10949        self.current_code_info().in_inlined_comp = true;
10950
10951        let mut temp_symbols: IndexMap<Name, Symbol> = IndexMap::default();
10952        let mut changed_fast_hidden = Vec::new();
10953
10954        let result = (|| {
10955            // If the symbol table still carries the inlined comprehension as
10956            // a child, splice its children here. The symtable normally
10957            // performs this splice before codegen, and the Inlined source path
10958            // has already done so.
10959            if matches!(comp_source, ComprehensionSymbolSource::Child)
10960                && !comp_table.sub_tables.is_empty()
10961            {
10962                let current_table = self
10963                    .symbol_table_stack
10964                    .last_mut()
10965                    .expect("no current symbol table");
10966                let insert_pos = current_table.next_sub_table;
10967                for (i, st) in comp_table.sub_tables.iter().enumerate() {
10968                    current_table.sub_tables.insert(insert_pos + i, st.clone());
10969                }
10970            }
10971            let mut pushed_locals: Vec<Name> = Vec::new();
10972            let mut fast_hidden_locals: Vec<Name> = Vec::new();
10973            for (name, sym) in &comp_table.symbols {
10974                if sym.flags.contains(SymbolFlags::DEF_PARAM) {
10975                    continue; // skip .0
10976                }
10977                let is_local = sym.flags.contains(SymbolFlags::DEF_LOCAL)
10978                    && !sym.flags.contains(SymbolFlags::DEF_NONLOCAL);
10979                if is_local {
10980                    pushed_locals.push(name.clone());
10981                }
10982                if is_local || tweak_in_class_block {
10983                    fast_hidden_locals.push(name.clone());
10984                }
10985            }
10986
10987            // TweakInlinedComprehensionScopes: temporarily override parent
10988            // symbols with comprehension scopes where they differ. For
10989            // module/class scopes, also enable temporary fast locals for
10990            // comprehension-bound names only.
10991            for (name, comp_sym) in &comp_table.symbols {
10992                if comp_sym.flags.contains(SymbolFlags::DEF_PARAM) {
10993                    continue; // skip .0
10994                }
10995                let comp_scope = comp_sym.scope;
10996
10997                let current_table = self.symbol_table_stack.last().expect("no symbol table");
10998                if let Some(outer_sym) = current_table.symbols.get(name) {
10999                    let outer_scope = outer_sym.scope;
11000                    if (comp_scope != outer_scope
11001                        && comp_scope != SymbolScope::Free
11002                        && !(comp_scope == SymbolScope::Cell && outer_scope == SymbolScope::Free))
11003                        || tweak_in_class_block
11004                    {
11005                        temp_symbols.insert(name.clone(), outer_sym.clone());
11006                        let current_table =
11007                            self.symbol_table_stack.last_mut().expect("no symbol table");
11008                        current_table.symbols.insert(name.clone(), comp_sym.clone());
11009                    }
11010                }
11011            }
11012            if !self.ctx.in_func() {
11013                for name in &fast_hidden_locals {
11014                    if self
11015                        .current_code_info()
11016                        .metadata
11017                        .fast_hidden
11018                        .get(name.as_str())
11019                        .is_none_or(|&hidden| !hidden)
11020                    {
11021                        self.current_code_info()
11022                            .metadata
11023                            .fast_hidden
11024                            .insert(name.clone().into(), true);
11025                        self.current_code_info()
11026                            .metadata
11027                            .fast_hidden_final
11028                            .insert(name.clone().into());
11029                        changed_fast_hidden.push(name.clone());
11030                    }
11031                }
11032            }
11033
11034            // Step 2: Save local variables that will be shadowed by the comprehension.
11035            // For each variable, we push the fast local value via LoadFastAndClear.
11036            // For merged CELL variables, LoadFastAndClear saves the cell object from
11037            // the merged slot, and MAKE_CELL creates a new empty cell in-place.
11038            // MAKE_CELL has no stack effect (operates only on fastlocals).
11039            self.set_source_range(comprehension_range);
11040            let mut total_stack_items: usize = 0;
11041            for name in &pushed_locals {
11042                let var_num = self.varname(name);
11043                emit!(self, Instruction::LoadFastAndClear { var_num });
11044                total_stack_items += 1;
11045                // If the comp symbol is CELL, emit MAKE_CELL to create fresh cell
11046                if let Some(comp_sym) = comp_table.symbols.get(name)
11047                    && comp_sym.scope == SymbolScope::Cell
11048                {
11049                    let i = if self
11050                        .current_symbol_table()
11051                        .symbols
11052                        .get(name)
11053                        .is_some_and(|s| s.scope == SymbolScope::Free)
11054                    {
11055                        self.get_free_var_index(name)
11056                    } else {
11057                        self.get_cell_var_index(name)
11058                    };
11059                    emit!(self, Instruction::MakeCell { i });
11060                }
11061            }
11062
11063            // Step 3: SWAP iterator to TOS (above saved locals + cell values)
11064            if total_stack_items > 0 {
11065                emit!(
11066                    self,
11067                    Instruction::Swap {
11068                        i: u32::try_from(total_stack_items + 1).unwrap()
11069                    }
11070                );
11071            }
11072
11073            // CPython's codegen_push_inlined_comprehension_locals()
11074            // installs the virtual cleanup before codegen_comprehension()
11075            // emits BUILD_LIST/BUILD_SET/BUILD_MAP for the result object.
11076            let cleanup_block = if !pushed_locals.is_empty() {
11077                let cleanup_block = self.new_block();
11078                emit!(
11079                    self,
11080                    PseudoInstruction::SetupFinally {
11081                        delta: cleanup_block
11082                    }
11083                );
11084                Some(cleanup_block)
11085            } else {
11086                None
11087            };
11088
11089            // Step 4: Create the collection (list/set/dict)
11090            if let Some(init_collection) = init_collection {
11091                self._emit(init_collection, OpArg::new(0), BlockIdx::NULL);
11092                // SWAP to get iterator on top
11093                emit!(self, Instruction::Swap { i: 2 });
11094            }
11095
11096            // Step 5: Compile the comprehension loop(s)
11097            let mut loop_labels: Vec<ComprehensionLoopControl> = vec![];
11098            let mut real_loop_depth = 0;
11099            for (i, generator) in generators.iter().enumerate() {
11100                if i > 0
11101                    && !generator.is_async
11102                    && let Some(singleton_iter) =
11103                        Self::singleton_comprehension_assignment_iter(&generator.iter)
11104                {
11105                    // CPython allocates start/if_cleanup/anchor labels before
11106                    // the singleton sub-iterator fast path sets start = NO_LABEL.
11107                    let _start_label = self.current_code_info().new_instr_sequence_label();
11108                    let if_cleanup_block = self.new_block();
11109                    let _anchor_label = self.current_code_info().new_instr_sequence_label();
11110                    self.compile_expression(singleton_iter)?;
11111                    self.compile_store(&generator.target)?;
11112
11113                    if !generator.ifs.is_empty() {
11114                        for if_condition in &generator.ifs {
11115                            self.compile_jump_if(if_condition, false, if_cleanup_block)?;
11116                        }
11117                    }
11118                    loop_labels.push(ComprehensionLoopControl::IfCleanupOnly { if_cleanup_block });
11119                    continue;
11120                }
11121
11122                let loop_block = self.new_block();
11123                let (send_block, after_block, if_cleanup_block) = if generator.is_async {
11124                    let send_block = self.new_block();
11125                    let after_block = self.new_block();
11126                    let if_cleanup_block = self.new_block();
11127                    (send_block, after_block, if_cleanup_block)
11128                } else {
11129                    let if_cleanup_block = self.new_block();
11130                    let after_block = self.new_block();
11131                    (BlockIdx::NULL, after_block, if_cleanup_block)
11132                };
11133
11134                if i > 0 {
11135                    self.compile_comprehension_iter(generator)?;
11136                }
11137
11138                self.use_cpython_label_block(loop_block);
11139
11140                let mut end_async_for_target = BlockIdx::NULL;
11141                if generator.is_async {
11142                    let loop_label = self.instr_sequence_label_for_block(loop_block);
11143                    self.push_fblock_labels(
11144                        FBlockType::AsyncComprehensionGenerator,
11145                        loop_label,
11146                        ir::InstructionSequenceLabel::NO_LABEL,
11147                        FBlockDatum::None,
11148                    )?;
11149                    emit!(self, PseudoInstruction::SetupFinally { delta: after_block });
11150                    emit!(self, Instruction::GetAnext);
11151                    self.emit_load_const(ConstantData::None);
11152                    self.use_cpython_label_block(send_block);
11153                    let _ = self.compile_yield_from_sequence(true);
11154                    end_async_for_target = send_block;
11155                    emit!(self, PseudoInstruction::PopBlock);
11156                    self.compile_store(&generator.target)?;
11157                } else {
11158                    let saved_range = self.current_source_range;
11159                    self.set_source_range(generator.iter.range());
11160                    emit!(self, Instruction::ForIter { delta: after_block });
11161                    self.set_source_range(saved_range);
11162                    self.compile_store(&generator.target)?;
11163                }
11164
11165                real_loop_depth += 1;
11166                let backedge_range = if i + 1 == generators.len() {
11167                    element_range
11168                } else {
11169                    outer_backedge_range
11170                };
11171                loop_labels.push(ComprehensionLoopControl::Iteration {
11172                    loop_block,
11173                    if_cleanup_block,
11174                    after_block,
11175                    backedge_range,
11176                    is_async: generator.is_async,
11177                    end_async_for_target,
11178                });
11179
11180                // CPython always lowers comprehension guards through codegen_jump_if
11181                // and leaves constant-folding to later CFG optimization passes.
11182                for if_condition in &generator.ifs {
11183                    self.compile_jump_if(if_condition, false, if_cleanup_block)?;
11184                }
11185            }
11186
11187            // Step 6: Compile the element expression and append to collection
11188            compile_element(self, real_loop_depth + 1)?;
11189
11190            // Step 7: Close all loops
11191            for loop_control in loop_labels.iter().rev().copied() {
11192                match loop_control {
11193                    ComprehensionLoopControl::Iteration {
11194                        loop_block,
11195                        if_cleanup_block,
11196                        after_block,
11197                        backedge_range,
11198                        is_async,
11199                        end_async_for_target,
11200                    } => {
11201                        self.use_cpython_label_block(if_cleanup_block);
11202                        self.set_source_range(backedge_range);
11203                        emit!(self, PseudoInstruction::Jump { delta: loop_block });
11204
11205                        if is_async {
11206                            let loop_label = self.instr_sequence_label_for_block(loop_block);
11207                            self.pop_fblock_label(
11208                                FBlockType::AsyncComprehensionGenerator,
11209                                loop_label,
11210                            );
11211                        }
11212
11213                        self.use_cpython_label_block(after_block);
11214                        if is_async {
11215                            self.set_source_range(comprehension_range);
11216                            self.emit_end_async_for(end_async_for_target);
11217                        } else {
11218                            self.emit_sync_comprehension_end_for();
11219                        }
11220                    }
11221                    ComprehensionLoopControl::IfCleanupOnly { if_cleanup_block } => {
11222                        self.use_cpython_label_block(if_cleanup_block);
11223                    }
11224                }
11225            }
11226
11227            // Step 8: Clean up - restore saved locals (and cell values)
11228            self.set_source_range(comprehension_range);
11229            if let Some(cleanup_block) = cleanup_block {
11230                emit!(self, PseudoInstruction::PopBlock);
11231                self.set_no_location();
11232
11233                // Match CPython codegen_pop_inlined_comprehension_locals():
11234                // the synthetic jump that skips the exception cleanup uses
11235                // JUMP_NO_INTERRUPT, which becomes JUMP_BACKWARD_NO_INTERRUPT
11236                // when the cleanup tail sits above the final restore block.
11237                let end_block = self.new_block();
11238                emit!(
11239                    self,
11240                    PseudoInstruction::JumpNoInterrupt { delta: end_block }
11241                );
11242                self.set_no_location();
11243
11244                // Exception cleanup path
11245                self.use_cpython_label_block(cleanup_block);
11246                // Stack: [saved_values..., collection, exception]
11247                emit!(self, Instruction::Swap { i: 2 });
11248                self.set_no_location();
11249                emit!(self, Instruction::PopTop); // Pop incomplete collection
11250                self.set_no_location();
11251
11252                // Restore locals and cell values
11253                self.set_source_range(comprehension_range);
11254                emit!(
11255                    self,
11256                    Instruction::Swap {
11257                        i: u32::try_from(total_stack_items + 1).unwrap()
11258                    }
11259                );
11260                for name in pushed_locals.iter().rev() {
11261                    let var_num = self.varname(name).as_u32();
11262                    emit!(self, PseudoInstruction::StoreFastMaybeNull { var_num });
11263                }
11264                // Re-raise the exception
11265                emit!(self, Instruction::Reraise { depth: 0 });
11266                self.set_no_location();
11267
11268                // Normal end path
11269                self.use_cpython_label_block(end_block);
11270                self.set_source_range(comprehension_range);
11271            }
11272
11273            // SWAP result to TOS (above saved values)
11274            if total_stack_items > 0 {
11275                emit!(
11276                    self,
11277                    Instruction::Swap {
11278                        i: u32::try_from(total_stack_items + 1).unwrap()
11279                    }
11280                );
11281            }
11282
11283            // Restore saved locals (StoreFast restores the saved cell object for merged cells)
11284            for name in pushed_locals.iter().rev() {
11285                let var_num = self.varname(name).as_u32();
11286                emit!(self, PseudoInstruction::StoreFastMaybeNull { var_num });
11287            }
11288            self.set_source_range(saved_source_range);
11289
11290            Ok(())
11291        })();
11292
11293        let current_table = self.symbol_table_stack.last_mut().expect("no symbol table");
11294        for (name, original_sym) in temp_symbols {
11295            current_table.symbols.insert(name, original_sym);
11296        }
11297        for name in changed_fast_hidden {
11298            self.current_code_info()
11299                .metadata
11300                .fast_hidden
11301                .insert(name.into(), false);
11302        }
11303        self.current_code_info().in_inlined_comp = was_in_inlined_comp;
11304
11305        result
11306    }
11307
11308    fn compile_future_features(&mut self, features: &[ast::Alias]) -> Result<(), CodegenError> {
11309        for feature in features {
11310            let future_feature = feature.name.as_str().try_into().map_err(|name| {
11311                self.error_ranged(CodegenErrorType::InvalidFutureFeature(name), feature.range)
11312            })?;
11313
11314            match future_feature {
11315                FutureFeature::Braces => {
11316                    return Err(
11317                        self.error_ranged(CodegenErrorType::InvalidFutureBraces, feature.range)
11318                    );
11319                }
11320                FutureFeature::Annotations => {
11321                    self.future_annotations = true;
11322                    self.future_features
11323                        .insert(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
11324                    self.current_code_info()
11325                        .flags
11326                        .insert(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
11327                }
11328                FutureFeature::BarryAsFLUFL => {
11329                    self.future_features
11330                        .insert(bytecode::CodeFlags::FUTURE_BARRY_AS_BDFL);
11331                    self.current_code_info()
11332                        .flags
11333                        .insert(bytecode::CodeFlags::FUTURE_BARRY_AS_BDFL);
11334                }
11335                FutureFeature::AbsoluteImport
11336                | FutureFeature::Division
11337                | FutureFeature::GeneratorStop
11338                | FutureFeature::Generators
11339                | FutureFeature::NestedScopes
11340                | FutureFeature::PrintFunction
11341                | FutureFeature::UnicodeLiterals
11342                | FutureFeature::WithStatement => {
11343                    // Python 3 features. They are already implemented by default.
11344                }
11345            }
11346        }
11347        Ok(())
11348    }
11349
11350    // Low level helper functions:
11351    /// CPython `_PyCfgBuilder_Addop()`: start a new basic block if the current
11352    /// one is terminated, then append the instruction to the current block.
11353    fn cpython_cfg_builder_addop(&mut self, info: ir::InstructionInfo) {
11354        self.maybe_start_cpython_cfg_addop_block();
11355        self.push_emitted_instruction(info);
11356    }
11357
11358    fn push_emitted_instruction(&mut self, info: ir::InstructionInfo) {
11359        self.current_code_info()
11360            .addop_to_instr_sequence(info)
11361            .expect("malformed instruction sequence emission");
11362        self.current_code_info()
11363            .addop_to_current_block(info)
11364            .expect("malformed CFG emission");
11365    }
11366
11367    fn push_emitted_instruction_with_target_label(
11368        &mut self,
11369        info: ir::InstructionInfo,
11370        target_label: ir::InstructionSequenceLabel,
11371    ) {
11372        self.current_code_info()
11373            .addop_to_instr_sequence_with_target_label(info, target_label)
11374            .expect("malformed instruction sequence emission");
11375        self.current_code_info()
11376            .addop_to_current_block(info)
11377            .expect("malformed CFG emission");
11378    }
11379
11380    fn last_emitted_instruction_mut(&mut self) -> Option<&mut ir::InstructionInfo> {
11381        self.current_code_info().last_current_block_instr_mut()
11382    }
11383
11384    fn set_last_emitted_lineno_override(&mut self, lineno_override: i32) {
11385        self.current_code_info()
11386            .set_last_instr_sequence_lineno_override(lineno_override);
11387        if let Some(last) = self.last_emitted_instruction_mut() {
11388            last.lineno_override = Some(lineno_override);
11389        }
11390    }
11391
11392    fn _emit<I: Into<AnyInstruction>>(&mut self, instr: I, arg: OpArg, target: BlockIdx) {
11393        if self.do_not_emit_bytecode > 0 {
11394            return;
11395        }
11396        let instr = instr.into();
11397        let opcode = AnyOpcode::from(instr);
11398        debug_assert!(
11399            !instr.is_assembler(),
11400            "CPython codegen_addop_* must not emit assembler-only opcodes"
11401        );
11402        debug_assert!(
11403            opcode.has_arg() || instr.has_target() || u32::from(arg) == 0,
11404            "CPython _PyInstructionSequence_Addop requires either OPCODE_HAS_ARG, HAS_TARGET, or oparg == 0"
11405        );
11406        debug_assert!(
11407            target == BlockIdx::NULL || instr.has_target(),
11408            "CPython codegen_addop_j only accepts HAS_TARGET opcodes"
11409        );
11410        let range = self.current_source_range;
11411        let source = self.source_file.to_source_code();
11412        let location = source.source_location(range.start(), PositionEncoding::Utf8);
11413        let end_location = source.source_location(range.end(), PositionEncoding::Utf8);
11414        let except_handler = None;
11415        self.cpython_cfg_builder_addop(ir::InstructionInfo {
11416            instr,
11417            arg,
11418            target,
11419            location,
11420            end_location,
11421            except_handler,
11422            lineno_override: None,
11423        });
11424    }
11425
11426    /// CPython `codegen_addop_j()`: emit a HAS_TARGET instruction with a
11427    /// jump_target_label oparg.
11428    fn emit_jump_label<I: Into<AnyInstruction>>(
11429        &mut self,
11430        instr: I,
11431        target_label: ir::InstructionSequenceLabel,
11432    ) {
11433        if self.do_not_emit_bytecode > 0 {
11434            return;
11435        }
11436        let instr = instr.into();
11437        debug_assert!(
11438            instr.has_target(),
11439            "CPython codegen_addop_j only accepts HAS_TARGET opcodes"
11440        );
11441        debug_assert!(
11442            !instr.is_assembler(),
11443            "CPython codegen_addop_j must not emit assembler-only opcodes"
11444        );
11445        let range = self.current_source_range;
11446        let source = self.source_file.to_source_code();
11447        let location = source.source_location(range.start(), PositionEncoding::Utf8);
11448        let end_location = source.source_location(range.end(), PositionEncoding::Utf8);
11449        let target = self
11450            .current_code_info()
11451            .block_for_instr_sequence_label(target_label);
11452        self.maybe_start_cpython_cfg_addop_block();
11453        self.push_emitted_instruction_with_target_label(
11454            ir::InstructionInfo {
11455                instr,
11456                arg: OpArg::NULL,
11457                target,
11458                location,
11459                end_location,
11460                except_handler: None,
11461                lineno_override: None,
11462            },
11463            target_label,
11464        );
11465    }
11466
11467    /// Mark the last emitted instruction as having no source location.
11468    /// Prevents it from triggering LINE events in sys.monitoring.
11469    fn set_no_location(&mut self) {
11470        self.set_last_emitted_lineno_override(-1);
11471    }
11472
11473    /// CPython `codegen_sync_comprehension_generator()` emits END_FOR/POP_ITER
11474    /// with NO_LOCATION and lets `flowgraph.c::propagate_line_numbers()` copy
11475    /// the FOR_ITER location onto the cleanup block.
11476    fn emit_sync_comprehension_end_for(&mut self) {
11477        emit!(self, Instruction::EndFor);
11478        self.set_no_location();
11479        emit!(self, Instruction::PopIter);
11480        self.set_no_location();
11481    }
11482
11483    /// CPython `codegen_wrap_in_stopiteration_handler()` inserts
11484    /// `SETUP_CLEANUP` into the instruction sequence at index 0 with
11485    /// `_PyInstructionSequence_InsertInstruction()`. Generator and cell/free
11486    /// prefixes are inserted later by `flowgraph.c::insert_prefix_instructions()`.
11487    fn insert_cpython_stopiteration_setup_cleanup(&mut self, handler_block: BlockIdx) {
11488        let code = self.current_code_info();
11489        let entry = code
11490            .blocks
11491            .first_mut()
11492            .expect("code unit must have an entry block");
11493        debug_assert!(
11494            entry
11495                .used_instructions()
11496                .first()
11497                .is_some_and(|info| match info.instr.real() {
11498                    Some(Instruction::Resume { context }) => matches!(
11499                        context.get(info.arg).location(),
11500                        oparg::ResumeLocation::AtFuncStart
11501                    ),
11502                    _ => false,
11503                }),
11504            "scope entry must start with a function-start RESUME"
11505        );
11506        debug_assert!(
11507            !entry.used_instructions().iter().any(|info| matches!(
11508                info.instr.real(),
11509                Some(
11510                    Instruction::ReturnGenerator
11511                        | Instruction::MakeCell { .. }
11512                        | Instruction::CopyFreeVars { .. }
11513                )
11514            )),
11515            "CPython inserts StopIteration cleanup before CFG prefix instructions"
11516        );
11517
11518        let result = code.insert_start_setup_cleanup(handler_block);
11519        unwrap_internal(self, result);
11520    }
11521
11522    fn emit_no_arg<I: Into<AnyInstruction>>(&mut self, ins: I) {
11523        self._emit(ins, OpArg::NULL, BlockIdx::NULL)
11524    }
11525
11526    fn emit_arg<A: OpArgType, T: EmitArg<A>, I: Into<AnyInstruction>>(
11527        &mut self,
11528        arg: T,
11529        f: impl FnOnce(OpArgMarker<A>) -> I,
11530    ) {
11531        let (op, arg, target) = arg.emit(f);
11532        self._emit(op, arg, target)
11533    }
11534
11535    // fn block_done()
11536
11537    fn arg_constant(&mut self, constant: ConstantData) -> oparg::ConstIdx {
11538        let info = self.current_code_info();
11539        if let ConstantData::Code { code } = &constant
11540            && let Some(idx) = info.metadata.consts.iter().position(|existing| {
11541                matches!(
11542                    existing,
11543                    ConstantData::Code {
11544                        code: existing_code
11545                    } if Self::code_objects_equivalent(existing_code, code)
11546                )
11547            })
11548        {
11549            return u32::try_from(idx)
11550                .expect("constant table index overflow")
11551                .into();
11552        }
11553        info.metadata.consts.insert_full(constant).0.to_u32().into()
11554    }
11555
11556    fn constants_equivalent(lhs: &ConstantData, rhs: &ConstantData) -> bool {
11557        match (lhs, rhs) {
11558            (ConstantData::Code { code: lhs }, ConstantData::Code { code: rhs }) => {
11559                Self::code_objects_equivalent(lhs, rhs)
11560            }
11561            (ConstantData::Tuple { elements: lhs }, ConstantData::Tuple { elements: rhs })
11562            | (
11563                ConstantData::Frozenset { elements: lhs },
11564                ConstantData::Frozenset { elements: rhs },
11565            ) => {
11566                lhs.len() == rhs.len()
11567                    && lhs
11568                        .iter()
11569                        .zip(rhs.iter())
11570                        .all(|(lhs, rhs)| Self::constants_equivalent(lhs, rhs))
11571            }
11572            (ConstantData::Slice { elements: lhs }, ConstantData::Slice { elements: rhs }) => lhs
11573                .iter()
11574                .zip(rhs.iter())
11575                .all(|(lhs, rhs)| Self::constants_equivalent(lhs, rhs)),
11576            _ => lhs == rhs,
11577        }
11578    }
11579
11580    fn code_objects_equivalent(lhs: &bytecode::CodeObject, rhs: &bytecode::CodeObject) -> bool {
11581        lhs.instructions.len() == rhs.instructions.len()
11582            && lhs
11583                .instructions
11584                .iter()
11585                .zip(rhs.instructions.iter())
11586                .all(|(lhs, rhs)| u8::from(lhs.op) == u8::from(rhs.op) && lhs.arg == rhs.arg)
11587            && lhs.locations == rhs.locations
11588            && lhs.flags.bits() == rhs.flags.bits()
11589            && lhs.posonlyarg_count == rhs.posonlyarg_count
11590            && lhs.arg_count == rhs.arg_count
11591            && lhs.kwonlyarg_count == rhs.kwonlyarg_count
11592            && lhs.source_path == rhs.source_path
11593            && lhs.first_line_number == rhs.first_line_number
11594            && lhs.max_stackdepth == rhs.max_stackdepth
11595            && lhs.obj_name == rhs.obj_name
11596            && lhs.qualname == rhs.qualname
11597            && lhs.constants.len() == rhs.constants.len()
11598            && lhs
11599                .constants
11600                .iter()
11601                .zip(rhs.constants.iter())
11602                .all(|(lhs, rhs)| Self::constants_equivalent(lhs, rhs))
11603            && lhs.names == rhs.names
11604            && lhs.varnames == rhs.varnames
11605            && lhs.cellvars == rhs.cellvars
11606            && lhs.freevars == rhs.freevars
11607            && lhs.localspluskinds == rhs.localspluskinds
11608            && lhs.linetable == rhs.linetable
11609            && lhs.exceptiontable == rhs.exceptiontable
11610    }
11611
11612    fn constant_as_fold_int(constant: &ConstantData) -> Option<(BigInt, bool)> {
11613        match constant {
11614            ConstantData::Boolean { value } => Some((BigInt::from(u8::from(*value)), true)),
11615            ConstantData::Integer { value } => Some((value.clone(), false)),
11616            _ => None,
11617        }
11618    }
11619
11620    fn try_fold_constant_binop(
11621        op: ast::Operator,
11622        left: &ConstantData,
11623        right: &ConstantData,
11624    ) -> Option<ConstantData> {
11625        let (left_int, left_is_bool) = Self::constant_as_fold_int(left)?;
11626        let (right_int, right_is_bool) = Self::constant_as_fold_int(right)?;
11627
11628        if !(left_is_bool && right_is_bool) {
11629            return None;
11630        }
11631
11632        match op {
11633            ast::Operator::BitAnd => Some(ConstantData::Boolean {
11634                value: !left_int.is_zero() & !right_int.is_zero(),
11635            }),
11636            ast::Operator::BitOr => Some(ConstantData::Boolean {
11637                value: !left_int.is_zero() | !right_int.is_zero(),
11638            }),
11639            ast::Operator::BitXor => Some(ConstantData::Boolean {
11640                value: !left_int.is_zero() ^ !right_int.is_zero(),
11641            }),
11642            _ => None,
11643        }
11644    }
11645
11646    fn try_fold_constant_expr(&mut self, expr: &ast::Expr) -> CompileResult<Option<ConstantData>> {
11647        if let Some(constant) = self.ast_constant_value(expr) {
11648            return Ok(Some(constant));
11649        }
11650        Ok(Some(match expr {
11651            ast::Expr::NumberLiteral(num) => match &num.value {
11652                ast::Number::Int(int) => ConstantData::Integer {
11653                    value: ruff_int_to_bigint(int).map_err(|e| self.error(e))?,
11654                },
11655                ast::Number::Float(f) => ConstantData::Float { value: *f },
11656                ast::Number::Complex { real, imag } => ConstantData::Complex {
11657                    value: Complex::new(*real, *imag),
11658                },
11659            },
11660            ast::Expr::StringLiteral(s) => ConstantData::Str {
11661                value: string_literal_value(&self.source_file, &s.value),
11662            },
11663            ast::Expr::BytesLiteral(b) => ConstantData::Bytes {
11664                value: b.value.bytes().collect(),
11665            },
11666            ast::Expr::BooleanLiteral(b) => ConstantData::Boolean { value: b.value },
11667            ast::Expr::NoneLiteral(_) => ConstantData::None,
11668            ast::Expr::EllipsisLiteral(_) => ConstantData::Ellipsis,
11669            ast::Expr::Name(ast::ExprName { id, ctx, .. })
11670                if matches!(ctx, ast::ExprContext::Load) && id.as_str() == "__debug__" =>
11671            {
11672                ConstantData::Boolean {
11673                    value: self.opts.optimize == 0,
11674                }
11675            }
11676            ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
11677                let mut elements = Vec::with_capacity(elts.len());
11678                for elt in elts {
11679                    let Some(constant) = self.try_fold_constant_expr(elt)? else {
11680                        return Ok(None);
11681                    };
11682                    elements.push(constant);
11683                }
11684                ConstantData::Tuple { elements }
11685            }
11686            ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
11687                let Some(container) = self.try_fold_constant_expr(value)? else {
11688                    return Ok(None);
11689                };
11690                let Some(index) = self.try_fold_constant_expr(slice)? else {
11691                    return Ok(None);
11692                };
11693                let ConstantData::Integer { value: index } = index else {
11694                    return Ok(None);
11695                };
11696                let Some(index): Option<i64> = index.try_into().ok() else {
11697                    return Ok(None);
11698                };
11699
11700                match container {
11701                    ConstantData::Str { value } => {
11702                        let string = value.to_string();
11703                        if string.contains(char::REPLACEMENT_CHARACTER) {
11704                            return Ok(None);
11705                        }
11706                        let chars: Vec<_> = string.chars().collect();
11707                        let Some(len) = i64::try_from(chars.len()).ok() else {
11708                            return Ok(None);
11709                        };
11710                        let idx: i64 = if index < 0 { len + index } else { index };
11711                        let Some(idx) = usize::try_from(idx).ok() else {
11712                            return Ok(None);
11713                        };
11714                        let Some(ch) = chars.get(idx) else {
11715                            return Ok(None);
11716                        };
11717                        ConstantData::Str {
11718                            value: ch.to_string().into(),
11719                        }
11720                    }
11721                    ConstantData::Bytes { value } => {
11722                        let Some(len) = i64::try_from(value.len()).ok() else {
11723                            return Ok(None);
11724                        };
11725                        let idx: i64 = if index < 0 { len + index } else { index };
11726                        let Some(idx) = usize::try_from(idx).ok() else {
11727                            return Ok(None);
11728                        };
11729                        let Some(byte) = value.get(idx) else {
11730                            return Ok(None);
11731                        };
11732                        ConstantData::Integer {
11733                            value: BigInt::from(*byte),
11734                        }
11735                    }
11736                    ConstantData::Tuple { elements } => {
11737                        let Some(len) = i64::try_from(elements.len()).ok() else {
11738                            return Ok(None);
11739                        };
11740                        let idx: i64 = if index < 0 { len + index } else { index };
11741                        let Some(idx) = usize::try_from(idx).ok() else {
11742                            return Ok(None);
11743                        };
11744                        let Some(element) = elements.get(idx) else {
11745                            return Ok(None);
11746                        };
11747                        element.clone()
11748                    }
11749                    _ => return Ok(None),
11750                }
11751            }
11752            ast::Expr::BinOp(ast::ExprBinOp {
11753                left, op, right, ..
11754            }) => {
11755                let Some(left) = self.try_fold_constant_expr(left)? else {
11756                    return Ok(None);
11757                };
11758                let Some(right) = self.try_fold_constant_expr(right)? else {
11759                    return Ok(None);
11760                };
11761                let Some(constant) = Self::try_fold_constant_binop(*op, &left, &right) else {
11762                    return Ok(None);
11763                };
11764                constant
11765            }
11766            ast::Expr::UnaryOp(ast::ExprUnaryOp { op, operand, .. }) => {
11767                let Some(constant) = self.try_fold_constant_expr(operand)? else {
11768                    return Ok(None);
11769                };
11770                match (op, constant) {
11771                    (ast::UnaryOp::UAdd, value) => value,
11772                    (ast::UnaryOp::USub, ConstantData::Integer { value }) => {
11773                        ConstantData::Integer { value: -value }
11774                    }
11775                    (ast::UnaryOp::USub, ConstantData::Float { value }) => {
11776                        ConstantData::Float { value: -value }
11777                    }
11778                    (ast::UnaryOp::USub, ConstantData::Complex { value }) => {
11779                        ConstantData::Complex { value: -value }
11780                    }
11781                    (ast::UnaryOp::Invert, ConstantData::Integer { value }) => {
11782                        ConstantData::Integer { value: !value }
11783                    }
11784                    (ast::UnaryOp::Not, ConstantData::Tuple { .. }) => return Ok(None),
11785                    (ast::UnaryOp::Not, value) => ConstantData::Boolean {
11786                        value: !Self::constant_truthiness(&value),
11787                    },
11788                    _ => return Ok(None),
11789                }
11790            }
11791            ast::Expr::BoolOp(ast::ExprBoolOp { op, values, .. }) => {
11792                let mut constants = Vec::with_capacity(values.len());
11793                for value in values {
11794                    let Some(constant) = self.try_fold_constant_expr(value)? else {
11795                        return Ok(None);
11796                    };
11797                    constants.push(constant);
11798                }
11799                let mut iter = constants.into_iter();
11800                let Some(first) = iter.next() else {
11801                    return Ok(None);
11802                };
11803                let mut selected = first;
11804                match op {
11805                    ast::BoolOp::Or => {
11806                        if !Self::constant_truthiness(&selected) {
11807                            for constant in iter {
11808                                let is_truthy = Self::constant_truthiness(&constant);
11809                                selected = constant;
11810                                if is_truthy {
11811                                    break;
11812                                }
11813                            }
11814                        }
11815                    }
11816                    ast::BoolOp::And => {
11817                        if Self::constant_truthiness(&selected) {
11818                            for constant in iter {
11819                                let is_truthy = Self::constant_truthiness(&constant);
11820                                selected = constant;
11821                                if !is_truthy {
11822                                    break;
11823                                }
11824                            }
11825                        }
11826                    }
11827                }
11828                selected
11829            }
11830            _ => return Ok(None),
11831        }))
11832    }
11833
11834    fn try_compile_ast_constant(
11835        &mut self,
11836        expr: &ast::Expr,
11837    ) -> CompileResult<Option<ConstantData>> {
11838        if let Some(constant) = self.ast_constant_value(expr) {
11839            return Ok(Some(constant));
11840        }
11841        Ok(Some(match expr {
11842            ast::Expr::NumberLiteral(num) => match &num.value {
11843                ast::Number::Int(int) => ConstantData::Integer {
11844                    value: ruff_int_to_bigint(int).map_err(|e| self.error(e))?,
11845                },
11846                ast::Number::Float(value) => ConstantData::Float { value: *value },
11847                ast::Number::Complex { real, imag } => ConstantData::Complex {
11848                    value: Complex::new(*real, *imag),
11849                },
11850            },
11851            ast::Expr::StringLiteral(s) => ConstantData::Str {
11852                value: string_literal_value(&self.source_file, &s.value),
11853            },
11854            ast::Expr::BytesLiteral(b) => ConstantData::Bytes {
11855                value: b.value.bytes().collect(),
11856            },
11857            ast::Expr::BooleanLiteral(b) => ConstantData::Boolean { value: b.value },
11858            ast::Expr::NoneLiteral(_) => ConstantData::None,
11859            ast::Expr::EllipsisLiteral(_) => ConstantData::Ellipsis,
11860            _ => return Ok(None),
11861        }))
11862    }
11863
11864    fn try_compile_match_mapping_key_direct_constant(
11865        &mut self,
11866        expr: &ast::Expr,
11867    ) -> CompileResult<Option<ConstantData>> {
11868        if let Some(constant) = self.ast_constant_value(expr) {
11869            return Ok(match constant {
11870                ConstantData::Integer { .. }
11871                | ConstantData::Float { .. }
11872                | ConstantData::Bytes { .. }
11873                | ConstantData::Complex { .. }
11874                | ConstantData::Str { .. }
11875                | ConstantData::Boolean { .. }
11876                | ConstantData::None => Some(constant),
11877                _ => None,
11878            });
11879        }
11880        if matches!(
11881            expr,
11882            ast::Expr::BooleanLiteral(_) | ast::Expr::NoneLiteral(_)
11883        ) {
11884            return self.try_compile_ast_constant(expr);
11885        }
11886        self.try_compile_match_pattern_direct_literal(expr)
11887    }
11888
11889    fn is_unexpected_match_literal_constant(expr: &ast::Expr) -> bool {
11890        if let Some(constant) = expr
11891            .as_constant_expr()
11892            .map(|expr| ast_constant_value_to_constant_data(expr.value.clone()))
11893        {
11894            return matches!(
11895                constant,
11896                ConstantData::Boolean { .. } | ConstantData::None | ConstantData::Ellipsis
11897            );
11898        }
11899        matches!(
11900            expr,
11901            ast::Expr::BooleanLiteral(_)
11902                | ast::Expr::NoneLiteral(_)
11903                | ast::Expr::EllipsisLiteral(_)
11904        )
11905    }
11906
11907    fn try_compile_match_pattern_direct_literal(
11908        &mut self,
11909        expr: &ast::Expr,
11910    ) -> CompileResult<Option<ConstantData>> {
11911        if let Some(constant) = self.ast_constant_value(expr) {
11912            return Ok(match constant {
11913                ConstantData::Integer { .. }
11914                | ConstantData::Float { .. }
11915                | ConstantData::Bytes { .. }
11916                | ConstantData::Complex { .. }
11917                | ConstantData::Str { .. } => Some(constant),
11918                _ => None,
11919            });
11920        }
11921        match expr {
11922            ast::Expr::NumberLiteral(_)
11923            | ast::Expr::StringLiteral(_)
11924            | ast::Expr::BytesLiteral(_) => self.try_compile_ast_constant(expr),
11925            _ => Ok(None),
11926        }
11927    }
11928
11929    fn try_negate_match_pattern_constant(constant: ConstantData) -> Option<ConstantData> {
11930        match constant {
11931            ConstantData::Integer { value } => Some(ConstantData::Integer { value: -value }),
11932            ConstantData::Float { value } => Some(ConstantData::Float { value: -value }),
11933            ConstantData::Complex { value } => Some(ConstantData::Complex { value: -value }),
11934            _ => None,
11935        }
11936    }
11937
11938    fn try_fold_match_pattern_binop(
11939        op: ast::Operator,
11940        left: &ConstantData,
11941        right: &ConstantData,
11942    ) -> Option<ConstantData> {
11943        let left = match left {
11944            ConstantData::Integer { value } => value.to_f64()?,
11945            ConstantData::Float { value } => *value,
11946            _ => return None,
11947        };
11948        let ConstantData::Complex { value: right } = right else {
11949            return None;
11950        };
11951        let value = match op {
11952            ast::Operator::Add => Complex::new(left + right.re, right.im),
11953            ast::Operator::Sub => Complex::new(left - right.re, -right.im),
11954            _ => return None,
11955        };
11956        Some(ConstantData::Complex { value })
11957    }
11958
11959    fn try_fold_match_pattern_const_expr(
11960        &mut self,
11961        expr: &ast::Expr,
11962    ) -> CompileResult<Option<ConstantData>> {
11963        // CPython 3.14 ast_preprocess.c::fold_const_match_patterns()
11964        // folds only the constant forms needed by match patterns before
11965        // codegen_pattern_value()/codegen_pattern_mapping_key() visit them.
11966        Ok(match expr {
11967            ast::Expr::UnaryOp(ast::ExprUnaryOp {
11968                op: ast::UnaryOp::USub,
11969                operand,
11970                ..
11971            }) => {
11972                let Some(constant) = self.try_compile_match_pattern_number_constant(operand)?
11973                else {
11974                    return Ok(None);
11975                };
11976                Self::try_negate_match_pattern_constant(constant)
11977            }
11978            ast::Expr::BinOp(ast::ExprBinOp {
11979                left, op, right, ..
11980            }) if matches!(op, ast::Operator::Add | ast::Operator::Sub) => {
11981                let Some(left) = self.try_compile_match_pattern_signed_real_constant(left)? else {
11982                    return Ok(None);
11983                };
11984                let Some(right) = self.try_compile_match_pattern_imaginary_constant(right)? else {
11985                    return Ok(None);
11986                };
11987                Self::try_fold_match_pattern_binop(*op, &left, &right)
11988            }
11989            _ => None,
11990        })
11991    }
11992
11993    fn try_compile_match_pattern_signed_real_constant(
11994        &mut self,
11995        expr: &ast::Expr,
11996    ) -> CompileResult<Option<ConstantData>> {
11997        if let Some(constant) = self.try_compile_match_pattern_real_constant(expr)? {
11998            return Ok(Some(constant));
11999        }
12000        let ast::Expr::UnaryOp(ast::ExprUnaryOp {
12001            op: ast::UnaryOp::USub,
12002            operand,
12003            ..
12004        }) = expr
12005        else {
12006            return Ok(None);
12007        };
12008        let Some(constant) = self.try_compile_match_pattern_real_constant(operand)? else {
12009            return Ok(None);
12010        };
12011        Ok(Self::try_negate_match_pattern_constant(constant))
12012    }
12013
12014    fn try_compile_match_pattern_real_constant(
12015        &mut self,
12016        expr: &ast::Expr,
12017    ) -> CompileResult<Option<ConstantData>> {
12018        let Some(constant) = self.try_compile_match_pattern_number_constant(expr)? else {
12019            return Ok(None);
12020        };
12021        Ok(match constant {
12022            ConstantData::Integer { .. } | ConstantData::Float { .. } => Some(constant),
12023            _ => None,
12024        })
12025    }
12026
12027    fn try_compile_match_pattern_imaginary_constant(
12028        &mut self,
12029        expr: &ast::Expr,
12030    ) -> CompileResult<Option<ConstantData>> {
12031        let Some(constant) = self.try_compile_match_pattern_number_constant(expr)? else {
12032            return Ok(None);
12033        };
12034        Ok(match constant {
12035            ConstantData::Complex { .. } => Some(constant),
12036            _ => None,
12037        })
12038    }
12039
12040    fn try_compile_match_pattern_number_constant(
12041        &mut self,
12042        expr: &ast::Expr,
12043    ) -> CompileResult<Option<ConstantData>> {
12044        if let Some(constant) = self.ast_constant_value(expr) {
12045            return Ok(match constant {
12046                ConstantData::Integer { .. }
12047                | ConstantData::Float { .. }
12048                | ConstantData::Complex { .. } => Some(constant),
12049                _ => None,
12050            });
12051        }
12052        match expr {
12053            ast::Expr::NumberLiteral(_) => self.try_compile_ast_constant(expr),
12054            _ => Ok(None),
12055        }
12056    }
12057
12058    fn compile_match_pattern_expr(&mut self, expr: &ast::Expr) -> CompileResult<()> {
12059        if let Some(constant) = self.try_fold_match_pattern_const_expr(expr)? {
12060            self.set_source_range(expr.range());
12061            self.emit_load_const(constant);
12062        } else {
12063            self.compile_expression(expr)?;
12064        }
12065        Ok(())
12066    }
12067
12068    fn emit_load_const(&mut self, constant: ConstantData) {
12069        let idx = self.arg_constant(constant);
12070        self.emit_arg(idx, |consti| Instruction::LoadConst { consti })
12071    }
12072
12073    fn try_fold_constant_slice(
12074        &mut self,
12075        lower: Option<&ast::Expr>,
12076        upper: Option<&ast::Expr>,
12077        step: Option<&ast::Expr>,
12078    ) -> CompileResult<Option<ConstantData>> {
12079        if [lower, upper, step]
12080            .into_iter()
12081            .flatten()
12082            .any(|expr| !self.is_constant_expr(expr))
12083        {
12084            return Ok(None);
12085        }
12086
12087        let start = match lower {
12088            Some(expr) => {
12089                let Some(constant) = self.try_fold_constant_expr(expr)? else {
12090                    return Ok(None);
12091                };
12092                constant
12093            }
12094            None => ConstantData::None,
12095        };
12096        let stop = match upper {
12097            Some(expr) => {
12098                let Some(constant) = self.try_fold_constant_expr(expr)? else {
12099                    return Ok(None);
12100                };
12101                constant
12102            }
12103            None => ConstantData::None,
12104        };
12105        let step = match step {
12106            Some(expr) => {
12107                let Some(constant) = self.try_fold_constant_expr(expr)? else {
12108                    return Ok(None);
12109                };
12110                constant
12111            }
12112            None => ConstantData::None,
12113        };
12114
12115        Ok(Some(ConstantData::Slice {
12116            elements: Box::new([start, stop, step]),
12117        }))
12118    }
12119
12120    fn emit_return_const(&mut self, constant: ConstantData) {
12121        self.emit_load_const(constant);
12122        emit!(self, Instruction::ReturnValue)
12123    }
12124
12125    fn emit_return_const_no_location(&mut self, constant: ConstantData) {
12126        self.emit_load_const(constant);
12127        self.set_no_location();
12128        emit!(self, Instruction::ReturnValue);
12129        self.set_no_location();
12130    }
12131
12132    fn emit_end_async_for(&mut self, send_target: BlockIdx) {
12133        self._emit(Instruction::EndAsyncFor, OpArg::NULL, send_target);
12134    }
12135
12136    /// Emit LOAD_ATTR for attribute access (method=false).
12137    /// Encodes: (name_idx << 1) | 0
12138    fn emit_load_attr(&mut self, name_idx: u32) {
12139        let encoded = LoadAttr::new(name_idx, false);
12140        self.emit_arg(encoded, |namei| Instruction::LoadAttr { namei })
12141    }
12142
12143    /// Emit LOAD_ATTR with method flag set (for method calls).
12144    /// Encodes: (name_idx << 1) | 1
12145    fn emit_load_attr_method(&mut self, name_idx: u32) {
12146        let encoded = LoadAttr::new(name_idx, true);
12147        self.emit_arg(encoded, |namei| Instruction::LoadAttr { namei })
12148    }
12149
12150    /// Emit LOAD_GLOBAL.
12151    /// Encodes: (name_idx << 1) | push_null_bit
12152    fn emit_load_global(&mut self, name_idx: u32, push_null: bool) {
12153        let encoded = (name_idx << 1) | u32::from(push_null);
12154        self.emit_arg(encoded, |namei| Instruction::LoadGlobal { namei });
12155    }
12156
12157    /// Emit LOAD_SUPER_ATTR for 2-arg super().attr access.
12158    /// Encodes: (name_idx << 2) | 0b10 (method=0, class=1)
12159    fn emit_load_super_attr(&mut self, name_idx: u32) {
12160        let encoded = LoadSuperAttr::new(name_idx, false, true);
12161        self.emit_arg(encoded, |namei| Instruction::LoadSuperAttr { namei })
12162    }
12163
12164    /// Emit LOAD_SUPER_ATTR for 2-arg super().method() call.
12165    /// Encodes: (name_idx << 2) | 0b11 (method=1, class=1)
12166    fn emit_load_super_method(&mut self, name_idx: u32) {
12167        let encoded = LoadSuperAttr::new(name_idx, true, true);
12168        self.emit_arg(encoded, |namei| Instruction::LoadSuperAttr { namei })
12169    }
12170
12171    /// Emit LOAD_SUPER_ATTR for 0-arg super().attr access.
12172    /// Encodes: (name_idx << 2) | 0b00 (method=0, class=0)
12173    fn emit_load_zero_super_attr(&mut self, name_idx: u32) {
12174        let encoded = LoadSuperAttr::new(name_idx, false, false);
12175        self.emit_arg(encoded, |namei| Instruction::LoadSuperAttr { namei })
12176    }
12177
12178    /// Emit LOAD_SUPER_ATTR for 0-arg super().method() call.
12179    /// Encodes: (name_idx << 2) | 0b01 (method=1, class=0)
12180    fn emit_load_zero_super_method(&mut self, name_idx: u32) {
12181        let encoded = LoadSuperAttr::new(name_idx, true, false);
12182        self.emit_arg(encoded, |namei| Instruction::LoadSuperAttr { namei })
12183    }
12184
12185    fn emit_return_value(&mut self) {
12186        emit!(self, Instruction::ReturnValue)
12187    }
12188
12189    fn allows_top_level_await_in_current_context(&self) -> bool {
12190        self.opts.allow_top_level_await
12191            && self.ctx.func == FunctionContext::NoFunction
12192            && !self.ctx.in_class
12193    }
12194
12195    fn current_code_info(&mut self) -> &mut ir::CodeInfo {
12196        self.code_stack.last_mut().expect("no code on stack")
12197    }
12198
12199    /// Enter a conditional block (if/for/while/match/try/with)
12200    /// PEP 649: Track conditional annotation context
12201    fn enter_conditional_block(&mut self) {
12202        self.current_code_info().in_conditional_block += 1;
12203    }
12204
12205    /// Leave a conditional block
12206    fn leave_conditional_block(&mut self) {
12207        let code_info = self.current_code_info();
12208        debug_assert!(code_info.in_conditional_block > 0);
12209        code_info.in_conditional_block -= 1;
12210    }
12211
12212    /// Compile break or continue statement with proper fblock cleanup.
12213    /// compiler_break, compiler_continue
12214    /// This handles unwinding through With blocks and exception handlers.
12215    fn compile_break_continue(
12216        &mut self,
12217        range: ruff_text_size::TextRange,
12218        is_break: bool,
12219    ) -> CompileResult<()> {
12220        if self.do_not_emit_bytecode > 0 {
12221            // Still validate that we're inside a loop even in dead code
12222            let code = self.current_code_info();
12223            let mut found_loop = false;
12224            for i in (0..code.fblock.len()).rev() {
12225                match code.fblock[i].fb_type {
12226                    FBlockType::WhileLoop | FBlockType::ForLoop => {
12227                        found_loop = true;
12228                        break;
12229                    }
12230                    FBlockType::ExceptionGroupHandler => {
12231                        return Err(self.error_ranged(
12232                            CodegenErrorType::BreakContinueReturnInExceptStar,
12233                            range,
12234                        ));
12235                    }
12236                    _ => {}
12237                }
12238            }
12239            if !found_loop {
12240                if is_break {
12241                    return Err(self.error_ranged(CodegenErrorType::InvalidBreak, range));
12242                }
12243                return Err(self.error_ranged(CodegenErrorType::InvalidContinue, range));
12244            }
12245            return Ok(());
12246        }
12247
12248        let prev_source_range = self.current_source_range;
12249        self.set_source_range(range);
12250        emit!(self, Instruction::Nop);
12251
12252        let (mut unwind_loc, loop_fblock) = self.unwind_fblock_stack_with_loop(false, true)?;
12253        let Some(loop_fblock) = loop_fblock else {
12254            self.set_source_range(prev_source_range);
12255            if is_break {
12256                return Err(self.error_ranged(CodegenErrorType::InvalidBreak, range));
12257            }
12258            return Err(self.error_ranged(CodegenErrorType::InvalidContinue, range));
12259        };
12260
12261        // CPython `codegen_break()` unwinds the loop fblock itself after
12262        // `codegen_unwind_fblock_stack()` returns it. `codegen_continue()`
12263        // jumps directly to the loop body label.
12264        if is_break {
12265            self.unwind_fblock(&loop_fblock, false, &mut unwind_loc)?;
12266        }
12267
12268        // Jump to target
12269        let target_label = if is_break {
12270            debug_assert!(loop_fblock.fb_exit.is_jump_target_label());
12271            loop_fblock.fb_exit
12272        } else {
12273            debug_assert!(loop_fblock.fb_block.is_jump_target_label());
12274            loop_fblock.fb_block
12275        };
12276        let jump_is_artificial = if let Some(loc) = unwind_loc {
12277            self.set_source_range(loc);
12278            false
12279        } else {
12280            true
12281        };
12282        self.emit_jump_label(
12283            PseudoInstruction::Jump {
12284                delta: OpArgMarker::marker(),
12285            },
12286            target_label,
12287        );
12288        if jump_is_artificial {
12289            self.set_no_location();
12290        }
12291        self.set_source_range(prev_source_range);
12292
12293        Ok(())
12294    }
12295
12296    /// CPython `_PyCfgBuilder_Addop()` calls
12297    /// `cfg_builder_maybe_start_new_block()` before appending each instruction.
12298    /// That keeps any `IS_TERMINATOR_OPCODE` as the final instruction in its
12299    /// basicblock before `flowgraph.c::check_cfg()`.
12300    fn maybe_start_cpython_cfg_addop_block(&mut self) {
12301        let code = self.current_code_info();
12302        let cur = code.current_block;
12303        if !code.blocks[cur.idx()]
12304            .used_instructions()
12305            .last()
12306            .is_some_and(|instr| instr.instr.is_terminator())
12307        {
12308            return;
12309        }
12310
12311        debug_assert_eq!(code.blocks[cur.idx()].next, BlockIdx::NULL);
12312        let block = self.cpython_cfg_builder_new_block();
12313        self.cpython_cfg_builder_use_next_block(block);
12314    }
12315
12316    /// CPython `codegen_funcbody()` emits `NEW_JUMP_TARGET_LABEL(start)` and
12317    /// `USE_LABEL(start)` after scope entry. That label is part of the
12318    /// instruction-sequence label map, but it does not become a CFG
12319    /// `basicblock.b_label` unless some emitted instruction targets it.
12320    fn use_cpython_function_start_label(&mut self) -> ir::InstructionSequenceLabel {
12321        let (label, result) = {
12322            let code = self.current_code_info();
12323            let label = code.new_instr_sequence_label();
12324            let result = code.use_raw_instr_sequence_label(label);
12325            (label, result)
12326        };
12327        unwrap_internal(self, result);
12328        label
12329    }
12330
12331    fn instr_sequence_label_for_block(&mut self, block: BlockIdx) -> ir::InstructionSequenceLabel {
12332        let result = self
12333            .current_code_info()
12334            .instr_sequence_label_for_block(block);
12335        unwrap_internal(self, result)
12336    }
12337
12338    /// Switch to a block as CPython instruction-sequence labels would resolve.
12339    ///
12340    /// Consecutive `USE_LABEL()` calls can map multiple labels to the same
12341    /// instruction offset before `_PyCfg_FromInstructionSequence()` builds a
12342    /// CFG. Reuse an empty current block even if it already carries a label so
12343    /// codegen CFG path preserves that aliasing.
12344    fn use_cpython_label_block(&mut self, block: BlockIdx) {
12345        let result = self.current_code_info().use_instr_sequence_label(block);
12346        unwrap_internal(self, result);
12347        let code = self.current_code_info();
12348        let block = code.resolve_instr_sequence_label(block);
12349        let cur = code.current_block;
12350        let can_reuse_current = cur != block
12351            && code.blocks[cur.idx()].is_empty()
12352            && code.blocks[cur.idx()].next == BlockIdx::NULL
12353            && code.blocks[block.idx()].is_empty()
12354            && code.blocks[block.idx()].next == BlockIdx::NULL;
12355
12356        if !can_reuse_current {
12357            let result = code.mark_cpython_cfg_label(block);
12358            unwrap_internal(self, result);
12359            self.switch_to_block(block);
12360            return;
12361        }
12362
12363        {
12364            let target = &code.blocks[block.idx()];
12365            debug_assert!(!target.except_handler);
12366            debug_assert!(!target.preserve_lasti);
12367            debug_assert_eq!(target.start_depth, ir::START_DEPTH_UNSET);
12368            debug_assert!(!target.cold);
12369        }
12370        let result = code.mark_cpython_cfg_label(cur);
12371        unwrap_internal(self, result);
12372
12373        let result = self
12374            .current_code_info()
12375            .use_instr_sequence_label_at_block(block, cur);
12376        unwrap_internal(self, result);
12377    }
12378
12379    fn new_block(&mut self) -> BlockIdx {
12380        let result = self
12381            .current_code_info()
12382            .blocks
12383            .try_reserve(1)
12384            .map_err(|_| InternalError::MalformedControlFlowGraph);
12385        unwrap_internal(self, result);
12386        let code = self.current_code_info();
12387        let idx = BlockIdx::new(code.blocks.len().to_u32());
12388        code.blocks.push(Block::default());
12389        let result = code.push_unmapped_instr_sequence_label();
12390        unwrap_internal(self, result);
12391        idx
12392    }
12393
12394    fn new_unlabeled_block(&mut self) -> BlockIdx {
12395        let result = self
12396            .current_code_info()
12397            .blocks
12398            .try_reserve(1)
12399            .map_err(|_| InternalError::MalformedControlFlowGraph);
12400        unwrap_internal(self, result);
12401        let code = self.current_code_info();
12402        let idx = BlockIdx::new(code.blocks.len().to_u32());
12403        code.blocks.push(Block::default());
12404        let result = code.push_unlabeled_instr_sequence_block();
12405        unwrap_internal(self, result);
12406        idx
12407    }
12408
12409    /// flowgraph.c cfg_builder_new_block
12410    fn cpython_cfg_builder_new_block(&mut self) -> BlockIdx {
12411        self.new_unlabeled_block()
12412    }
12413
12414    /// flowgraph.c cfg_builder_use_next_block
12415    fn cpython_cfg_builder_use_next_block(&mut self, block: BlockIdx) {
12416        let code = self.current_code_info();
12417        let cur = code.current_block;
12418        code.blocks[cur.idx()].next = block;
12419        code.current_block = block;
12420    }
12421
12422    fn switch_to_block(&mut self, block: BlockIdx) {
12423        let result = self.current_code_info().use_instr_sequence_label(block);
12424        unwrap_internal(self, result);
12425        let code = self.current_code_info();
12426        let block = code.resolve_instr_sequence_label(block);
12427        let prev = code.current_block;
12428        assert_ne!(prev, block, "recursive switching {prev:?} -> {block:?}");
12429        assert_eq!(
12430            code.blocks[block].next,
12431            BlockIdx::NULL,
12432            "switching {prev:?} -> {block:?} to completed block"
12433        );
12434        let prev_block = &mut code.blocks[prev.idx()];
12435        assert_eq!(
12436            u32::from(prev_block.next),
12437            u32::MAX,
12438            "switching {prev:?} -> {block:?} from block that's already got a next"
12439        );
12440        prev_block.next = block;
12441        code.current_block = block;
12442    }
12443
12444    const fn set_source_range(&mut self, range: TextRange) {
12445        self.current_source_range = range;
12446    }
12447
12448    fn update_start_location_to_match_attr(
12449        &self,
12450        loc_range: TextRange,
12451        attr_range: TextRange,
12452        attr: &str,
12453    ) -> TextRange {
12454        let source = self.source_file.to_source_code();
12455        if source.line_index(loc_range.start()) == source.line_index(attr_range.end()) {
12456            return loc_range;
12457        }
12458        let Ok(attr_len) = u32::try_from(attr.chars().count()) else {
12459            return TextRange::new(loc_range.start(), loc_range.end());
12460        };
12461        let attr_len = TextSize::new(attr_len);
12462        if attr_len > attr_range.len() {
12463            return TextRange::new(loc_range.start(), loc_range.end());
12464        }
12465        TextRange::new(attr_range.end() - attr_len, loc_range.end())
12466    }
12467
12468    fn source_line_start_range(&self, lineno: u32) -> TextRange {
12469        let source = self.source_file.to_source_code();
12470        let line = OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN);
12471        let start = source.line_start(line);
12472        TextRange::new(start, start)
12473    }
12474
12475    fn module_start_location(&self, body: &[ast::Stmt]) -> TextRange {
12476        body.first()
12477            .map_or_else(|| self.source_line_start_range(1), Ranged::range)
12478    }
12479
12480    fn get_source_line_number(&mut self) -> OneIndexed {
12481        self.source_file
12482            .to_source_code()
12483            .line_index(self.current_source_range.start())
12484    }
12485
12486    fn mark_generator(&mut self) {
12487        let is_async = self.ctx.func == FunctionContext::AsyncFunction;
12488        let flags = &mut self.current_code_info().flags;
12489        if is_async {
12490            flags.remove(bytecode::CodeFlags::COROUTINE);
12491            flags.insert(bytecode::CodeFlags::ASYNC_GENERATOR);
12492        } else {
12493            flags.insert(bytecode::CodeFlags::GENERATOR);
12494        }
12495    }
12496
12497    /// Whether the expression contains an await expression and
12498    /// thus requires the function to be async.
12499    ///
12500    /// Both:
12501    /// ```py
12502    /// async with: ...
12503    /// async for: ...
12504    /// ```
12505    /// are statements, so we won't check for them here
12506    fn contains_await(expression: &ast::Expr) -> bool {
12507        use ast::visitor::Visitor;
12508
12509        #[derive(Default)]
12510        struct AwaitVisitor {
12511            found: bool,
12512        }
12513
12514        impl ast::visitor::Visitor<'_> for AwaitVisitor {
12515            fn visit_expr(&mut self, expr: &ast::Expr) {
12516                if self.found {
12517                    return;
12518                }
12519
12520                match expr {
12521                    ast::Expr::Await(_) => self.found = true,
12522                    ast::Expr::ListComp(ast::ExprListComp { generators, .. })
12523                    | ast::Expr::SetComp(ast::ExprSetComp { generators, .. })
12524                    | ast::Expr::DictComp(ast::ExprDictComp { generators, .. })
12525                        if generators.iter().any(|generator| generator.is_async) =>
12526                    {
12527                        self.found = true
12528                    }
12529                    _ => ast::visitor::walk_expr(self, expr),
12530                }
12531            }
12532        }
12533
12534        let mut visitor = AwaitVisitor::default();
12535        visitor.visit_expr(expression);
12536        visitor.found
12537    }
12538
12539    /// Check if any of the generators (except the first one's iter) contains an await expression.
12540    /// The first generator's iter is evaluated outside the comprehension scope.
12541    fn generators_contain_await(generators: &[ast::Comprehension]) -> bool {
12542        for (i, generator) in generators.iter().enumerate() {
12543            // First generator's iter is evaluated outside the comprehension
12544            if i > 0 && Self::contains_await(&generator.iter) {
12545                return true;
12546            }
12547            // Check ifs in all generators
12548            for if_expr in &generator.ifs {
12549                if Self::contains_await(if_expr) {
12550                    return true;
12551                }
12552            }
12553        }
12554        false
12555    }
12556
12557    fn compile_expr_fstring(&mut self, fstring: &ast::ExprFString) -> CompileResult<()> {
12558        let fstring_range = fstring.range;
12559        let fstring = fstring.value.as_slice();
12560        if self.count_fstring_parts(fstring) > STACK_USE_GUIDELINE {
12561            return self.compile_fstring_parts_joined(fstring, fstring_range);
12562        }
12563
12564        let mut element_count = 0;
12565        let mut pending_literal = None;
12566        let mut pending_literal_range = None;
12567        let mut pending_literal_no_location = false;
12568        for part in fstring {
12569            self.compile_fstring_part_into(
12570                part,
12571                &mut pending_literal,
12572                &mut pending_literal_range,
12573                &mut pending_literal_no_location,
12574                &mut element_count,
12575                None,
12576            )?;
12577        }
12578        self.finish_fstring(
12579            pending_literal,
12580            pending_literal_range,
12581            pending_literal_no_location,
12582            element_count,
12583            Some(fstring_range),
12584        );
12585        Ok(())
12586    }
12587
12588    fn compile_runtime_joined_str(
12589        &mut self,
12590        fstring: &ast::ExprFString,
12591        values: &[ast::Expr],
12592    ) -> CompileResult<()> {
12593        let range = fstring.range;
12594        let value_count: u32 = values
12595            .len()
12596            .try_into()
12597            .expect("JoinedStr value count overflowed");
12598        if value_count > STACK_USE_GUIDELINE {
12599            self.set_source_range(range);
12600            self.emit_load_const(ConstantData::Str {
12601                value: Wtf8Buf::new(),
12602            });
12603            let join_idx = self.get_global_name_index("join");
12604            self.emit_load_attr_method(join_idx);
12605            emit!(self, Instruction::BuildList { count: 0 });
12606            for value in values {
12607                self.compile_expression(value)?;
12608                self.set_source_range(range);
12609                emit!(self, Instruction::ListAppend { i: 1 });
12610            }
12611            self.set_source_range(range);
12612            emit!(self, Instruction::Call { argc: 1 });
12613        } else {
12614            for value in values {
12615                self.compile_expression(value)?;
12616            }
12617            if value_count > 1 {
12618                self.set_source_range(range);
12619                emit!(self, Instruction::BuildString { count: value_count });
12620            } else if value_count == 0 {
12621                self.set_source_range(range);
12622                self.emit_load_const(ConstantData::Str {
12623                    value: Wtf8Buf::new(),
12624                });
12625            }
12626        }
12627        Ok(())
12628    }
12629
12630    fn compile_fstring_parts_joined(
12631        &mut self,
12632        fstring: &[ast::FStringPart],
12633        fstring_range: TextRange,
12634    ) -> CompileResult<()> {
12635        self.set_source_range(fstring_range);
12636        self.emit_load_const(ConstantData::Str {
12637            value: Wtf8Buf::new(),
12638        });
12639        let join_idx = self.get_global_name_index("join");
12640        self.emit_load_attr_method(join_idx);
12641        emit!(self, Instruction::BuildList { count: 0 });
12642
12643        let mut element_count = 0;
12644        let mut pending_literal = None;
12645        let mut pending_literal_range = None;
12646        let mut pending_literal_no_location = false;
12647        for part in fstring {
12648            self.compile_fstring_part_into(
12649                part,
12650                &mut pending_literal,
12651                &mut pending_literal_range,
12652                &mut pending_literal_no_location,
12653                &mut element_count,
12654                Some(fstring_range),
12655            )?;
12656        }
12657        self.finish_fstring_join(
12658            pending_literal,
12659            pending_literal_range,
12660            pending_literal_no_location,
12661            element_count,
12662            fstring_range,
12663        );
12664        Ok(())
12665    }
12666
12667    fn compile_fstring_part_into(
12668        &mut self,
12669        part: &ast::FStringPart,
12670        pending_literal: &mut Option<Wtf8Buf>,
12671        pending_literal_range: &mut Option<TextRange>,
12672        pending_literal_no_location: &mut bool,
12673        element_count: &mut u32,
12674        join_append_range: Option<TextRange>,
12675    ) -> CompileResult<()> {
12676        match part {
12677            ast::FStringPart::Literal(string) => {
12678                let value = string_literal_part_value(&self.source_file, string);
12679                if pending_literal.is_none() {
12680                    *pending_literal_range = Some(string.range);
12681                    *pending_literal_no_location = string.range == TextRange::default();
12682                    *pending_literal = Some(value);
12683                } else if let Some(pending) = pending_literal.as_mut() {
12684                    Self::extend_pending_literal_range(pending_literal_range, string.range);
12685                    *pending_literal_no_location &= string.range == TextRange::default();
12686                    pending.push_wtf8(value.as_ref());
12687                }
12688                Ok(())
12689            }
12690            ast::FStringPart::FString(fstring) => self.compile_fstring_elements_into(
12691                fstring.flags,
12692                &fstring.elements,
12693                pending_literal,
12694                (pending_literal_range, pending_literal_no_location),
12695                element_count,
12696                join_append_range,
12697            ),
12698        }
12699    }
12700
12701    fn finish_fstring(
12702        &mut self,
12703        mut pending_literal: Option<Wtf8Buf>,
12704        mut pending_literal_range: Option<TextRange>,
12705        mut pending_literal_no_location: bool,
12706        mut element_count: u32,
12707        fstring_range: Option<TextRange>,
12708    ) {
12709        self.emit_pending_fstring_literal(
12710            &mut pending_literal,
12711            &mut pending_literal_range,
12712            &mut pending_literal_no_location,
12713            &mut element_count,
12714            None,
12715        );
12716
12717        if element_count == 0 {
12718            if let Some(fstring_range) = fstring_range {
12719                self.set_source_range(fstring_range);
12720            }
12721            self.emit_load_const(ConstantData::Str {
12722                value: Wtf8Buf::new(),
12723            });
12724        } else if element_count > 1 {
12725            if let Some(fstring_range) = fstring_range {
12726                self.set_source_range(fstring_range);
12727            }
12728            emit!(
12729                self,
12730                Instruction::BuildString {
12731                    count: element_count
12732                }
12733            );
12734        }
12735    }
12736
12737    fn finish_fstring_join(
12738        &mut self,
12739        mut pending_literal: Option<Wtf8Buf>,
12740        mut pending_literal_range: Option<TextRange>,
12741        mut pending_literal_no_location: bool,
12742        mut element_count: u32,
12743        fstring_range: TextRange,
12744    ) {
12745        self.emit_pending_fstring_literal(
12746            &mut pending_literal,
12747            &mut pending_literal_range,
12748            &mut pending_literal_no_location,
12749            &mut element_count,
12750            Some(fstring_range),
12751        );
12752        self.set_source_range(fstring_range);
12753        emit!(self, Instruction::Call { argc: 1 });
12754    }
12755
12756    fn emit_pending_fstring_literal(
12757        &mut self,
12758        pending_literal: &mut Option<Wtf8Buf>,
12759        pending_literal_range: &mut Option<TextRange>,
12760        pending_literal_no_location: &mut bool,
12761        element_count: &mut u32,
12762        join_append_range: Option<TextRange>,
12763    ) {
12764        let Some(value) = pending_literal.take() else {
12765            return;
12766        };
12767        let range = pending_literal_range.take();
12768        let no_location = *pending_literal_no_location;
12769        *pending_literal_no_location = false;
12770
12771        // An empty literal fragment contributes nothing, so it is dropped. An
12772        // f-string left with no fragments at all still loads an empty string,
12773        // positioned at the whole f-string rather than at any one fragment.
12774        if value.is_empty() {
12775            return;
12776        }
12777
12778        if let Some(range) = range {
12779            self.set_source_range(range);
12780        }
12781        self.emit_load_const(ConstantData::Str { value });
12782        if no_location {
12783            self.set_no_location();
12784        }
12785        *element_count += 1;
12786        if let Some(join_append_range) = join_append_range {
12787            self.set_source_range(join_append_range);
12788            emit!(self, Instruction::ListAppend { i: 1 });
12789        }
12790    }
12791
12792    fn extend_pending_literal_range(pending: &mut Option<TextRange>, range: TextRange) {
12793        let Some(existing) = pending else {
12794            *pending = Some(range);
12795            return;
12796        };
12797        if *existing == TextRange::default() {
12798            *existing = range;
12799        } else if range != TextRange::default() {
12800            *existing = TextRange::new(existing.start(), range.end());
12801        }
12802    }
12803
12804    fn count_fstring_parts(&self, fstring: &[ast::FStringPart]) -> u32 {
12805        let mut element_count = 0;
12806        let mut pending_literal = None;
12807        for part in fstring {
12808            self.count_fstring_part_into(part, &mut pending_literal, &mut element_count);
12809        }
12810        Self::count_pending_fstring_literal(&mut pending_literal, &mut element_count);
12811        element_count
12812    }
12813
12814    fn count_fstring_part_into(
12815        &self,
12816        part: &ast::FStringPart,
12817        pending_literal: &mut Option<Wtf8Buf>,
12818        element_count: &mut u32,
12819    ) {
12820        match part {
12821            ast::FStringPart::Literal(string) => {
12822                let value = string_literal_part_value(&self.source_file, string);
12823                if let Some(pending) = pending_literal.as_mut() {
12824                    pending.push_wtf8(value.as_ref());
12825                } else {
12826                    *pending_literal = Some(value);
12827                }
12828            }
12829            ast::FStringPart::FString(fstring) => self.count_fstring_elements_into(
12830                fstring.flags,
12831                &fstring.elements,
12832                pending_literal,
12833                element_count,
12834            ),
12835        }
12836    }
12837
12838    fn count_pending_fstring_literal(
12839        pending_literal: &mut Option<Wtf8Buf>,
12840        element_count: &mut u32,
12841    ) {
12842        let Some(value) = pending_literal.take() else {
12843            return;
12844        };
12845
12846        if value.is_empty() {
12847            return;
12848        }
12849
12850        *element_count += 1;
12851    }
12852
12853    fn compile_fstring_elements(
12854        &mut self,
12855        flags: ast::FStringFlags,
12856        fstring_elements: &ast::InterpolatedStringElements,
12857        fstring_range: Option<TextRange>,
12858    ) -> CompileResult<()> {
12859        if self.count_fstring_elements(flags, fstring_elements) > STACK_USE_GUIDELINE {
12860            let fstring_range = fstring_range.unwrap_or(self.current_source_range);
12861            return self.compile_fstring_elements_joined(flags, fstring_elements, fstring_range);
12862        }
12863
12864        let mut element_count = 0;
12865        let mut pending_literal: Option<Wtf8Buf> = None;
12866        let mut pending_literal_range: Option<TextRange> = None;
12867        let mut pending_literal_no_location = false;
12868        self.compile_fstring_elements_into(
12869            flags,
12870            fstring_elements,
12871            &mut pending_literal,
12872            (&mut pending_literal_range, &mut pending_literal_no_location),
12873            &mut element_count,
12874            None,
12875        )?;
12876        self.finish_fstring(
12877            pending_literal,
12878            pending_literal_range,
12879            pending_literal_no_location,
12880            element_count,
12881            fstring_range,
12882        );
12883        Ok(())
12884    }
12885
12886    fn compile_fstring_elements_joined(
12887        &mut self,
12888        flags: ast::FStringFlags,
12889        fstring_elements: &ast::InterpolatedStringElements,
12890        fstring_range: TextRange,
12891    ) -> CompileResult<()> {
12892        self.set_source_range(fstring_range);
12893        self.emit_load_const(ConstantData::Str {
12894            value: Wtf8Buf::new(),
12895        });
12896        let join_idx = self.get_global_name_index("join");
12897        self.emit_load_attr_method(join_idx);
12898        emit!(self, Instruction::BuildList { count: 0 });
12899
12900        let mut element_count = 0;
12901        let mut pending_literal: Option<Wtf8Buf> = None;
12902        let mut pending_literal_range: Option<TextRange> = None;
12903        let mut pending_literal_no_location = false;
12904        self.compile_fstring_elements_into(
12905            flags,
12906            fstring_elements,
12907            &mut pending_literal,
12908            (&mut pending_literal_range, &mut pending_literal_no_location),
12909            &mut element_count,
12910            Some(fstring_range),
12911        )?;
12912        self.finish_fstring_join(
12913            pending_literal,
12914            pending_literal_range,
12915            pending_literal_no_location,
12916            element_count,
12917            fstring_range,
12918        );
12919        Ok(())
12920    }
12921
12922    fn cpython_format_spec_range(&self, range: TextRange) -> TextRange {
12923        let start = range.start().to_usize();
12924        if start == 0 {
12925            return range;
12926        }
12927        let source = self.source_file.source_text().as_bytes();
12928        if source.get(start - 1) == Some(&b':') {
12929            TextRange::new(range.start() - TextSize::new(1), range.end())
12930        } else {
12931            range
12932        }
12933    }
12934
12935    fn compile_fstring_elements_into(
12936        &mut self,
12937        flags: ast::FStringFlags,
12938        fstring_elements: &ast::InterpolatedStringElements,
12939        pending_literal: &mut Option<Wtf8Buf>,
12940        pending_literal_meta: (&mut Option<TextRange>, &mut bool),
12941        element_count: &mut u32,
12942        join_append_range: Option<TextRange>,
12943    ) -> CompileResult<()> {
12944        let (pending_literal_range, pending_literal_no_location) = pending_literal_meta;
12945        for element in fstring_elements {
12946            match element {
12947                ast::InterpolatedStringElement::Literal(string) => {
12948                    let value =
12949                        interpolated_string_literal_value(&self.source_file, string, flags.into());
12950                    if pending_literal.is_none() {
12951                        *pending_literal_range = Some(string.range);
12952                        *pending_literal_no_location = string.range == TextRange::default();
12953                        *pending_literal = Some(value);
12954                    } else if let Some(pending) = pending_literal.as_mut() {
12955                        Self::extend_pending_literal_range(pending_literal_range, string.range);
12956                        *pending_literal_no_location &= string.range == TextRange::default();
12957                        pending.push_wtf8(value.as_ref());
12958                    }
12959                }
12960                ast::InterpolatedStringElement::Interpolation(fstring_expr) => {
12961                    let mut conversion = match fstring_expr.conversion {
12962                        ast::ConversionFlag::None => ConvertValueOparg::None,
12963                        ast::ConversionFlag::Str => ConvertValueOparg::Str,
12964                        ast::ConversionFlag::Repr => ConvertValueOparg::Repr,
12965                        ast::ConversionFlag::Ascii => ConvertValueOparg::Ascii,
12966                    };
12967
12968                    if let Some(debug_text) = &fstring_expr.debug_text {
12969                        let (text, debug_text_range) = interpolation_debug_text(
12970                            &self.source_file,
12971                            debug_text,
12972                            fstring_expr.expression.range(),
12973                        );
12974                        let text: Wtf8Buf = text.into();
12975                        Self::extend_pending_literal_range(pending_literal_range, debug_text_range);
12976                        *pending_literal_no_location = false;
12977                        pending_literal
12978                            .get_or_insert_with(Wtf8Buf::new)
12979                            .push_wtf8(text.as_ref());
12980
12981                        // If debug text is present, apply repr conversion when no `format_spec` specified.
12982                        // See action_helpers.c: fstring_find_expr_replacement
12983                        if matches!(
12984                            (conversion, &fstring_expr.format_spec),
12985                            (ConvertValueOparg::None, None)
12986                        ) {
12987                            conversion = ConvertValueOparg::Repr;
12988                        }
12989                    }
12990
12991                    self.emit_pending_fstring_literal(
12992                        pending_literal,
12993                        pending_literal_range,
12994                        pending_literal_no_location,
12995                        element_count,
12996                        join_append_range,
12997                    );
12998
12999                    self.compile_expression(&fstring_expr.expression)?;
13000
13001                    let formatted_value_range = fstring_expr.range;
13002                    match conversion {
13003                        ConvertValueOparg::None => {}
13004                        ConvertValueOparg::Str
13005                        | ConvertValueOparg::Repr
13006                        | ConvertValueOparg::Ascii => {
13007                            self.set_source_range(formatted_value_range);
13008                            emit!(self, Instruction::ConvertValue { oparg: conversion })
13009                        }
13010                    }
13011
13012                    if let Some(format_spec) =
13013                        fstring_expr.runtime_formatted_value_format_spec.as_deref()
13014                    {
13015                        self.compile_expression(format_spec)?;
13016
13017                        self.set_source_range(formatted_value_range);
13018                        emit!(self, Instruction::FormatWithSpec);
13019                    } else {
13020                        match &fstring_expr.format_spec {
13021                            Some(format_spec) => {
13022                                let format_spec_range =
13023                                    self.cpython_format_spec_range(format_spec.range);
13024                                self.compile_fstring_elements(
13025                                    flags,
13026                                    &format_spec.elements,
13027                                    Some(format_spec_range),
13028                                )?;
13029
13030                                self.set_source_range(formatted_value_range);
13031                                emit!(self, Instruction::FormatWithSpec);
13032                            }
13033                            None => {
13034                                self.set_source_range(formatted_value_range);
13035                                emit!(self, Instruction::FormatSimple);
13036                            }
13037                        }
13038                    }
13039
13040                    *element_count += 1;
13041                    if let Some(join_append_range) = join_append_range {
13042                        self.set_source_range(join_append_range);
13043                        emit!(self, Instruction::ListAppend { i: 1 });
13044                    }
13045                }
13046            }
13047        }
13048
13049        Ok(())
13050    }
13051
13052    fn count_fstring_elements(
13053        &self,
13054        flags: ast::FStringFlags,
13055        fstring_elements: &ast::InterpolatedStringElements,
13056    ) -> u32 {
13057        let mut element_count = 0;
13058        let mut pending_literal = None;
13059        self.count_fstring_elements_into(
13060            flags,
13061            fstring_elements,
13062            &mut pending_literal,
13063            &mut element_count,
13064        );
13065        Self::count_pending_fstring_literal(&mut pending_literal, &mut element_count);
13066        element_count
13067    }
13068
13069    fn count_fstring_elements_into(
13070        &self,
13071        flags: ast::FStringFlags,
13072        fstring_elements: &ast::InterpolatedStringElements,
13073        pending_literal: &mut Option<Wtf8Buf>,
13074        element_count: &mut u32,
13075    ) {
13076        for element in fstring_elements {
13077            match element {
13078                ast::InterpolatedStringElement::Literal(string) => {
13079                    let value =
13080                        interpolated_string_literal_value(&self.source_file, string, flags.into());
13081                    if let Some(pending) = pending_literal.as_mut() {
13082                        pending.push_wtf8(value.as_ref());
13083                    } else {
13084                        *pending_literal = Some(value);
13085                    }
13086                }
13087                ast::InterpolatedStringElement::Interpolation(fstring_expr) => {
13088                    if let Some(debug_text) = &fstring_expr.debug_text {
13089                        let (text, _) = interpolation_debug_text(
13090                            &self.source_file,
13091                            debug_text,
13092                            fstring_expr.expression.range(),
13093                        );
13094                        let text: Wtf8Buf = text.into();
13095                        pending_literal
13096                            .get_or_insert_with(Wtf8Buf::new)
13097                            .push_wtf8(text.as_ref());
13098                    }
13099
13100                    Self::count_pending_fstring_literal(pending_literal, element_count);
13101                    *element_count += 1;
13102                }
13103            }
13104        }
13105    }
13106
13107    fn compile_expr_tstring(&mut self, expr_tstring: &ast::ExprTString) -> CompileResult<()> {
13108        // ast::TStringValue can contain multiple ast::TString parts (implicit
13109        // concatenation). Match CPython's stack order by materializing the
13110        // strings tuple first, then evaluating interpolations left-to-right.
13111        let tstring_value = &expr_tstring.value;
13112
13113        let mut all_strings: Vec<(Wtf8Buf, TextRange)> = Vec::new();
13114        let mut current_string = Wtf8Buf::new();
13115        let mut current_string_range = None;
13116        let mut interp_count: u32 = 0;
13117
13118        for tstring in tstring_value {
13119            self.collect_tstring_strings(
13120                tstring,
13121                &mut all_strings,
13122                &mut current_string,
13123                &mut current_string_range,
13124                &mut interp_count,
13125                expr_tstring.range,
13126            );
13127        }
13128
13129        all_strings.push((
13130            core::mem::take(&mut current_string),
13131            current_string_range.unwrap_or(expr_tstring.range),
13132        ));
13133
13134        let string_count: u32 = all_strings
13135            .len()
13136            .try_into()
13137            .expect("t-string string count overflowed");
13138        for (s, range) in &all_strings {
13139            self.set_source_range(*range);
13140            self.emit_load_const(ConstantData::Str { value: s.clone() });
13141        }
13142        self.set_source_range(expr_tstring.range);
13143        emit!(
13144            self,
13145            Instruction::BuildTuple {
13146                count: string_count
13147            }
13148        );
13149
13150        for tstring in tstring_value {
13151            self.compile_tstring_interpolations(tstring)?;
13152        }
13153
13154        self.set_source_range(expr_tstring.range);
13155        emit!(
13156            self,
13157            Instruction::BuildTuple {
13158                count: interp_count
13159            }
13160        );
13161        self.set_source_range(expr_tstring.range);
13162        emit!(self, Instruction::BuildTemplate);
13163
13164        Ok(())
13165    }
13166
13167    fn compile_runtime_template_str(
13168        &mut self,
13169        expr_tstring: &ast::ExprTString,
13170        values: &[ast::Expr],
13171    ) -> CompileResult<()> {
13172        let mut last_was_interpolation = true;
13173        let mut strings_len = 0;
13174        for value in values {
13175            if self.runtime_template_value_interpolation(value).is_some() {
13176                if last_was_interpolation {
13177                    self.set_source_range(expr_tstring.range);
13178                    self.emit_load_const(ConstantData::Str {
13179                        value: Wtf8Buf::new(),
13180                    });
13181                    strings_len += 1;
13182                }
13183                last_was_interpolation = true;
13184            } else {
13185                self.compile_expression(value)?;
13186                strings_len += 1;
13187                last_was_interpolation = false;
13188            }
13189        }
13190        if last_was_interpolation {
13191            self.set_source_range(expr_tstring.range);
13192            self.emit_load_const(ConstantData::Str {
13193                value: Wtf8Buf::new(),
13194            });
13195            strings_len += 1;
13196        }
13197        self.set_source_range(expr_tstring.range);
13198        emit!(self, Instruction::BuildTuple { count: strings_len });
13199
13200        let mut interpolations_len = 0;
13201        for value in values {
13202            if let Some((tstring, interpolation)) = self.runtime_template_value_interpolation(value)
13203            {
13204                self.compile_runtime_interpolation(tstring, interpolation)?;
13205                interpolations_len += 1;
13206            }
13207        }
13208        self.set_source_range(expr_tstring.range);
13209        emit!(
13210            self,
13211            Instruction::BuildTuple {
13212                count: interpolations_len
13213            }
13214        );
13215        self.set_source_range(expr_tstring.range);
13216        emit!(self, Instruction::BuildTemplate);
13217        Ok(())
13218    }
13219
13220    fn runtime_template_value_interpolation<'a>(
13221        &self,
13222        value: &'a ast::Expr,
13223    ) -> Option<(
13224        &'a ast::ExprTString,
13225        (&'a ast::ConstantValue, Option<&'a ast::Expr>),
13226    )> {
13227        let ast::Expr::TString(tstring) = value else {
13228            return None;
13229        };
13230        let interpolation = Self::single_runtime_interpolation(tstring)?;
13231        Self::single_tstring_interpolation(tstring)?;
13232        Some((tstring, interpolation))
13233    }
13234
13235    fn compile_runtime_interpolation(
13236        &mut self,
13237        expr_tstring: &ast::ExprTString,
13238        interpolation: (&ast::ConstantValue, Option<&ast::Expr>),
13239    ) -> CompileResult<bool> {
13240        let Some(interp) = Self::single_tstring_interpolation(expr_tstring) else {
13241            return Ok(false);
13242        };
13243
13244        self.compile_interpolation(interp, interpolation)?;
13245        Ok(true)
13246    }
13247
13248    fn compile_interpolation(
13249        &mut self,
13250        interp: &ast::InterpolatedElement,
13251        interpolation: (&ast::ConstantValue, Option<&ast::Expr>),
13252    ) -> CompileResult<()> {
13253        let (str, format_spec) = interpolation;
13254        self.compile_expression(&interp.expression)?;
13255        self.set_source_range(interp.range);
13256        self.emit_load_const(ast_constant_value_to_constant_data(str.clone()));
13257
13258        let conversion = match interp.conversion {
13259            ast::ConversionFlag::None => 0,
13260            ast::ConversionFlag::Str => 1,
13261            ast::ConversionFlag::Repr => 2,
13262            ast::ConversionFlag::Ascii => 3,
13263        };
13264
13265        let has_format_spec = format_spec.is_some();
13266        if let Some(format_spec) = format_spec {
13267            self.compile_expression(format_spec)?;
13268        }
13269
13270        let format = 2 | (conversion << 2) | u32::from(has_format_spec);
13271        self.set_source_range(interp.range);
13272        emit!(self, Instruction::BuildInterpolation { format });
13273        Ok(())
13274    }
13275
13276    fn single_tstring_interpolation(
13277        expr_tstring: &ast::ExprTString,
13278    ) -> Option<&ast::InterpolatedElement> {
13279        let [tstring] = expr_tstring.value.as_slice() else {
13280            return None;
13281        };
13282        let mut elements = tstring.elements.iter();
13283        let ast::InterpolatedStringElement::Interpolation(interp) = elements.next()? else {
13284            return None;
13285        };
13286        if elements.next().is_some() {
13287            return None;
13288        }
13289        Some(interp)
13290    }
13291
13292    fn collect_tstring_strings(
13293        &self,
13294        tstring: &ast::TString,
13295        strings: &mut Vec<(Wtf8Buf, TextRange)>,
13296        current_string: &mut Wtf8Buf,
13297        current_string_range: &mut Option<TextRange>,
13298        interp_count: &mut u32,
13299        template_range: TextRange,
13300    ) {
13301        for element in &tstring.elements {
13302            match element {
13303                ast::InterpolatedStringElement::Literal(lit) => {
13304                    if current_string_range.is_none() {
13305                        *current_string_range = Some(lit.range);
13306                    } else {
13307                        Self::extend_pending_literal_range(current_string_range, lit.range);
13308                    }
13309                    current_string.push_wtf8(&interpolated_string_literal_value(
13310                        &self.source_file,
13311                        lit,
13312                        tstring.flags.into(),
13313                    ));
13314                }
13315                ast::InterpolatedStringElement::Interpolation(interp) => {
13316                    if let Some(debug_text) = &interp.debug_text {
13317                        let (text, debug_text_range) = interpolation_debug_text(
13318                            &self.source_file,
13319                            debug_text,
13320                            interp.expression.range(),
13321                        );
13322                        if current_string_range.is_none() {
13323                            *current_string_range = Some(debug_text_range);
13324                        } else {
13325                            Self::extend_pending_literal_range(
13326                                current_string_range,
13327                                debug_text_range,
13328                            );
13329                        }
13330                        current_string.push_str(&text);
13331                        strings.push((
13332                            core::mem::take(current_string),
13333                            current_string_range.take().unwrap_or(template_range),
13334                        ));
13335                    } else {
13336                        strings.push((
13337                            core::mem::take(current_string),
13338                            current_string_range.take().unwrap_or(template_range),
13339                        ));
13340                    }
13341                    *interp_count += 1;
13342                }
13343            }
13344        }
13345    }
13346
13347    fn compile_tstring_interpolations(&mut self, tstring: &ast::TString) -> CompileResult<()> {
13348        for element in &tstring.elements {
13349            let ast::InterpolatedStringElement::Interpolation(interp) = element else {
13350                continue;
13351            };
13352
13353            if let Some(runtime_str) = interp.runtime_str.as_ref() {
13354                let interpolation = (
13355                    runtime_str,
13356                    interp.runtime_interpolation_format_spec.as_deref(),
13357                );
13358                self.compile_interpolation(interp, interpolation)?;
13359                continue;
13360            }
13361
13362            self.compile_expression(&interp.expression)?;
13363
13364            let expr_range = interp.expression.range();
13365            let expr_source = if interp.range.start() < expr_range.start()
13366                && interp.range.end() >= expr_range.end()
13367            {
13368                let after_brace = interp.range.start() + TextSize::new(1);
13369                self.source_file
13370                    .source_text()
13371                    .slice(TextRange::new(after_brace, expr_range.end()))
13372            } else {
13373                self.source_file.source_text().slice(expr_range)
13374            }
13375            .to_string();
13376            self.set_source_range(interp.range);
13377            self.emit_load_const(ConstantData::Str {
13378                value: expr_source.into(),
13379            });
13380
13381            let mut conversion: u32 = match interp.conversion {
13382                ast::ConversionFlag::None => 0,
13383                ast::ConversionFlag::Str => 1,
13384                ast::ConversionFlag::Repr => 2,
13385                ast::ConversionFlag::Ascii => 3,
13386            };
13387
13388            if interp.debug_text.is_some() && conversion == 0 && interp.format_spec.is_none() {
13389                conversion = 2;
13390            }
13391
13392            let has_format_spec = interp.format_spec.is_some();
13393            if let Some(format_spec) = &interp.format_spec {
13394                let format_spec_range = self.cpython_format_spec_range(format_spec.range);
13395                self.compile_fstring_elements(
13396                    ast::FStringFlags::empty(),
13397                    &format_spec.elements,
13398                    Some(format_spec_range),
13399                )?;
13400            }
13401
13402            // CPython keeps bit 1 set in BUILD_INTERPOLATION's oparg and uses
13403            // bit 0 for the optional format spec.
13404            let format = 2 | (conversion << 2) | u32::from(has_format_spec);
13405            self.set_source_range(interp.range);
13406            emit!(self, Instruction::BuildInterpolation { format });
13407        }
13408
13409        Ok(())
13410    }
13411}
13412
13413trait EmitArg<Arg: OpArgType> {
13414    fn emit<I: Into<AnyInstruction>>(
13415        self,
13416        f: impl FnOnce(OpArgMarker<Arg>) -> I,
13417    ) -> (AnyInstruction, OpArg, BlockIdx);
13418}
13419
13420impl<T: OpArgType> EmitArg<T> for T {
13421    fn emit<I: Into<AnyInstruction>>(
13422        self,
13423        f: impl FnOnce(OpArgMarker<T>) -> I,
13424    ) -> (AnyInstruction, OpArg, BlockIdx) {
13425        let (marker, arg) = OpArgMarker::new(self);
13426        (f(marker).into(), arg, BlockIdx::NULL)
13427    }
13428}
13429
13430impl EmitArg<bytecode::Label> for BlockIdx {
13431    fn emit<I: Into<AnyInstruction>>(
13432        self,
13433        f: impl FnOnce(OpArgMarker<bytecode::Label>) -> I,
13434    ) -> (AnyInstruction, OpArg, BlockIdx) {
13435        (f(OpArgMarker::marker()).into(), OpArg::NULL, self)
13436    }
13437}
13438
13439/// Strips leading whitespace from a docstring.
13440///
13441/// `inspect.cleandoc` is a good reference, but has a few incompatibilities.
13442// = _PyCompile_CleanDoc
13443fn clean_doc(doc: &str) -> String {
13444    let doc = expandtabs(doc, 8);
13445    // First pass: find minimum indentation of non-blank lines AFTER the first line.
13446    // A "blank line" is one containing only spaces (or empty).
13447    let margin = doc
13448        .split('\n')
13449        .skip(1) // skip first line
13450        .filter(|line| line.chars().any(|c| c != ' ')) // non-blank lines only
13451        .map(|line| line.chars().take_while(|c| *c == ' ').count())
13452        .min()
13453        .unwrap_or(0);
13454
13455    let mut cleaned = String::with_capacity(doc.len());
13456    // Strip all leading spaces from the first line
13457    if let Some(first_line) = doc.split('\n').next() {
13458        let trimmed = first_line.trim_start();
13459        // Early exit: no leading spaces on first line AND margin == 0
13460        if trimmed.len() == first_line.len() && margin == 0 {
13461            return doc.to_owned();
13462        }
13463        cleaned.push_str(trimmed);
13464    }
13465    // Subsequent lines: skip up to `margin` leading spaces
13466    for line in doc.split('\n').skip(1) {
13467        cleaned.push('\n');
13468        let skip = line.chars().take(margin).take_while(|c| *c == ' ').count();
13469        cleaned.push_str(&line[skip..]);
13470    }
13471
13472    cleaned
13473}
13474
13475// copied from rustpython_common::str, so we don't have to depend on it just for this function
13476fn expandtabs(input: &str, tab_size: usize) -> String {
13477    let tab_stop = tab_size;
13478    let mut expanded_str = String::with_capacity(input.len());
13479    let mut tab_size = tab_stop;
13480    let mut col_count = 0usize;
13481    for ch in input.chars() {
13482        match ch {
13483            '\t' => {
13484                let num_spaces = tab_size - col_count;
13485                col_count += num_spaces;
13486                let expand = " ".repeat(num_spaces);
13487                expanded_str.push_str(&expand);
13488            }
13489            '\r' | '\n' => {
13490                expanded_str.push(ch);
13491                col_count = 0;
13492                tab_size = 0;
13493            }
13494            _ => {
13495                expanded_str.push(ch);
13496                col_count += 1;
13497            }
13498        }
13499        if col_count >= tab_size {
13500            tab_size += tab_stop;
13501        }
13502    }
13503    expanded_str
13504}
13505
13506fn split_doc_with_range<'a>(
13507    body: &'a [ast::Stmt],
13508    opts: &CompileOpts,
13509) -> (Option<(String, TextRange)>, &'a [ast::Stmt]) {
13510    if let Some((ast::Stmt::Expr(expr), body_rest)) = body.split_first() {
13511        let doc_comment: Option<(&str, TextRange)> = match &*expr.value {
13512            ast::Expr::StringLiteral(value) => Some((value.value.to_str(), expr.value.range())),
13513            ast::Expr::Constant(ast::ExprConstant {
13514                value: ast::ConstantValue::Str(value),
13515                ..
13516            }) => Some((value.as_ref(), expr.value.range())),
13517            // f-strings are not allowed in Python doc comments.
13518            ast::Expr::FString(_) => None,
13519            _ => None,
13520        };
13521        if let Some((doc, range)) = doc_comment {
13522            return if opts.optimize < 2 {
13523                (Some((clean_doc(doc), range)), body_rest)
13524            } else {
13525                (None, body_rest)
13526            };
13527        }
13528    }
13529    (None, body)
13530}
13531
13532#[cfg(test)]
13533fn split_doc<'a>(body: &'a [ast::Stmt], opts: &CompileOpts) -> (Option<String>, &'a [ast::Stmt]) {
13534    let (doc, body) = split_doc_with_range(body, opts);
13535    (doc.map(|(doc, _)| doc), body)
13536}
13537
13538pub fn ruff_int_to_bigint(int: &ast::Int) -> Result<BigInt, CodegenErrorType> {
13539    if let Some(small) = int.as_u64() {
13540        Ok(BigInt::from(small))
13541    } else {
13542        parse_big_integer(int)
13543    }
13544}
13545
13546/// Converts a `ruff` ast integer into a `BigInt`.
13547/// Unlike small integers, big integers may be stored in one of four possible radix representations.
13548fn parse_big_integer(int: &ast::Int) -> Result<BigInt, CodegenErrorType> {
13549    // TODO: Improve ruff API
13550    // Can we avoid this copy?
13551    let s = format!("{int}");
13552    let mut s = s.as_str();
13553    // See: https://peps.python.org/pep-0515/#literal-grammar
13554    let radix = match s.get(0..2) {
13555        Some("0b" | "0B") => {
13556            s = s.get(2..).unwrap_or(s);
13557            2
13558        }
13559        Some("0o" | "0O") => {
13560            s = s.get(2..).unwrap_or(s);
13561            8
13562        }
13563        Some("0x" | "0X") => {
13564            s = s.get(2..).unwrap_or(s);
13565            16
13566        }
13567        _ => 10,
13568    };
13569
13570    BigInt::from_str_radix(s, radix).map_err(|e| {
13571        CodegenErrorType::SyntaxError(format!(
13572            "unparsed integer literal (radix {radix}): {s} ({e})"
13573        ))
13574    })
13575}
13576
13577// Note: Not a good practice in general. Keep this trait private only for compiler
13578trait ToU32 {
13579    fn to_u32(self) -> u32;
13580}
13581
13582impl ToU32 for usize {
13583    fn to_u32(self) -> u32 {
13584        self.try_into().unwrap()
13585    }
13586}
13587
13588#[cfg(test)]
13589mod ruff_tests {
13590    use super::*;
13591    use ast::name::Name;
13592
13593    /// Test if the compiler can correctly identify fstrings containing an `await` expression.
13594    #[test]
13595    fn fstring_contains_await() {
13596        let range = TextRange::default();
13597        let flags = ast::FStringFlags::empty();
13598
13599        // f'{x}'
13600        let expr_x = ast::Expr::Name(ast::ExprName {
13601            node_index: ast::AtomicNodeIndex::NONE,
13602            range,
13603            id: Name::new("x"),
13604            ctx: ast::ExprContext::Load,
13605        });
13606        let not_present = &ast::Expr::FString(ast::ExprFString {
13607            node_index: ast::AtomicNodeIndex::NONE,
13608            range,
13609            value: ast::FStringValue::single(ast::FString {
13610                node_index: ast::AtomicNodeIndex::NONE,
13611                range,
13612                elements: vec![ast::InterpolatedStringElement::Interpolation(
13613                    ast::InterpolatedElement {
13614                        node_index: ast::AtomicNodeIndex::NONE,
13615                        range,
13616                        expression: Box::new(expr_x),
13617                        debug_text: None,
13618                        conversion: ast::ConversionFlag::None,
13619                        format_spec: None,
13620                        runtime_str: None,
13621                        runtime_interpolation_format_spec: None,
13622                        runtime_formatted_value_format_spec: None,
13623                    },
13624                )]
13625                .into(),
13626                flags,
13627            }),
13628            runtime_joined_str: None,
13629            runtime_values: None,
13630        });
13631        assert!(!Compiler::contains_await(not_present));
13632
13633        // f'{await x}'
13634        let expr_await_x = ast::Expr::Await(ast::ExprAwait {
13635            node_index: ast::AtomicNodeIndex::NONE,
13636            range,
13637            value: Box::new(ast::Expr::Name(ast::ExprName {
13638                node_index: ast::AtomicNodeIndex::NONE,
13639                range,
13640                id: Name::new("x"),
13641                ctx: ast::ExprContext::Load,
13642            })),
13643        });
13644        let present = &ast::Expr::FString(ast::ExprFString {
13645            node_index: ast::AtomicNodeIndex::NONE,
13646            range,
13647            value: ast::FStringValue::single(ast::FString {
13648                node_index: ast::AtomicNodeIndex::NONE,
13649                range,
13650                elements: vec![ast::InterpolatedStringElement::Interpolation(
13651                    ast::InterpolatedElement {
13652                        node_index: ast::AtomicNodeIndex::NONE,
13653                        range,
13654                        expression: Box::new(expr_await_x),
13655                        debug_text: None,
13656                        conversion: ast::ConversionFlag::None,
13657                        format_spec: None,
13658                        runtime_str: None,
13659                        runtime_interpolation_format_spec: None,
13660                        runtime_formatted_value_format_spec: None,
13661                    },
13662                )]
13663                .into(),
13664                flags,
13665            }),
13666            runtime_joined_str: None,
13667            runtime_values: None,
13668        });
13669        assert!(Compiler::contains_await(present));
13670
13671        // f'{x:{await y}}'
13672        let expr_x = ast::Expr::Name(ast::ExprName {
13673            node_index: ast::AtomicNodeIndex::NONE,
13674            range,
13675            id: Name::new("x"),
13676            ctx: ast::ExprContext::Load,
13677        });
13678        let expr_await_y = ast::Expr::Await(ast::ExprAwait {
13679            node_index: ast::AtomicNodeIndex::NONE,
13680            range,
13681            value: Box::new(ast::Expr::Name(ast::ExprName {
13682                node_index: ast::AtomicNodeIndex::NONE,
13683                range,
13684                id: Name::new("y"),
13685                ctx: ast::ExprContext::Load,
13686            })),
13687        });
13688        let present = &ast::Expr::FString(ast::ExprFString {
13689            node_index: ast::AtomicNodeIndex::NONE,
13690            range,
13691            value: ast::FStringValue::single(ast::FString {
13692                node_index: ast::AtomicNodeIndex::NONE,
13693                range,
13694                elements: vec![ast::InterpolatedStringElement::Interpolation(
13695                    ast::InterpolatedElement {
13696                        node_index: ast::AtomicNodeIndex::NONE,
13697                        range,
13698                        expression: Box::new(expr_x),
13699                        debug_text: None,
13700                        conversion: ast::ConversionFlag::None,
13701                        format_spec: Some(Box::new(ast::InterpolatedStringFormatSpec {
13702                            node_index: ast::AtomicNodeIndex::NONE,
13703                            range,
13704                            elements: vec![ast::InterpolatedStringElement::Interpolation(
13705                                ast::InterpolatedElement {
13706                                    node_index: ast::AtomicNodeIndex::NONE,
13707                                    range,
13708                                    expression: Box::new(expr_await_y),
13709                                    debug_text: None,
13710                                    conversion: ast::ConversionFlag::None,
13711                                    format_spec: None,
13712                                    runtime_str: None,
13713                                    runtime_interpolation_format_spec: None,
13714                                    runtime_formatted_value_format_spec: None,
13715                                },
13716                            )]
13717                            .into(),
13718                        })),
13719                        runtime_str: None,
13720                        runtime_interpolation_format_spec: None,
13721                        runtime_formatted_value_format_spec: None,
13722                    },
13723                )]
13724                .into(),
13725                flags,
13726            }),
13727            runtime_joined_str: None,
13728            runtime_values: None,
13729        });
13730        assert!(Compiler::contains_await(present));
13731    }
13732}
13733
13734#[cfg(test)]
13735mod tests {
13736    use super::*;
13737    use rustpython_compiler_core::{
13738        SourceFileBuilder,
13739        bytecode::{CO_FAST_ARG_KW, CO_FAST_ARG_POS, CodeUnit, OpArg},
13740    };
13741
13742    fn assert_scope_exit_locations(code: &CodeObject) {
13743        for (instr, (location, _)) in code.instructions.iter().zip(code.locations.iter()) {
13744            if matches!(
13745                instr.op,
13746                Instruction::ReturnValue
13747                    | Instruction::RaiseVarargs { .. }
13748                    | Instruction::Reraise { .. }
13749            ) {
13750                assert!(
13751                    location.line.get() > 0,
13752                    "scope-exit instruction {instr:?} is missing a line number"
13753                );
13754            }
13755        }
13756        for constant in code.constants.iter() {
13757            if let ConstantData::Code { code } = constant {
13758                assert_scope_exit_locations(code);
13759            }
13760        }
13761    }
13762
13763    fn compile_exec(source: &str) -> CodeObject {
13764        let opts = CompileOpts::default();
13765        compile_exec_with_options(source, opts)
13766    }
13767
13768    fn compile_single(source: &str) -> CodeObject {
13769        let opts = CompileOpts::default();
13770        let source_file = SourceFileBuilder::new("source_path", source).finish();
13771        let parsed = ruff_python_parser::parse(
13772            source_file.source_text(),
13773            ruff_python_parser::Mode::Module.into(),
13774        )
13775        .unwrap()
13776        .into_syntax();
13777        compile_top(parsed, source_file, Mode::Single, opts).unwrap()
13778    }
13779
13780    fn compile_exec_optimized(source: &str) -> CodeObject {
13781        let opts = CompileOpts {
13782            optimize: 1,
13783            ..CompileOpts::default()
13784        };
13785        compile_exec_with_options(source, opts)
13786    }
13787
13788    fn compile_exec_with_options(source: &str, mut opts: CompileOpts) -> CodeObject {
13789        let source_file = SourceFileBuilder::new("source_path", source).finish();
13790        let parsed = ruff_python_parser::parse(
13791            source_file.source_text(),
13792            ruff_python_parser::Mode::Module.into(),
13793        )
13794        .unwrap();
13795        let mut ast = parsed.into_syntax();
13796        opts.future_features |= checked_future_features(&ast, &source_file).unwrap();
13797        let future_annotations = opts
13798            .future_features
13799            .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
13800        preprocess::preprocess_mod(&mut ast, opts.optimize, future_annotations, false);
13801        let ast = match ast {
13802            ruff_python_ast::Mod::Module(stmts) => stmts,
13803            _ => unreachable!(),
13804        };
13805        let symbol_table = SymbolTable::scan_program_with_options(
13806            &ast,
13807            source_file.clone(),
13808            opts.allow_top_level_await,
13809            opts.future_features
13810                .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS),
13811            opts.recursion_limit,
13812        )
13813        .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))
13814        .unwrap();
13815        let mut compiler =
13816            Compiler::new_with_syntax_warning_handler(opts, source_file, "<module>", None);
13817        compiler.compile_program(&ast, symbol_table).unwrap();
13818        compiler.exit_scope()
13819    }
13820
13821    fn compile_module_instruction_infos(source: &str, mode: Mode) -> Vec<ir::InstructionInfo> {
13822        let mut opts = CompileOpts::default();
13823        let source_file = SourceFileBuilder::new("source_path", source).finish();
13824        let parsed = ruff_python_parser::parse(
13825            source_file.source_text(),
13826            ruff_python_parser::Mode::Module.into(),
13827        )
13828        .unwrap();
13829        let mut ast = parsed.into_syntax();
13830        opts.future_features |= checked_future_features(&ast, &source_file).unwrap();
13831        let future_annotations = opts
13832            .future_features
13833            .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
13834        if matches!(mode, Mode::Single)
13835            && let ruff_python_ast::Mod::Module(module) = &mut ast
13836        {
13837            preprocess::preprocess_statements(
13838                &mut module.body,
13839                opts.optimize,
13840                future_annotations,
13841                false,
13842            );
13843        } else {
13844            preprocess::preprocess_mod(&mut ast, opts.optimize, future_annotations, false);
13845        }
13846        let ast = match ast {
13847            ruff_python_ast::Mod::Module(stmts) => stmts,
13848            _ => unreachable!(),
13849        };
13850        let symbol_table = SymbolTable::scan_program_with_options(
13851            &ast,
13852            source_file.clone(),
13853            opts.allow_top_level_await,
13854            opts.future_features
13855                .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS),
13856            opts.recursion_limit,
13857        )
13858        .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))
13859        .unwrap();
13860        let mut compiler =
13861            Compiler::new_with_syntax_warning_handler(opts, source_file, "<module>", None);
13862        match mode {
13863            Mode::Single => compiler.compile_program_single(&ast.body, symbol_table),
13864            _ => compiler.compile_program(&ast, symbol_table),
13865        }
13866        .unwrap();
13867
13868        compiler
13869            .current_code_info()
13870            .blocks
13871            .iter()
13872            .flat_map(|block| block.used_instructions().iter().copied())
13873            .collect()
13874    }
13875
13876    fn compile_eval_ast_with_options(expr: ast::Expr, opts: CompileOpts) -> CodeObject {
13877        let source_file = SourceFileBuilder::new("source_path", "").finish();
13878        let parsed = ruff_python_ast::Mod::Expression(ast::ModExpression {
13879            node_index: ast::AtomicNodeIndex::NONE,
13880            range: TextRange::default(),
13881            body: Box::new(expr),
13882        });
13883        compile_top(parsed, source_file, Mode::Eval, opts).unwrap()
13884    }
13885
13886    fn set_ast_constant(expr: &mut ast::Expr, constant: ConstantData) {
13887        let constant = crate::constant_data_to_ast_constant_value(constant);
13888        let range = expr.range();
13889        *expr = ast::Expr::Constant(ast::ExprConstant {
13890            node_index: Default::default(),
13891            range,
13892            value: constant,
13893            kind: None,
13894            invalid_type: None,
13895        });
13896    }
13897
13898    fn compile_ast_constant_expr(mut expr: ast::Expr, constant: ConstantData) -> CodeObject {
13899        set_ast_constant(&mut expr, constant);
13900        compile_eval_ast_with_options(expr, CompileOpts::default())
13901    }
13902
13903    fn first_ast_constant_warning(expr: ast::Expr) -> String {
13904        let opts = CompileOpts::default();
13905        let source_file = SourceFileBuilder::new("source_path", "").finish();
13906        let parsed = ruff_python_ast::Mod::Expression(ast::ModExpression {
13907            node_index: ast::AtomicNodeIndex::NONE,
13908            range: TextRange::default(),
13909            body: Box::new(expr),
13910        });
13911        let mut warning = None;
13912        let mut handler = |location, message: String| {
13913            warning = Some(message.clone());
13914            Err(CodegenError {
13915                location: Some(location),
13916                end_location: None,
13917                error: CodegenErrorType::SyntaxError(message),
13918                source_path: "source_path".to_owned(),
13919            })
13920        };
13921        compile_top_with_syntax_warning_handler(
13922            parsed,
13923            source_file,
13924            Mode::Eval,
13925            opts,
13926            Some(&mut handler),
13927        )
13928        .expect_err("expected SyntaxWarning handler to stop compilation");
13929        warning.expect("expected warning message")
13930    }
13931
13932    fn first_exec_warning(source: &str) -> String {
13933        let opts = CompileOpts::default();
13934        let source_file = SourceFileBuilder::new("source_path", source).finish();
13935        let parsed = ruff_python_parser::parse(
13936            source_file.source_text(),
13937            ruff_python_parser::Mode::Module.into(),
13938        )
13939        .unwrap()
13940        .into_syntax();
13941        let mut warning = None;
13942        let mut handler = |location, message: String| {
13943            warning = Some(message.clone());
13944            Err(CodegenError {
13945                location: Some(location),
13946                end_location: None,
13947                error: CodegenErrorType::SyntaxError(message),
13948                source_path: "source_path".to_owned(),
13949            })
13950        };
13951        compile_top_with_syntax_warning_handler(
13952            parsed,
13953            source_file,
13954            Mode::Exec,
13955            opts,
13956            Some(&mut handler),
13957        )
13958        .expect_err("expected SyntaxWarning handler to stop compilation");
13959        warning.expect("expected warning message")
13960    }
13961
13962    fn frozenset_call_expr() -> ast::Expr {
13963        ast::Expr::Call(ast::ExprCall {
13964            node_index: ast::AtomicNodeIndex::NONE,
13965            range_start: TextSize::default(),
13966            func: Box::new(ast::Expr::Name(ast::ExprName {
13967                node_index: ast::AtomicNodeIndex::NONE,
13968                range: TextRange::default(),
13969                id: ast::name::Name::new_static("frozenset"),
13970                ctx: ast::ExprContext::Load,
13971            })),
13972            arguments: ast::Arguments {
13973                node_index: ast::AtomicNodeIndex::NONE,
13974                range: TextRange::default(),
13975                args: Box::default(),
13976                keywords: Default::default(),
13977                runtime_args: None,
13978                runtime_bases: None,
13979            },
13980        })
13981    }
13982
13983    fn compile_exec_parsed_error(
13984        source: &str,
13985        parsed: ruff_python_parser::Parsed<ruff_python_ast::Mod>,
13986    ) -> CodegenError {
13987        let mut opts = CompileOpts::default();
13988        let source_file = SourceFileBuilder::new("source_path", source).finish();
13989        let mut ast = parsed.into_syntax();
13990        opts.future_features |= match checked_future_features(&ast, &source_file) {
13991            Ok(features) => features,
13992            Err(err) => return err,
13993        };
13994        let future_annotations = opts
13995            .future_features
13996            .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
13997        preprocess::preprocess_mod(&mut ast, opts.optimize, future_annotations, false);
13998        let ast = match ast {
13999            ruff_python_ast::Mod::Module(stmts) => stmts,
14000            _ => unreachable!(),
14001        };
14002        let symbol_table = match SymbolTable::scan_program(&ast, source_file.clone()) {
14003            Ok(symbol_table) => symbol_table,
14004            Err(err) => return err.into_codegen_error(source_file.name().to_owned()),
14005        };
14006        let mut compiler =
14007            Compiler::new_with_syntax_warning_handler(opts, source_file, "<module>", None);
14008        compiler.compile_program(&ast, symbol_table).unwrap_err()
14009    }
14010
14011    fn compile_exec_error(source: &str) -> CodegenError {
14012        let source_file = SourceFileBuilder::new("source_path", source).finish();
14013        let parsed = ruff_python_parser::parse(
14014            source_file.source_text(),
14015            ruff_python_parser::Mode::Module.into(),
14016        )
14017        .unwrap();
14018        compile_exec_parsed_error(source, parsed)
14019    }
14020
14021    fn compile_exec_error_message(source: &str) -> String {
14022        compile_exec_error(source).error.to_string()
14023    }
14024
14025    fn compile_exec_unchecked_error_message(source: &str) -> String {
14026        let source_file = SourceFileBuilder::new("source_path", source).finish();
14027        let parsed = ruff_python_parser::parse_unchecked(
14028            source_file.source_text(),
14029            ruff_python_parser::Mode::Module.into(),
14030        );
14031        compile_exec_parsed_error(source, parsed).error.to_string()
14032    }
14033
14034    #[test]
14035    fn ast_constant_frozenset_compiles_as_load_const() {
14036        let code = compile_ast_constant_expr(
14037            frozenset_call_expr(),
14038            ConstantData::Frozenset {
14039                elements: vec![ConstantData::Integer {
14040                    value: BigInt::from(1u8),
14041                }],
14042            },
14043        );
14044        let ops: Vec<_> = code
14045            .instructions
14046            .iter()
14047            .map(|unit| unit.op)
14048            .filter(|op| !matches!(op, Instruction::Cache))
14049            .collect();
14050
14051        assert!(
14052            ops.iter()
14053                .any(|op| matches!(op, Instruction::LoadConst { .. })),
14054            "public ast.Constant(frozenset(...)) must use CPython Constant_kind LOAD_CONST path, got {ops:?}"
14055        );
14056        assert!(
14057            !ops.iter().any(|op| matches!(
14058                op,
14059                Instruction::LoadName { .. }
14060                    | Instruction::Call { .. }
14061                    | Instruction::CallKw { .. }
14062            )),
14063            "public ast.Constant(frozenset(...)) must not compile as a frozenset() call, got {ops:?}"
14064        );
14065        assert!(
14066            code.constants.iter().any(|constant| matches!(
14067                constant,
14068                ConstantData::Frozenset { elements }
14069                    if matches!(
14070                        elements.as_slice(),
14071                        [ConstantData::Integer { value }] if *value == BigInt::from(1u8)
14072                    )
14073            )),
14074            "missing frozenset constant in code constants"
14075        );
14076    }
14077
14078    #[test]
14079    fn ast_constant_is_not_scanned_as_lowered_expression() {
14080        let mut expr = frozenset_call_expr();
14081        set_ast_constant(
14082            &mut expr,
14083            ConstantData::Frozenset {
14084                elements: Vec::new(),
14085            },
14086        );
14087        let module = ast::ModExpression {
14088            node_index: ast::AtomicNodeIndex::NONE,
14089            range: TextRange::default(),
14090            body: Box::new(expr),
14091        };
14092        let table = SymbolTable::scan_expr_with_options(
14093            &module,
14094            SourceFileBuilder::new("source_path", "").finish(),
14095            false,
14096            false,
14097            CompileOpts::default().recursion_limit,
14098        )
14099        .unwrap();
14100
14101        assert!(
14102            table.lookup(&"frozenset".into()).is_none(),
14103            "CPython symtable Constant_kind does not visit the lowered frozenset() expression"
14104        );
14105    }
14106
14107    #[test]
14108    fn ast_constant_frozenset_call_warns_like_cpython_constant() {
14109        let mut func = frozenset_call_expr();
14110        set_ast_constant(
14111            &mut func,
14112            ConstantData::Frozenset {
14113                elements: Vec::new(),
14114            },
14115        );
14116        let message = first_ast_constant_warning(ast::Expr::Call(ast::ExprCall {
14117            node_index: ast::AtomicNodeIndex::NONE,
14118            range_start: TextSize::default(),
14119            func: Box::new(func),
14120            arguments: ast::Arguments {
14121                node_index: ast::AtomicNodeIndex::NONE,
14122                range: TextRange::default(),
14123                args: Box::default(),
14124                keywords: Default::default(),
14125                runtime_args: None,
14126                runtime_bases: None,
14127            },
14128        }));
14129        assert!(
14130            message.contains("'frozenset' object is not callable"),
14131            "expected public ast.Constant(frozenset()) callable warning, got {message:?}"
14132        );
14133    }
14134
14135    #[test]
14136    fn ast_constant_frozenset_subscript_warns_like_cpython_constant() {
14137        let mut value = frozenset_call_expr();
14138        set_ast_constant(
14139            &mut value,
14140            ConstantData::Frozenset {
14141                elements: Vec::new(),
14142            },
14143        );
14144        let message = first_ast_constant_warning(ast::Expr::Subscript(ast::ExprSubscript {
14145            node_index: ast::AtomicNodeIndex::NONE,
14146            range: TextRange::default(),
14147            value: Box::new(value),
14148            slice: Box::new(ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
14149                node_index: ast::AtomicNodeIndex::NONE,
14150                range: TextRange::default(),
14151                value: ast::Number::Int(ast::Int::ZERO),
14152            })),
14153            ctx: ast::ExprContext::Load,
14154        }));
14155        assert!(
14156            message.contains("'frozenset' object is not subscriptable"),
14157            "expected public ast.Constant(frozenset()) subscript warning, got {message:?}"
14158        );
14159    }
14160
14161    #[test]
14162    fn ast_constant_str_bad_index_warns_like_cpython_constant() {
14163        let mut value = frozenset_call_expr();
14164        set_ast_constant(
14165            &mut value,
14166            ConstantData::Str {
14167                value: "abc".into(),
14168            },
14169        );
14170        let message = first_ast_constant_warning(ast::Expr::Subscript(ast::ExprSubscript {
14171            node_index: ast::AtomicNodeIndex::NONE,
14172            range: TextRange::default(),
14173            value: Box::new(value),
14174            slice: Box::new(ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
14175                node_index: ast::AtomicNodeIndex::NONE,
14176                range: TextRange::default(),
14177                value: ast::Number::Float(1.0),
14178            })),
14179            ctx: ast::ExprContext::Load,
14180        }));
14181        assert!(
14182            message.contains("str indices must be integers or slices, not float"),
14183            "expected public ast.Constant(str) bad-index warning, got {message:?}"
14184        );
14185    }
14186
14187    #[test]
14188    fn ast_constant_frozenset_is_warns_like_cpython_constant() {
14189        let mut left = frozenset_call_expr();
14190        set_ast_constant(
14191            &mut left,
14192            ConstantData::Frozenset {
14193                elements: Vec::new(),
14194            },
14195        );
14196        let message = first_ast_constant_warning(ast::Expr::Compare(ast::ExprCompare {
14197            node_index: ast::AtomicNodeIndex::NONE,
14198            range: TextRange::default(),
14199            left: Box::new(left),
14200            ops: Box::new([ast::CmpOp::Is]),
14201            comparators: Box::new([ast::Expr::NoneLiteral(ast::ExprNoneLiteral {
14202                node_index: ast::AtomicNodeIndex::NONE,
14203                range: TextRange::default(),
14204            })]),
14205            runtime_comparators: None,
14206        }));
14207        assert!(
14208            message.contains("\"is\" with 'frozenset' literal"),
14209            "expected public ast.Constant(frozenset()) identity warning, got {message:?}"
14210        );
14211    }
14212
14213    #[test]
14214    fn ast_constant_tuple_compiles_as_load_const() {
14215        let expr = ast::Expr::Tuple(ast::ExprTuple {
14216            node_index: ast::AtomicNodeIndex::NONE,
14217            range: TextRange::default(),
14218            elts: Vec::new(),
14219            ctx: ast::ExprContext::Load,
14220            parenthesized: true,
14221            runtime_elts: None,
14222        });
14223        let code = compile_ast_constant_expr(
14224            expr,
14225            ConstantData::Tuple {
14226                elements: vec![ConstantData::Integer {
14227                    value: BigInt::from(1u8),
14228                }],
14229            },
14230        );
14231        let ops: Vec<_> = code
14232            .instructions
14233            .iter()
14234            .map(|unit| unit.op)
14235            .filter(|op| !matches!(op, Instruction::Cache))
14236            .collect();
14237
14238        assert!(
14239            ops.iter()
14240                .any(|op| matches!(op, Instruction::LoadConst { .. })),
14241            "public ast.Constant(tuple(...)) must use CPython Constant_kind LOAD_CONST path, got {ops:?}"
14242        );
14243        assert!(
14244            !ops.iter()
14245                .any(|op| matches!(op, Instruction::BuildTuple { .. })),
14246            "public ast.Constant(tuple(...)) must not compile as a tuple display, got {ops:?}"
14247        );
14248        assert!(
14249            code.constants.iter().any(|constant| matches!(
14250                constant,
14251                ConstantData::Tuple { elements }
14252                    if matches!(
14253                        elements.as_slice(),
14254                        [ConstantData::Integer { value }] if *value == BigInt::from(1u8)
14255                    )
14256            )),
14257            "missing tuple constant in code constants"
14258        );
14259    }
14260
14261    #[test]
14262    fn ast_constant_slice_bound_uses_cpython_constant_slice_path() {
14263        let mut lower = frozenset_call_expr();
14264        set_ast_constant(
14265            &mut lower,
14266            ConstantData::Integer {
14267                value: BigInt::from(1u8),
14268            },
14269        );
14270        let expr = ast::Expr::Subscript(ast::ExprSubscript {
14271            node_index: ast::AtomicNodeIndex::NONE,
14272            range: TextRange::default(),
14273            value: Box::new(ast::Expr::Name(ast::ExprName {
14274                node_index: ast::AtomicNodeIndex::NONE,
14275                range: TextRange::default(),
14276                id: ast::name::Name::new_static("obj"),
14277                ctx: ast::ExprContext::Load,
14278            })),
14279            slice: Box::new(ast::Expr::Slice(ast::ExprSlice {
14280                node_index: ast::AtomicNodeIndex::NONE,
14281                range: TextRange::default(),
14282                lower: Some(Box::new(lower)),
14283                upper: Some(Box::new(ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
14284                    node_index: ast::AtomicNodeIndex::NONE,
14285                    range: TextRange::default(),
14286                    value: ast::Number::Int(ast::Int::ZERO),
14287                }))),
14288                step: None,
14289            })),
14290            ctx: ast::ExprContext::Load,
14291        });
14292        let code = compile_eval_ast_with_options(expr, CompileOpts::default());
14293        let ops: Vec<_> = code
14294            .instructions
14295            .iter()
14296            .map(|unit| unit.op)
14297            .filter(|op| !matches!(op, Instruction::Cache))
14298            .collect();
14299        assert!(
14300            !ops.iter().any(|op| matches!(
14301                op,
14302                Instruction::BinarySlice | Instruction::BuildSlice { .. }
14303            )),
14304            "public ast.Constant slice bound must follow CPython Constant_kind folded slice path, got {ops:?}"
14305        );
14306        assert!(
14307            code.constants.iter().any(|constant| matches!(
14308                constant,
14309                ConstantData::Slice { elements }
14310                    if matches!(
14311                        elements.as_ref(),
14312                        [
14313                            ConstantData::Integer { value },
14314                            ConstantData::Integer { .. },
14315                            ConstantData::None,
14316                        ] if *value == BigInt::from(1u8)
14317                    )
14318            )),
14319            "missing folded slice constant for public ast.Constant bound"
14320        );
14321    }
14322
14323    #[test]
14324    fn match_pattern_errors_use_cpython_sequence_messages() {
14325        let many_names = (0..256)
14326            .map(|i| format!("a{i}"))
14327            .collect::<Vec<_>>()
14328            .join(", ");
14329        let too_many = format!(
14330            "\
14331match x:
14332    case [{many_names}, *rest]:
14333        pass
14334"
14335        );
14336        assert_eq!(
14337            compile_exec_error_message(&too_many),
14338            "too many expressions in star-unpacking sequence pattern"
14339        );
14340
14341        assert_eq!(
14342            compile_exec_error_message(
14343                "\
14344match x:
14345    case [*a, *b]:
14346        pass
14347"
14348            ),
14349            "multiple starred names in sequence pattern"
14350        );
14351
14352        assert_eq!(
14353            compile_exec_unchecked_error_message(
14354                "\
14355match x:
14356    case {**_}:
14357        pass
14358"
14359            ),
14360            "invalid syntax"
14361        );
14362    }
14363
14364    #[test]
14365    fn match_mapping_duplicate_literal_keys_use_cpython_equality() {
14366        for (source, expected) in [
14367            (
14368                "\
14369match x:
14370    case {1: a, True: b}:
14371        pass
14372",
14373                "mapping pattern checks duplicate key (True)",
14374            ),
14375            (
14376                "\
14377match x:
14378    case {1: a, 1.0: b}:
14379        pass
14380",
14381                "mapping pattern checks duplicate key (1.0)",
14382            ),
14383            (
14384                "\
14385match x:
14386    case {0.0: a, -0.0: b}:
14387        pass
14388",
14389                "mapping pattern checks duplicate key (-0.0)",
14390            ),
14391            (
14392                "\
14393match x:
14394    case {9007199254740992: a, 9007199254740992.0: b}:
14395        pass
14396",
14397                "mapping pattern checks duplicate key (9007199254740992.0)",
14398            ),
14399            (
14400                "\
14401match x:
14402    case {-9007199254740992: a, -9007199254740992.0: b}:
14403        pass
14404",
14405                "mapping pattern checks duplicate key (-9007199254740992.0)",
14406            ),
14407            (
14408                "\
14409match x:
14410    case {1 + 0j: a, 1: b}:
14411        pass
14412",
14413                "mapping pattern checks duplicate key (1)",
14414            ),
14415            (
14416                "\
14417match x:
14418    case {1: a, 1 + 0j: b}:
14419        pass
14420",
14421                "mapping pattern checks duplicate key ((1+0j))",
14422            ),
14423            (
14424                "\
14425match x:
14426    case {0j: a, -0.0: b}:
14427        pass
14428",
14429                "mapping pattern checks duplicate key (-0.0)",
14430            ),
14431        ] {
14432            assert_eq!(compile_exec_error_message(source), expected);
14433        }
14434    }
14435
14436    #[test]
14437    fn match_mapping_accepts_folded_literal_keys_like_cpython() {
14438        compile_exec(
14439            "\
14440def f(x):
14441    match x:
14442        case {-1: a, 1 + 0j: b}:
14443            return a, b
14444        case {9007199254740993: a, 9007199254740992.0: b}:
14445            return a, b
14446        case {-9007199254740993: a, -9007199254740992.0: b}:
14447            return a, b
14448        case {1 + 1j: a, 1: b}:
14449            return a, b
14450        case _:
14451            return None
14452",
14453        );
14454    }
14455
14456    #[test]
14457    fn match_mapping_accepts_public_ast_constant_keys_like_cpython() {
14458        let source = "\
14459match x:
14460    case {'a': _, b'b': _, 2: _, 1.5: _, 1j: _, True: _, None: _}:
14461        pass
14462";
14463        let source_file = SourceFileBuilder::new("source_path", source).finish();
14464        let parsed = ruff_python_parser::parse(
14465            source_file.source_text(),
14466            ruff_python_parser::Mode::Module.into(),
14467        )
14468        .unwrap();
14469        let mut ast = parsed.into_syntax();
14470        let ast::Mod::Module(module) = &mut ast else {
14471            unreachable!();
14472        };
14473        let ast::Stmt::Match(match_stmt) = &mut module.body[0] else {
14474            unreachable!();
14475        };
14476        let ast::Pattern::MatchMapping(mapping) = &mut match_stmt.cases[0].pattern else {
14477            unreachable!();
14478        };
14479
14480        for (key, value) in mapping.keys.iter_mut().zip([
14481            ast::ConstantValue::Str("a".into()),
14482            ast::ConstantValue::Bytes(vec![b'b'].into_boxed_slice()),
14483            ast::ConstantValue::Integer("2".into()),
14484            ast::ConstantValue::Float(1.5),
14485            ast::ConstantValue::Complex {
14486                real: 0.0,
14487                imag: 1.0,
14488            },
14489            ast::ConstantValue::Boolean(true),
14490            ast::ConstantValue::None,
14491        ]) {
14492            let range = key.range();
14493            *key = ast::Expr::Constant(ast::ExprConstant {
14494                node_index: ast::AtomicNodeIndex::NONE,
14495                range,
14496                value,
14497                kind: None,
14498                invalid_type: None,
14499            });
14500        }
14501
14502        compile_top(ast, source_file, Mode::Exec, CompileOpts::default()).unwrap();
14503    }
14504
14505    #[test]
14506    fn match_literal_binop_folding_uses_cpython_complex_shape() {
14507        assert!(
14508            Compiler::try_fold_match_pattern_binop(
14509                ast::Operator::Add,
14510                &ConstantData::Integer {
14511                    value: BigInt::from(1)
14512                },
14513                &ConstantData::Integer {
14514                    value: BigInt::from(2)
14515                },
14516            )
14517            .is_none()
14518        );
14519        assert!(
14520            Compiler::try_fold_match_pattern_binop(
14521                ast::Operator::Add,
14522                &ConstantData::Float { value: 1.0 },
14523                &ConstantData::Float { value: 2.0 },
14524            )
14525            .is_none()
14526        );
14527        assert!(matches!(
14528            Compiler::try_fold_match_pattern_binop(
14529                ast::Operator::Add,
14530                &ConstantData::Integer {
14531                    value: BigInt::from(1)
14532                },
14533                &ConstantData::Complex {
14534                    value: Complex::new(0.0, 2.0)
14535                },
14536            ),
14537            Some(ConstantData::Complex { value }) if value == Complex::new(1.0, 2.0)
14538        ));
14539        assert!(matches!(
14540            Compiler::try_fold_match_pattern_binop(
14541                ast::Operator::Sub,
14542                &ConstantData::Float { value: 1.5 },
14543                &ConstantData::Complex {
14544                    value: Complex::new(0.0, 2.0)
14545                },
14546            ),
14547            Some(ConstantData::Complex { value }) if value == Complex::new(1.5, -2.0)
14548        ));
14549    }
14550
14551    #[test]
14552    fn match_literal_patterns_reject_unexpected_constants_like_cpython() {
14553        assert!(Compiler::is_unexpected_match_literal_constant(
14554            &ast::ExprEllipsisLiteral {
14555                range: TextRange::default(),
14556                node_index: ast::AtomicNodeIndex::NONE,
14557            }
14558            .into()
14559        ));
14560    }
14561
14562    #[test]
14563    fn unpack_ex_allows_large_after_count_like_cpython() {
14564        let suffix = (0..256)
14565            .map(|i| format!("a{i}"))
14566            .collect::<Vec<_>>()
14567            .join(", ");
14568        let code = compile_exec(&format!(
14569            "\
14570def assignment(values):
14571    *rest, {suffix} = values
14572    return a255
14573
14574def pattern(values):
14575    match values:
14576        case [*rest, {suffix}]:
14577            return a255
14578        case _:
14579            return None
14580"
14581        ));
14582
14583        let assignment = find_code(&code, "assignment").expect("missing assignment code");
14584        assert_eq!(
14585            full_opargs_for(assignment, |op| matches!(op, Instruction::UnpackEx { .. })),
14586            vec![256 << 8]
14587        );
14588
14589        let pattern = find_code(&code, "pattern").expect("missing pattern code");
14590        assert_eq!(
14591            full_opargs_for(pattern, |op| matches!(op, Instruction::UnpackEx { .. })),
14592            vec![256 << 8]
14593        );
14594    }
14595
14596    #[test]
14597    fn match_irrefutable_pattern_errors_use_cpython_messages() {
14598        assert_eq!(
14599            compile_exec_error_message(
14600                "\
14601match x:
14602    case y | 1:
14603        pass
14604"
14605            ),
14606            "name capture 'y' makes remaining patterns unreachable"
14607        );
14608
14609        assert_eq!(
14610            compile_exec_error_message(
14611                "\
14612match x:
14613    case _ | 1:
14614        pass
14615"
14616            ),
14617            "wildcard makes remaining patterns unreachable"
14618        );
14619    }
14620
14621    #[test]
14622    fn empty_module_implicit_return_inherits_resume_location_like_cpython() {
14623        let code = compile_exec("");
14624        // codegen emits the implicit LOAD_CONST/RETURN_VALUE with
14625        // NO_LOCATION, then flowgraph.c::propagate_line_numbers() propagates
14626        // the module RESUME location, whose line is 0.
14627        assert_eq!(code.linetable.as_ref(), &[0xf2, 0x03, 0x01, 0x01, 0x01]);
14628    }
14629
14630    #[test]
14631    fn module_docstring_load_uses_doc_location_like_cpython() {
14632        let code = compile_exec(
14633            "\
14634\"doc\"
14635x = 1
14636",
14637        );
14638
14639        // codegen_body() emits the docstring LOAD_CONST at the
14640        // string expression location, then emits STORE_NAME __doc__ with
14641        // NO_LOCATION.
14642        assert_eq!(
14643            code.linetable.as_ref(),
14644            &[
14645                0xf0, 0x03, 0x01, 0x01, 0x01, 0xd9, 0x00, 0x05, 0xd8, 0x04, 0x05, 0x82, 0x01,
14646            ],
14647        );
14648    }
14649
14650    #[test]
14651    fn redundant_nop_location_copies_full_location_like_cpython() {
14652        let code = compile_exec(
14653            "\
14654def f(x, y, z):
14655    while x:
14656        if y:
14657            pass
14658        elif z:
14659            if y < 0:
14660                return y
14661            if z:
14662                y = y + 1
14663        elif y:
14664            return 1
14665    return -1
14666",
14667        );
14668        let f = find_code(&code, "f").expect("missing function code");
14669        assert_eq!(
14670            f.linetable.as_ref(),
14671            &[
14672                0x80, 0x00, 0xdf, 0x0a, 0x0b, 0xdf, 0x0b, 0x0c, 0xd9, 0x0c, 0x10, 0xdf, 0x0d, 0x0e,
14673                0xd8, 0x0f, 0x10, 0x90, 0x31, 0x8c, 0x75, 0xd8, 0x17, 0x18, 0x90, 0x08, 0xdf, 0x0f,
14674                0x10, 0xd8, 0x14, 0x15, 0x98, 0x01, 0x95, 0x45, 0x92, 0x01, 0xf1, 0x03, 0x00, 0x10,
14675                0x11, 0xe7, 0x0d, 0x0e, 0x89, 0x51, 0xd9, 0x13, 0x14, 0xd8, 0x0b, 0x0d, 0x80, 0x49,
14676            ],
14677            "CPython basicblock_remove_redundant_nops() copies the full NOP location into a following no-location jump"
14678        );
14679    }
14680
14681    fn scan_program_symbol_table(source: &str) -> SymbolTable {
14682        let source_file = SourceFileBuilder::new("source_path", source).finish();
14683        let parsed = ruff_python_parser::parse(
14684            source_file.source_text(),
14685            ruff_python_parser::Mode::Module.into(),
14686        )
14687        .unwrap();
14688        let ast = parsed.into_syntax();
14689        let ast = match ast {
14690            ruff_python_ast::Mod::Module(stmts) => stmts,
14691            _ => unreachable!(),
14692        };
14693        SymbolTable::scan_program(&ast, source_file)
14694            .map_err(|e| e.into_codegen_error("source_path".to_owned()))
14695            .unwrap()
14696    }
14697
14698    fn find_symbol_table<'a>(table: &'a SymbolTable, name: &str) -> Option<&'a SymbolTable> {
14699        if table.name == name {
14700            return Some(table);
14701        }
14702        table
14703            .sub_tables
14704            .iter()
14705            .find_map(|sub_table| find_symbol_table(sub_table, name))
14706    }
14707
14708    fn compile_exec_late_cfg_trace(source: &str) -> Vec<(String, String)> {
14709        let opts = CompileOpts::default();
14710        let source_file = SourceFileBuilder::new("source_path", source).finish();
14711        let parsed = ruff_python_parser::parse(
14712            source_file.source_text(),
14713            ruff_python_parser::Mode::Module.into(),
14714        )
14715        .unwrap();
14716        let ast = parsed.into_syntax();
14717        let ast = match ast {
14718            ruff_python_ast::Mod::Module(stmts) => stmts,
14719            _ => unreachable!(),
14720        };
14721        let symbol_table = SymbolTable::scan_program(&ast, source_file.clone())
14722            .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))
14723            .unwrap();
14724        let mut compiler =
14725            Compiler::new_with_syntax_warning_handler(opts, source_file, "<module>", None);
14726        compiler.compile_program(&ast, symbol_table).unwrap();
14727        compiler.pop_symbol_table();
14728        let stack_top = compiler.code_stack.pop().unwrap();
14729        stack_top.debug_late_cfg_trace().unwrap()
14730    }
14731
14732    fn compile_single_function_late_cfg_trace(
14733        source: &str,
14734        function_name: &str,
14735    ) -> Vec<(String, String)> {
14736        let opts = CompileOpts::default();
14737        let source_file = SourceFileBuilder::new("source_path", source).finish();
14738        let parsed = ruff_python_parser::parse(
14739            source_file.source_text(),
14740            ruff_python_parser::Mode::Module.into(),
14741        )
14742        .unwrap();
14743        let ast = parsed.into_syntax();
14744        let ast = match ast {
14745            ruff_python_ast::Mod::Module(stmts) => stmts,
14746            _ => unreachable!(),
14747        };
14748        let mut symbol_table = SymbolTable::scan_program(&ast, source_file.clone())
14749            .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))
14750            .unwrap();
14751        let function = ast
14752            .body
14753            .iter()
14754            .find_map(|stmt| match stmt {
14755                ast::Stmt::FunctionDef(f) if f.name.as_str() == function_name => Some(f),
14756                _ => None,
14757            })
14758            .unwrap_or_else(|| panic!("missing function {function_name}"));
14759        symbol_table.next_sub_table = symbol_table
14760            .sub_tables
14761            .iter()
14762            .position(|table| table.name == function_name)
14763            .unwrap_or_else(|| panic!("missing symbol table for {function_name}"));
14764
14765        let name = &function.name;
14766        let parameters = &function.parameters;
14767        let body = &function.body;
14768        let is_async = function.is_async;
14769        let range = function.range();
14770
14771        let mut compiler =
14772            Compiler::new_with_syntax_warning_handler(opts, source_file, "<module>", None);
14773        compiler.future_annotations = symbol_table.future_annotations;
14774        compiler.symbol_table_stack.push(symbol_table);
14775        compiler.set_source_range(range);
14776        compiler.enter_function(name.as_str(), parameters).unwrap();
14777        compiler
14778            .current_code_info()
14779            .flags
14780            .set(bytecode::CodeFlags::COROUTINE, is_async);
14781
14782        let prev_ctx = compiler.ctx;
14783        compiler.ctx = CompileContext {
14784            in_class: prev_ctx.in_class,
14785            func: if is_async {
14786                FunctionContext::AsyncFunction
14787            } else {
14788                FunctionContext::Function
14789            },
14790            in_async_scope: is_async,
14791        };
14792        compiler.set_qualname();
14793        let (_, body) = split_doc(body, &compiler.opts);
14794        let start_label = compiler.use_cpython_function_start_label();
14795        let is_gen = is_async || compiler.current_symbol_table().is_generator;
14796        let stop_iteration_block = if is_gen {
14797            let handler_block = compiler.new_block();
14798            compiler.insert_cpython_stopiteration_setup_cleanup(handler_block);
14799            compiler
14800                .push_fblock_labels(
14801                    FBlockType::StopIteration,
14802                    start_label,
14803                    ir::InstructionSequenceLabel::NO_LABEL,
14804                    FBlockDatum::None,
14805                )
14806                .unwrap();
14807            Some(handler_block)
14808        } else {
14809            None
14810        };
14811        compiler.compile_statements(body).unwrap();
14812        match body.last() {
14813            Some(ast::Stmt::Return(_)) => {}
14814            _ => compiler.emit_return_const_no_location(ConstantData::None),
14815        }
14816        if compiler.current_code_info().metadata.consts.is_empty() {
14817            compiler.arg_constant(ConstantData::None);
14818        }
14819        if let Some(handler_block) = stop_iteration_block {
14820            compiler.pop_fblock_label(FBlockType::StopIteration, start_label);
14821            compiler.use_cpython_label_block(handler_block);
14822            emit!(
14823                compiler,
14824                Instruction::CallIntrinsic1 {
14825                    func: oparg::IntrinsicFunction1::StopIterationError
14826                }
14827            );
14828            compiler.set_no_location();
14829            emit!(compiler, Instruction::Reraise { depth: 1u32 });
14830            compiler.set_no_location();
14831        }
14832
14833        compiler.pop_symbol_table();
14834        let stack_top = compiler.code_stack.pop().unwrap();
14835        stack_top.debug_late_cfg_trace().unwrap()
14836    }
14837
14838    #[test]
14839    fn try_else_nested_try_const_list_keeps_setup_finally_nop() {
14840        let trace = compile_single_function_late_cfg_trace(
14841            r#"
14842def f(arch):
14843    try:
14844        [arch, *_] = g()
14845    except OSError:
14846        pass
14847    else:
14848        try:
14849            arch = ['x86', 'MIPS', 'Alpha', 'PowerPC', None,
14850                    'ARM', 'ia64', None, None,
14851                    'AMD64', None, None, 'ARM64',
14852            ][int(arch)]
14853        except (ValueError, IndexError):
14854            pass
14855        else:
14856            if arch:
14857                return arch
14858"#,
14859            "f",
14860        );
14861        let (_, dump) = trace
14862            .iter()
14863            .find(|(label, _)| label == "after_convert_pseudo_ops")
14864            .expect("missing convert_pseudo_ops trace");
14865        assert!(
14866            dump.contains("[disp=8:9 raw=8:9-17:28 override=None] Real(Nop)"),
14867            "SETUP_FINALLY should survive as a line-bearing NOP like CPython"
14868        );
14869        assert!(
14870            dump.contains("[disp=9:20 raw=9:20-12:14 override=None] Real(BuildList"),
14871            "CPython optimize_lists_and_sets() restores the literal location to BUILD_LIST"
14872        );
14873    }
14874
14875    #[test]
14876    fn for_try_except_break_keeps_cpython_if_layout() {
14877        let code = compile_exec(
14878            "\
14879def f(support, func, value):
14880    for _ in support.sleeping_retry(support.SHORT_TIMEOUT):
14881        try:
14882            if func() == value:
14883                break
14884        except NotImplementedError:
14885            break
14886    sink(value)
14887",
14888        );
14889        let f = find_code(&code, "f").expect("missing f code");
14890        let ops = f
14891            .instructions
14892            .iter()
14893            .map(|unit| unit.op)
14894            .filter(|op| !matches!(op, Instruction::Cache))
14895            .collect::<Vec<_>>();
14896        let cond = ops
14897            .iter()
14898            .position(|op| matches!(op, Instruction::PopJumpIfFalse { .. }))
14899            .expect("missing CPython-style false jump for if/break");
14900        assert!(
14901            matches!(
14902                ops.get(cond..cond + 5),
14903                Some([
14904                    Instruction::PopJumpIfFalse { .. },
14905                    Instruction::NotTaken,
14906                    Instruction::PopTop,
14907                    Instruction::JumpForward { .. },
14908                    Instruction::JumpBackward { .. },
14909                ])
14910            ),
14911            "CPython codegen_if() keeps the break cleanup in the true-body fallthrough before the loop backedge, got ops={ops:?}"
14912        );
14913    }
14914
14915    #[test]
14916    fn try_else_loop_break_keeps_body_before_protected_backedge() {
14917        let code = compile_exec(
14918            "\
14919def f(input):
14920    while 1:
14921        try:
14922            pass
14923        except IndexError:
14924            break
14925        else:
14926            key = None
14927            while key is None:
14928                key = input()
14929                if key not in ('', 'q'):
14930                    key = None
14931            if key == 'q':
14932                break
14933",
14934        );
14935        let f = find_code(&code, "f").expect("missing f code");
14936        let ops = f
14937            .instructions
14938            .iter()
14939            .map(|unit| unit.op)
14940            .filter(|op| !matches!(op, Instruction::Cache))
14941            .collect::<Vec<_>>();
14942        assert!(
14943            ops.windows(6).any(|window| {
14944                matches!(
14945                    window,
14946                    [
14947                        Instruction::CompareOp { .. },
14948                        Instruction::PopJumpIfFalse { .. },
14949                        Instruction::NotTaken,
14950                        Instruction::LoadConst { .. },
14951                        Instruction::ReturnValue,
14952                        Instruction::JumpBackward { .. },
14953                    ]
14954                )
14955            }),
14956            "CPython codegen_if() keeps the break body before the false backedge into the protected try/except loop, got ops={ops:?}"
14957        );
14958    }
14959
14960    #[test]
14961    fn loop_nested_if_tail_keeps_duplicate_jump_back_label() {
14962        let code = compile_exec(
14963            "\
14964def f(value, digits):
14965    for digit in value:
14966        if isinstance(digit, int) and 0 <= digit <= 9:
14967            if digits or digit != 0:
14968                digits.append(digit)
14969        else:
14970            raise ValueError('x')
14971    return digits
14972",
14973        );
14974        let f = find_code(&code, "f").expect("missing f code");
14975        let ops = f
14976            .instructions
14977            .iter()
14978            .map(|unit| unit.op)
14979            .filter(|op| !matches!(op, Instruction::Cache | Instruction::Nop))
14980            .collect::<Vec<_>>();
14981        assert!(
14982            ops.windows(4).any(|window| {
14983                matches!(
14984                    window,
14985                    [
14986                        Instruction::PopTop,
14987                        Instruction::JumpBackward { .. },
14988                        Instruction::JumpBackward { .. },
14989                        Instruction::LoadGlobal { .. },
14990                    ]
14991                )
14992            }),
14993            "CPython codegen_if() leaves a distinct no-location end label before the loop else/raise path, got ops={ops:?}"
14994        );
14995    }
14996
14997    #[test]
14998    fn match_for_break_threads_empty_end_label_to_outer_backedge() {
14999        let code = compile_exec(
15000            "\
15001def f(items, T):
15002    for st in items:
15003        match st.type:
15004            case T.TYPE:
15005                for c in st.children:
15006                    if c.name == st.name:
15007                        x = 1
15008                        break
15009    return x
15010",
15011        );
15012        let f = find_code(&code, "f").expect("missing f code");
15013        let ops = f
15014            .instructions
15015            .iter()
15016            .map(|unit| unit.op)
15017            .filter(|op| !matches!(op, Instruction::Cache | Instruction::Nop))
15018            .collect::<Vec<_>>();
15019        assert!(
15020            ops.windows(5).any(|window| {
15021                matches!(
15022                    window,
15023                    [
15024                        Instruction::PopTop,
15025                        Instruction::JumpBackward { .. },
15026                        Instruction::EndFor,
15027                        Instruction::PopIter,
15028                        Instruction::JumpBackward { .. },
15029                    ]
15030                )
15031            }),
15032            "CPython codegen_break() threads the match-case inner for break through the empty end label to the outer loop backedge, got ops={ops:?}"
15033        );
15034    }
15035
15036    #[test]
15037    fn match_constant_guard_keeps_cpython_guard_nop_before_subject_pop() {
15038        let code = compile_exec(
15039            "\
15040def f(self):
15041    x = 0
15042    match x:
15043        case 0 if True:
15044            y = 0
15045        case 0 if True:
15046            y = 1
15047    self.assertEqual(x, 0)
15048    self.assertEqual(y, 0)
15049",
15050        );
15051        let f = find_code(&code, "f").expect("missing f code");
15052        let ops = f
15053            .instructions
15054            .iter()
15055            .map(|unit| unit.op)
15056            .filter(|op| !matches!(op, Instruction::Cache))
15057            .collect::<Vec<_>>();
15058        assert!(
15059            ops.windows(7).any(|window| {
15060                matches!(
15061                    window,
15062                    [
15063                        Instruction::CompareOp { .. },
15064                        Instruction::PopJumpIfFalse { .. },
15065                        Instruction::NotTaken,
15066                        Instruction::Nop,
15067                        Instruction::PopTop,
15068                        Instruction::LoadSmallInt { .. } | Instruction::LoadConst { .. },
15069                        Instruction::StoreFast { .. },
15070                    ]
15071                )
15072            }),
15073            "CPython codegen_match_inner() emits the guard through codegen_jump_if(), and flowgraph.c keeps the folded constant-guard NOP in a separate success block before POP_TOP, got ops={ops:?}"
15074        );
15075    }
15076
15077    #[test]
15078    fn match_or_default_tail_uses_cpython_load_fast_borrow() {
15079        let code = compile_exec(
15080            "\
15081def f(format, annotationlib, cls, annotation_fields, return_type, MISSING):
15082    Format = annotationlib.Format
15083    match format:
15084        case Format.VALUE | Format.FORWARDREF | Format.STRING:
15085            cls_annotations = {}
15086            for base in reversed(cls.__mro__):
15087                cls_annotations.update(
15088                    annotationlib.get_annotations(base, format=format)
15089                )
15090            new_annotations = {}
15091            for k in annotation_fields:
15092                try:
15093                    new_annotations[k] = cls_annotations[k]
15094                except KeyError:
15095                    pass
15096            if return_type is not MISSING:
15097                if format == Format.STRING:
15098                    new_annotations['return'] = annotationlib.type_repr(return_type)
15099                else:
15100                    new_annotations['return'] = return_type
15101            return new_annotations
15102        case _:
15103            raise NotImplementedError(format)
15104",
15105        );
15106        let f = find_code(&code, "f").expect("missing f code");
15107        let ops = f
15108            .instructions
15109            .iter()
15110            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15111            .collect::<Vec<_>>();
15112        let raise = ops
15113            .iter()
15114            .position(|unit| matches!(unit.op, Instruction::RaiseVarargs { .. }))
15115            .expect("missing default raise");
15116        let load_format = &ops[raise - 2];
15117        let Instruction::LoadFastBorrow { var_num } = load_format.op else {
15118            panic!(
15119                "CPython codegen_match_inner() emits the default case without a load-fast barrier, so optimize_load_fast() borrows the raise argument; got ops={ops:?}"
15120            );
15121        };
15122        let arg = OpArg::new(u32::from(u8::from(load_format.arg)));
15123        assert_eq!(f.varnames[usize::from(var_num.get(arg))], "format");
15124    }
15125
15126    #[test]
15127    fn preceding_match_or_default_tail_keeps_cpython_strong_load_fast() {
15128        let code = compile_exec(
15129            "\
15130def f(format):
15131    match format:
15132        case _lazy_annotationlib.Format.VALUE | _lazy_annotationlib.Format.FORWARDREF:
15133            return checked_types
15134        case _lazy_annotationlib.Format.STRING:
15135            return _lazy_annotationlib.annotations_to_string(types)
15136        case _:
15137            raise NotImplementedError(format)
15138",
15139        );
15140        let f = find_code(&code, "f").expect("missing f code");
15141        let ops = f
15142            .instructions
15143            .iter()
15144            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15145            .collect::<Vec<_>>();
15146        let raise = ops
15147            .iter()
15148            .position(|unit| matches!(unit.op, Instruction::RaiseVarargs { .. }))
15149            .expect("missing default raise");
15150        let load_format = &ops[raise - 2];
15151        let Instruction::LoadFast { var_num } = load_format.op else {
15152            panic!(
15153                "CPython keeps the default raise argument strong when an earlier copied OR-pattern precedes the final non-default case; got ops={ops:?}"
15154            );
15155        };
15156        let arg = OpArg::new(u32::from(u8::from(load_format.arg)));
15157        assert_eq!(f.varnames[usize::from(var_num.get(arg))], "format");
15158    }
15159
15160    #[test]
15161    fn try_else_after_nested_try_except_exit_keeps_cpython_strong_load_fast() {
15162        let code = compile_exec(
15163            "\
15164def f(self):
15165    try:
15166        try:
15167            1 / 0
15168        except ZeroDivisionError:
15169            raise OSError
15170    except OSError as e:
15171        self.assertIsInstance(e.__context__, ZeroDivisionError)
15172    else:
15173        self.fail('No exception raised')
15174",
15175        );
15176        let f = find_code(&code, "f").expect("missing f code");
15177        let ops = f
15178            .instructions
15179            .iter()
15180            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15181            .collect::<Vec<_>>();
15182        let fail = ops
15183            .iter()
15184            .position(|unit| match unit.op {
15185                Instruction::LoadAttr { namei } => {
15186                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
15187                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "fail"
15188                }
15189                _ => false,
15190            })
15191            .expect("missing fail load");
15192        assert!(
15193            matches!(ops[fail - 1].op, Instruction::LoadFast { .. }),
15194            "CPython codegen_try_except() keeps the orelse entry after a nested try/except end label strong, got ops={ops:?}"
15195        );
15196    }
15197
15198    #[test]
15199    fn try_after_inherited_try_barrier_keeps_successor_loads_strong() {
15200        let code = compile_exec(
15201            "\
15202def f(self, x):
15203    try:
15204        1 / 0
15205    except EOFError:
15206        pass
15207    except TypeError as msg:
15208        pass
15209    except:
15210        pass
15211    else:
15212        pass
15213    try:
15214        x
15215    except (EOFError, TypeError, ZeroDivisionError):
15216        pass
15217    with self.assertRaises(SyntaxError):
15218        pass
15219
15220def g(self):
15221    try:
15222        1 / 0
15223    except:
15224        pass
15225    try:
15226        1 / 0
15227    except (EOFError, TypeError, ZeroDivisionError):
15228        pass
15229    with self.assertRaises(SyntaxError):
15230        pass
15231",
15232        );
15233        let f = find_code(&code, "f").expect("missing f code");
15234        let x_loads = load_fast_ops_for_var(f, "x");
15235        assert!(
15236            x_loads
15237                .iter()
15238                .all(|op| matches!(op, Instruction::LoadFast { .. })),
15239            "CPython codegen_try_except() reaches the first try end through USE_LABEL(end); flowgraph.c keeps that barrier state through the following try end, got x loads {x_loads:?}"
15240        );
15241        let self_loads = load_fast_ops_for_var(f, "self");
15242        assert!(
15243            self_loads
15244                .iter()
15245                .all(|op| matches!(op, Instruction::LoadFast { .. })),
15246            "CPython keeps successor with-statement loads strong after a try that started from an inherited try barrier, got self loads {self_loads:?}"
15247        );
15248        let g = find_code(&code, "g").expect("missing g code");
15249        let g_self_loads = load_fast_ops_for_var(g, "self");
15250        assert!(
15251            g_self_loads
15252                .iter()
15253                .all(|op| matches!(op, Instruction::LoadFast { .. })),
15254            "CPython keeps a bare-handler try end label as a barrier when the next statement is another try, got self loads {g_self_loads:?}"
15255        );
15256    }
15257
15258    #[test]
15259    fn loop_continue_try_before_try_else_keeps_orelse_loads_strong() {
15260        let code = compile_exec(
15261            "\
15262def f(candidate_locales, locales):
15263    for loc in candidate_locales:
15264        try:
15265            work(loc)
15266        except Error:
15267            continue
15268        encoding = getencoding()
15269        try:
15270            localeconv()
15271        except Exception as err:
15272            print(loc, encoding, type(err), err)
15273        else:
15274            locales.append(loc)
15275",
15276        );
15277        let f = find_code(&code, "f").expect("missing f code");
15278        let locales_loads = load_fast_ops_for_var(f, "locales");
15279        assert!(
15280            locales_loads
15281                .iter()
15282                .all(|op| matches!(op, Instruction::LoadFast { .. })),
15283            "CPython codegen_try_except() leaves an empty end label after a loop try whose handlers continue; optimize_load_fast() keeps the following try/else append receiver strong, got {locales_loads:?}"
15284        );
15285        let loc_loads = load_fast_ops_for_var(f, "loc");
15286        assert!(
15287            loc_loads
15288                .iter()
15289                .any(|op| matches!(op, Instruction::LoadFast { .. })),
15290            "CPython keeps the try/else append argument strong after the inherited end-label barrier, got {loc_loads:?}"
15291        );
15292    }
15293
15294    #[test]
15295    fn try_else_after_try_finally_conditional_finalbody_keeps_store_attr_loads_strong() {
15296        let code = compile_exec(
15297            "\
15298def f(self, w, pid, prev):
15299    try:
15300        try:
15301            if cond:
15302                prev = call()
15303            pid = spawn()
15304        finally:
15305            if prev is not None:
15306                reset(prev)
15307    except:
15308        close(w)
15309        raise
15310    else:
15311        self._fd = w
15312        self._pid = pid
15313    finally:
15314        close(r)
15315",
15316        );
15317        let f = find_code(&code, "f").expect("missing f code");
15318        let w_self_pairs = load_fast_pair_ops_for_vars(f, "w", "self");
15319        let pid_self_pairs = load_fast_pair_ops_for_vars(f, "pid", "self");
15320        assert!(
15321            w_self_pairs
15322                .iter()
15323                .all(|op| matches!(op, Instruction::LoadFastLoadFast { .. }))
15324                && !w_self_pairs.is_empty(),
15325            "CPython codegen_try_finally() calls codegen_try_except() inside the active finally try; the else suite starts from the inner try/finally exit label and keeps w/self strong, got {w_self_pairs:?}"
15326        );
15327        assert!(
15328            pid_self_pairs
15329                .iter()
15330                .all(|op| matches!(op, Instruction::LoadFastLoadFast { .. }))
15331                && !pid_self_pairs.is_empty(),
15332            "CPython keeps the second store-attr source pair strong in the same try/except/else/finally else suite, got {pid_self_pairs:?}"
15333        );
15334    }
15335
15336    #[test]
15337    fn try_except_end_before_following_try_keeps_protected_attr_loads_strong() {
15338        let code = compile_exec(
15339            "\
15340def f(f, dotlock=True):
15341    dotlock_done = False
15342    try:
15343        if dotlock:
15344            try:
15345                pre_lock = _create_temporary(f.name + '.lock')
15346                pre_lock.close()
15347            except OSError as e:
15348                if e.errno in (errno.EACCES, errno.EROFS):
15349                    return
15350                else:
15351                    raise
15352            try:
15353                try:
15354                    os.link(pre_lock.name, f.name + '.lock')
15355                    dotlock_done = True
15356                except (AttributeError, PermissionError):
15357                    os.rename(pre_lock.name, f.name + '.lock')
15358                    dotlock_done = True
15359                else:
15360                    os.unlink(pre_lock.name)
15361            except FileExistsError:
15362                os.remove(pre_lock.name)
15363                raise ExternalClashError('dot lock unavailable: %s' %
15364                                         f.name)
15365    except:
15366        if dotlock_done:
15367            os.remove(f.name + '.lock')
15368        raise
15369",
15370        );
15371        let f = find_code(&code, "f").expect("missing f code");
15372        let pre_lock_loads = load_fast_ops_for_var(f, "pre_lock");
15373        let strong_pre_lock_loads = pre_lock_loads
15374            .iter()
15375            .filter(|op| matches!(op, Instruction::LoadFast { .. }))
15376            .count();
15377        assert!(
15378            strong_pre_lock_loads >= 2,
15379            "CPython codegen_try_except() emits USE_LABEL(end) before the following try; flowgraph.c::optimize_load_fast() does not push fallthrough through the empty end label, so protected pre_lock attr receivers stay strong, got {pre_lock_loads:?}"
15380        );
15381        let f_loads = load_fast_ops_for_var(f, "f");
15382        assert!(
15383            f_loads
15384                .iter()
15385                .any(|op| matches!(op, Instruction::LoadFast { .. })),
15386            "CPython keeps the f.name receiver inside the following protected try strong after the preceding try/except end label, got {f_loads:?}"
15387        );
15388    }
15389
15390    #[test]
15391    fn try_except_method_probe_end_before_if_keeps_loads_strong() {
15392        let code = compile_exec(
15393            "\
15394def f(param, value=None, quote=True):
15395    if value is not None and len(value) > 0:
15396        if isinstance(value, tuple):
15397            param += '*'
15398            value = encode(value[2], value[0], value[1])
15399            return f'{param}={value}'
15400        else:
15401            try:
15402                value.encode('ascii')
15403            except UnicodeEncodeError:
15404                param += '*'
15405                value = encode(value, 'utf-8', '')
15406                return f'{param}={value}'
15407        if quote or tspecials.search(value):
15408            return f'{param}=\"{quote_value(value)}\"'
15409        else:
15410            return f'{param}={value}'
15411    else:
15412        return param
15413",
15414        );
15415        let f = find_code(&code, "f").expect("missing f code");
15416        let quote_loads = load_fast_ops_for_var(f, "quote");
15417        assert!(
15418            quote_loads
15419                .iter()
15420                .any(|op| matches!(op, Instruction::LoadFast { .. })),
15421            "CPython codegen_try_except() emits USE_LABEL(end) after a handled method probe; flowgraph.c::optimize_load_fast() leaves the following if-test local strong, got {quote_loads:?}"
15422        );
15423        let param_loads = load_fast_ops_for_var(f, "param");
15424        assert!(
15425            param_loads
15426                .iter()
15427                .any(|op| matches!(op, Instruction::LoadFast { .. })),
15428            "CPython keeps the return f-string local loads strong after the protected method-probe end label, got {param_loads:?}"
15429        );
15430        let value_loads = load_fast_ops_for_var(f, "value");
15431        assert!(
15432            value_loads
15433                .iter()
15434                .any(|op| matches!(op, Instruction::LoadFast { .. })),
15435            "CPython keeps the post-try value loads strong after the protected method-probe end label, got {value_loads:?}"
15436        );
15437    }
15438
15439    #[test]
15440    fn try_except_fallthrough_before_return_call_keeps_borrow() {
15441        let code = compile_exec(
15442            "\
15443def f(obj, lock, ctx, cls, class_cache):
15444    if cond1(obj):
15445        return Synchronized(obj, lock, ctx)
15446    elif cond2(obj):
15447        return SynchronizedArray(obj, lock, ctx)
15448    else:
15449        try:
15450            scls = class_cache[cls]
15451        except KeyError:
15452            scls = make_synchronized(cls)
15453        return scls(obj, lock, ctx)
15454",
15455        );
15456        let f = find_code(&code, "f").expect("missing f code");
15457        let scls_loads = load_fast_ops_for_var(f, "scls");
15458        assert!(
15459            scls_loads
15460                .iter()
15461                .all(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
15462            "CPython codegen_try_except() emits USE_LABEL(end) and the following return call into the same basic block; flowgraph.c borrows the callable local, got {scls_loads:?}"
15463        );
15464        let obj_lock_pairs = load_fast_pair_ops_for_vars(f, "obj", "lock");
15465        assert!(
15466            obj_lock_pairs
15467                .iter()
15468                .all(|op| matches!(op, Instruction::LoadFastBorrowLoadFastBorrow { .. })),
15469            "CPython flowgraph.c borrows the return call argument pair after a typed fallthrough handler, got {obj_lock_pairs:?}"
15470        );
15471        let ctx_loads = load_fast_ops_for_var(f, "ctx");
15472        assert!(
15473            ctx_loads
15474                .iter()
15475                .all(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
15476            "CPython flowgraph.c borrows the trailing return call argument after the try-end label, got {ctx_loads:?}"
15477        );
15478    }
15479
15480    #[test]
15481    fn try_except_comprehension_handler_before_return_call_keeps_borrow() {
15482        let code = compile_exec(
15483            "\
15484def f(obj, lock, ctx):
15485    assert not isinstance(obj, SynchronizedBase), 'object already synchronized'
15486    ctx = ctx or get_context()
15487
15488    if isinstance(obj, ctypes._SimpleCData):
15489        return Synchronized(obj, lock, ctx)
15490    elif isinstance(obj, ctypes.Array):
15491        if obj._type_ is ctypes.c_char:
15492            return SynchronizedString(obj, lock, ctx)
15493        return SynchronizedArray(obj, lock, ctx)
15494    else:
15495        cls = type(obj)
15496        try:
15497            scls = class_cache[cls]
15498        except KeyError:
15499            names = [field[0] for field in cls._fields_]
15500            d = {name: make_property(name) for name in names}
15501            classname = 'Synchronized' + cls.__name__
15502            scls = class_cache[cls] = type(classname, (SynchronizedBase,), d)
15503        return scls(obj, lock, ctx)
15504",
15505        );
15506        let f = find_code(&code, "f").expect("missing f code");
15507        let scls_loads = load_fast_ops_for_var(f, "scls");
15508        assert!(
15509            scls_loads
15510                .iter()
15511                .any(|op| matches!(op, Instruction::LoadFastBorrow { .. }))
15512                && scls_loads
15513                    .iter()
15514                    .all(|op| !matches!(op, Instruction::LoadFast { .. })),
15515            "CPython codegen_try_except() keeps the return call in the end-label block even when the handler contains comprehensions, got {scls_loads:?}"
15516        );
15517        let obj_lock_pairs = load_fast_pair_ops_for_vars(f, "obj", "lock");
15518        assert!(
15519            obj_lock_pairs
15520                .iter()
15521                .any(|op| matches!(op, Instruction::LoadFastBorrowLoadFastBorrow { .. }))
15522                && obj_lock_pairs
15523                    .iter()
15524                    .all(|op| !matches!(op, Instruction::LoadFastLoadFast { .. })),
15525            "CPython flowgraph.c borrows the return call argument pair after the try-end label and cold handler reordering, got {obj_lock_pairs:?}"
15526        );
15527        let instructions = non_cache_instructions(f).collect::<Vec<_>>();
15528        let ctx_idx = varname_index(f, "ctx");
15529        let has_borrowed_return_tail = instructions.windows(6).any(|window| {
15530            matches!(window[0].op, Instruction::LoadFastBorrow { .. })
15531                && matches!(window[1].op, Instruction::PushNull)
15532                && matches!(
15533                    window[2].op,
15534                    Instruction::LoadFastBorrowLoadFastBorrow { .. }
15535                )
15536                && matches!(window[3].op, Instruction::LoadFastBorrow { .. })
15537                && {
15538                    let Instruction::LoadFastBorrow { var_num } = window[3].op else {
15539                        return false;
15540                    };
15541                    usize::from(var_num.get(OpArg::new(u32::from(u8::from(window[3].arg)))))
15542                        == ctx_idx
15543                }
15544                && matches!(window[4].op, Instruction::Call { .. })
15545                && matches!(window[5].op, Instruction::ReturnValue)
15546        });
15547        assert!(
15548            has_borrowed_return_tail,
15549            "CPython flowgraph.c borrows the full final return-call tail after the protected try body, got instructions={instructions:?}"
15550        );
15551    }
15552
15553    #[test]
15554    fn try_finally_closed_conditional_exit_allows_cpython_borrow() {
15555        let code = compile_exec(
15556            "\
15557def f(self, os, tempfile, oldmode):
15558    try:
15559        work()
15560    finally:
15561        if os.name == 'nt':
15562            os.chmod(tempfile.tempdir, oldmode)
15563        else:
15564            os.chmod(tempfile.tempdir, oldmode)
15565    self.assertEqual(os.listdir(tempfile.tempdir), [])
15566",
15567        );
15568        let f = find_code(&code, "f").expect("missing f code");
15569        let ops = f
15570            .instructions
15571            .iter()
15572            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15573            .collect::<Vec<_>>();
15574        let assert_equal = ops
15575            .iter()
15576            .position(|unit| match unit.op {
15577                Instruction::LoadAttr { namei } => {
15578                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
15579                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
15580                        == "assertEqual"
15581                }
15582                _ => false,
15583            })
15584            .expect("missing assertEqual load");
15585        assert!(
15586            matches!(ops[assert_equal - 1].op, Instruction::LoadFastBorrow { .. }),
15587            "CPython codegen_try_finally() does not make a load-fast barrier after a closed conditional finalbody, got ops={ops:?}"
15588        );
15589    }
15590
15591    #[test]
15592    fn handler_resume_after_nested_try_keeps_successor_load_fast_strong() {
15593        let code = compile_exec(
15594            r#"
15595def f(x):
15596    try:
15597        try:
15598            import readline
15599        except ImportError:
15600            readline = None
15601        else:
15602            import rlcompleter
15603    except ImportError:
15604        return
15605    try:
15606        if x:
15607            y = 1
15608    except ImportError:
15609        return
15610"#,
15611        );
15612        let f = find_code(&code, "f").expect("missing f code");
15613        let ops = f
15614            .instructions
15615            .iter()
15616            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15617            .collect::<Vec<_>>();
15618        let truthiness_load = ops
15619            .windows(3)
15620            .find(|window| {
15621                let arg = OpArg::new(u32::from(u8::from(window[0].arg)));
15622                matches!(
15623                    window[0].op,
15624                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
15625                        if f.varnames[usize::from(var_num.get(arg))] == "x"
15626                ) && matches!(window[1].op, Instruction::ToBool)
15627                    && matches!(window[2].op, Instruction::PopJumpIfFalse { .. })
15628            })
15629            .unwrap_or_else(|| {
15630                panic!(
15631                    "missing if x truthiness load: {:?}",
15632                    ops.iter().map(|unit| unit.op).collect::<Vec<_>>()
15633                )
15634            });
15635
15636        assert!(
15637            matches!(truthiness_load[0].op, Instruction::LoadFast { .. }),
15638            "CPython flowgraph.c leaves an empty try-end label before this successor block, so optimize_load_fast() does not borrow the if-test load: {:?}",
15639            f.instructions
15640                .iter()
15641                .map(|unit| unit.op)
15642                .collect::<Vec<_>>()
15643        );
15644    }
15645
15646    #[test]
15647    fn nested_finally_handler_try_end_keeps_return_load_fast_strong() {
15648        let code = compile_exec(
15649            r#"
15650def f(sys):
15651    try:
15652        import _testinternalcapi
15653        depth = _testinternalcapi.get_recursion_depth()
15654    except (ImportError, RecursionError) as exc:
15655        try:
15656            depth = 0
15657            frame = sys._getframe()
15658            while frame is not None:
15659                depth += 1
15660                frame = frame.f_back
15661        finally:
15662            frame = None
15663    return max(depth - 1, 1)
15664"#,
15665        );
15666        let f = find_code(&code, "f").expect("missing f code");
15667        let ops = f
15668            .instructions
15669            .iter()
15670            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15671            .collect::<Vec<_>>();
15672        let depth_load = ops
15673            .windows(3)
15674            .find(|window| {
15675                let arg = OpArg::new(u32::from(u8::from(window[0].arg)));
15676                matches!(
15677                    window[0].op,
15678                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
15679                        if f.varnames[usize::from(var_num.get(arg))] == "depth"
15680                ) && matches!(window[1].op, Instruction::LoadSmallInt { .. })
15681                    && matches!(window[2].op, Instruction::BinaryOp { .. })
15682            })
15683            .unwrap_or_else(|| {
15684                panic!(
15685                    "missing return depth load: {:?}",
15686                    ops.iter().map(|unit| unit.op).collect::<Vec<_>>()
15687                )
15688            });
15689
15690        assert!(
15691            matches!(depth_load[0].op, Instruction::LoadFast { .. }),
15692            "CPython flowgraph.c preserves an empty try-end label before this return block, so optimize_load_fast() leaves depth strong: {:?}",
15693            f.instructions
15694                .iter()
15695                .map(|unit| unit.op)
15696                .collect::<Vec<_>>()
15697        );
15698    }
15699
15700    #[test]
15701    fn try_except_finally_exit_label_keeps_return_load_fast_strong() {
15702        let code = compile_exec(
15703            r#"
15704def f():
15705    global _importing_zlib
15706    if _importing_zlib:
15707        _bootstrap._verbose_message('zipimport: zlib UNAVAILABLE')
15708        raise ZipImportError("can't decompress data; zlib not available")
15709
15710    _importing_zlib = True
15711    try:
15712        from zlib import decompress
15713    except Exception:
15714        _bootstrap._verbose_message('zipimport: zlib UNAVAILABLE')
15715        raise ZipImportError("can't decompress data; zlib not available")
15716    finally:
15717        _importing_zlib = False
15718
15719    _bootstrap._verbose_message('zipimport: zlib available')
15720    return decompress
15721"#,
15722        );
15723        let f = find_code(&code, "f").expect("missing f code");
15724        let ops = f
15725            .instructions
15726            .iter()
15727            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15728            .collect::<Vec<_>>();
15729        let return_load = ops
15730            .windows(2)
15731            .find(|window| {
15732                let arg = OpArg::new(u32::from(u8::from(window[0].arg)));
15733                matches!(
15734                    window[0].op,
15735                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
15736                        if f.varnames[usize::from(var_num.get(arg))] == "decompress"
15737                ) && matches!(window[1].op, Instruction::ReturnValue)
15738            })
15739            .unwrap_or_else(|| {
15740                panic!(
15741                    "missing return decompress load: {:?}",
15742                    ops.iter().map(|unit| unit.op).collect::<Vec<_>>()
15743                )
15744            });
15745
15746        assert!(
15747            matches!(return_load[0].op, Instruction::LoadFast { .. }),
15748            "CPython codegen_try_finally() emits a JUMP_NO_INTERRUPT to an empty exit label after the normal finally body, and flowgraph.c::optimize_load_fast() does not fall through an empty block: {:?}",
15749            f.instructions
15750                .iter()
15751                .map(|unit| unit.op)
15752                .collect::<Vec<_>>()
15753        );
15754    }
15755
15756    #[test]
15757    fn bare_except_finally_exit_label_keeps_successor_load_fast_strong() {
15758        let code = compile_exec(
15759            r#"
15760def f(self):
15761    hit = False
15762    try:
15763        pass
15764    except:
15765        hit = True
15766    finally:
15767        done = True
15768    self.assertFalse(hit)
15769"#,
15770        );
15771        let f = find_code(&code, "f").expect("missing f code");
15772        let ops = f
15773            .instructions
15774            .iter()
15775            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15776            .collect::<Vec<_>>();
15777        let assert_false = ops
15778            .windows(3)
15779            .find(|window| {
15780                let self_arg = OpArg::new(u32::from(u8::from(window[0].arg)));
15781                let attr_arg = OpArg::new(u32::from(u8::from(window[1].arg)));
15782                matches!(
15783                    window[0].op,
15784                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
15785                        if f.varnames[usize::from(var_num.get(self_arg))] == "self"
15786                ) && matches!(
15787                    window[1].op,
15788                    Instruction::LoadAttr { namei }
15789                        if f.names[usize::try_from(namei.get(attr_arg).name_idx()).unwrap()]
15790                            == "assertFalse"
15791                ) && matches!(
15792                    window[2].op,
15793                    Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. }
15794                )
15795            })
15796            .unwrap_or_else(|| {
15797                panic!(
15798                    "missing assertFalse call: {:?}",
15799                    ops.iter().map(|unit| unit.op).collect::<Vec<_>>()
15800                )
15801            });
15802
15803        assert!(
15804            matches!(assert_false[0].op, Instruction::LoadFast { .. })
15805                && matches!(assert_false[2].op, Instruction::LoadFast { .. }),
15806            "CPython codegen_try_finally() wraps codegen_try_except(); with a bare handler, the normal finally body jumps to an empty exit label, and flowgraph.c::optimize_load_fast() does not fall through that empty block: {:?}",
15807            f.instructions
15808                .iter()
15809                .map(|unit| unit.op)
15810                .collect::<Vec<_>>()
15811        );
15812    }
15813
15814    #[test]
15815    fn typed_except_finally_fallthrough_keeps_successor_load_fast_borrow() {
15816        let code = compile_exec(
15817            r#"
15818def f(self):
15819    hit = False
15820    try:
15821        pass
15822    except Exception:
15823        hit = True
15824    finally:
15825        done = True
15826    self.assertFalse(hit)
15827"#,
15828        );
15829        let f = find_code(&code, "f").expect("missing f code");
15830        let ops = f
15831            .instructions
15832            .iter()
15833            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15834            .collect::<Vec<_>>();
15835        let assert_false = ops
15836            .windows(3)
15837            .find(|window| {
15838                let self_arg = OpArg::new(u32::from(u8::from(window[0].arg)));
15839                let attr_arg = OpArg::new(u32::from(u8::from(window[1].arg)));
15840                matches!(
15841                    window[0].op,
15842                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
15843                        if f.varnames[usize::from(var_num.get(self_arg))] == "self"
15844                ) && matches!(
15845                    window[1].op,
15846                    Instruction::LoadAttr { namei }
15847                        if f.names[usize::try_from(namei.get(attr_arg).name_idx()).unwrap()]
15848                            == "assertFalse"
15849                ) && matches!(
15850                    window[2].op,
15851                    Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. }
15852                )
15853            })
15854            .unwrap_or_else(|| {
15855                panic!(
15856                    "missing assertFalse call: {:?}",
15857                    ops.iter().map(|unit| unit.op).collect::<Vec<_>>()
15858                )
15859            });
15860
15861        assert!(
15862            matches!(assert_false[0].op, Instruction::LoadFastBorrow { .. })
15863                && matches!(assert_false[2].op, Instruction::LoadFastBorrow { .. }),
15864            "CPython typed-handler fallthrough keeps this successor reachable for optimize_load_fast(), so the loads remain borrowed: {:?}",
15865            f.instructions
15866                .iter()
15867                .map(|unit| unit.op)
15868                .collect::<Vec<_>>()
15869        );
15870    }
15871
15872    #[test]
15873    fn bare_except_finally_no_exception_shares_return_target() {
15874        let code = compile_exec(
15875            "\
15876def func():
15877    try:
15878        2
15879    except:
15880        4
15881    finally:
15882        6
15883",
15884        );
15885        let f = find_code(&code, "func").expect("missing func code");
15886        let ops = f
15887            .instructions
15888            .iter()
15889            .map(|unit| unit.op)
15890            .filter(|op| !matches!(op, Instruction::Cache))
15891            .collect::<Vec<_>>();
15892        let first_push_exc = ops
15893            .iter()
15894            .position(|op| matches!(op, Instruction::PushExcInfo))
15895            .expect("missing PushExcInfo");
15896        let returns_before_handler = ops[..first_push_exc]
15897            .iter()
15898            .filter(|op| matches!(op, Instruction::ReturnValue))
15899            .count();
15900
15901        assert_eq!(
15902            returns_before_handler, 1,
15903            "CPython codegen_try_finally() wraps codegen_try_except(); the bare handler jumps back to the normal finally return target instead of forcing duplicate_end_returns() to create a Rust-only return copy, got ops={ops:?}"
15904        );
15905    }
15906
15907    #[test]
15908    fn for_exhaustion_assert_false_message_borrows_load_fast() {
15909        let code = compile_exec(
15910            r#"
15911def f(arg, opcode):
15912    for i, nb_op in enumerate(opcode._nb_ops):
15913        if arg == nb_op[0]:
15914            return i
15915    assert False, f"{arg} is not a valid BINARY_OP argument."
15916"#,
15917        );
15918        let f = find_code(&code, "f").expect("missing f code");
15919        let ops = f
15920            .instructions
15921            .iter()
15922            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15923            .collect::<Vec<_>>();
15924        let assertion_arg_load = ops
15925            .windows(2)
15926            .find(|window| {
15927                let arg = OpArg::new(u32::from(u8::from(window[0].arg)));
15928                matches!(
15929                    window[0].op,
15930                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
15931                        if f.varnames[usize::from(var_num.get(arg))] == "arg"
15932                ) && matches!(window[1].op, Instruction::FormatSimple)
15933            })
15934            .unwrap_or_else(|| {
15935                panic!(
15936                    "missing assertion message arg load: {:?}",
15937                    ops.iter().map(|unit| unit.op).collect::<Vec<_>>()
15938                )
15939            });
15940
15941        assert!(
15942            matches!(assertion_arg_load[0].op, Instruction::LoadFastBorrow { .. }),
15943            "CPython codegen_assert() emits AssertionError directly after the failing test, so flowgraph.c::optimize_load_fast() visits the assertion message block: {:?}",
15944            f.instructions
15945                .iter()
15946                .map(|unit| unit.op)
15947                .collect::<Vec<_>>()
15948        );
15949    }
15950
15951    #[test]
15952    fn try_except_else_conditional_join_borrows_else_receiver() {
15953        let code = compile_exec(
15954            r#"
15955def f(self):
15956    try:
15957        if not self.result_is_file() or not self.sendfile():
15958            for data in self.result:
15959                self.write(data)
15960            self.finish_content()
15961    except:
15962        if hasattr(self.result, 'close'):
15963            self.result.close()
15964        raise
15965    else:
15966        self.close()
15967"#,
15968        );
15969        let f = find_code(&code, "f").expect("missing f code");
15970        let ops = f
15971            .instructions
15972            .iter()
15973            .filter(|unit| !matches!(unit.op, Instruction::Cache))
15974            .collect::<Vec<_>>();
15975        let else_close_load = ops
15976            .windows(6)
15977            .find(|window| {
15978                let load_arg = OpArg::new(u32::from(u8::from(window[0].arg)));
15979                let attr_arg = OpArg::new(u32::from(u8::from(window[1].arg)));
15980                matches!(
15981                    window[0].op,
15982                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
15983                        if f.varnames[usize::from(var_num.get(load_arg))] == "self"
15984                ) && matches!(
15985                    window[1].op,
15986                    Instruction::LoadAttr { namei }
15987                        if f.names[usize::try_from(namei.get(attr_arg).name_idx()).unwrap()]
15988                            == "close"
15989                ) && matches!(window[2].op, Instruction::Call { .. })
15990                    && matches!(window[3].op, Instruction::PopTop)
15991                    && matches!(window[4].op, Instruction::LoadConst { .. })
15992                    && matches!(window[5].op, Instruction::ReturnValue)
15993            })
15994            .unwrap_or_else(|| {
15995                panic!(
15996                    "missing else self.close return sequence: {:?}",
15997                    ops.iter().map(|unit| unit.op).collect::<Vec<_>>()
15998                )
15999            });
16000        assert!(
16001            matches!(else_close_load[0].op, Instruction::LoadFastBorrow { .. }),
16002            "CPython codegen_try_except() emits Try.orelse directly with VISIT_SEQ(), so flowgraph.c::optimize_load_fast() reaches the else self.close receiver: {:?}",
16003            f.instructions
16004                .iter()
16005                .map(|unit| unit.op)
16006                .collect::<Vec<_>>()
16007        );
16008    }
16009
16010    #[test]
16011    fn try_finally_exit_label_reuses_empty_block_for_borrow() {
16012        let code = compile_exec(
16013            r#"
16014def f(self, exc):
16015    try:
16016        end_time = self.get_time()
16017    finally:
16018        result = self.context.__exit__(*exc)
16019    self.seconds = end_time - self.start_time
16020    return result
16021"#,
16022        );
16023        let f = find_code(&code, "f").expect("missing f code");
16024        let ops = f
16025            .instructions
16026            .iter()
16027            .filter(|unit| !matches!(unit.op, Instruction::Cache))
16028            .collect::<Vec<_>>();
16029        let seconds_store = ops
16030            .windows(5)
16031            .find(|window| {
16032                let load_pair = OpArg::new(u32::from(u8::from(window[0].arg)));
16033                let self_load = OpArg::new(u32::from(u8::from(window[3].arg)));
16034                matches!(
16035                    window[0].op,
16036                    Instruction::LoadFastLoadFast { var_nums }
16037                        | Instruction::LoadFastBorrowLoadFastBorrow { var_nums }
16038                        if {
16039                            let (left, right) = var_nums.get(load_pair).indexes();
16040                            f.varnames[usize::from(left)] == "end_time"
16041                                && f.varnames[usize::from(right)] == "self"
16042                        }
16043                ) && matches!(window[1].op, Instruction::LoadAttr { .. })
16044                    && matches!(window[2].op, Instruction::BinaryOp { .. })
16045                    && matches!(
16046                        window[3].op,
16047                        Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
16048                            if f.varnames[usize::from(var_num.get(self_load))] == "self"
16049                    )
16050                    && matches!(window[4].op, Instruction::StoreAttr { .. })
16051            })
16052            .unwrap_or_else(|| {
16053                panic!(
16054                    "missing self.seconds store sequence: {:?}",
16055                    ops.iter().map(|unit| unit.op).collect::<Vec<_>>()
16056                )
16057            });
16058        let return_result = ops
16059            .windows(2)
16060            .find(|window| {
16061                let arg = OpArg::new(u32::from(u8::from(window[0].arg)));
16062                matches!(
16063                    window[0].op,
16064                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
16065                        if f.varnames[usize::from(var_num.get(arg))] == "result"
16066                ) && matches!(window[1].op, Instruction::ReturnValue)
16067            })
16068            .unwrap_or_else(|| {
16069                panic!(
16070                    "missing result return sequence: {:?}",
16071                    ops.iter().map(|unit| unit.op).collect::<Vec<_>>()
16072                )
16073            });
16074
16075        assert!(
16076            matches!(
16077                seconds_store[0].op,
16078                Instruction::LoadFastBorrowLoadFastBorrow { .. }
16079            ) && matches!(seconds_store[3].op, Instruction::LoadFastBorrow { .. })
16080                && matches!(return_result[0].op, Instruction::LoadFastBorrow { .. }),
16081            "CPython codegen_try_finally() emits JUMP_NO_INTERRUPT to the exit label, and flowgraph.c labels the current empty block instead of inserting a b_next barrier before following code: {:?}",
16082            f.instructions
16083                .iter()
16084                .map(|unit| unit.op)
16085                .collect::<Vec<_>>()
16086        );
16087    }
16088
16089    #[test]
16090    fn with_tail_while_true_break_successor_uses_strong_load() {
16091        let code = compile_exec(
16092            r#"
16093def f(self, cm):
16094    with cm as out:
16095        while 1:
16096            data = out.read()
16097            if not data:
16098                break
16099    self.close()
16100"#,
16101        );
16102        let f = find_code(&code, "f").expect("missing f code");
16103        let instructions: Vec<_> = f
16104            .instructions
16105            .iter()
16106            .filter(|unit| !matches!(unit.op, Instruction::Cache))
16107            .collect();
16108        let close_attr = instructions
16109            .iter()
16110            .position(|unit| match unit.op {
16111                Instruction::LoadAttr { namei } => {
16112                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
16113                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "close"
16114                }
16115                _ => false,
16116            })
16117            .expect("missing close load");
16118
16119        assert!(
16120            matches!(
16121                instructions[close_attr - 1].op,
16122                Instruction::LoadFast { .. }
16123            ),
16124            "CPython codegen_while() emits USE_LABEL(end) for the tail break target before codegen_with_inner() emits normal __exit__ cleanup, so flowgraph.c::optimize_load_fast() leaves the successor receiver strong: {:?}",
16125            f.instructions
16126                .iter()
16127                .map(|unit| unit.op)
16128                .collect::<Vec<_>>()
16129        );
16130    }
16131
16132    #[test]
16133    fn folded_ifexp_nested_in_call_keeps_successor_load_fast_strong() {
16134        let code = compile_exec(
16135            r#"
16136def f(self, g):
16137    self.x = g(self.y, optimization='' if __debug__ else 1)
16138    self.close()
16139"#,
16140        );
16141        let f = find_code(&code, "f").expect("missing f code");
16142        let instructions: Vec<_> = f
16143            .instructions
16144            .iter()
16145            .filter(|unit| !matches!(unit.op, Instruction::Cache))
16146            .collect();
16147        let close_attr = instructions
16148            .iter()
16149            .position(|unit| match unit.op {
16150                Instruction::LoadAttr { namei } => {
16151                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
16152                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "close"
16153                }
16154                _ => false,
16155            })
16156            .expect("missing close load");
16157
16158        assert!(
16159            matches!(
16160                instructions[close_attr - 1].op,
16161                Instruction::LoadFast { .. }
16162            ),
16163            "CPython codegen_ifexp() emits USE_LABEL(end); with a folded conditional nested in a larger stack expression, flowgraph.c::optimize_load_fast() sees an empty end block and does not visit the successor loads: {:?}",
16164            f.instructions
16165                .iter()
16166                .map(|unit| unit.op)
16167                .collect::<Vec<_>>()
16168        );
16169    }
16170
16171    #[test]
16172    fn folded_ifexp_assignment_before_with_keeps_context_load_fast_strong() {
16173        let code = compile_exec(
16174            r#"
16175def f(self, cm):
16176    optlevel = 1 if __debug__ else 0
16177    with cm as t:
16178        self.use(t, optlevel)
16179"#,
16180        );
16181        let f = find_code(&code, "f").expect("missing f code");
16182        let instructions: Vec<_> = f
16183            .instructions
16184            .iter()
16185            .filter(|unit| !matches!(unit.op, Instruction::Cache))
16186            .collect();
16187        let first_exit = instructions
16188            .iter()
16189            .position(|unit| match unit.op {
16190                Instruction::LoadSpecial { method } => {
16191                    method.get(OpArg::new(u32::from(u8::from(unit.arg)))) == SpecialMethod::Exit
16192                }
16193                _ => false,
16194            })
16195            .expect("missing __exit__ load");
16196        let cm_load = (0..first_exit)
16197            .rev()
16198            .find(|idx| {
16199                let arg = OpArg::new(u32::from(u8::from(instructions[*idx].arg)));
16200                matches!(
16201                    instructions[*idx].op,
16202                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
16203                        if f.varnames[usize::from(var_num.get(arg))] == "cm"
16204                )
16205            })
16206            .expect("missing cm load before __exit__");
16207
16208        assert!(
16209            matches!(instructions[cm_load].op, Instruction::LoadFast { .. }),
16210            "CPython codegen_ifexp() emits USE_LABEL(end), then codegen_with_inner() starts the with header after that empty block; flowgraph.c::optimize_load_fast() does not push fallthrough successors from empty blocks, so the context manager load stays strong: {:?}",
16211            f.instructions
16212                .iter()
16213                .map(|unit| unit.op)
16214                .collect::<Vec<_>>()
16215        );
16216    }
16217
16218    #[test]
16219    fn folded_ifexp_assignment_keeps_later_statement_load_fast_strong() {
16220        let code = compile_exec(
16221            r#"
16222def f(self, x):
16223    optlevel = 1 if __debug__ else 0
16224    ext = '.pyc'
16225    self.use(x, ext)
16226"#,
16227        );
16228        let f = find_code(&code, "f").expect("missing f code");
16229        let instructions: Vec<_> = f
16230            .instructions
16231            .iter()
16232            .filter(|unit| !matches!(unit.op, Instruction::Cache))
16233            .collect();
16234        let use_attr = instructions
16235            .iter()
16236            .position(|unit| match unit.op {
16237                Instruction::LoadAttr { namei } => {
16238                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
16239                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "use"
16240                }
16241                _ => false,
16242            })
16243            .expect("missing use load");
16244
16245        assert!(
16246            matches!(instructions[use_attr - 1].op, Instruction::LoadFast { .. }),
16247            "CPython codegen_ifexp() emits USE_LABEL(end) for the folded assignment; flowgraph.c::optimize_load_fast() does not push fallthrough from the empty end block, so the next statement receiver stays strong: {:?}",
16248            f.instructions
16249                .iter()
16250                .map(|unit| unit.op)
16251                .collect::<Vec<_>>()
16252        );
16253    }
16254
16255    #[test]
16256    fn const_assignment_before_with_keeps_context_load_fast_borrowed() {
16257        let code = compile_exec(
16258            r#"
16259def f(self, cm):
16260    optlevel = 1
16261    with cm as t:
16262        self.use(t, optlevel)
16263"#,
16264        );
16265        let f = find_code(&code, "f").expect("missing f code");
16266        let instructions: Vec<_> = f
16267            .instructions
16268            .iter()
16269            .filter(|unit| !matches!(unit.op, Instruction::Cache))
16270            .collect();
16271        let first_exit = instructions
16272            .iter()
16273            .position(|unit| match unit.op {
16274                Instruction::LoadSpecial { method } => {
16275                    method.get(OpArg::new(u32::from(u8::from(unit.arg)))) == SpecialMethod::Exit
16276                }
16277                _ => false,
16278            })
16279            .expect("missing __exit__ load");
16280        let cm_load = (0..first_exit)
16281            .rev()
16282            .find(|idx| {
16283                let arg = OpArg::new(u32::from(u8::from(instructions[*idx].arg)));
16284                matches!(
16285                    instructions[*idx].op,
16286                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
16287                        if f.varnames[usize::from(var_num.get(arg))] == "cm"
16288                )
16289            })
16290            .expect("missing cm load before __exit__");
16291
16292        assert!(
16293            matches!(instructions[cm_load].op, Instruction::LoadFastBorrow { .. }),
16294            "without CPython's folded if-expression end label, flowgraph.c::optimize_load_fast() sees the following with header in the same reachable state and borrows the context manager load: {:?}",
16295            f.instructions
16296                .iter()
16297                .map(|unit| unit.op)
16298                .collect::<Vec<_>>()
16299        );
16300    }
16301
16302    #[test]
16303    fn if_end_label_reuse_allows_following_return_borrow() {
16304        let code = compile_exec(
16305            r#"
16306def f(data):
16307    msgids = []
16308    reading_msgid = False
16309    cur_msgid = []
16310    for line in data.split('\n'):
16311        if reading_msgid:
16312            if line.startswith('"'):
16313                cur_msgid.append(line.strip('"'))
16314            else:
16315                msgids.append('\n'.join(cur_msgid))
16316                cur_msgid = []
16317                reading_msgid = False
16318                continue
16319        if line.startswith('msgid '):
16320            line = line[len('msgid '):]
16321            cur_msgid.append(line.strip('"'))
16322            reading_msgid = True
16323    else:
16324        if reading_msgid:
16325            msgids.append('\n'.join(cur_msgid))
16326
16327    return msgids
16328"#,
16329        );
16330        let f = find_code(&code, "f").expect("missing f code");
16331        let instructions: Vec<_> = f
16332            .instructions
16333            .iter()
16334            .filter(|unit| !matches!(unit.op, Instruction::Cache))
16335            .collect();
16336        let return_load = instructions
16337            .windows(2)
16338            .find(|window| {
16339                let arg = OpArg::new(u32::from(u8::from(window[0].arg)));
16340                matches!(
16341                    window[0].op,
16342                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
16343                        if f.varnames[usize::from(var_num.get(arg))] == "msgids"
16344                ) && matches!(window[1].op, Instruction::ReturnValue)
16345            })
16346            .unwrap_or_else(|| {
16347                panic!(
16348                    "missing return msgids sequence: {:?}",
16349                    instructions.iter().map(|unit| unit.op).collect::<Vec<_>>()
16350                )
16351            });
16352
16353        assert!(
16354            matches!(return_load[0].op, Instruction::LoadFastBorrow { .. }),
16355            "CPython codegen_if() ends with USE_LABEL(end), and flowgraph.c::cfg_builder_current_block_is_terminated() reuses the current empty block for that label instead of leaving a b_next barrier before the following return: {:?}",
16356            f.instructions
16357                .iter()
16358                .map(|unit| unit.op)
16359                .collect::<Vec<_>>()
16360        );
16361    }
16362
16363    #[test]
16364    fn named_except_continue_resume_try_body_keeps_method_borrows() {
16365        let code = compile_exec(
16366            r#"
16367def f(self, block=True):
16368    if not block and not self.wait(timeout=0):
16369        return None
16370    while self.event_queue.empty():
16371        while True:
16372            try:
16373                self.push_char(self.read(1))
16374            except OSError as err:
16375                if err.errno == errno.EINTR:
16376                    if not self.event_queue.empty():
16377                        return self.event_queue.get()
16378                    else:
16379                        continue
16380                else:
16381                    raise
16382            else:
16383                break
16384    return self.event_queue.get()
16385"#,
16386        );
16387        let f = find_code(&code, "f").expect("missing f code");
16388        let instructions: Vec<_> = f
16389            .instructions
16390            .iter()
16391            .filter(|unit| !matches!(unit.op, Instruction::Cache))
16392            .collect();
16393
16394        let attr_idx = |name: &str| {
16395            instructions
16396                .iter()
16397                .position(|unit| match unit.op {
16398                    Instruction::LoadAttr { namei } => {
16399                        let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
16400                        f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == name
16401                    }
16402                    _ => false,
16403                })
16404                .unwrap_or_else(|| panic!("missing {name} LOAD_ATTR"))
16405        };
16406        let push_char_idx = attr_idx("push_char");
16407        let read_idx = attr_idx("read");
16408
16409        assert!(
16410            matches!(
16411                instructions[push_char_idx - 1].op,
16412                Instruction::LoadFastBorrow { .. }
16413            ),
16414            "named-except cleanup continue backedge should not deopt the protected try-body method receiver, got instructions={:?}",
16415            instructions.iter().map(|unit| unit.op).collect::<Vec<_>>()
16416        );
16417        assert!(
16418            matches!(
16419                instructions[read_idx - 1].op,
16420                Instruction::LoadFastBorrow { .. }
16421            ),
16422            "nested protected try-body method receiver should remain borrowed like CPython, got instructions={:?}",
16423            instructions.iter().map(|unit| unit.op).collect::<Vec<_>>()
16424        );
16425    }
16426
16427    #[test]
16428    fn boolop_or_shared_body_keeps_false_jump_before_loop_backedge() {
16429        let code = compile_exec(
16430            r#"
16431def f(value):
16432    digits = []
16433    for digit in value:
16434        if isinstance(digit, int) and 0 <= digit <= 9:
16435            if digits or digit != 0:
16436                digits.append(digit)
16437        else:
16438            raise ValueError
16439    return digits
16440"#,
16441        );
16442        let f = find_code(&code, "f").expect("missing function code");
16443        let ops: Vec<_> = f
16444            .instructions
16445            .iter()
16446            .map(|unit| unit.op)
16447            .filter(|op| !matches!(op, Instruction::Cache))
16448            .collect();
16449
16450        assert!(
16451            ops.windows(11).any(|window| {
16452                matches!(
16453                    window,
16454                    [
16455                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
16456                        Instruction::ToBool,
16457                        Instruction::PopJumpIfTrue { .. },
16458                        Instruction::NotTaken,
16459                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
16460                        Instruction::LoadSmallInt { .. },
16461                        Instruction::CompareOp { .. },
16462                        Instruction::PopJumpIfFalse { .. },
16463                        Instruction::NotTaken,
16464                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
16465                        Instruction::LoadAttr { .. },
16466                    ]
16467                )
16468            }),
16469            "OR-shared body should keep CPython last-condition false jump before the loop backedge, got ops={ops:?}"
16470        );
16471        assert!(
16472            !ops.windows(6).any(|window| {
16473                matches!(
16474                    window,
16475                    [
16476                        Instruction::CompareOp { .. },
16477                        Instruction::PopJumpIfTrue { .. },
16478                        Instruction::NotTaken,
16479                        Instruction::JumpBackward { .. }
16480                            | Instruction::JumpBackwardNoInterrupt { .. },
16481                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
16482                        Instruction::LoadAttr { .. },
16483                    ]
16484                )
16485            }),
16486            "OR-shared body should not be moved after the implicit loop backedge, got ops={ops:?}"
16487        );
16488    }
16489
16490    #[test]
16491    fn single_if_loop_backedge_keeps_true_body_fallthrough_backedge_shape() {
16492        let code = compile_exec(
16493            r##"
16494def f(buffer, pos, last_char):
16495    while pos > 0:
16496        pos -= 1
16497        if buffer[pos] == "#":
16498            last_char = None
16499    return last_char
16500"##,
16501        );
16502        let f = find_code(&code, "f").expect("missing f code");
16503        let ops: Vec<_> = f
16504            .instructions
16505            .iter()
16506            .map(|unit| unit.op)
16507            .filter(|op| !matches!(op, Instruction::Cache))
16508            .collect();
16509
16510        assert!(
16511            ops.windows(6).any(|window| {
16512                matches!(
16513                    window,
16514                    [
16515                        Instruction::CompareOp { .. },
16516                        Instruction::PopJumpIfTrue { .. },
16517                        Instruction::NotTaken,
16518                        Instruction::JumpBackward { .. }
16519                            | Instruction::JumpBackwardNoInterrupt { .. },
16520                        Instruction::LoadConst { .. },
16521                        Instruction::StoreFast { .. },
16522                    ]
16523                )
16524            }),
16525            "single-if loop tail should keep CPython true-body plus fallthrough-backedge shape, got ops={ops:?}",
16526        );
16527        assert!(
16528            !ops.windows(4).any(|window| {
16529                matches!(
16530                    window,
16531                    [
16532                        Instruction::CompareOp { .. },
16533                        Instruction::PopJumpIfFalse { .. },
16534                        Instruction::NotTaken,
16535                        Instruction::LoadConst { .. },
16536                    ]
16537                )
16538            }),
16539            "single-if loop tail should not be inverted away from CPython shape, got ops={ops:?}",
16540        );
16541    }
16542
16543    fn location_range(
16544        locations: &(SourceLocation, SourceLocation),
16545    ) -> (usize, usize, usize, usize) {
16546        let (location, end_location) = locations;
16547        (
16548            location.line.get(),
16549            location.character_offset.get(),
16550            end_location.line.get(),
16551            end_location.character_offset.get(),
16552        )
16553    }
16554
16555    fn instruction_range(
16556        code: &CodeObject,
16557        matches: impl Fn(&Instruction) -> bool,
16558    ) -> Option<(usize, usize, usize, usize)> {
16559        code.instructions
16560            .iter()
16561            .zip(&code.locations)
16562            .find_map(|(unit, locations)| matches(&unit.op).then(|| location_range(locations)))
16563    }
16564
16565    fn find_code<'a>(code: &'a CodeObject, name: &str) -> Option<&'a CodeObject> {
16566        if code.obj_name == name {
16567            return Some(code);
16568        }
16569        code.constants.iter().find_map(|constant| {
16570            if let ConstantData::Code { code } = constant {
16571                find_code(code, name)
16572            } else {
16573                None
16574            }
16575        })
16576    }
16577
16578    fn find_direct_child_code<'a>(code: &'a CodeObject, name: &str) -> Option<&'a CodeObject> {
16579        code.constants.iter().find_map(|constant| {
16580            if let ConstantData::Code { code } = constant {
16581                (code.obj_name == name).then_some(code.as_ref())
16582            } else {
16583                None
16584            }
16585        })
16586    }
16587
16588    #[test]
16589    fn annotated_multiline_function_body_keeps_def_firstlineno_like_cpython() {
16590        let code = compile_exec(
16591            r#"
16592a = 1
16593def f(
16594    x: a,
16595): ...
16596"#,
16597        );
16598        let f = find_code(&code, "f").expect("missing f code");
16599        // CPython 3.14 codegen_function() computes firstlineno from the
16600        // FunctionDef before compiling annotations, then passes it to
16601        // codegen_function_body().
16602        assert_eq!(f.linetable.as_ref(), &[0x80, 0x00, 0xe1, 0x03, 0x06]);
16603    }
16604
16605    #[test]
16606    fn annotation_scope_return_uses_function_location_like_cpython() {
16607        let code = compile_exec(
16608            r#"
16609def g():
16610    def f(x: not (int is int), /): ...
16611"#,
16612        );
16613        let g = find_code(&code, "g").expect("missing g code");
16614        let annotate = find_code(g, "__annotate__").expect("missing annotation code");
16615        // CPython 3.14 codegen_function_annotations() receives LOC(function)
16616        // and uses it for the annotation closure's BUILD_MAP/RETURN_VALUE and
16617        // for the parent MAKE_FUNCTION annotate sequence.
16618        assert_eq!(g.linetable.as_ref(), &[0x80, 0x00, 0xdf, 0x04, 0x26]);
16619        assert_eq!(
16620            annotate.linetable.as_ref(),
16621            &[
16622                0x80, 0x00, 0xd7, 0x04, 0x26, 0xd1, 0x04, 0x26, 0x94, 0x23, 0x9c, 0x13, 0xd0, 0x0d,
16623                0x1d, 0xd1, 0x04, 0x26,
16624            ],
16625        );
16626    }
16627
16628    #[test]
16629    fn starred_arg_annotation_unpack_uses_function_location_like_cpython() {
16630        let code = compile_exec("def f(*args: *Ts): pass\n");
16631        let annotate = find_code(&code, "__annotate__").expect("missing annotation code");
16632
16633        // codegen_argannotation() visits `Ts` at the annotation
16634        // expression location, then emits UNPACK_SEQUENCE at LOC(function).
16635        assert_eq!(
16636            annotate.linetable.as_ref(),
16637            &[
16638                0x80, 0x00, 0xd7, 0x00, 0x17, 0xd1, 0x00, 0x17, 0x8c, 0x62, 0xd3, 0x00, 0x17
16639            ],
16640        );
16641    }
16642
16643    #[test]
16644    fn module_deferred_annotations_use_start_location_like_cpython() {
16645        let code = compile_exec(
16646            "\
16647import os
16648X: int
16649Y: str
16650",
16651        );
16652        let annotate = find_code(&code, "__annotate__").expect("missing __annotate__ code");
16653
16654        // compile.c::start_location() passes the first module
16655        // statement location into _PyCodegen_Module(), and
16656        // codegen_process_deferred_annotations() uses that loc for annotation
16657        // scope setup, BUILD_MAP, STORE_SUBSCR, and RETURN_VALUE.
16658        assert_eq!(
16659            annotate.linetable.as_ref(),
16660            &[
16661                0x80, 0x00, 0x87, 0x09, 0x81, 0x09, 0xdf, 0x00, 0x06, 0x82, 0x06, 0x84, 0x33, 0x81,
16662                0x06, 0xf1, 0x03, 0x00, 0x01, 0x0a, 0xe7, 0x00, 0x06, 0x82, 0x06, 0x84, 0x33, 0x81,
16663                0x06, 0xf2, 0x05, 0x00, 0x01, 0x0a,
16664            ]
16665        );
16666    }
16667
16668    #[test]
16669    fn super_method_call_kw_names_use_attribute_location_like_cpython() {
16670        let code = compile_exec(
16671            "\
16672class C:
16673    def f(self, x, y):
16674        super().__init__(
16675            x=x,
16676            y=y)
16677",
16678        );
16679        let f = find_code(&code, "f").expect("missing f code");
16680        let call_kw_index = f
16681            .instructions
16682            .iter()
16683            .position(|unit| matches!(unit.op, Instruction::CallKw { .. }))
16684            .expect("missing CALL_KW");
16685        let (kw_names, (location, end_location)) = f
16686            .instructions
16687            .iter()
16688            .zip(&f.locations)
16689            .take(call_kw_index)
16690            .rev()
16691            .find(|(unit, _)| matches!(unit.op, Instruction::LoadConst { .. }))
16692            .expect("missing CALL_KW names tuple");
16693
16694        assert!(
16695            matches!(kw_names.op, Instruction::LoadConst { .. }),
16696            "expected keyword names tuple before CALL_KW"
16697        );
16698        assert_eq!(
16699            (location.line.get(), end_location.line.get()),
16700            (3, 3),
16701            "CPython maybe_optimize_method_call() passes the updated method-attribute loc into codegen_call_simple_kw_helper()"
16702        );
16703    }
16704
16705    #[test]
16706    fn multiline_super_method_load_uses_expression_start_location_like_cpython() {
16707        let code = compile_exec(
16708            "\
16709class C:
16710    def f(self):
16711        return super(
16712        ).m()
16713",
16714        );
16715        let f = find_code(&code, "f").expect("missing f code");
16716        let load_super_index = f
16717            .instructions
16718            .iter()
16719            .position(|unit| match unit.op {
16720                Instruction::LoadSuperAttr { namei } => namei
16721                    .get(OpArg::new(u32::from(u8::from(unit.arg))))
16722                    .is_load_method(),
16723                _ => false,
16724            })
16725            .expect("missing LOAD_SUPER_METHOD");
16726        let (load_location, _) = f.locations[load_super_index];
16727
16728        assert_eq!(
16729            load_location.line.get(),
16730            3,
16731            "CPython maybe_optimize_method_call() emits LOAD_SUPER_METHOD at LOC(meth), before updating to the attribute start"
16732        );
16733    }
16734
16735    #[test]
16736    fn multiline_non_ascii_attribute_uses_cpython_unicode_length() {
16737        let code = compile_exec(
16738            "\
16739def f(obj):
16740    return (obj
16741        .é)
16742",
16743        );
16744        let f = find_code(&code, "f").expect("missing f code");
16745        let load_attr_position = f
16746            .instructions
16747            .iter()
16748            .zip(&f.locations)
16749            .find_map(|(unit, (location, end_location))| {
16750                matches!(unit.op, Instruction::LoadAttr { .. }).then_some((
16751                    location.line.get(),
16752                    location.character_offset.get(),
16753                    end_location.line.get(),
16754                    end_location.character_offset.get(),
16755                ))
16756            })
16757            .expect("missing LOAD_ATTR");
16758
16759        assert_eq!(
16760            load_attr_position,
16761            (3, 11, 3, 12),
16762            "CPython update_start_location_to_match_attr() subtracts PyUnicode_GET_LENGTH(attr), not the UTF-8 byte length; Rust SourceLocation exposes the resulting columns as one-based"
16763        );
16764    }
16765
16766    #[test]
16767    fn two_arg_super_attr_in_class_body_is_optimized_like_cpython() {
16768        let code = compile_exec(
16769            "\
16770class C:
16771    x = super(C, self).attr
16772",
16773        );
16774        let class_code = find_code(&code, "C").expect("missing class code");
16775
16776        assert!(
16777            class_code
16778                .instructions
16779                .iter()
16780                .any(|unit| matches!(unit.op, Instruction::LoadSuperAttr { .. })),
16781            "CPython can_optimize_super_call() does not require function scope for two-argument super()"
16782        );
16783    }
16784
16785    #[test]
16786    fn module_super_symbol_blocks_zero_arg_super_optimization_like_cpython() {
16787        let code = compile_exec(
16788            "\
16789super
16790class C:
16791    def f(self):
16792        return super().attr
16793",
16794        );
16795        let f = find_code(&code, "f").expect("missing f code");
16796
16797        assert!(
16798            !f.instructions
16799                .iter()
16800                .any(|unit| matches!(unit.op, Instruction::LoadSuperAttr { .. })),
16801            "CPython can_optimize_super_call() rejects any top-level symbol-table entry for super"
16802        );
16803    }
16804
16805    #[test]
16806    fn lambda_return_uses_body_location_like_cpython() {
16807        let code = compile_exec(
16808            "\
16809def outer():
16810    return lambda x: x if x else 1
16811",
16812        );
16813        let lambda = find_code(&code, "<lambda>").expect("missing lambda code");
16814        let return_positions: Vec<_> = lambda
16815            .instructions
16816            .iter()
16817            .zip(&lambda.locations)
16818            .filter_map(|(unit, (location, end_location))| {
16819                matches!(unit.op, Instruction::ReturnValue).then_some((
16820                    location.line.get(),
16821                    location.character_offset.get(),
16822                    end_location.line.get(),
16823                    end_location.character_offset.get(),
16824                ))
16825            })
16826            .collect();
16827
16828        assert_eq!(
16829            return_positions,
16830            vec![(2, 22, 2, 35), (2, 22, 2, 35)],
16831            "CPython codegen_lambda() emits RETURN_VALUE at LOC(lambda body)"
16832        );
16833    }
16834
16835    #[test]
16836    fn explicit_return_value_locations_match_cpython_codegen_return() {
16837        let code = compile_exec(
16838            "\
16839def dynamic(x):
16840    return x
16841
16842def constant():
16843    return 1
16844
16845def bare():
16846    return
16847",
16848        );
16849
16850        let cases = [
16851            ("dynamic", vec![(2, 5, 2, 13)]),
16852            ("constant", vec![(5, 12, 5, 13)]),
16853            ("bare", vec![(8, 5, 8, 11)]),
16854        ];
16855        for (name, expected) in cases {
16856            let function = find_code(&code, name).expect("missing function code");
16857            let return_positions: Vec<_> = function
16858                .instructions
16859                .iter()
16860                .zip(&function.locations)
16861                .filter_map(|(unit, (location, end_location))| {
16862                    matches!(unit.op, Instruction::ReturnValue).then_some((
16863                        location.line.get(),
16864                        location.character_offset.get(),
16865                        end_location.line.get(),
16866                        end_location.character_offset.get(),
16867                    ))
16868                })
16869                .collect();
16870
16871            assert_eq!(
16872                return_positions, expected,
16873                "CPython codegen_return() emits explicit return at loc for {name}"
16874            );
16875        }
16876    }
16877
16878    #[test]
16879    fn continue_jump_keeps_statement_location_like_cpython() {
16880        let code = compile_exec(
16881            "\
16882def continues(xs):
16883    for x in xs:
16884        if x:
16885            continue
16886        use(x)
16887",
16888        );
16889
16890        {
16891            let (name, expected_position) = ("continues", (4, 13, 4, 21));
16892            let function = find_code(&code, name).expect("missing function code");
16893            let jump_positions: Vec<_> = function
16894                .instructions
16895                .iter()
16896                .zip(&function.locations)
16897                .filter_map(|(unit, (location, end_location))| {
16898                    matches!(
16899                        unit.op,
16900                        Instruction::JumpForward { .. } | Instruction::JumpBackward { .. }
16901                    )
16902                    .then_some((
16903                        location.line.get(),
16904                        location.character_offset.get(),
16905                        end_location.line.get(),
16906                        end_location.character_offset.get(),
16907                    ))
16908                })
16909                .collect();
16910
16911            assert!(
16912                jump_positions.contains(&expected_position),
16913                "CPython codegen_continue() emits final jump at statement loc for {name}, got {jump_positions:?}"
16914            );
16915        }
16916    }
16917
16918    #[test]
16919    fn not_compare_uses_unary_location_like_cpython() {
16920        let code = compile_exec(
16921            "\
16922def f(self, other):
16923    return not self == other
16924",
16925        );
16926        let f = find_code(&code, "f").expect("missing f code");
16927
16928        // CPython 3.14 parses the Compare inside UnaryOp(Not) with the
16929        // UnaryOp start location, so codegen_compare() emits COMPARE_OP at
16930        // the full "not self == other" range before flowgraph folds TO_BOOL.
16931        assert_eq!(
16932            f.linetable.as_ref(),
16933            &[
16934                0x80, 0x00, 0xd8, 0x0f, 0x13, 0xd2, 0x0b, 0x1c, 0xd0, 0x04, 0x1c,
16935            ]
16936        );
16937    }
16938
16939    #[test]
16940    fn not_chained_compare_keeps_compare_location_like_cpython() {
16941        let code = compile_exec(
16942            "\
16943def f(c):
16944    return not (b\" \" <= c <= b\"~\")
16945",
16946        );
16947        let f = find_code(&code, "f").expect("missing f code");
16948
16949        // CPython's single Compare under UnaryOp(Not) includes "not" in the
16950        // Compare range, but chained comparisons keep their inner range for
16951        // compare scaffolding and only use the UnaryOp range for TO_BOOL and
16952        // UNARY_NOT.
16953        assert_eq!(
16954            f.linetable.as_ref(),
16955            &[
16956                0x80, 0x00, 0xd8, 0x10, 0x14, 0x98, 0x01, 0xd7, 0x10, 0x21, 0xd4, 0x10, 0x21, 0x98,
16957                0x54, 0xd1, 0x10, 0x21, 0xd4, 0x0b, 0x22, 0xd0, 0x04, 0x22, 0xd1, 0x10, 0x21, 0xd4,
16958                0x0b, 0x22, 0xd0, 0x04, 0x22,
16959            ]
16960        );
16961    }
16962
16963    #[test]
16964    fn type_param_scopes_use_cpython_locations() {
16965        let code = compile_exec("type BoundGenericAlias[X: int] = set[X]\n");
16966        let type_params = find_code(&code, "<generic parameters of BoundGenericAlias>")
16967            .expect("missing generic parameters code");
16968        let bound = find_direct_child_code(type_params, "X").expect("missing X bound code");
16969        let alias =
16970            find_direct_child_code(type_params, "BoundGenericAlias").expect("missing alias code");
16971
16972        // CPython 3.14 codegen_type_params() emits type-parameter ops at
16973        // LOC(typeparam), bound/default evaluator ops at LOC(e), and type alias
16974        // body plumbing at LOC(s).
16975        assert_eq!(
16976            type_params.linetable.as_ref(),
16977            &[
16978                0xf8, 0x80, 0x00, 0xd0, 0x00, 0x27, 0x90, 0x76, 0x9b, 0x23, 0x93, 0x76, 0xd7, 0x00,
16979                0x27, 0xd1, 0x00, 0x27,
16980            ],
16981        );
16982        assert_eq!(
16983            bound.linetable.as_ref(),
16984            &[0x80, 0x00, 0x9f, 0x23, 0x9e, 0x23]
16985        );
16986        assert_eq!(
16987            alias.linetable.as_ref(),
16988            &[
16989                0xf8, 0x80, 0x00, 0xd7, 0x00, 0x27, 0xd0, 0x00, 0x27, 0xa4, 0x13, 0xa0, 0x51, 0xa5,
16990                0x16, 0xd0, 0x00, 0x27,
16991            ],
16992        );
16993    }
16994
16995    #[test]
16996    fn typealias_value_scope_has_single_return_like_cpython() {
16997        let code = compile_exec("type Alias = int\n");
16998        let alias = find_direct_child_code(&code, "Alias").expect("missing alias code");
16999        let return_count = alias
17000            .instructions
17001            .iter()
17002            .filter(|unit| matches!(unit.op, Instruction::ReturnValue))
17003            .count();
17004        assert_eq!(
17005            return_count, 1,
17006            "CPython codegen_typealias_body() emits one RETURN_VALUE and assembles with addNone=0, got instructions={:?}",
17007            alias.instructions
17008        );
17009    }
17010
17011    #[test]
17012    fn generic_typealias_wrapper_return_uses_alias_location_like_cpython() {
17013        let code = compile_exec("type A[T] = T\n");
17014        let type_params =
17015            find_code(&code, "<generic parameters of A>").expect("missing type params code");
17016
17017        // codegen_typealias() assembles the generic-parameters
17018        // wrapper with addNone=0 after codegen_typealias_body() leaves the type
17019        // alias object on the stack. The final RETURN_VALUE keeps LOC(type alias).
17020        assert_eq!(
17021            type_params.linetable.as_ref(),
17022            &[
17023                0xf8, 0x80, 0x00, 0x80, 0x0d, 0x84, 0x71, 0x87, 0x0d, 0x81, 0x0d
17024            ],
17025        );
17026    }
17027
17028    #[test]
17029    fn type_param_bound_scope_has_single_return_like_cpython() {
17030        let code = compile_exec("type Alias[T: int] = T\n");
17031        let type_params =
17032            find_code(&code, "<generic parameters of Alias>").expect("missing type params code");
17033        let bound = find_direct_child_code(type_params, "T").expect("missing T bound code");
17034        let return_count = bound
17035            .instructions
17036            .iter()
17037            .filter(|unit| matches!(unit.op, Instruction::ReturnValue))
17038            .count();
17039        assert_eq!(
17040            return_count, 1,
17041            "CPython codegen_type_param_bound_or_default() emits one explicit RETURN_VALUE before OptimizeAndAssemble(addNone=1), got instructions={:?}",
17042            bound.instructions
17043        );
17044    }
17045
17046    #[test]
17047    fn class_body_scope_has_single_return_like_cpython() {
17048        let code = compile_exec("class C:\n    pass\n");
17049        let class_code = find_code(&code, "C").expect("missing class code");
17050        let return_count = class_code
17051            .instructions
17052            .iter()
17053            .filter(|unit| matches!(unit.op, Instruction::ReturnValue))
17054            .count();
17055        assert_eq!(
17056            return_count, 1,
17057            "CPython codegen_class_body() emits one explicit RETURN_VALUE before OptimizeAndAssemble(addNone=1), got instructions={:?}",
17058            class_code.instructions
17059        );
17060    }
17061
17062    #[test]
17063    fn generic_function_annotation_scope_uses_function_location_like_cpython() {
17064        let code = compile_exec("def f[T](x: int): ...\n");
17065        let type_params =
17066            find_code(&code, "<generic parameters of f>").expect("missing type params code");
17067        let annotate =
17068            find_direct_child_code(type_params, "__annotate__").expect("missing annotation code");
17069
17070        // CPython 3.14 passes LOC(function) into codegen_function_annotations(),
17071        // even when the annotation closure is emitted inside the generic
17072        // parameters scope after codegen_type_params().
17073        assert_eq!(
17074            annotate.linetable.as_ref(),
17075            &[
17076                0x80, 0x00, 0xd7, 0x00, 0x15, 0xd1, 0x00, 0x15, 0x8c, 0x43, 0xd1, 0x00, 0x15,
17077            ],
17078        );
17079    }
17080
17081    #[test]
17082    fn decorated_generic_function_type_params_use_decorator_firstlineno_like_cpython() {
17083        let code = compile_exec(
17084            "\
17085def deco(obj): return obj
17086@deco
17087def f[T](): pass
17088",
17089        );
17090        let type_params =
17091            find_code(&code, "<generic parameters of f>").expect("missing type params code");
17092
17093        // codegen_function() passes firstlineno, not LOC(s).lineno, to
17094        // the generic-parameters scope.
17095        assert_eq!(type_params.first_line_number.unwrap().get(), 2);
17096    }
17097
17098    #[test]
17099    fn generic_class_type_params_store_uses_class_location_like_cpython() {
17100        let code = compile_exec(
17101            "\
17102def outer():
17103    class X[T]: ...
17104",
17105        );
17106        let type_params =
17107            find_code(&code, "<generic parameters of X>").expect("missing type params code");
17108
17109        // CPython 3.14 codegen_class() calls codegen_type_params(), then stores
17110        // the resulting .type_params cell with codegen_nameop(c, LOC(class), ...).
17111        assert_eq!(
17112            type_params.linetable.as_ref(),
17113            &[
17114                0xf8, 0x80, 0x00, 0x8c, 0x41, 0x87, 0x4f, 0x87, 0x4f, 0x80, 0x4f,
17115            ]
17116        );
17117    }
17118
17119    #[test]
17120    fn generic_class_wrapper_ops_use_class_location_like_cpython() {
17121        let code = compile_exec(
17122            "\
17123def f():
17124    class X[T](tuple):
17125        pass
17126",
17127        );
17128        let f = find_code(&code, "f").expect("missing function code");
17129        let wrapper_positions: Vec<_> = f
17130            .instructions
17131            .iter()
17132            .filter(|unit| !matches!(unit.op, Instruction::Resume { .. }))
17133            .take(4)
17134            .zip(f.locations.iter().filter(|_| true).skip(1))
17135            .map(|(unit, (location, end_location))| {
17136                (
17137                    unit.op,
17138                    location.line.get(),
17139                    location.character_offset.get(),
17140                    end_location.line.get(),
17141                    end_location.character_offset.get(),
17142                )
17143            })
17144            .collect();
17145        assert_eq!(
17146            wrapper_positions
17147                .iter()
17148                .map(|(_, line, col, end_line, end_col)| (*line, *col, *end_line, *end_col))
17149                .collect::<Vec<_>>(),
17150            vec![(2, 5, 3, 13); 4],
17151            "CPython codegen_class() emits type-params wrapper closure, PUSH_NULL, and CALL at LOC(class)"
17152        );
17153
17154        let type_params =
17155            find_code(f, "<generic parameters of X>").expect("missing generic parameters code");
17156        let generic_base_position = type_params
17157            .instructions
17158            .iter()
17159            .zip(&type_params.locations)
17160            .find_map(|(unit, (location, end_location))| {
17161                let Instruction::LoadFastBorrow { var_num } = unit.op else {
17162                    return None;
17163                };
17164                let idx = var_num.get(OpArg::new(u32::from(u8::from(unit.arg))));
17165                let localsplus = type_params
17166                    .varnames
17167                    .iter()
17168                    .chain(type_params.cellvars.iter())
17169                    .chain(type_params.freevars.iter())
17170                    .collect::<Vec<_>>();
17171                localsplus
17172                    .get(usize::from(idx))
17173                    .is_some_and(|name| name.as_str() == ".generic_base")
17174                    .then_some((
17175                        location.line.get(),
17176                        location.character_offset.get(),
17177                        end_location.line.get(),
17178                        end_location.character_offset.get(),
17179                    ))
17180            })
17181            .expect("missing .generic_base load");
17182        assert_eq!(
17183            generic_base_position,
17184            (2, 5, 3, 13),
17185            "CPython codegen_class() injects .generic_base with LOC(class)"
17186        );
17187    }
17188
17189    #[test]
17190    fn try_except_else_finally_child_scopes_follow_cpython_symbol_order() {
17191        let code = compile_exec(
17192            "\
17193def f(x):
17194    try:
17195        pass
17196    except Exception:
17197        y = 1
17198        def h():
17199            return y
17200    else:
17201        def e():
17202            return x
17203    finally:
17204        def z():
17205            return x
17206",
17207        );
17208        let f = find_code(&code, "f").expect("missing function code");
17209        let h = find_code(f, "h").expect("missing handler function code");
17210        let e = find_code(f, "e").expect("missing else function code");
17211        let z = find_code(f, "z").expect("missing finally function code");
17212
17213        assert_eq!(
17214            h.freevars
17215                .iter()
17216                .map(|name| name.as_str())
17217                .collect::<Vec<_>>(),
17218            vec!["y"],
17219            "handler child scope should consume the handler symbol table"
17220        );
17221        assert_eq!(
17222            e.freevars
17223                .iter()
17224                .map(|name| name.as_str())
17225                .collect::<Vec<_>>(),
17226            vec!["x"],
17227            "else child scope should be consumed before handler scopes, matching CPython codegen_try_except()"
17228        );
17229        assert_eq!(
17230            z.freevars
17231                .iter()
17232                .map(|name| name.as_str())
17233                .collect::<Vec<_>>(),
17234            vec!["x"],
17235            "finally child scope should remain after body/else/handler scopes"
17236        );
17237    }
17238
17239    #[test]
17240    fn try_star_child_scopes_follow_codegen_order_like_cpython() {
17241        let code = compile_exec(
17242            "\
17243def f(x):
17244    try:
17245        pass
17246    except* Exception:
17247        y = 1
17248        def h():
17249            return y
17250    else:
17251        def e():
17252            return x
17253",
17254        );
17255        let f = find_code(&code, "f").expect("missing function code");
17256        let h = find_code(f, "h").expect("missing except* handler function code");
17257        let e = find_code(f, "e").expect("missing else function code");
17258
17259        assert_eq!(
17260            h.freevars
17261                .iter()
17262                .map(|name| name.as_str())
17263                .collect::<Vec<_>>(),
17264            vec!["y"],
17265            "except* handler child scope should consume handler symbol table before else"
17266        );
17267        assert_eq!(
17268            e.freevars
17269                .iter()
17270                .map(|name| name.as_str())
17271                .collect::<Vec<_>>(),
17272            vec!["x"],
17273            "except* else child scope should be consumed after handler scopes, matching CPython codegen_try_star_except()"
17274        );
17275    }
17276
17277    #[test]
17278    fn function_default_and_decorator_child_scopes_follow_cpython_symbol_order() {
17279        fn direct_child_codes<'a>(code: &'a CodeObject, name: &str) -> Vec<&'a CodeObject> {
17280            code.constants
17281                .iter()
17282                .filter_map(|constant| {
17283                    if let ConstantData::Code { code } = constant
17284                        && code.obj_name == name
17285                    {
17286                        Some(code.as_ref())
17287                    } else {
17288                        None
17289                    }
17290                })
17291                .collect()
17292        }
17293
17294        let code = compile_exec(
17295            "\
17296def outer(x, deco):
17297    @(lambda f: deco(f))
17298    def inner(a=(lambda: x)()):
17299        return a
17300",
17301        );
17302        let outer = find_code(&code, "outer").expect("missing outer function code");
17303        let lambdas = direct_child_codes(outer, "<lambda>");
17304
17305        assert_eq!(lambdas.len(), 2);
17306        assert_eq!(
17307            lambdas[0]
17308                .freevars
17309                .iter()
17310                .map(|name| name.as_str())
17311                .collect::<Vec<_>>(),
17312            vec!["deco"],
17313            "decorator lambda is emitted first by codegen_function()"
17314        );
17315        assert_eq!(
17316            lambdas[1]
17317                .freevars
17318                .iter()
17319                .map(|name| name.as_str())
17320                .collect::<Vec<_>>(),
17321            vec!["x"],
17322            "default lambda should still consume the default symbol table"
17323        );
17324    }
17325
17326    #[test]
17327    fn decorated_generic_class_type_params_use_decorator_firstlineno_like_cpython() {
17328        let code = compile_exec(
17329            "\
17330def deco(obj): return obj
17331@deco
17332class C[T]: pass
17333",
17334        );
17335        let type_params =
17336            find_code(&code, "<generic parameters of C>").expect("missing type params code");
17337
17338        // codegen_class() also enters the generic-parameters scope with
17339        // firstlineno, which is the first decorator line when decorators exist.
17340        assert_eq!(type_params.first_line_number.unwrap().get(), 2);
17341    }
17342
17343    #[test]
17344    fn class_deferred_annotations_use_class_body_location_like_cpython() {
17345        let code = compile_exec(
17346            r#"
17347class C:
17348    "doc"
17349    x: int
17350"#,
17351        );
17352        let class_code = find_code(&code, "C").expect("missing class code");
17353
17354        // CPython 3.14 calls codegen_body(c, loc, ...) from codegen_class_body()
17355        // with LOCATION(firstlineno, firstlineno, 0, 0). Deferred annotation
17356        // closure setup and following artificial class tail inherit that class
17357        // body location, not the annotation expression location.
17358        assert_eq!(
17359            class_code.linetable.as_ref(),
17360            &[
17361                0xf8, 0x87, 0x00, 0x80, 0x00, 0xd9, 0x04, 0x09, 0xf7, 0x03, 0x00, 0x01, 0x01, 0x83,
17362                0x00,
17363            ],
17364        );
17365    }
17366
17367    #[test]
17368    fn future_annotation_string_uses_annotation_location_like_cpython() {
17369        let code = compile_exec("from __future__ import annotations\nclass Bar:\n    foo: Foo\n");
17370        let class_code = find_code(&code, "Bar").expect("missing class code");
17371
17372        // CPython 3.14 codegen_annassign() calls codegen_visit_annexpr(),
17373        // which emits the stringized annotation at LOC(annotation), then emits
17374        // the __annotations__ store sequence at LOC(AnnAssign).
17375        assert_eq!(
17376            class_code.linetable.as_ref(),
17377            &[0x87, 0x00, 0xd8, 0x09, 0x0c, 0x87, 0x48]
17378        );
17379    }
17380
17381    #[test]
17382    #[expect(
17383        clippy::literal_string_with_formatting_args,
17384        reason = "the literal is the expected t-string annotation"
17385    )]
17386    fn future_tstring_annotation_preserves_interpolation_source_like_cpython() {
17387        let code = compile_exec(
17388            "from __future__ import annotations\nx: t'{a    +  b}'\ny: t'{ a + b }'\nz: f'{a    + b =}'\nu: t'{a    + b =}'\nv: t'{a    + b =:>10}'\np: t'{(a)}'\nq: t'{((a))!r}'\nr: t'{ ((a)) = !r:>10}'\ns: t'{(a)=}'\nt: t'{a == b = }'\na1: t'''{a= # x=y\n}'''\na2: t'''{a # x=y\n}'''\na3: t'''{(a # x=y\n)}'''\na4: t'''{a # x=y\n!r}'''\na5: t'''{a # x=y\n:>10}'''\na6: t'''{'#'}'''\na7: t'''{('#', a) # c=d\n}'''\n",
17389        );
17390        let annotation_strings = code
17391            .constants
17392            .iter()
17393            .filter_map(|constant| match constant {
17394                ConstantData::Str { value }
17395                    if value.starts_with("t'")
17396                        || value.starts_with("t\"")
17397                        || value.starts_with("f'") =>
17398                {
17399                    Some(value.to_string())
17400                }
17401                _ => None,
17402            })
17403            .collect::<Vec<_>>();
17404        assert_eq!(
17405            annotation_strings,
17406            [
17407                "t'{a    +  b}'",
17408                "t'{ a + b}'",
17409                "f'a    + b ={a + b!r}'",
17410                "t'a    + b ={a    + b!r}'",
17411                "t'a    + b ={a    + b:>10}'",
17412                "t'{(a)}'",
17413                "t'{((a))!r}'",
17414                "t' ((a)) = { ((a))!r:>10}'",
17415                "t'(a)={(a)!r}'",
17416                "t'a == b = {a == b!r}'",
17417                "t'a= \\n{a!r}'",
17418                "t'{a}'",
17419                "t'{(a \\n)}'",
17420                "t'{a!r}'",
17421                "t'{a:>10}'",
17422                "t\"{'#'}\"",
17423                "t\"{('#', a)}\"",
17424            ]
17425        );
17426    }
17427
17428    #[test]
17429    fn lambda_dict_literal_ops_use_dict_location_like_cpython() {
17430        let code = compile_exec(
17431            "\
17432f = lambda data: {'x': data}
17433g = lambda i: {**i}
17434",
17435        );
17436        let f = find_code(&code, "<lambda>").expect("missing f lambda code");
17437        let g = code
17438            .constants
17439            .iter()
17440            .filter_map(|constant| {
17441                if let ConstantData::Code { code } = constant {
17442                    (code.obj_name == "<lambda>").then_some(code.as_ref())
17443                } else {
17444                    None
17445                }
17446            })
17447            .nth(1)
17448            .expect("missing g lambda code");
17449
17450        // CPython 3.14 codegen_dict()/codegen_subdict() uses LOC(dict) for
17451        // BUILD_MAP, MAP_ADD, and DICT_UPDATE, so the lambda RETURN_VALUE
17452        // inherits the full dict literal location after compiling its body.
17453        assert_eq!(
17454            f.linetable.as_ref(),
17455            &[0x80, 0x00, 0x90, 0x23, 0x90, 0x74, 0x91, 0x1b]
17456        );
17457        assert_eq!(
17458            g.linetable.as_ref(),
17459            &[0x80, 0x00, 0x88, 0x65, 0x90, 0x11, 0x89, 0x65]
17460        );
17461    }
17462
17463    #[test]
17464    fn dict_unpacking_large_regular_run_uses_subdict_chunks_like_cpython() {
17465        let pairs = (0..17)
17466            .map(|i| format!("{i}: {i}"))
17467            .collect::<Vec<_>>()
17468            .join(", ");
17469        let source = format!("def f(x):\n    return {{{pairs}, **x}}\n");
17470        let code = compile_exec(&source);
17471        let f = find_code(&code, "f").expect("missing f code");
17472        let first_dict_update = f
17473            .instructions
17474            .iter()
17475            .position(|unit| matches!(unit.op, Instruction::DictUpdate { .. }))
17476            .expect("missing DICT_UPDATE");
17477        let prefix = &f.instructions[..first_dict_update];
17478        let build_map_args: Vec<_> = prefix
17479            .iter()
17480            .filter_map(|unit| {
17481                matches!(unit.op, Instruction::BuildMap { .. }).then_some(u8::from(unit.arg))
17482            })
17483            .collect();
17484        let map_adds = prefix
17485            .iter()
17486            .filter(|unit| matches!(unit.op, Instruction::MapAdd { .. }))
17487            .count();
17488
17489        assert_eq!(
17490            build_map_args,
17491            vec![0],
17492            "CPython codegen_dict() routes a 17-pair run before ** through codegen_subdict(), got instructions={:?}",
17493            f.instructions
17494        );
17495        assert_eq!(
17496            map_adds, 17,
17497            "CPython codegen_subdict() uses MAP_ADD for all 17 pairs before **, got instructions={:?}",
17498            f.instructions
17499        );
17500    }
17501
17502    #[test]
17503    fn class_function_like_scopes_set_method_flag_like_cpython() {
17504        let code = compile_exec_with_options(
17505            r#"
17506class C:
17507    def m(self):
17508        pass
17509
17510    async def am(self):
17511        pass
17512
17513    f = lambda self: self
17514    y = (i for i in ())
17515
17516def f():
17517    pass
17518"#,
17519            CompileOpts::default(),
17520        );
17521        let class_code = find_code(&code, "C").expect("missing class code");
17522        let method = find_code(class_code, "m").expect("missing method code");
17523        let async_method = find_code(class_code, "am").expect("missing async method code");
17524        let lambda = find_code(class_code, "<lambda>").expect("missing lambda code");
17525        let genexpr = find_code(class_code, "<genexpr>").expect("missing genexpr code");
17526        let module_function = find_code(&code, "f").expect("missing module function code");
17527
17528        for code in [method, async_method, lambda, genexpr] {
17529            assert!(
17530                code.flags.contains(bytecode::CodeFlags::METHOD),
17531                "class-scope function-like code should carry CO_METHOD like CPython 3.14, got {:?}",
17532                code.flags
17533            );
17534        }
17535        assert!(
17536            !module_function.flags.contains(bytecode::CodeFlags::METHOD),
17537            "module-scope function must not carry CO_METHOD"
17538        );
17539    }
17540
17541    #[test]
17542    fn inlined_comprehension_lambda_in_class_is_not_method_like_cpython() {
17543        let code = compile_exec(
17544            "\
17545class C:
17546    def method(self):
17547        super()
17548        return __class__
17549    items = [(lambda: i) for i in range(5)]
17550",
17551        );
17552        let class_code = find_code(&code, "C").expect("missing class code");
17553        let lambda = find_code(class_code, "<lambda>").expect("missing lambda code");
17554        assert!(
17555            lambda.flags.contains(bytecode::CodeFlags::NESTED),
17556            "lambda under inlined class comprehension should stay nested"
17557        );
17558        assert!(
17559            !lambda.flags.contains(bytecode::CodeFlags::METHOD),
17560            "CPython creates this lambda while the current symtable block is the comprehension, not the class"
17561        );
17562    }
17563
17564    #[test]
17565    fn class_inlined_comprehension_pushes_only_bound_locals_like_cpython() {
17566        let code = compile_exec(
17567            "\
17568class C:
17569    x = 1
17570    items = [x for i in range(3)]
17571",
17572        );
17573        let class_code = find_code(&code, "C").expect("missing class code");
17574        let cleared_names = class_code
17575            .instructions
17576            .iter()
17577            .filter_map(|unit| match unit.op {
17578                Instruction::LoadFastAndClear { var_num } => {
17579                    let idx = var_num.get(OpArg::new(u32::from(u8::from(unit.arg))));
17580                    Some(class_code.varnames[usize::from(idx)].as_str())
17581                }
17582                _ => None,
17583            })
17584            .collect::<Vec<_>>();
17585
17586        assert!(
17587            cleared_names.contains(&"i"),
17588            "the comprehension iteration variable should be isolated, got {cleared_names:?}"
17589        );
17590        assert!(
17591            !cleared_names.contains(&"x"),
17592            "CPython applies the class-block special case while tweaking scopes, but codegen_push_inlined_comprehension_locals() runs after u_in_inlined_comp is set and only clears DEF_LOCAL names; got {cleared_names:?}"
17593        );
17594    }
17595
17596    #[test]
17597    fn genexpr_implicit_iterator_is_not_posonly_like_cpython() {
17598        let code = compile_exec("x = (i for i in ())");
17599        let genexpr = find_code(&code, "<genexpr>").expect("missing genexpr code");
17600
17601        assert_eq!(genexpr.arg_count, 1);
17602        assert_eq!(
17603            genexpr.posonlyarg_count, 0,
17604            "CPython codegen_comprehension() sets u_argcount=1 and leaves u_posonlyargcount=0"
17605        );
17606    }
17607
17608    #[test]
17609    fn posonly_function_argcount_metadata_matches_cpython_assemble_split() {
17610        let code = compile_exec("def f(a, /, b):\n    pass\n");
17611        let func = find_code(&code, "f").expect("missing function code");
17612
17613        assert_eq!(
17614            func.arg_count, 2,
17615            "CPython assemble.c exposes co_argcount as u_posonlyargcount + u_argcount"
17616        );
17617        assert_eq!(func.posonlyarg_count, 1);
17618        assert_eq!(func.varnames.as_ref(), &["a".to_owned(), "b".to_owned()]);
17619        assert_eq!(
17620            func.localspluskinds[0] & (CO_FAST_ARG_POS | CO_FAST_ARG_KW),
17621            CO_FAST_ARG_POS,
17622            "CPython compute_localsplus_info marks only u_posonlyargcount slots as positional-only"
17623        );
17624        assert_eq!(
17625            func.localspluskinds[1] & (CO_FAST_ARG_POS | CO_FAST_ARG_KW),
17626            CO_FAST_ARG_POS | CO_FAST_ARG_KW,
17627            "CPython compute_localsplus_info marks u_argcount slots after posonly as positional-or-keyword"
17628        );
17629    }
17630
17631    #[test]
17632    fn async_generator_uses_cpython_async_generator_flag() {
17633        let code = compile_exec_with_options(
17634            r#"
17635def g():
17636    yield 1
17637
17638async def c():
17639    return 1
17640
17641async def ag():
17642    yield 1
17643"#,
17644            CompileOpts::default(),
17645        );
17646        let generator = find_code(&code, "g").expect("missing generator code");
17647        let coroutine = find_code(&code, "c").expect("missing coroutine code");
17648        let async_generator = find_code(&code, "ag").expect("missing async generator code");
17649
17650        assert!(generator.flags.contains(bytecode::CodeFlags::GENERATOR));
17651        assert!(!generator.flags.contains(bytecode::CodeFlags::COROUTINE));
17652        assert!(
17653            !generator
17654                .flags
17655                .contains(bytecode::CodeFlags::ASYNC_GENERATOR)
17656        );
17657
17658        assert!(coroutine.flags.contains(bytecode::CodeFlags::COROUTINE));
17659        assert!(!coroutine.flags.contains(bytecode::CodeFlags::GENERATOR));
17660        assert!(
17661            !coroutine
17662                .flags
17663                .contains(bytecode::CodeFlags::ASYNC_GENERATOR)
17664        );
17665
17666        assert!(
17667            async_generator
17668                .flags
17669                .contains(bytecode::CodeFlags::ASYNC_GENERATOR)
17670        );
17671        assert!(
17672            !async_generator
17673                .flags
17674                .contains(bytecode::CodeFlags::GENERATOR)
17675        );
17676        assert!(
17677            !async_generator
17678                .flags
17679                .contains(bytecode::CodeFlags::COROUTINE)
17680        );
17681    }
17682
17683    #[test]
17684    fn is_none_jump_preserves_cpython_const_order() {
17685        let code = compile_exec_with_options(
17686            r#"
17687def f(self, payload):
17688    "doc"
17689    if self.x is None:
17690        self.x = [payload]
17691    else:
17692        raise TypeError("bad")
17693"#,
17694            CompileOpts::default(),
17695        );
17696        let function = find_code(&code, "f").expect("missing function code");
17697        assert!(
17698            matches!(
17699                function.constants.as_ref(),
17700                [
17701                    ConstantData::Str { value: doc },
17702                    ConstantData::None,
17703                    ConstantData::Str { value: message },
17704                ] if doc.as_ref() == "doc" && message.as_ref() == "bad"
17705            ),
17706            "CPython registers None from the pre-folded `is None` comparison before the else-body string"
17707        );
17708    }
17709
17710    #[test]
17711    fn stop_iteration_handler_starts_at_scope_start_resume_like_cpython() {
17712        let code = compile_exec_with_options(
17713            r#"
17714def g():
17715    yield 1
17716
17717async def c():
17718    return 1
17719
17720x = (i for i in ())
17721"#,
17722            CompileOpts::default(),
17723        );
17724
17725        fn assert_stop_iteration_table_starts_at_resume(code: &CodeObject) {
17726            let resume_idx = u32::try_from(
17727                code.instructions
17728                    .iter()
17729                    .position(|unit| {
17730                        matches!(
17731                            unit.op,
17732                            Instruction::Resume { context }
17733                                if matches!(
17734                                    context
17735                                        .get(OpArg::new(u32::from(u8::from(unit.arg))))
17736                                        .location(),
17737                                    oparg::ResumeLocation::AtFuncStart
17738                                )
17739                        )
17740                    })
17741                    .expect("missing function-start RESUME"),
17742            )
17743            .unwrap();
17744            let entries = bytecode::decode_exception_table(&code.exceptiontable);
17745            assert!(
17746                entries.iter().any(|entry| entry.start == resume_idx),
17747                "CPython codegen_wrap_in_stopiteration_handler() inserts SETUP_CLEANUP before RESUME so the StopIteration table starts at RESUME; resume_idx={resume_idx}, entries={entries:?}, instructions={:?}",
17748                code.instructions
17749            );
17750        }
17751
17752        assert_stop_iteration_table_starts_at_resume(find_code(&code, "g").expect("missing g"));
17753        assert_stop_iteration_table_starts_at_resume(find_code(&code, "c").expect("missing c"));
17754        assert_stop_iteration_table_starts_at_resume(
17755            find_code(&code, "<genexpr>").expect("missing genexpr"),
17756        );
17757    }
17758
17759    #[test]
17760    fn inlined_comprehension_cleanup_starts_at_result_build_like_cpython() {
17761        let code = compile_exec_with_options(
17762            r#"
17763def f(self):
17764    return [k for k, v in self._headers]
17765"#,
17766            CompileOpts::default(),
17767        );
17768        let f = find_code(&code, "f").expect("missing f");
17769        let build_list_idx = u32::try_from(
17770            f.instructions
17771                .iter()
17772                .position(|unit| matches!(unit.op, Instruction::BuildList { .. }))
17773                .expect("missing BUILD_LIST"),
17774        )
17775        .unwrap();
17776        let entries = bytecode::decode_exception_table(&f.exceptiontable);
17777        assert!(
17778            entries.iter().any(|entry| {
17779                entry.start == build_list_idx && entry.depth == 3 && !entry.push_lasti
17780            }),
17781            "CPython codegen_push_inlined_comprehension_locals() emits SETUP_FINALLY before BUILD_LIST, so the virtual cleanup table starts at BUILD_LIST with saved locals depth; build_list_idx={build_list_idx}, entries={entries:?}, instructions={:?}",
17782            f.instructions
17783        );
17784    }
17785
17786    #[test]
17787    fn or_return_not_taken_before_jump_target_splits_exception_table_like_cpython() {
17788        let code = compile_exec_with_options(
17789            r#"
17790def f(self, maintype):
17791    if maintype != "multipart" or not self.is_multipart():
17792        return
17793    yield 1
17794"#,
17795            CompileOpts::default(),
17796        );
17797        let f = find_code(&code, "f").expect("missing f");
17798        let not_taken_before_return = u32::try_from(
17799            f.instructions
17800                .windows(3)
17801                .position(|window| {
17802                    matches!(
17803                        window,
17804                        [
17805                            CodeUnit {
17806                                op: Instruction::NotTaken,
17807                                ..
17808                            },
17809                            CodeUnit {
17810                                op: Instruction::LoadConst { .. },
17811                                ..
17812                            },
17813                            CodeUnit {
17814                                op: Instruction::ReturnValue,
17815                                ..
17816                            },
17817                        ]
17818                    )
17819                })
17820                .expect("missing NOT_TAKEN before return"),
17821        )
17822        .unwrap();
17823        let return_load = not_taken_before_return + 1;
17824        let entries = bytecode::decode_exception_table(&f.exceptiontable);
17825
17826        assert!(
17827            entries.iter().all(|entry| {
17828                not_taken_before_return < entry.start || not_taken_before_return >= entry.end
17829            }),
17830            "CPython normalize_jumps() can leave a NOT_TAKEN before a separately labelled jump target outside the generator StopIteration range; entries={entries:?}, instructions={:?}",
17831            f.instructions
17832        );
17833        assert!(
17834            entries
17835                .iter()
17836                .any(|entry| entry.start <= return_load && return_load < entry.end),
17837            "the return block after that NOT_TAKEN is still protected by the StopIteration handler; entries={entries:?}, instructions={:?}",
17838            f.instructions
17839        );
17840    }
17841
17842    #[test]
17843    fn loop_break_condition_splits_exception_table_like_cpython() {
17844        let code = compile_exec_with_options(
17845            r#"
17846def f(start, items):
17847    if start:
17848        for x in items:
17849            if x == start:
17850                break
17851    yield 1
17852"#,
17853            CompileOpts::default(),
17854        );
17855        let f = find_code(&code, "f").expect("missing f");
17856        let break_jump = u32::try_from(
17857            f.instructions
17858                .windows(3)
17859                .position(|window| {
17860                    matches!(
17861                        window,
17862                        [
17863                            CodeUnit {
17864                                op: Instruction::PopJumpIfTrue { .. },
17865                                ..
17866                            },
17867                            CodeUnit {
17868                                op: Instruction::Cache,
17869                                ..
17870                            },
17871                            CodeUnit {
17872                                op: Instruction::NotTaken,
17873                                ..
17874                            },
17875                        ]
17876                    ) || matches!(
17877                        window,
17878                        [
17879                            CodeUnit {
17880                                op: Instruction::PopJumpIfTrue { .. },
17881                                ..
17882                            },
17883                            CodeUnit {
17884                                op: Instruction::NotTaken,
17885                                ..
17886                            },
17887                            CodeUnit {
17888                                op: Instruction::JumpBackward { .. },
17889                                ..
17890                            },
17891                        ]
17892                    )
17893                })
17894                .expect("missing loop break conditional jump"),
17895        )
17896        .unwrap();
17897        let entries = bytecode::decode_exception_table(&f.exceptiontable);
17898
17899        assert!(
17900            entries
17901                .iter()
17902                .all(|entry| break_jump < entry.start || break_jump >= entry.end),
17903            "CPython normalize_jumps() leaves the loop-break conditional before the synthetic NOT_TAKEN/JUMP_BACKWARD block outside the StopIteration table; break_jump={break_jump}, entries={entries:?}, instructions={:?}",
17904            f.instructions
17905        );
17906    }
17907
17908    #[test]
17909    fn nested_ifexp_not_taken_splits_exception_table_like_cpython() {
17910        let code = compile_exec_with_options(
17911            r#"
17912def f(flag, subparts):
17913    if flag:
17914        candidate = subparts[0] if subparts else None
17915    yield 1
17916"#,
17917            CompileOpts::default(),
17918        );
17919        let f = find_code(&code, "f").expect("missing f");
17920        let conditional_expr_not_taken = u32::try_from(
17921            f.instructions
17922                .iter()
17923                .enumerate()
17924                .find_map(|(idx, unit)| {
17925                    if !matches!(unit.op, Instruction::NotTaken) {
17926                        return None;
17927                    }
17928                    let prev = f.instructions[..idx]
17929                        .iter()
17930                        .rev()
17931                        .find(|unit| !matches!(unit.op, Instruction::Cache))?;
17932                    let mut following = f.instructions[idx + 1..]
17933                        .iter()
17934                        .filter(|unit| !matches!(unit.op, Instruction::Cache));
17935                    let next = following.next()?;
17936                    let after_next = following.next()?;
17937                    (matches!(prev.op, Instruction::PopJumpIfFalse { .. })
17938                        && matches!(next.op, Instruction::LoadFastBorrow { .. })
17939                        && matches!(after_next.op, Instruction::LoadSmallInt { .. }))
17940                    .then_some(idx)
17941                })
17942                .expect("missing conditional expression NOT_TAKEN"),
17943        )
17944        .unwrap();
17945        let body_start = conditional_expr_not_taken + 1;
17946        let entries = bytecode::decode_exception_table(&f.exceptiontable);
17947
17948        assert!(
17949            entries.iter().all(|entry| {
17950                conditional_expr_not_taken < entry.start || conditional_expr_not_taken >= entry.end
17951            }),
17952            "CPython codegen_ifexp() uses a separate orelse label inside conditional statements, leaving the normalize_jumps NOT_TAKEN outside the StopIteration table; not_taken={conditional_expr_not_taken}, entries={entries:?}, instructions={:?}",
17953            f.instructions
17954        );
17955        assert!(
17956            entries
17957                .iter()
17958                .any(|entry| entry.start <= body_start && body_start < entry.end),
17959            "the conditional-expression body after that NOT_TAKEN remains protected; body_start={body_start}, entries={entries:?}, instructions={:?}",
17960            f.instructions
17961        );
17962    }
17963
17964    #[test]
17965    fn bool_not_taken_after_conditional_yield_splits_like_cpython() {
17966        let code = compile_exec_with_options(
17967            r#"
17968def f(a, b, c):
17969    if a:
17970        yield 1
17971    if b:
17972        x = 2
17973    if c:
17974        x = 3
17975    yield 4
17976"#,
17977            CompileOpts::default(),
17978        );
17979        let f = find_code(&code, "f").expect("missing f");
17980        let split_not_taken = f
17981            .instructions
17982            .iter()
17983            .enumerate()
17984            .filter_map(|(idx, unit)| {
17985                if !matches!(unit.op, Instruction::NotTaken) {
17986                    return None;
17987                }
17988                let prev = f.instructions[..idx]
17989                    .iter()
17990                    .rev()
17991                    .find(|unit| !matches!(unit.op, Instruction::Cache))?;
17992                matches!(
17993                    prev.op,
17994                    Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
17995                )
17996                .then(|| u32::try_from(idx).unwrap())
17997            })
17998            .nth(1)
17999            .expect("missing second bool conditional NOT_TAKEN");
18000        let entries = bytecode::decode_exception_table(&f.exceptiontable);
18001
18002        assert!(
18003            entries
18004                .iter()
18005                .all(|entry| split_not_taken < entry.start || split_not_taken >= entry.end),
18006            "CPython labels exception targets before normalize_jumps(), so the general bool-jump NOT_TAKEN after a conditional yield is outside the StopIteration table; not_taken={split_not_taken}, entries={entries:?}, instructions={:?}",
18007            f.instructions
18008        );
18009    }
18010
18011    fn non_cache_instructions(code: &CodeObject) -> impl Iterator<Item = &CodeUnit> {
18012        code.instructions
18013            .iter()
18014            .filter(|unit| !matches!(unit.op, Instruction::Cache))
18015    }
18016
18017    fn full_opargs_for(
18018        code: &CodeObject,
18019        mut predicate: impl FnMut(Instruction) -> bool,
18020    ) -> Vec<u32> {
18021        let mut extended = 0u32;
18022        let mut args = Vec::new();
18023        for unit in non_cache_instructions(code) {
18024            let byte = u32::from(u8::from(unit.arg));
18025            if matches!(unit.op, Instruction::ExtendedArg) {
18026                extended = (extended << 8) | byte;
18027                continue;
18028            }
18029            let oparg = (extended << 8) | byte;
18030            extended = 0;
18031            if predicate(unit.op) {
18032                args.push(oparg);
18033            }
18034        }
18035        args
18036    }
18037
18038    fn varname_index(code: &CodeObject, name: &str) -> usize {
18039        code.varnames
18040            .iter()
18041            .position(|varname| varname.as_str() == name)
18042            .unwrap_or_else(|| panic!("missing {name} local"))
18043    }
18044
18045    fn load_fast_ops_for_var(code: &CodeObject, name: &str) -> Vec<Instruction> {
18046        let var_idx = varname_index(code, name);
18047        non_cache_instructions(code)
18048            .filter_map(|unit| match unit.op {
18049                Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
18050                    let var_num = var_num.get(OpArg::new(u32::from(u8::from(unit.arg))));
18051                    (usize::from(var_num) == var_idx).then_some(unit.op)
18052                }
18053                _ => None,
18054            })
18055            .collect()
18056    }
18057
18058    fn load_fast_pair_ops_for_vars(
18059        code: &CodeObject,
18060        left_name: &str,
18061        right_name: &str,
18062    ) -> Vec<Instruction> {
18063        let left_idx = varname_index(code, left_name);
18064        let right_idx = varname_index(code, right_name);
18065        non_cache_instructions(code)
18066            .filter_map(|unit| {
18067                let var_nums = match unit.op {
18068                    Instruction::LoadFastLoadFast { var_nums }
18069                    | Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => var_nums,
18070                    _ => return None,
18071                };
18072                let (left, right) = var_nums
18073                    .get(OpArg::new(u32::from(u8::from(unit.arg))))
18074                    .indexes();
18075                (usize::from(left) == left_idx && usize::from(right) == right_idx)
18076                    .then_some(unit.op)
18077            })
18078            .collect()
18079    }
18080
18081    fn count_strong_loads_for_vars(code: &CodeObject, names: &[&str]) -> usize {
18082        let var_indices = names
18083            .iter()
18084            .map(|name| varname_index(code, name))
18085            .collect::<Vec<_>>();
18086        non_cache_instructions(code)
18087            .filter(|unit| match unit.op {
18088                Instruction::LoadFast { var_num } => {
18089                    let var_num = var_num.get(OpArg::new(u32::from(u8::from(unit.arg))));
18090                    var_indices.contains(&usize::from(var_num))
18091                }
18092                _ => false,
18093            })
18094            .count()
18095    }
18096
18097    fn count_strong_loads(code: &CodeObject) -> usize {
18098        non_cache_instructions(code)
18099            .filter(|unit| matches!(unit.op, Instruction::LoadFast { .. }))
18100            .count()
18101    }
18102
18103    #[test]
18104    fn match_or_default_block_keeps_load_fast_strong() {
18105        let code = compile_exec(
18106            r#"
18107def f(format, other):
18108    match format:
18109        case 1 | 2:
18110            return other
18111        case _:
18112            raise NotImplementedError(other)
18113"#,
18114        );
18115        let function = find_code(&code, "f").expect("missing function code");
18116        let loads = load_fast_ops_for_var(function, "other");
18117        assert!(
18118            matches!(
18119                loads.as_slice(),
18120                [
18121                    Instruction::LoadFastBorrow { .. },
18122                    Instruction::LoadFastBorrow { .. },
18123                    Instruction::LoadFast { .. },
18124                ]
18125            ),
18126            "CPython optimize_load_fast() keeps trailing OR-pattern default loads strong, got {loads:?}",
18127        );
18128    }
18129
18130    #[test]
18131    fn match_nested_or_default_block_keeps_load_fast_strong() {
18132        let code = compile_exec(
18133            r#"
18134def f(format, other):
18135    match format:
18136        case [1 | 2, value]:
18137            return other
18138        case _:
18139            raise NotImplementedError(other)
18140"#,
18141        );
18142        let function = find_code(&code, "f").expect("missing function code");
18143        let loads = load_fast_ops_for_var(function, "other");
18144        assert!(
18145            loads
18146                .iter()
18147                .all(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
18148            "CPython 3.14 optimize_load_fast() borrows nested OR-pattern default loads, got {loads:?}",
18149        );
18150    }
18151
18152    #[test]
18153    fn match_success_next_location_preserves_pass_nop() {
18154        let code = compile_exec(
18155            r#"
18156def f(command):
18157    match command:
18158        case "":
18159            pass
18160        case _ as unknown:
18161            sink(unknown)
18162    return False
18163"#,
18164        );
18165        let function = find_code(&code, "f").expect("missing function code");
18166        let ops = non_cache_instructions(function)
18167            .map(|unit| unit.op)
18168            .collect::<Vec<_>>();
18169        assert!(
18170            ops.windows(3).any(|window| matches!(
18171                window,
18172                [
18173                    Instruction::PopTop,
18174                    Instruction::Nop,
18175                    Instruction::LoadConst { .. },
18176                ]
18177            )),
18178            "CPython NEXT_LOCATION keeps the pass NOP after match subject POP_TOP, got {ops:?}",
18179        );
18180    }
18181
18182    #[test]
18183    fn match_subject_copy_uses_case_pattern_location_like_cpython() {
18184        let code = compile_exec(
18185            "\
18186def f(x):
18187    match x:
18188        case 1:
18189            return True
18190        case 2:
18191            return False
18192",
18193        );
18194        let f = find_code(&code, "f").expect("missing f code");
18195        let copy_line = f
18196            .instructions
18197            .iter()
18198            .zip(&f.locations)
18199            .find_map(|(unit, (location, _))| {
18200                let Instruction::Copy { i } = unit.op else {
18201                    return None;
18202                };
18203                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
18204                (i.get(arg) == 1).then_some(location.line.get())
18205            })
18206            .expect("missing match subject COPY");
18207        assert_eq!(
18208            copy_line, 3,
18209            "CPython codegen_match_inner() emits ADDOP_I(c, LOC(m->pattern), COPY, 1)"
18210        );
18211    }
18212
18213    #[test]
18214    fn match_or_alternative_copies_use_alternative_locations_like_cpython() {
18215        let code = compile_exec(
18216            "\
18217def f():
18218    x = False
18219    match 0:
18220        case 0 | 1 | 2 | 3:
18221            x = True
18222    return x
18223",
18224        );
18225        let f = find_code(&code, "f").expect("missing f code");
18226        assert_eq!(
18227            f.linetable.as_ref(),
18228            &[
18229                0x80, 0x00, 0xd8, 0x08, 0x0d, 0x80, 0x41, 0xd8, 0x0a, 0x0b, 0xdf, 0x0d, 0x0e, 0x97,
18230                0x11, 0x97, 0x51, 0x9f, 0x11, 0x88, 0x5d, 0xe0, 0x0b, 0x0c, 0x80, 0x48, 0xf0, 0x05,
18231                0x00, 0x0e, 0x1b, 0xd8, 0x10, 0x14, 0x88, 0x41, 0xd8, 0x0b, 0x0c, 0x80, 0x48,
18232            ],
18233            "CPython codegen_pattern_or() emits each alternative COPY with LOC(alt)"
18234        );
18235    }
18236
18237    #[test]
18238    fn match_or_conflicting_bind_error_uses_or_pattern_location_like_cpython() {
18239        let error = compile_exec_error(
18240            "\
18241def f(x):
18242    match x:
18243        case (
18244            a
18245            | b
18246        ):
18247            pass
18248",
18249        );
18250        let location = error.location.expect("missing error location");
18251        assert_eq!(
18252            location.line.get(),
18253            4,
18254            "CPython codegen_pattern_or() reports alternative binding mismatches at LOC(p), not LOC(alt)"
18255        );
18256    }
18257
18258    #[test]
18259    fn match_or_duplicate_store_error_uses_or_pattern_location_like_cpython() {
18260        let error = compile_exec_error(
18261            "\
18262def f(value):
18263    match value:
18264        case [
18265            x,
18266            (x | x),
18267        ]:
18268            pass
18269",
18270        );
18271        let location = error.location.expect("missing error location");
18272        assert_eq!(
18273            location.line.get(),
18274            5,
18275            "CPython codegen_pattern_or() reports merge-time duplicate stores at LOC(p)"
18276        );
18277    }
18278
18279    #[test]
18280    fn match_success_jump_uses_no_location_like_cpython() {
18281        let code = compile_exec(
18282            "\
18283def f(self):
18284    match 0:
18285        case 0:
18286            x = True
18287        case 0:
18288            x = False
18289    self.assertIs(x, True)
18290",
18291        );
18292        let f = find_code(&code, "f").expect("missing f code");
18293        assert_eq!(
18294            f.linetable.as_ref(),
18295            &[
18296                0x80, 0x00, 0xd8, 0x0a, 0x0b, 0xde, 0x0d, 0x0e, 0xd9, 0x10, 0x14, 0x89, 0x41, 0xdd,
18297                0x0d, 0x0e, 0xd8, 0x10, 0x15, 0x88, 0x41, 0xd8, 0x04, 0x08, 0x87, 0x4d, 0x81, 0x4d,
18298                0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a,
18299            ],
18300            "CPython codegen_match_inner() emits the success jump with NO_LOCATION"
18301        );
18302    }
18303
18304    #[test]
18305    fn match_default_simple_guard_jump_uses_guard_location_like_cpython() {
18306        let code = compile_exec(
18307            "\
18308def f(x, y):
18309    match x:
18310        case 0:
18311            return 1
18312        case _ if y:
18313            return 2
18314    return 3
18315",
18316        );
18317        let f = find_code(&code, "f").expect("missing f code");
18318        let guard_jump_location = f
18319            .instructions
18320            .iter()
18321            .enumerate()
18322            .find_map(|(idx, unit)| {
18323                let (Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }) =
18324                    unit.op
18325                else {
18326                    return None;
18327                };
18328                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
18329                if f.varnames[usize::from(var_num.get(arg))] != "y" {
18330                    return None;
18331                }
18332                f.instructions
18333                    .iter()
18334                    .zip(&f.locations)
18335                    .skip(idx + 1)
18336                    .take(8)
18337                    .find_map(|(unit, (location, _))| {
18338                        matches!(unit.op, Instruction::PopJumpIfFalse { .. }).then_some(*location)
18339                    })
18340            })
18341            .expect("missing default guard jump");
18342
18343        assert_eq!(
18344            (
18345                guard_jump_location.line.get(),
18346                guard_jump_location.character_offset.get()
18347            ),
18348            (5, 19),
18349            "CPython codegen_jump_if() receives LOC(pattern), but the simple guard fallback emits TO_BOOL/jump at LOC(guard)"
18350        );
18351    }
18352
18353    #[test]
18354    fn match_mapping_keys_scaffolding_uses_mapping_location_like_cpython() {
18355        let code = compile_exec(
18356            "\
18357def f(self):
18358    x = {}
18359    y = None
18360    match x:
18361        case {0: 0}:
18362            y = 0
18363    self.assertIs(y, None)
18364",
18365        );
18366        let f = find_code(&code, "f").expect("missing f code");
18367        assert_eq!(
18368            f.linetable.as_ref(),
18369            &[
18370                0x80, 0x00, 0xd8, 0x08, 0x0a, 0x80, 0x41, 0xd8, 0x08, 0x0c, 0x80, 0x41, 0xd8, 0x0a,
18371                0x0b, 0xdf, 0x0d, 0x13, 0x8f, 0x56, 0x8a, 0x56, 0x95, 0x11, 0x89, 0x56, 0xd8, 0x10,
18372                0x11, 0x89, 0x41, 0xf2, 0x03, 0x00, 0x0e, 0x14, 0xe0, 0x04, 0x08, 0x87, 0x4d, 0x81,
18373                0x4d, 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a,
18374            ],
18375            "CPython codegen_pattern_mapping() returns to LOC(p) for BUILD_TUPLE/MATCH_KEYS scaffolding"
18376        );
18377    }
18378
18379    #[test]
18380    fn match_mapping_rest_cleanup_uses_mapping_location_like_cpython() {
18381        let code = compile_exec(
18382            "\
18383def f(x):
18384    match x:
18385        case {
18386            0: _,
18387            **rest,
18388        }:
18389            return rest
18390",
18391        );
18392        let f = find_code(&code, "f").expect("missing f code");
18393        let rest_cleanup_start = f
18394            .instructions
18395            .iter()
18396            .position(|unit| matches!(unit.op, Instruction::BuildMap { .. }))
18397            .expect("missing BUILD_MAP");
18398        for expected in [
18399            "BUILD_MAP",
18400            "DICT_UPDATE",
18401            "DELETE_SUBSCR",
18402            "rest cleanup COPY",
18403            "rest cleanup SWAP",
18404        ] {
18405            let location = f
18406                .instructions
18407                .iter()
18408                .zip(&f.locations)
18409                .skip(rest_cleanup_start)
18410                .find_map(|(unit, (location, _))| {
18411                    let found = matches!(
18412                        (expected, unit.op),
18413                        ("BUILD_MAP", Instruction::BuildMap { .. })
18414                            | ("DICT_UPDATE", Instruction::DictUpdate { .. })
18415                            | ("DELETE_SUBSCR", Instruction::DeleteSubscr)
18416                            | ("rest cleanup COPY", Instruction::Copy { .. })
18417                            | ("rest cleanup SWAP", Instruction::Swap { .. })
18418                    );
18419                    found.then_some(*location)
18420                })
18421                .unwrap_or_else(|| panic!("missing {expected}"));
18422            assert_eq!(
18423                location.line.get(),
18424                3,
18425                "CPython codegen_pattern_mapping() emits {expected} with LOC(p)"
18426            );
18427        }
18428    }
18429
18430    #[test]
18431    fn match_class_scaffolding_uses_class_pattern_location_like_cpython() {
18432        let code = compile_exec(
18433            "\
18434def f(x):
18435    match x:
18436        case bool(z):
18437            y = 0
18438    return y, z
18439",
18440        );
18441        let f = find_code(&code, "f").expect("missing f code");
18442        assert_eq!(
18443            f.linetable.as_ref(),
18444            &[
18445                0x80, 0x00, 0xd8, 0x0a, 0x0b, 0xdc, 0x0d, 0x11, 0x8f, 0x57, 0x88, 0x57, 0xd8, 0x10,
18446                0x11, 0x88, 0x41, 0xd8, 0x0b, 0x0c, 0x88, 0x34, 0x80, 0x4b, 0xf0, 0x05, 0x00, 0x0e,
18447                0x15, 0xe0, 0x0b, 0x0c, 0x88, 0x61, 0x88, 0x34, 0x80, 0x4b,
18448            ],
18449            "CPython codegen_pattern_class() returns to LOC(p) after VISIT(cls)"
18450        );
18451    }
18452
18453    #[test]
18454    fn match_class_wildcard_pop_uses_class_pattern_location_like_cpython() {
18455        let code = compile_exec(
18456            "\
18457def f(x):
18458    match x:
18459        case bool(
18460            _
18461        ):
18462            return True
18463",
18464        );
18465        let f = find_code(&code, "f").expect("missing f code");
18466        let unpack_index = f
18467            .instructions
18468            .iter()
18469            .position(|unit| matches!(unit.op, Instruction::UnpackSequence { .. }))
18470            .expect("missing class pattern UNPACK_SEQUENCE");
18471        let wildcard_pop_location = f
18472            .instructions
18473            .iter()
18474            .zip(&f.locations)
18475            .skip(unpack_index + 1)
18476            .find_map(|(unit, (location, _))| {
18477                matches!(unit.op, Instruction::PopTop).then_some(*location)
18478            })
18479            .expect("missing wildcard POP_TOP");
18480        assert_eq!(
18481            wildcard_pop_location.line.get(),
18482            3,
18483            "CPython codegen_pattern_class() emits wildcard POP_TOP with LOC(p)"
18484        );
18485    }
18486
18487    #[test]
18488    fn while_try_body_layout_keeps_false_jump_to_anchor() {
18489        let code = compile_exec(
18490            r#"
18491def f(stack, itstack, node_to_stack_index):
18492    while True:
18493        while stack:
18494            try:
18495                node = itstack[-1]()
18496                break
18497            except StopIteration:
18498                del node_to_stack_index[stack.pop()]
18499                itstack.pop()
18500        else:
18501            break
18502"#,
18503        );
18504        let function = find_code(&code, "f").expect("missing function code");
18505        let ops = non_cache_instructions(function)
18506            .map(|unit| unit.op)
18507            .collect::<Vec<_>>();
18508        let stack_test = ops
18509            .windows(5)
18510            .find(|window| {
18511                matches!(
18512                    window,
18513                    [
18514                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
18515                        Instruction::ToBool,
18516                        Instruction::PopJumpIfFalse { .. },
18517                        Instruction::NotTaken,
18518                        Instruction::Nop,
18519                    ]
18520                )
18521            })
18522            .unwrap_or_else(|| {
18523                panic!("expected CPython-style while/try false jump to anchor, got {ops:?}")
18524            });
18525        assert!(matches!(stack_test[2], Instruction::PopJumpIfFalse { .. }));
18526    }
18527
18528    #[test]
18529    fn while_if_not_break_keeps_body_call() {
18530        let code = compile_exec(
18531            r#"
18532def f(waiters):
18533    while waiters:
18534        waiter = waiters.popleft()
18535        if not waiter.done():
18536            waiter.set_result(None)
18537            break
18538"#,
18539        );
18540        let function = find_code(&code, "f").expect("missing function code");
18541        let ops = non_cache_instructions(function)
18542            .map(|unit| unit.op)
18543            .collect::<Vec<_>>();
18544        assert!(
18545            ops.windows(4).any(|window| matches!(
18546                window,
18547                [
18548                    Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
18549                    Instruction::LoadAttr { .. },
18550                    Instruction::LoadConst { .. },
18551                    Instruction::Call { .. },
18552                ]
18553            )),
18554            "CPython keeps waiter.set_result(None) before the break, got {ops:?}",
18555        );
18556    }
18557
18558    fn localsplus_name(code: &CodeObject, idx: usize) -> Option<&str> {
18559        if idx < code.varnames.len() {
18560            return Some(code.varnames[idx].as_str());
18561        }
18562
18563        let mut extra_idx = idx - code.varnames.len();
18564        for cellvar in &code.cellvars {
18565            if !code.varnames.iter().any(|varname| varname == cellvar) {
18566                if extra_idx == 0 {
18567                    return Some(cellvar.as_str());
18568                }
18569                extra_idx -= 1;
18570            }
18571        }
18572        code.freevars.get(extra_idx).map(|name| name.as_str())
18573    }
18574
18575    fn has_common_constant(code: &CodeObject, expected: bytecode::CommonConstant) -> bool {
18576        code.instructions.iter().any(|unit| match unit.op {
18577            Instruction::LoadCommonConstant { idx } => {
18578                idx.get(OpArg::new(u32::from(u8::from(unit.arg)))) == expected
18579            }
18580            _ => false,
18581        })
18582    }
18583
18584    fn has_intrinsic_1(code: &CodeObject, expected: IntrinsicFunction1) -> bool {
18585        code.instructions.iter().any(|unit| match unit.op {
18586            Instruction::CallIntrinsic1 { func } => {
18587                func.get(OpArg::new(u32::from(u8::from(unit.arg)))) == expected
18588            }
18589            _ => false,
18590        })
18591    }
18592
18593    #[test]
18594    fn trace_assert_true_try_pair() {
18595        let trace = compile_exec_late_cfg_trace(
18596            "\
18597try:
18598    assert True
18599except AssertionError as e:
18600    fail()
18601try:
18602    assert True, 'msg'
18603except AssertionError as e:
18604    fail()
18605",
18606        );
18607        for (stage, dump) in trace {
18608            eprintln!("=== {stage} ===\n{dump}");
18609        }
18610    }
18611
18612    #[test]
18613    fn trace_for_unpack_list_literal() {
18614        let trace = compile_exec_late_cfg_trace(
18615            "\
18616result = []
18617for x, in [(1,), (2,), (3,)]:
18618    result.append(x)
18619",
18620        );
18621        for (stage, dump) in trace {
18622            eprintln!("=== {stage} ===\n{dump}");
18623        }
18624    }
18625
18626    #[test]
18627    fn trace_break_in_finally_function() {
18628        let trace = compile_single_function_late_cfg_trace(
18629            "\
18630def f(self):
18631    count = 0
18632    while count < 2:
18633        count += 1
18634        try:
18635            pass
18636        finally:
18637            break
18638    self.assertEqual(count, 1)
18639",
18640            "f",
18641        );
18642        for (stage, dump) in trace {
18643            eprintln!("=== {stage} ===\n{dump}");
18644        }
18645    }
18646
18647    #[test]
18648    fn import_originated_name_disables_method_call_optimization_even_with_local_import() {
18649        let code = compile_exec(
18650            "\
18651import warnings
18652
18653def f(ch):
18654    import warnings
18655    warnings.warn(
18656        '\"\\\\%c\" is an invalid escape sequence' % ch
18657        if 0x20 <= ch < 0x7F
18658        else '\"\\\\x%02x\" is an invalid escape sequence' % ch,
18659        DeprecationWarning,
18660        stacklevel=2,
18661    )
18662",
18663        );
18664        let f = find_code(&code, "f").expect("missing f code");
18665        let ops: Vec<_> = f.instructions.iter().map(|unit| unit.op).collect();
18666        let warn_attr = ops
18667            .iter()
18668            .position(|op| matches!(op, Instruction::LoadAttr { .. }))
18669            .expect("missing LOAD_ATTR for warnings.warn");
18670        let push_null = ops[warn_attr + 10..]
18671            .iter()
18672            .position(|op| matches!(op, Instruction::PushNull))
18673            .map(|idx| warn_attr + 10 + idx)
18674            .expect("expected PUSH_NULL after plain LOAD_ATTR");
18675
18676        let load_attr = match f.instructions[warn_attr].op {
18677            Instruction::LoadAttr { namei } => namei.get(OpArg::new(u32::from(u8::from(
18678                f.instructions[warn_attr].arg,
18679            )))),
18680            _ => unreachable!(),
18681        };
18682        assert!(
18683            !load_attr.is_method(),
18684            "import-originated names should use plain LOAD_ATTR"
18685        );
18686        assert!(
18687            matches!(ops[push_null + 1], Instruction::LoadSmallInt { .. }),
18688            "expected warning message expression to start after PUSH_NULL, got ops={ops:?}"
18689        );
18690    }
18691
18692    #[test]
18693    fn trace_constant_false_elif_chain() {
18694        let trace = compile_exec_late_cfg_trace(
18695            "\
18696if 0: pass
18697elif 0: pass
18698elif 0: pass
18699elif 0: pass
18700else: pass
18701",
18702        );
18703        for (stage, dump) in trace {
18704            eprintln!("=== {stage} ===\n{dump}");
18705        }
18706    }
18707
18708    #[test]
18709    fn trace_multi_pass_suite() {
18710        let trace = compile_exec_late_cfg_trace(
18711            "\
18712if 1:
18713    #
18714    #
18715    #
18716    pass
18717    pass
18718    #
18719    pass
18720    #
18721",
18722        );
18723        for (stage, dump) in trace {
18724            eprintln!("=== {stage} ===\n{dump}");
18725        }
18726    }
18727
18728    #[test]
18729    fn trace_single_compare_if() {
18730        let trace = compile_exec_late_cfg_trace(
18731            "\
18732if 1 == 1:
18733    pass
18734",
18735        );
18736        for (stage, dump) in trace {
18737            eprintln!("=== {stage} ===\n{dump}");
18738        }
18739    }
18740
18741    #[test]
18742    fn trace_comparison_suite() {
18743        let trace = compile_exec_late_cfg_trace(
18744            "\
18745if 1: pass
18746x = (1 == 1)
18747if 1 == 1: pass
18748if 1 != 1: pass
18749if 1 < 1: pass
18750if 1 > 1: pass
18751if 1 <= 1: pass
18752if 1 >= 1: pass
18753if x is x: pass
18754if x is not x: pass
18755if 1 in (): pass
18756if 1 not in (): pass
18757",
18758        );
18759        for (stage, dump) in trace {
18760            eprintln!("=== {stage} ===\n{dump}");
18761        }
18762    }
18763
18764    #[test]
18765    fn trace_if_for_except_layout() {
18766        let trace = compile_exec_late_cfg_trace(
18767            "\
18768from sys import maxsize
18769if maxsize == 2147483647:
18770    for s in ('2147483648', '0o40000000000', '0x100000000', '0b10000000000000000000000000000000'):
18771        try:
18772            x = eval(s)
18773        except OverflowError:
18774            fail(\"OverflowError on huge integer literal %r\" % s)
18775elif maxsize == 9223372036854775807:
18776    pass
18777",
18778        );
18779        for (stage, dump) in trace {
18780            eprintln!("=== {stage} ===\n{dump}");
18781        }
18782    }
18783
18784    #[test]
18785    fn break_in_finally_tail_loads_borrow_through_empty_fallthrough_block() {
18786        let code = compile_exec(
18787            "\
18788def f(self):
18789    count = 0
18790    while count < 2:
18791        count += 1
18792        try:
18793            pass
18794        finally:
18795            break
18796    self.assertEqual(count, 1)
18797",
18798        );
18799        let code = find_code(&code, "f").unwrap();
18800        let ops: Vec<_> = code
18801            .instructions
18802            .iter()
18803            .map(|unit| unit.op)
18804            .filter(|op| !matches!(op, Instruction::Cache))
18805            .collect();
18806        assert!(
18807            ops.windows(5).any(|window| {
18808                matches!(
18809                    window,
18810                    [
18811                        Instruction::LoadFastBorrow { .. },
18812                        Instruction::LoadAttr { .. },
18813                        Instruction::LoadFastBorrow { .. },
18814                        Instruction::LoadSmallInt { .. },
18815                        Instruction::Call { .. }
18816                    ]
18817                )
18818            }),
18819            "{:?}",
18820            code.instructions
18821                .iter()
18822                .map(|unit| unit.op)
18823                .collect::<Vec<_>>()
18824        );
18825    }
18826
18827    #[test]
18828    fn plain_constant_bool_op_folds_to_selected_operand() {
18829        let code = compile_exec(
18830            "\
18831x = 1 or 2 or 3
18832",
18833        );
18834        let ops: Vec<_> = code
18835            .instructions
18836            .iter()
18837            .map(|unit| unit.op)
18838            .filter(|op| !matches!(op, Instruction::Cache))
18839            .collect();
18840        let folded_small_int = code.instructions.iter().any(|unit| {
18841            matches!(
18842                unit.op,
18843                Instruction::LoadSmallInt { i }
18844                    if i.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 1
18845            )
18846        });
18847        let folded_const_one = code
18848            .instructions
18849            .iter()
18850            .find_map(|unit| match unit.op {
18851                Instruction::LoadConst { .. } => code.constants.get(usize::from(u8::from(unit.arg))),
18852                _ => None,
18853            })
18854            .is_some_and(|constant| {
18855                matches!(constant, ConstantData::Integer { value } if *value == BigInt::from(1))
18856            });
18857
18858        assert!(
18859            folded_small_int || folded_const_one,
18860            "expected folded constant 1, got ops={ops:?}"
18861        );
18862        assert!(
18863            !ops.iter().any(|op| {
18864                matches!(
18865                    op,
18866                    Instruction::Copy { .. }
18867                        | Instruction::ToBool
18868                        | Instruction::PopJumpIfTrue { .. }
18869                        | Instruction::PopJumpIfFalse { .. }
18870                )
18871            }),
18872            "plain constant BoolOp should not leave short-circuit scaffolding, got ops={ops:?}"
18873        );
18874    }
18875
18876    #[test]
18877    fn taken_constant_boolop_load_const_uses_literal_location_like_cpython() {
18878        let code = compile_exec(
18879            "\
18880def and_false(x):
18881    return False and x
18882
18883def or_true(x):
18884    return True or x
18885",
18886        );
18887        let and_false = find_code(&code, "and_false").expect("missing and_false code");
18888        let or_true = find_code(&code, "or_true").expect("missing or_true code");
18889
18890        // CPython 3.14 codegen_boolop() VISITs the selected literal before the
18891        // short-circuit jump is optimized away, so the surviving LOAD_CONST
18892        // keeps the literal range rather than the whole BoolOp range.
18893        assert_eq!(
18894            and_false.linetable.as_ref(),
18895            &[0x80, 0x00, 0xd8, 0x0b, 0x10, 0xd0, 0x04, 0x16]
18896        );
18897        assert_eq!(
18898            or_true.linetable.as_ref(),
18899            &[0x80, 0x00, 0xd8, 0x0b, 0x0f, 0xd0, 0x04, 0x14]
18900        );
18901    }
18902
18903    #[test]
18904    fn assert_false_message_call_uses_assert_location_like_cpython() {
18905        let code = compile_exec(
18906            "\
18907def f():
18908    assert False, \"x\"
18909",
18910        );
18911        let f = find_code(&code, "f").expect("missing f code");
18912
18913        // CPython 3.14 codegen_assert() emits LOAD_COMMON_CONSTANT and CALL
18914        // at LOC(assert statement), then RAISE_VARARGS at LOC(test).
18915        assert_eq!(
18916            f.linetable.as_ref(),
18917            &[
18918                0x80, 0x00, 0xd8, 0x04, 0x15, 0x90, 0x23, 0xd3, 0x04, 0x15, 0x88, 0x35,
18919            ]
18920        );
18921    }
18922
18923    #[test]
18924    fn static_swap_implicit_return_keeps_preswap_store_location_like_cpython() {
18925        let code = compile_exec(
18926            "\
18927def f(a, b):
18928    a, b = a, b
18929    b, a = a, b
18930",
18931        );
18932        let f = find_code(&code, "f").expect("missing f code");
18933
18934        // CPython 3.14 flowgraph.c resolves line numbers before
18935        // optimize_basic_block() turns BUILD_TUPLE/UNPACK_SEQUENCE into SWAP
18936        // and apply_static_swaps() reorders the STORE_FAST pair.  The
18937        // synthetic return epilogue therefore keeps the pre-swap final store
18938        // location.
18939        assert_eq!(
18940            f.linetable.as_ref(),
18941            &[
18942                0x80, 0x00, 0xd8, 0x0b, 0x0c, 0x80, 0x71, 0xd8, 0x0b, 0x0c, 0x82, 0x71,
18943            ]
18944        );
18945    }
18946
18947    #[test]
18948    fn unpack_store_pair_jump_uses_second_target_location_like_cpython() {
18949        let code = compile_exec(
18950            "\
18951def f(value):
18952    if value.startswith('=?'):
18953        try:
18954            token, value = get_encoded_word(value)
18955        except E:
18956            token, value = get_atext(value)
18957    else:
18958        token, value = get_atext(value)
18959    atom.append(token)
18960",
18961        );
18962        let f = find_code(&code, "f").expect("missing f code");
18963        let jump_position = f
18964            .instructions
18965            .iter()
18966            .zip(&f.locations)
18967            .find_map(|(unit, (location, end_location))| {
18968                matches!(unit.op, Instruction::JumpForward { .. }).then_some((
18969                    location.line.get(),
18970                    location.character_offset.get(),
18971                    end_location.line.get(),
18972                    end_location.character_offset.get(),
18973                ))
18974            })
18975            .expect("missing post-try JUMP_FORWARD");
18976
18977        // CPython 3.14 flowgraph.c turns the second STORE_FAST into a NOP
18978        // during STORE_FAST_STORE_FAST fusion, then NOP removal copies that
18979        // second target location onto the following no-location jump.
18980        assert_eq!(jump_position, (4, 20, 4, 25));
18981    }
18982
18983    #[test]
18984    fn chained_store_pair_jump_keeps_copy_target_location_like_cpython() {
18985        let code = compile_exec(
18986            "\
18987def f(flag):
18988    if flag:
18989        a = b = True
18990    else:
18991        a = False
18992        b = False
18993    g(a, b)
18994    return a
18995",
18996        );
18997        let f = find_code(&code, "f").expect("missing f code");
18998        let jump_position = f
18999            .instructions
19000            .windows(2)
19001            .zip(f.locations.windows(2))
19002            .find_map(|(units, locations)| {
19003                matches!(units[0].op, Instruction::StoreFastStoreFast { .. })
19004                    .then(|| {
19005                        matches!(units[1].op, Instruction::JumpForward { .. }).then_some((
19006                            locations[1].0.line.get(),
19007                            locations[1].0.character_offset.get(),
19008                            locations[1].1.line.get(),
19009                            locations[1].1.character_offset.get(),
19010                        ))
19011                    })
19012                    .flatten()
19013            })
19014            .expect("missing jump after chained STORE_FAST_STORE_FAST");
19015
19016        // CPython 3.14 flowgraph.c preserves the second chained-assignment
19017        // target location on the jump that skips the else body.
19018        assert_eq!(jump_position, (3, 13, 3, 14));
19019    }
19020
19021    #[test]
19022    fn tuple_store_pair_jump_keeps_fused_store_location_like_cpython() {
19023        let code = compile_exec(
19024            "\
19025def f(flag, n, exp):
19026    if flag:
19027        n, d = n * 10**exp, 1
19028    else:
19029        d = -exp
19030    g(n, d)
19031    return n
19032",
19033        );
19034        let f = find_code(&code, "f").expect("missing f code");
19035        let jump_position = f
19036            .instructions
19037            .windows(2)
19038            .zip(f.locations.windows(2))
19039            .find_map(|(units, locations)| {
19040                matches!(units[0].op, Instruction::StoreFastStoreFast { .. })
19041                    .then(|| {
19042                        matches!(units[1].op, Instruction::JumpForward { .. }).then_some((
19043                            locations[1].0.line.get(),
19044                            locations[1].0.character_offset.get(),
19045                            locations[1].1.line.get(),
19046                            locations[1].1.character_offset.get(),
19047                        ))
19048                    })
19049                    .flatten()
19050            })
19051            .expect("missing jump after tuple STORE_FAST_STORE_FAST");
19052
19053        // Without COPY before the fused stores, CPython keeps the fused
19054        // STORE_FAST_STORE_FAST location on the following jump.
19055        assert_eq!(jump_position, (3, 12, 3, 13));
19056    }
19057
19058    #[test]
19059    fn genexpr_make_closure_and_call_use_genexpr_location_like_cpython() {
19060        let code = compile_exec(
19061            "\
19062def f(parameters):
19063    return ((p, type(p)) for p in parameters)
19064",
19065        );
19066        let f = find_code(&code, "f").expect("missing f code");
19067        let genexpr = find_code(f, "<genexpr>").expect("missing genexpr code");
19068
19069        // CPython 3.14 codegen_comprehension() uses LOC(e) for
19070        // codegen_make_closure(), the outer CALL, and the implicit .0 load
19071        // in codegen_sync_comprehension_generator().
19072        assert_eq!(
19073            f.linetable.as_ref(),
19074            &[
19075                0x80, 0x00, 0xd9, 0x0b, 0x2d, 0xa1, 0x2a, 0xd3, 0x0b, 0x2d, 0xd0, 0x04, 0x2d,
19076            ]
19077        );
19078        assert_eq!(
19079            genexpr.linetable.as_ref(),
19080            &[
19081                0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0b, 0x2d, 0xa1, 0x2a, 0x98, 0x51, 0x94, 0x04, 0x90,
19082                0x51, 0x93, 0x07, 0x8d, 0x4c, 0xa3, 0x2a, 0xf9,
19083            ]
19084        );
19085    }
19086
19087    #[test]
19088    fn implicit_call_genexpr_range_includes_call_parens_like_cpython() {
19089        let code = compile_exec(
19090            "\
19091def implicit():
19092    return list(x for x in range(10))
19093
19094def explicit():
19095    return list((x for x in range(10)))
19096",
19097        );
19098        let implicit = find_code(&code, "implicit").expect("missing implicit code");
19099        let implicit_gen = find_code(implicit, "<genexpr>").expect("missing implicit genexpr code");
19100        let explicit = find_code(&code, "explicit").expect("missing explicit code");
19101        let explicit_gen = find_code(explicit, "<genexpr>").expect("missing explicit genexpr code");
19102
19103        // CPython's parser gives an unparenthesized sole GeneratorExp call
19104        // argument the call-parenthesized range, and codegen_comprehension()
19105        // uses LOC(e) for MAKE_FUNCTION, the outer CALL, and the implicit .0
19106        // LOAD_FAST.  Explicitly parenthesized genexprs already carry their own
19107        // parentheses and must not be widened again.
19108        assert_eq!(
19109            implicit.linetable.as_ref(),
19110            &[
19111                0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x25, 0x9c, 0x35, 0xa0, 0x12, 0x9c, 0x39,
19112                0xd3, 0x0f, 0x25, 0xd3, 0x0b, 0x25, 0xd0, 0x04, 0x25,
19113            ]
19114        );
19115        assert_eq!(
19116            implicit_gen.linetable.as_ref(),
19117            &[
19118                0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0f, 0x25, 0x99, 0x39, 0x90, 0x61, 0x94, 0x01, 0x9b,
19119                0x39, 0xf9,
19120            ]
19121        );
19122        assert_eq!(
19123            explicit_gen.linetable.as_ref(),
19124            &[
19125                0xe9, 0x00, 0x80, 0x00, 0xd0, 0x10, 0x26, 0x99, 0x49, 0x90, 0x71, 0x94, 0x11, 0x9b,
19126                0x49, 0xf9,
19127            ]
19128        );
19129    }
19130
19131    #[test]
19132    fn implicit_call_genexpr_parenthesized_element_range_like_cpython() {
19133        let code = compile_exec(
19134            "\
19135def bytes_binop():
19136    return bytes((x ^ 0x5C) for x in range(256))
19137
19138def dict_tuple(d):
19139    return dict((v, k) for (k, v) in d.items())
19140
19141def plain_tuple_elt(xs):
19142    return list((x, y) for x, y in xs)
19143
19144def explicit_gen(xs):
19145    return list(((x, y) for x, y in xs))
19146",
19147        );
19148        let bytes_binop = find_code(&code, "bytes_binop").expect("missing bytes_binop code");
19149        let bytes_gen = find_code(bytes_binop, "<genexpr>").expect("missing bytes genexpr code");
19150        let dict_tuple = find_code(&code, "dict_tuple").expect("missing dict_tuple code");
19151        let dict_gen = find_code(dict_tuple, "<genexpr>").expect("missing dict genexpr code");
19152        let plain_tuple_elt =
19153            find_code(&code, "plain_tuple_elt").expect("missing plain_tuple_elt code");
19154        let plain_gen =
19155            find_code(plain_tuple_elt, "<genexpr>").expect("missing plain genexpr code");
19156        let explicit_gen = find_code(&code, "explicit_gen").expect("missing explicit_gen code");
19157        let explicit_inner =
19158            find_code(explicit_gen, "<genexpr>").expect("missing explicit genexpr code");
19159
19160        // CPython 3.14's parser includes the call argument parentheses in
19161        // LOC(GeneratorExp) for implicit sole-argument generator expressions,
19162        // even when the element expression itself starts with parentheses.
19163        assert_eq!(
19164            bytes_binop.linetable.as_ref(),
19165            &[
19166                0x80, 0x00, 0xdc, 0x0b, 0x10, 0xd1, 0x10, 0x30, 0xa4, 0x55, 0xa8, 0x33, 0xa4, 0x5a,
19167                0xd3, 0x10, 0x30, 0xd3, 0x0b, 0x30, 0xd0, 0x04, 0x30,
19168            ]
19169        );
19170        assert_eq!(
19171            bytes_gen.linetable.as_ref(),
19172            &[
19173                0xe9, 0x00, 0x80, 0x00, 0xd0, 0x10, 0x30, 0xa1, 0x5a, 0xa0, 0x01, 0x90, 0x64, 0x97,
19174                0x28, 0x92, 0x28, 0xa3, 0x5a, 0xf9,
19175            ]
19176        );
19177        assert_eq!(
19178            dict_tuple.linetable.as_ref(),
19179            &[
19180                0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x2f, 0xa0, 0x51, 0xa7, 0x57, 0xa1, 0x57,
19181                0xa4, 0x59, 0xd3, 0x0f, 0x2f, 0xd3, 0x0b, 0x2f, 0xd0, 0x04, 0x2f,
19182            ]
19183        );
19184        assert_eq!(
19185            dict_gen.linetable.as_ref(),
19186            &[
19187                0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0f, 0x2f, 0xa1, 0x59, 0x99, 0x36, 0x98, 0x41, 0x90,
19188                0x11, 0x95, 0x06, 0xa3, 0x59, 0xf9,
19189            ]
19190        );
19191        assert_eq!(
19192            plain_tuple_elt.linetable.as_ref(),
19193            &[
19194                0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x26, 0xa1, 0x32, 0xd3, 0x0f, 0x26, 0xd3,
19195                0x0b, 0x26, 0xd0, 0x04, 0x26,
19196            ]
19197        );
19198        assert_eq!(
19199            plain_gen.linetable.as_ref(),
19200            &[
19201                0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0f, 0x26, 0xa1, 0x32, 0x99, 0x34, 0x98, 0x31, 0x90,
19202                0x11, 0x95, 0x06, 0xa3, 0x32, 0xf9,
19203            ]
19204        );
19205        assert_eq!(
19206            explicit_gen.linetable.as_ref(),
19207            &[
19208                0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x10, 0x27, 0xa1, 0x42, 0xd3, 0x10, 0x27, 0xd3,
19209                0x0b, 0x28, 0xd0, 0x04, 0x28,
19210            ]
19211        );
19212        assert_eq!(
19213            explicit_inner.linetable.as_ref(),
19214            &[
19215                0xe9, 0x00, 0x80, 0x00, 0xd0, 0x10, 0x27, 0xa1, 0x42, 0x99, 0x44, 0x98, 0x41, 0x90,
19216                0x21, 0x95, 0x16, 0xa3, 0x42, 0xf9,
19217            ]
19218        );
19219    }
19220
19221    #[test]
19222    fn implicit_call_genexpr_operator_element_range_like_cpython() {
19223        // The element opens with a parenthesized group that the rest of the
19224        // expression continues, so the call's own parentheses are the only
19225        // ones that bound the generator.
19226        let code = compile_exec(
19227            "\
19228def f(p, q):
19229    return sum((px - qx) ** 2.0 for px, qx in zip(p, q))
19230",
19231        );
19232        let f = find_code(&code, "f").expect("missing f code");
19233        let genexpr = find_code(f, "<genexpr>").expect("missing genexpr code");
19234
19235        // Columns are one-based here, so these are `dis`'s 14..56.
19236        assert_eq!(
19237            instruction_range(f, |op| matches!(op, Instruction::MakeFunction)),
19238            Some((2, 15, 2, 57))
19239        );
19240        assert_eq!(
19241            instruction_range(genexpr, |op| matches!(op, Instruction::LoadFast { .. })),
19242            Some((2, 15, 2, 57))
19243        );
19244    }
19245
19246    #[test]
19247    fn fstring_concatenation_without_content_uses_whole_range_like_cpython() {
19248        // Every fragment is empty, so none of them is kept and the empty string
19249        // that replaces them belongs to the whole concatenation. The last line
19250        // keeps its one fragment and stays at that fragment.
19251        let code = compile_exec(
19252            "\
19253x = '' f''
19254y = f'' ''
19255z = '' f'' '' f''
19256w = f'' 'a' f''
19257",
19258        );
19259
19260        let ranges: Vec<_> = code
19261            .instructions
19262            .iter()
19263            .zip(&code.locations)
19264            .filter(|(unit, _)| matches!(unit.op, Instruction::LoadConst { .. }))
19265            .map(|(_, locations)| location_range(locations))
19266            .take(4)
19267            .collect();
19268        // Columns are one-based here, so these are `dis`'s 4..10, 4..10,
19269        // 4..17 and 8..11.
19270        assert_eq!(
19271            ranges,
19272            vec![(1, 5, 1, 11), (2, 5, 2, 11), (3, 5, 3, 18), (4, 9, 4, 12)]
19273        );
19274    }
19275
19276    #[test]
19277    fn genexpr_filter_cleanup_jumps_use_element_location_like_cpython() {
19278        let code = compile_exec(
19279            "\
19280def simple(names):
19281    return (x for x in names if not _ishidden(x))
19282
19283def boolop(fields):
19284    return (f for f in fields if f.init and not f.kw_only)
19285",
19286        );
19287        let simple = find_code(&code, "simple").expect("missing simple code");
19288        let simple_gen = find_code(simple, "<genexpr>").expect("missing simple genexpr code");
19289        let boolop = find_code(&code, "boolop").expect("missing boolop code");
19290        let boolop_gen = find_code(boolop, "<genexpr>").expect("missing boolop genexpr code");
19291
19292        // codegen_sync_comprehension_generator() emits the
19293        // comprehension guard jump to if_cleanup, then emits the if_cleanup
19294        // backedge with elt_loc. flowgraph.c::jump_thread() copies that target
19295        // jump location to the threaded POP_JUMP/NOT_TAKEN cleanup path.
19296        assert_eq!(
19297            simple_gen.linetable.as_ref(),
19298            &[
19299                0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0b, 0x31, 0x91, 0x75, 0x90, 0x21, 0xa4, 0x49, 0xa8,
19300                0x61, 0xa7, 0x4c, 0x8f, 0x41, 0x8a, 0x41, 0x93, 0x75, 0xf9,
19301            ]
19302        );
19303        assert_eq!(
19304            boolop_gen.linetable.as_ref(),
19305            &[
19306                0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0b, 0x3a, 0x91, 0x76, 0x90, 0x21, 0xa7, 0x16, 0xa5,
19307                0x16, 0x8c, 0x41, 0xb0, 0x01, 0xb7, 0x09, 0xb5, 0x09, 0x8f, 0x41, 0x8a, 0x41, 0x93,
19308                0x76, 0xf9,
19309            ]
19310        );
19311    }
19312
19313    #[test]
19314    fn try_finally_exception_scaffolding_uses_no_location_like_cpython() {
19315        let code = compile_exec(
19316            "\
19317def f(self, node):
19318    self.flag = True
19319    try:
19320        self.body(node)
19321    finally:
19322        self.flag = False
19323",
19324        );
19325        let f = find_code(&code, "f").expect("missing f code");
19326
19327        // codegen_try_finally() emits the exception path
19328        // SETUP_CLEANUP/PUSH_EXC_INFO and POP_EXCEPT_AND_RERAISE with
19329        // NO_LOCATION; flowgraph line propagation then gives only the
19330        // finalbody's direct RERAISE the finalbody location.
19331        assert_eq!(
19332            f.linetable.as_ref(),
19333            &[
19334                0x80, 0x00, 0xd8, 0x10, 0x14, 0x80, 0x44, 0x84, 0x49, 0xf0, 0x02, 0x03, 0x05, 0x1a,
19335                0xd8, 0x08, 0x0c, 0x8f, 0x09, 0x89, 0x09, 0x90, 0x24, 0x8c, 0x0f, 0xe0, 0x14, 0x19,
19336                0x88, 0x04, 0x8e, 0x09, 0xf8, 0x90, 0x45, 0x88, 0x04, 0x8d, 0x09, 0xfa,
19337            ]
19338        );
19339    }
19340
19341    #[test]
19342    fn return_debug_in_finally_uses_cpython_preprocessed_constant_order() {
19343        let code = compile_exec(
19344            "\
19345def f(close):
19346    try:
19347        return __debug__
19348    finally:
19349        close()
19350",
19351        );
19352        let f = find_code(&code, "f").expect("missing f code");
19353        let call_pos = f
19354            .instructions
19355            .iter()
19356            .position(|unit| matches!(unit.op, Instruction::Call { .. }))
19357            .expect("missing finally-body call");
19358        let debug_load_pos = f
19359            .instructions
19360            .iter()
19361            .position(|unit| {
19362                let Instruction::LoadConst { consti } = unit.op else {
19363                    return false;
19364                };
19365                let constant = &f.constants[consti.get(OpArg::new(u32::from(u8::from(unit.arg))))];
19366                matches!(constant, ConstantData::Boolean { value: true })
19367            })
19368            .expect("missing __debug__ constant load");
19369
19370        assert!(
19371            call_pos < debug_load_pos,
19372            "CPython ast_preprocess.c folds __debug__ to Constant before codegen_return(), so the return constant is loaded after finally cleanup; ops={:?}",
19373            f.instructions
19374                .iter()
19375                .map(|unit| unit.op)
19376                .collect::<Vec<_>>()
19377        );
19378    }
19379
19380    #[test]
19381    fn debug_statement_is_preprocessed_constant_like_cpython() {
19382        for code in [
19383            compile_exec("__debug__\n"),
19384            compile_exec_optimized("__debug__\n"),
19385        ] {
19386            let ops = non_cache_instructions(&code)
19387                .map(|unit| unit.op)
19388                .collect::<Vec<_>>();
19389            assert!(
19390                !ops.iter().any(|op| matches!(op, Instruction::PopTop)),
19391                "CPython ast_preprocess.c folds __debug__ to Constant before codegen_stmt_expr(), so it must not compile as LOAD_CONST/POP_TOP; ops={ops:?}"
19392            );
19393        }
19394    }
19395
19396    #[test]
19397    fn statement_expr_pop_top_uses_no_location_like_cpython() {
19398        let infos = compile_module_instruction_infos("x + 1\n", Mode::Exec);
19399        let pop = infos
19400            .iter()
19401            .find(|info| matches!(info.instr.real(), Some(Instruction::PopTop)))
19402            .expect("missing expression-statement POP_TOP");
19403
19404        assert_eq!(
19405            pop.lineno_override,
19406            Some(ir::NO_LOCATION_OVERRIDE),
19407            "CPython codegen_stmt_expr() emits artificial expression-statement POP_TOP at NO_LOCATION"
19408        );
19409    }
19410
19411    #[test]
19412    fn interactive_statement_expr_pop_top_uses_no_location_like_cpython() {
19413        let infos = compile_module_instruction_infos("x + 1\n", Mode::Single);
19414        let print = infos
19415            .iter()
19416            .position(|info| {
19417                matches!(
19418                    info.instr.real(),
19419                    Some(Instruction::CallIntrinsic1 { func })
19420                        if func.get(info.arg) == bytecode::IntrinsicFunction1::Print
19421                )
19422            })
19423            .expect("missing interactive PRINT intrinsic");
19424        let pop = infos
19425            .get(print + 1)
19426            .expect("missing POP_TOP after interactive PRINT");
19427
19428        assert!(
19429            matches!(pop.instr.real(), Some(Instruction::PopTop)),
19430            "CPython codegen_stmt_expr() emits POP_TOP immediately after INTRINSIC_PRINT; got {pop:?}"
19431        );
19432        assert_eq!(
19433            pop.lineno_override,
19434            Some(ir::NO_LOCATION_OVERRIDE),
19435            "CPython codegen_stmt_expr() emits interactive PRINT cleanup POP_TOP at NO_LOCATION"
19436        );
19437    }
19438
19439    #[test]
19440    fn import_star_pop_top_uses_no_location_like_cpython() {
19441        let infos = compile_module_instruction_infos("from m import *\n", Mode::Exec);
19442        let import_star = infos
19443            .iter()
19444            .position(|info| {
19445                matches!(
19446                    info.instr.real(),
19447                    Some(Instruction::CallIntrinsic1 { func })
19448                        if func.get(info.arg) == bytecode::IntrinsicFunction1::ImportStar
19449                )
19450            })
19451            .expect("missing IMPORT_STAR intrinsic");
19452        let pop = infos
19453            .get(import_star + 1)
19454            .expect("missing POP_TOP after IMPORT_STAR");
19455
19456        assert!(
19457            matches!(pop.instr.real(), Some(Instruction::PopTop)),
19458            "CPython codegen_from_import() emits POP_TOP immediately after INTRINSIC_IMPORT_STAR; got {pop:?}"
19459        );
19460        assert_eq!(
19461            pop.lineno_override,
19462            Some(ir::NO_LOCATION_OVERRIDE),
19463            "CPython codegen_from_import() emits import-star cleanup POP_TOP at NO_LOCATION"
19464        );
19465    }
19466
19467    #[test]
19468    fn adjacent_no_location_entries_merge_like_cpython() {
19469        let code = compile_exec(
19470            "\
19471def f(file):
19472    if sys.platform == \"win32\":
19473        try:
19474            import nt
19475            if not nt._supports_virtual_terminal():
19476                return False
19477        except (ImportError, AttributeError):
19478            return False
19479    try:
19480        return os.isatty(file.fileno())
19481    except OSError:
19482        return hasattr(file, \"isatty\") and file.isatty()
19483",
19484        );
19485        let f = find_code(&code, "f").expect("missing f code");
19486
19487        // CPython's NO_LOCATION is {-1, -1, -1, -1}, and
19488        // assemble.c::assemble_location_info() merges adjacent instructions
19489        // with the same NO_LOCATION into one linetable entry.
19490        assert_eq!(
19491            f.linetable.as_ref(),
19492            &[
19493                0x80, 0x00, 0xdc, 0x07, 0x0a, 0x87, 0x7c, 0x81, 0x7c, 0x90, 0x77, 0xd4, 0x07, 0x1e,
19494                0xf0, 0x02, 0x05, 0x09, 0x19, 0xdb, 0x0c, 0x15, 0xd8, 0x13, 0x15, 0xd7, 0x13, 0x30,
19495                0xd1, 0x13, 0x30, 0xd7, 0x13, 0x32, 0xd2, 0x13, 0x32, 0xd9, 0x17, 0x1c, 0xf0, 0x03,
19496                0x00, 0x14, 0x33, 0xf0, 0x08, 0x03, 0x05, 0x39, 0xdc, 0x0f, 0x11, 0x8f, 0x79, 0x89,
19497                0x79, 0x98, 0x14, 0x9f, 0x1b, 0x99, 0x1b, 0x9b, 0x1d, 0xd3, 0x0f, 0x27, 0xd0, 0x08,
19498                0x27, 0xf8, 0xf4, 0x07, 0x00, 0x11, 0x1c, 0x9c, 0x5e, 0xd0, 0x0f, 0x2c, 0xf4, 0x00,
19499                0x01, 0x09, 0x19, 0xda, 0x13, 0x18, 0xf0, 0x03, 0x01, 0x09, 0x19, 0xfb, 0xf4, 0x08,
19500                0x00, 0x0c, 0x13, 0xf4, 0x00, 0x01, 0x05, 0x39, 0xdc, 0x0f, 0x16, 0x90, 0x74, 0x98,
19501                0x58, 0xd3, 0x0f, 0x26, 0xd7, 0x0f, 0x38, 0xd0, 0x0f, 0x38, 0xa8, 0x34, 0xaf, 0x3b,
19502                0xa9, 0x3b, 0xab, 0x3d, 0xd2, 0x08, 0x38, 0xf0, 0x03, 0x01, 0x05, 0x39, 0xfa,
19503            ]
19504        );
19505    }
19506
19507    #[test]
19508    fn fstring_format_ops_use_formatted_value_location_like_cpython() {
19509        let code = compile_exec(
19510            "\
19511def simple(self):
19512    return f'{self.value}'
19513
19514def spec(x):
19515    return f'{x!r:>3}'
19516",
19517        );
19518        let simple = find_code(&code, "simple").expect("missing simple code");
19519        let spec = find_code(&code, "spec").expect("missing spec code");
19520
19521        // CPython 3.14 codegen_formatted_value() VISITs the inner expression
19522        // first, then emits CONVERT_VALUE / FORMAT_* at LOC(FormattedValue).
19523        assert_eq!(
19524            simple.linetable.as_ref(),
19525            &[
19526                0x80, 0x00, 0xd8, 0x0e, 0x12, 0x8f, 0x6a, 0x89, 0x6a, 0x88, 0x5c, 0xd0, 0x04, 0x1a,
19527            ]
19528        );
19529        assert_eq!(
19530            spec.linetable.as_ref(),
19531            &[
19532                0x80, 0x00, 0xd8, 0x0e, 0x0f, 0x88, 0x58, 0x90, 0x22, 0x88, 0x58, 0xd0, 0x04, 0x16,
19533            ]
19534        );
19535    }
19536
19537    #[test]
19538    fn debug_fstring_literal_merging_and_location_like_cpython() {
19539        fn string_load_position(code: &CodeObject, expected: &str) -> (usize, usize, usize, usize) {
19540            code.instructions
19541                .iter()
19542                .zip(&code.locations)
19543                .find_map(|(unit, (location, end_location))| {
19544                    let Instruction::LoadConst { consti } = unit.op else {
19545                        return None;
19546                    };
19547                    let constant =
19548                        &code.constants[consti.get(OpArg::new(u32::from(u8::from(unit.arg))))];
19549                    matches!(constant, ConstantData::Str { value } if value.to_string() == expected)
19550                        .then_some((
19551                            location.line.get(),
19552                            location.character_offset.get(),
19553                            end_location.line.get(),
19554                            end_location.character_offset.get(),
19555                        ))
19556                })
19557                .expect("missing debug f-string literal")
19558        }
19559
19560        let code = compile_exec(
19561            r#"def simple(x):
19562    return f'{x=}'
19563
19564def prefixed(x):
19565    return f'a {x=} b'
19566
19567def commented(x):
19568    return f"""{  # comment
19569x=}"""
19570"#,
19571        );
19572        let simple = find_code(&code, "simple").expect("missing simple code");
19573        let prefixed = find_code(&code, "prefixed").expect("missing prefixed code");
19574        let commented = find_code(&code, "commented").expect("missing commented code");
19575
19576        assert_eq!(
19577            string_load_position(simple, "x="),
19578            (2, 15, 2, 17),
19579            "CPython represents f'{{x=}}' debug text as a literal at the expression/debug-text location"
19580        );
19581        assert_eq!(
19582            string_load_position(prefixed, "a x="),
19583            (5, 14, 5, 19),
19584            "CPython merges debug text with the preceding JoinedStr literal"
19585        );
19586        assert_eq!(
19587            string_load_position(commented, "  \nx="),
19588            (8, 17, 9, 3),
19589            "a stripped comment shortens the debug text but not the source range it spans"
19590        );
19591    }
19592
19593    #[test]
19594    fn fstring_format_spec_build_string_location_like_cpython() {
19595        let code = compile_exec(
19596            "\
19597def simple(lbl, label_width):
19598    return f'{lbl:>{label_width}}'
19599
19600def padded(digits, int_len):
19601    return f'{digits:0>{int_len + 1}d}'
19602",
19603        );
19604        let simple = find_code(&code, "simple").expect("missing simple code");
19605        let padded = find_code(&code, "padded").expect("missing padded code");
19606
19607        let build_string_position = |code: &CodeObject| {
19608            code.instructions
19609                .iter()
19610                .zip(&code.locations)
19611                .find_map(|(unit, (location, end_location))| {
19612                    matches!(unit.op, Instruction::BuildString { .. }).then_some((
19613                        location.line.get(),
19614                        location.character_offset.get(),
19615                        end_location.line.get(),
19616                        end_location.character_offset.get(),
19617                    ))
19618                })
19619                .expect("missing format-spec BUILD_STRING")
19620        };
19621
19622        assert_eq!(
19623            build_string_position(simple),
19624            (2, 18, 2, 33),
19625            "CPython uses the format-spec JoinedStr location, including the ':' prefix, for BUILD_STRING"
19626        );
19627        assert_eq!(
19628            build_string_position(padded),
19629            (5, 21, 5, 38),
19630            "CPython format-spec JoinedStr location spans from ':' through the final literal"
19631        );
19632    }
19633
19634    #[test]
19635    fn joined_string_literals_extend_pending_literal_location_like_cpython() {
19636        let code = compile_exec(
19637            "\
19638def f(a):
19639    return (
19640        'x'
19641        f'y{a}z'
19642        'w'
19643    )
19644",
19645        );
19646        let f = find_code(&code, "f").expect("missing f code");
19647        assert_eq!(
19648            f.linetable.as_ref(),
19649            &[
19650                0x80, 0x00, 0xf0, 0x04, 0x01, 0x09, 0x0c, 0xd8, 0x0c, 0x0d, 0x88, 0x33, 0xf0, 0x00,
19651                0x01, 0x0f, 0x0c, 0xf0, 0x03, 0x02, 0x09, 0x0c, 0xf0, 0x03, 0x04, 0x05, 0x06,
19652            ],
19653            "CPython parser/codegen represents adjacent f-string literal fragments as Constant ranges spanning the merged fragments"
19654        );
19655    }
19656
19657    #[test]
19658    fn starred_call_preserves_bool_op_short_circuit_shape() {
19659        let code = compile_exec(
19660            "\
19661def f(g):
19662    return g(*(() or (1,)))
19663",
19664        );
19665        let f = find_code(&code, "f").expect("missing function code");
19666        let ops: Vec<_> = f
19667            .instructions
19668            .iter()
19669            .map(|unit| unit.op)
19670            .filter(|op| !matches!(op, Instruction::Cache))
19671            .collect();
19672
19673        assert!(
19674            ops.iter().any(|op| matches!(op, Instruction::Copy { .. })),
19675            "starred BoolOp should keep short-circuit COPY, got ops={ops:?}"
19676        );
19677        assert!(
19678            ops.iter().any(|op| matches!(op, Instruction::ToBool)),
19679            "starred BoolOp should keep TO_BOOL, got ops={ops:?}"
19680        );
19681        assert!(
19682            ops.iter()
19683                .any(|op| matches!(op, Instruction::PopJumpIfTrue { .. })),
19684            "starred BoolOp should keep POP_JUMP_IF_TRUE, got ops={ops:?}"
19685        );
19686    }
19687
19688    #[test]
19689    fn partial_constant_bool_op_folds_prefix_in_value_context() {
19690        let code = compile_exec(
19691            "\
19692def outer(null):
19693    @False or null
19694    def f(x):
19695        pass
19696",
19697        );
19698        let outer = find_code(&code, "outer").expect("missing outer code");
19699        let ops: Vec<_> = outer
19700            .instructions
19701            .iter()
19702            .map(|unit| unit.op)
19703            .filter(|op| !matches!(op, Instruction::Cache))
19704            .collect();
19705
19706        assert!(
19707            ops.iter().any(|op| {
19708                matches!(
19709                    op,
19710                    Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. }
19711                )
19712            }),
19713            "expected surviving decorator expression to load null directly, got ops={ops:?}"
19714        );
19715        assert!(
19716            !ops.iter().any(|op| {
19717                matches!(
19718                    op,
19719                    Instruction::Copy { .. }
19720                        | Instruction::ToBool
19721                        | Instruction::PopJumpIfTrue { .. }
19722                        | Instruction::PopJumpIfFalse { .. }
19723                )
19724            }),
19725            "partial constant BoolOp should not leave short-circuit scaffolding, got ops={ops:?}"
19726        );
19727    }
19728
19729    #[test]
19730    fn decorated_definitions_use_cpython_locations() {
19731        let code = compile_exec(
19732            "\
19733def dec(f): return f
19734
19735class C:
19736    @dec
19737    def f(self):
19738        yield
19739
19740@dec
19741class D:
19742    pass
19743
19744class E:
19745    @dec
19746    def g(self, flags: int, /) -> memoryview:
19747        raise NotImplementedError
19748",
19749        );
19750        let c = find_code(&code, "C").expect("missing C code");
19751        let d = find_code(&code, "D").expect("missing D code");
19752        let e = find_code(&code, "E").expect("missing E code");
19753        let annotate = find_code(e, "__annotate__").expect("missing annotation code");
19754
19755        // CPython 3.14 codegen_function()/codegen_class() evaluate
19756        // decorators first, then use LOC(s) for codegen_make_closure() and
19757        // codegen_nameop(); codegen_apply_decorators() emits CALL at each
19758        // decorator expression's location.
19759        assert_eq!(
19760            c.linetable.as_ref(),
19761            &[
19762                0xf8, 0x87, 0x00, 0x80, 0x00, 0xd8, 0x05, 0x08, 0xf1, 0x02, 0x01, 0x05, 0x0e, 0xf3,
19763                0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x0e,
19764            ]
19765        );
19766        assert_eq!(d.linetable.as_ref(), &[0x86, 0x00, 0xe3, 0x04, 0x08]);
19767        assert_eq!(
19768            e.linetable.as_ref(),
19769            &[
19770                0xf8, 0x87, 0x00, 0x80, 0x00, 0xd8, 0x05, 0x08, 0xf7, 0x02, 0x01, 0x05, 0x22, 0xf3,
19771                0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x22,
19772            ]
19773        );
19774        assert_eq!(
19775            annotate.linetable.as_ref(),
19776            &[
19777                0xf8, 0x80, 0x00, 0xf7, 0x00, 0x01, 0x05, 0x22, 0xf1, 0x00, 0x01, 0x05, 0x22, 0x91,
19778                0x73, 0xf0, 0x00, 0x01, 0x05, 0x22, 0xa1, 0x2a, 0xf1, 0x00, 0x01, 0x05, 0x22,
19779            ]
19780        );
19781    }
19782
19783    #[test]
19784    fn taken_constant_boolop_jump_disables_following_borrows() {
19785        for source in [
19786            "\
19787def f(self):
19788    if 0 and self.h:
19789        self.x = self.y
19790    elif self.a and self.b:
19791        self.x = self.y
19792    self.z = self.w
19793",
19794            "\
19795def f(self):
19796    if 1 or self.h:
19797        self.x = self.y
19798    self.z = self.w
19799",
19800        ] {
19801            let code = compile_exec(source);
19802            let f = find_code(&code, "f").expect("missing f code");
19803            let ops: Vec<_> = f
19804                .instructions
19805                .iter()
19806                .map(|unit| unit.op)
19807                .filter(|op| !matches!(op, Instruction::Cache))
19808                .collect();
19809            assert!(
19810                ops.iter()
19811                    .any(|op| matches!(op, Instruction::LoadFast { .. })),
19812                "CPython keeps plain LOAD_FAST after an always-taken constant bool-op jump, got ops={ops:?}"
19813            );
19814            assert!(
19815                !ops.iter()
19816                    .any(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
19817                "always-taken constant bool-op jump should suppress later LOAD_FAST_BORROW, got ops={ops:?}"
19818            );
19819        }
19820    }
19821
19822    #[test]
19823    fn not_taken_constant_boolop_jump_keeps_following_borrows() {
19824        for source in [
19825            "\
19826def f(self):
19827    if 1 and self.h:
19828        self.x = self.y
19829    self.z = self.w
19830",
19831            "\
19832def f(self):
19833    if 0 or self.h:
19834        self.x = self.y
19835    self.z = self.w
19836",
19837        ] {
19838            let code = compile_exec(source);
19839            let f = find_code(&code, "f").expect("missing f code");
19840            let ops: Vec<_> = f
19841                .instructions
19842                .iter()
19843                .map(|unit| unit.op)
19844                .filter(|op| !matches!(op, Instruction::Cache))
19845                .collect();
19846            assert!(
19847                ops.iter()
19848                    .any(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
19849                "constant bool-op jump that is not taken should keep CPython-style borrows, got ops={ops:?}"
19850            );
19851        }
19852    }
19853
19854    #[test]
19855    fn while_before_folded_boolop_if_keeps_successor_load_fast_strong() {
19856        let code = compile_exec(
19857            "\
19858def f(running, errors):
19859    try:
19860        while running:
19861            pass
19862        if __debug__ and errors:
19863            raise ExceptionGroup('x', errors)
19864        return errors
19865    finally:
19866        del errors
19867",
19868        );
19869        let f = find_code(&code, "f").expect("missing function code");
19870        let errors_loads = load_fast_ops_for_var(f, "errors");
19871        assert!(
19872            errors_loads
19873                .iter()
19874                .all(|op| matches!(op, Instruction::LoadFast { .. })),
19875            "CPython codegen_while() emits USE_LABEL(end), then codegen_jump_if() emits a folded constant BoolOp prefix; flowgraph.c::basicblock_optimize_load_const() and optimize_load_fast() leave the successor errors loads strong, got {errors_loads:?}"
19876        );
19877    }
19878
19879    #[test]
19880    fn with_try_except_tail_keeps_successor_load_fast_strong() {
19881        let code = compile_exec(
19882            "\
19883def f(self, cm, value):
19884    with cm:
19885        try:
19886            self.run()
19887        except OSError as e:
19888            if e.errno:
19889                raise ConnectionError
19890            else:
19891                raise
19892    self.run_loop(value.done)
19893    self.assertTrue(value.nbytes)
19894",
19895        );
19896        let f = find_code(&code, "f").expect("missing function code");
19897        let value_loads = load_fast_ops_for_var(f, "value");
19898        assert!(
19899            value_loads
19900                .iter()
19901                .all(|op| matches!(op, Instruction::LoadFast { .. })),
19902            "CPython codegen_try_except() leaves USE_LABEL(end) before codegen_with_inner() emits normal __exit__ cleanup, so optimize_load_fast() leaves with-successor loads strong; got {value_loads:?}"
19903        );
19904    }
19905
19906    #[test]
19907    fn with_try_except_conditional_body_allows_successor_borrow() {
19908        let code = compile_exec(
19909            "\
19910def f(max_decode, gzf, cm, decoded):
19911    with cm:
19912        try:
19913            if max_decode < 0:
19914                decoded = gzf.read()
19915            else:
19916                decoded = gzf.read(max_decode + 1)
19917        except OSError:
19918            raise ValueError('invalid data')
19919    if max_decode >= 0 and len(decoded) > max_decode:
19920        raise ValueError('too large')
19921    return decoded
19922",
19923        );
19924        let f = find_code(&code, "f").expect("missing f code");
19925        let max_decode_loads = load_fast_ops_for_var(f, "max_decode");
19926        assert!(
19927            max_decode_loads
19928                .iter()
19929                .all(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
19930            "CPython codegen_try_except() keeps borrowing through a with tail when the protected try body ends with a conditional label, got {max_decode_loads:?}"
19931        );
19932    }
19933
19934    #[test]
19935    fn loop_try_orelse_nested_try_before_next_try_keeps_load_fast_strong() {
19936        let code = compile_exec(
19937            "\
19938def f(test_cases):
19939    for src, dest in test_cases:
19940        try:
19941            os.symlink(src, dest)
19942        except FileNotFoundError:
19943            pass
19944        else:
19945            try:
19946                os.remove(dest)
19947            except OSError:
19948                pass
19949        try:
19950            os.symlink(os.fsencode(src), os.fsencode(dest))
19951        except FileNotFoundError:
19952            pass
19953        else:
19954            try:
19955                os.remove(dest)
19956            except OSError:
19957                pass
19958",
19959        );
19960        let f = find_code(&code, "f").expect("missing function code");
19961        let strong_src_dest_loads = count_strong_loads_for_vars(f, &["src", "dest"]);
19962        assert!(
19963            strong_src_dest_loads >= 2,
19964            "CPython codegen_try_except() emits orelse then USE_LABEL(end) before the following loop try, so optimize_load_fast() leaves fsencode arguments strong; got {strong_src_dest_loads} strong src/dest loads"
19965        );
19966    }
19967
19968    #[test]
19969    fn try_orelse_with_before_next_try_keeps_load_fast_strong() {
19970        let code = compile_exec(
19971            "\
19972def f(self, f, cm):
19973    try:
19974        f = C(0)
19975    except ValueError:
19976        pass
19977    else:
19978        self.assertTrue(f.readable())
19979        with cm:
19980            with C(False):
19981                pass
19982    try:
19983        f = C(1)
19984    except ValueError:
19985        pass
19986    else:
19987        self.assertFalse(f.readable())
19988",
19989        );
19990        let f_code = find_code(&code, "f").expect("missing function code");
19991        let strong_self_or_f_loads = count_strong_loads_for_vars(f_code, &["self", "f"]);
19992        assert!(
19993            strong_self_or_f_loads >= 2,
19994            "CPython codegen_try_except() emits orelse with nested with cleanup before USE_LABEL(end), so optimize_load_fast() leaves loads in the following try/else strong; got {strong_self_or_f_loads} strong self/f loads"
19995        );
19996    }
19997
19998    #[test]
19999    fn try_orelse_single_with_before_next_try_keeps_borrows() {
20000        let code = compile_exec(
20001            "\
20002def f(self, cm):
20003    try:
20004        import _testcapi
20005    except ImportError:
20006        pass
20007    else:
20008        code = 'x'
20009        with cm:
20010            out = self.run_xdev('-c', code)
20011        self.assertEqual(out, 'x')
20012    try:
20013        import faulthandler
20014    except ImportError:
20015        pass
20016    else:
20017        code = 'y'
20018        out = self.run_xdev('-c', code)
20019        self.assertEqual(out, 'y')
20020",
20021        );
20022        let f = find_code(&code, "f").expect("missing function code");
20023        let instructions: Vec<_> = f
20024            .instructions
20025            .iter()
20026            .filter(|unit| !matches!(unit.op, Instruction::Cache))
20027            .collect();
20028        let final_return = instructions
20029            .iter()
20030            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
20031            .expect("missing return");
20032        let tail = &instructions[..final_return];
20033        let borrowed_self_loads = tail
20034            .iter()
20035            .filter(|unit| match unit.op {
20036                Instruction::LoadFastBorrow { var_num } => {
20037                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
20038                    f.varnames[usize::from(var_num.get(arg))] == "self"
20039                }
20040                _ => false,
20041            })
20042            .count();
20043        assert!(
20044            borrowed_self_loads >= 4,
20045            "CPython codegen_with() for a single with in try/else does not make the following try/else a load-fast barrier; expected borrowed self loads, got instructions={instructions:?}"
20046        );
20047    }
20048
20049    #[test]
20050    fn with_try_finally_nested_with_keeps_successor_load_fast_strong() {
20051        let code = compile_exec(
20052            "\
20053def f(self, cm):
20054    with cm as cm1:
20055        try:
20056            work()
20057        finally:
20058            with cm as cm2:
20059                work()
20060    e1 = cm1.exception
20061    e12 = e1.__cause__
20062    self.assertIsInstance(e12, Error)
20063",
20064        );
20065        let f = find_code(&code, "f").expect("missing function code");
20066        let strong_loads = count_strong_loads(f);
20067        assert!(
20068            strong_loads >= 3,
20069            "CPython codegen_try_finally() inlines a finalbody with codegen_with_inner() cleanup before the with exit label, so optimize_load_fast() leaves successor loads strong; got {strong_loads} strong loads"
20070        );
20071    }
20072
20073    #[test]
20074    fn nonliteral_constant_bool_op_preserves_short_circuit_shape() {
20075        let code = compile_exec(
20076            "\
20077x = (\"a\"[0]) or 2
20078",
20079        );
20080        let ops: Vec<_> = code
20081            .instructions
20082            .iter()
20083            .map(|unit| unit.op)
20084            .filter(|op| !matches!(op, Instruction::Cache))
20085            .collect();
20086
20087        assert!(
20088            !code.instructions.iter().any(|unit| matches!(
20089                unit.op,
20090                Instruction::BinaryOp { op }
20091                    if op.get(OpArg::new(u32::from(u8::from(unit.arg))))
20092                        == oparg::BinaryOperator::Subscr
20093            )),
20094            "constant subscript should fold before bool-op lowering, got ops={ops:?}"
20095        );
20096        assert!(
20097            ops.iter().any(|op| matches!(op, Instruction::Copy { .. })),
20098            "folded non-literal BoolOp operand should keep COPY, got ops={ops:?}"
20099        );
20100        assert!(
20101            ops.iter().any(|op| matches!(op, Instruction::ToBool)),
20102            "folded non-literal BoolOp operand should keep TO_BOOL, got ops={ops:?}"
20103        );
20104        assert!(
20105            ops.iter()
20106                .any(|op| matches!(op, Instruction::PopJumpIfTrue { .. })),
20107            "folded non-literal BoolOp operand should keep POP_JUMP_IF_TRUE, got ops={ops:?}"
20108        );
20109    }
20110
20111    #[test]
20112    fn unary_positive_complex_constant_folds_to_load_const() {
20113        let code = compile_exec(
20114            "\
20115x = +0.0j
20116",
20117        );
20118        let ops: Vec<_> = code
20119            .instructions
20120            .iter()
20121            .map(|unit| unit.op)
20122            .filter(|op| !matches!(op, Instruction::Cache))
20123            .collect();
20124
20125        assert!(
20126            !ops.iter()
20127                .any(|op| matches!(op, Instruction::CallIntrinsic1 { .. })),
20128            "unary positive complex constant should not leave CALL_INTRINSIC_1, got ops={ops:?}"
20129        );
20130        assert!(
20131            matches!(
20132                ops.as_slice(),
20133                [
20134                    Instruction::Resume { .. },
20135                    Instruction::LoadConst { .. },
20136                    Instruction::StoreName { .. },
20137                    Instruction::LoadConst { .. },
20138                    Instruction::ReturnValue
20139                ]
20140            ),
20141            "expected module assignment to fold +0.0j into LOAD_CONST, got ops={ops:?}"
20142        );
20143    }
20144
20145    #[test]
20146    fn folded_nonliteral_bool_op_tail_keeps_plain_load_fast() {
20147        let code = compile_exec(
20148            "\
20149def and_true(x):
20150    return True and x
20151
20152def or_false(x):
20153    return False or x
20154",
20155        );
20156
20157        for name in ["and_true", "or_false"] {
20158            let function = find_code(&code, name).unwrap_or_else(|| panic!("missing {name} code"));
20159            let ops: Vec<_> = function
20160                .instructions
20161                .iter()
20162                .map(|unit| unit.op)
20163                .filter(|op| !matches!(op, Instruction::Cache))
20164                .collect();
20165
20166            assert!(
20167                ops.iter()
20168                    .any(|op| matches!(op, Instruction::LoadFast { .. })),
20169                "expected folded bool-op tail to keep LOAD_FAST in {name}, got ops={ops:?}"
20170            );
20171            assert!(
20172                !ops.iter().any(|op| {
20173                    matches!(
20174                        op,
20175                        Instruction::LoadFastBorrow { .. }
20176                            | Instruction::LoadFastBorrowLoadFastBorrow { .. }
20177                    )
20178                }),
20179                "folded bool-op tail should not introduce borrow loads in {name}, got ops={ops:?}"
20180            );
20181        }
20182    }
20183
20184    #[test]
20185    fn folded_nonliteral_bool_op_direct_tail_load_keeps_plain_load_fast() {
20186        let code = compile_exec(
20187            "\
20188def return_tail(x):
20189    return False or x
20190
20191def assign_tail(x):
20192    y = False or x
20193    return y
20194
20195def call_arg(x, g):
20196    return g(False or x)
20197
20198def attr_tail(x):
20199    return False or x.y
20200
20201def class_tail(class_decorator):
20202    @False or class_decorator
20203    class H:
20204        pass
20205",
20206        );
20207
20208        for name in ["return_tail", "assign_tail", "call_arg", "class_tail"] {
20209            let function = find_code(&code, name).unwrap_or_else(|| panic!("missing {name} code"));
20210            let local = if name == "class_tail" {
20211                "class_decorator"
20212            } else {
20213                "x"
20214            };
20215            let loads = load_fast_ops_for_var(function, local);
20216            assert!(
20217                loads
20218                    .iter()
20219                    .any(|op| matches!(op, Instruction::LoadFast { .. })),
20220                "CPython codegen_boolop() emits USE_LABEL(end) after the folded BoolOp tail, so optimize_load_fast() leaves the direct tail load strong in {name}; got {loads:?}"
20221            );
20222        }
20223
20224        let attr_tail = find_code(&code, "attr_tail").expect("missing attr_tail code");
20225        let attr_receiver_loads = load_fast_ops_for_var(attr_tail, "x");
20226        assert!(
20227            attr_receiver_loads
20228                .iter()
20229                .all(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
20230            "CPython only keeps direct folded tail local loads strong; an attribute receiver is consumed before the BoolOp end label, got {attr_receiver_loads:?}"
20231        );
20232    }
20233
20234    #[test]
20235    fn folded_nonliteral_tuple_unpack_tail_keeps_plain_load_fast() {
20236        let code = compile_exec(
20237            "\
20238def f(self, mod):
20239    optimize, opt = (1, 1) if __debug__ else (0, '')
20240    mod.call(self.path, optimize=optimize)
20241    cached = mod.cache(self.source_path, optimization=opt)
20242    self.assertTrue(cached)
20243",
20244        );
20245        let f = find_code(&code, "f").expect("missing f code");
20246        let ops: Vec<_> = f
20247            .instructions
20248            .iter()
20249            .map(|unit| unit.op)
20250            .filter(|op| !matches!(op, Instruction::Cache))
20251            .collect();
20252        let tail_start = ops
20253            .iter()
20254            .position(|op| matches!(op, Instruction::LoadFast { .. }))
20255            .expect("missing folded assignment tail load");
20256        let tail = &ops[tail_start..];
20257
20258        assert!(
20259            tail.iter()
20260                .any(|op| matches!(op, Instruction::LoadFast { .. })),
20261            "expected folded nonliteral tuple-unpack tail to use strong LOAD_FAST, got tail={tail:?}"
20262        );
20263        assert!(
20264            !tail.iter().any(|op| {
20265                matches!(
20266                    op,
20267                    Instruction::LoadFastBorrow { .. }
20268                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
20269                )
20270            }),
20271            "folded nonliteral tuple-unpack tail should not borrow local loads, got tail={tail:?}"
20272        );
20273    }
20274
20275    #[test]
20276    fn scope_exit_instructions_keep_line_numbers() {
20277        let code = compile_exec(
20278            "\
20279async def test():
20280    for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):
20281        with self.subTest(type=type(stop_exc)):
20282            try:
20283                async with egg():
20284                    raise stop_exc
20285            except Exception as ex:
20286                self.assertIs(ex, stop_exc)
20287            else:
20288                self.fail(f'{stop_exc} was suppressed')
20289",
20290        );
20291        assert_scope_exit_locations(&code);
20292    }
20293
20294    #[test]
20295    fn attribute_ex_call_uses_plain_load_attr() {
20296        let code = compile_exec(
20297            "\
20298def f(cls, args, kwargs):
20299    cls.__new__(cls, *args)
20300    cls.__new__(cls, *args, **kwargs)
20301",
20302        );
20303        let f = find_code(&code, "f").expect("missing function code");
20304
20305        let ex_call_count = f
20306            .instructions
20307            .iter()
20308            .filter(|unit| matches!(unit.op, Instruction::CallFunctionEx))
20309            .count();
20310        let load_attr_count = f
20311            .instructions
20312            .iter()
20313            .filter(|unit| matches!(unit.op, Instruction::LoadAttr { .. }))
20314            .count();
20315
20316        assert_eq!(ex_call_count, 2);
20317        assert_eq!(load_attr_count, 2);
20318
20319        for unit in f.instructions.iter() {
20320            if let Instruction::LoadAttr { namei } = unit.op {
20321                let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
20322                assert!(
20323                    !load_attr.is_method(),
20324                    "CALL_FUNCTION_EX should use plain LOAD_ATTR"
20325                );
20326            }
20327        }
20328    }
20329
20330    #[test]
20331    fn method_call_at_stack_guideline_uses_plain_load_attr_like_cpython() {
20332        let params = (0..STACK_USE_GUIDELINE)
20333            .map(|i| format!("a{i}"))
20334            .collect::<Vec<_>>()
20335            .join(", ");
20336        let code = compile_exec(&format!(
20337            "def f(obj, {params}):\n    return obj.m({params})\n"
20338        ));
20339        let f = find_code(&code, "f").expect("missing function code");
20340        let ops: Vec<_> = f
20341            .instructions
20342            .iter()
20343            .map(|unit| unit.op)
20344            .filter(|op| !matches!(op, Instruction::Cache))
20345            .collect();
20346
20347        let plain_load_attr = f.instructions.iter().any(|unit| {
20348            if let Instruction::LoadAttr { namei } = unit.op {
20349                !namei
20350                    .get(OpArg::new(u32::from(u8::from(unit.arg))))
20351                    .is_method()
20352            } else {
20353                false
20354            }
20355        });
20356        let direct_call_30 = f.instructions.iter().any(|unit| match unit.op {
20357            Instruction::Call { argc } => {
20358                argc.get(OpArg::new(u32::from(u8::from(unit.arg)))) == STACK_USE_GUIDELINE
20359            }
20360            _ => false,
20361        });
20362
20363        assert!(
20364            plain_load_attr && direct_call_30,
20365            "CPython maybe_optimize_method_call rejects arg count at the guideline, got ops={ops:?}"
20366        );
20367        assert!(
20368            !ops.iter()
20369                .any(|op| matches!(op, Instruction::CallFunctionEx)),
20370            "exactly guideline-sized method call should stay direct after LOAD_ATTR fallback, got ops={ops:?}"
20371        );
20372    }
20373
20374    #[test]
20375    fn method_call_many_keywords_stays_load_method_call_kw_like_cpython() {
20376        let params = (0..16)
20377            .map(|i| format!("a{i}"))
20378            .collect::<Vec<_>>()
20379            .join(", ");
20380        let keywords = (0..16)
20381            .map(|i| format!("k{i}=a{i}"))
20382            .collect::<Vec<_>>()
20383            .join(", ");
20384        let code = compile_exec(&format!(
20385            "def f(obj, {params}):\n    return obj.m({keywords})\n"
20386        ));
20387        let f = find_code(&code, "f").expect("missing function code");
20388        let ops: Vec<_> = f
20389            .instructions
20390            .iter()
20391            .map(|unit| unit.op)
20392            .filter(|op| !matches!(op, Instruction::Cache))
20393            .collect();
20394
20395        let method_load_attr = f.instructions.iter().any(|unit| {
20396            if let Instruction::LoadAttr { namei } = unit.op {
20397                namei
20398                    .get(OpArg::new(u32::from(u8::from(unit.arg))))
20399                    .is_method()
20400            } else {
20401                false
20402            }
20403        });
20404        let call_kw_16 = f.instructions.iter().any(|unit| match unit.op {
20405            Instruction::CallKw { argc } => {
20406                argc.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 16
20407            }
20408            _ => false,
20409        });
20410
20411        assert!(
20412            method_load_attr && call_kw_16,
20413            "CPython maybe_optimize_method_call emits LOAD_METHOD/CALL_KW under its own stack threshold, got ops={ops:?}"
20414        );
20415        assert!(
20416            !ops.iter()
20417                .any(|op| matches!(op, Instruction::CallFunctionEx)),
20418            "method-call keyword path should not reuse codegen_call_helper_impl's lower kw threshold, got ops={ops:?}"
20419        );
20420    }
20421
20422    #[test]
20423    fn large_plain_call_uses_direct_call_until_stack_guideline() {
20424        let code = compile_exec(
20425            "\
20426def f(g):
20427    return g(a0, a1, a2, a3, a4, a5, a6, a7, a8,
20428             a9, a10, a11, a12, a13, a14, a15, a16, a17)
20429",
20430        );
20431        let f = find_code(&code, "f").expect("missing function code");
20432        let direct_call_18 = f.instructions.iter().any(|unit| match unit.op {
20433            Instruction::Call { argc } => argc.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 18,
20434            _ => false,
20435        });
20436        let ops: Vec<_> = f
20437            .instructions
20438            .iter()
20439            .map(|unit| unit.op)
20440            .filter(|op| !matches!(op, Instruction::Cache))
20441            .collect();
20442
20443        assert!(
20444            direct_call_18,
20445            "18 positional arguments should stay on CPython's direct CALL path, got ops={ops:?}"
20446        );
20447        assert!(
20448            !ops.iter()
20449                .any(|op| matches!(op, Instruction::CallFunctionEx)),
20450            "18 positional arguments should not use CALL_FUNCTION_EX, got ops={ops:?}"
20451        );
20452    }
20453
20454    #[test]
20455    fn too_large_plain_call_uses_cpython_tuple_ex_call_path() {
20456        let args = (0..=STACK_USE_GUIDELINE)
20457            .map(|i| format!("'v{i}'"))
20458            .collect::<Vec<_>>()
20459            .join(", ");
20460        let code = compile_exec(&format!("def f(g):\n    return g({args})\n"));
20461        let f = find_code(&code, "f").expect("missing function code");
20462        let ops: Vec<_> = f
20463            .instructions
20464            .iter()
20465            .map(|unit| unit.op)
20466            .filter(|op| !matches!(op, Instruction::Cache))
20467            .collect();
20468
20469        assert!(
20470            ops.iter()
20471                .any(|op| matches!(op, Instruction::CallFunctionEx)),
20472            "CPython routes calls over _PY_STACK_USE_GUIDELINE through CALL_FUNCTION_EX, got ops={ops:?}"
20473        );
20474        assert!(
20475            !ops.iter().any(|op| matches!(
20476                op,
20477                Instruction::BuildTuple { .. }
20478                    | Instruction::ListAppend { .. }
20479                    | Instruction::CallIntrinsic1 { .. }
20480            )),
20481            "CPython flowgraph.c folds the starunpack tuple for constant too-large calls, got ops={ops:?}"
20482        );
20483        assert!(
20484            f.constants.iter().any(|constant| {
20485                matches!(
20486                    constant,
20487                    ConstantData::Tuple { elements }
20488                        if elements.len() == usize::try_from(STACK_USE_GUIDELINE + 1).unwrap()
20489                )
20490            }),
20491            "expected CPython folded tuple constant for too-large call args, got constants={:?}",
20492            f.constants.iter().collect::<Vec<_>>()
20493        );
20494    }
20495
20496    #[test]
20497    fn simple_attribute_call_keeps_method_load() {
20498        let code = compile_exec(
20499            "\
20500def f(obj, arg):
20501    return obj.method(arg)
20502",
20503        );
20504        let f = find_code(&code, "f").expect("missing function code");
20505        let load_attr = f
20506            .instructions
20507            .iter()
20508            .find_map(|unit| match unit.op {
20509                Instruction::LoadAttr { namei } => {
20510                    Some(namei.get(OpArg::new(u32::from(u8::from(unit.arg)))))
20511                }
20512                _ => None,
20513            })
20514            .expect("missing LOAD_ATTR");
20515
20516        assert!(
20517            load_attr.is_method(),
20518            "simple method calls should stay optimized"
20519        );
20520    }
20521
20522    #[test]
20523    fn starred_super_call_keeps_attr_line_nop() {
20524        let code = compile_exec(
20525            "\
20526def outer(log):
20527    class DelegatingHTTPRequestHandler(BaseHTTPRequestHandler):
20528        def log_message(self, format, *args):
20529            if log:
20530                super(DelegatingHTTPRequestHandler,
20531                      self).log_message(format, *args)
20532",
20533        );
20534        let log_message = find_code(&code, "log_message").expect("missing log_message code");
20535        let ops: Vec<_> = log_message
20536            .instructions
20537            .iter()
20538            .map(|unit| unit.op)
20539            .filter(|op| !matches!(op, Instruction::Cache))
20540            .collect();
20541
20542        assert!(
20543            ops.windows(4).any(|window| {
20544                matches!(
20545                    window,
20546                    [
20547                        Instruction::LoadSuperAttr { .. },
20548                        Instruction::Nop,
20549                        Instruction::PushNull,
20550                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
20551                    ]
20552                )
20553            }),
20554            "starred super call should keep CPython's attr-line NOP after LOAD_SUPER_ATTR, got ops={ops:?}"
20555        );
20556    }
20557
20558    #[test]
20559    fn builtin_any_genexpr_call_is_optimized() {
20560        let code = compile_exec(
20561            "\
20562def f(xs):
20563    return any(x for x in xs)
20564",
20565        );
20566        let f = find_code(&code, "f").expect("missing function code");
20567
20568        assert!(has_common_constant(f, bytecode::CommonConstant::BuiltinAny));
20569        assert!(
20570            f.instructions
20571                .iter()
20572                .any(|unit| matches!(unit.op, Instruction::PopJumpIfTrue { .. }))
20573        );
20574        assert!(
20575            f.instructions
20576                .iter()
20577                .any(|unit| matches!(unit.op, Instruction::NotTaken))
20578        );
20579        assert_eq!(
20580            f.instructions
20581                .iter()
20582                .filter(|unit| matches!(unit.op, Instruction::PushNull))
20583                .count(),
20584            1,
20585            "fallback call path should remain for shadowed any()"
20586        );
20587        let genexpr_const_count = f
20588            .constants
20589            .iter()
20590            .filter(|constant| {
20591                matches!(constant, ConstantData::Code { code } if code.obj_name == "<genexpr>")
20592            })
20593            .count();
20594        assert_eq!(
20595            genexpr_const_count, 1,
20596            "optimized and fallback any(genexpr) paths should share the same CPython-range code const"
20597        );
20598        assert_eq!(
20599            f.linetable.as_ref(),
20600            &[
20601                0x80, 0x00, 0xdf, 0x0b, 0x0e, 0x8b, 0x33, 0x89, 0x6f, 0x99, 0x22, 0x8b, 0x6f, 0x8f,
20602                0x33, 0x8c, 0x33, 0xd0, 0x04, 0x1d, 0x8a, 0x33, 0xd0, 0x04, 0x1d, 0x88, 0x33, 0x89,
20603                0x6f, 0x99, 0x22, 0x8b, 0x6f, 0xd3, 0x0b, 0x1d, 0xd0, 0x04, 0x1d,
20604            ]
20605        );
20606    }
20607
20608    #[test]
20609    fn builtin_any_async_genexpr_call_is_optimized_like_cpython() {
20610        for source in [
20611            "async def f(xs):\n    return any(x async for x in xs)\n",
20612            "async def f(xs):\n    return any(await x for x in xs)\n",
20613        ] {
20614            let code = compile_exec(source);
20615            let f = find_code(&code, "f").expect("missing function code");
20616
20617            assert!(
20618                has_common_constant(f, bytecode::CommonConstant::BuiltinAny),
20619                "maybe_optimize_function_call() guards any(genexpr) whether or not the \
20620                 generator is a coroutine: {source}"
20621            );
20622            assert!(
20623                f.instructions
20624                    .iter()
20625                    .any(|unit| matches!(unit.op, Instruction::ForIter { .. })),
20626                "the guarded path inlines the loop: {source}"
20627            );
20628            assert!(
20629                f.instructions
20630                    .iter()
20631                    .any(|unit| matches!(unit.op, Instruction::Call { .. })),
20632                "the fallback still calls the name it loaded: {source}"
20633            );
20634        }
20635    }
20636
20637    #[test]
20638    fn builtin_any_genexpr_outermost_await_is_optimized_like_cpython() {
20639        let code = compile_exec(
20640            "\
20641async def f(get_xs):
20642    return any(x for x in await get_xs())
20643",
20644        );
20645        let f = find_code(&code, "f").expect("missing function code");
20646
20647        assert!(
20648            has_common_constant(f, bytecode::CommonConstant::BuiltinAny),
20649            "CPython checks the generator expression symtable entry, so await in the outermost iterator does not make the genexpr coroutine"
20650        );
20651    }
20652
20653    #[test]
20654    fn builtin_tuple_genexpr_call_is_optimized_but_list_set_are_not() {
20655        let code = compile_exec(
20656            "\
20657def tuple_f(xs):
20658    return tuple(x for x in xs)
20659
20660def list_f(xs):
20661    return list(x for x in xs)
20662
20663def set_f(xs):
20664    return set(x for x in xs)
20665",
20666        );
20667
20668        let tuple_f = find_code(&code, "tuple_f").expect("missing tuple_f code");
20669        assert!(has_common_constant(
20670            tuple_f,
20671            bytecode::CommonConstant::BuiltinTuple
20672        ));
20673        assert!(has_intrinsic_1(tuple_f, IntrinsicFunction1::ListToTuple));
20674        let tuple_list_append = tuple_f
20675            .instructions
20676            .iter()
20677            .find_map(|unit| match unit.op {
20678                Instruction::ListAppend { .. } => Some(u32::from(u8::from(unit.arg))),
20679                _ => None,
20680            })
20681            .expect("tuple(genexpr) fast path should emit LIST_APPEND");
20682        assert_eq!(tuple_list_append, 2);
20683        assert_eq!(
20684            tuple_f.linetable.as_ref(),
20685            &[
20686                0x80, 0x00, 0xdf, 0x0b, 0x10, 0x8c, 0x35, 0x91, 0x0f, 0x99, 0x42, 0x93, 0x0f, 0x8f,
20687                0x35, 0xd0, 0x04, 0x1f, 0x88, 0x35, 0x91, 0x0f, 0x99, 0x42, 0x93, 0x0f, 0xd3, 0x0b,
20688                0x1f, 0xd0, 0x04, 0x1f,
20689            ]
20690        );
20691
20692        let list_f = find_code(&code, "list_f").expect("missing list_f code");
20693        assert!(
20694            list_f
20695                .instructions
20696                .iter()
20697                .any(|unit| matches!(unit.op, Instruction::Call { .. })),
20698            "list(genexpr) should stay on the normal call path"
20699        );
20700        assert!(
20701            !has_common_constant(list_f, bytecode::CommonConstant::BuiltinList),
20702            "CPython 3.14.5 does not optimize list(genexpr)"
20703        );
20704
20705        let set_f = find_code(&code, "set_f").expect("missing set_f code");
20706        assert!(
20707            set_f
20708                .instructions
20709                .iter()
20710                .any(|unit| matches!(unit.op, Instruction::Call { .. })),
20711            "set(genexpr) should stay on the normal call path"
20712        );
20713        assert!(
20714            !has_common_constant(set_f, bytecode::CommonConstant::BuiltinSet),
20715            "CPython 3.14.5 does not optimize set(genexpr)"
20716        );
20717    }
20718
20719    #[test]
20720    fn builtin_tuple_genexpr_try_assignment_uses_shared_tail() {
20721        let code = compile_exec(
20722            "\
20723def f(xs):
20724    global y
20725    try:
20726        y = tuple(int(i) for i in xs.split('.'))
20727    except ValueError:
20728        y = ()
20729    return y
20730",
20731        );
20732        let f = find_code(&code, "f").expect("missing function code");
20733        let ops: Vec<_> = f
20734            .instructions
20735            .iter()
20736            .map(|unit| unit.op)
20737            .filter(|op| !matches!(op, Instruction::Cache))
20738            .collect();
20739        let intrinsic = ops
20740            .iter()
20741            .position(|op| matches!(op, Instruction::CallIntrinsic1 { .. }))
20742            .expect("tuple(genexpr) fast path should emit LIST_TO_TUPLE");
20743        let first_fallback = ops[intrinsic + 1..]
20744            .iter()
20745            .position(|op| matches!(op, Instruction::PushNull))
20746            .map(|offset| intrinsic + 1 + offset)
20747            .expect("shadowed tuple fallback call should remain after fast path");
20748        let first_store = ops[intrinsic + 1..]
20749            .iter()
20750            .position(|op| matches!(op, Instruction::StoreGlobal { .. }))
20751            .map(|offset| intrinsic + 1 + offset)
20752            .expect("tuple(genexpr) result should be stored after fast or fallback call");
20753
20754        assert!(
20755            matches!(ops[intrinsic + 1], Instruction::JumpForward { .. })
20756                && first_fallback < first_store,
20757            "tuple(genexpr) fast path should jump over fallback to CPython-style shared store tail, got ops={ops:?}"
20758        );
20759    }
20760
20761    #[test]
20762    fn builtin_tuple_genexpr_unprotected_assignment_return_duplicates_tail() {
20763        let code = compile_exec(
20764            "\
20765def f(arg):
20766    if isinstance(arg, (list, tuple)):
20767        arg = tuple(a for a in arg)
20768    elif not p(arg):
20769        raise TypeError(f'bad {arg}')
20770    return arg
20771",
20772        );
20773        let f = find_code(&code, "f").expect("missing function code");
20774        let ops: Vec<_> = f
20775            .instructions
20776            .iter()
20777            .map(|unit| unit.op)
20778            .filter(|op| !matches!(op, Instruction::Cache))
20779            .collect();
20780        let intrinsic = ops
20781            .iter()
20782            .position(|op| matches!(op, Instruction::CallIntrinsic1 { .. }))
20783            .expect("tuple(genexpr) fast path should emit LIST_TO_TUPLE");
20784
20785        assert!(
20786            matches!(ops[intrinsic + 1], Instruction::StoreFast { .. })
20787                && matches!(
20788                    ops[intrinsic + 2],
20789                    Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. }
20790                )
20791                && matches!(ops[intrinsic + 3], Instruction::ReturnValue),
20792            "unprotected tuple(genexpr) assignment before return should inline CPython's assignment-return tail, got ops={ops:?}"
20793        );
20794    }
20795
20796    #[test]
20797    fn unprotected_builtin_any_prefix_before_returning_try_keeps_borrow() {
20798        let code = compile_exec(
20799            "\
20800def f(template):
20801    if any(part.expression.strip() == '' for part in template.interpolations):
20802        return ctor(template)
20803    try:
20804        parsed = tuple(
20805            ast.parse(f'({part.expression})', mode='eval').body
20806            for part in template.interpolations
20807        )
20808    except SyntaxError:
20809        return ctor(template)
20810    return lit(template, parsed)
20811",
20812        );
20813        let f = find_code(&code, "f").expect("missing f code");
20814        let instructions: Vec<_> = f
20815            .instructions
20816            .iter()
20817            .filter(|unit| !matches!(unit.op, Instruction::Cache))
20818            .collect();
20819        let first_try_nop = instructions
20820            .iter()
20821            .position(|unit| matches!(unit.op, Instruction::Nop))
20822            .expect("missing try entry NOP");
20823        let mut saw_interpolations = false;
20824        for window in instructions[..first_try_nop].windows(2) {
20825            let [receiver, attr] = window else {
20826                continue;
20827            };
20828            let Instruction::LoadAttr { namei } = attr.op else {
20829                continue;
20830            };
20831            let load_attr = namei.get(OpArg::new(u32::from(u8::from(attr.arg))));
20832            if f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() != "interpolations"
20833            {
20834                continue;
20835            }
20836            saw_interpolations = true;
20837            assert!(
20838                matches!(receiver.op, Instruction::LoadFastBorrow { .. }),
20839                "unprotected builtin any(genexpr) prefix before a later returning try should keep CPython-style borrowed receiver, got instructions={instructions:?}"
20840            );
20841        }
20842        assert!(
20843            saw_interpolations,
20844            "missing interpolations attr load in builtin any prefix, got instructions={instructions:?}"
20845        );
20846    }
20847
20848    #[test]
20849    fn module_store_uses_store_global_when_nested_scope_declares_global() {
20850        let code = compile_exec(
20851            "\
20852_address_fmt_re = None
20853
20854class C:
20855    def f(self):
20856        global _address_fmt_re
20857        if _address_fmt_re is None:
20858            _address_fmt_re = 1
20859",
20860        );
20861
20862        assert!(code.instructions.iter().any(|unit| match unit.op {
20863            Instruction::StoreGlobal { namei } => {
20864                let idx = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
20865                code.names[usize::try_from(idx).unwrap()].as_str() == "_address_fmt_re"
20866            }
20867            _ => false,
20868        }));
20869    }
20870
20871    #[test]
20872    fn conditional_return_epilogue_is_duplicated() {
20873        let code = compile_exec(
20874            "\
20875def f(base, cls, state):
20876    if base is object:
20877        obj = object.__new__(cls)
20878    else:
20879        obj = base.__new__(cls, state)
20880    return obj
20881",
20882        );
20883        let f = find_code(&code, "f").expect("missing function code");
20884        let return_count = f
20885            .instructions
20886            .iter()
20887            .filter(|unit| matches!(unit.op, Instruction::ReturnValue))
20888            .count();
20889
20890        assert_eq!(return_count, 2);
20891    }
20892
20893    #[test]
20894    fn loop_store_subscr_threads_direct_backedge() {
20895        let code = compile_exec(
20896            "\
20897def f(kwonlyargs, kw_only_defaults, arg2value):
20898    missing = 0
20899    for kwarg in kwonlyargs:
20900        if kwarg not in arg2value:
20901            if kw_only_defaults and kwarg in kw_only_defaults:
20902                arg2value[kwarg] = kw_only_defaults[kwarg]
20903            else:
20904                missing += 1
20905    return missing
20906",
20907        );
20908        let f = find_code(&code, "f").expect("missing function code");
20909        let ops: Vec<_> = f
20910            .instructions
20911            .iter()
20912            .map(|unit| unit.op)
20913            .filter(|op| !matches!(op, Instruction::Cache))
20914            .collect();
20915
20916        let store_subscr = ops
20917            .iter()
20918            .position(|op| matches!(op, Instruction::StoreSubscr))
20919            .expect("missing STORE_SUBSCR");
20920        let next_op = ops
20921            .get(store_subscr + 1)
20922            .expect("missing jump after STORE_SUBSCR");
20923        let window_start = store_subscr.saturating_sub(3);
20924        let window_end = (store_subscr + 5).min(ops.len());
20925        let window = &ops[window_start..window_end];
20926
20927        assert!(
20928            matches!(next_op, Instruction::JumpBackward { .. }),
20929            "expected direct loop backedge after STORE_SUBSCR, got {next_op:?}; ops={window:?}"
20930        );
20931    }
20932
20933    #[test]
20934    fn protected_store_subscr_tail_uses_strong_loads() {
20935        let code = compile_exec(
20936            "\
20937def f(cache, lock, format):
20938    with lock:
20939        format_regex = cache.get(format)
20940        if not format_regex:
20941            try:
20942                format_regex = cache.compile(format)
20943            except KeyError as err:
20944                bad_directive = err.args[0]
20945                del err
20946                raise ValueError(bad_directive) from None
20947            cache[format] = format_regex
20948    return format_regex.match('x')
20949",
20950        );
20951        let f = find_code(&code, "f").expect("missing function code");
20952        let ops: Vec<_> = f
20953            .instructions
20954            .iter()
20955            .map(|unit| unit.op)
20956            .filter(|op| !matches!(op, Instruction::Cache))
20957            .collect();
20958
20959        assert!(
20960            ops.windows(4).any(|window| {
20961                matches!(
20962                    window,
20963                    [
20964                        Instruction::LoadFastLoadFast { .. },
20965                        Instruction::LoadFast { .. },
20966                        Instruction::StoreSubscr,
20967                        Instruction::LoadConst { .. },
20968                    ]
20969                )
20970            }),
20971            "expected CPython-style strong loads before protected STORE_SUBSCR tail, got ops={ops:?}"
20972        );
20973
20974        let code = compile_exec(
20975            "\
20976cache = {}
20977def g(lock, format):
20978    with lock:
20979        format_regex = cache.get(format)
20980        if not format_regex:
20981            try:
20982                format_regex = compile(format)
20983            except KeyError as err:
20984                bad_directive = err.args[0]
20985                del err
20986                raise ValueError(bad_directive) from None
20987            cache[format] = format_regex
20988    return format_regex.match('x')
20989",
20990        );
20991        let g = find_code(&code, "g").expect("missing function code");
20992        let ops: Vec<_> = g
20993            .instructions
20994            .iter()
20995            .map(|unit| unit.op)
20996            .filter(|op| !matches!(op, Instruction::Cache))
20997            .collect();
20998
20999        assert!(
21000            ops.windows(4).any(|window| {
21001                matches!(
21002                    window,
21003                    [
21004                        Instruction::LoadFast { .. },
21005                        Instruction::LoadGlobal { .. },
21006                        Instruction::LoadFast { .. },
21007                        Instruction::StoreSubscr,
21008                    ]
21009                )
21010            }),
21011            "expected CPython-style strong value/key loads around global STORE_SUBSCR tail, got ops={ops:?}"
21012        );
21013    }
21014
21015    #[test]
21016    fn try_except_inner_for_cleanup_allows_try_end_borrow() {
21017        let code = compile_exec(
21018            "\
21019def f(self, futures, already_completed, future, short_timeout):
21020    for timeout in (0, short_timeout):
21021        with self.subTest(timeout):
21022            completed_futures = set()
21023            try:
21024                for item in futures.as_completed(already_completed | {future}, timeout):
21025                    completed_futures.add(item)
21026            except futures.TimeoutError:
21027                pass
21028            self.assertEqual(completed_futures, already_completed)
21029",
21030        );
21031        let f = find_code(&code, "f").expect("missing function code");
21032        let borrow_pair_count = f
21033            .instructions
21034            .iter()
21035            .filter(|unit| matches!(unit.op, Instruction::LoadFastBorrowLoadFastBorrow { .. }))
21036            .count();
21037
21038        assert!(
21039            borrow_pair_count >= 2,
21040            "expected CPython-style borrowed pair loads before and after inner for cleanup, got ops={:?}",
21041            f.instructions
21042                .iter()
21043                .map(|unit| unit.op)
21044                .collect::<Vec<_>>()
21045        );
21046    }
21047
21048    #[test]
21049    fn augassign_two_part_slice_uses_slice_opcodes() {
21050        let code = compile_exec(
21051            "\
21052def aug(x, a, b, y):
21053    x[a:b] += y
21054",
21055        );
21056        let aug = find_code(&code, "aug").expect("missing aug code");
21057        let ops: Vec<_> = aug
21058            .instructions
21059            .iter()
21060            .map(|unit| unit.op)
21061            .filter(|op| !matches!(op, Instruction::Cache))
21062            .collect();
21063
21064        assert_eq!(
21065            ops.iter()
21066                .filter(|op| matches!(op, Instruction::BinarySlice))
21067                .count(),
21068            1,
21069            "expected one BINARY_SLICE in augassign slice path, got ops={ops:?}"
21070        );
21071        assert_eq!(
21072            ops.iter()
21073                .filter(|op| matches!(op, Instruction::StoreSlice))
21074                .count(),
21075            1,
21076            "expected one STORE_SLICE in augassign slice path, got ops={ops:?}"
21077        );
21078        assert!(
21079            !ops.iter().any(|op| {
21080                matches!(
21081                    op,
21082                    Instruction::BuildSlice { .. } | Instruction::StoreSubscr
21083                )
21084            }),
21085            "two-part augassign slice should avoid BUILD_SLICE/STORE_SUBSCR, got ops={ops:?}"
21086        );
21087        assert!(
21088            ops.windows(10).any(|window| {
21089                matches!(
21090                    window,
21091                    [
21092                        Instruction::Copy { .. },
21093                        Instruction::Copy { .. },
21094                        Instruction::Copy { .. },
21095                        Instruction::BinarySlice,
21096                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
21097                        Instruction::BinaryOp { .. },
21098                        Instruction::Swap { .. },
21099                        Instruction::Swap { .. },
21100                        Instruction::Swap { .. },
21101                        Instruction::StoreSlice,
21102                    ]
21103                )
21104            }),
21105            "expected CPython-style augassign slice window, got ops={ops:?}"
21106        );
21107    }
21108
21109    #[test]
21110    fn augassign_constant_slice_copy_uses_subscript_location_like_cpython() {
21111        let code = compile_exec(
21112            "\
21113def aug_const(x, y):
21114    x[1:2] += y
21115",
21116        );
21117        let aug_const = find_code(&code, "aug_const").expect("missing aug_const code");
21118
21119        // CPython 3.14 codegen_augassign() visits a constant slice, then emits
21120        // COPY/COPY/BINARY_OP NB_SUBSCR at LOC(target), not at LOC(slice).
21121        assert_eq!(
21122            aug_const.linetable.as_ref(),
21123            &[
21124                0x80, 0x00, 0xd8, 0x04, 0x05, 0x80, 0x63, 0x87, 0x46, 0x88, 0x61, 0x85, 0x4b, 0x85,
21125                0x46,
21126            ]
21127        );
21128    }
21129
21130    #[test]
21131    fn augassign_attribute_copy_uses_target_location_like_cpython() {
21132        let code = compile_exec(
21133            "\
21134def f(obj, value):
21135    obj.attr += value
21136",
21137        );
21138        let f = find_code(&code, "f").expect("missing f code");
21139        let copy_position = f
21140            .instructions
21141            .iter()
21142            .zip(&f.locations)
21143            .find_map(|(unit, (location, end_location))| {
21144                let Instruction::Copy { i } = unit.op else {
21145                    return None;
21146                };
21147                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
21148                (i.get(arg) == 1).then_some((
21149                    location.line.get(),
21150                    location.character_offset.get(),
21151                    end_location.line.get(),
21152                    end_location.character_offset.get(),
21153                ))
21154            })
21155            .expect("missing augmented attribute COPY");
21156
21157        assert_eq!(
21158            copy_position,
21159            (2, 5, 2, 13),
21160            "CPython codegen_augassign() emits COPY 1 at LOC(target) before updating to attr location"
21161        );
21162    }
21163
21164    #[test]
21165    fn loop_return_reorders_backedge_before_exit_cleanup() {
21166        let code = compile_exec(
21167            "\
21168def f(obj):
21169    for base in obj.__mro__:
21170        if base is not object:
21171            doc = base.__doc__
21172            if doc is not None:
21173                return doc
21174",
21175        );
21176        let f = find_code(&code, "f").expect("missing function code");
21177        let ops: Vec<_> = f
21178            .instructions
21179            .iter()
21180            .map(|unit| unit.op)
21181            .filter(|op| !matches!(op, Instruction::Cache))
21182            .collect();
21183
21184        let has_cpython_shape = ops.windows(7).any(|window| {
21185            matches!(
21186                window,
21187                [
21188                    Instruction::PopJumpIfNotNone { .. },
21189                    Instruction::NotTaken,
21190                    Instruction::JumpBackward { .. },
21191                    Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
21192                    Instruction::Swap { .. },
21193                    Instruction::PopTop,
21194                    Instruction::ReturnValue,
21195                ]
21196            )
21197        });
21198        assert!(
21199            has_cpython_shape,
21200            "expected loop return null-check to keep the backedge adjacent to the return cleanup, got ops={ops:?}"
21201        );
21202
21203        let end_for_idx = ops
21204            .iter()
21205            .position(|op| matches!(op, Instruction::EndFor))
21206            .expect("missing END_FOR");
21207        let return_before_end = ops[..end_for_idx]
21208            .iter()
21209            .rposition(|op| matches!(op, Instruction::ReturnValue))
21210            .expect("missing loop-body RETURN_VALUE");
21211        assert!(
21212            matches!(ops.get(return_before_end - 1), Some(Instruction::PopTop)),
21213            "expected POP_TOP before loop-body RETURN_VALUE, got {:?}; ops={ops:?}",
21214            ops.get(return_before_end.saturating_sub(1))
21215        );
21216    }
21217
21218    #[test]
21219    fn nested_try_finally_cleanup_reorder_does_not_invert_forward_jumps() {
21220        compile_exec(include_str!("../../../Lib/poplib.py"));
21221    }
21222
21223    #[test]
21224    fn conditional_body_is_preserved_before_final_return() {
21225        let code = compile_exec(
21226            "\
21227def f(x, y):
21228    if x == y:
21229        print('then', flush=True)
21230",
21231        );
21232        let f = find_code(&code, "f").expect("missing function code");
21233        let ops: Vec<_> = f
21234            .instructions
21235            .iter()
21236            .map(|unit| unit.op)
21237            .filter(|op| !matches!(op, Instruction::Cache))
21238            .collect();
21239
21240        let cond_idx = ops
21241            .iter()
21242            .position(|op| matches!(op, Instruction::PopJumpIfFalse { .. }))
21243            .expect("missing POP_JUMP_IF_FALSE");
21244        let first_return_idx = ops
21245            .iter()
21246            .position(|op| matches!(op, Instruction::ReturnValue))
21247            .expect("missing RETURN_VALUE");
21248
21249        assert!(
21250            ops[cond_idx..first_return_idx]
21251                .iter()
21252                .any(|op| matches!(op, Instruction::CallKw { .. })),
21253            "expected conditional body call before final return, got ops={ops:?}"
21254        );
21255    }
21256
21257    #[test]
21258    fn nested_conditional_body_is_preserved_before_final_return() {
21259        let code = compile_exec(
21260            "\
21261def outer():
21262    def side():
21263        print('side', flush=True)
21264    def cb():
21265        flag = True
21266        if flag:
21267            side()
21268    return cb
21269",
21270        );
21271        let cb = find_code(&code, "cb").expect("missing nested cb code");
21272        let ops: Vec<_> = cb
21273            .instructions
21274            .iter()
21275            .map(|unit| unit.op)
21276            .filter(|op| !matches!(op, Instruction::Cache))
21277            .collect();
21278
21279        let cond_idx = ops
21280            .iter()
21281            .position(|op| matches!(op, Instruction::PopJumpIfFalse { .. }))
21282            .expect("missing POP_JUMP_IF_FALSE");
21283        let first_return_idx = ops
21284            .iter()
21285            .position(|op| matches!(op, Instruction::ReturnValue))
21286            .expect("missing RETURN_VALUE");
21287
21288        assert!(
21289            ops[cond_idx..first_return_idx]
21290                .iter()
21291                .any(|op| matches!(op, Instruction::Call { .. })),
21292            "expected nested conditional body call before final return, got ops={ops:?}"
21293        );
21294    }
21295
21296    #[test]
21297    fn try_line_nop_is_preserved_before_setup_finally() {
21298        let code = compile_exec(
21299            "\
21300def f(msg):
21301    try:
21302        fw = _wm.formatwarning
21303    except AttributeError:
21304        pass
21305    else:
21306        if fw is not _formatwarning_orig:
21307            return fw(msg.message, msg.category, msg.filename, msg.lineno, msg.line)
21308    return _wm._formatwarnmsg_impl(msg)
21309",
21310        );
21311        let f = find_code(&code, "f").expect("missing function code");
21312        let ops: Vec<_> = f
21313            .instructions
21314            .iter()
21315            .map(|unit| unit.op)
21316            .filter(|op| !matches!(op, Instruction::Cache))
21317            .collect();
21318
21319        assert!(
21320            matches!(
21321                ops.as_slice(),
21322                [Instruction::Resume { .. }, Instruction::Nop, ..]
21323            ),
21324            "expected CPython try-line NOP before setup/fetch, got ops={ops:?}"
21325        );
21326    }
21327
21328    #[test]
21329    fn nested_try_line_nops_after_for_cleanup_are_preserved() {
21330        let code = compile_exec(
21331            "\
21332def f(xs, env):
21333    for x in xs:
21334        pass
21335    try:
21336        try:
21337            if env is not None:
21338                env_list = []
21339            else:
21340                env_list = None
21341        finally:
21342            pass
21343    finally:
21344        pass
21345",
21346        );
21347        let f = find_code(&code, "f").expect("missing function code");
21348        let ops: Vec<_> = f
21349            .instructions
21350            .iter()
21351            .map(|unit| unit.op)
21352            .filter(|op| !matches!(op, Instruction::Cache))
21353            .collect();
21354
21355        assert!(
21356            ops.windows(6).any(|window| {
21357                matches!(
21358                    window,
21359                    [
21360                        Instruction::EndFor,
21361                        Instruction::PopIter,
21362                        Instruction::Nop,
21363                        Instruction::Nop,
21364                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
21365                        Instruction::PopJumpIfNone { .. },
21366                    ]
21367                )
21368            }),
21369            "expected CPython-style outer and inner try-line NOPs after for cleanup, got ops={ops:?}"
21370        );
21371    }
21372
21373    #[test]
21374    fn try_finally_assert_keeps_finalbody_entry_nop() {
21375        let code = compile_exec(
21376            "\
21377def f(x):
21378    try:
21379        assert x
21380    finally:
21381        g()
21382",
21383        );
21384        let f = find_code(&code, "f").expect("missing function code");
21385        let ops: Vec<_> = f
21386            .instructions
21387            .iter()
21388            .map(|unit| unit.op)
21389            .filter(|op| !matches!(op, Instruction::Cache))
21390            .collect();
21391
21392        assert!(
21393            ops.windows(4).any(|window| {
21394                matches!(
21395                    window,
21396                    [
21397                        Instruction::RaiseVarargs { .. },
21398                        Instruction::Nop,
21399                        Instruction::LoadGlobal { .. },
21400                        Instruction::Call { .. },
21401                    ]
21402                )
21403            }),
21404            "assert in try/finally should preserve CPython finalbody-entry NOP after the raise edge, got ops={ops:?}"
21405        );
21406        assert!(
21407            ops.windows(6).any(|window| {
21408                matches!(
21409                    window,
21410                    [
21411                        Instruction::PopJumpIfTrue { .. },
21412                        Instruction::NotTaken,
21413                        Instruction::LoadCommonConstant { .. },
21414                        Instruction::RaiseVarargs { .. },
21415                        Instruction::Nop,
21416                        Instruction::LoadGlobal { .. },
21417                    ]
21418                )
21419            }),
21420            "assert true edge should land on a distinct finalbody-entry NOP, got ops={ops:?}"
21421        );
21422    }
21423
21424    #[test]
21425    fn try_finally_if_break_false_edge_keeps_finalbody_entry_nop() {
21426        let code = compile_exec(
21427            "\
21428def f(self, pid):
21429    while True:
21430        try:
21431            if pid == self.pid:
21432                self.h()
21433                break
21434        finally:
21435            self.r()
21436        self.g()
21437    return self.x
21438",
21439        );
21440        let f = find_code(&code, "f").expect("missing function code");
21441        let ops: Vec<_> = f
21442            .instructions
21443            .iter()
21444            .map(|unit| unit.op)
21445            .filter(|op| !matches!(op, Instruction::Cache))
21446            .collect();
21447
21448        assert!(
21449            ops.windows(6).any(|window| {
21450                matches!(
21451                    window,
21452                    [
21453                        Instruction::ReturnValue,
21454                        Instruction::Nop,
21455                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
21456                        Instruction::LoadAttr { .. },
21457                        Instruction::Call { .. },
21458                        Instruction::PopTop,
21459                    ]
21460                )
21461            }),
21462            "expected CPython-style if-line NOP before fallthrough finally body, got ops={ops:?}"
21463        );
21464    }
21465
21466    #[test]
21467    fn try_percent_format_preprocess_removes_redundant_try_nop() {
21468        let code = compile_exec(
21469            "\
21470def f(self, signal):
21471    if self.returncode and self.returncode < 0:
21472        try:
21473            return \"Command '%s' died with %r.\" % (
21474                self.cmd, signal.Signals(-self.returncode))
21475        except ValueError:
21476            return \"Command '%s' died with unknown signal %d.\" % (
21477                self.cmd, -self.returncode)
21478    return \"Command '%s' returned non-zero exit status %d.\" % (
21479        self.cmd, self.returncode)
21480",
21481        );
21482        let f = find_code(&code, "f").expect("missing function code");
21483        let ops: Vec<_> = f
21484            .instructions
21485            .iter()
21486            .map(|unit| unit.op)
21487            .filter(|op| !matches!(op, Instruction::Cache))
21488            .collect();
21489
21490        assert!(
21491            ops.windows(3).any(|window| {
21492                matches!(
21493                    window,
21494                    [
21495                        Instruction::NotTaken,
21496                        Instruction::LoadConst { .. },
21497                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
21498                    ]
21499                )
21500            }),
21501            "expected preprocessed percent-format body immediately after condition, got ops={ops:?}"
21502        );
21503        assert!(
21504            !ops.windows(4).any(|window| {
21505                matches!(
21506                    window,
21507                    [
21508                        Instruction::NotTaken,
21509                        Instruction::Nop,
21510                        Instruction::LoadConst { .. },
21511                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
21512                    ]
21513                )
21514            }),
21515            "percent-format preprocessing should let CFG remove the try-line NOP, got ops={ops:?}"
21516        );
21517    }
21518
21519    #[test]
21520    fn nested_try_except_in_finally_exception_path_shares_continuation() {
21521        let code = compile_exec(
21522            "\
21523def f(self, exc_type, KeyboardInterrupt, TimeoutExpired):
21524    try:
21525        if self.stdin:
21526            self.stdin.close()
21527    finally:
21528        if exc_type == KeyboardInterrupt:
21529            if self._sigint_wait_secs > 0:
21530                try:
21531                    self._wait(timeout=self._sigint_wait_secs)
21532                except TimeoutExpired:
21533                    pass
21534            self._sigint_wait_secs = 0
21535        else:
21536            self.wait()
21537",
21538        );
21539        let f = find_code(&code, "f").expect("missing function code");
21540        let ops: Vec<_> = f
21541            .instructions
21542            .iter()
21543            .map(|unit| unit.op)
21544            .filter(|op| !matches!(op, Instruction::Cache))
21545            .collect();
21546        let store_reraise_tails = ops
21547            .windows(2)
21548            .filter(|window| {
21549                matches!(
21550                    window,
21551                    [Instruction::StoreAttr { .. }, Instruction::Reraise { .. },]
21552                )
21553            })
21554            .count();
21555
21556        assert_eq!(
21557            store_reraise_tails, 1,
21558            "nested try/except inside an exceptional finally body should share the remaining finalbody tail before RERAISE, got ops={ops:?}"
21559        );
21560        assert!(
21561            ops.windows(5).any(|window| {
21562                matches!(
21563                    window,
21564                    [
21565                        Instruction::LoadSmallInt { .. },
21566                        Instruction::LoadFastBorrow { .. },
21567                        Instruction::StoreAttr { .. },
21568                        Instruction::LoadConst { .. },
21569                        Instruction::ReturnValue,
21570                    ]
21571                )
21572            }),
21573            "normal finally body should keep CPython-style borrowed load before STORE_ATTR, got ops={ops:?}"
21574        );
21575    }
21576
21577    #[test]
21578    fn try_else_return_keeps_nop_before_final_call_return() {
21579        let code = compile_exec(
21580            "\
21581def f(msg):
21582    try:
21583        fw = _wm.formatwarning
21584    except AttributeError:
21585        pass
21586    else:
21587        if fw is not _formatwarning_orig:
21588            return fw(msg.message, msg.category, msg.filename, msg.lineno, msg.line)
21589    return _wm._formatwarnmsg_impl(msg)
21590",
21591        );
21592        let f = find_code(&code, "f").expect("missing function code");
21593        let ops: Vec<_> = f
21594            .instructions
21595            .iter()
21596            .map(|unit| unit.op)
21597            .filter(|op| !matches!(op, Instruction::Cache))
21598            .collect();
21599
21600        assert!(
21601            ops.windows(7).any(|window| {
21602                matches!(
21603                    window,
21604                    [
21605                        Instruction::ReturnValue,
21606                        Instruction::Nop,
21607                        Instruction::LoadGlobal { .. },
21608                        Instruction::LoadAttr { .. },
21609                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
21610                        Instruction::Call { .. },
21611                        Instruction::ReturnValue,
21612                    ]
21613                )
21614            }),
21615            "expected CPython-style NOP between conditional return and final call return, got ops={ops:?}"
21616        );
21617    }
21618
21619    #[test]
21620    fn try_else_conditional_scope_exit_keeps_pop_block_nop() {
21621        let code = compile_exec(
21622            "\
21623def f(values, check):
21624    found = ''
21625    for value in values:
21626        try:
21627            if check(value):
21628                raise UnicodeError
21629        except UnicodeError:
21630            pass
21631        else:
21632            found = value
21633            break
21634    return found
21635",
21636        );
21637        let f = find_code(&code, "f").expect("missing function code");
21638        let ops: Vec<_> = f
21639            .instructions
21640            .iter()
21641            .map(|unit| unit.op)
21642            .filter(|op| !matches!(op, Instruction::Cache))
21643            .collect();
21644
21645        assert!(
21646            ops.windows(5).any(|window| {
21647                matches!(
21648                    window,
21649                    [
21650                        Instruction::RaiseVarargs { .. },
21651                        Instruction::Nop,
21652                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
21653                        Instruction::StoreFast { .. },
21654                        Instruction::PopTop,
21655                    ]
21656                )
21657            }),
21658            "try-else after conditional scope exit should keep CPython's POP_BLOCK NOP anchor, got ops={ops:?}"
21659        );
21660    }
21661
21662    #[test]
21663    fn try_else_loop_fallthrough_keeps_end_jump_nop_before_finally() {
21664        let code = compile_exec(
21665            "\
21666def f(locale, category, locales):
21667    try:
21668        orig_locale = locale.setlocale(category)
21669    except AttributeError:
21670        raise
21671    except Exception:
21672        locale = orig_locale = None
21673        if '' not in locales:
21674            raise SkipTest('no locales')
21675    else:
21676        for loc in locales:
21677            try:
21678                locale.setlocale(category, loc)
21679                break
21680            except locale.Error:
21681                pass
21682        else:
21683            if '' not in locales:
21684                raise SkipTest(locales)
21685    try:
21686        yield
21687    finally:
21688        if locale and orig_locale:
21689            locale.setlocale(category, orig_locale)
21690",
21691        );
21692        let f = find_code(&code, "f").expect("missing function code");
21693        let ops: Vec<_> = f
21694            .instructions
21695            .iter()
21696            .map(|unit| unit.op)
21697            .filter(|op| !matches!(op, Instruction::Cache))
21698            .collect();
21699
21700        assert!(
21701            ops.windows(5).any(|window| {
21702                matches!(
21703                    window,
21704                    [
21705                        Instruction::RaiseVarargs { .. },
21706                        Instruction::Nop,
21707                        Instruction::Nop,
21708                        Instruction::LoadConst { .. },
21709                        Instruction::YieldValue { .. },
21710                    ]
21711                )
21712            }),
21713            "try-else loop fallthrough should keep CPython's end-label NOP before following try/finally, got ops={ops:?}"
21714        );
21715    }
21716
21717    #[test]
21718    fn conditional_compare_uses_bool_compare_oparg() {
21719        let code = compile_exec(
21720            "\
21721def f(x, y):
21722    if x == y:
21723        return 1
21724    return 0
21725",
21726        );
21727        let f = find_code(&code, "f").expect("missing function code");
21728        let compare = f
21729            .instructions
21730            .iter()
21731            .find(|unit| matches!(unit.op, Instruction::CompareOp { .. }))
21732            .expect("missing COMPARE_OP");
21733
21734        assert_eq!(u8::from(compare.arg), 88);
21735    }
21736
21737    #[test]
21738    fn multiline_is_none_conditional_keeps_comparator_nop() {
21739        let code = compile_exec(
21740            "\
21741def f(x):
21742    if x.find(
21743            'a') is not None:
21744        return 1
21745    return 0
21746",
21747        );
21748        let f = find_code(&code, "f").expect("missing function code");
21749        let ops: Vec<_> = f
21750            .instructions
21751            .iter()
21752            .map(|unit| unit.op)
21753            .filter(|op| !matches!(op, Instruction::Cache))
21754            .collect();
21755
21756        assert!(
21757            ops.windows(3).any(|window| {
21758                matches!(
21759                    window,
21760                    [
21761                        Instruction::Call { .. },
21762                        Instruction::Nop,
21763                        Instruction::PopJumpIfNone { .. },
21764                    ]
21765                )
21766            }),
21767            "expected CPython-style comparator NOP before folded POP_JUMP_IF_NONE, got ops={ops:?}"
21768        );
21769    }
21770
21771    #[test]
21772    fn chained_conditional_compares_use_bool_compare_oparg() {
21773        let code = compile_exec(
21774            "\
21775def f(a, b, c):
21776    if a < b < c:
21777        return 1
21778    return 0
21779",
21780        );
21781        let f = find_code(&code, "f").expect("missing function code");
21782        let compare_args: Vec<_> = f
21783            .instructions
21784            .iter()
21785            .filter(|unit| matches!(unit.op, Instruction::CompareOp { .. }))
21786            .map(|unit| u8::from(unit.arg))
21787            .collect();
21788
21789        assert_eq!(compare_args, vec![18, 18]);
21790    }
21791
21792    #[test]
21793    fn shared_final_return_is_cloned_for_jump_target() {
21794        let code = compile_exec(
21795            "\
21796def f(node):
21797    if not isinstance(
21798        node, (AsyncFunctionDef, FunctionDef, ClassDef, Module)
21799    ) or len(node.body) < 1:
21800        return None
21801    node = node.body[0]
21802    if not isinstance(node, Expr):
21803        return None
21804    node = node.value
21805    if isinstance(node, Constant) and isinstance(node.value, str):
21806        return node
21807",
21808        );
21809        let f = find_code(&code, "f").expect("missing function code");
21810        let ops: Vec<_> = f
21811            .instructions
21812            .iter()
21813            .map(|unit| unit.op)
21814            .filter(|op| !matches!(op, Instruction::Cache))
21815            .collect();
21816
21817        let return_count = ops
21818            .iter()
21819            .filter(|op| matches!(op, Instruction::ReturnValue))
21820            .count();
21821        assert_eq!(
21822            return_count, 5,
21823            "expected cloned return sites for each shared return edge, got ops={ops:?}"
21824        );
21825    }
21826
21827    #[test]
21828    fn for_break_uses_poptop_cleanup() {
21829        let code = compile_exec(
21830            "\
21831def f(parts):
21832    for value in parts:
21833        if value:
21834            break
21835",
21836        );
21837        let f = find_code(&code, "f").expect("missing function code");
21838        let ops: Vec<_> = f
21839            .instructions
21840            .iter()
21841            .map(|unit| unit.op)
21842            .filter(|op| !matches!(op, Instruction::Cache))
21843            .collect();
21844
21845        let pop_iter_count = ops
21846            .iter()
21847            .filter(|op| matches!(op, Instruction::PopIter))
21848            .count();
21849        assert_eq!(
21850            pop_iter_count, 1,
21851            "expected only the loop-exhaustion POP_ITER, got ops={ops:?}"
21852        );
21853
21854        let break_cleanup_idx = ops
21855            .windows(3)
21856            .position(|window| {
21857                matches!(
21858                    window,
21859                    [
21860                        Instruction::PopTop,
21861                        Instruction::LoadConst { .. },
21862                        Instruction::ReturnValue
21863                    ]
21864                )
21865            })
21866            .expect("missing POP_TOP/LOAD_CONST/RETURN_VALUE break cleanup");
21867        let end_for_idx = ops
21868            .iter()
21869            .position(|op| matches!(op, Instruction::EndFor))
21870            .expect("missing END_FOR");
21871        assert!(
21872            break_cleanup_idx < end_for_idx,
21873            "expected break cleanup before END_FOR, got ops={ops:?}"
21874        );
21875    }
21876
21877    #[test]
21878    fn for_exit_before_elif_does_not_leave_line_anchor_nop() {
21879        let code = compile_exec(
21880            "\
21881from sys import maxsize
21882if maxsize == 2147483647:
21883    for s in ('2147483648', '0o40000000000', '0x100000000', '0b10000000000000000000000000000000'):
21884        try:
21885            x = eval(s)
21886        except OverflowError:
21887            fail('OverflowError on huge integer literal %r' % s)
21888elif maxsize == 9223372036854775807:
21889    pass
21890",
21891        );
21892        let ops: Vec<_> = code
21893            .instructions
21894            .iter()
21895            .map(|unit| unit.op)
21896            .filter(|op| !matches!(op, Instruction::Cache))
21897            .collect();
21898
21899        assert!(
21900            ops.windows(4).any(|window| {
21901                matches!(
21902                    window,
21903                    [
21904                        Instruction::EndFor,
21905                        Instruction::PopIter,
21906                        Instruction::LoadConst { .. },
21907                        Instruction::ReturnValue,
21908                    ]
21909                )
21910            }),
21911            "expected for-exit epilogue without extra NOP, got ops={ops:?}"
21912        );
21913        assert!(
21914            !ops.windows(4).any(|window| {
21915                matches!(
21916                    window,
21917                    [
21918                        Instruction::EndFor,
21919                        Instruction::PopIter,
21920                        Instruction::Nop,
21921                        Instruction::LoadConst { .. },
21922                    ]
21923                )
21924            }),
21925            "unexpected line-anchor NOP before for-exit epilogue, got ops={ops:?}"
21926        );
21927    }
21928
21929    #[test]
21930    fn for_tuple_target_does_not_leave_loop_header_nop() {
21931        let code = compile_exec(
21932            "\
21933def f(pairs):
21934    for left, right in pairs:
21935        pass
21936",
21937        );
21938        let f = find_code(&code, "f").expect("missing function code");
21939        let ops: Vec<_> = f
21940            .instructions
21941            .iter()
21942            .map(|unit| unit.op)
21943            .filter(|op| !matches!(op, Instruction::Cache))
21944            .collect();
21945
21946        assert!(
21947            ops.windows(2).any(|window| {
21948                matches!(
21949                    window,
21950                    [
21951                        Instruction::ForIter { .. },
21952                        Instruction::UnpackSequence { .. }
21953                    ]
21954                )
21955            }),
21956            "expected FOR_ITER to flow directly into UNPACK_SEQUENCE, got ops={ops:?}"
21957        );
21958        assert!(
21959            !ops.windows(3).any(|window| {
21960                matches!(
21961                    window,
21962                    [
21963                        Instruction::ForIter { .. },
21964                        Instruction::Nop,
21965                        Instruction::UnpackSequence { .. },
21966                    ]
21967                )
21968            }),
21969            "unexpected loop-header NOP before tuple unpack, got ops={ops:?}"
21970        );
21971    }
21972
21973    #[test]
21974    fn tstring_build_template_matches_cpython_stack_order() {
21975        let code = compile_exec("t = t\"{0}\"");
21976        let units: Vec<_> = code
21977            .instructions
21978            .iter()
21979            .copied()
21980            .filter(|unit| !matches!(unit.op, Instruction::Cache))
21981            .collect();
21982
21983        assert!(
21984            units.windows(6).any(|window| {
21985                matches!(
21986                    window,
21987                    [
21988                        a,
21989                        b,
21990                        c,
21991                        d,
21992                        e,
21993                        f,
21994                    ]
21995                    if matches!(a.op, Instruction::LoadConst { .. })
21996                        && matches!(b.op, Instruction::LoadSmallInt { .. })
21997                        && matches!(c.op, Instruction::LoadConst { .. })
21998                        && matches!(d.op, Instruction::BuildInterpolation { .. })
21999                        && u8::from(d.arg) == 2
22000                        && matches!(e.op, Instruction::BuildTuple { .. })
22001                        && u8::from(e.arg) == 1
22002                        && matches!(f.op, Instruction::BuildTemplate)
22003                )
22004            }),
22005            "expected CPython-style t-string lowering, got units={units:?}"
22006        );
22007        assert!(
22008            !units
22009                .iter()
22010                .any(|unit| matches!(unit.op, Instruction::Swap { .. })),
22011            "unexpected SWAP in t-string lowering, got units={units:?}"
22012        );
22013    }
22014
22015    #[test]
22016    fn tstring_debug_specifier_uses_debug_literal_and_repr_default() {
22017        let code = compile_exec(
22018            "\
22019value = 42
22020t = t\"Value: {value=}\"
22021",
22022        );
22023
22024        let string_consts = code
22025            .instructions
22026            .iter()
22027            .filter_map(|unit| match unit.op {
22028                Instruction::LoadConst { consti } => {
22029                    Some(&code.constants[consti.get(OpArg::new(u32::from(u8::from(unit.arg))))])
22030                }
22031                _ => None,
22032            })
22033            .collect::<Vec<_>>();
22034
22035        assert!(
22036            string_consts.iter().any(|constant| matches!(
22037                constant,
22038                ConstantData::Tuple { elements }
22039                    if matches!(
22040                        &elements[..],
22041                        [
22042                            ConstantData::Str { value: first },
22043                            ConstantData::Str { value: second },
22044                        ] if first.to_string() == "Value: value=" && second.is_empty()
22045                    )
22046            )),
22047            "expected debug literal prefix in t-string constants, got {string_consts:?}"
22048        );
22049        assert!(
22050            code.instructions.iter().any(|unit| matches!(
22051                unit.op,
22052                Instruction::BuildInterpolation { .. }
22053            ) && u8::from(unit.arg) == 10),
22054            "expected default repr conversion for debug t-string"
22055        );
22056    }
22057
22058    #[test]
22059    fn tstring_ops_restore_template_and_interpolation_locations_like_cpython() {
22060        let code = compile_exec(
22061            "\
22062def f(x):
22063    return t\"{x}\"
22064",
22065        );
22066        let f = find_code(&code, "f").expect("missing f code");
22067        let mut build_interpolation = None;
22068        let mut build_tuple = None;
22069        let mut build_template = None;
22070        for (unit, (location, end_location)) in f.instructions.iter().zip(&f.locations) {
22071            let range = (
22072                location.line.get(),
22073                location.character_offset.get(),
22074                end_location.line.get(),
22075                end_location.character_offset.get(),
22076            );
22077            match unit.op {
22078                Instruction::BuildInterpolation { .. } => build_interpolation = Some(range),
22079                Instruction::BuildTuple { .. } => build_tuple = Some(range),
22080                Instruction::BuildTemplate => build_template = Some(range),
22081                _ => {}
22082            }
22083        }
22084
22085        assert_eq!(
22086            build_interpolation,
22087            Some((2, 14, 2, 17)),
22088            "CPython codegen_interpolation() restores LOC(Interpolation) after visiting the value; this direct codegen path uses the parser's Interpolation range"
22089        );
22090        assert_eq!(
22091            build_tuple,
22092            Some((2, 12, 2, 18)),
22093            "CPython codegen_template_str() emits the interpolations tuple at LOC(TemplateStr); this direct codegen path uses the parser's TemplateStr range"
22094        );
22095        assert_eq!(
22096            build_template,
22097            Some((2, 12, 2, 18)),
22098            "CPython codegen_template_str() emits BUILD_TEMPLATE at LOC(TemplateStr); this direct codegen path uses the parser's TemplateStr range"
22099        );
22100    }
22101
22102    #[test]
22103    fn regular_call_push_null_uses_callee_location_like_cpython() {
22104        let code = compile_exec(
22105            "\
22106def f(g, x):
22107    return (
22108        g
22109    )(x)
22110",
22111        );
22112        let f = find_code(&code, "f").expect("missing f code");
22113        let push_null = f
22114            .instructions
22115            .iter()
22116            .zip(&f.locations)
22117            .find_map(|(unit, (location, end_location))| {
22118                matches!(unit.op, Instruction::PushNull).then_some((
22119                    location.line.get(),
22120                    location.character_offset.get(),
22121                    end_location.line.get(),
22122                    end_location.character_offset.get(),
22123                ))
22124            })
22125            .expect("missing PUSH_NULL");
22126
22127        assert_eq!(
22128            push_null,
22129            (3, 9, 3, 10),
22130            "CPython codegen_call() resets loc to LOC(func) before emitting PUSH_NULL; this direct codegen path uses the parser's callee range"
22131        );
22132    }
22133
22134    #[test]
22135    fn tstring_literal_preserves_surrogate_wtf8() {
22136        let code = compile_exec("t = t\"\\ud800\"");
22137
22138        assert!(code.constants.iter().any(|constant| matches!(
22139            constant,
22140            ConstantData::Str { value } if value.clone().into_bytes() == [0xED, 0xA0, 0x80]
22141        )));
22142    }
22143
22144    #[test]
22145    fn break_in_finally_after_return_keeps_load_fast_check_for_loop_locals() {
22146        let code = compile_exec(
22147            "\
22148def g2(x):
22149    for count in [0, 1]:
22150        for count2 in [10, 20]:
22151            try:
22152                return count + count2
22153            finally:
22154                if x:
22155                    break
22156    return 'end', count, count2
22157",
22158        );
22159        let g2 = find_code(&code, "g2").expect("missing g2 code");
22160        let ops: Vec<_> = g2
22161            .instructions
22162            .iter()
22163            .map(|unit| unit.op)
22164            .filter(|op| !matches!(op, Instruction::Cache))
22165            .collect();
22166
22167        assert!(
22168            ops.windows(4).any(|window| {
22169                matches!(
22170                    window,
22171                    [
22172                        Instruction::LoadConst { .. },
22173                        Instruction::LoadFastCheck { .. },
22174                        Instruction::LoadFastCheck { .. },
22175                        Instruction::BuildTuple { .. },
22176                    ]
22177                )
22178            }),
22179            "expected LOAD_FAST_CHECK pair for after-return loop locals, got ops={ops:?}"
22180        );
22181    }
22182
22183    #[test]
22184    fn high_index_parameter_stays_initialized_in_fast_scan() {
22185        let params = (0..65)
22186            .map(|idx| format!("p{idx}"))
22187            .collect::<Vec<_>>()
22188            .join(", ");
22189        let code = compile_exec(&format!(
22190            "\
22191def f({params}):
22192    return p64
22193"
22194        ));
22195        let f = find_code(&code, "f").expect("missing f code");
22196
22197        assert!(
22198            f.instructions.iter().any(|unit| matches!(
22199                unit.op,
22200                Instruction::LoadFastCheck { var_num }
22201                    if f.varnames
22202                        [usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))))]
22203                        == "p64"
22204            )),
22205            "CPython 3.14 fast_scan_many_locals() checks high-index parameters per block; expected p64 to use LOAD_FAST_CHECK, got ops={:?}",
22206            f.instructions
22207                .iter()
22208                .map(|unit| unit.op)
22209                .collect::<Vec<_>>()
22210        );
22211    }
22212
22213    #[test]
22214    fn deleted_high_index_parameter_uses_load_fast_check() {
22215        let params = (0..65)
22216            .map(|idx| format!("p{idx}"))
22217            .collect::<Vec<_>>()
22218            .join(", ");
22219        let code = compile_exec(&format!(
22220            "\
22221def f({params}):
22222    del p64
22223    return p64
22224"
22225        ));
22226        let f = find_code(&code, "f").expect("missing f code");
22227
22228        assert!(
22229            f.instructions.iter().any(|unit| matches!(
22230                unit.op,
22231                Instruction::LoadFastCheck { var_num }
22232                    if f.varnames
22233                        [usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))))]
22234                        == "p64"
22235            )),
22236            "expected deleted high-index parameter p64 to use LOAD_FAST_CHECK, got ops={:?}",
22237            f.instructions
22238                .iter()
22239                .map(|unit| unit.op)
22240                .collect::<Vec<_>>()
22241        );
22242    }
22243
22244    #[test]
22245    fn assert_without_message_raises_class_directly() {
22246        let code = compile_exec(
22247            "\
22248def f(x):
22249    assert x
22250",
22251        );
22252        let f = find_code(&code, "f").expect("missing function code");
22253        let call_count = f
22254            .instructions
22255            .iter()
22256            .filter(|unit| matches!(unit.op, Instruction::Call { .. }))
22257            .count();
22258        let push_null_count = f
22259            .instructions
22260            .iter()
22261            .filter(|unit| matches!(unit.op, Instruction::PushNull))
22262            .count();
22263
22264        assert_eq!(call_count, 0);
22265        assert_eq!(push_null_count, 0);
22266    }
22267
22268    #[test]
22269    fn assert_with_message_uses_common_constant_direct_call() {
22270        let code = compile_exec(
22271            "\
22272def f(x, y):
22273    assert x, y
22274",
22275        );
22276        let f = find_code(&code, "f").expect("missing f code");
22277        let load_assertion = f
22278            .instructions
22279            .iter()
22280            .position(|unit| {
22281                matches!(unit.op, Instruction::LoadCommonConstant { .. })
22282                    && matches!(
22283                        unit.op,
22284                        Instruction::LoadCommonConstant { idx }
22285                            if idx.get(OpArg::new(u32::from(u8::from(unit.arg))))
22286                                == bytecode::CommonConstant::AssertionError
22287                    )
22288            })
22289            .expect("missing LOAD_COMMON_CONSTANT AssertionError");
22290
22291        assert!(
22292            !matches!(
22293                f.instructions.get(load_assertion + 1).map(|unit| unit.op),
22294                Some(Instruction::PushNull)
22295            ),
22296            "assert message path should not use PUSH_NULL, got ops={:?}",
22297            f.instructions
22298                .iter()
22299                .map(|unit| unit.op)
22300                .collect::<Vec<_>>()
22301        );
22302        assert!(
22303            matches!(
22304                f.instructions.get(load_assertion + 2).map(|unit| unit.op),
22305                Some(Instruction::Call { .. })
22306            ),
22307            "expected direct CALL after loading assert message, got ops={:?}",
22308            f.instructions
22309                .iter()
22310                .map(|unit| unit.op)
22311                .collect::<Vec<_>>()
22312        );
22313
22314        let call_arg = f.instructions[load_assertion + 2].arg;
22315        assert_eq!(u8::from(call_arg), 0);
22316    }
22317
22318    #[test]
22319    fn conditional_assert_message_target_uses_strong_load_fast() {
22320        let code = compile_exec(
22321            "\
22322def f(fname):
22323    if fname == 'a':
22324        return 1
22325    assert False, 'Unknown attrname %s' % fname
22326",
22327        );
22328        let f = find_code(&code, "f").expect("missing f code");
22329        let ops: Vec<_> = f
22330            .instructions
22331            .iter()
22332            .map(|unit| unit.op)
22333            .filter(|op| !matches!(op, Instruction::Cache))
22334            .collect();
22335        let assertion_error = ops
22336            .iter()
22337            .position(|op| matches!(op, Instruction::LoadCommonConstant { .. }))
22338            .expect("missing LOAD_COMMON_CONSTANT AssertionError");
22339        let window = &ops[assertion_error..(assertion_error + 5).min(ops.len())];
22340        assert!(
22341            matches!(
22342                window,
22343                [
22344                    Instruction::LoadCommonConstant { .. },
22345                    Instruction::LoadConst { .. },
22346                    Instruction::LoadFast { .. },
22347                    Instruction::BinaryOp { .. },
22348                    Instruction::Call { .. },
22349                    ..
22350                ]
22351            ),
22352            "expected CPython-style strong LOAD_FAST in targeted assert message block, got {window:?}"
22353        );
22354    }
22355
22356    #[test]
22357    fn chained_compare_assert_message_keeps_borrowed_load_fast() {
22358        let code = compile_exec(
22359            "\
22360def f(month):
22361    assert 1 <= month <= 12, f'month must be in 1..12, not {month}'
22362",
22363        );
22364        let f = find_code(&code, "f").expect("missing f code");
22365        let ops: Vec<_> = f
22366            .instructions
22367            .iter()
22368            .map(|unit| unit.op)
22369            .filter(|op| !matches!(op, Instruction::Cache))
22370            .collect();
22371        let assertion_error = ops
22372            .iter()
22373            .position(|op| matches!(op, Instruction::LoadCommonConstant { .. }))
22374            .expect("missing LOAD_COMMON_CONSTANT AssertionError");
22375        let raise = ops[assertion_error..]
22376            .iter()
22377            .position(|op| matches!(op, Instruction::RaiseVarargs { .. }))
22378            .map(|idx| assertion_error + idx)
22379            .expect("missing assert raise");
22380        let message_path = &ops[assertion_error..raise];
22381
22382        assert!(
22383            message_path
22384                .iter()
22385                .any(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
22386            "CPython keeps assert message loads borrowed for same-line chained compare failure blocks, got {message_path:?}"
22387        );
22388        assert!(
22389            !message_path
22390                .iter()
22391                .any(|op| matches!(op, Instruction::LoadFast { .. })),
22392            "same-line chained compare assert message should not be deoptimized to LOAD_FAST, got {message_path:?}"
22393        );
22394    }
22395
22396    #[test]
22397    fn assert_message_after_condition_in_same_block_keeps_borrowed_loads() {
22398        let code = compile_exec(
22399            "\
22400def f(expected_ns, namespace):
22401    try:
22402        assert expected_ns == namespace, ('expected %s, got %s' % (expected_ns, namespace))
22403    except AssertionError as e:
22404        raise RuntimeError(e)
22405    setattr(namespace, 'spam', expected_ns)
22406",
22407        );
22408        let f = find_code(&code, "f").expect("missing f code");
22409        let ops: Vec<_> = f
22410            .instructions
22411            .iter()
22412            .filter(|unit| !matches!(unit.op, Instruction::Cache))
22413            .collect();
22414        let assertion_error = ops
22415            .iter()
22416            .position(|unit| matches!(unit.op, Instruction::LoadCommonConstant { .. }))
22417            .expect("missing LOAD_COMMON_CONSTANT AssertionError");
22418        let raise = ops[assertion_error..]
22419            .iter()
22420            .position(|unit| matches!(unit.op, Instruction::RaiseVarargs { .. }))
22421            .map(|idx| assertion_error + idx)
22422            .expect("missing assert raise");
22423        let message_path = &ops[assertion_error..raise];
22424
22425        let load_fast_name = |unit: &&bytecode::CodeUnit| match unit.op {
22426            Instruction::LoadFast { var_num } => {
22427                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
22428                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
22429            }
22430            _ => None,
22431        };
22432        let borrow_name = |unit: &&bytecode::CodeUnit| match unit.op {
22433            Instruction::LoadFastBorrow { var_num } => {
22434                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
22435                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
22436            }
22437            _ => None,
22438        };
22439
22440        assert!(
22441            message_path
22442                .iter()
22443                .filter_map(load_fast_name)
22444                .all(|name| name != "expected_ns" && name != "namespace"),
22445            "assert message after same-block condition should keep borrowed loads, got {message_path:?}"
22446        );
22447        for name in ["expected_ns", "namespace"] {
22448            assert!(
22449                message_path
22450                    .iter()
22451                    .filter_map(borrow_name)
22452                    .any(|var| var == name),
22453                "expected borrowed {name} load in assert message path, got {message_path:?}"
22454            );
22455        }
22456
22457        let setattr = ops
22458            .iter()
22459            .position(|unit| matches!(unit.op, Instruction::LoadGlobal { .. }))
22460            .expect("missing final setattr load");
22461        let tail = &ops[setattr..];
22462        for name in ["expected_ns", "namespace"] {
22463            assert!(
22464                tail.iter()
22465                    .filter_map(load_fast_name)
22466                    .any(|var| var == name),
22467                "expected strong {name} load in post-try tail, got {tail:?}"
22468            );
22469        }
22470    }
22471
22472    #[test]
22473    fn bare_function_annotations_check_attribute_and_subscript_expressions() {
22474        let code = compile_exec(
22475            "\
22476def f(one: int):
22477    int.new_attr: int
22478    [list][0].new_attr: [int, str]
22479    my_lst = [1]
22480    my_lst[one]: int
22481    return my_lst
22482",
22483        );
22484        let f = find_code(&code, "f").expect("missing f code");
22485        let loads_global = |name: &str| {
22486            f.instructions.iter().any(|unit| match unit.op {
22487                Instruction::LoadGlobal { namei } => {
22488                    let namei = namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) >> 1;
22489                    f.names[usize::try_from(namei).unwrap()].as_str() == name
22490                }
22491                _ => false,
22492            })
22493        };
22494        assert!(
22495            loads_global("int"),
22496            "bare attribute annotations should evaluate int, got ops={:?}",
22497            f.instructions
22498        );
22499        assert!(
22500            loads_global("list"),
22501            "bare subscript annotations should evaluate list, got ops={:?}",
22502            f.instructions
22503        );
22504    }
22505
22506    #[test]
22507    fn function_local_annassign_annotation_does_not_capture_outer_local() {
22508        let code = compile_exec(
22509            "\
22510def f():
22511    from collections import namedtuple
22512
22513    class MyHandler:
22514        def __init__(self, resource):
22515            self.resource: namedtuple = resource
22516
22517    return namedtuple
22518",
22519        );
22520        let f = find_code(&code, "f").expect("missing f code");
22521        let class_code = find_code(&code, "MyHandler").expect("missing MyHandler code");
22522        let init = find_code(&code, "__init__").expect("missing __init__ code");
22523
22524        assert!(
22525            !f.cellvars.iter().any(|name| name == "namedtuple"),
22526            "function-local AnnAssign annotation must not make namedtuple a cell, got cellvars={:?}",
22527            f.cellvars
22528        );
22529        assert!(
22530            !class_code.freevars.iter().any(|name| name == "namedtuple"),
22531            "class body must not close over function-local annotation-only name, got freevars={:?}",
22532            class_code.freevars
22533        );
22534        assert!(
22535            !init.freevars.iter().any(|name| name == "namedtuple"),
22536            "method body must not close over function-local annotation-only name, got freevars={:?}",
22537            init.freevars
22538        );
22539    }
22540
22541    #[test]
22542    fn finally_exception_path_inlines_except_pass_reraise_tail() {
22543        let source = "\
22544def f(self, file, backupfilename):
22545    try:
22546        if file:
22547            file.close()
22548    finally:
22549        backupfilename = self._backupfilename
22550        self._backupfilename = None
22551        if backupfilename and not self._backup:
22552            try:
22553                os.unlink(backupfilename)
22554            except OSError:
22555                pass
22556        self._isstdin = False
22557";
22558        let code = compile_exec(source);
22559        let f = find_code(&code, "f").expect("missing f code");
22560        assert!(
22561            f.instructions.windows(9).any(|window| {
22562                matches!(
22563                    [
22564                        window[0].op,
22565                        window[1].op,
22566                        window[2].op,
22567                        window[3].op,
22568                        window[8].op,
22569                    ],
22570                    [
22571                        Instruction::PopExcept,
22572                        Instruction::LoadConst { .. },
22573                        Instruction::LoadFast { .. },
22574                        Instruction::StoreAttr { .. },
22575                        Instruction::Reraise { .. },
22576                    ]
22577                ) && match window[8].op {
22578                    Instruction::Reraise { depth } => {
22579                        depth.get(OpArg::new(u32::from(u8::from(window[8].arg)))) == 0
22580                    }
22581                    _ => false,
22582                }
22583            }),
22584            "except-pass normal exit in a finally exception path should inline the CPython reraise tail, got instructions={:?}",
22585            f.instructions
22586        );
22587    }
22588
22589    #[test]
22590    fn nested_finally_exception_path_prunes_dead_normal_cleanup() {
22591        let code = compile_exec(
22592            "\
22593def f():
22594    try:
22595        raise ValueError
22596    finally:
22597        try:
22598            raise KeyError
22599        finally:
22600            1/0
22601",
22602        );
22603        let f = find_code(&code, "f").expect("missing f code");
22604        let ops: Vec<_> = f
22605            .instructions
22606            .iter()
22607            .filter(|unit| !matches!(unit.op, Instruction::Cache))
22608            .collect();
22609        let load_global_name = |unit: &&bytecode::CodeUnit| match unit.op {
22610            Instruction::LoadGlobal { namei } => {
22611                let name = namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) >> 1;
22612                Some(f.names[usize::try_from(name).unwrap()].as_str())
22613            }
22614            _ => None,
22615        };
22616        let value_error_pos = ops
22617            .iter()
22618            .position(|unit| load_global_name(unit) == Some("ValueError"))
22619            .expect("missing ValueError load");
22620        let key_error_pos = ops
22621            .iter()
22622            .position(|unit| load_global_name(unit) == Some("KeyError"))
22623            .expect("missing KeyError load");
22624        let first_push_exc_after_value_error = ops[value_error_pos..]
22625            .iter()
22626            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
22627            .map(|pos| pos + value_error_pos)
22628            .expect("missing finally exception entry");
22629        let first_copy_after_value_error = ops[value_error_pos..]
22630            .iter()
22631            .position(|unit| matches!(unit.op, Instruction::Copy { .. }))
22632            .map(|pos| pos + value_error_pos)
22633            .expect("missing finally cleanup copy");
22634
22635        assert!(
22636            first_push_exc_after_value_error < key_error_pos,
22637            "CPython codegen_try_finally() enters the outer finally exception path before compiling the nested try body; got ops={ops:?}"
22638        );
22639        assert!(
22640            first_push_exc_after_value_error < first_copy_after_value_error,
22641            "CPython remove_unreachable() must not keep cleanup targets from dead normal-finally paths before the live exception path; got ops={ops:?}"
22642        );
22643    }
22644
22645    #[test]
22646    fn non_simple_bare_name_annotation_does_not_create_local_binding() {
22647        let code = compile_exec(
22648            "\
22649def f2bad():
22650    (no_such_global): int
22651    print(no_such_global)
22652",
22653        );
22654        let f = find_code(&code, "f2bad").expect("missing f2bad code");
22655        assert!(
22656            f.instructions
22657                .iter()
22658                .any(|unit| matches!(unit.op, Instruction::LoadGlobal { .. })),
22659            "expected LOAD_GLOBAL for non-simple bare annotated name, got ops={:?}",
22660            f.instructions
22661                .iter()
22662                .map(|unit| unit.op)
22663                .collect::<Vec<_>>()
22664        );
22665        assert!(
22666            !f.instructions
22667                .iter()
22668                .any(|unit| matches!(unit.op, Instruction::LoadFastCheck { .. })),
22669            "non-simple bare annotated name should not become a local binding, got ops={:?}",
22670            f.instructions
22671                .iter()
22672                .map(|unit| unit.op)
22673                .collect::<Vec<_>>()
22674        );
22675    }
22676
22677    #[test]
22678    fn negative_constant_binop_folds_after_unary_folding() {
22679        let code = compile_exec(
22680            "\
22681def f():
22682    return -2147483647 - 1
22683",
22684        );
22685        let f = find_code(&code, "f").expect("missing function code");
22686
22687        assert!(
22688            !f.instructions
22689                .iter()
22690                .any(|unit| matches!(unit.op, Instruction::BinaryOp { .. })),
22691            "negative constant expression should fold to a single constant, got ops={:?}",
22692            f.instructions
22693                .iter()
22694                .map(|unit| unit.op)
22695                .collect::<Vec<_>>()
22696        );
22697        assert!(
22698            f.instructions
22699                .iter()
22700                .any(|unit| matches!(unit.op, Instruction::LoadConst { .. })),
22701            "expected folded constant load, got ops={:?}",
22702            f.instructions
22703                .iter()
22704                .map(|unit| unit.op)
22705                .collect::<Vec<_>>()
22706        );
22707    }
22708
22709    #[test]
22710    fn genexpr_filter_header_uses_store_fast_load_fast() {
22711        let code = compile_exec(
22712            "\
22713def f(it):
22714    return (x for x in it if x)
22715",
22716        );
22717        let genexpr = find_code(&code, "<genexpr>").expect("missing <genexpr> code");
22718        let store_fast_load_fast_idx = genexpr
22719            .instructions
22720            .iter()
22721            .position(|unit| matches!(unit.op, Instruction::StoreFastLoadFast { .. }))
22722            .expect("missing STORE_FAST_LOAD_FAST in genexpr header");
22723
22724        assert!(
22725            matches!(
22726                genexpr
22727                    .instructions
22728                    .get(store_fast_load_fast_idx + 1)
22729                    .map(|unit| unit.op),
22730                Some(Instruction::ToBool)
22731            ),
22732            "expected TO_BOOL immediately after STORE_FAST_LOAD_FAST, got ops={:?}",
22733            genexpr
22734                .instructions
22735                .iter()
22736                .map(|unit| unit.op)
22737                .collect::<Vec<_>>()
22738        );
22739    }
22740
22741    #[test]
22742    fn generator_filter_keeps_cpython_style_forward_yield_body_entry() {
22743        let code = compile_exec(
22744            "\
22745def gen(it):
22746    for f in it:
22747        if f.name:
22748            yield f.name
22749",
22750        );
22751        let gen_code = find_code(&code, "gen").expect("missing gen code");
22752        let ops: Vec<_> = gen_code
22753            .instructions
22754            .iter()
22755            .map(|unit| unit.op)
22756            .filter(|op| !matches!(op, Instruction::Cache))
22757            .collect();
22758
22759        assert!(
22760            ops.windows(7).any(|window| {
22761                matches!(
22762                    window,
22763                    [
22764                        Instruction::ToBool,
22765                        Instruction::PopJumpIfTrue { .. },
22766                        Instruction::NotTaken,
22767                        Instruction::JumpBackward { .. }
22768                            | Instruction::JumpBackwardNoInterrupt { .. },
22769                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
22770                        Instruction::LoadAttr { .. },
22771                        Instruction::YieldValue { .. },
22772                    ]
22773                )
22774            }),
22775            "expected CPython-style generator filter to jump on true into the yield body and fall through into the loop backedge on false, got ops={ops:?}"
22776        );
22777    }
22778
22779    #[test]
22780    fn generator_negated_filter_keeps_cpython_style_false_edge_into_yield_body() {
22781        let code = compile_exec(
22782            "\
22783def gen(fields):
22784    for f in fields:
22785        if f.init and not f.kw_only:
22786            yield f
22787",
22788        );
22789        let gen_code = find_code(&code, "gen").expect("missing gen code");
22790        let ops: Vec<_> = gen_code
22791            .instructions
22792            .iter()
22793            .map(|unit| unit.op)
22794            .filter(|op| !matches!(op, Instruction::Cache))
22795            .collect();
22796
22797        assert!(
22798            ops.windows(7).any(|window| {
22799                matches!(
22800                    window,
22801                    [
22802                        Instruction::ToBool,
22803                        Instruction::PopJumpIfFalse { .. },
22804                        Instruction::NotTaken,
22805                        Instruction::JumpBackward { .. }
22806                            | Instruction::JumpBackwardNoInterrupt { .. },
22807                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
22808                        Instruction::YieldValue { .. },
22809                        Instruction::Resume { .. },
22810                    ]
22811                )
22812            }),
22813            "expected CPython-style negated generator filter to jump on false into the yield body and fall through into the loop backedge on true, got ops={ops:?}"
22814        );
22815    }
22816
22817    #[test]
22818    fn loop_filter_with_nested_loop_body_uses_cpython_implicit_continue_layout() {
22819        let code = compile_exec(
22820            "\
22821def f(values):
22822    for fmt, items in values:
22823        nd = make(items, fmt)
22824        for i in range(-5, 5):
22825            check(nd[i], items[i])
22826        check(nd[-6])
22827        check(nd[5])
22828        if is_memoryview_format(fmt):
22829            mv = memoryview(nd)
22830            check(mv, nd)
22831            for i in range(-5, 5):
22832                check(mv[i], items[i])
22833            check(mv[-6])
22834            check(mv[5])
22835    return None
22836",
22837        );
22838        let f = find_code(&code, "f").expect("missing f code");
22839        let ops: Vec<_> = f
22840            .instructions
22841            .iter()
22842            .map(|unit| unit.op)
22843            .filter(|op| !matches!(op, Instruction::Cache))
22844            .collect();
22845
22846        assert!(
22847            ops.windows(5).any(|window| {
22848                matches!(
22849                    window,
22850                    [
22851                        Instruction::ToBool,
22852                        Instruction::PopJumpIfTrue { .. },
22853                        Instruction::NotTaken,
22854                        Instruction::JumpBackward { .. }
22855                            | Instruction::JumpBackwardNoInterrupt { .. },
22856                        Instruction::LoadGlobal { .. },
22857                    ]
22858                )
22859            }),
22860            "expected CPython-style nested loop filter to fall through into the implicit continue and jump on true into the body, got ops={ops:?}"
22861        );
22862    }
22863
22864    #[test]
22865    fn final_elif_with_inlined_comprehensions_threads_backedge_before_body() {
22866        let code = compile_exec(
22867            "\
22868def f(checks, enumeration, named):
22869    for check in checks:
22870        if check == 1:
22871            pass
22872        elif check is named:
22873            member_names = enumeration._member_names_
22874            member_values = [m.value for m in enumeration]
22875            missing_names = []
22876            missing_value = 0
22877            for name, alias in enumeration._member_map_.items():
22878                if name in member_names:
22879                    continue
22880                if alias.value < 0:
22881                    continue
22882                values = list(_iter_bits_lsb(alias.value))
22883                missed = [v for v in values if v not in member_values]
22884                if missed:
22885                    missing_names.append(name)
22886                    for val in missed:
22887                        missing_value |= val
22888            if missing_names:
22889                raise ValueError('x')
22890    return enumeration
22891",
22892        );
22893        let f = find_code(&code, "f").expect("missing f code");
22894        let ops: Vec<_> = f
22895            .instructions
22896            .iter()
22897            .map(|unit| unit.op)
22898            .filter(|op| !matches!(op, Instruction::Cache))
22899            .collect();
22900
22901        assert!(
22902            ops.windows(6).any(|window| {
22903                matches!(
22904                    window,
22905                    [
22906                        Instruction::IsOp { .. },
22907                        Instruction::PopJumpIfTrue { .. },
22908                        Instruction::NotTaken,
22909                        Instruction::JumpBackward { .. }
22910                            | Instruction::JumpBackwardNoInterrupt { .. },
22911                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
22912                        Instruction::LoadAttr { .. },
22913                    ]
22914                )
22915            }),
22916            "expected CPython-style final elif to put loop backedge before the inlined-comprehension body, got ops={ops:?}"
22917        );
22918        assert!(
22919            !ops.windows(5).any(|window| {
22920                matches!(
22921                    window,
22922                    [
22923                        Instruction::IsOp { .. },
22924                        Instruction::PopJumpIfFalse { .. },
22925                        Instruction::NotTaken,
22926                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
22927                        Instruction::LoadAttr { .. },
22928                    ]
22929                )
22930            }),
22931            "unexpected final elif body before loop backedge, got ops={ops:?}"
22932        );
22933    }
22934
22935    #[test]
22936    fn multi_with_header_uses_store_fast_load_fast() {
22937        let code = compile_exec(
22938            "\
22939def f(manager):
22940    with manager() as x, manager():
22941        pass
22942",
22943        );
22944        let f = find_code(&code, "f").expect("missing function code");
22945        assert!(
22946            f.instructions
22947                .iter()
22948                .any(|unit| matches!(unit.op, Instruction::StoreFastLoadFast { .. })),
22949            "expected STORE_FAST_LOAD_FAST in multi-with header, got ops={:?}",
22950            f.instructions
22951                .iter()
22952                .map(|unit| unit.op)
22953                .collect::<Vec<_>>()
22954        );
22955    }
22956
22957    #[test]
22958    fn sequential_store_then_load_uses_store_fast_load_fast() {
22959        let code = compile_exec(
22960            "\
22961def f(self):
22962    x = ''; y = \"\"; self.assertTrue(len(x) == 0 and x == y)
22963",
22964        );
22965        let f = find_code(&code, "f").expect("missing function code");
22966        assert!(
22967            f.instructions
22968                .iter()
22969                .any(|unit| matches!(unit.op, Instruction::StoreFastLoadFast { .. })),
22970            "expected STORE_FAST_LOAD_FAST in sequential statement body, got ops={:?}",
22971            f.instructions
22972                .iter()
22973                .map(|unit| unit.op)
22974                .collect::<Vec<_>>()
22975        );
22976    }
22977
22978    #[test]
22979    fn match_guard_capture_uses_store_fast_load_fast() {
22980        let code = compile_exec(
22981            "\
22982def f():
22983    match 0:
22984        case x if x:
22985            z = 0
22986",
22987        );
22988        let f = find_code(&code, "f").expect("missing function code");
22989        assert!(
22990            f.instructions
22991                .iter()
22992                .any(|unit| matches!(unit.op, Instruction::StoreFastLoadFast { .. })),
22993            "expected STORE_FAST_LOAD_FAST in match guard capture path, got ops={:?}",
22994            f.instructions
22995                .iter()
22996                .map(|unit| unit.op)
22997                .collect::<Vec<_>>()
22998        );
22999    }
23000
23001    #[test]
23002    fn match_nested_capture_uses_store_fast_store_fast() {
23003        let code = compile_exec(
23004            "\
23005def f(x):
23006    match x:
23007        case ((0 as w) as z):
23008            return w, z
23009",
23010        );
23011        let f = find_code(&code, "f").expect("missing function code");
23012        assert!(
23013            f.instructions
23014                .iter()
23015                .any(|unit| matches!(unit.op, Instruction::StoreFastStoreFast { .. })),
23016            "expected STORE_FAST_STORE_FAST in nested match capture path, got ops={:?}",
23017            f.instructions
23018                .iter()
23019                .map(|unit| unit.op)
23020                .collect::<Vec<_>>()
23021        );
23022    }
23023
23024    #[test]
23025    fn match_value_real_zero_minus_zero_complex_folds_to_negative_zero_imag() {
23026        let code = compile_exec(
23027            "\
23028def f(x):
23029    match x:
23030        case 0 - 0j:
23031            return 0
23032",
23033        );
23034        let f = find_code(&code, "f").expect("missing function code");
23035        assert!(
23036            f.constants.iter().any(|constant| matches!(
23037                constant,
23038                ConstantData::Complex { value }
23039                    if value.re == 0.0 && value.im == 0.0 && value.im.is_sign_negative()
23040            )),
23041            "expected folded -0j constant in match value"
23042        );
23043    }
23044
23045    #[test]
23046    fn match_negative_value_const_precedes_implicit_none_like_cpython() {
23047        let code = compile_exec(
23048            "\
23049def f(x):
23050    match x:
23051        case -0.0:
23052            y = 0
23053",
23054        );
23055        let f = find_code(&code, "f").expect("missing function code");
23056        let negative_zero_index = f
23057            .constants
23058            .iter()
23059            .position(|constant| {
23060                matches!(
23061                    constant,
23062                    ConstantData::Float { value } if *value == 0.0 && value.is_sign_negative()
23063                )
23064            })
23065            .expect("missing folded -0.0 match value");
23066        let none_index = f
23067            .constants
23068            .iter()
23069            .position(|constant| matches!(constant, ConstantData::None))
23070            .expect("missing implicit None");
23071        assert!(
23072            negative_zero_index < none_index,
23073            "CPython ast_preprocess.c folds MatchValue constants before codegen registers the implicit None"
23074        );
23075    }
23076
23077    #[test]
23078    fn match_or_uses_shared_success_block() {
23079        let code = compile_exec(
23080            "\
23081def http_error(status):
23082    match status:
23083        case 400:
23084            return 'Bad request'
23085        case 401 | 403 | 404:
23086            return 'Not allowed'
23087        case 418:
23088            return 'I am a teapot'
23089",
23090        );
23091        let f = find_code(&code, "http_error").expect("missing http_error code");
23092        let ops: Vec<_> = f
23093            .instructions
23094            .iter()
23095            .map(|unit| unit.op)
23096            .filter(|op| !matches!(op, Instruction::Cache))
23097            .collect();
23098
23099        let jump_positions: Vec<_> = ops
23100            .iter()
23101            .enumerate()
23102            .filter_map(|(i, op)| matches!(op, Instruction::JumpForward { .. }).then_some(i))
23103            .collect();
23104
23105        assert!(
23106            jump_positions.len() >= 4,
23107            "expected shared-success JumpForward ops in OR pattern, got ops={ops:?}"
23108        );
23109
23110        let first_pop_top_pair = ops
23111            .windows(2)
23112            .position(|window| matches!(window, [Instruction::PopTop, Instruction::PopTop]))
23113            .expect("missing POP_TOP/POP_TOP success cleanup");
23114
23115        assert!(
23116            jump_positions
23117                .iter()
23118                .take(3)
23119                .all(|&idx| idx < first_pop_top_pair),
23120            "expected OR-alternative jumps before shared success cleanup, got ops={ops:?}"
23121        );
23122    }
23123
23124    #[test]
23125    fn match_try_body_keeps_setup_nop_after_success_pop() {
23126        let code = compile_exec(
23127            "\
23128def f(x):
23129    match x:
23130        case 1:
23131            try:
23132                y = 2
23133            except Exception:
23134                pass
23135        case 2:
23136            y = 3
23137",
23138        );
23139        let f = find_code(&code, "f").expect("missing function code");
23140        let ops: Vec<_> = f
23141            .instructions
23142            .iter()
23143            .map(|unit| unit.op)
23144            .filter(|op| !matches!(op, Instruction::Cache))
23145            .collect();
23146
23147        assert!(
23148            ops.windows(3).any(|window| matches!(
23149                window,
23150                [
23151                    Instruction::PopTop,
23152                    Instruction::Nop,
23153                    Instruction::LoadSmallInt { .. } | Instruction::LoadConst { .. }
23154                ]
23155            )),
23156            "expected CPython-style match success POP_TOP followed by try-entry NOP, got ops={ops:?}"
23157        );
23158    }
23159
23160    #[test]
23161    fn match_mapping_attribute_key_keeps_plain_load_fast_without_block_disable() {
23162        let code = compile_exec(
23163            "\
23164def f(self):
23165    class Keys:
23166        KEY = 'a'
23167    x = {'a': 0, 'b': 1}
23168    with self.assertRaises(ValueError):
23169        match x:
23170            case {Keys.KEY: y, 'a': z}:
23171                w = 0
23172",
23173        );
23174        let f = find_code(&code, "f").expect("missing function code");
23175        let assert_raises_attr = f
23176            .instructions
23177            .iter()
23178            .position(|unit| match unit.op {
23179                Instruction::LoadAttr { namei } => {
23180                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
23181                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
23182                        == "assertRaises"
23183                }
23184                _ => false,
23185            })
23186            .expect("missing assertRaises attribute load");
23187        let assert_raises_receiver = f.instructions[assert_raises_attr - 1].op;
23188        assert!(
23189            matches!(assert_raises_receiver, Instruction::LoadFastBorrow { .. }),
23190            "mapping attribute key handling must not disable borrow optimization for the whole block; got ops={:?}",
23191            f.instructions
23192                .iter()
23193                .map(|unit| unit.op)
23194                .collect::<Vec<_>>()
23195        );
23196        let key_load_idx = f
23197            .instructions
23198            .iter()
23199            .position(|unit| match unit.op {
23200                Instruction::LoadAttr { namei } => {
23201                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
23202                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "KEY"
23203                }
23204                _ => false,
23205            })
23206            .expect("missing Keys.KEY attribute load");
23207        let prev = f.instructions[key_load_idx - 1].op;
23208        assert!(
23209            matches!(prev, Instruction::LoadFast { .. }),
23210            "CPython optimize_load_fast() records MATCH_KEYS' no-input pseudo-ref with the produced-value loop index, so this consumed Keys load stays strong; got ops={:?}",
23211            f.instructions
23212                .iter()
23213                .map(|unit| unit.op)
23214                .collect::<Vec<_>>()
23215        );
23216    }
23217
23218    #[test]
23219    fn if_false_body_blocks_following_load_fast_borrow() {
23220        let code = compile_exec(
23221            "\
23222def f(self, groupby):
23223    self.a()
23224    if False:
23225        self.dead()
23226    self.b(groupby)
23227",
23228        );
23229        let f = find_code(&code, "f").expect("missing function code");
23230        let units: Vec<_> = f
23231            .instructions
23232            .iter()
23233            .filter(|unit| !matches!(unit.op, Instruction::Cache))
23234            .collect();
23235        let b_attr_idx = units
23236            .iter()
23237            .position(|unit| match unit.op {
23238                Instruction::LoadAttr { namei } => {
23239                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
23240                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "b"
23241                }
23242                _ => false,
23243            })
23244            .expect("missing self.b attribute load");
23245        assert!(
23246            matches!(units[b_attr_idx - 1].op, Instruction::LoadFast { .. }),
23247            "CPython keeps plain LOAD_FAST after an if False dead-body placeholder, got ops={:?}",
23248            units.iter().map(|unit| unit.op).collect::<Vec<_>>()
23249        );
23250        assert!(
23251            matches!(units[b_attr_idx + 1].op, Instruction::LoadFast { .. }),
23252            "CPython keeps the argument LOAD_FAST plain after an if False dead-body placeholder, got ops={:?}",
23253            units.iter().map(|unit| unit.op).collect::<Vec<_>>()
23254        );
23255    }
23256
23257    #[test]
23258    fn imap_append_untagged_assert_tail_keeps_load_fast() {
23259        let code = compile_exec(
23260            "\
23261def f(self, typ, dat):
23262    if self._idle_capture:
23263        if (not self._idle_responses or
23264            isinstance(self._idle_responses[-1][1][-1], bytes)):
23265            self._idle_responses.append((typ, [dat]))
23266        else:
23267            response = self._idle_responses[-1]
23268            assert response[0] == typ
23269            response[1].append(dat)
23270        if __debug__ and self.debug >= 5:
23271            self._mesg(f'idle: queue untagged {typ} {dat!r}')
23272        return
23273",
23274        );
23275        let f = find_code(&code, "f").expect("missing f code");
23276        let units: Vec<_> = f
23277            .instructions
23278            .iter()
23279            .filter(|unit| !matches!(unit.op, Instruction::Cache))
23280            .collect();
23281        let debug_attr_idx = units
23282            .iter()
23283            .position(|unit| match unit.op {
23284                Instruction::LoadAttr { namei } => {
23285                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
23286                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "debug"
23287                }
23288                _ => false,
23289            })
23290            .expect("missing debug attribute load");
23291        assert!(
23292            matches!(units[debug_attr_idx - 1].op, Instruction::LoadFast { .. }),
23293            "CPython keeps the debug tail after an emptied bool-op block as LOAD_FAST, got ops={:?}",
23294            units.iter().map(|unit| unit.op).collect::<Vec<_>>()
23295        );
23296
23297        let mesg_attr_idx = units
23298            .iter()
23299            .position(|unit| match unit.op {
23300                Instruction::LoadAttr { namei } => {
23301                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
23302                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "_mesg"
23303                }
23304                _ => false,
23305            })
23306            .expect("missing _mesg attribute load");
23307        assert!(
23308            matches!(units[mesg_attr_idx - 1].op, Instruction::LoadFast { .. })
23309                && matches!(units[mesg_attr_idx + 2].op, Instruction::LoadFast { .. })
23310                && matches!(units[mesg_attr_idx + 5].op, Instruction::LoadFast { .. }),
23311            "CPython keeps LOAD_FAST in the debug message after the empty join block, got ops={:?}",
23312            units.iter().map(|unit| unit.op).collect::<Vec<_>>()
23313        );
23314    }
23315
23316    #[test]
23317    fn assert_success_empty_boolop_block_keeps_load_fast() {
23318        let code = compile_exec(
23319            "\
23320def f(self):
23321    imap = self._imap
23322    assert not imap._idle_responses
23323    assert not imap._idle_capture
23324    if __debug__ and imap.debug >= 4:
23325        imap._mesg(f'idle start duration={self._duration}')
23326",
23327        );
23328        let f = find_code(&code, "f").expect("missing f code");
23329        let units: Vec<_> = f
23330            .instructions
23331            .iter()
23332            .filter(|unit| !matches!(unit.op, Instruction::Cache))
23333            .collect();
23334        let debug_attr_idx = units
23335            .iter()
23336            .position(|unit| match unit.op {
23337                Instruction::LoadAttr { namei } => {
23338                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
23339                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "debug"
23340                }
23341                _ => false,
23342            })
23343            .expect("missing debug attribute load");
23344        assert!(
23345            matches!(units[debug_attr_idx - 1].op, Instruction::LoadFast { .. }),
23346            "CPython preserves the empty assert-success bool-op block as a LOAD_FAST barrier, got ops={:?}",
23347            units.iter().map(|unit| unit.op).collect::<Vec<_>>()
23348        );
23349    }
23350
23351    #[test]
23352    fn try_except_else_with_finally_keeps_with_handler_before_outer_except() {
23353        let code = compile_exec(
23354            "\
23355def f(i):
23356    try:
23357        1 / 0
23358    except ZeroDivisionError:
23359        print('e')
23360    else:
23361        with i as dodgy:
23362            print('w')
23363    finally:
23364        print('d')
23365",
23366        );
23367        let jumpy = find_code(&code, "f").expect("missing f code");
23368        let ops: Vec<_> = jumpy
23369            .instructions
23370            .iter()
23371            .map(|unit| unit.op)
23372            .filter(|op| !matches!(op, Instruction::Cache))
23373            .collect();
23374
23375        let with_except_idx = ops
23376            .iter()
23377            .position(|op| matches!(op, Instruction::WithExceptStart))
23378            .expect("missing WITH_EXCEPT_START");
23379        let check_exc_idx = ops
23380            .iter()
23381            .position(|op| matches!(op, Instruction::CheckExcMatch))
23382            .expect("missing CHECK_EXC_MATCH");
23383        assert!(
23384            with_except_idx < check_exc_idx,
23385            "expected with-except cleanup to be emitted before outer except matching like CPython, got ops={ops:?}",
23386        );
23387
23388        let with_cleanup_end = ops
23389            .windows(5)
23390            .position(|window| {
23391                matches!(
23392                    window,
23393                    [
23394                        Instruction::LoadConst { .. },
23395                        Instruction::LoadConst { .. },
23396                        Instruction::LoadConst { .. },
23397                        Instruction::Call { .. },
23398                        Instruction::PopTop,
23399                    ]
23400                )
23401            })
23402            .expect("missing with success cleanup")
23403            + 5;
23404        assert!(
23405            !matches!(ops.get(with_cleanup_end), Some(Instruction::Nop)),
23406            "expected with success cleanup to fall straight into the surrounding continuation without a synthetic NOP target, got ops={ops:?}",
23407        );
23408    }
23409
23410    #[test]
23411    fn nested_try_finally_keeps_inner_finally_cleanup_nop() {
23412        let code = compile_exec(
23413            "\
23414def f(a, b, d):
23415    try:
23416        try:
23417            a()
23418        finally:
23419            b()
23420    finally:
23421        d()
23422",
23423        );
23424        let f = find_code(&code, "f").expect("missing f code");
23425        let ops_lines: Vec<_> = f
23426            .instructions
23427            .iter()
23428            .zip(&f.locations)
23429            .filter_map(|(unit, (location, _))| {
23430                (!matches!(unit.op, Instruction::Cache)).then_some((unit.op, location.line.get()))
23431            })
23432            .collect();
23433
23434        assert!(
23435            ops_lines.windows(3).any(|window| {
23436                matches!(
23437                    window,
23438                    [
23439                        (Instruction::PopTop, 6),
23440                        (Instruction::Nop, 6),
23441                        (
23442                            Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
23443                            8
23444                        ),
23445                    ]
23446                )
23447            }),
23448            "expected CPython-style inner finally cleanup NOP before outer finalbody, got ops_lines={ops_lines:?}",
23449        );
23450    }
23451
23452    #[test]
23453    fn nested_finally_open_conditional_falls_through_without_entry_nop() {
23454        let code = compile_exec(
23455            "\
23456def f(self, f, closed, new_key):
23457    try:
23458        try:
23459            work()
23460        finally:
23461            if self._locked:
23462                _unlock_file(f)
23463    finally:
23464        if not closed:
23465            _sync_close(f)
23466    return new_key
23467",
23468        );
23469        let f = find_code(&code, "f").expect("missing f code");
23470        let ops: Vec<_> = f
23471            .instructions
23472            .iter()
23473            .map(|unit| unit.op)
23474            .filter(|op| !matches!(op, Instruction::Cache | Instruction::NotTaken))
23475            .collect();
23476
23477        assert!(
23478            ops.windows(3).any(|window| {
23479                matches!(
23480                    window,
23481                    [
23482                        Instruction::PopTop,
23483                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
23484                        Instruction::ToBool,
23485                    ]
23486                )
23487            }),
23488            "expected CPython-style fallthrough from inner finally body into outer finalbody condition, got ops={ops:?}"
23489        );
23490        assert!(
23491            !ops.windows(4).any(|window| {
23492                matches!(
23493                    window,
23494                    [
23495                        Instruction::PopTop,
23496                        Instruction::Nop,
23497                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
23498                        Instruction::ToBool,
23499                    ]
23500                )
23501            }),
23502            "unexpected preserved inner-finally entry NOP before outer finalbody condition, got ops={ops:?}"
23503        );
23504    }
23505
23506    #[test]
23507    fn nested_finally_closed_conditional_falls_through_without_extra_entry_nop() {
23508        let code = compile_exec(
23509            "\
23510def f(was_enabled, faulthandler, sys, orig_stderr):
23511    try:
23512        try:
23513            faulthandler.enable()
23514            faulthandler.disable()
23515        finally:
23516            if was_enabled:
23517                faulthandler.enable()
23518            else:
23519                faulthandler.disable()
23520    finally:
23521        sys.stderr = orig_stderr
23522",
23523        );
23524        let f = find_code(&code, "f").expect("missing f code");
23525        let ops: Vec<_> = f
23526            .instructions
23527            .iter()
23528            .map(|unit| unit.op)
23529            .filter(|op| !matches!(op, Instruction::Cache | Instruction::NotTaken))
23530            .collect();
23531
23532        assert!(
23533            ops.windows(3).any(|window| {
23534                matches!(
23535                    window,
23536                    [
23537                        Instruction::Nop,
23538                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
23539                            | Instruction::LoadFastLoadFast { .. },
23540                        Instruction::StoreAttr { .. },
23541                    ]
23542                )
23543            }),
23544            "CPython keeps the inner finally cleanup anchor before the outer finalbody, got ops={ops:?}"
23545        );
23546        assert!(
23547            !ops.windows(4).any(|window| {
23548                matches!(
23549                    window,
23550                    [
23551                        Instruction::Nop,
23552                        Instruction::Nop,
23553                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
23554                            | Instruction::LoadFastLoadFast { .. },
23555                        Instruction::StoreAttr { .. },
23556                    ]
23557                )
23558            }),
23559            "closed conditional inner finalbody should not add a second outer finalbody-entry NOP, got ops={ops:?}"
23560        );
23561    }
23562
23563    #[test]
23564    fn with_try_finally_normal_cleanup_keeps_redundant_jump_nop() {
23565        let code = compile_exec(
23566            "\
23567def f(cm):
23568    with cm:
23569        try:
23570            x = 1
23571        finally:
23572            del x
23573    return x
23574",
23575        );
23576        let f = find_code(&code, "f").expect("missing f code");
23577        let ops_lines: Vec<_> = f
23578            .instructions
23579            .iter()
23580            .zip(&f.locations)
23581            .filter_map(|(unit, (location, _))| {
23582                (!matches!(unit.op, Instruction::Cache)).then_some((unit.op, location.line.get()))
23583            })
23584            .collect();
23585
23586        assert!(
23587            ops_lines.windows(6).any(|window| {
23588                matches!(
23589                    window,
23590                    [
23591                        (Instruction::DeleteFast { .. }, 6),
23592                        (Instruction::Nop, 6),
23593                        (Instruction::LoadConst { .. }, 2),
23594                        (Instruction::LoadConst { .. }, 2),
23595                        (Instruction::LoadConst { .. }, 2),
23596                        (Instruction::Call { .. }, 2),
23597                    ]
23598                )
23599            }),
23600            "expected CPython-style redundant finally cleanup jump to become a line NOP before with-exit cleanup, got ops_lines={ops_lines:?}",
23601        );
23602    }
23603
23604    #[test]
23605    fn with_try_except_normal_cleanup_keeps_body_exit_nop() {
23606        let code = compile_exec(
23607            "\
23608def f(cm, names, modname):
23609    with cm:
23610        try:
23611            exec('import %s' % modname, names)
23612        except:
23613            raise FailedImport(modname)
23614    return names
23615",
23616        );
23617        let f = find_code(&code, "f").expect("missing f code");
23618        let ops: Vec<_> = f
23619            .instructions
23620            .iter()
23621            .map(|unit| unit.op)
23622            .filter(|op| !matches!(op, Instruction::Cache))
23623            .collect();
23624
23625        assert!(
23626            ops.windows(7).any(|window| {
23627                matches!(
23628                    window,
23629                    [
23630                        Instruction::Call { .. },
23631                        Instruction::PopTop,
23632                        Instruction::Nop,
23633                        Instruction::LoadConst { .. },
23634                        Instruction::LoadConst { .. },
23635                        Instruction::LoadConst { .. },
23636                        Instruction::Call { .. },
23637                    ]
23638                )
23639            }),
23640            "try/except inside with should preserve the CPython body-exit NOP before with cleanup, got ops={ops:?}"
23641        );
23642    }
23643
23644    #[test]
23645    fn with_try_except_return_handler_keeps_body_exit_nop() {
23646        let code = compile_exec(
23647            "\
23648def f(cm):
23649    with cm:
23650        try:
23651            x = 1
23652        except OSError:
23653            return False
23654    return True
23655",
23656        );
23657        let f = find_code(&code, "f").expect("missing f code");
23658        let ops: Vec<_> = f
23659            .instructions
23660            .iter()
23661            .map(|unit| unit.op)
23662            .filter(|op| !matches!(op, Instruction::Cache))
23663            .collect();
23664
23665        assert!(
23666            ops.windows(6).any(|window| {
23667                matches!(
23668                    window,
23669                    [
23670                        Instruction::StoreFast { .. },
23671                        Instruction::Nop,
23672                        Instruction::LoadConst { .. },
23673                        Instruction::LoadConst { .. },
23674                        Instruction::LoadConst { .. },
23675                        Instruction::Call { .. },
23676                    ]
23677                )
23678            }),
23679            "scope-exiting except handler inside with should preserve the CPython body-exit NOP before with cleanup, got ops={ops:?}"
23680        );
23681    }
23682
23683    #[test]
23684    fn with_try_except_else_return_handler_keeps_body_exit_nop() {
23685        let code = compile_exec(
23686            "\
23687def f(cm, func, check):
23688    with cm:
23689        try:
23690            func()
23691        except ValueError:
23692            return False
23693        else:
23694            check()
23695    return True
23696",
23697        );
23698        let f = find_code(&code, "f").expect("missing f code");
23699        let ops: Vec<_> = f
23700            .instructions
23701            .iter()
23702            .map(|unit| unit.op)
23703            .filter(|op| !matches!(op, Instruction::Cache))
23704            .collect();
23705
23706        assert!(
23707            ops.windows(7).any(|window| {
23708                matches!(
23709                    window,
23710                    [
23711                        Instruction::Call { .. },
23712                        Instruction::PopTop,
23713                        Instruction::Nop,
23714                        Instruction::LoadConst { .. },
23715                        Instruction::LoadConst { .. },
23716                        Instruction::LoadConst { .. },
23717                        Instruction::Call { .. },
23718                    ]
23719                )
23720            }),
23721            "try/except/else inside with with scope-exiting handler should preserve the CPython body-exit NOP before with cleanup, got ops={ops:?}"
23722        );
23723    }
23724
23725    #[test]
23726    fn with_try_except_else_continue_handler_keeps_body_exit_nop() {
23727        let code = compile_exec(
23728            "\
23729def f(meta_path, cm):
23730    for finder in meta_path:
23731        with cm:
23732            try:
23733                find_spec = finder.find_spec
23734            except AttributeError:
23735                continue
23736            else:
23737                spec = find_spec()
23738        if spec is not None:
23739            return spec
23740    return None
23741",
23742        );
23743        let f = find_code(&code, "f").expect("missing f code");
23744        let ops_lines: Vec<_> = f
23745            .instructions
23746            .iter()
23747            .zip(&f.locations)
23748            .filter_map(|(unit, (location, _))| {
23749                (!matches!(unit.op, Instruction::Cache)).then_some((unit.op, location.line.get()))
23750            })
23751            .collect();
23752
23753        assert!(
23754            ops_lines.windows(6).any(|window| {
23755                matches!(
23756                    window,
23757                    [
23758                        (Instruction::Call { .. }, 9),
23759                        (Instruction::StoreFast { .. }, 9),
23760                        (Instruction::Nop, 9),
23761                        (Instruction::LoadConst { .. }, 3),
23762                        (Instruction::LoadConst { .. }, 3),
23763                        (Instruction::LoadConst { .. }, 3),
23764                    ]
23765                )
23766            }),
23767            "try/except/else with continue handler should preserve the CPython body-exit NOP before with cleanup, got ops_lines={ops_lines:?}",
23768        );
23769    }
23770
23771    #[test]
23772    fn elif_boolop_skips_following_elif_with_forward_jumpback_block() {
23773        let code = compile_exec(
23774            r#"
23775def f(module, fromlist, import_, recursive=False):
23776    for x in fromlist:
23777        if not isinstance(x, str):
23778            raise TypeError
23779        elif x == '*':
23780            if not recursive and hasattr(module, '__all__'):
23781                _handle_fromlist(module, module.__all__, import_, recursive=True)
23782        elif not hasattr(module, x):
23783            pass
23784"#,
23785        );
23786        let f = find_code(&code, "f").expect("missing f code");
23787        let ops: Vec<_> = f
23788            .instructions
23789            .iter()
23790            .filter(|unit| !matches!(unit.op, Instruction::Cache))
23791            .collect();
23792        let is_load_global =
23793            |unit: &&rustpython_compiler_core::bytecode::CodeUnit, name: &str| match unit.op {
23794                Instruction::LoadGlobal { namei } => {
23795                    let namei = namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) >> 1;
23796                    f.names[usize::try_from(namei).unwrap()].as_str() == name
23797                }
23798                _ => false,
23799            };
23800
23801        assert!(
23802            ops.windows(4).any(|window| {
23803                matches!(
23804                    window,
23805                    [
23806                        unit0,
23807                        unit1,
23808                        unit2,
23809                        unit3,
23810                    ] if matches!(unit0.op, Instruction::ToBool)
23811                        && matches!(unit1.op, Instruction::PopJumpIfTrue { .. })
23812                        && matches!(unit2.op, Instruction::NotTaken)
23813                        && is_load_global(unit3, "hasattr")
23814                )
23815            }),
23816            "boolop branch in elif should keep CPython's forward jump-back block before hasattr, got ops={ops:?}",
23817        );
23818    }
23819
23820    #[test]
23821    fn with_nonterminal_try_except_normal_cleanup_drops_body_exit_nop() {
23822        let code = compile_exec(
23823            "\
23824def f(cm):
23825    with cm:
23826        try:
23827            x = 1
23828        except Exception:
23829            pass
23830    return x
23831",
23832        );
23833        let f = find_code(&code, "f").expect("missing f code");
23834        let ops: Vec<_> = f
23835            .instructions
23836            .iter()
23837            .map(|unit| unit.op)
23838            .filter(|op| !matches!(op, Instruction::Cache))
23839            .collect();
23840
23841        assert!(
23842            !ops.windows(6).any(|window| {
23843                matches!(
23844                    window,
23845                    [
23846                        Instruction::StoreFast { .. },
23847                        Instruction::Nop,
23848                        Instruction::LoadConst { .. },
23849                        Instruction::LoadConst { .. },
23850                        Instruction::LoadConst { .. },
23851                        Instruction::Call { .. },
23852                    ]
23853                )
23854            }),
23855            "non-terminal except inside with should not preserve a body-exit NOP before with cleanup, got ops={ops:?}"
23856        );
23857        assert!(
23858            ops.windows(5).any(|window| {
23859                matches!(
23860                    window,
23861                    [
23862                        Instruction::StoreFast { .. },
23863                        Instruction::LoadConst { .. },
23864                        Instruction::LoadConst { .. },
23865                        Instruction::LoadConst { .. },
23866                        Instruction::Call { .. },
23867                    ]
23868                )
23869            }),
23870            "expected CPython-style direct fallthrough into with cleanup, got ops={ops:?}"
23871        );
23872    }
23873
23874    #[test]
23875    fn with_try_except_scope_exit_body_handler_fallthrough_keeps_body_exit_nop() {
23876        let code = compile_exec(
23877            "\
23878def f(cm, ValueError):
23879    with cm:
23880        try:
23881            raise ValueError
23882        except 42:
23883            pass
23884",
23885        );
23886        let f = find_code(&code, "f").expect("missing f code");
23887        let ops_lines: Vec<_> = f
23888            .instructions
23889            .iter()
23890            .zip(&f.locations)
23891            .filter_map(|(unit, (location, _))| {
23892                (!matches!(unit.op, Instruction::Cache)).then_some((unit.op, location.line.get()))
23893            })
23894            .collect();
23895
23896        assert!(
23897            ops_lines.windows(5).any(|window| {
23898                matches!(
23899                    window,
23900                    [
23901                        (Instruction::Nop, 6),
23902                        (Instruction::LoadConst { .. }, 2),
23903                        (Instruction::LoadConst { .. }, 2),
23904                        (Instruction::LoadConst { .. }, 2),
23905                        (Instruction::Call { .. }, 2),
23906                    ]
23907                )
23908            }),
23909            "handler fallthrough target should preserve the CPython NOP before with cleanup, got ops_lines={ops_lines:?}",
23910        );
23911    }
23912
23913    #[test]
23914    fn with_try_except_nested_with_normal_cleanup_drops_body_exit_nop() {
23915        let code = compile_exec(
23916            "\
23917def f(open, src, dst, copyfileobj):
23918    with open(src) as fsrc:
23919        try:
23920            with open(dst) as fdst:
23921                copyfileobj(fsrc, fdst)
23922        except IsADirectoryError:
23923            raise
23924    return dst
23925",
23926        );
23927        let f = find_code(&code, "f").expect("missing f code");
23928        let ops: Vec<_> = f
23929            .instructions
23930            .iter()
23931            .map(|unit| unit.op)
23932            .filter(|op| !matches!(op, Instruction::Cache))
23933            .collect();
23934
23935        assert!(
23936            ops.windows(7).any(|window| {
23937                matches!(
23938                    window,
23939                    [
23940                        Instruction::Call { .. },
23941                        Instruction::PopTop,
23942                        Instruction::LoadConst { .. },
23943                        Instruction::LoadConst { .. },
23944                        Instruction::LoadConst { .. },
23945                        Instruction::Call { .. },
23946                        Instruction::PopTop,
23947                    ]
23948                )
23949            }),
23950            "nested with normal cleanup in try/except should fall directly into outer with cleanup, got ops={ops:?}"
23951        );
23952        assert!(
23953            !ops.windows(8).any(|window| {
23954                matches!(
23955                    window,
23956                    [
23957                        Instruction::Call { .. },
23958                        Instruction::PopTop,
23959                        Instruction::Nop,
23960                        Instruction::LoadConst { .. },
23961                        Instruction::LoadConst { .. },
23962                        Instruction::LoadConst { .. },
23963                        Instruction::Call { .. },
23964                        Instruction::PopTop,
23965                    ]
23966                )
23967            }),
23968            "nested with normal cleanup in try/except should not preserve a body-exit NOP before outer with cleanup, got ops={ops:?}"
23969        );
23970    }
23971
23972    #[test]
23973    fn with_nested_if_try_except_normal_cleanup_drops_body_exit_nop() {
23974        let code = compile_exec(
23975            "\
23976def f(cm, root):
23977    with cm:
23978        if root:
23979            try:
23980                x = 1
23981            except Exception as e:
23982                raise ValueError from e
23983    return root
23984",
23985        );
23986        let f = find_code(&code, "f").expect("missing f code");
23987        let ops: Vec<_> = f
23988            .instructions
23989            .iter()
23990            .map(|unit| unit.op)
23991            .filter(|op| !matches!(op, Instruction::Cache))
23992            .collect();
23993
23994        assert!(
23995            !ops.windows(6).any(|window| {
23996                matches!(
23997                    window,
23998                    [
23999                        Instruction::StoreFast { .. },
24000                        Instruction::Nop,
24001                        Instruction::LoadConst { .. },
24002                        Instruction::LoadConst { .. },
24003                        Instruction::LoadConst { .. },
24004                        Instruction::Call { .. },
24005                    ]
24006                )
24007            }),
24008            "nested try/except should not preserve a body-exit NOP before with cleanup, got ops={ops:?}"
24009        );
24010    }
24011
24012    #[test]
24013    fn try_except_finally_normal_cleanup_keeps_body_exit_nop() {
24014        let code = compile_exec(
24015            "\
24016def f(self, x):
24017    if x and self.sock:
24018        saved = self.sock.gettimeout()
24019        self.sock.settimeout(x)
24020        try:
24021            resp = self._get_line()
24022        except TimeoutError as err:
24023            raise self._timeout from err
24024        finally:
24025            self.sock.settimeout(saved)
24026    else:
24027        resp = self._get_line()
24028    return resp
24029",
24030        );
24031        let f = find_code(&code, "f").expect("missing f code");
24032        let ops: Vec<_> = f
24033            .instructions
24034            .iter()
24035            .filter(|unit| !matches!(unit.op, Instruction::Cache))
24036            .collect();
24037        let resp_store = ops
24038            .iter()
24039            .position(|unit| match unit.op {
24040                Instruction::StoreFast { var_num } => {
24041                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
24042                    f.varnames[usize::from(var_num.get(arg))] == "resp"
24043                }
24044                _ => false,
24045            })
24046            .expect("missing resp store");
24047
24048        assert!(
24049            matches!(
24050                ops.get(resp_store + 1).map(|unit| unit.op),
24051                Some(Instruction::Nop)
24052            ),
24053            "expected CPython-style NOP between try/except normal body exit and finally cleanup, got ops={ops:?}",
24054        );
24055    }
24056
24057    #[test]
24058    fn try_except_finally_open_conditional_fallthrough_drops_body_exit_nop() {
24059        let code = compile_exec(
24060            "\
24061def f(err, ov, self):
24062    try:
24063        if err:
24064            assert ov
24065    except:
24066        ov.cancel()
24067        raise
24068    finally:
24069        self.cleanup()
24070",
24071        );
24072        let f = find_code(&code, "f").expect("missing f code");
24073        let ops: Vec<_> = f
24074            .instructions
24075            .iter()
24076            .map(|unit| unit.op)
24077            .filter(|op| !matches!(op, Instruction::Cache))
24078            .collect();
24079        let assert_raise = ops
24080            .iter()
24081            .position(|op| matches!(op, Instruction::RaiseVarargs { .. }))
24082            .expect("missing assertion raise");
24083
24084        assert!(
24085            !matches!(ops.get(assert_raise + 1), Some(Instruction::Nop)),
24086            "open conditional fallthrough should go directly into finally cleanup, got ops={ops:?}",
24087        );
24088    }
24089
24090    #[test]
24091    fn try_finally_loop_fallthrough_keeps_finalbody_entry_nop() {
24092        let code = compile_exec(
24093            "\
24094def f(close, dup, first, second):
24095    try:
24096        retries = 0
24097        while second != first + 1:
24098            close(first)
24099            retries += 1
24100            if retries > 10:
24101                raise RuntimeError
24102            first, second = second, dup(second)
24103    finally:
24104        close(second)
24105    close(first)
24106",
24107        );
24108        let f = find_code(&code, "f").expect("missing f code");
24109        let ops: Vec<_> = f
24110            .instructions
24111            .iter()
24112            .map(|unit| unit.op)
24113            .filter(|op| !matches!(op, Instruction::Cache))
24114            .collect();
24115
24116        assert!(
24117            ops.windows(4).any(|window| {
24118                matches!(
24119                    window,
24120                    [
24121                        Instruction::JumpBackward { .. },
24122                        Instruction::Nop,
24123                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
24124                        Instruction::PushNull,
24125                    ]
24126                )
24127            }),
24128            "try/finally loop fallthrough should preserve CPython finalbody-entry NOP, got ops={ops:?}"
24129        );
24130    }
24131
24132    #[test]
24133    fn try_finally_boolop_while_fallthrough_drops_finalbody_entry_nop() {
24134        let code = compile_exec(
24135            "\
24136def f(active, socket_map, asyncore):
24137    try:
24138        while active and socket_map:
24139            asyncore.loop(timeout=0.1, count=1)
24140    finally:
24141        asyncore.close_all(ignore_all=True)
24142",
24143        );
24144        let f = find_code(&code, "f").expect("missing f code");
24145        let ops: Vec<_> = f
24146            .instructions
24147            .iter()
24148            .map(|unit| unit.op)
24149            .filter(|op| !matches!(op, Instruction::Cache))
24150            .collect();
24151
24152        assert!(
24153            ops.windows(3).any(|window| {
24154                matches!(
24155                    window,
24156                    [
24157                        Instruction::JumpBackward { .. },
24158                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
24159                        Instruction::LoadAttr { .. },
24160                    ]
24161                )
24162            }),
24163            "CPython removes the no-location POP_BLOCK NOP before a boolop-while try/finally finalbody, got ops={ops:?}"
24164        );
24165        assert!(
24166            !ops.windows(4).any(|window| {
24167                matches!(
24168                    window,
24169                    [
24170                        Instruction::JumpBackward { .. },
24171                        Instruction::Nop,
24172                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
24173                        Instruction::LoadAttr { .. },
24174                    ]
24175                )
24176            }),
24177            "boolop-while try/finally finalbody should not keep a POP_BLOCK NOP, got ops={ops:?}"
24178        );
24179    }
24180
24181    #[test]
24182    fn try_finally_with_infinite_loop_body_drops_finalbody_entry_nop() {
24183        let code = compile_exec(
24184            "\
24185def f(self, func, args, kwargs):
24186    try:
24187        with self.assertRaises(ZeroDivisionError) as cm:
24188            while True:
24189                self.setAlarm(self.alarm_time)
24190                func(*args, **kwargs)
24191    finally:
24192        self.setAlarm(0)
24193",
24194        );
24195        let f = find_code(&code, "f").expect("missing f code");
24196        let ops: Vec<_> = f
24197            .instructions
24198            .iter()
24199            .map(|unit| unit.op)
24200            .filter(|op| !matches!(op, Instruction::Cache))
24201            .collect();
24202
24203        assert!(
24204            ops.windows(3).any(|window| {
24205                matches!(
24206                    window,
24207                    [
24208                        Instruction::Reraise { .. },
24209                        Instruction::LoadFast { .. },
24210                        Instruction::LoadAttr { .. },
24211                    ]
24212                )
24213            }),
24214            "CPython removes the no-location POP_BLOCK NOP before the normal finalbody after a with-wrapped infinite loop, got ops={ops:?}"
24215        );
24216        assert!(
24217            !ops.windows(4).any(|window| {
24218                matches!(
24219                    window,
24220                    [
24221                        Instruction::Reraise { .. },
24222                        Instruction::Nop,
24223                        Instruction::LoadFast { .. },
24224                        Instruction::LoadAttr { .. },
24225                    ]
24226                )
24227            }),
24228            "with-wrapped infinite loop try/finally should not keep a finalbody-entry NOP, got ops={ops:?}"
24229        );
24230    }
24231
24232    #[test]
24233    fn try_finally_with_finalbody_blocks_following_with_borrow() {
24234        let code = compile_exec(
24235            "\
24236def f(self, sock, socket, HOST, OSError, TypeError):
24237    try:
24238        sock.bind((HOST, 0))
24239        socket.close(sock.fileno())
24240        with self.assertRaises(OSError):
24241            sock.listen(1)
24242    finally:
24243        with self.assertRaises(OSError):
24244            sock.close()
24245    with self.assertRaises(TypeError):
24246        socket.close(42, 42)
24247    with self.assertRaises(OSError):
24248        socket.close(-1)
24249",
24250        );
24251        let f = find_code(&code, "f").expect("missing f code");
24252        let strong_self_loads = count_strong_loads_for_vars(f, &["self"]);
24253        assert!(
24254            strong_self_loads >= 2,
24255            "CPython codegen_try_finally() emits USE_LABEL(exit) after a with finalbody, so optimize_load_fast() leaves following with receivers strong; got {strong_self_loads} strong self loads"
24256        );
24257    }
24258
24259    #[test]
24260    fn try_finally_loop_direct_break_drops_finalbody_entry_nop() {
24261        let code = compile_exec(
24262            "\
24263def f(lines, close):
24264    try:
24265        while lines:
24266            if lines[0]:
24267                break
24268            close(1)
24269    finally:
24270        close(2)
24271    close(3)
24272",
24273        );
24274        let f = find_code(&code, "f").expect("missing f code");
24275        let ops: Vec<_> = f
24276            .instructions
24277            .iter()
24278            .map(|unit| unit.op)
24279            .filter(|op| !matches!(op, Instruction::Cache))
24280            .collect();
24281
24282        assert!(
24283            ops.windows(3).any(|window| {
24284                matches!(
24285                    window,
24286                    [
24287                        Instruction::JumpBackward { .. }
24288                            | Instruction::JumpBackwardNoInterrupt { .. },
24289                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
24290                        Instruction::PushNull,
24291                    ]
24292                )
24293            }),
24294            "direct loop break should enter CPython finalbody without a NOP, got ops={ops:?}",
24295        );
24296        assert!(
24297            !ops.windows(4).any(|window| {
24298                matches!(
24299                    window,
24300                    [
24301                        Instruction::JumpBackward { .. }
24302                            | Instruction::JumpBackwardNoInterrupt { .. },
24303                        Instruction::Nop,
24304                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
24305                        Instruction::PushNull,
24306                    ]
24307                )
24308            }),
24309            "direct loop break should not preserve finalbody-entry NOP, got ops={ops:?}",
24310        );
24311    }
24312
24313    #[test]
24314    fn try_finally_return_inside_with_pops_unwound_fblocks_for_finalbody() {
24315        let code = compile_exec(
24316            "\
24317def f(cm):
24318    try:
24319        with cm:
24320            return 1
24321    finally:
24322        return 2
24323",
24324        );
24325        let f = find_code(&code, "f").expect("missing f code");
24326        assert!(
24327            f.instructions
24328                .iter()
24329                .any(|unit| matches!(unit.op, Instruction::ReturnValue)),
24330            "return inside with/try-finally should compile without leaving an invalid CFG"
24331        );
24332    }
24333
24334    #[test]
24335    fn except_star_return_after_with_unwind_uses_no_location_like_cpython() {
24336        let err = compile_exec_error(
24337            "\
24338def f(cm):
24339    try:
24340        pass
24341    except* Exception:
24342        with cm:
24343            return 1
24344",
24345        );
24346        assert!(matches!(
24347            err.error,
24348            CodegenErrorType::BreakContinueReturnInExceptStar
24349        ));
24350        assert!(
24351            err.location.is_none(),
24352            "CPython codegen_unwind_fblock(WITH) sets *ploc = NO_LOCATION before the except* error"
24353        );
24354    }
24355
24356    #[test]
24357    fn async_generator_return_value_error_message_matches_cpython() {
24358        assert_eq!(
24359            compile_exec_error_message(
24360                "\
24361async def f():
24362    yield 1
24363    return 2
24364"
24365            ),
24366            "'return' with value in async generator"
24367        );
24368    }
24369
24370    #[test]
24371    fn try_except_finally_handler_normal_exit_keeps_nointerrupt_jump() {
24372        let code = compile_exec(
24373            "\
24374def f():
24375    try:
24376        2
24377    except:
24378        4
24379    finally:
24380        6
24381",
24382        );
24383        let f = find_code(&code, "f").expect("missing f code");
24384        let ops: Vec<_> = f
24385            .instructions
24386            .iter()
24387            .map(|unit| unit.op)
24388            .filter(|op| !matches!(op, Instruction::Cache))
24389            .collect();
24390
24391        assert!(
24392            ops.windows(2).any(|window| {
24393                matches!(
24394                    window,
24395                    [
24396                        Instruction::PopExcept,
24397                        Instruction::JumpBackwardNoInterrupt { .. }
24398                    ]
24399                )
24400            }),
24401            "CPython codegen_try_except() emits JUMP_NO_INTERRUPT to the inner end label; when wrapped by codegen_try_finally(), push_cold_blocks_to_end() preserves it as a backward no-interrupt jump, got ops={ops:?}",
24402        );
24403        assert!(
24404            !ops.windows(3).any(|window| {
24405                matches!(
24406                    window,
24407                    [
24408                        Instruction::PopExcept,
24409                        Instruction::LoadConst { .. },
24410                        Instruction::ReturnValue,
24411                    ]
24412                )
24413            }),
24414            "try/except/finally handler normal exit should not inline the function epilogue over CPython's no-interrupt jump, got ops={ops:?}",
24415        );
24416    }
24417
24418    #[test]
24419    fn nested_while_break_keeps_cpython_unreachable_end_epilogue() {
24420        let code = compile_exec(
24421            "\
24422def f():
24423    TRUE = 1
24424    while TRUE:
24425        while TRUE:
24426            break
24427        break
24428",
24429        );
24430        let f = find_code(&code, "f").expect("missing f code");
24431        let ops: Vec<_> = f
24432            .instructions
24433            .iter()
24434            .map(|unit| unit.op)
24435            .filter(|op| !matches!(op, Instruction::Cache))
24436            .collect();
24437        let returns = ops
24438            .iter()
24439            .filter(|op| matches!(op, Instruction::ReturnValue))
24440            .count();
24441
24442        assert_eq!(
24443            returns, 3,
24444            "CPython codegen_while() emits separate anchor/end labels and codegen_break() jumps to loop->fb_exit; after redundant jump removal, the b_next return epilogue still remains, got ops={ops:?}",
24445        );
24446    }
24447
24448    #[test]
24449    fn while_else_break_keeps_separate_continue_backedges() {
24450        let code = compile_exec(
24451            "\
24452def func():
24453    TRUE = 1
24454    x = [1]
24455    while x:
24456        x.pop()
24457        while TRUE:
24458            break
24459        else:
24460            continue
24461",
24462        );
24463        let func = find_code(&code, "func").expect("missing func code");
24464        let ops: Vec<_> = func
24465            .instructions
24466            .iter()
24467            .map(|unit| unit.op)
24468            .filter(|op| !matches!(op, Instruction::Cache))
24469            .collect();
24470        let jump_backwards = ops
24471            .iter()
24472            .filter(|op| matches!(op, Instruction::JumpBackward { .. }))
24473            .count();
24474
24475        assert_eq!(
24476            jump_backwards, 2,
24477            "CPython codegen_break() emits a line-bearing jump to the inner while end, and codegen_while() keeps the else anchor separate from the end label; the break path and else-continue path should remain distinct backedges, got ops={ops:?}",
24478        );
24479    }
24480
24481    #[test]
24482    fn break_through_finally_assert_tail_keeps_borrow_loads() {
24483        let code = compile_exec(
24484            "\
24485def func():
24486    a, c, d, i = 1, 1, 1, 99
24487    try:
24488        for i in range(3):
24489            try:
24490                a = 5
24491                if i > 0:
24492                    break
24493                a = 8
24494            finally:
24495                c = 10
24496    except:
24497        d = 12
24498    assert a == 5 and c == 10 and d == 1
24499",
24500        );
24501        let func = find_code(&code, "func").expect("missing func code");
24502        for name in ["a", "c", "d"] {
24503            let loads = load_fast_ops_for_var(func, name);
24504            assert!(
24505                loads
24506                    .iter()
24507                    .any(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
24508                "CPython flowgraph.c::optimize_load_fast() keeps assert-tail {name} loads borrowed after a resuming bare except; got loads={loads:?}",
24509            );
24510        }
24511    }
24512
24513    #[test]
24514    fn try_except_finally_suppressing_handler_drops_body_exit_nop() {
24515        let code = compile_exec(
24516            "\
24517def f(self):
24518    try:
24519        self.sock.shutdown(socket.SHUT_RDWR)
24520    except OSError as exc:
24521        if exc.errno != errno.ENOTCONN:
24522            raise
24523    finally:
24524        self.sock.close()
24525",
24526        );
24527        let f = find_code(&code, "f").expect("missing f code");
24528        let ops: Vec<_> = f
24529            .instructions
24530            .iter()
24531            .map(|unit| unit.op)
24532            .filter(|op| !matches!(op, Instruction::Cache))
24533            .collect();
24534        let shutdown_pop = ops
24535            .iter()
24536            .position(|op| matches!(op, Instruction::PopTop))
24537            .expect("missing shutdown POP_TOP");
24538
24539        assert!(
24540            !matches!(ops.get(shutdown_pop + 1), Some(Instruction::Nop)),
24541            "suppressing except handler should fall directly into finally cleanup without a CPython body-exit NOP, got ops={ops:?}",
24542        );
24543        assert!(
24544            matches!(
24545                ops.get(shutdown_pop + 1),
24546                Some(Instruction::LoadFastBorrow { .. })
24547            ),
24548            "suppressing except handler should keep CPython-style borrowed finally cleanup receiver, got ops={ops:?}",
24549        );
24550    }
24551
24552    #[test]
24553    fn conditional_break_finally_does_not_keep_break_cleanup_nop() {
24554        let code = compile_exec(
24555            "\
24556def f(tar1, x):
24557    try:
24558        while True:
24559            if x:
24560                break
24561            x = 1
24562    finally:
24563        tar1.close()
24564",
24565        );
24566        let f = find_code(&code, "f").expect("missing f code");
24567        let instructions: Vec<_> = f
24568            .instructions
24569            .iter()
24570            .filter(|unit| !matches!(unit.op, Instruction::Cache))
24571            .collect();
24572        let ops_lines: Vec<_> = f
24573            .instructions
24574            .iter()
24575            .zip(&f.locations)
24576            .filter_map(|(unit, (location, _))| {
24577                (!matches!(unit.op, Instruction::Cache)).then_some((unit.op, location.line.get()))
24578            })
24579            .collect();
24580
24581        assert!(
24582            !ops_lines.windows(2).any(|window| {
24583                matches!(
24584                    window,
24585                    [
24586                        (Instruction::Nop, 5),
24587                        (
24588                            Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
24589                            8
24590                        ),
24591                    ]
24592                )
24593            }),
24594            "expected CPython-style break cleanup to jump directly into finally body, got ops_lines={ops_lines:?}",
24595        );
24596
24597        let close_attr = instructions
24598            .iter()
24599            .position(|unit| match unit.op {
24600                Instruction::LoadAttr { namei } => {
24601                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
24602                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "close"
24603                }
24604                _ => false,
24605            })
24606            .expect("missing close load");
24607        assert!(
24608            matches!(
24609                instructions[close_attr - 1].op,
24610                Instruction::LoadFastBorrow { .. }
24611            ),
24612            "CPython visits the finalbody through the loop fallthrough when break is not the loop body tail; got instructions={instructions:?}",
24613        );
24614    }
24615
24616    #[test]
24617    fn tail_conditional_break_finally_uses_empty_end_label_barrier() {
24618        let code = compile_exec(
24619            "\
24620def f(tar1, x):
24621    try:
24622        while True:
24623            if x:
24624                break
24625    finally:
24626        tar1.close()
24627",
24628        );
24629        let f = find_code(&code, "f").expect("missing f code");
24630        let instructions: Vec<_> = f
24631            .instructions
24632            .iter()
24633            .filter(|unit| !matches!(unit.op, Instruction::Cache))
24634            .collect();
24635        let close_attr = instructions
24636            .iter()
24637            .position(|unit| match unit.op {
24638                Instruction::LoadAttr { namei } => {
24639                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
24640                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "close"
24641                }
24642                _ => false,
24643            })
24644            .expect("missing close load");
24645        assert!(
24646            matches!(
24647                instructions[close_attr - 1].op,
24648                Instruction::LoadFast { .. }
24649            ),
24650            "CPython leaves an empty while-end label before finalbody when the direct break is the loop body tail; got instructions={instructions:?}",
24651        );
24652    }
24653
24654    #[test]
24655    fn with_break_cleanup_makes_following_jump_artificial() {
24656        let code = compile_exec(
24657            "\
24658def f(self):
24659    while self.returncode is None:
24660        with self._waitpid_lock:
24661            if self.returncode is not None:
24662                break
24663            self.work()
24664    return self.returncode
24665",
24666        );
24667        let f = find_code(&code, "f").expect("missing f code");
24668        let ops_lines: Vec<_> = f
24669            .instructions
24670            .iter()
24671            .zip(&f.locations)
24672            .filter_map(|(unit, (location, _))| {
24673                (!matches!(unit.op, Instruction::Cache)).then_some((unit.op, location.line.get()))
24674            })
24675            .collect();
24676
24677        assert!(
24678            !ops_lines.windows(2).any(|window| {
24679                matches!(
24680                    window,
24681                    [
24682                        (Instruction::Nop, 5),
24683                        (
24684                            Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
24685                            7
24686                        ),
24687                    ]
24688                )
24689            }),
24690            "expected CPython-style artificial jump after with-break cleanup, got ops_lines={ops_lines:?}",
24691        );
24692    }
24693
24694    #[test]
24695    fn while_exit_before_with_cleanup_materializes_anchor_nop() {
24696        let code = compile_exec(
24697            "\
24698def f(selector, self):
24699    with selector:
24700        while selector.get_map():
24701            pass
24702    try:
24703        self.wait()
24704    except Exception:
24705        pass
24706",
24707        );
24708        let f = find_code(&code, "f").expect("missing f code");
24709        let ops_lines: Vec<_> = f
24710            .instructions
24711            .iter()
24712            .zip(&f.locations)
24713            .filter_map(|(unit, (location, _))| {
24714                (!matches!(unit.op, Instruction::Cache)).then_some((unit.op, location.line.get()))
24715            })
24716            .collect();
24717
24718        assert!(
24719            ops_lines.windows(6).any(|window| {
24720                matches!(
24721                    window,
24722                    [
24723                        (Instruction::JumpBackward { .. }, 4),
24724                        (Instruction::Nop, 3),
24725                        (Instruction::LoadConst { .. }, 2),
24726                        (Instruction::LoadConst { .. }, 2),
24727                        (Instruction::LoadConst { .. }, 2),
24728                        (Instruction::Call { .. }, 2),
24729                    ]
24730                )
24731            }),
24732            "expected CPython-style while-exit anchor NOP before with cleanup, got ops_lines={ops_lines:?}",
24733        );
24734    }
24735
24736    #[test]
24737    fn nested_boolop_same_or_prefixes_compile_without_extra_boolop_block() {
24738        let code = compile_exec(
24739            "\
24740def f(c, encodeO, encodeWS):
24741    return (
24742        (c > 127 or utf7_special[c] == 1)
24743        or (encodeWS and (utf7_special[c] == 2))
24744        or (encodeO and (utf7_special[c] == 3))
24745    )
24746",
24747        );
24748        let f = find_code(&code, "f").expect("missing function code");
24749        let pop_jump_if_true_count = f
24750            .instructions
24751            .iter()
24752            .filter(|unit| matches!(unit.op, Instruction::PopJumpIfTrue { .. }))
24753            .count();
24754
24755        assert!(
24756            pop_jump_if_true_count >= 3,
24757            "expected nested boolop prefix path to compile short-circuit jumps, got ops={:?}",
24758            f.instructions
24759                .iter()
24760                .map(|unit| unit.op)
24761                .collect::<Vec<_>>()
24762        );
24763    }
24764
24765    #[test]
24766    fn nested_opposite_boolop_threads_to_fallthrough_like_cpython() {
24767        for source in [
24768            "\
24769def f(a, b, c):
24770    return ((a and b)
24771            or c)
24772",
24773            "\
24774def f(a, b, c):
24775    return ((a or b)
24776            and c)
24777",
24778        ] {
24779            let code = compile_exec(source);
24780            let f = find_code(&code, "f").expect("missing f code");
24781            let jumps: Vec<_> = f
24782                .instructions
24783                .iter()
24784                .filter(|unit| {
24785                    matches!(
24786                        unit.op,
24787                        Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
24788                    )
24789                })
24790                .collect();
24791
24792            assert_eq!(
24793                jumps.len(),
24794                2,
24795                "expected two conditional jumps, got {jumps:?}"
24796            );
24797            assert!(
24798                u8::from(jumps[0].arg) > u8::from(jumps[1].arg),
24799                "expected CPython-style first jump to bypass the opposite short-circuit test, got jumps={jumps:?}"
24800            );
24801        }
24802    }
24803
24804    #[test]
24805    fn loop_or_continue_keeps_boolop_true_edge_to_continue() {
24806        let code = compile_exec(
24807            "\
24808def f(numpy_array, lshape, rshape, litems, fmt, tl):
24809    for _ in range(3):
24810        if numpy_array:
24811            if 0 in lshape or 0 in rshape:
24812                continue
24813            zl = numpy_array_from_structure(litems, fmt, tl)
24814",
24815        );
24816        let f = find_code(&code, "f").expect("missing f code");
24817        let ops: Vec<_> = f
24818            .instructions
24819            .iter()
24820            .map(|unit| unit.op)
24821            .filter(|op| !matches!(op, Instruction::Cache))
24822            .collect();
24823
24824        assert!(
24825            ops.windows(8).any(|window| {
24826                matches!(
24827                    window,
24828                    [
24829                        Instruction::ContainsOp { .. },
24830                        Instruction::PopJumpIfTrue { .. },
24831                        Instruction::NotTaken,
24832                        Instruction::LoadSmallInt { .. },
24833                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
24834                        Instruction::ContainsOp { .. },
24835                        Instruction::PopJumpIfFalse { .. },
24836                        Instruction::NotTaken,
24837                    ]
24838                )
24839            }),
24840            "expected CPython-style `or` continue test to keep first true edge to continue, got ops={ops:?}"
24841        );
24842        assert!(
24843            !ops.windows(5).any(|window| {
24844                matches!(
24845                    window,
24846                    [
24847                        Instruction::ContainsOp { .. },
24848                        Instruction::PopJumpIfFalse { .. },
24849                        Instruction::NotTaken,
24850                        Instruction::JumpBackward { .. }
24851                            | Instruction::JumpBackwardNoInterrupt { .. },
24852                        Instruction::LoadSmallInt { .. },
24853                    ]
24854                )
24855            }),
24856            "unexpected inverted first `or` continue condition before second operand, got ops={ops:?}"
24857        );
24858    }
24859
24860    #[test]
24861    fn nested_and_or_expression_threads_same_false_short_circuit() {
24862        let code = compile_exec(
24863            "\
24864def f(fmt, MEMORYVIEW):
24865    x = len(fmt)
24866    return ((x == 1 or (x == 2 and fmt[0] == '@')) and
24867            fmt[x - 1] in MEMORYVIEW)
24868",
24869        );
24870        let f = find_code(&code, "f").expect("missing f code");
24871        let false_jumps: Vec<_> = f
24872            .instructions
24873            .iter()
24874            .filter(|unit| matches!(unit.op, Instruction::PopJumpIfFalse { .. }))
24875            .collect();
24876
24877        assert!(
24878            false_jumps.len() >= 2,
24879            "expected nested boolop false jumps, got ops={:?}",
24880            f.instructions
24881                .iter()
24882                .map(|unit| unit.op)
24883                .collect::<Vec<_>>()
24884        );
24885        assert!(
24886            u8::from(false_jumps[0].arg) > u8::from(false_jumps[1].arg),
24887            "expected CPython-style same-false short-circuit threading to outer end, got false_jumps={false_jumps:?}"
24888        );
24889    }
24890
24891    #[test]
24892    fn broad_exception_import_keeps_borrow_in_common_tail() {
24893        let code = compile_exec(
24894            "\
24895def f(msg):
24896    if msg.source is not None:
24897        try:
24898            import tracemalloc
24899        except Exception:
24900            suggest_tracemalloc = False
24901            tb = None
24902        suggest_tracemalloc = not tracemalloc.is_tracing()
24903        tb = tracemalloc.get_object_traceback(msg.source)
24904        if tb is not None:
24905            for frame in tb:
24906                pass
24907    return 0
24908",
24909        );
24910        let f = find_code(&code, "f").expect("missing function code");
24911        let import_idx = f
24912            .instructions
24913            .iter()
24914            .position(|unit| matches!(unit.op, Instruction::ImportName { .. }))
24915            .expect("missing IMPORT_NAME");
24916
24917        assert!(
24918            f.instructions[import_idx + 1..]
24919                .iter()
24920                .any(|unit| matches!(unit.op, Instruction::LoadFastBorrow { .. })),
24921            "expected common tail after broad-exception import to keep LOAD_FAST_BORROW, got ops={:?}",
24922            f.instructions
24923                .iter()
24924                .map(|unit| unit.op)
24925                .collect::<Vec<_>>()
24926        );
24927    }
24928
24929    #[test]
24930    fn try_import_return_handler_deopts_common_tail_borrow() {
24931        let code = compile_exec(
24932            "\
24933def f():
24934    try:
24935        import pwd, grp
24936    except ImportError:
24937        return False
24938    if pwd.getpwuid(0)[0] != 'root':
24939        return False
24940    if grp.getgrgid(0)[0] != 'root':
24941        return False
24942    return True
24943",
24944        );
24945        let f = find_code(&code, "f").expect("missing f code");
24946        let ops: Vec<_> = f
24947            .instructions
24948            .iter()
24949            .map(|unit| unit.op)
24950            .filter(|op| !matches!(op, Instruction::Cache))
24951            .collect();
24952
24953        assert!(
24954            !ops.iter()
24955                .any(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
24956            "expected CPython-style LOAD_FAST after protected import common tail, got ops={ops:?}",
24957        );
24958    }
24959
24960    #[test]
24961    fn try_import_return_handler_deopts_later_protected_tail_borrow() {
24962        let code = compile_exec(
24963            "\
24964def f(info_add):
24965    try:
24966        import pwd
24967    except ImportError:
24968        return
24969    import os
24970    uid = os.getuid()
24971    try:
24972        entry = pwd.getpwuid(uid)
24973    except KeyError:
24974        entry = None
24975    info_add(uid, entry)
24976    if entry is None:
24977        return
24978    if hasattr(os, 'getgrouplist'):
24979        groups = os.getgrouplist(entry.pw_name, entry.pw_gid)
24980        groups = ', '.join(map(str, groups))
24981        info_add('os.getgrouplist', groups)
24982",
24983        );
24984        let f = find_code(&code, "f").expect("missing f code");
24985        let ops: Vec<_> = f
24986            .instructions
24987            .iter()
24988            .map(|unit| unit.op)
24989            .filter(|op| !matches!(op, Instruction::Cache))
24990            .collect();
24991        let import_idx = ops
24992            .iter()
24993            .position(|op| matches!(op, Instruction::ImportName { .. }))
24994            .expect("missing IMPORT_NAME");
24995        let protected_tail = &ops[import_idx + 1..];
24996
24997        assert!(
24998            !protected_tail.iter().any(|op| {
24999                matches!(
25000                    op,
25001                    Instruction::LoadFastBorrow { .. }
25002                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
25003                )
25004            }),
25005            "CPython keeps strong LOAD_FAST ops after protected import with return handler, even across later protected tails, got tail={protected_tail:?}"
25006        );
25007    }
25008
25009    #[test]
25010    fn try_import_continue_handler_deopts_loop_tail_borrow() {
25011        let code = compile_exec(
25012            "\
25013def f(size):
25014    pos = 0
25015    while pos < size:
25016        try:
25017            import unicodedata
25018        except ImportError:
25019            continue
25020        if pos < size:
25021            pos += 1
25022    return pos
25023",
25024        );
25025        let f = find_code(&code, "f").expect("missing f code");
25026        let ops: Vec<_> = f
25027            .instructions
25028            .iter()
25029            .map(|unit| unit.op)
25030            .filter(|op| !matches!(op, Instruction::Cache))
25031            .collect();
25032
25033        let import_idx = ops
25034            .iter()
25035            .position(|op| matches!(op, Instruction::ImportName { .. }))
25036            .expect("missing IMPORT_NAME");
25037        let handler_start = ops
25038            .iter()
25039            .position(|op| matches!(op, Instruction::PushExcInfo))
25040            .expect("missing handler entry");
25041        let normal_tail = &ops[import_idx + 1..handler_start];
25042        let return_idx = normal_tail
25043            .iter()
25044            .position(|op| matches!(op, Instruction::ReturnValue))
25045            .unwrap_or(normal_tail.len());
25046        let loop_tail = &normal_tail[..return_idx.saturating_sub(1)];
25047
25048        assert!(
25049            !loop_tail.iter().any(|op| {
25050                matches!(
25051                    op,
25052                    Instruction::LoadFastBorrow { .. }
25053                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
25054                )
25055            }),
25056            "CPython keeps strong LOAD_FAST ops in the loop tail after protected import with continue handler, got tail={loop_tail:?}",
25057        );
25058    }
25059
25060    #[test]
25061    fn try_import_continue_inside_loop_keeps_earlier_loop_body_borrows() {
25062        let code = compile_exec(
25063            r#"
25064def f(s, size, errors):
25065    p = []
25066    pos = 0
25067    while pos < size:
25068        if s[pos] != ord("\\"):
25069            p.append(chr(s[pos]))
25070            pos += 1
25071            continue
25072        if pos < size:
25073            try:
25074                import unicodedata
25075            except ImportError:
25076                errors(pos, size)
25077                continue
25078            if pos < size:
25079                p.append(chr(s[pos]))
25080        pos += 1
25081    return p, pos
25082"#,
25083        );
25084        let f = find_code(&code, "f").expect("missing f code");
25085        let instructions: Vec<_> = f
25086            .instructions
25087            .iter()
25088            .filter(|unit| !matches!(unit.op, Instruction::Cache))
25089            .collect();
25090        let import_idx = instructions
25091            .iter()
25092            .position(|unit| matches!(unit.op, Instruction::ImportName { .. }))
25093            .expect("missing IMPORT_NAME");
25094        let is_pair = |unit: &&CodeUnit, left_name: &str, right_name: &str, borrowed: bool| {
25095            let Some(var_nums) = (match (unit.op, borrowed) {
25096                (Instruction::LoadFastBorrowLoadFastBorrow { var_nums }, true)
25097                | (Instruction::LoadFastLoadFast { var_nums }, false) => Some(var_nums),
25098                _ => None,
25099            }) else {
25100                return false;
25101            };
25102            let arg = OpArg::new(u32::from(u8::from(unit.arg)));
25103            let (left, right) = var_nums.get(arg).indexes();
25104            f.varnames[usize::from(left)] == left_name
25105                && f.varnames[usize::from(right)] == right_name
25106        };
25107        let before_import = &instructions[..import_idx];
25108
25109        assert!(
25110            before_import
25111                .iter()
25112                .any(|unit| is_pair(unit, "s", "pos", true)),
25113            "loop body before protected import should keep CPython-style borrowed s/pos pair, got instructions={instructions:?}"
25114        );
25115        assert!(
25116            !before_import
25117                .iter()
25118                .any(|unit| is_pair(unit, "s", "pos", false)),
25119            "protected import later in the loop should not deopt earlier s/pos pair to strong LOAD_FAST_LOAD_FAST, got instructions={instructions:?}"
25120        );
25121    }
25122
25123    #[test]
25124    fn try_import_pass_else_keeps_borrow() {
25125        let code = compile_exec(
25126            "\
25127def f(self):
25128    try:
25129        from _ctypes import set_conversion_mode
25130    except ImportError:
25131        pass
25132    else:
25133        self.prev_conv_mode = set_conversion_mode('ascii', 'strict')
25134",
25135        );
25136        let f = find_code(&code, "f").expect("missing f code");
25137        let ops: Vec<_> = f
25138            .instructions
25139            .iter()
25140            .map(|unit| unit.op)
25141            .filter(|op| !matches!(op, Instruction::Cache))
25142            .collect();
25143        let handler_start = ops
25144            .iter()
25145            .position(|op| matches!(op, Instruction::PushExcInfo))
25146            .expect("missing handler entry");
25147        let normal_tail = &ops[..handler_start];
25148
25149        assert!(
25150            normal_tail.iter().any(|op| {
25151                matches!(
25152                    op,
25153                    Instruction::LoadFastBorrow { .. }
25154                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
25155                )
25156            }),
25157            "try-import pass/else normal path should keep CPython-style borrows, got tail={normal_tail:?}"
25158        );
25159    }
25160
25161    #[test]
25162    fn try_import_broad_handler_implicit_return_keeps_borrow() {
25163        let code = compile_exec(
25164            "\
25165def f(self, record):
25166    try:
25167        import smtplib
25168        port = self.mailport
25169        if not port:
25170            port = smtplib.SMTP_PORT
25171        smtp = smtplib.SMTP(self.mailhost, port, timeout=self.timeout)
25172        smtp.quit()
25173    except Exception:
25174        self.handleError(record)
25175",
25176        );
25177        let f = find_code(&code, "f").expect("missing f code");
25178        let instructions: Vec<_> = f
25179            .instructions
25180            .iter()
25181            .filter(|unit| !matches!(unit.op, Instruction::Cache))
25182            .collect();
25183        let handler_start = instructions
25184            .iter()
25185            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
25186            .expect("missing handler entry");
25187        let normal_tail = &instructions[..handler_start];
25188        let local_name = |unit: &&CodeUnit| match unit.op {
25189            Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
25190                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
25191                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
25192            }
25193            _ => None,
25194        };
25195
25196        for name in ["self", "smtplib", "smtp"] {
25197            assert!(
25198                normal_tail
25199                    .iter()
25200                    .any(|unit| matches!(unit.op, Instruction::LoadFastBorrow { .. })
25201                        && local_name(unit) == Some(name)),
25202                "broad except handler with implicit return should keep CPython-style borrowed {name} loads, got tail={normal_tail:?}"
25203            );
25204            assert!(
25205                !normal_tail
25206                    .iter()
25207                    .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })
25208                        && local_name(unit) == Some(name)),
25209                "broad except handler with implicit return should not force strong {name} loads, got tail={normal_tail:?}"
25210            );
25211        }
25212    }
25213
25214    #[test]
25215    fn try_import_handler_assignment_resume_tail_keeps_borrow() {
25216        let code = compile_exec(
25217            "\
25218def f():
25219    try:
25220        import subprocess
25221        out = subprocess.check_output(['/usr/bin/lslpp', '-Lqc', 'bos.rte'])
25222    except ImportError:
25223        out = _read_cmd_output('/usr/bin/lslpp -Lqc bos.rte')
25224    out = out.decode('utf-8')
25225    out = out.strip().split(':')
25226    _bd = int(out[-1]) if out[-1] != '' else 9988
25227    return (str(out[2]), _bd)
25228",
25229        );
25230        let f = find_code(&code, "f").expect("missing f code");
25231        let instructions: Vec<_> = f
25232            .instructions
25233            .iter()
25234            .filter(|unit| !matches!(unit.op, Instruction::Cache))
25235            .collect();
25236        let handler_start = instructions
25237            .iter()
25238            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
25239            .expect("missing handler entry");
25240        let normal_tail = &instructions[..handler_start];
25241        let out_idx = f
25242            .varnames
25243            .iter()
25244            .position(|name| name == "out")
25245            .expect("missing out local");
25246        let load_out_is = |unit: &&CodeUnit, borrowed: bool| match (unit.op, borrowed) {
25247            (Instruction::LoadFastBorrow { var_num }, true)
25248            | (Instruction::LoadFast { var_num }, false) => {
25249                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
25250                usize::from(var_num.get(arg)) == out_idx
25251            }
25252            _ => false,
25253        };
25254
25255        assert!(
25256            normal_tail.iter().any(|unit| load_out_is(unit, true)),
25257            "handler assignment resume tail should keep CPython-style borrowed out loads, got tail={normal_tail:?}"
25258        );
25259        assert!(
25260            !normal_tail.iter().any(|unit| load_out_is(unit, false)),
25261            "handler assignment resume tail should not force strong out loads, got tail={normal_tail:?}"
25262        );
25263    }
25264
25265    #[test]
25266    fn empty_fallthrough_handler_assignment_tail_keeps_borrows() {
25267        let code = compile_exec(
25268            "\
25269def f(value):
25270    obs_local_part = ObsLocalPart()
25271    try:
25272        token, value = get_word(value)
25273    except HeaderParseError:
25274        if value[0] not in CFWS_LEADER:
25275            raise
25276        token, value = get_cfws(value)
25277    obs_local_part.append(token)
25278    return obs_local_part, value
25279",
25280        );
25281        let f = find_code(&code, "f").expect("missing f code");
25282        let ops: Vec<_> = f
25283            .instructions
25284            .iter()
25285            .filter(|unit| !matches!(unit.op, Instruction::Cache))
25286            .collect();
25287        let handler_start = ops
25288            .iter()
25289            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
25290            .expect("missing handler entry");
25291        let normal_path = &ops[..handler_start];
25292        let load_name = |unit: &&CodeUnit, name: &str, borrowed: bool| {
25293            let arg = OpArg::new(u32::from(u8::from(unit.arg)));
25294            match (unit.op, borrowed) {
25295                (Instruction::LoadFastBorrow { var_num }, true)
25296                | (Instruction::LoadFast { var_num }, false) => {
25297                    f.varnames[usize::from(var_num.get(arg))].as_str() == name
25298                }
25299                _ => false,
25300            }
25301        };
25302
25303        for name in ["obs_local_part", "token"] {
25304            assert!(
25305                normal_path.iter().any(|unit| load_name(unit, name, true)),
25306                "handler assignment tail should keep CPython-style borrowed {name} loads, got path={normal_path:?}"
25307            );
25308            assert!(
25309                !normal_path.iter().any(|unit| load_name(unit, name, false)),
25310                "handler assignment tail should not force strong {name}, got path={normal_path:?}"
25311            );
25312        }
25313    }
25314
25315    #[test]
25316    fn protected_store_of_preinitialized_local_keeps_return_borrow() {
25317        let code = compile_exec(
25318            "\
25319def f(obj):
25320    maybe_routine = obj
25321    try:
25322        maybe_routine = inspect.unwrap(maybe_routine)
25323    except ValueError:
25324        pass
25325    return inspect.isroutine(maybe_routine)
25326",
25327        );
25328        let f = find_code(&code, "f").expect("missing f code");
25329        let ops: Vec<_> = f
25330            .instructions
25331            .iter()
25332            .filter(|unit| !matches!(unit.op, Instruction::Cache))
25333            .collect();
25334        let handler_start = ops
25335            .iter()
25336            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
25337            .expect("missing handler entry");
25338        let normal_path = &ops[..handler_start];
25339        let maybe_routine_idx = f
25340            .varnames
25341            .iter()
25342            .position(|name| name == "maybe_routine")
25343            .expect("missing maybe_routine local");
25344        let loads_maybe_routine = |unit: &&CodeUnit, borrowed: bool| match (unit.op, borrowed) {
25345            (Instruction::LoadFastBorrow { var_num }, true)
25346            | (Instruction::LoadFast { var_num }, false) => {
25347                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
25348                usize::from(var_num.get(arg)) == maybe_routine_idx
25349            }
25350            _ => false,
25351        };
25352
25353        assert!(
25354            normal_path
25355                .iter()
25356                .any(|unit| loads_maybe_routine(unit, true)),
25357            "preinitialized protected-store tail should keep CPython-style borrowed local, got path={normal_path:?}"
25358        );
25359        assert!(
25360            !normal_path
25361                .iter()
25362                .any(|unit| loads_maybe_routine(unit, false)),
25363            "preinitialized protected-store tail should not force strong local, got path={normal_path:?}"
25364        );
25365    }
25366
25367    #[test]
25368    fn protected_attr_direct_return_keeps_borrow() {
25369        let code = compile_exec(
25370            "\
25371def f(obj):
25372    try:
25373        x = 1
25374    except ValueError:
25375        return False
25376    return obj.values()
25377",
25378        );
25379        let f = find_code(&code, "f").expect("missing function code");
25380        let ops: Vec<_> = f
25381            .instructions
25382            .iter()
25383            .map(|unit| unit.op)
25384            .filter(|op| !matches!(op, Instruction::Cache))
25385            .collect();
25386        let handler_start = ops
25387            .iter()
25388            .position(|op| matches!(op, Instruction::PushExcInfo))
25389            .expect("missing handler entry");
25390        let protected_tail = &ops[..handler_start];
25391
25392        assert!(
25393            protected_tail.windows(4).any(|window| {
25394                matches!(
25395                    window,
25396                    [
25397                        Instruction::LoadFastBorrow { .. },
25398                        Instruction::LoadAttr { .. },
25399                        Instruction::Call { .. },
25400                        Instruction::ReturnValue,
25401                    ]
25402                )
25403            }),
25404            "expected protected direct attr-call return to keep LOAD_FAST_BORROW, got tail={protected_tail:?}"
25405        );
25406    }
25407
25408    #[test]
25409    fn protected_store_normal_tail_uses_strong_loads() {
25410        let code = compile_exec(
25411            "\
25412def f(tarfile, tarinfo, self):
25413    try:
25414        filtered = tarfile.tar_filter(tarinfo, '')
25415    except UnicodeEncodeError:
25416        return None
25417    self.assertIs(filtered.name, tarinfo.name)
25418    return filtered
25419",
25420        );
25421        let f = find_code(&code, "f").expect("missing function code");
25422        let ops: Vec<_> = f
25423            .instructions
25424            .iter()
25425            .map(|unit| unit.op)
25426            .filter(|op| !matches!(op, Instruction::Cache))
25427            .collect();
25428        let filtered_store = ops
25429            .iter()
25430            .position(|op| matches!(op, Instruction::StoreFast { .. }))
25431            .expect("missing filtered store");
25432        let handler_start = ops
25433            .iter()
25434            .position(|op| matches!(op, Instruction::PushExcInfo))
25435            .expect("missing handler entry");
25436        let normal_tail = &ops[filtered_store + 1..handler_start];
25437
25438        assert!(
25439            !normal_tail.iter().any(|op| matches!(
25440                op,
25441                Instruction::LoadFastBorrow { .. }
25442                    | Instruction::LoadFastBorrowLoadFastBorrow { .. }
25443            )),
25444            "expected CPython-style strong LOAD_FAST in protected store normal tail, got tail={normal_tail:?}",
25445        );
25446    }
25447
25448    #[test]
25449    fn protected_subscript_store_normal_tail_uses_strong_loads() {
25450        let code = compile_exec(
25451            "\
25452def f(self, d, option, fallback):
25453    try:
25454        value = d[option]
25455    except KeyError:
25456        return fallback
25457    return self.convert(value, option)
25458",
25459        );
25460        let f = find_code(&code, "f").expect("missing function code");
25461        let ops: Vec<_> = f
25462            .instructions
25463            .iter()
25464            .map(|unit| unit.op)
25465            .filter(|op| !matches!(op, Instruction::Cache))
25466            .collect();
25467        let value_store = ops
25468            .iter()
25469            .position(|op| matches!(op, Instruction::StoreFast { .. }))
25470            .expect("missing value store");
25471        let handler_start = ops
25472            .iter()
25473            .position(|op| matches!(op, Instruction::PushExcInfo))
25474            .expect("missing handler entry");
25475        let normal_tail = &ops[value_store + 1..handler_start];
25476
25477        assert!(
25478            !normal_tail.iter().any(|op| matches!(
25479                op,
25480                Instruction::LoadFastBorrow { .. }
25481                    | Instruction::LoadFastBorrowLoadFastBorrow { .. }
25482            )),
25483            "expected CPython-style strong LOAD_FAST after protected subscript store, got tail={normal_tail:?}",
25484        );
25485    }
25486
25487    #[test]
25488    fn protected_call_arm_final_store_return_uses_strong_load() {
25489        let code = compile_exec(
25490            "\
25491def f(self, action, default_metavar):
25492    get_metavar = self._metavar_formatter(action, default_metavar)
25493    if action.nargs is None:
25494        result = '%s' % get_metavar(1)
25495    elif action.nargs == OPTIONAL:
25496        result = '[%s]' % get_metavar(1)
25497    elif action.nargs == ZERO_OR_MORE:
25498        metavar = get_metavar(1)
25499        if len(metavar) == 2:
25500            result = '[%s [%s ...]]' % metavar
25501        else:
25502            result = '[%s ...]' % metavar
25503    elif action.nargs == ONE_OR_MORE:
25504        result = '%s [%s ...]' % get_metavar(2)
25505    elif action.nargs == REMAINDER:
25506        result = '...'
25507    elif action.nargs == PARSER:
25508        result = '%s ...' % get_metavar(1)
25509    elif action.nargs == SUPPRESS:
25510        result = ''
25511    else:
25512        try:
25513            formats = ['%s' for _ in range(action.nargs)]
25514        except TypeError:
25515            raise ValueError(\"invalid nargs value\") from None
25516        result = ' '.join(formats) % get_metavar(action.nargs)
25517    return result
25518",
25519        );
25520        let f = find_code(&code, "f").expect("missing function code");
25521        let ops: Vec<_> = f
25522            .instructions
25523            .iter()
25524            .filter(|unit| !matches!(unit.op, Instruction::Cache))
25525            .collect();
25526        let modulo = ops
25527            .iter()
25528            .position(|unit| {
25529                matches!(
25530                    unit.op,
25531                    Instruction::BinaryOp { op }
25532                        if op.get(OpArg::new(u32::from(u8::from(unit.arg))))
25533                            == BinaryOperator::Remainder
25534                )
25535            })
25536            .expect("missing final format modulo");
25537        let tail = &ops[modulo..];
25538        assert!(
25539            tail.windows(3).any(|window| {
25540                matches!(
25541                    window,
25542                    [
25543                        bytecode::CodeUnit {
25544                            op: Instruction::StoreFast { .. },
25545                            ..
25546                        },
25547                        bytecode::CodeUnit {
25548                            op: Instruction::LoadFast { .. },
25549                            ..
25550                        },
25551                        bytecode::CodeUnit {
25552                            op: Instruction::ReturnValue,
25553                            ..
25554                        },
25555                    ]
25556                )
25557            }),
25558            "protected call arm final store-return should keep CPython-style strong LOAD_FAST, got tail={tail:?}"
25559        );
25560    }
25561
25562    #[test]
25563    fn protected_store_try_else_tail_keeps_borrowed_loads() {
25564        let code = compile_exec(
25565            "\
25566def f(value):
25567    message_id = MessageID()
25568    try:
25569        token, value = get_msg_id(value)
25570        message_id.append(token)
25571    except HeaderParseError as ex:
25572        token = get_unstructured(value)
25573        message_id = InvalidMessageID(token)
25574        message_id.defects.append(InvalidHeaderDefect('Invalid msg-id: {!r}'.format(ex)))
25575    else:
25576        if value:
25577            message_id.defects.append(InvalidHeaderDefect('Unexpected {!r}'.format(value)))
25578    return message_id
25579",
25580        );
25581        let f = find_code(&code, "f").expect("missing function code");
25582        let ops: Vec<_> = f
25583            .instructions
25584            .iter()
25585            .filter(|unit| !matches!(unit.op, Instruction::Cache))
25586            .collect();
25587        let handler_start = ops
25588            .iter()
25589            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
25590            .expect("missing handler entry");
25591        let normal_tail = &ops[..handler_start];
25592
25593        assert!(
25594            normal_tail.windows(3).any(|window| {
25595                matches!(
25596                    window,
25597                    [
25598                        CodeUnit {
25599                            op: Instruction::LoadFastBorrow { .. },
25600                            ..
25601                        },
25602                        CodeUnit {
25603                            op: Instruction::ToBool,
25604                            ..
25605                        },
25606                        CodeUnit {
25607                            op: Instruction::PopJumpIfFalse { .. }
25608                                | Instruction::PopJumpIfTrue { .. },
25609                            ..
25610                        },
25611                    ]
25612                )
25613            }),
25614            "try/except/else bool guard should keep CPython-style borrowed load, got tail={normal_tail:?}",
25615        );
25616
25617        let defects_idx = normal_tail
25618            .iter()
25619            .position(|unit| match unit.op {
25620                Instruction::LoadAttr { namei } => {
25621                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
25622                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "defects"
25623                }
25624                _ => false,
25625            })
25626            .expect("missing defects LOAD_ATTR in else tail");
25627        assert!(
25628            matches!(
25629                normal_tail[defects_idx - 1].op,
25630                Instruction::LoadFastBorrow { .. }
25631            ),
25632            "try/except/else method receiver should stay borrowed like CPython, got tail={normal_tail:?}",
25633        );
25634
25635        let format_idx = normal_tail
25636            .iter()
25637            .rposition(|unit| match unit.op {
25638                Instruction::LoadAttr { namei } => {
25639                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
25640                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "format"
25641                }
25642                _ => false,
25643            })
25644            .expect("missing format LOAD_ATTR in else tail");
25645        assert!(
25646            matches!(
25647                normal_tail[format_idx + 1].op,
25648                Instruction::LoadFastBorrow { .. }
25649            ),
25650            "try/except/else format argument should stay borrowed like CPython, got tail={normal_tail:?}",
25651        );
25652    }
25653
25654    #[test]
25655    fn nested_try_except_common_tail_uses_strong_loads() {
25656        let code = compile_exec(
25657            "\
25658def f(value):
25659    address = Address()
25660    try:
25661        token, value = get_group(value)
25662    except HeaderParseError:
25663        try:
25664            token, value = get_mailbox(value)
25665        except HeaderParseError:
25666            raise HeaderParseError('expected {}'.format(value))
25667    address.append(token)
25668    return address, value
25669",
25670        );
25671        let f = find_code(&code, "f").expect("missing function code");
25672        let ops: Vec<_> = f
25673            .instructions
25674            .iter()
25675            .map(|unit| unit.op)
25676            .filter(|op| !matches!(op, Instruction::Cache))
25677            .collect();
25678
25679        assert!(
25680            ops.windows(6).any(|window| {
25681                matches!(
25682                    window,
25683                    [
25684                        Instruction::LoadFast { .. },
25685                        Instruction::LoadAttr { .. },
25686                        Instruction::LoadFast { .. },
25687                        Instruction::Call { .. },
25688                        Instruction::PopTop,
25689                        Instruction::LoadFastLoadFast { .. },
25690                    ]
25691                )
25692            }),
25693            "nested try/except common tail should use CPython-style strong loads, got ops={ops:?}"
25694        );
25695    }
25696
25697    #[test]
25698    fn nested_try_except_branch_tail_with_following_try_uses_strong_loads() {
25699        let code = compile_exec(
25700            r#"
25701def f(value):
25702    msg_id = MsgID()
25703    try:
25704        token, value = get_dot_atom_text(value)
25705    except HeaderParseError:
25706        try:
25707            token, value = get_obs_local_part(value)
25708            msg_id.defects.append(ObsoleteHeaderDefect("obsolete id-left in msg-id"))
25709        except HeaderParseError:
25710            raise HeaderParseError("expected {}".format(value))
25711    msg_id.append(token)
25712    if not value or value[0] != "@":
25713        msg_id.defects.append(InvalidHeaderDefect("msg-id with no id-right"))
25714        if value and value[0] == ">":
25715            msg_id.append(ValueTerminal(">", "msg-id-end"))
25716            value = value[1:]
25717        return msg_id, value
25718    msg_id.append(ValueTerminal("@", "address-at-symbol"))
25719    value = value[1:]
25720    try:
25721        token, value = get_dot_atom_text(value)
25722    except HeaderParseError:
25723        pass
25724    return msg_id, value
25725"#,
25726        );
25727        let f = find_code(&code, "f").expect("missing function code");
25728        let ops: Vec<_> = f
25729            .instructions
25730            .iter()
25731            .map(|unit| unit.op)
25732            .filter(|op| !matches!(op, Instruction::Cache))
25733            .collect();
25734
25735        assert!(
25736            ops.windows(16).any(|window| {
25737                matches!(
25738                    window,
25739                    [
25740                        Instruction::ReturnValue,
25741                        Instruction::LoadFast { .. },
25742                        Instruction::LoadAttr { .. },
25743                        Instruction::LoadGlobal { .. },
25744                        Instruction::LoadConst { .. },
25745                        Instruction::LoadConst { .. },
25746                        Instruction::Call { .. },
25747                        Instruction::Call { .. },
25748                        Instruction::PopTop,
25749                        Instruction::LoadFast { .. },
25750                        Instruction::LoadConst { .. },
25751                        Instruction::BinaryOp { .. },
25752                        Instruction::StoreFast { .. },
25753                        Instruction::Nop,
25754                        Instruction::LoadGlobal { .. },
25755                        Instruction::LoadFast { .. },
25756                    ]
25757                )
25758            }),
25759            "nested try branch tail before a following try should use CPython-style strong loads, got ops={ops:?}"
25760        );
25761    }
25762
25763    #[test]
25764    fn nested_try_store_subscr_following_try_tail_uses_strong_loads() {
25765        let code = compile_exec(
25766            r#"
25767def f(value):
25768    local_part = LocalPart()
25769    try:
25770        token, value = get_dot_atom(value)
25771    except HeaderParseError:
25772        try:
25773            token, value = get_word(value)
25774        except HeaderParseError:
25775            token = TokenList()
25776    if value:
25777        obs_local_part, value = get_obs_local_part(str(local_part) + value)
25778        if obs_local_part.token_type == "invalid-obs-local-part":
25779            local_part.defects.append(InvalidHeaderDefect("invalid"))
25780        else:
25781            local_part.defects.append(ObsoleteHeaderDefect("obsolete"))
25782        local_part[0] = obs_local_part
25783    try:
25784        local_part.value.encode("ascii")
25785    except UnicodeEncodeError:
25786        local_part.defects.append(NonASCIILocalPartDefect("non-ascii"))
25787    return local_part, value
25788"#,
25789        );
25790        let f = find_code(&code, "f").expect("missing function code");
25791        let ops: Vec<_> = f
25792            .instructions
25793            .iter()
25794            .map(|unit| unit.op)
25795            .filter(|op| !matches!(op, Instruction::Cache))
25796            .collect();
25797
25798        assert!(
25799            ops.windows(10).any(|window| {
25800                matches!(
25801                    window,
25802                    [
25803                        Instruction::StoreSubscr,
25804                        Instruction::Nop,
25805                        Instruction::LoadFast { .. },
25806                        Instruction::LoadAttr { .. },
25807                        Instruction::LoadAttr { .. },
25808                        Instruction::LoadConst { .. },
25809                        Instruction::Call { .. },
25810                        Instruction::PopTop,
25811                        Instruction::LoadFastLoadFast { .. },
25812                        Instruction::BuildTuple { .. },
25813                    ]
25814                )
25815            }),
25816            "nested try STORE_SUBSCR tail before a following try should use CPython-style strong loads, got ops={ops:?}"
25817        );
25818    }
25819
25820    #[test]
25821    fn resuming_except_in_loop_keeps_post_try_store_tail_borrowed() {
25822        let code = compile_exec(
25823            "\
25824def f(part, lines, maxlen, encoding):
25825    for name, value in part.params:
25826        charset = encoding
25827        error_handler = 'strict'
25828        try:
25829            value.encode(encoding)
25830            encoding_required = False
25831        except UnicodeEncodeError:
25832            encoding_required = True
25833            charset = 'utf-8'
25834        if encoding_required:
25835            encoded_value = quote(value, safe='', errors=error_handler)
25836            tstr = \"{}*={}''{}\".format(name, charset, encoded_value)
25837        else:
25838            tstr = '{}={}'.format(name, quote_string(value))
25839        if len(lines[-1]) + len(tstr) + 1 < maxlen:
25840            lines[-1] = lines[-1] + ' ' + tstr
25841            continue
25842",
25843        );
25844        let f = find_code(&code, "f").expect("missing function code");
25845        let ops: Vec<_> = f
25846            .instructions
25847            .iter()
25848            .map(|unit| unit.op)
25849            .filter(|op| !matches!(op, Instruction::Cache))
25850            .collect();
25851
25852        assert!(
25853            ops.windows(3).any(|window| {
25854                matches!(
25855                    window,
25856                    [
25857                        Instruction::LoadFastBorrow { .. },
25858                        Instruction::ToBool,
25859                        Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. },
25860                    ]
25861                )
25862            }),
25863            "resuming except handler should keep CPython-style borrowed bool guard, got ops={ops:?}"
25864        );
25865        assert!(
25866            ops.windows(3).any(|window| {
25867                matches!(
25868                    window,
25869                    [
25870                        Instruction::LoadFastBorrow { .. },
25871                        Instruction::LoadConst { .. },
25872                        Instruction::StoreSubscr,
25873                    ]
25874                )
25875            }),
25876            "resuming except handler should not deopt the post-try STORE_SUBSCR tail, got ops={ops:?}"
25877        );
25878    }
25879
25880    #[test]
25881    fn handler_resume_loop_latch_method_call_uses_strong_loads() {
25882        let code = compile_exec(
25883            "\
25884def f(phrase, value):
25885    while value and value[0] not in PHRASE_ENDS:
25886        if value[0] == '.':
25887            phrase.append(DOT)
25888            phrase.defects.append(ObsoleteHeaderDefect('period in phrase'))
25889            value = value[1:]
25890        else:
25891            try:
25892                token, value = get_word(value)
25893            except HeaderParseError:
25894                if value[0] in CFWS_LEADER:
25895                    token, value = get_cfws(value)
25896                    phrase.defects.append(ObsoleteHeaderDefect('comment found without atom'))
25897                else:
25898                    raise
25899            phrase.append(token)
25900",
25901        );
25902        let f = find_code(&code, "f").expect("missing function code");
25903        let ops: Vec<_> = f
25904            .instructions
25905            .iter()
25906            .map(|unit| unit.op)
25907            .filter(|op| !matches!(op, Instruction::Cache))
25908            .collect();
25909
25910        assert!(
25911            ops.windows(6).any(|window| {
25912                matches!(
25913                    window,
25914                    [
25915                        Instruction::LoadFast { .. },
25916                        Instruction::LoadAttr { .. },
25917                        Instruction::LoadFast { .. },
25918                        Instruction::Call { .. },
25919                        Instruction::PopTop,
25920                        Instruction::JumpBackward { .. },
25921                    ]
25922                )
25923            }) || ops.windows(7).any(|window| {
25924                matches!(
25925                    window,
25926                    [
25927                        Instruction::LoadFast { .. },
25928                        Instruction::LoadAttr { .. },
25929                        Instruction::LoadFast { .. },
25930                        Instruction::Call { .. },
25931                        Instruction::PopTop,
25932                        Instruction::JumpBackward { .. },
25933                        Instruction::LoadConst { .. },
25934                    ]
25935                )
25936            }),
25937            "exception-handler resume loop latch should match CPython's strong method-call loads, got ops={ops:?}"
25938        );
25939    }
25940
25941    #[test]
25942    fn single_handler_multiple_resume_branches_keep_post_try_tail_borrowed() {
25943        let code = compile_exec(
25944            "\
25945def f(part, lines, maxlen, encoding):
25946    for name, value in part.params:
25947        charset = encoding
25948        error_handler = 'strict'
25949        try:
25950            value.encode(encoding)
25951            encoding_required = False
25952        except UnicodeEncodeError:
25953            encoding_required = True
25954            if utils._has_surrogates(value):
25955                charset = 'unknown-8bit'
25956                error_handler = 'surrogateescape'
25957            else:
25958                charset = 'utf-8'
25959        if encoding_required:
25960            encoded_value = quote(value, safe='', errors=error_handler)
25961            tstr = \"{}*={}''{}\".format(name, charset, encoded_value)
25962        else:
25963            tstr = '{}={}'.format(name, quote_string(value))
25964        if len(lines[-1]) + len(tstr) + 1 < maxlen:
25965            lines[-1] = lines[-1] + ' ' + tstr
25966            continue
25967",
25968        );
25969        let f = find_code(&code, "f").expect("missing function code");
25970        let ops: Vec<_> = f
25971            .instructions
25972            .iter()
25973            .map(|unit| unit.op)
25974            .filter(|op| !matches!(op, Instruction::Cache))
25975            .collect();
25976
25977        assert!(
25978            ops.windows(3).any(|window| {
25979                matches!(
25980                    window,
25981                    [
25982                        Instruction::LoadFastBorrow { .. },
25983                        Instruction::ToBool,
25984                        Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. },
25985                    ]
25986                )
25987            }),
25988            "single except handler with multiple resume branches should keep the post-try bool guard borrowed, got ops={ops:?}"
25989        );
25990        assert!(
25991            ops.windows(3).any(|window| {
25992                matches!(
25993                    window,
25994                    [
25995                        Instruction::LoadFastBorrow { .. },
25996                        Instruction::LoadConst { .. },
25997                        Instruction::StoreSubscr,
25998                    ]
25999                )
26000            }),
26001            "single except handler with multiple resume branches should keep the post-try STORE_SUBSCR tail borrowed, got ops={ops:?}"
26002        );
26003    }
26004
26005    #[test]
26006    fn nested_exception_handler_resume_update_tail_uses_strong_load() {
26007        let code = compile_exec(
26008            "\
26009def f(inpos, size, g, replacement):
26010    while inpos < size:
26011        try:
26012            g()
26013        except KeyError:
26014            try:
26015                for y in replacement:
26016                    g(y)
26017            except KeyError:
26018                raise ValueError(inpos)
26019        inpos += 1
26020    return inpos
26021",
26022        );
26023        let f = find_code(&code, "f").expect("missing function code");
26024        let ops: Vec<_> = f
26025            .instructions
26026            .iter()
26027            .filter(|unit| !matches!(unit.op, Instruction::Cache))
26028            .collect();
26029        let arg = |unit: &&bytecode::CodeUnit| OpArg::new(u32::from(u8::from(unit.arg)));
26030        let is_inpos_load = |unit: &&bytecode::CodeUnit| match unit.op {
26031            Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
26032                f.varnames[usize::from(var_num.get(arg(unit)))].as_str() == "inpos"
26033            }
26034            _ => false,
26035        };
26036        let is_inpos_store = |unit: &&bytecode::CodeUnit| match unit.op {
26037            Instruction::StoreFast { var_num } => {
26038                f.varnames[usize::from(var_num.get(arg(unit)))].as_str() == "inpos"
26039            }
26040            _ => false,
26041        };
26042        let update = ops
26043            .windows(4)
26044            .find(|window| {
26045                is_inpos_load(&window[0])
26046                    && matches!(
26047                        window[1].op,
26048                        Instruction::LoadSmallInt { i } if i.get(arg(&window[1])) == 1
26049                    )
26050                    && matches!(
26051                        window[2].op,
26052                        Instruction::BinaryOp { op }
26053                            if op.get(arg(&window[2])) == BinaryOperator::InplaceAdd
26054                    )
26055                    && is_inpos_store(&window[3])
26056            })
26057            .expect("missing inpos += 1 update");
26058
26059        assert!(
26060            matches!(update[0].op, Instruction::LoadFast { .. }),
26061            "CPython keeps a strong LOAD_FAST for nested-handler resumed inplace update, got update={update:?}"
26062        );
26063    }
26064
26065    #[test]
26066    fn protected_store_finally_cleanup_keeps_borrow_tail() {
26067        let code = compile_exec(
26068            "\
26069def f(re, f):
26070    try:
26071        try:
26072            m = re.search('x', f.read())
26073        finally:
26074            f.close()
26075        if m is not None:
26076            return m.group(1)
26077    except OSError:
26078        pass
26079    return None
26080",
26081        );
26082        let f = find_code(&code, "f").expect("missing function code");
26083        let ops: Vec<_> = f
26084            .instructions
26085            .iter()
26086            .filter(|unit| !matches!(unit.op, Instruction::Cache))
26087            .collect();
26088        let is_m_borrow = |unit: &bytecode::CodeUnit| match unit.op {
26089            Instruction::LoadFastBorrow { var_num } => {
26090                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
26091                f.varnames[usize::from(var_num.get(arg))].as_str() == "m"
26092            }
26093            _ => false,
26094        };
26095
26096        assert!(
26097            ops.windows(3).any(|window| {
26098                is_m_borrow(window[0])
26099                    && matches!(window[1].op, Instruction::PopJumpIfNone { .. })
26100                    && matches!(window[2].op, Instruction::NotTaken)
26101            }) && ops.windows(2).any(|window| {
26102                is_m_borrow(window[0]) && matches!(window[1].op, Instruction::LoadAttr { .. })
26103            }),
26104            "finally cleanup RERAISE should not make the outer except deopt the normal m tail, got ops={ops:?}"
26105        );
26106    }
26107
26108    #[test]
26109    fn try_else_finally_cleanup_keeps_borrow_tail() {
26110        let code = compile_exec(
26111            "\
26112def f(re, open):
26113    global _SYSTEM_VERSION
26114    if _SYSTEM_VERSION is None:
26115        _SYSTEM_VERSION = ''
26116        try:
26117            f = open('/System/Library/CoreServices/SystemVersion.plist', encoding='utf-8')
26118        except OSError:
26119            pass
26120        else:
26121            try:
26122                m = re.search('x', f.read())
26123            finally:
26124                f.close()
26125            if m is not None:
26126                _SYSTEM_VERSION = '.'.join(m.group(1).split('.')[:2])
26127    return _SYSTEM_VERSION
26128",
26129        );
26130        let f = find_code(&code, "f").expect("missing function code");
26131        let ops: Vec<_> = f
26132            .instructions
26133            .iter()
26134            .filter(|unit| !matches!(unit.op, Instruction::Cache))
26135            .collect();
26136        let is_m_borrow = |unit: &bytecode::CodeUnit| match unit.op {
26137            Instruction::LoadFastBorrow { var_num } => {
26138                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
26139                f.varnames[usize::from(var_num.get(arg))].as_str() == "m"
26140            }
26141            _ => false,
26142        };
26143
26144        assert!(
26145            ops.windows(3).any(|window| {
26146                is_m_borrow(window[0])
26147                    && matches!(window[1].op, Instruction::PopJumpIfNone { .. })
26148                    && matches!(window[2].op, Instruction::NotTaken)
26149            }) && ops.windows(2).any(|window| {
26150                is_m_borrow(window[0]) && matches!(window[1].op, Instruction::LoadAttr { .. })
26151            }),
26152            "try/else finally cleanup should keep CPython-style borrowed m tail, got ops={ops:?}"
26153        );
26154    }
26155
26156    #[test]
26157    fn typed_terminal_attr_store_deopts_later_protected_store_subscr() {
26158        let code = compile_exec(
26159            "\
26160def f(instance, self, _NOT_FOUND):
26161    try:
26162        cache = instance.__dict__
26163    except AttributeError:
26164        raise TypeError('missing dict') from None
26165    val = cache.get(self.attrname, _NOT_FOUND)
26166    if val is _NOT_FOUND:
26167        val = self.func(instance)
26168        try:
26169            cache[self.attrname] = val
26170        except TypeError:
26171            raise TypeError('bad cache') from None
26172    return val
26173",
26174        );
26175        let f = find_code(&code, "f").expect("missing function code");
26176        let ops: Vec<_> = f
26177            .instructions
26178            .iter()
26179            .map(|unit| unit.op)
26180            .filter(|op| !matches!(op, Instruction::Cache))
26181            .collect();
26182        let store_subscr = ops
26183            .iter()
26184            .position(|op| matches!(op, Instruction::StoreSubscr))
26185            .expect("missing STORE_SUBSCR");
26186        let window = &ops[store_subscr.saturating_sub(3)..=store_subscr];
26187
26188        assert!(
26189            matches!(
26190                window,
26191                [
26192                    Instruction::LoadFastLoadFast { .. },
26193                    Instruction::LoadFast { .. },
26194                    Instruction::LoadAttr { .. },
26195                    Instruction::StoreSubscr,
26196                ]
26197            ),
26198            "CPython keeps strong loads before protected STORE_SUBSCR after a typed terminal attr-store try, got window={window:?}; ops={ops:?}"
26199        );
26200    }
26201
26202    #[test]
26203    fn generator_protected_store_subscr_tail_uses_strong_loads() {
26204        let code = compile_exec(
26205            "\
26206def f(names, modules):
26207    for name in names:
26208        try:
26209            mod = __import__(name)
26210        except ImportError:
26211            continue
26212        modules[name] = mod
26213        yield mod
26214",
26215        );
26216        let f = find_code(&code, "f").expect("missing function code");
26217        let ops: Vec<_> = f
26218            .instructions
26219            .iter()
26220            .map(|unit| unit.op)
26221            .filter(|op| !matches!(op, Instruction::Cache))
26222            .collect();
26223        let store_subscr = ops
26224            .iter()
26225            .position(|op| matches!(op, Instruction::StoreSubscr))
26226            .expect("missing STORE_SUBSCR");
26227        let window = &ops[store_subscr.saturating_sub(2)..(store_subscr + 3).min(ops.len())];
26228
26229        assert!(
26230            matches!(
26231                window,
26232                [
26233                    Instruction::LoadFastLoadFast { .. },
26234                    Instruction::LoadFast { .. },
26235                    Instruction::StoreSubscr,
26236                    Instruction::LoadFast { .. },
26237                    Instruction::YieldValue { .. },
26238                    ..
26239                ]
26240            ),
26241            "expected CPython-style strong LOAD_FAST around protected STORE_SUBSCR generator tail, got {window:?}"
26242        );
26243    }
26244
26245    #[test]
26246    fn protected_call_function_ex_store_tail_uses_strong_loads() {
26247        let code = compile_exec(
26248            "\
26249def f(func, *args):
26250    try:
26251        result = func(*args)
26252    except Exception:
26253        return None
26254    return type(result)
26255",
26256        );
26257        let f = find_code(&code, "f").expect("missing function code");
26258        let ops: Vec<_> = f
26259            .instructions
26260            .iter()
26261            .map(|unit| unit.op)
26262            .filter(|op| !matches!(op, Instruction::Cache))
26263            .collect();
26264        let tail_call = ops
26265            .iter()
26266            .rposition(|op| matches!(op, Instruction::Call { .. }))
26267            .expect("missing tail CALL");
26268        let result_store = ops[..tail_call]
26269            .iter()
26270            .rposition(|op| matches!(op, Instruction::StoreFast { .. }))
26271            .expect("missing protected result STORE_FAST");
26272        let tail = &ops[result_store + 1..tail_call];
26273
26274        assert!(
26275            tail.iter()
26276                .any(|op| matches!(op, Instruction::LoadFast { .. })),
26277            "expected CPython-style strong LOAD_FAST after protected CALL_FUNCTION_EX store, got ops={ops:?}",
26278        );
26279        assert!(
26280            !tail
26281                .iter()
26282                .any(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
26283            "protected CALL_FUNCTION_EX store tail should not borrow result, got ops={ops:?}",
26284        );
26285    }
26286
26287    #[test]
26288    fn protected_attr_subscript_tail_uses_strong_load_fast() {
26289        let code = compile_exec(
26290            "\
26291def f(obj, idx):
26292    try:
26293        x = 1
26294    except ValueError:
26295        return False
26296    return obj.__closure__[idx]
26297",
26298        );
26299        let f = find_code(&code, "f").expect("missing function code");
26300        let ops: Vec<_> = f
26301            .instructions
26302            .iter()
26303            .map(|unit| unit.op)
26304            .filter(|op| !matches!(op, Instruction::Cache))
26305            .collect();
26306        let handler_start = ops
26307            .iter()
26308            .position(|op| matches!(op, Instruction::PushExcInfo))
26309            .expect("missing handler entry");
26310        let protected_tail = &ops[..handler_start];
26311
26312        assert!(
26313            !protected_tail.iter().any(|op| {
26314                matches!(
26315                    op,
26316                    Instruction::LoadFastBorrow { .. }
26317                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
26318                )
26319            }),
26320            "expected protected attr-subscript tail to keep strong LOAD_FAST ops, got tail={protected_tail:?}"
26321        );
26322    }
26323
26324    #[test]
26325    fn protected_direct_subscript_tail_uses_strong_load_fast() {
26326        let code = compile_exec(
26327            "\
26328def f(seq):
26329    try:
26330        items = [int(item) for item in seq]
26331    except ValueError:
26332        return None
26333    return items[0] + items[1]
26334",
26335        );
26336        let f = find_code(&code, "f").expect("missing function code");
26337        let ops: Vec<_> = f
26338            .instructions
26339            .iter()
26340            .map(|unit| unit.op)
26341            .filter(|op| !matches!(op, Instruction::Cache))
26342            .collect();
26343        let handler_start = ops
26344            .iter()
26345            .position(|op| matches!(op, Instruction::PushExcInfo))
26346            .expect("missing handler entry");
26347        let protected_store = ops[..handler_start]
26348            .iter()
26349            .rposition(|op| matches!(op, Instruction::StoreFast { .. }))
26350            .expect("missing protected local store");
26351        let tail = &ops[protected_store + 1..handler_start];
26352
26353        assert!(
26354            !tail.iter().any(|op| {
26355                matches!(
26356                    op,
26357                    Instruction::LoadFastBorrow { .. }
26358                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
26359                )
26360            }),
26361            "expected protected direct-subscript tail to keep strong LOAD_FAST ops, got tail={tail:?}"
26362        );
26363    }
26364
26365    #[test]
26366    fn protected_attr_iter_chain_uses_strong_load_fast() {
26367        let code = compile_exec(
26368            "\
26369def f(fields):
26370    try:
26371        x = 1
26372    except ValueError:
26373        return False
26374    return tuple(v for v in fields.values())
26375",
26376        );
26377        let f = find_code(&code, "f").expect("missing function code");
26378        let ops: Vec<_> = f
26379            .instructions
26380            .iter()
26381            .map(|unit| unit.op)
26382            .filter(|op| !matches!(op, Instruction::Cache))
26383            .collect();
26384        let handler_start = ops
26385            .iter()
26386            .position(|op| matches!(op, Instruction::PushExcInfo))
26387            .expect("missing handler entry");
26388        let protected_tail = &ops[..handler_start];
26389
26390        assert!(
26391            !protected_tail.iter().any(|op| {
26392                matches!(
26393                    op,
26394                    Instruction::LoadFastBorrow { .. }
26395                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
26396                )
26397            }),
26398            "expected protected attr-iter chain to keep strong LOAD_FAST ops, got tail={protected_tail:?}"
26399        );
26400    }
26401
26402    #[test]
26403    fn generator_except_return_handler_deopts_normal_tail_borrows() {
26404        let code = compile_exec(
26405            "\
26406def f(fields):
26407    try:
26408        x = 1
26409    except ValueError:
26410        return
26411    for fielddesc in fields:
26412        yield fielddesc
26413",
26414        );
26415        let f = find_code(&code, "f").expect("missing f code");
26416        let ops: Vec<_> = f
26417            .instructions
26418            .iter()
26419            .map(|unit| unit.op)
26420            .filter(|op| !matches!(op, Instruction::Cache))
26421            .collect();
26422        let handler_start = ops
26423            .iter()
26424            .position(|op| matches!(op, Instruction::PushExcInfo))
26425            .expect("missing handler entry");
26426        let normal_tail = &ops[..handler_start];
26427
26428        assert!(
26429            normal_tail
26430                .iter()
26431                .any(|op| matches!(op, Instruction::LoadFast { .. })),
26432            "generator tail after non-yielding except return should keep CPython-style strong LOAD_FAST, got tail={normal_tail:?}"
26433        );
26434        assert!(
26435            !normal_tail.iter().any(|op| {
26436                matches!(
26437                    op,
26438                    Instruction::LoadFastBorrow { .. }
26439                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
26440                )
26441            }),
26442            "generator tail after non-yielding except return should not borrow, got tail={normal_tail:?}"
26443        );
26444    }
26445
26446    #[test]
26447    fn generator_except_yielding_handler_keeps_normal_tail_borrows() {
26448        let code = compile_exec(
26449            "\
26450def f(tp, parent=None):
26451    try:
26452        fields = tp._fields_
26453    except AttributeError:
26454        yield parent
26455    else:
26456        for fielddesc in fields:
26457            yield fielddesc
26458",
26459        );
26460        let f = find_code(&code, "f").expect("missing f code");
26461        let ops: Vec<_> = f
26462            .instructions
26463            .iter()
26464            .map(|unit| unit.op)
26465            .filter(|op| !matches!(op, Instruction::Cache))
26466            .collect();
26467        let handler_start = ops
26468            .iter()
26469            .position(|op| matches!(op, Instruction::PushExcInfo))
26470            .expect("missing handler entry");
26471        let normal_tail = &ops[..handler_start];
26472
26473        assert!(
26474            normal_tail.iter().any(|op| {
26475                matches!(
26476                    op,
26477                    Instruction::LoadFastBorrow { .. }
26478                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
26479                )
26480            }),
26481            "generator tail after yielding except handler should keep CPython-style borrows, got tail={normal_tail:?}"
26482        );
26483    }
26484
26485    #[test]
26486    fn generator_returning_except_keeps_yield_from_resume_tail_borrow() {
26487        let code = compile_exec(
26488            "\
26489def f(self, action):
26490    try:
26491        get_subactions = action._get_subactions
26492    except AttributeError:
26493        pass
26494    else:
26495        self._indent()
26496        yield from get_subactions()
26497        self._dedent()
26498",
26499        );
26500        let f = find_code(&code, "f").expect("missing f code");
26501        let instructions: Vec<_> = f
26502            .instructions
26503            .iter()
26504            .filter(|unit| !matches!(unit.op, Instruction::Cache))
26505            .collect();
26506        let end_send = instructions
26507            .iter()
26508            .position(|unit| matches!(unit.op, Instruction::EndSend))
26509            .expect("missing END_SEND");
26510        let dedent_attr = instructions[end_send..]
26511            .iter()
26512            .position(|unit| match unit.op {
26513                Instruction::LoadAttr { namei } => {
26514                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
26515                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "_dedent"
26516                }
26517                _ => false,
26518            })
26519            .map(|idx| end_send + idx)
26520            .expect("missing _dedent LOAD_ATTR");
26521
26522        assert!(
26523            matches!(
26524                instructions[dedent_attr - 1].op,
26525                Instruction::LoadFastBorrow { .. }
26526            ),
26527            "CPython keeps yield-from resume receiver borrowed after END_SEND, got instructions={instructions:?}"
26528        );
26529    }
26530
26531    #[test]
26532    fn generator_except_pass_resume_tail_keeps_borrows() {
26533        let code = compile_exec(
26534            "\
26535def f(self, msg):
26536    if self.log_queue is not None:
26537        yield
26538        output = []
26539        try:
26540            while True:
26541                output.append(self.log_queue.get_nowait().getMessage())
26542        except queue.Empty:
26543            pass
26544    else:
26545        with self.assertLogs('concurrent.futures', 'CRITICAL') as cm:
26546            yield
26547        output = cm.output
26548    self.assertTrue(any(msg in line for line in output), output)
26549",
26550        );
26551        let f = find_code(&code, "f").expect("missing f code");
26552
26553        let has_strong_load = |name: &str| {
26554            f.instructions.iter().any(|unit| match unit.op {
26555                Instruction::LoadFast { var_num } => {
26556                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
26557                    f.varnames[usize::from(var_num.get(arg))] == name
26558                }
26559                _ => false,
26560            })
26561        };
26562        let has_borrow_load = |name: &str| {
26563            f.instructions.iter().any(|unit| match unit.op {
26564                Instruction::LoadFastBorrow { var_num } => {
26565                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
26566                    f.varnames[usize::from(var_num.get(arg))] == name
26567                }
26568                _ => false,
26569            })
26570        };
26571
26572        for name in ["msg", "output"] {
26573            assert!(
26574                has_borrow_load(name),
26575                "generator except-pass resume tail should borrow {name}, got instructions={:?}",
26576                f.instructions
26577            );
26578            assert!(
26579                !has_strong_load(name),
26580                "generator except-pass resume tail should not force strong LOAD_FAST for {name}, got instructions={:?}",
26581                f.instructions
26582            );
26583        }
26584    }
26585
26586    #[test]
26587    fn async_for_cleanup_resume_tail_uses_strong_loads() {
26588        let code = compile_exec(
26589            "\
26590async def f(g, self, x):
26591    async for val in g:
26592        break
26593    self.x(x)
26594    await g.aclose()
26595",
26596        );
26597        let f = find_code(&code, "f").expect("missing f code");
26598        let instructions: Vec<_> = f
26599            .instructions
26600            .iter()
26601            .filter(|unit| !matches!(unit.op, Instruction::Cache))
26602            .collect();
26603        let ops: Vec<_> = instructions.iter().map(|unit| unit.op).collect();
26604        let aclose_idx = instructions
26605            .iter()
26606            .position(|unit| match unit.op {
26607                Instruction::LoadAttr { namei } => {
26608                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
26609                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "aclose"
26610                }
26611                _ => false,
26612            })
26613            .expect("missing aclose load");
26614
26615        assert!(
26616            ops.windows(4).any(|window| {
26617                matches!(
26618                    window,
26619                    [
26620                        Instruction::LoadFast { .. },
26621                        Instruction::LoadAttr { .. },
26622                        Instruction::LoadFast { .. },
26623                        Instruction::Call { .. },
26624                    ]
26625                )
26626            }),
26627            "async-for cleanup resume tail should use strong LOAD_FAST ops before the await, got ops={ops:?}"
26628        );
26629        assert!(
26630            matches!(
26631                instructions
26632                    .get(aclose_idx.saturating_sub(1))
26633                    .map(|unit| unit.op),
26634                Some(Instruction::LoadFast { .. })
26635            ),
26636            "async-for cleanup resume tail should keep g strong before aclose, got ops={ops:?}"
26637        );
26638    }
26639
26640    #[test]
26641    fn async_generator_async_with_yield_keeps_borrow() {
26642        let code = compile_exec(
26643            "\
26644async def f(self, my_cm):
26645    async with self.exit_stack() as stack:
26646        await stack.enter_async_context(my_cm())
26647        yield stack
26648",
26649        );
26650        let f = find_code(&code, "f").expect("missing f code");
26651        let instructions: Vec<_> = f
26652            .instructions
26653            .iter()
26654            .filter(|unit| !matches!(unit.op, Instruction::Cache))
26655            .collect();
26656        let ops: Vec<_> = instructions.iter().map(|unit| unit.op).collect();
26657        let wrap_idx = instructions
26658            .iter()
26659            .position(|unit| match unit.op {
26660                Instruction::CallIntrinsic1 { func } => {
26661                    func.get(OpArg::new(u32::from(u8::from(unit.arg))))
26662                        == IntrinsicFunction1::AsyncGenWrap
26663                }
26664                _ => false,
26665            })
26666            .expect("missing async generator wrap");
26667
26668        assert!(
26669            matches!(
26670                ops.get(wrap_idx.saturating_sub(1)),
26671                Some(Instruction::LoadFastBorrow { .. })
26672            ),
26673            "async generator yield inside async-with should borrow stack like CPython, got ops={ops:?}"
26674        );
26675    }
26676
26677    #[test]
26678    fn deoptimized_async_with_enter_continuation_uses_strong_loads() {
26679        let code = compile_exec(
26680            "\
26681async def f():
26682    async def cm():
26683        pass
26684    try:
26685        async with cm():
26686            1 / 0
26687    except ZeroDivisionError as e:
26688        frames = e
26689    class E(RuntimeError):
26690        pass
26691    try:
26692        async with cm():
26693            raise E(42)
26694    except E as e:
26695        frames = e
26696",
26697        );
26698        let f = find_code(&code, "f").expect("missing f code");
26699        let ops: Vec<_> = f
26700            .instructions
26701            .iter()
26702            .map(|unit| unit.op)
26703            .filter(|op| !matches!(op, Instruction::Cache))
26704            .collect();
26705
26706        assert!(
26707            ops.windows(5).any(|window| {
26708                matches!(
26709                    window,
26710                    [
26711                        Instruction::LoadFast { .. },
26712                        Instruction::PushNull,
26713                        Instruction::LoadSmallInt { .. },
26714                        Instruction::Call { .. },
26715                        Instruction::RaiseVarargs { .. },
26716                    ]
26717                )
26718            }),
26719            "async-with enter continuation after a deoptimized setup block should keep raised class strong, got ops={ops:?}"
26720        );
26721    }
26722
26723    #[test]
26724    fn async_with_bare_raise_continuation_keeps_borrow() {
26725        let code = compile_exec(
26726            "\
26727async def f(tg):
26728    class E(Exception):
26729        pass
26730    try:
26731        async with tg:
26732            raise E
26733    except ExceptionGroup:
26734        pass
26735",
26736        );
26737        let f = find_code(&code, "f").expect("missing f code");
26738        let ops: Vec<_> = f
26739            .instructions
26740            .iter()
26741            .map(|unit| unit.op)
26742            .filter(|op| !matches!(op, Instruction::Cache))
26743            .collect();
26744        let raise_idx = ops
26745            .iter()
26746            .position(|op| matches!(op, Instruction::RaiseVarargs { .. }))
26747            .expect("missing raise");
26748
26749        assert!(
26750            matches!(
26751                ops.get(raise_idx.saturating_sub(1)),
26752                Some(Instruction::LoadFastBorrow { .. })
26753            ),
26754            "bare async-with raise continuation should keep the raised class borrowed like CPython, got ops={ops:?}"
26755        );
26756    }
26757
26758    #[test]
26759    fn except_star_tail_uses_strong_loads() {
26760        let code = compile_exec(
26761            "\
26762def f(self):
26763    try:
26764        pass
26765    except* ValueError:
26766        pass
26767    self.fail('x')
26768",
26769        );
26770        let f = find_code(&code, "f").expect("missing f code");
26771        let ops: Vec<_> = f
26772            .instructions
26773            .iter()
26774            .map(|unit| unit.op)
26775            .filter(|op| !matches!(op, Instruction::Cache))
26776            .collect();
26777        let fail_attr = ops
26778            .iter()
26779            .position(|op| matches!(op, Instruction::LoadAttr { .. }))
26780            .expect("missing self.fail load");
26781
26782        assert!(
26783            ops.windows(4).any(|window| {
26784                matches!(
26785                    window,
26786                    [
26787                        Instruction::LoadFast { .. },
26788                        Instruction::LoadAttr { .. },
26789                        Instruction::LoadConst { .. },
26790                        Instruction::Call { .. },
26791                    ]
26792                )
26793            }),
26794            "except* tail should use strong LOAD_FAST like CPython, got ops={ops:?}"
26795        );
26796        assert!(
26797            !ops.windows(4).any(|window| {
26798                matches!(
26799                    window,
26800                    [
26801                        Instruction::LoadFastBorrow { .. },
26802                        Instruction::LoadAttr { .. },
26803                        Instruction::LoadConst { .. },
26804                        Instruction::Call { .. },
26805                    ]
26806                )
26807            }),
26808            "except* tail should not borrow the receiver after the handler region, got ops={ops:?}"
26809        );
26810        assert!(
26811            !matches!(
26812                ops.get(fail_attr.saturating_sub(2)),
26813                Some(Instruction::JumpForward { .. })
26814            ),
26815            "except* end label should not compile as an extra jump before the continuation, got ops={ops:?}"
26816        );
26817    }
26818
26819    #[test]
26820    fn protected_attr_subscript_store_tail_uses_strong_load_fast() {
26821        let code = compile_exec(
26822            "\
26823def f(f, oldcls, newcls):
26824    try:
26825        idx = f.__code__.co_freevars.index('__class__')
26826    except ValueError:
26827        return False
26828    closure = f.__closure__[idx]
26829    if closure.cell_contents is oldcls:
26830        closure.cell_contents = newcls
26831        return True
26832    return False
26833",
26834        );
26835        let f = find_code(&code, "f").expect("missing f code");
26836        let ops: Vec<_> = f
26837            .instructions
26838            .iter()
26839            .map(|unit| unit.op)
26840            .filter(|op| !matches!(op, Instruction::Cache))
26841            .collect();
26842        let handler_start = ops
26843            .iter()
26844            .position(|op| matches!(op, Instruction::PushExcInfo))
26845            .expect("missing handler entry");
26846        let protected_tail = &ops[..handler_start];
26847        let store_closure_idx = protected_tail
26848            .windows(2)
26849            .position(|window| {
26850                matches!(
26851                    window,
26852                    [Instruction::BinaryOp { .. }, Instruction::StoreFast { .. }]
26853                )
26854            })
26855            .map(|idx| idx + 1)
26856            .expect("missing STORE_FAST for closure");
26857        let post_store_tail = &protected_tail[store_closure_idx + 1..];
26858
26859        assert!(
26860            !post_store_tail.iter().any(|op| {
26861                matches!(
26862                    op,
26863                    Instruction::LoadFastBorrow { .. }
26864                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
26865                )
26866            }),
26867            "expected protected attr-subscript store tail to keep strong LOAD_FAST ops, got tail={post_store_tail:?}"
26868        );
26869    }
26870
26871    #[test]
26872    fn plain_attr_subscript_tail_keeps_borrow() {
26873        let code = compile_exec(
26874            "\
26875def f(self, name):
26876    annotations = self.method_annotations[name]
26877    return annotations
26878",
26879        );
26880        let f = find_code(&code, "f").expect("missing f code");
26881        let ops: Vec<_> = f
26882            .instructions
26883            .iter()
26884            .map(|unit| unit.op)
26885            .filter(|op| !matches!(op, Instruction::Cache))
26886            .collect();
26887
26888        assert!(
26889            ops.windows(4).any(|window| {
26890                matches!(
26891                    window,
26892                    [
26893                        Instruction::LoadFastBorrow { .. },
26894                        Instruction::LoadAttr { .. },
26895                        Instruction::LoadFastBorrow { .. },
26896                        Instruction::BinaryOp { .. },
26897                    ]
26898                )
26899            }),
26900            "expected plain attr-subscript tail to keep borrowed receiver/index loads, got ops={ops:?}"
26901        );
26902    }
26903
26904    #[test]
26905    fn plain_attr_iter_chain_keeps_borrow() {
26906        let code = compile_exec(
26907            "\
26908def f(fields):
26909    return tuple(v for v in fields.values())
26910",
26911        );
26912        let f = find_code(&code, "f").expect("missing f code");
26913        let ops: Vec<_> = f
26914            .instructions
26915            .iter()
26916            .map(|unit| unit.op)
26917            .filter(|op| !matches!(op, Instruction::Cache))
26918            .collect();
26919
26920        assert!(
26921            ops.windows(4).any(|window| {
26922                matches!(
26923                    window,
26924                    [
26925                        Instruction::LoadFastBorrow { .. },
26926                        Instruction::LoadAttr { .. },
26927                        Instruction::Call { .. },
26928                        Instruction::GetIter,
26929                    ]
26930                )
26931            }),
26932            "expected plain attr-iter chain to keep borrowed receiver, got ops={ops:?}"
26933        );
26934    }
26935
26936    #[test]
26937    fn genexpr_true_filter_omits_bool_scaffolding() {
26938        let code = compile_exec(
26939            "\
26940def f(it):
26941    return (x for x in it if True)
26942",
26943        );
26944        let genexpr = find_code(&code, "<genexpr>").expect("missing <genexpr> code");
26945        assert!(
26946            !genexpr.instructions.iter().any(|unit| {
26947                matches!(unit.op, Instruction::LoadConst { .. })
26948                    && matches!(
26949                        genexpr.constants.get(usize::from(u8::from(unit.arg))),
26950                        Some(ConstantData::Boolean { value: true })
26951                    )
26952            }),
26953            "constant-true filter should not load True, got ops={:?}",
26954            genexpr
26955                .instructions
26956                .iter()
26957                .map(|unit| unit.op)
26958                .collect::<Vec<_>>()
26959        );
26960        assert!(
26961            !genexpr
26962                .instructions
26963                .iter()
26964                .any(|unit| matches!(unit.op, Instruction::PopJumpIfTrue { .. })),
26965            "constant-true filter should not leave POP_JUMP_IF_TRUE scaffolding, got ops={:?}",
26966            genexpr
26967                .instructions
26968                .iter()
26969                .map(|unit| unit.op)
26970                .collect::<Vec<_>>()
26971        );
26972    }
26973
26974    #[test]
26975    fn classdictcell_uses_load_closure_path_and_borrows_after_optimize() {
26976        let code = compile_exec(
26977            "\
26978class C:
26979    def method(self):
26980        return 1
26981",
26982        );
26983        let class_code = find_code(&code, "C").expect("missing class code");
26984        let store_classdictcell = class_code
26985            .instructions
26986            .iter()
26987            .position(|unit| {
26988                matches!(
26989                    unit.op,
26990                    Instruction::StoreName { namei }
26991                        if class_code.names
26992                            [namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize]
26993                            .as_str()
26994                            == "__classdictcell__"
26995                )
26996            })
26997            .expect("missing STORE_NAME __classdictcell__");
26998
26999        assert!(
27000            matches!(
27001                class_code
27002                    .instructions
27003                    .get(store_classdictcell.saturating_sub(1))
27004                    .map(|unit| unit.op),
27005                Some(Instruction::LoadFastBorrow { .. })
27006            ),
27007            "expected LOAD_FAST_BORROW before __classdictcell__ store, got ops={:?}",
27008            class_code
27009                .instructions
27010                .iter()
27011                .map(|unit| unit.op)
27012                .collect::<Vec<_>>()
27013        );
27014    }
27015
27016    #[test]
27017    fn explicit_class_dunder_class_store_uses_namespace_like_cpython() {
27018        let code = compile_exec(
27019            "\
27020class C:
27021    def method(self):
27022        return __class__
27023    __class__ = 413
27024",
27025        );
27026        let class_code = find_code(&code, "C").expect("missing class code");
27027        let class_name_index = class_code
27028            .names
27029            .iter()
27030            .position(|name| name.as_str() == "__class__")
27031            .expect("missing __class__ name");
27032
27033        assert!(class_code.instructions.iter().any(|unit| {
27034            matches!(
27035                unit.op,
27036                Instruction::StoreName { namei }
27037                    if namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize
27038                        == class_name_index
27039            )
27040        }));
27041        assert!(!class_code.instructions.iter().any(|unit| {
27042            matches!(
27043                unit.op,
27044                Instruction::StoreDeref { i }
27045                    if class_code.cellvars
27046                        [usize::from(i.get(OpArg::new(u32::from(u8::from(unit.arg)))))]
27047                        .as_str()
27048                        == "__class__"
27049            )
27050        }));
27051    }
27052
27053    #[test]
27054    fn conditional_class_body_duplicates_no_location_exit_tail() {
27055        let code = compile_exec(
27056            "\
27057flag = False
27058class C:
27059    if flag:
27060        value = 1
27061",
27062        );
27063        let class_code = find_code(&code, "C").expect("missing class code");
27064        let ops: Vec<_> = class_code
27065            .instructions
27066            .iter()
27067            .map(|unit| unit.op)
27068            .filter(|op| !matches!(op, Instruction::Cache))
27069            .collect();
27070        let return_count = ops
27071            .iter()
27072            .filter(|op| matches!(op, Instruction::ReturnValue))
27073            .count();
27074        let static_attrs_count = class_code
27075            .instructions
27076            .iter()
27077            .filter(|unit| {
27078                matches!(
27079                    unit.op,
27080                    Instruction::StoreName { namei }
27081                        if class_code.names
27082                            [namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize]
27083                            .as_str()
27084                            == "__static_attributes__"
27085                )
27086            })
27087            .count();
27088
27089        assert_eq!(
27090            return_count, 2,
27091            "conditional class body should duplicate CPython no-location return tail, got ops={ops:?}"
27092        );
27093        assert_eq!(
27094            static_attrs_count, 2,
27095            "conditional class body should duplicate __static_attributes__ tail, got ops={ops:?}"
27096        );
27097    }
27098
27099    #[test]
27100    fn class_lambda_assignment_does_not_create_classdictcell() {
27101        let code = compile_exec(
27102            "\
27103class C:
27104    data = start = end = lambda *a: None
27105",
27106        );
27107        let class_code = find_code(&code, "C").expect("missing class code");
27108
27109        assert!(
27110            !class_code.instructions.iter().any(|unit| {
27111                matches!(
27112                    unit.op,
27113                    Instruction::StoreName { namei }
27114                        if class_code.names
27115                            [namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize]
27116                            .as_str()
27117                            == "__classdictcell__"
27118                )
27119            }),
27120            "lambda-only class should not create __classdictcell__, got ops={:?}",
27121            class_code
27122                .instructions
27123                .iter()
27124                .map(|unit| unit.op)
27125                .collect::<Vec<_>>()
27126        );
27127    }
27128
27129    #[test]
27130    fn nested_function_static_attributes_are_collected() {
27131        let code = compile_exec(
27132            "\
27133class C:
27134    def f(self):
27135        self.x = 1
27136        self.y = 2
27137        self.x = 3
27138
27139    def g(self, obj):
27140        self.y = 4
27141        self.z = 5
27142
27143        def h(self, a):
27144            self.u = 6
27145            self.v = 7
27146
27147        obj.self = 8
27148",
27149        );
27150        let class_code = find_code(&code, "C").expect("missing class code");
27151
27152        assert!(
27153            class_code.constants.iter().any(|constant| matches!(
27154                constant,
27155                ConstantData::Tuple { elements }
27156                    if elements
27157                        == &[
27158                            ConstantData::Str { value: "u".into() },
27159                            ConstantData::Str { value: "v".into() },
27160                            ConstantData::Str { value: "x".into() },
27161                            ConstantData::Str { value: "y".into() },
27162                            ConstantData::Str { value: "z".into() },
27163                        ]
27164            )),
27165            "expected nested function static attributes in class consts"
27166        );
27167    }
27168
27169    #[test]
27170    fn static_attributes_match_cpython_store_rule() {
27171        let code = compile_exec(
27172            "\
27173class C:
27174    @staticmethod
27175    def f():
27176        self.x = 1
27177
27178    @classmethod
27179    def g(cls):
27180        self.y = 2
27181
27182    def h(obj):
27183        obj.z = 3
27184        tarinfo.uid = 4
27185
27186    def i(self):
27187        self.a: int
27188        self.b: int = 1
27189        self.c += 1
27190        del self.d
27191",
27192        );
27193        let class_code = find_code(&code, "C").expect("missing class code");
27194
27195        assert!(
27196            class_code.constants.iter().any(|constant| matches!(
27197                constant,
27198                ConstantData::Tuple { elements }
27199                    if elements
27200                        == &[
27201                            ConstantData::Str { value: "b".into() },
27202                            ConstantData::Str { value: "x".into() },
27203                            ConstantData::Str { value: "y".into() },
27204                        ]
27205            )),
27206            "expected only CPython-collected static attributes in class consts"
27207        );
27208    }
27209
27210    #[test]
27211    fn decorated_class_uses_first_decorator_for_firstlineno() {
27212        let code = compile_exec(
27213            "\
27214@dec1
27215@dec2
27216class C:
27217    pass
27218",
27219        );
27220        let class_code = find_code(&code, "C").expect("missing class code");
27221        let store_firstlineno = class_code
27222            .instructions
27223            .iter()
27224            .position(|unit| {
27225                matches!(
27226                    unit.op,
27227                    Instruction::StoreName { namei }
27228                        if class_code.names
27229                            [namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize]
27230                            .as_str()
27231                            == "__firstlineno__"
27232                )
27233            })
27234            .expect("missing STORE_NAME __firstlineno__");
27235        let load_firstlineno = class_code
27236            .instructions
27237            .get(store_firstlineno.saturating_sub(1))
27238            .expect("missing LOAD_CONST for __firstlineno__");
27239
27240        let expected = ConstantData::Integer {
27241            value: BigInt::from(1),
27242        };
27243        assert!(
27244            matches!(
27245                load_firstlineno.op,
27246                Instruction::LoadSmallInt { .. } | Instruction::LoadConst { .. }
27247            ),
27248            "expected LOAD_SMALL_INT/LOAD_CONST before __firstlineno__, got {:?}",
27249            load_firstlineno.op
27250        );
27251        if let Instruction::LoadConst { consti } = load_firstlineno.op {
27252            let value = &class_code.constants
27253                [consti.get(OpArg::new(u32::from(u8::from(load_firstlineno.arg))))];
27254            assert_eq!(value, &expected);
27255        } else {
27256            assert_eq!(u32::from(u8::from(load_firstlineno.arg)), 1);
27257        }
27258    }
27259
27260    #[test]
27261    fn class_firstlineno_store_uses_name_resolution() {
27262        let code = compile_exec(
27263            "\
27264def f():
27265    __firstlineno__ = 1
27266    class C:
27267        nonlocal __firstlineno__
27268    return C
27269",
27270        );
27271        let class_code = find_code(&code, "C").expect("missing class code");
27272
27273        assert!(
27274            class_code
27275                .freevars
27276                .iter()
27277                .any(|name| name == "__firstlineno__"),
27278            "class should close over nonlocal __firstlineno__, got freevars={:?}",
27279            class_code.freevars
27280        );
27281        assert!(
27282            class_code.instructions.iter().any(|unit| match unit.op {
27283                Instruction::StoreDeref { i } => {
27284                    let idx = i.get(OpArg::new(u32::from(u8::from(unit.arg)))).as_usize();
27285                    localsplus_name(class_code, idx) == Some("__firstlineno__")
27286                }
27287                _ => false,
27288            }),
27289            "CPython routes __firstlineno__ through name resolution and emits STORE_DEREF for __firstlineno__, got ops={:?} freevars={:?}",
27290            class_code.instructions,
27291            class_code.freevars
27292        );
27293        assert!(
27294            !class_code.instructions.iter().any(|unit| {
27295                matches!(
27296                    unit.op,
27297                    Instruction::StoreName { namei }
27298                        if class_code.names
27299                            [namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize]
27300                            .as_str()
27301                            == "__firstlineno__"
27302                )
27303            }),
27304            "nonlocal __firstlineno__ should not be stored with STORE_NAME, got ops={:?}",
27305            class_code.instructions
27306        );
27307    }
27308
27309    #[test]
27310    fn lambda_parent_qualname_includes_locals() {
27311        let code = compile_exec(
27312            "\
27313def f():
27314    return lambda: (lambda: None)
27315",
27316        );
27317        let mut lambda_qualnames = Vec::new();
27318        fn collect_lambda_qualnames(code: &CodeObject, out: &mut Vec<String>) {
27319            if code.obj_name == "<lambda>" {
27320                out.push(code.qualname.to_string());
27321            }
27322            for constant in code.constants.iter() {
27323                if let ConstantData::Code { code } = constant {
27324                    collect_lambda_qualnames(code, out);
27325                }
27326            }
27327        }
27328        collect_lambda_qualnames(&code, &mut lambda_qualnames);
27329
27330        assert!(
27331            lambda_qualnames
27332                .iter()
27333                .any(|name| name == "f.<locals>.<lambda>"),
27334            "missing outer lambda qualname, got {lambda_qualnames:?}"
27335        );
27336        assert!(
27337            lambda_qualnames
27338                .iter()
27339                .any(|name| name == "f.<locals>.<lambda>.<locals>.<lambda>"),
27340            "nested lambda parent should include .<locals> like CPython, got {lambda_qualnames:?}"
27341        );
27342    }
27343
27344    #[test]
27345    fn future_annotations_class_uses_direct_annotation_store() {
27346        let code = compile_exec(
27347            "\
27348from __future__ import annotations
27349class C:
27350    x: int
27351",
27352        );
27353        let class_code = find_code(&code, "C").expect("missing class code");
27354
27355        assert!(
27356            !class_code
27357                .cellvars
27358                .iter()
27359                .any(|name| name.as_str() == "__conditional_annotations__"),
27360            "future annotations should not create __conditional_annotations__ cellvar, got cellvars={:?}",
27361            class_code.cellvars
27362        );
27363        let ops: Vec<_> = class_code
27364            .instructions
27365            .iter()
27366            .map(|unit| unit.op)
27367            .filter(|op| !matches!(op, Instruction::Cache))
27368            .collect();
27369        assert!(
27370            ops.iter()
27371                .any(|op| matches!(op, Instruction::SetupAnnotations)),
27372            "future annotations should emit SETUP_ANNOTATIONS, got ops={ops:?}"
27373        );
27374        assert!(
27375            ops.iter().any(|op| matches!(op, Instruction::StoreSubscr)),
27376            "future annotations should store directly into __annotations__, got ops={ops:?}"
27377        );
27378        assert!(
27379            !ops.iter()
27380                .any(|op| matches!(op, Instruction::BuildSet { .. })),
27381            "future annotations should not initialize __conditional_annotations__, got ops={ops:?}"
27382        );
27383    }
27384
27385    #[test]
27386    fn future_annotations_module_keeps_conditional_annotations_cell() {
27387        let code = compile_exec(
27388            "\
27389from __future__ import annotations
27390x: int = 1
27391",
27392        );
27393
27394        assert!(
27395            code.cellvars
27396                .iter()
27397                .any(|name| name.as_str() == "__conditional_annotations__"),
27398            "module annotations should create __conditional_annotations__ cellvar, got cellvars={:?}",
27399            code.cellvars
27400        );
27401    }
27402
27403    #[test]
27404    fn future_annotations_conditional_class_keeps_conditional_annotations_cell() {
27405        let code = compile_exec(
27406            "\
27407from __future__ import annotations
27408class C:
27409    if True:
27410        x: int = 1
27411",
27412        );
27413        let class_code = find_code(&code, "C").expect("missing class code");
27414
27415        assert!(
27416            class_code
27417                .cellvars
27418                .iter()
27419                .any(|name| name.as_str() == "__conditional_annotations__"),
27420            "conditional class annotations should create __conditional_annotations__ cellvar, got cellvars={:?}",
27421            class_code.cellvars
27422        );
27423    }
27424
27425    #[test]
27426    fn future_annotations_setup_precedes_docstring() {
27427        let code = compile_exec(
27428            "\
27429\"module doc\"
27430from __future__ import annotations
27431x: int = 1
27432
27433class C:
27434    \"class doc\"
27435    x: int = 1
27436",
27437        );
27438        let module_setup = code
27439            .instructions
27440            .iter()
27441            .position(|unit| matches!(unit.op, Instruction::SetupAnnotations))
27442            .expect("missing module SETUP_ANNOTATIONS");
27443        let module_doc = code
27444            .instructions
27445            .iter()
27446            .position(|unit| {
27447                matches!(
27448                    unit.op,
27449                    Instruction::StoreName { namei }
27450                        if code.names
27451                            [namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize]
27452                            .as_str()
27453                            == "__doc__"
27454                )
27455            })
27456            .expect("missing module doc store");
27457        assert!(
27458            module_setup < module_doc,
27459            "module SETUP_ANNOTATIONS should precede docstring store, got instructions={:?}",
27460            code.instructions
27461        );
27462
27463        let class_code = find_code(&code, "C").expect("missing class code");
27464        let class_setup = class_code
27465            .instructions
27466            .iter()
27467            .position(|unit| matches!(unit.op, Instruction::SetupAnnotations))
27468            .expect("missing class SETUP_ANNOTATIONS");
27469        let class_doc = class_code
27470            .instructions
27471            .iter()
27472            .position(|unit| {
27473                matches!(
27474                    unit.op,
27475                    Instruction::StoreName { namei }
27476                        if class_code.names
27477                            [namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize]
27478                            .as_str()
27479                            == "__doc__"
27480                )
27481            })
27482            .expect("missing class doc store");
27483        assert!(
27484            class_setup < class_doc,
27485            "class SETUP_ANNOTATIONS should precede docstring store, got instructions={:?}",
27486            class_code.instructions
27487        );
27488    }
27489
27490    #[test]
27491    fn optimize_two_strips_docstrings_during_preprocess() {
27492        let code = compile_exec_with_options(
27493            "\
27494\"module doc\"
27495
27496def f():
27497    \"function doc\"
27498    return 1
27499
27500class C:
27501    \"class doc\"
27502    x = 1
27503",
27504            CompileOpts {
27505                optimize: 2,
27506                ..CompileOpts::default()
27507            },
27508        );
27509
27510        assert!(
27511            !code.instructions.iter().any(|unit| {
27512                matches!(
27513                    unit.op,
27514                    Instruction::StoreName { namei }
27515                        if code.names
27516                            [namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize]
27517                            .as_str()
27518                            == "__doc__"
27519                )
27520            }),
27521            "module docstring should be stripped before codegen, got instructions={:?}",
27522            code.instructions
27523        );
27524
27525        let function_code = find_code(&code, "f").expect("missing function code");
27526        assert!(
27527            !function_code
27528                .flags
27529                .contains(bytecode::CodeFlags::HAS_DOCSTRING),
27530            "function docstring should not set HAS_DOCSTRING when optimize=2"
27531        );
27532
27533        let class_code = find_code(&code, "C").expect("missing class code");
27534        assert!(
27535            !class_code.instructions.iter().any(|unit| {
27536                matches!(
27537                    unit.op,
27538                    Instruction::StoreName { namei }
27539                        if class_code.names
27540                            [namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize]
27541                            .as_str()
27542                            == "__doc__"
27543                )
27544            }),
27545            "class docstring should be stripped before codegen, got instructions={:?}",
27546            class_code.instructions
27547        );
27548    }
27549
27550    #[test]
27551    fn future_annotations_flag_is_inherited_like_cpython() {
27552        let code = compile_exec(
27553            "\
27554from __future__ import annotations
27555
27556def f():
27557    class C:
27558        pass
27559    return C
27560",
27561        );
27562        assert!(code.flags.contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS));
27563        let f = find_code(&code, "f").expect("missing f code");
27564        assert!(f.flags.contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS));
27565        let class_code = find_code(f, "C").expect("missing C code");
27566        assert!(
27567            class_code
27568                .flags
27569                .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS)
27570        );
27571    }
27572
27573    #[test]
27574    fn future_flags_from_compile_options_are_merged_like_cpython() {
27575        let opts = CompileOpts {
27576            future_features: bytecode::CodeFlags::FUTURE_ANNOTATIONS
27577                | bytecode::CodeFlags::FUTURE_DIVISION,
27578            ..CompileOpts::default()
27579        };
27580        let code = compile_exec_with_options(
27581            "\
27582x: int
27583def f():
27584    pass
27585",
27586            opts,
27587        );
27588        assert!(code.flags.contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS));
27589        assert!(code.flags.contains(bytecode::CodeFlags::FUTURE_DIVISION));
27590        assert!(
27591            code.instructions
27592                .iter()
27593                .any(|unit| matches!(unit.op, Instruction::SetupAnnotations))
27594        );
27595        let f = find_code(&code, "f").expect("missing f code");
27596        assert!(f.flags.contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS));
27597        assert!(f.flags.contains(bytecode::CodeFlags::FUTURE_DIVISION));
27598    }
27599
27600    #[test]
27601    fn future_barry_as_flufl_sets_module_and_nested_code_flags() {
27602        let code = compile_exec(
27603            "\
27604from __future__ import barry_as_FLUFL
27605
27606def f():
27607    pass
27608",
27609        );
27610        assert!(
27611            code.flags
27612                .contains(bytecode::CodeFlags::FUTURE_BARRY_AS_BDFL)
27613        );
27614        let f = find_code(&code, "f").expect("missing f code");
27615        assert!(f.flags.contains(bytecode::CodeFlags::FUTURE_BARRY_AS_BDFL));
27616    }
27617
27618    #[test]
27619    fn function_annotation_qualnames_include_the_annotated_function() {
27620        let code = compile_exec(
27621            "\
27622def f(x: int):
27623    pass
27624class C:
27625    def m(self, x: int):
27626        pass
27627def outer():
27628    def inner(x: int):
27629        pass
27630",
27631        );
27632        let mut qualnames = Vec::new();
27633        fn collect(code: &CodeObject, qualnames: &mut Vec<String>) {
27634            for constant in code.constants.iter() {
27635                if let ConstantData::Code { code } = constant {
27636                    if code.obj_name == "__annotate__" {
27637                        qualnames.push(code.qualname.clone());
27638                    }
27639                    collect(code.as_ref(), qualnames);
27640                }
27641            }
27642        }
27643        collect(&code, &mut qualnames);
27644        assert_eq!(
27645            qualnames,
27646            [
27647                "f.__annotate__",
27648                "C.m.__annotate__",
27649                "outer.<locals>.inner.__annotate__"
27650            ]
27651        );
27652    }
27653
27654    #[test]
27655    fn relative_future_import_does_not_enable_annotations_like_cpython() {
27656        let code = compile_exec(
27657            "\
27658from .__future__ import annotations
27659x: int
27660",
27661        );
27662        assert!(!code.flags.contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS));
27663    }
27664
27665    #[test]
27666    fn future_import_after_extra_string_is_rejected_like_cpython() {
27667        assert_eq!(
27668            compile_exec_error_message(
27669                "\
27670\"\"\"Docstring\"\"\"
27671\"this is not a docstring\"
27672from __future__ import nested_scopes
27673",
27674            ),
27675            "from __future__ imports must occur at the beginning of the file"
27676        );
27677    }
27678
27679    #[test]
27680    fn future_braces_uses_cpython_special_error() {
27681        assert_eq!(
27682            compile_exec_error_message("from __future__ import braces\n"),
27683            "not a chance"
27684        );
27685    }
27686
27687    #[test]
27688    fn invalid_future_feature_is_checked_before_ast_preprocess_like_cpython() {
27689        assert_eq!(
27690            compile_exec_error_message("from __future__ import spam, annotations\nx: (y := int)\n"),
27691            "future feature spam is not defined"
27692        );
27693    }
27694
27695    #[test]
27696    fn allow_top_level_await_marks_module_coroutine_like_cpython() {
27697        let opts = CompileOpts {
27698            allow_top_level_await: true,
27699            ..CompileOpts::default()
27700        };
27701        let code = compile_exec_with_options("await f()\n", opts);
27702        assert!(code.flags.contains(bytecode::CodeFlags::COROUTINE));
27703    }
27704
27705    #[test]
27706    fn allow_top_level_await_accepts_module_async_for_like_cpython() {
27707        let opts = CompileOpts {
27708            allow_top_level_await: true,
27709            ..CompileOpts::default()
27710        };
27711        let code = compile_exec_with_options(
27712            "\
27713async for x in y:
27714    pass
27715",
27716            opts,
27717        );
27718        assert!(code.flags.contains(bytecode::CodeFlags::COROUTINE));
27719    }
27720
27721    #[test]
27722    fn annotation_scope_nested_flag_matches_cpython() {
27723        let code = compile_exec(
27724            "\
27725class C:
27726    x: int
27727
27728def outer():
27729    class D:
27730        y: int
27731",
27732        );
27733        let class_code = find_code(&code, "C").expect("missing C code");
27734        let class_annotate =
27735            find_code(class_code, "__annotate__").expect("missing class annotation code");
27736        assert!(
27737            !class_annotate.flags.contains(bytecode::CodeFlags::NESTED),
27738            "module-level class annotation scope should not be nested"
27739        );
27740
27741        let outer = find_code(&code, "outer").expect("missing outer code");
27742        let nested_class = find_code(outer, "D").expect("missing nested class code");
27743        let nested_annotate =
27744            find_code(nested_class, "__annotate__").expect("missing nested annotation code");
27745        assert!(
27746            nested_annotate.flags.contains(bytecode::CodeFlags::NESTED),
27747            "annotation scope under a nested class should be nested"
27748        );
27749    }
27750
27751    #[test]
27752    fn function_like_parent_marks_child_nested_like_cpython() {
27753        let code = compile_exec(
27754            "\
27755x = lambda: (lambda: None)
27756type A[T] = T
27757",
27758        );
27759        let outer_lambda = find_code(&code, "<lambda>").expect("missing outer lambda code");
27760        assert!(
27761            !outer_lambda.flags.contains(bytecode::CodeFlags::NESTED),
27762            "module-level lambda should not be nested"
27763        );
27764        let inner_lambda =
27765            find_direct_child_code(outer_lambda, "<lambda>").expect("missing inner lambda code");
27766        assert!(
27767            inner_lambda.flags.contains(bytecode::CodeFlags::NESTED),
27768            "lambda inside lambda should be nested"
27769        );
27770
27771        let type_params =
27772            find_code(&code, "<generic parameters of A>").expect("missing type params code");
27773        assert!(
27774            !type_params.flags.contains(bytecode::CodeFlags::NESTED),
27775            "module-level type-parameter scope should not be nested"
27776        );
27777        let type_alias = find_direct_child_code(type_params, "A").expect("missing type alias code");
27778        assert!(
27779            type_alias.flags.contains(bytecode::CodeFlags::NESTED),
27780            "type alias body inside type-parameter scope should be nested"
27781        );
27782    }
27783
27784    #[test]
27785    fn plain_super_call_keeps_class_freevar() {
27786        let code = compile_exec(
27787            "\
27788class A:
27789    pass
27790
27791class B(A):
27792    def method(self):
27793        return super()
27794",
27795        );
27796        let method = find_code(&code, "method").expect("missing method code");
27797        assert!(
27798            method.freevars.iter().any(|name| name == "__class__"),
27799            "plain super() must keep __class__ freevar, got freevars={:?}",
27800            method.freevars
27801        );
27802        assert!(
27803            method
27804                .instructions
27805                .iter()
27806                .any(|unit| matches!(unit.op, Instruction::CopyFreeVars { .. })),
27807            "plain super() must keep COPY_FREE_VARS prelude, got ops={:?}",
27808            method
27809                .instructions
27810                .iter()
27811                .map(|unit| unit.op)
27812                .collect::<Vec<_>>()
27813        );
27814    }
27815
27816    #[test]
27817    fn nested_class_super_does_not_create_outer_class_closure() {
27818        let code = compile_exec(
27819            "\
27820class C:
27821    def outer(self):
27822        class D:
27823            def __init__(self):
27824                super().__init__()
27825",
27826        );
27827        let outer_class = find_code(&code, "C").expect("missing outer class code");
27828        let nested_class = find_code(&code, "D").expect("missing nested class code");
27829        let init = find_code(&code, "__init__").expect("missing nested __init__ code");
27830
27831        assert!(
27832            !outer_class.cellvars.iter().any(|name| name == "__class__"),
27833            "nested super() must not force __class__ on outer class, got cellvars={:?}",
27834            outer_class.cellvars
27835        );
27836        assert!(
27837            nested_class.cellvars.iter().any(|name| name == "__class__"),
27838            "nested class should own __class__ cell, got cellvars={:?}",
27839            nested_class.cellvars
27840        );
27841        assert!(
27842            init.freevars.iter().any(|name| name == "__class__"),
27843            "method using super() should close over nested class, got freevars={:?}",
27844            init.freevars
27845        );
27846    }
27847
27848    #[test]
27849    fn nested_class_body_loads_outer_dunder_class_while_methods_use_own_cell() {
27850        let code = compile_exec(
27851            "\
27852class Outer:
27853    def method(self):
27854        class Inner:
27855            value = __class__
27856            def nested():
27857                return __class__
27858",
27859        );
27860        let inner = find_code(&code, "Inner").expect("missing nested class code");
27861
27862        assert!(inner.cellvars.iter().any(|name| name == "__class__"));
27863        assert!(inner.freevars.iter().any(|name| name == "__class__"));
27864        assert!(
27865            inner
27866                .instructions
27867                .iter()
27868                .any(|unit| matches!(unit.op, Instruction::LoadFromDictOrDeref { .. })),
27869            "the class body must resolve __class__ from the enclosing method while the nested method closes over the new class cell"
27870        );
27871    }
27872
27873    #[test]
27874    fn nested_closure_parameter_class_does_not_create_outer_class_closure() {
27875        let code = compile_exec(
27876            "\
27877class C:
27878    def m(self):
27879        def create_closure(__class__):
27880            return (lambda: __class__).__closure__
27881",
27882        );
27883        let outer_class = find_code(&code, "C").expect("missing class code");
27884        let create_closure =
27885            find_code(&code, "create_closure").expect("missing create_closure code");
27886        let lambda = find_code(&code, "<lambda>").expect("missing lambda code");
27887
27888        assert!(
27889            !outer_class.cellvars.iter().any(|name| name == "__class__"),
27890            "nested __class__ parameter must not force outer class cell, got cellvars={:?}",
27891            outer_class.cellvars
27892        );
27893        assert!(
27894            create_closure
27895                .cellvars
27896                .iter()
27897                .any(|name| name == "__class__"),
27898            "create_closure should own __class__ parameter cell, got cellvars={:?}",
27899            create_closure.cellvars
27900        );
27901        assert!(
27902            lambda.freevars.iter().any(|name| name == "__class__"),
27903            "lambda should close over create_closure parameter, got freevars={:?}",
27904            lambda.freevars
27905        );
27906    }
27907
27908    #[test]
27909    fn chained_compare_jump_uses_single_cleanup_copy() {
27910        let code = compile_exec(
27911            "\
27912def f(code):
27913    if not 1 <= code <= 2147483647:
27914        raise ValueError('x')
27915",
27916        );
27917        let f = find_code(&code, "f").expect("missing function code");
27918        let copy_count = f
27919            .instructions
27920            .iter()
27921            .filter(|unit| matches!(unit.op, Instruction::Copy { .. }))
27922            .count();
27923        let pop_top_count = f
27924            .instructions
27925            .iter()
27926            .filter(|unit| matches!(unit.op, Instruction::PopTop))
27927            .count();
27928
27929        assert_eq!(copy_count, 1);
27930        assert_eq!(pop_top_count, 1);
27931    }
27932
27933    #[test]
27934    fn yield_from_cleanup_jumps_to_shared_end_send() {
27935        let code = compile_exec(
27936            "\
27937def outer():
27938    def inner():
27939        yield from outer_gen
27940    return inner
27941",
27942        );
27943        let inner = find_code(&code, "inner").expect("missing inner code");
27944        let ops: Vec<_> = inner
27945            .instructions
27946            .iter()
27947            .map(|unit| unit.op)
27948            .filter(|op| !matches!(op, Instruction::Cache))
27949            .collect();
27950
27951        let cleanup_idx = ops
27952            .iter()
27953            .position(|op| matches!(op, Instruction::CleanupThrow))
27954            .expect("missing CLEANUP_THROW");
27955        assert!(
27956            matches!(
27957                ops.get(cleanup_idx + 1),
27958                Some(Instruction::JumpBackwardNoInterrupt { .. } | Instruction::JumpForward { .. })
27959            ),
27960            "expected CLEANUP_THROW to jump to shared END_SEND block, got ops={ops:?}"
27961        );
27962        assert!(
27963            !matches!(ops.get(cleanup_idx + 1), Some(Instruction::EndSend)),
27964            "CLEANUP_THROW should not inline END_SEND directly, got ops={ops:?}"
27965        );
27966    }
27967
27968    #[test]
27969    fn try_except_falls_through_to_post_handler_code() {
27970        let code = compile_exec(
27971            "\
27972def f():
27973    try:
27974        line = 2
27975        raise KeyError
27976    except:
27977        line = 5
27978    line = 6
27979",
27980        );
27981        let f = find_code(&code, "f").expect("missing f code");
27982        let ops: Vec<_> = f
27983            .instructions
27984            .iter()
27985            .map(|unit| unit.op)
27986            .filter(|op| !matches!(op, Instruction::Cache))
27987            .collect();
27988
27989        let first_pop_except = ops
27990            .iter()
27991            .position(|op| matches!(op, Instruction::PopExcept))
27992            .expect("missing POP_EXCEPT");
27993        assert!(
27994            !matches!(
27995                ops.get(first_pop_except + 1),
27996                Some(Instruction::JumpForward { .. })
27997            ),
27998            "expected except body to fall through to post-handler code, got ops={ops:?}"
27999        );
28000        assert!(
28001            matches!(
28002                ops.get(first_pop_except + 1),
28003                Some(Instruction::LoadSmallInt { .. } | Instruction::LoadConst { .. })
28004            ),
28005            "expected line-after-except code immediately after POP_EXCEPT, got ops={ops:?}"
28006        );
28007    }
28008
28009    #[test]
28010    fn try_finally_loop_fallthrough_pop_block_bounds_exception_table() {
28011        let code = compile_exec(
28012            "\
28013def f(os, E, data):
28014    try:
28015        while True:
28016            part = os.read(3, 50000)
28017            data += part
28018            if not part or len(data) > 50000:
28019                break
28020    finally:
28021        os.close(3)
28022    if data:
28023        raise E(2, 'x')
28024",
28025        );
28026        let f = find_code(&code, "f").expect("missing f code");
28027        let raise_idx = u32::try_from(
28028            f.instructions
28029                .iter()
28030                .position(|unit| matches!(unit.op, Instruction::RaiseVarargs { .. }))
28031                .expect("missing post-finally raise"),
28032        )
28033        .unwrap();
28034        let entries = bytecode::decode_exception_table(&f.exceptiontable);
28035
28036        assert!(
28037            entries
28038                .iter()
28039                .all(|entry| raise_idx < entry.start || raise_idx >= entry.end),
28040            "post-finally raise should not remain protected by the try/finally table; entries={entries:?}, instructions={:?}",
28041            f.instructions
28042        );
28043    }
28044
28045    #[test]
28046    fn except_as_alias_cleanup_exception_table_matches_cpython() {
28047        let code = compile_exec(
28048            "\
28049def bug():
28050    try:
28051        1/0
28052    except Exception as e:
28053        tb = e.__traceback__
28054    return tb
28055",
28056        );
28057        let bug = find_code(&code, "bug").expect("missing bug code");
28058        let entries = bytecode::decode_exception_table(&bug.exceptiontable);
28059        let not_taken_idx = u32::try_from(
28060            bug.instructions
28061                .iter()
28062                .position(|unit| matches!(unit.op, Instruction::NotTaken))
28063                .expect("missing NOT_TAKEN"),
28064        )
28065        .unwrap();
28066        let alias_store_idx = not_taken_idx + 1;
28067        let copy_idx = u32::try_from(
28068            bug.instructions
28069                .iter()
28070                .position(|unit| {
28071                    matches!(
28072                        unit.op,
28073                        Instruction::Copy { i }
28074                            if i.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 3
28075                    )
28076                })
28077                .expect("missing outer cleanup COPY"),
28078        )
28079        .unwrap();
28080
28081        assert!(
28082            entries.iter().any(|entry| {
28083                entry.start <= not_taken_idx
28084                    && alias_store_idx < entry.end
28085                    && entry.target == copy_idx
28086                    && entry.depth == 1
28087                    && entry.push_lasti
28088            }),
28089            "CPython codegen_try_except() stores the exception alias before the inner SETUP_CLEANUP, so NOT_TAKEN and the alias store stay covered by the outer cleanup entry; entries={entries:?}, instructions={:?}",
28090            bug.instructions
28091        );
28092    }
28093
28094    #[test]
28095    fn try_except_while_body_preserves_while_exit_line_nop() {
28096        let code = compile_exec(
28097            "\
28098def f(x, E):
28099    try:
28100        while x:
28101            x -= 1
28102    except E:
28103        if not x:
28104            return None
28105    assert x
28106    return x
28107",
28108        );
28109        let f = find_code(&code, "f").expect("missing f code");
28110        let ops: Vec<_> = f
28111            .instructions
28112            .iter()
28113            .map(|unit| unit.op)
28114            .filter(|op| !matches!(op, Instruction::Cache))
28115            .collect();
28116        let assertion_error = ops
28117            .iter()
28118            .position(|op| matches!(op, Instruction::LoadCommonConstant { .. }))
28119            .expect("missing assertion error load");
28120
28121        assert!(
28122            ops[..assertion_error].windows(4).any(|window| {
28123                matches!(
28124                    window,
28125                    [
28126                        Instruction::JumpBackward { .. },
28127                        Instruction::Nop,
28128                        Instruction::LoadFastBorrow { .. },
28129                        Instruction::ToBool,
28130                    ]
28131                )
28132            }),
28133            "try/except while body should preserve CPython while-exit NOP before following assert, got ops={ops:?}"
28134        );
28135    }
28136
28137    #[test]
28138    fn constant_true_while_preserves_loop_line_nop() {
28139        let code = compile_exec(
28140            "\
28141def f(self, callback):
28142    i = 1
28143    while True:
28144        for j in 1, 2, 5:
28145            number = i * j
28146            if callback:
28147                callback(number, j)
28148            return number, j
28149",
28150        );
28151        let f = find_code(&code, "f").expect("missing f code");
28152        let ops: Vec<_> = f
28153            .instructions
28154            .iter()
28155            .map(|unit| unit.op)
28156            .filter(|op| !matches!(op, Instruction::Cache))
28157            .collect();
28158        let store_i = ops
28159            .iter()
28160            .position(|op| matches!(op, Instruction::StoreFast { .. }))
28161            .expect("missing i store");
28162
28163        assert!(
28164            matches!(ops.get(store_i + 1), Some(Instruction::Nop)),
28165            "constant-true while should keep CPython loop-line NOP after setup, got ops={ops:?}"
28166        );
28167    }
28168
28169    #[test]
28170    fn try_except_for_direct_break_preserves_normal_exhaustion_nop() {
28171        let code = compile_exec(
28172            "\
28173def f(xs, g, E):
28174    try:
28175        for x in xs:
28176            if x:
28177                break
28178            g()
28179    except E:
28180        pass
28181    g()
28182",
28183        );
28184        let f = find_code(&code, "f").expect("missing f code");
28185        let ops: Vec<_> = f
28186            .instructions
28187            .iter()
28188            .map(|unit| unit.op)
28189            .filter(|op| !matches!(op, Instruction::Cache))
28190            .collect();
28191
28192        assert!(
28193            ops.windows(4).any(|window| {
28194                matches!(
28195                    window,
28196                    [
28197                        Instruction::EndFor,
28198                        Instruction::PopIter,
28199                        Instruction::Nop,
28200                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
28201                    ]
28202                )
28203            }),
28204            "try/except for-body with direct break should keep CPython normal-exhaustion NOP, got ops={ops:?}"
28205        );
28206    }
28207
28208    #[test]
28209    fn try_except_for_without_direct_break_drops_normal_exhaustion_nop() {
28210        let code = compile_exec(
28211            "\
28212def f(xs, g, E):
28213    try:
28214        for x in xs:
28215            g()
28216    except E:
28217        pass
28218    g()
28219",
28220        );
28221        let f = find_code(&code, "f").expect("missing f code");
28222        let ops: Vec<_> = f
28223            .instructions
28224            .iter()
28225            .map(|unit| unit.op)
28226            .filter(|op| !matches!(op, Instruction::Cache))
28227            .collect();
28228
28229        assert!(
28230            !ops.windows(4).any(|window| {
28231                matches!(
28232                    window,
28233                    [
28234                        Instruction::EndFor,
28235                        Instruction::PopIter,
28236                        Instruction::Nop,
28237                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
28238                    ]
28239                )
28240            }),
28241            "try/except for-body without direct break should not keep a redundant normal-exhaustion NOP, got ops={ops:?}"
28242        );
28243    }
28244
28245    #[test]
28246    fn terminal_except_before_conditional_tail_uses_strong_load() {
28247        let code = compile_exec(
28248            "\
28249def f(self, Exception):
28250    try:
28251        tree = self.g()
28252    except Exception:
28253        return False
28254    if tree.body:
28255        return True
28256    return False
28257",
28258        );
28259        let f = find_code(&code, "f").expect("missing f code");
28260        let instructions: Vec<_> = f
28261            .instructions
28262            .iter()
28263            .filter(|unit| !matches!(unit.op, Instruction::Cache))
28264            .collect();
28265        let tree_load_before_body_attr = instructions.windows(2).find(|window| {
28266            let loads_tree = match window[0].op {
28267                Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
28268                    f.varnames
28269                        [usize::from(var_num.get(OpArg::new(u32::from(u8::from(window[0].arg)))))]
28270                        == "tree"
28271                }
28272                _ => false,
28273            };
28274            loads_tree && matches!(window[1].op, Instruction::LoadAttr { .. })
28275        });
28276
28277        assert!(
28278            matches!(
28279                tree_load_before_body_attr.map(|window| window[0].op),
28280                Some(Instruction::LoadFast { .. })
28281            ),
28282            "conditional tail after terminal except should match CPython's strong LOAD_FAST, got instructions={instructions:?}",
28283        );
28284    }
28285
28286    #[test]
28287    fn try_except_continuation_folded_tuple_drops_operand_nop() {
28288        let code = compile_exec(
28289            "\
28290def f():
28291    try:
28292        import sqlite3
28293    except ImportError:
28294        return
28295
28296    attributes = ('sqlite_version',)
28297",
28298        );
28299        let f = find_code(&code, "f").expect("missing f code");
28300        let ops: Vec<_> = f
28301            .instructions
28302            .iter()
28303            .map(|unit| unit.op)
28304            .filter(|op| !matches!(op, Instruction::Cache))
28305            .collect();
28306
28307        assert!(
28308            !ops.windows(4).any(|window| {
28309                matches!(
28310                    window,
28311                    [
28312                        Instruction::StoreFast { .. },
28313                        Instruction::Nop,
28314                        Instruction::LoadConst { .. },
28315                        Instruction::StoreFast { .. },
28316                    ]
28317                )
28318            }),
28319            "expected CPython nop_out-style folded tuple operand NOP to be removed, got ops={ops:?}",
28320        );
28321    }
28322
28323    #[test]
28324    fn if_else_normal_fallthrough_end_label_drops_return_anchor_nop() {
28325        let code = compile_exec(
28326            "\
28327def f(s):
28328    if s[0] in (0o200, 0o377):
28329        n = 0
28330        for i in range(len(s) - 1):
28331            n <<= 8
28332            n += s[i + 1]
28333        if s[0] == 0o377:
28334            n = -(256 ** (len(s) - 1) - n)
28335    else:
28336        try:
28337            s = nts(s, 'ascii', 'strict')
28338            n = int(s.strip() or '0', 8)
28339        except ValueError:
28340            raise InvalidHeaderError('invalid header')
28341    return n
28342",
28343        );
28344        let f = find_code(&code, "f").expect("missing f code");
28345        let ops: Vec<_> = f
28346            .instructions
28347            .iter()
28348            .map(|unit| unit.op)
28349            .filter(|op| !matches!(op, Instruction::Cache))
28350            .collect();
28351
28352        assert!(
28353            !ops.windows(4).any(|window| {
28354                matches!(
28355                    window,
28356                    [
28357                        Instruction::StoreFast { .. },
28358                        Instruction::Nop,
28359                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
28360                        Instruction::ReturnValue,
28361                    ]
28362                )
28363            }),
28364            "normal fallthrough into an if-end final return should not keep a CPython return-anchor NOP, got ops={ops:?}",
28365        );
28366    }
28367
28368    #[test]
28369    fn explicit_final_return_none_is_not_duplicated() {
28370        let code = compile_exec(
28371            "\
28372def f(src, dst, length, exception, bufsize):
28373    if length == 0:
28374        return
28375    if length is None:
28376        copyfileobj(src, dst, bufsize)
28377        return
28378
28379    blocks, remainder = divmod(length, bufsize)
28380    for b in range(blocks):
28381        buf = src.read(bufsize)
28382        if len(buf) < bufsize:
28383            raise exception('unexpected end of data')
28384        dst.write(buf)
28385
28386    if remainder != 0:
28387        buf = src.read(remainder)
28388        if len(buf) < remainder:
28389            raise exception('unexpected end of data')
28390        dst.write(buf)
28391    return
28392",
28393        );
28394        let f = find_code(&code, "f").expect("missing f code");
28395        let return_count = f
28396            .instructions
28397            .iter()
28398            .filter(|unit| matches!(unit.op, Instruction::ReturnValue))
28399            .count();
28400
28401        assert_eq!(
28402            return_count,
28403            3,
28404            "explicit final return None should not be duplicated as a synthetic no-location epilogue, got ops={:?}",
28405            f.instructions
28406                .iter()
28407                .map(|unit| unit.op)
28408                .collect::<Vec<_>>()
28409        );
28410    }
28411
28412    #[test]
28413    fn named_except_cleanup_keeps_jump_over_cleanup_and_next_try() {
28414        let code = compile_exec(
28415            r#"
28416def f(self):
28417    try:
28418        assert 0, 'msg'
28419    except AssertionError as e:
28420        self.assertEqual(e.args[0], 'msg')
28421    else:
28422        self.fail("AssertionError not raised by assert 0")
28423
28424    try:
28425        assert False
28426    except AssertionError as e:
28427        self.assertEqual(len(e.args), 0)
28428    else:
28429        self.fail("AssertionError not raised by 'assert False'")
28430"#,
28431        );
28432        let f = find_code(&code, "f").expect("missing f code");
28433        let ops: Vec<_> = f
28434            .instructions
28435            .iter()
28436            .map(|unit| unit.op)
28437            .filter(|op| !matches!(op, Instruction::Cache))
28438            .collect();
28439
28440        let first_pop_except = ops
28441            .iter()
28442            .position(|op| matches!(op, Instruction::PopExcept))
28443            .expect("missing POP_EXCEPT");
28444        let window = &ops[first_pop_except..(first_pop_except + 6).min(ops.len())];
28445        assert!(
28446            matches!(
28447                window,
28448                [
28449                    Instruction::PopExcept,
28450                    Instruction::LoadConst { .. },
28451                    Instruction::StoreName { .. } | Instruction::StoreFast { .. },
28452                    Instruction::DeleteName { .. } | Instruction::DeleteFast { .. },
28453                    Instruction::JumpForward { .. },
28454                    ..
28455                ]
28456            ),
28457            "expected named except cleanup to jump over cleanup reraise block, got ops={window:?}"
28458        );
28459    }
28460
28461    #[test]
28462    fn named_except_with_suppress_does_not_duplicate_following_with() {
28463        let code = compile_exec(
28464            "\
28465def f(StringIO, captured_output, print):
28466    try:
28467        raise KeyError
28468    except KeyError as e:
28469        with captured_output('stderr') as tbstderr:
28470            print('x')
28471        with captured_output('stderr') as tbstderr:
28472            print('y')
28473    else:
28474        print('else')
28475    s = StringIO()
28476    return s
28477",
28478        );
28479        let f = find_code(&code, "f").expect("missing f code");
28480        let load_y_count = f
28481            .instructions
28482            .iter()
28483            .filter(|unit| match unit.op {
28484                Instruction::LoadConst { consti } => {
28485                    matches!(
28486                        &f.constants
28487                            [consti.get(OpArg::new(u32::from(u8::from(unit.arg))))],
28488                        ConstantData::Str { value } if value.as_str() == Ok("y")
28489                    )
28490                }
28491                _ => false,
28492            })
28493            .count();
28494
28495        assert_eq!(
28496            load_y_count, 1,
28497            "following with body should not be duplicated into the previous with suppress path"
28498        );
28499    }
28500
28501    #[test]
28502    fn bare_except_deopts_post_handler_load_fast_borrow() {
28503        let code = compile_exec(
28504            "\
28505def f(self):
28506    try:
28507        1 / 0
28508    except:
28509        pass
28510    with self.assertRaises(SyntaxError):
28511        pass
28512",
28513        );
28514        let f = find_code(&code, "f").expect("missing f code");
28515        let ops: Vec<_> = f
28516            .instructions
28517            .iter()
28518            .map(|unit| unit.op)
28519            .filter(|op| !matches!(op, Instruction::Cache))
28520            .collect();
28521
28522        let attr_idx = ops
28523            .iter()
28524            .position(|op| matches!(op, Instruction::LoadAttr { .. }))
28525            .expect("missing LOAD_ATTR for assertRaises");
28526        assert!(
28527            matches!(ops.get(attr_idx - 1), Some(Instruction::LoadFast { .. })),
28528            "bare except tail should deopt self to LOAD_FAST, got ops={ops:?}"
28529        );
28530    }
28531
28532    #[test]
28533    fn bare_except_before_if_deopts_successor_load_fast_borrow() {
28534        let code = compile_exec(
28535            "\
28536def f(self, x):
28537    try:
28538        x = g()
28539    except:
28540        self.fail('raised')
28541    if x:
28542        self.fail('unexpected')
28543",
28544        );
28545        let f = find_code(&code, "f").expect("missing f code");
28546        let instructions: Vec<_> = f
28547            .instructions
28548            .iter()
28549            .filter(|unit| !matches!(unit.op, Instruction::Cache))
28550            .collect();
28551        let fail_loads = instructions
28552            .iter()
28553            .enumerate()
28554            .filter_map(|(idx, unit)| {
28555                let Instruction::LoadAttr { namei } = unit.op else {
28556                    return None;
28557                };
28558                let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
28559                (f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "fail")
28560                    .then_some(idx)
28561            })
28562            .collect::<Vec<_>>();
28563        assert!(
28564            fail_loads.len() >= 2,
28565            "expected handler and successor fail calls, got instructions={instructions:?}"
28566        );
28567        assert!(
28568            matches!(
28569                instructions.get(fail_loads[1] - 1).map(|unit| unit.op),
28570                Some(Instruction::LoadFast { .. })
28571            ),
28572            "CPython codegen_try_except() sends a fallthrough bare handler through USE_LABEL(end); flowgraph.c::optimize_load_fast() stops at that empty end label before the following if, got instructions={instructions:?}"
28573        );
28574    }
28575
28576    #[test]
28577    fn typed_except_keeps_post_handler_load_fast_borrow() {
28578        let code = compile_exec(
28579            "\
28580def f(self):
28581    try:
28582        1 / 0
28583    except ZeroDivisionError:
28584        pass
28585    with self.assertRaises(SyntaxError):
28586        pass
28587",
28588        );
28589        let f = find_code(&code, "f").expect("missing f code");
28590        let ops: Vec<_> = f
28591            .instructions
28592            .iter()
28593            .map(|unit| unit.op)
28594            .filter(|op| !matches!(op, Instruction::Cache))
28595            .collect();
28596
28597        let attr_idx = ops
28598            .iter()
28599            .position(|op| matches!(op, Instruction::LoadAttr { .. }))
28600            .expect("missing LOAD_ATTR for assertRaises");
28601        assert!(
28602            matches!(
28603                ops.get(attr_idx - 1),
28604                Some(Instruction::LoadFastBorrow { .. })
28605            ),
28606            "typed except tail should keep LOAD_FAST_BORROW, got ops={ops:?}"
28607        );
28608    }
28609
28610    #[test]
28611    fn bare_except_terminal_handler_store_subscr_tail_uses_strong_loads() {
28612        let code = compile_exec(
28613            "\
28614def f(g, cache, filename, mtime, result):
28615    try:
28616        module = g()
28617    except:
28618        return None
28619    cache[filename] = (mtime, result)
28620    return result
28621",
28622        );
28623        let f = find_code(&code, "f").expect("missing f code");
28624        let ops: Vec<_> = f
28625            .instructions
28626            .iter()
28627            .filter(|unit| !matches!(unit.op, Instruction::Cache))
28628            .collect();
28629        let is_pair = |unit: &&CodeUnit, left_name: &str, right_name: &str| {
28630            let Instruction::LoadFastLoadFast { var_nums } = unit.op else {
28631                return false;
28632            };
28633            let arg = OpArg::new(u32::from(u8::from(unit.arg)));
28634            let (left, right) = var_nums.get(arg).indexes();
28635            f.varnames[usize::from(left)] == left_name
28636                && f.varnames[usize::from(right)] == right_name
28637        };
28638        let is_borrow_pair = |unit: &&CodeUnit, left_name: &str, right_name: &str| {
28639            let Instruction::LoadFastBorrowLoadFastBorrow { var_nums } = unit.op else {
28640                return false;
28641            };
28642            let arg = OpArg::new(u32::from(u8::from(unit.arg)));
28643            let (left, right) = var_nums.get(arg).indexes();
28644            f.varnames[usize::from(left)] == left_name
28645                && f.varnames[usize::from(right)] == right_name
28646        };
28647
28648        assert!(
28649            ops.iter().any(|unit| is_pair(unit, "mtime", "result"))
28650                && ops.iter().any(|unit| is_pair(unit, "cache", "filename")),
28651            "CPython optimize_load_fast() stops at the empty try-end block for a terminal bare handler, got ops={ops:?}"
28652        );
28653        assert!(
28654            !ops.iter()
28655                .any(|unit| is_borrow_pair(unit, "mtime", "result")
28656                    || is_borrow_pair(unit, "cache", "filename")),
28657            "terminal bare handler post-try store tail should not be borrowed, got ops={ops:?}"
28658        );
28659    }
28660
28661    #[test]
28662    fn while_true_try_else_break_tail_uses_strong_loads() {
28663        let code = compile_exec(
28664            "\
28665def f(path, prefix, self, cache, read, E, stat):
28666    while True:
28667        try:
28668            st = stat(path)
28669        except E:
28670            path = path.dirname
28671        else:
28672            if st.mode:
28673                raise E
28674            break
28675    if path not in cache:
28676        cache[path] = read(path)
28677    self.archive = path
28678    self.prefix = prefix
28679",
28680        );
28681        let f = find_code(&code, "f").expect("missing f code");
28682        let ops: Vec<_> = f
28683            .instructions
28684            .iter()
28685            .filter(|unit| !matches!(unit.op, Instruction::Cache))
28686            .collect();
28687        let is_pair = |unit: &&CodeUnit, left_name: &str, right_name: &str| {
28688            let Instruction::LoadFastLoadFast { var_nums } = unit.op else {
28689                return false;
28690            };
28691            let arg = OpArg::new(u32::from(u8::from(unit.arg)));
28692            let (left, right) = var_nums.get(arg).indexes();
28693            f.varnames[usize::from(left)] == left_name
28694                && f.varnames[usize::from(right)] == right_name
28695        };
28696        let is_borrow_pair = |unit: &&CodeUnit, left_name: &str, right_name: &str| {
28697            let Instruction::LoadFastBorrowLoadFastBorrow { var_nums } = unit.op else {
28698                return false;
28699            };
28700            let arg = OpArg::new(u32::from(u8::from(unit.arg)));
28701            let (left, right) = var_nums.get(arg).indexes();
28702            f.varnames[usize::from(left)] == left_name
28703                && f.varnames[usize::from(right)] == right_name
28704        };
28705
28706        assert!(
28707            ops.iter().any(|unit| is_pair(unit, "path", "cache"))
28708                && ops.iter().any(|unit| is_pair(unit, "path", "self"))
28709                && ops.iter().any(|unit| is_pair(unit, "prefix", "self")),
28710            "CPython codegen_while() leaves an empty break end label that stops optimize_load_fast(), got ops={ops:?}"
28711        );
28712        assert!(
28713            !ops.iter().any(|unit| is_borrow_pair(unit, "path", "cache")
28714                || is_borrow_pair(unit, "path", "self")
28715                || is_borrow_pair(unit, "prefix", "self")),
28716            "while-true break successor should not be reached through a Rust-only fallthrough, got ops={ops:?}"
28717        );
28718    }
28719
28720    #[test]
28721    fn except_handler_resume_return_call_tail_keeps_borrow() {
28722        let code = compile_exec(
28723            "\
28724def f(class_cache, cls, KeyError, make, obj, lock, ctx):
28725    try:
28726        scls = class_cache[cls]
28727    except KeyError:
28728        scls = make(cls)
28729    return scls(obj, lock, ctx)
28730",
28731        );
28732        let f = find_code(&code, "f").expect("missing f code");
28733        let ops: Vec<_> = f
28734            .instructions
28735            .iter()
28736            .filter(|unit| !matches!(unit.op, Instruction::Cache))
28737            .collect();
28738        let borrows_name = |unit: &&CodeUnit, name: &str| match unit.op {
28739            Instruction::LoadFastBorrow { var_num } => {
28740                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
28741                f.varnames[usize::from(var_num.get(arg))] == name
28742            }
28743            Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
28744                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
28745                let (left, right) = var_nums.get(arg).indexes();
28746                f.varnames[usize::from(left)] == name || f.varnames[usize::from(right)] == name
28747            }
28748            _ => false,
28749        };
28750        let strong_loads_name = |unit: &&CodeUnit, name: &str| match unit.op {
28751            Instruction::LoadFast { var_num } => {
28752                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
28753                f.varnames[usize::from(var_num.get(arg))] == name
28754            }
28755            Instruction::LoadFastLoadFast { var_nums } => {
28756                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
28757                let (left, right) = var_nums.get(arg).indexes();
28758                f.varnames[usize::from(left)] == name || f.varnames[usize::from(right)] == name
28759            }
28760            _ => false,
28761        };
28762        let return_idx = ops
28763            .iter()
28764            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
28765            .expect("missing return");
28766        let tail = &ops[..return_idx];
28767
28768        for name in ["scls", "obj", "lock", "ctx"] {
28769            assert!(
28770                tail.iter().any(|unit| borrows_name(unit, name)),
28771                "handler resume to CPython codegen_try_except() end label should keep return-call {name} borrowed, got tail={tail:?}"
28772            );
28773            assert!(
28774                !tail.iter().any(|unit| strong_loads_name(unit, name)),
28775                "handler resume return-call tail should not be separated by a Rust-only empty end block for {name}, got tail={tail:?}"
28776            );
28777        }
28778    }
28779
28780    #[test]
28781    fn typed_except_named_handler_closure_tail_keeps_borrows() {
28782        let code = compile_exec(
28783            "\
28784def f(self):
28785    filename = TESTFN
28786    ICACLS = expandvars('icacls')
28787    try:
28788        check_output([ICACLS, filename])
28789    except CalledProcessError as ex:
28790        self.skipTest('Unable to create inaccessible file')
28791    def cleanup():
28792        check_output([ICACLS, filename])
28793    self.addCleanup(cleanup)
28794    stat1 = stat(filename)
28795    stat2 = stat(filename)
28796    self.assertEqual(stat1, stat2)
28797",
28798        );
28799        let f = find_code(&code, "f").expect("missing f code");
28800        let ops: Vec<_> = f
28801            .instructions
28802            .iter()
28803            .map(|unit| unit.op)
28804            .filter(|op| !matches!(op, Instruction::Cache))
28805            .collect();
28806        let make_function = ops
28807            .iter()
28808            .position(|op| matches!(op, Instruction::MakeFunction))
28809            .expect("missing MAKE_FUNCTION for cleanup closure");
28810        let handler_start = ops
28811            .iter()
28812            .position(|op| matches!(op, Instruction::PushExcInfo))
28813            .expect("missing handler entry");
28814        let tail = &ops[make_function.saturating_sub(2)..handler_start];
28815
28816        assert!(
28817            tail.iter().any(|op| {
28818                matches!(
28819                    op,
28820                    Instruction::LoadFastBorrow { .. }
28821                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
28822                )
28823            }),
28824            "typed except closure continuation should be visited by optimize_load_fast(), got tail={tail:?}",
28825        );
28826        assert!(
28827            !tail
28828                .iter()
28829                .any(|op| matches!(op, Instruction::LoadFast { .. })),
28830            "CPython codegen_try_except() uses USE_LABEL(end), so the handler continuation should be a shared passthrough and post-handler closure/tail loads should borrow; got tail={tail:?}",
28831        );
28832    }
28833
28834    #[test]
28835    fn named_terminal_raise_handler_keeps_return_pair_borrowed() {
28836        let code = compile_exec(
28837            "\
28838def f(factory, worker_json, test_name, stdout, E):
28839    try:
28840        result = factory(worker_json)
28841    except E as exc:
28842        raise RuntimeError(test_name, stdout, exc)
28843    return result, stdout
28844",
28845        );
28846        let f = find_code(&code, "f").expect("missing f code");
28847        let instructions: Vec<_> = f
28848            .instructions
28849            .iter()
28850            .filter(|unit| !matches!(unit.op, Instruction::Cache))
28851            .collect();
28852        let has_borrowed_pair = instructions.iter().any(|unit| {
28853            let Instruction::LoadFastBorrowLoadFastBorrow { var_nums } = unit.op else {
28854                return false;
28855            };
28856            let arg = OpArg::new(u32::from(u8::from(unit.arg)));
28857            let (left, right) = var_nums.get(arg).indexes();
28858            f.varnames[usize::from(left)] == "result" && f.varnames[usize::from(right)] == "stdout"
28859        });
28860
28861        assert!(
28862            has_borrowed_pair,
28863            "named terminal-raise handler should follow CPython codegen_try_except()/flowgraph.c cleanup reachability and keep return pair borrowed; got instructions={instructions:?}"
28864        );
28865    }
28866
28867    #[test]
28868    fn conditional_typed_except_return_join_keeps_borrow() {
28869        let code = compile_exec(
28870            "\
28871def f(cond, obj, xs, E):
28872    if cond:
28873        try:
28874            obj.m()
28875        except E:
28876            return 1
28877    for x in xs:
28878        obj.n(x)
28879    return obj
28880",
28881        );
28882        let f = find_code(&code, "f").expect("missing f code");
28883        let instructions: Vec<_> = f
28884            .instructions
28885            .iter()
28886            .filter(|unit| !matches!(unit.op, Instruction::Cache))
28887            .collect();
28888        let handler_start = instructions
28889            .iter()
28890            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
28891            .expect("missing handler entry");
28892        let normal_tail = &instructions[..handler_start];
28893        let load_name = |unit: &&CodeUnit| match unit.op {
28894            Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
28895                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
28896                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
28897            }
28898            _ => None,
28899        };
28900
28901        for name in ["xs", "obj", "x"] {
28902            assert!(
28903                normal_tail
28904                    .iter()
28905                    .any(|unit| matches!(unit.op, Instruction::LoadFastBorrow { .. })
28906                        && load_name(unit) == Some(name)),
28907                "conditional typed except return join should keep CPython-style borrowed {name} loads, got tail={normal_tail:?}"
28908            );
28909            assert!(
28910                !normal_tail
28911                    .iter()
28912                    .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })
28913                        && load_name(unit) == Some(name)),
28914                "conditional typed except return join should not force strong {name} loads, got tail={normal_tail:?}"
28915            );
28916        }
28917    }
28918
28919    #[test]
28920    fn typed_except_pass_resume_store_subscr_tail_keeps_borrows() {
28921        let code = compile_exec(
28922            "\
28923def f(self, sys, KeyError):
28924    mod_name = self.mod_name
28925    try:
28926        self._saved_module.append(sys.modules[mod_name])
28927    except KeyError:
28928        pass
28929    sys.modules[mod_name] = self.module
28930    return self
28931",
28932        );
28933        let f = find_code(&code, "f").expect("missing f code");
28934        let ops: Vec<_> = f
28935            .instructions
28936            .iter()
28937            .map(|unit| unit.op)
28938            .filter(|op| !matches!(op, Instruction::Cache))
28939            .collect();
28940
28941        let store_subscr_idx = ops
28942            .iter()
28943            .position(|op| matches!(op, Instruction::StoreSubscr))
28944            .expect("missing STORE_SUBSCR tail");
28945        assert!(
28946            matches!(
28947                ops.get(store_subscr_idx - 5),
28948                Some(Instruction::LoadFastBorrow { .. })
28949            ) && matches!(
28950                ops.get(store_subscr_idx - 3),
28951                Some(Instruction::LoadFastBorrow { .. })
28952            ) && matches!(
28953                ops.get(store_subscr_idx - 1),
28954                Some(Instruction::LoadFastBorrow { .. })
28955            ),
28956            "typed except pass tail should keep STORE_SUBSCR operands borrowed, got ops={ops:?}"
28957        );
28958        assert!(
28959            matches!(
28960                ops.get(store_subscr_idx + 1),
28961                Some(Instruction::LoadFastBorrow { .. })
28962            ),
28963            "typed except pass return should keep self borrowed, got ops={ops:?}"
28964        );
28965    }
28966
28967    #[test]
28968    fn reraising_typed_except_deopts_post_handler_loads() {
28969        let code = compile_exec(
28970            "\
28971def f(x, os, self, pid, exitcode):
28972    try:
28973        y = 1
28974    except RuntimeError:
28975        raise
28976    if x:
28977        os._exit(exitcode)
28978    self.wait_impl(pid, exitcode=exitcode)
28979",
28980        );
28981        let f = find_code(&code, "f").expect("missing f code");
28982        let ops: Vec<_> = f
28983            .instructions
28984            .iter()
28985            .map(|unit| unit.op)
28986            .filter(|op| !matches!(op, Instruction::Cache))
28987            .collect();
28988
28989        let guard_idx = ops
28990            .iter()
28991            .position(|op| matches!(op, Instruction::ToBool))
28992            .and_then(|idx| idx.checked_sub(1))
28993            .expect("missing post-handler bool guard");
28994        assert!(
28995            matches!(ops.get(guard_idx), Some(Instruction::LoadFast { .. })),
28996            "reraising typed except tail should deopt guard load, got ops={ops:?}"
28997        );
28998
28999        let wait_idx = ops
29000            .iter()
29001            .position(|op| matches!(op, Instruction::CallKw { .. }))
29002            .expect("missing wait_impl CALL_KW");
29003        let call_args = &ops[wait_idx.saturating_sub(3)..wait_idx];
29004        assert!(
29005            call_args.iter().any(|op| matches!(
29006                op,
29007                Instruction::LoadFastLoadFast { .. } | Instruction::LoadFast { .. }
29008            )),
29009            "reraising typed except tail should keep strong fast loads for call args, got ops={ops:?}"
29010        );
29011        assert!(
29012            !call_args.iter().any(|op| matches!(
29013                op,
29014                Instruction::LoadFastBorrowLoadFastBorrow { .. }
29015                    | Instruction::LoadFastBorrow { .. }
29016            )),
29017            "reraising typed except tail should not borrow call args, got ops={ops:?}"
29018        );
29019    }
29020
29021    #[test]
29022    fn reraising_outer_handler_keeps_explicit_raise_call_arg_borrow() {
29023        let code = compile_exec(
29024            "\
29025def f(file, os, stat, errno, self, fd):
29026    try:
29027        self._stat_atopen = os.fstat(fd)
29028        try:
29029            if stat.S_ISDIR(self._stat_atopen.st_mode):
29030                raise IsADirectoryError(errno.EISDIR, os.strerror(errno.EISDIR), file)
29031        except AttributeError:
29032            pass
29033        self.name = file
29034        try:
29035            os.lseek(fd, 0, SEEK_END)
29036        except OSError as e:
29037            if e.errno != errno.ESPIPE:
29038                raise
29039    except OSError:
29040        raise
29041",
29042        );
29043        let f = find_code(&code, "f").expect("missing f code");
29044        let instructions: Vec<_> = f
29045            .instructions
29046            .iter()
29047            .filter(|unit| !matches!(unit.op, Instruction::Cache))
29048            .collect();
29049        let file_borrow_before_raise_call = instructions.windows(3).any(|window| {
29050            let is_file_borrow = match window[0].op {
29051                Instruction::LoadFastBorrow { var_num } => {
29052                    f.varnames
29053                        [usize::from(var_num.get(OpArg::new(u32::from(u8::from(window[0].arg)))))]
29054                        == "file"
29055                }
29056                _ => false,
29057            };
29058            is_file_borrow
29059                && matches!(window[1].op, Instruction::Call { .. })
29060                && matches!(window[2].op, Instruction::RaiseVarargs { .. })
29061        });
29062
29063        assert!(
29064            file_borrow_before_raise_call,
29065            "outer reraising handler should not deopt explicit raise call args; CPython keeps file as LOAD_FAST_BORROW, got instructions={instructions:?}"
29066        );
29067    }
29068
29069    #[test]
29070    fn reraising_except_loop_backedge_keeps_loop_header_borrow() {
29071        let code = compile_exec(
29072            "\
29073def f(self, tag, expect_bye):
29074    while 1:
29075        result = self.tagged_commands[tag]
29076        if result is not None:
29077            del self.tagged_commands[tag]
29078            return result
29079        if expect_bye:
29080            typ = 'BYE'
29081            bye = self.untagged_responses.pop(typ, None)
29082            if bye is not None:
29083                return (typ, bye)
29084        self._check_bye()
29085        try:
29086            self._get_response()
29087        except self.abort as val:
29088            if __debug__:
29089                if self.debug >= 1:
29090                    self.print_log()
29091            raise
29092",
29093        );
29094        let f = find_code(&code, "f").expect("missing f code");
29095        let instructions: Vec<_> = f
29096            .instructions
29097            .iter()
29098            .filter(|unit| !matches!(unit.op, Instruction::Cache))
29099            .collect();
29100        let handler_start = instructions
29101            .iter()
29102            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
29103            .expect("missing handler entry");
29104        let warm_ops: Vec<_> = instructions[..handler_start]
29105            .iter()
29106            .map(|unit| unit.op)
29107            .collect();
29108
29109        assert!(
29110            warm_ops.iter().any(|op| matches!(
29111                op,
29112                Instruction::LoadFastBorrow { .. }
29113                    | Instruction::LoadFastBorrowLoadFastBorrow { .. }
29114            )),
29115            "expected loop body before reraising handler to keep borrowed loads, got ops={warm_ops:?}"
29116        );
29117        assert!(
29118            warm_ops
29119                .iter()
29120                .all(|op| !matches!(op, Instruction::LoadFast { .. })),
29121            "loop backedge into reraising handler should not deopt warm loop loads, got ops={warm_ops:?}"
29122        );
29123    }
29124
29125    #[test]
29126    fn protected_store_break_handler_deopts_bool_guard_tail() {
29127        let code = compile_exec(
29128            "\
29129def f(self, size):
29130    parts = []
29131    while size > 0:
29132        try:
29133            buf = self.sock.recv(DEFAULT_BUFFER_SIZE)
29134        except ConnectionError:
29135            break
29136        if not buf:
29137            break
29138        self._readbuf.append(buf)
29139        size -= len(buf)
29140    return b''.join(parts)
29141",
29142        );
29143        let f = find_code(&code, "f").expect("missing f code");
29144        let instructions: Vec<_> = f
29145            .instructions
29146            .iter()
29147            .filter(|unit| !matches!(unit.op, Instruction::Cache))
29148            .collect();
29149        let guard_bool = instructions
29150            .iter()
29151            .position(|unit| matches!(unit.op, Instruction::ToBool))
29152            .expect("missing bool guard");
29153        let store_buf = instructions[..guard_bool]
29154            .iter()
29155            .rposition(|unit| matches!(unit.op, Instruction::StoreFast { .. }))
29156            .expect("missing protected STORE_FAST before bool guard");
29157        let guard_load = instructions[store_buf + 1].op;
29158        let append_call = instructions[store_buf + 1..]
29159            .iter()
29160            .position(|unit| matches!(unit.op, Instruction::Call { .. }))
29161            .map(|idx| idx + store_buf + 1)
29162            .expect("missing append call");
29163        let append_arg = instructions[append_call - 1].op;
29164
29165        assert!(
29166            matches!(guard_load, Instruction::LoadFast { .. }),
29167            "CPython uses strong LOAD_FAST for protected-store break guard, got ops={:?}",
29168            instructions.iter().map(|unit| unit.op).collect::<Vec<_>>()
29169        );
29170        assert!(
29171            matches!(append_arg, Instruction::LoadFast { .. }),
29172            "CPython uses strong LOAD_FAST for protected-store append arg, got ops={:?}",
29173            instructions.iter().map(|unit| unit.op).collect::<Vec<_>>()
29174        );
29175    }
29176
29177    #[test]
29178    fn assertion_success_join_keeps_following_debug_tail_borrowed() {
29179        let code = compile_exec(
29180            "\
29181def f(self, typ, dat):
29182    if self._idle_capture:
29183        if self._idle_responses:
29184            response = self._idle_responses[-1]
29185            assert response[0] == typ
29186            response[1].append(dat)
29187        else:
29188            self._idle_responses.append((typ, [dat]))
29189        if self.debug >= 5:
29190            self._mesg(f'idle: queue untagged {typ} {dat!r}')
29191        return
29192",
29193        );
29194        let f = find_code(&code, "f").expect("missing f code");
29195        let instructions: Vec<_> = f
29196            .instructions
29197            .iter()
29198            .filter(|unit| !matches!(unit.op, Instruction::Cache))
29199            .collect();
29200        let debug_attr = instructions
29201            .iter()
29202            .position(|unit| match unit.op {
29203                Instruction::LoadAttr { namei } => {
29204                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
29205                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "debug"
29206                }
29207                _ => false,
29208            })
29209            .expect("missing debug LOAD_ATTR");
29210        let mesg_attr = instructions
29211            .iter()
29212            .position(|unit| match unit.op {
29213                Instruction::LoadAttr { namei } => {
29214                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
29215                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "_mesg"
29216                }
29217                _ => false,
29218            })
29219            .expect("missing _mesg LOAD_ATTR");
29220
29221        assert!(
29222            matches!(
29223                instructions[debug_attr - 1].op,
29224                Instruction::LoadFastBorrow { .. }
29225            ),
29226            "CPython keeps LOAD_FAST_BORROW after assertion success join, got ops={:?}",
29227            instructions.iter().map(|unit| unit.op).collect::<Vec<_>>()
29228        );
29229        assert!(
29230            matches!(
29231                instructions[mesg_attr - 1].op,
29232                Instruction::LoadFastBorrow { .. }
29233            ),
29234            "CPython keeps LOAD_FAST_BORROW in assertion-success debug body, got ops={:?}",
29235            instructions.iter().map(|unit| unit.op).collect::<Vec<_>>()
29236        );
29237    }
29238
29239    #[test]
29240    fn multi_protected_method_call_terminal_handler_keeps_try_body_borrows() {
29241        let code = compile_exec(
29242            "\
29243def f(self, literal):
29244    try:
29245        self.send(literal)
29246        self.send(CRLF)
29247    except OSError as val:
29248        raise self.abort('socket error: %s' % val)
29249",
29250        );
29251        let f = find_code(&code, "f").expect("missing f code");
29252        let instructions: Vec<_> = f
29253            .instructions
29254            .iter()
29255            .filter(|unit| !matches!(unit.op, Instruction::Cache))
29256            .collect();
29257        let first_send = instructions
29258            .iter()
29259            .position(|unit| match unit.op {
29260                Instruction::LoadAttr { namei } => {
29261                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
29262                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "send"
29263                }
29264                _ => false,
29265            })
29266            .expect("missing send LOAD_ATTR");
29267        let first_literal = instructions[first_send + 1].op;
29268        let second_send = instructions[first_send + 1..]
29269            .iter()
29270            .position(|unit| match unit.op {
29271                Instruction::LoadAttr { namei } => {
29272                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
29273                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "send"
29274                }
29275                _ => false,
29276            })
29277            .map(|idx| idx + first_send + 1)
29278            .expect("missing second send LOAD_ATTR");
29279
29280        assert!(
29281            matches!(
29282                instructions[first_send - 1].op,
29283                Instruction::LoadFastBorrow { .. }
29284            ),
29285            "CPython keeps first protected send receiver borrowed, got ops={:?}",
29286            instructions.iter().map(|unit| unit.op).collect::<Vec<_>>()
29287        );
29288        assert!(
29289            matches!(first_literal, Instruction::LoadFastBorrow { .. }),
29290            "CPython keeps first protected send arg borrowed, got ops={:?}",
29291            instructions.iter().map(|unit| unit.op).collect::<Vec<_>>()
29292        );
29293        assert!(
29294            matches!(
29295                instructions[second_send - 1].op,
29296                Instruction::LoadFastBorrow { .. }
29297            ),
29298            "CPython keeps second protected send receiver borrowed, got ops={:?}",
29299            instructions.iter().map(|unit| unit.op).collect::<Vec<_>>()
29300        );
29301    }
29302
29303    #[test]
29304    fn dunder_debug_constant_false_if_deopts_tail_borrow() {
29305        let code = compile_exec(
29306            "\
29307def f(self):
29308    if not __debug__:
29309        self.skipTest('need asserts, run without -O')
29310    self.do_disassembly_test()
29311",
29312        );
29313        let f = find_code(&code, "f").expect("missing f code");
29314        let instructions: Vec<_> = f
29315            .instructions
29316            .iter()
29317            .filter(|unit| !matches!(unit.op, Instruction::Cache))
29318            .collect();
29319        let attr_idx = instructions
29320            .iter()
29321            .position(|unit| match unit.op {
29322                Instruction::LoadAttr { namei } => {
29323                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
29324                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
29325                        == "do_disassembly_test"
29326                }
29327                _ => false,
29328            })
29329            .expect("missing LOAD_ATTR for do_disassembly_test");
29330        let ops: Vec<_> = instructions.iter().map(|unit| unit.op).collect();
29331        assert!(
29332            matches!(ops.get(attr_idx - 1), Some(Instruction::LoadFast { .. })),
29333            "constant-false __debug__ tail should deopt self to LOAD_FAST, got ops={ops:?}"
29334        );
29335    }
29336
29337    #[test]
29338    fn constant_slice_folds_constant_bounds() {
29339        let code = compile_exec(
29340            "\
29341def f(obj):
29342    return obj['a':123456789012345678901234567890]
29343",
29344        );
29345        let f = find_code(&code, "f").expect("missing function code");
29346        let ops: Vec<_> = f
29347            .instructions
29348            .iter()
29349            .map(|unit| unit.op)
29350            .filter(|op| !matches!(op, Instruction::Cache))
29351            .collect();
29352        let folded_slice = f
29353            .constants
29354            .iter()
29355            .find_map(|constant| match constant {
29356                ConstantData::Slice { elements } => Some(elements),
29357                _ => None,
29358            })
29359            .expect("missing folded slice constant");
29360        assert!(
29361            matches!(
29362                folded_slice.as_ref(),
29363                [
29364                    ConstantData::Str { .. },
29365                    ConstantData::Integer { .. },
29366                    ConstantData::None,
29367                ]
29368            ),
29369            "expected folded slice('a', 123456789012345678901234567890, None), got {folded_slice:?}"
29370        );
29371        assert!(
29372            matches!(
29373                ops.as_slice(),
29374                [
29375                    Instruction::Resume { .. },
29376                    Instruction::LoadFastBorrow { .. },
29377                    Instruction::LoadConst { .. },
29378                    Instruction::BinaryOp { .. },
29379                    Instruction::ReturnValue,
29380                ]
29381            ),
29382            "expected CPython-style LOAD_CONST(slice(...)) path for constant bounds, got ops={ops:?}"
29383        );
29384    }
29385
29386    #[test]
29387    fn negative_step_slice_uses_build_slice() {
29388        let code = compile_exec(
29389            "\
29390def f(obj):
29391    return obj[::-1]
29392",
29393        );
29394        let f = find_code(&code, "f").expect("missing function code");
29395        let ops: Vec<_> = f
29396            .instructions
29397            .iter()
29398            .map(|unit| unit.op)
29399            .filter(|op| !matches!(op, Instruction::Cache))
29400            .collect();
29401
29402        assert!(
29403            matches!(
29404                ops.as_slice(),
29405                [
29406                    Instruction::Resume { .. },
29407                    Instruction::LoadFastBorrow { .. },
29408                    Instruction::LoadConst { .. },
29409                    Instruction::LoadConst { .. },
29410                    Instruction::LoadConst { .. },
29411                    Instruction::BuildSlice { .. },
29412                    Instruction::BinaryOp { .. },
29413                    Instruction::ReturnValue,
29414                ]
29415            ),
29416            "expected CPython-style BUILD_SLICE path for non-literal negative step, got ops={ops:?}"
29417        );
29418    }
29419
29420    #[test]
29421    fn slice_none_bounds_and_build_slice_use_slice_location_like_cpython() {
29422        let code = compile_exec(
29423            "\
29424def f(obj, step):
29425    return obj[::step]
29426",
29427        );
29428        let f = find_code(&code, "f").expect("missing function code");
29429        let slice_positions: Vec<_> = f
29430            .instructions
29431            .iter()
29432            .zip(&f.locations)
29433            .filter_map(|(unit, (location, end_location))| {
29434                let op = match unit.op {
29435                    Instruction::LoadConst { .. } => "LOAD_CONST",
29436                    Instruction::BuildSlice { .. } => "BUILD_SLICE",
29437                    _ => return None,
29438                };
29439                Some((
29440                    op,
29441                    location.line.get(),
29442                    location.character_offset.get(),
29443                    end_location.line.get(),
29444                    end_location.character_offset.get(),
29445                ))
29446            })
29447            .collect();
29448
29449        assert_eq!(
29450            slice_positions,
29451            vec![
29452                ("LOAD_CONST", 2, 16, 2, 22),
29453                ("LOAD_CONST", 2, 16, 2, 22),
29454                ("BUILD_SLICE", 2, 16, 2, 22),
29455            ],
29456            "CPython codegen_slice() emits missing bounds and BUILD_SLICE at LOC(slice)"
29457        );
29458    }
29459
29460    #[test]
29461    fn bool_int_binop_constants_fold() {
29462        let code = compile_exec(
29463            "\
29464def f():
29465    return False + 2, True + 2, False + False, True / 1, True & False
29466
29467def g():
29468    return False + 2
29469",
29470        );
29471        let f = find_code(&code, "f").expect("missing function code");
29472        let ops: Vec<_> = f
29473            .instructions
29474            .iter()
29475            .map(|unit| unit.op)
29476            .filter(|op| !matches!(op, Instruction::Cache))
29477            .collect();
29478
29479        assert!(
29480            !ops.iter()
29481                .any(|op| matches!(op, Instruction::BinaryOp { .. })),
29482            "expected CPython-style folded bool/int binops, got ops={ops:?}"
29483        );
29484        assert!(
29485            matches!(
29486                ops.as_slice(),
29487                [
29488                    Instruction::Resume { .. },
29489                    Instruction::LoadConst { .. },
29490                    Instruction::ReturnValue
29491                ]
29492            ),
29493            "expected folded constants for bool/int binops, got ops={ops:?}"
29494        );
29495
29496        let g = find_code(&code, "g").expect("missing function code");
29497        let g_ops: Vec<_> = g
29498            .instructions
29499            .iter()
29500            .map(|unit| unit.op)
29501            .filter(|op| !matches!(op, Instruction::Cache))
29502            .collect();
29503        assert!(
29504            !g_ops
29505                .iter()
29506                .any(|op| matches!(op, Instruction::BinaryOp { .. })),
29507            "expected top-level bool/int binop to fold, got ops={g_ops:?}"
29508        );
29509    }
29510
29511    #[test]
29512    fn double_not_expression_folds_to_bool_conversion() {
29513        let code = compile_exec(
29514            "\
29515def f(x):
29516    return not not x
29517",
29518        );
29519        let f = find_code(&code, "f").expect("missing function code");
29520        let ops: Vec<_> = f
29521            .instructions
29522            .iter()
29523            .map(|unit| unit.op)
29524            .filter(|op| !matches!(op, Instruction::Cache))
29525            .collect();
29526
29527        assert!(
29528            matches!(
29529                ops.as_slice(),
29530                [
29531                    Instruction::Resume { .. },
29532                    Instruction::LoadFastBorrow { .. },
29533                    Instruction::ToBool,
29534                    Instruction::ReturnValue,
29535                ]
29536            ),
29537            "expected CPython-style double-not bool conversion, got ops={ops:?}"
29538        );
29539    }
29540
29541    #[test]
29542    fn tuple_bound_slice_uses_two_part_slice_path() {
29543        let code = compile_exec(
29544            "\
29545def f(obj):
29546    return obj[(1, 2):]
29547",
29548        );
29549        let f = find_code(&code, "f").expect("missing function code");
29550        let ops: Vec<_> = f
29551            .instructions
29552            .iter()
29553            .map(|unit| unit.op)
29554            .filter(|op| !matches!(op, Instruction::Cache))
29555            .collect();
29556
29557        assert!(
29558            matches!(
29559                ops.as_slice(),
29560                [
29561                    Instruction::Resume { .. },
29562                    Instruction::LoadFastBorrow { .. },
29563                    Instruction::LoadConst { .. },
29564                    Instruction::LoadConst { .. },
29565                    Instruction::BinarySlice,
29566                    Instruction::ReturnValue,
29567                ]
29568            ),
29569            "expected CPython-style BINARY_SLICE path for tuple lower bound, got ops={ops:?}"
29570        );
29571    }
29572
29573    #[test]
29574    fn exception_cleanup_jump_to_return_is_inlined() {
29575        let code = compile_exec(
29576            "\
29577def f(names, cls):
29578    try:
29579        cls.attr = names
29580    except:
29581        pass
29582    return names
29583",
29584        );
29585        let f = find_code(&code, "f").expect("missing function code");
29586        let return_count = f
29587            .instructions
29588            .iter()
29589            .filter(|unit| matches!(unit.op, Instruction::ReturnValue))
29590            .count();
29591
29592        assert_eq!(
29593            return_count, 2,
29594            "expected CPython-style distinct return sites for normal and except paths"
29595        );
29596    }
29597
29598    #[test]
29599    fn except_break_preserves_plain_jump_when_inlining_no_lineno_tail() {
29600        let code = compile_exec(
29601            "\
29602def f(compiler_so, cc_args):
29603    strip_sysroot = True
29604    if '-arch' in cc_args:
29605        while True:
29606            try:
29607                index = compiler_so.index('-arch')
29608                del compiler_so[index:index + 2]
29609            except ValueError:
29610                break
29611    if strip_sysroot:
29612        while True:
29613            indices = [i for i, x in enumerate(compiler_so) if x.startswith('-isysroot')]
29614            if not indices:
29615                break
29616            index = indices[0]
29617            del compiler_so[index:index + 1]
29618    return compiler_so
29619",
29620        );
29621        let f = find_code(&code, "f").expect("missing function code");
29622        let ops: Vec<_> = f
29623            .instructions
29624            .iter()
29625            .map(|unit| unit.op)
29626            .filter(|op| !matches!(op, Instruction::Cache))
29627            .collect();
29628
29629        assert!(
29630            ops.windows(2).any(|window| {
29631                matches!(
29632                    window,
29633                    [Instruction::PopExcept, Instruction::JumpBackward { .. }]
29634                )
29635            }) && !ops.windows(2).any(|window| {
29636                matches!(
29637                    window,
29638                    [
29639                        Instruction::PopExcept,
29640                        Instruction::JumpBackwardNoInterrupt { .. }
29641                    ]
29642                )
29643            }),
29644            "except-break cleanup should preserve CPython's plain JUMP when a no-lineno tail is inlined, got ops={ops:?}"
29645        );
29646    }
29647
29648    #[test]
29649    fn nested_with_bare_except_keeps_handler_cleanup_before_following_code() {
29650        let code = compile_exec(
29651            "\
29652def f(cm, self):
29653    try:
29654        with cm:
29655            raise Exception
29656    except:
29657        pass
29658    self.g()
29659",
29660        );
29661        let f = find_code(&code, "f").expect("missing function code");
29662        let ops: Vec<_> = f
29663            .instructions
29664            .iter()
29665            .map(|unit| unit.op)
29666            .filter(|op| !matches!(op, Instruction::Cache))
29667            .collect();
29668
29669        let outer_handler = ops
29670            .iter()
29671            .enumerate()
29672            .filter_map(|(idx, op)| matches!(op, Instruction::PushExcInfo).then_some(idx))
29673            .next_back()
29674            .expect("missing outer handler");
29675        assert!(
29676            ops[outer_handler..].windows(6).any(|window| {
29677                matches!(
29678                    window,
29679                    [
29680                        Instruction::PopExcept,
29681                        Instruction::JumpForward { .. },
29682                        Instruction::Copy { .. },
29683                        Instruction::PopExcept,
29684                        Instruction::Reraise { .. },
29685                        Instruction::LoadFast { .. },
29686                    ]
29687                )
29688            }),
29689            "expected CPython-style handler cleanup before following code, got ops={ops:?}"
29690        );
29691    }
29692
29693    #[test]
29694    fn try_else_for_cleanup_drops_redundant_jump_nop() {
29695        let code = compile_exec(
29696            "\
29697def f(self, xs, ys, cm1, cm2):
29698    for x in xs:
29699        with self.subTest(x=x):
29700            try:
29701                with cm1:
29702                    self.a()
29703            except Exception:
29704                if x:
29705                    pass
29706                else:
29707                    raise
29708            else:
29709                for y in ys:
29710                    with self.subTest(y=y):
29711                        with cm2:
29712                            self.b()
29713",
29714        );
29715        let f = find_code(&code, "f").expect("missing function code");
29716        let ops: Vec<_> = f
29717            .instructions
29718            .iter()
29719            .map(|unit| unit.op)
29720            .filter(|op| !matches!(op, Instruction::Cache))
29721            .collect();
29722
29723        assert!(
29724            ops.windows(7).any(|window| {
29725                matches!(
29726                    window,
29727                    [
29728                        Instruction::EndFor,
29729                        Instruction::PopIter,
29730                        Instruction::LoadConst { .. },
29731                        Instruction::LoadConst { .. },
29732                        Instruction::LoadConst { .. },
29733                        Instruction::Call { .. },
29734                        Instruction::PopTop,
29735                    ]
29736                )
29737            }),
29738            "expected inner for cleanup to fall directly into surrounding with cleanup, got ops={ops:?}",
29739        );
29740        assert!(
29741            !ops.windows(8).any(|window| {
29742                matches!(
29743                    window,
29744                    [
29745                        Instruction::EndFor,
29746                        Instruction::PopIter,
29747                        Instruction::Nop,
29748                        Instruction::LoadConst { .. },
29749                        Instruction::LoadConst { .. },
29750                        Instruction::LoadConst { .. },
29751                        Instruction::Call { .. },
29752                        Instruction::PopTop,
29753                    ]
29754                )
29755            }),
29756            "expected CPython-style removal of the redundant jump NOP after for cleanup, got ops={ops:?}",
29757        );
29758    }
29759
29760    #[test]
29761    fn non_none_final_return_is_not_duplicated() {
29762        let code = compile_exec(
29763            "\
29764def f(p, s):
29765    if p == '':
29766        if s == '':
29767            return 0
29768    return -1
29769",
29770        );
29771        let f = find_code(&code, "f").expect("missing function code");
29772        let minus_one_loads = f
29773            .instructions
29774            .iter()
29775            .filter(|unit| {
29776                matches!(
29777                    unit.op,
29778                    Instruction::LoadConst { consti }
29779                        if matches!(
29780                            f.constants.get(
29781                                consti
29782                                    .get(OpArg::new(u32::from(u8::from(unit.arg))))
29783                                    .as_usize()
29784                            ),
29785                            Some(ConstantData::Integer { value }) if value == &BigInt::from(-1)
29786                        )
29787                )
29788            })
29789            .count();
29790
29791        assert_eq!(
29792            minus_one_loads,
29793            1,
29794            "expected a single final return -1 epilogue, got ops={:?}",
29795            f.instructions
29796                .iter()
29797                .map(|unit| unit.op)
29798                .collect::<Vec<_>>()
29799        );
29800    }
29801
29802    #[test]
29803    fn for_return_unary_constant_preserves_value_over_iterator_cleanup() {
29804        let code = compile_exec(
29805            "\
29806def f(xs):
29807    for x in xs:
29808        return -1
29809",
29810        );
29811        let f = find_code(&code, "f").expect("missing function code");
29812        let units: Vec<_> = f
29813            .instructions
29814            .iter()
29815            .filter(|unit| !matches!(unit.op, Instruction::Cache))
29816            .collect();
29817
29818        assert!(
29819            units.windows(4).any(|window| {
29820                matches!(
29821                    window[0].op,
29822                    Instruction::LoadConst { .. } | Instruction::LoadSmallInt { .. }
29823                ) && matches!(
29824                    window[1].op,
29825                    Instruction::Swap { i }
29826                        if i.get(OpArg::new(u32::from(u8::from(window[1].arg)))) == 2
29827                ) && matches!(window[2].op, Instruction::PopTop)
29828                    && matches!(window[3].op, Instruction::ReturnValue)
29829            }),
29830            "expected CPython-style LOAD_CONST/SWAP/POP_TOP/RETURN_VALUE cleanup, got units={units:?}"
29831        );
29832    }
29833
29834    #[test]
29835    fn try_else_if_return_keeps_conditional_target_nop() {
29836        let code = compile_exec(
29837            "\
29838def f(cond):
29839    try:
29840        x = cond
29841    except E:
29842        pass
29843    else:
29844        if x:
29845            return 1
29846    return 2
29847",
29848        );
29849        let f = find_code(&code, "f").expect("missing function code");
29850        let ops: Vec<_> = f
29851            .instructions
29852            .iter()
29853            .map(|unit| unit.op)
29854            .filter(|op| !matches!(op, Instruction::Cache))
29855            .collect();
29856
29857        let has_cpython_nop_target = ops.windows(5).any(|window| {
29858            matches!(
29859                window,
29860                [
29861                    Instruction::LoadSmallInt { .. } | Instruction::LoadConst { .. },
29862                    Instruction::ReturnValue,
29863                    Instruction::Nop,
29864                    Instruction::LoadSmallInt { .. } | Instruction::LoadConst { .. },
29865                    Instruction::ReturnValue,
29866                ]
29867            )
29868        });
29869        assert!(
29870            has_cpython_nop_target,
29871            "expected CPython-style try-else conditional target NOP, got ops={ops:?}"
29872        );
29873    }
29874
29875    #[test]
29876    fn try_else_nested_if_return_drops_inner_conditional_target_nop() {
29877        let code = compile_exec(
29878            "\
29879def f(obj, Sig):
29880    try:
29881        sig = obj.__signature__
29882    except AttributeError:
29883        pass
29884    else:
29885        if sig is not None:
29886            if not isinstance(sig, Sig):
29887                raise TypeError(sig)
29888            return sig
29889    return obj
29890",
29891        );
29892        let f = find_code(&code, "f").expect("missing function code");
29893        let ops: Vec<_> = f
29894            .instructions
29895            .iter()
29896            .map(|unit| unit.op)
29897            .filter(|op| !matches!(op, Instruction::Cache))
29898            .collect();
29899
29900        assert!(
29901            !ops.windows(4).any(|window| {
29902                matches!(
29903                    window,
29904                    [
29905                        Instruction::RaiseVarargs { .. },
29906                        Instruction::Nop,
29907                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
29908                        Instruction::ReturnValue,
29909                    ]
29910                )
29911            }),
29912            "inner try-else if target should not materialize as a NOP before the return, got ops={ops:?}"
29913        );
29914        assert!(
29915            ops.windows(4).any(|window| {
29916                matches!(
29917                    window,
29918                    [
29919                        Instruction::RaiseVarargs { .. },
29920                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
29921                        Instruction::ReturnValue,
29922                        Instruction::Nop,
29923                    ]
29924                )
29925            }),
29926            "expected only the outer try-else if false target after the return, got ops={ops:?}"
29927        );
29928    }
29929
29930    #[test]
29931    fn try_else_nested_final_if_return_drops_nested_conditional_target_nop() {
29932        let code = compile_exec(
29933            "\
29934def f(cond, outer):
29935    try:
29936        x = cond
29937    except E:
29938        pass
29939    else:
29940        if outer:
29941            if x:
29942                return 1
29943    return 2
29944",
29945        );
29946        let f = find_code(&code, "f").expect("missing function code");
29947        let ops: Vec<_> = f
29948            .instructions
29949            .iter()
29950            .map(|unit| unit.op)
29951            .filter(|op| !matches!(op, Instruction::Cache))
29952            .collect();
29953
29954        assert!(
29955            !ops.windows(4).any(|window| {
29956                matches!(
29957                    window,
29958                    [
29959                        Instruction::ReturnValue,
29960                        Instruction::Nop,
29961                        Instruction::LoadSmallInt { .. } | Instruction::LoadConst { .. },
29962                        Instruction::ReturnValue,
29963                    ]
29964                )
29965            }),
29966            "nested try-else conditional target should fall through directly to following code, got ops={ops:?}"
29967        );
29968    }
29969
29970    #[test]
29971    fn named_except_conditional_branch_duplicates_cleanup_return() {
29972        let code = compile_exec(
29973            "\
29974def f(self):
29975    try:
29976        raise TypeError('x')
29977    except TypeError as e:
29978        if '+' not in str(e):
29979            self.fail('join() ate exception message')
29980",
29981        );
29982        let f = find_code(&code, "f").expect("missing function code");
29983        let ops: Vec<_> = f
29984            .instructions
29985            .iter()
29986            .map(|unit| unit.op)
29987            .filter(|op| !matches!(op, Instruction::Cache))
29988            .collect();
29989
29990        let cleanup_return_count = ops
29991            .windows(6)
29992            .filter(|window| {
29993                matches!(
29994                    window,
29995                    [
29996                        Instruction::PopExcept,
29997                        Instruction::LoadConst { .. },
29998                        Instruction::StoreFast { .. } | Instruction::StoreName { .. },
29999                        Instruction::DeleteFast { .. } | Instruction::DeleteName { .. },
30000                        Instruction::LoadConst { .. },
30001                        Instruction::ReturnValue,
30002                    ]
30003                )
30004            })
30005            .count();
30006
30007        assert_eq!(
30008            cleanup_return_count, 2,
30009            "expected duplicated named-except cleanup return blocks, got ops={ops:?}"
30010        );
30011    }
30012
30013    #[test]
30014    fn named_except_conditional_before_explicit_return_shares_cleanup_return() {
30015        let code = compile_exec(
30016            "\
30017def f(onerror, err, OSError):
30018    try:
30019        x()
30020    except OSError as err:
30021        if onerror is not None:
30022            onerror(err)
30023        return
30024",
30025        );
30026        let f = find_code(&code, "f").expect("missing function code");
30027        let ops: Vec<_> = f
30028            .instructions
30029            .iter()
30030            .map(|unit| unit.op)
30031            .filter(|op| !matches!(op, Instruction::Cache))
30032            .collect();
30033
30034        let cleanup_return_count = ops
30035            .windows(6)
30036            .filter(|window| {
30037                matches!(
30038                    window,
30039                    [
30040                        Instruction::PopExcept,
30041                        Instruction::LoadConst { .. },
30042                        Instruction::StoreFast { .. } | Instruction::StoreName { .. },
30043                        Instruction::DeleteFast { .. } | Instruction::DeleteName { .. },
30044                        Instruction::LoadConst { .. },
30045                        Instruction::ReturnValue,
30046                    ]
30047                )
30048            })
30049            .count();
30050
30051        assert_eq!(
30052            cleanup_return_count, 1,
30053            "explicit return after the conditional should share the named-except cleanup return block, got ops={ops:?}"
30054        );
30055    }
30056
30057    #[test]
30058    fn named_except_boolop_condition_shares_cleanup_return() {
30059        let code = compile_exec(
30060            "\
30061def f(self, module_name, ModuleNotFoundError):
30062    try:
30063        return importlib.import_module(module_name)
30064    except ModuleNotFoundError as error:
30065        if self._warn_on_extension_import and module_name in builtin_hashes:
30066            logging.getLogger(__name__).warning('msg', error, exc_info=error)
30067    return None
30068",
30069        );
30070        let f = find_code(&code, "f").expect("missing function code");
30071        let ops: Vec<_> = f
30072            .instructions
30073            .iter()
30074            .map(|unit| unit.op)
30075            .filter(|op| !matches!(op, Instruction::Cache))
30076            .collect();
30077
30078        let cleanup_return_count = ops
30079            .windows(6)
30080            .filter(|window| {
30081                matches!(
30082                    window,
30083                    [
30084                        Instruction::PopExcept,
30085                        Instruction::LoadConst { .. },
30086                        Instruction::StoreFast { .. } | Instruction::StoreName { .. },
30087                        Instruction::DeleteFast { .. } | Instruction::DeleteName { .. },
30088                        Instruction::LoadConst { .. },
30089                        Instruction::ReturnValue,
30090                    ]
30091                )
30092            })
30093            .count();
30094
30095        assert_eq!(
30096            cleanup_return_count, 1,
30097            "CPython keeps a shared named-except cleanup return when multiple BoolOp false edges target the same cleanup block, got ops={ops:?}"
30098        );
30099    }
30100
30101    #[test]
30102    fn listcomp_cleanup_tail_keeps_split_store_fast_pair() {
30103        let code = compile_exec(
30104            "\
30105def f(escaped_string, quote_types):
30106    possible_quotes = [q for q in quote_types if q not in escaped_string]
30107    return possible_quotes
30108",
30109        );
30110        let f = find_code(&code, "f").expect("missing function code");
30111        let ops: Vec<_> = f
30112            .instructions
30113            .iter()
30114            .map(|unit| unit.op)
30115            .filter(|op| !matches!(op, Instruction::Cache))
30116            .collect();
30117
30118        let pop_iter_idx = ops
30119            .iter()
30120            .position(|op| matches!(op, Instruction::PopIter))
30121            .expect("missing POP_ITER");
30122        let tail = &ops[pop_iter_idx + 1..];
30123
30124        assert!(
30125            matches!(
30126                tail,
30127                [
30128                    Instruction::StoreFast { .. },
30129                    Instruction::StoreFast { .. },
30130                    Instruction::LoadFastBorrow { .. },
30131                    Instruction::ReturnValue,
30132                    ..
30133                ]
30134            ),
30135            "expected split STORE_FAST pair after listcomp cleanup, got ops={ops:?}"
30136        );
30137    }
30138
30139    #[test]
30140    fn dictcomp_cleanup_tail_keeps_split_store_fast_pair() {
30141        let code = compile_exec(
30142            "\
30143def f(obj, g):
30144    return {g(k): g(v) for k, v in obj.items()}
30145",
30146        );
30147        let f = find_code(&code, "f").expect("missing function code");
30148        let ops: Vec<_> = f
30149            .instructions
30150            .iter()
30151            .map(|unit| unit.op)
30152            .filter(|op| !matches!(op, Instruction::Cache))
30153            .collect();
30154
30155        let pop_iter_idx = ops
30156            .iter()
30157            .position(|op| matches!(op, Instruction::PopIter))
30158            .expect("missing POP_ITER");
30159        let tail = &ops[pop_iter_idx + 1..];
30160
30161        assert!(
30162            matches!(
30163                tail,
30164                [
30165                    Instruction::Swap { .. },
30166                    Instruction::StoreFast { .. },
30167                    Instruction::StoreFast { .. },
30168                    Instruction::ReturnValue,
30169                    ..
30170                ]
30171            ),
30172            "expected split STORE_FAST pair after dictcomp cleanup, got ops={ops:?}"
30173        );
30174    }
30175
30176    #[test]
30177    fn static_swap_triple_assign_keeps_store_fast_store_fast() {
30178        let code = compile_exec(
30179            "\
30180def f(x, y, z):
30181    a, b, a = x, y, z
30182    return a
30183",
30184        );
30185        let f = find_code(&code, "f").expect("missing function code");
30186        let ops: Vec<_> = f
30187            .instructions
30188            .iter()
30189            .map(|unit| unit.op)
30190            .filter(|op| !matches!(op, Instruction::Cache))
30191            .collect();
30192
30193        assert!(
30194            ops.windows(3).any(|window| {
30195                matches!(
30196                    window,
30197                    [
30198                        Instruction::Swap { .. },
30199                        Instruction::StoreFastStoreFast { .. },
30200                        Instruction::StoreFast { .. }
30201                    ]
30202                )
30203            }),
30204            "expected CPython-style SWAP/STORE_FAST_STORE_FAST/STORE_FAST sequence, got ops={ops:?}"
30205        );
30206    }
30207
30208    #[test]
30209    fn static_swap_duplicate_pair_eliminates_swap() {
30210        let code = compile_exec(
30211            "\
30212def f(x, y):
30213    a, a = x, y
30214    return a
30215",
30216        );
30217        let f = find_code(&code, "f").expect("missing function code");
30218        let ops: Vec<_> = f
30219            .instructions
30220            .iter()
30221            .map(|unit| unit.op)
30222            .filter(|op| !matches!(op, Instruction::Cache))
30223            .collect();
30224
30225        assert!(
30226            !ops.iter().any(|op| matches!(op, Instruction::Swap { .. })),
30227            "duplicate pair assignment should statically eliminate SWAP, got ops={ops:?}"
30228        );
30229        assert!(
30230            ops.windows(2).any(|window| {
30231                matches!(window, [Instruction::StoreFast { .. }, Instruction::PopTop])
30232            }),
30233            "expected CPython-style STORE_FAST/POP_TOP duplicate assignment, got ops={ops:?}"
30234        );
30235    }
30236
30237    #[test]
30238    fn static_swap_duplicate_prefix_eliminates_swap() {
30239        let code = compile_exec(
30240            "\
30241def f(x, y, z):
30242    a, a, b = x, y, z
30243    return a
30244",
30245        );
30246        let f = find_code(&code, "f").expect("missing function code");
30247        let ops: Vec<_> = f
30248            .instructions
30249            .iter()
30250            .map(|unit| unit.op)
30251            .filter(|op| !matches!(op, Instruction::Cache))
30252            .collect();
30253
30254        assert!(
30255            !ops.iter().any(|op| matches!(op, Instruction::Swap { .. })),
30256            "duplicate-prefix assignment should statically eliminate SWAP, got ops={ops:?}"
30257        );
30258        assert!(
30259            ops.windows(2).any(|window| {
30260                matches!(
30261                    window,
30262                    [Instruction::StoreFastStoreFast { .. }, Instruction::PopTop]
30263                )
30264            }),
30265            "expected CPython-style STORE_FAST_STORE_FAST/POP_TOP duplicate prefix, got ops={ops:?}"
30266        );
30267    }
30268
30269    #[test]
30270    fn constant_if_expression_stmt_in_loop_removes_empty_body() {
30271        let code = compile_exec(
30272            "\
30273def f(x):
30274    while x:
30275        0 if 1 else 0
30276",
30277        );
30278        let f = find_code(&code, "f").expect("missing function code");
30279        let ops: Vec<_> = f
30280            .instructions
30281            .iter()
30282            .map(|unit| unit.op)
30283            .filter(|op| !matches!(op, Instruction::Cache))
30284            .collect();
30285
30286        assert!(
30287            !ops.iter()
30288                .any(|op| matches!(op, Instruction::LoadSmallInt { .. })),
30289            "expected constant if-expression statement to compile away inside loop, got ops={ops:?}"
30290        );
30291    }
30292
30293    #[test]
30294    fn if_expression_in_jump_context_skips_constant_true_arm_load() {
30295        let code = compile_exec(
30296            "\
30297def f():
30298    a if (1 if b else c) else d
30299",
30300        );
30301        let f = find_code(&code, "f").expect("missing function code");
30302        let ops: Vec<_> = f
30303            .instructions
30304            .iter()
30305            .map(|unit| unit.op)
30306            .filter(|op| !matches!(op, Instruction::Cache))
30307            .collect();
30308
30309        assert!(
30310            !ops.iter()
30311                .any(|op| matches!(op, Instruction::LoadSmallInt { .. })),
30312            "expected jump-context if-expression to avoid materializing constant truthy arm, got ops={ops:?}"
30313        );
30314    }
30315
30316    #[test]
30317    fn with_suppress_tail_duplicates_final_return_none() {
30318        let code = compile_exec(
30319            "\
30320def f(cm, cond):
30321    if cond:
30322        with cm():
30323            pass
30324",
30325        );
30326        let f = find_code(&code, "f").expect("missing function code");
30327        let ops: Vec<_> = f
30328            .instructions
30329            .iter()
30330            .map(|unit| unit.op)
30331            .filter(|op| !matches!(op, Instruction::Cache))
30332            .collect();
30333
30334        let return_count = ops
30335            .iter()
30336            .filter(|op| matches!(op, Instruction::ReturnValue))
30337            .count();
30338
30339        assert_eq!(
30340            return_count, 3,
30341            "expected duplicated return-none epilogues, got ops={ops:?}"
30342        );
30343        assert!(
30344            !ops.iter()
30345                .any(|op| matches!(op, Instruction::JumpBackwardNoInterrupt { .. })),
30346            "with suppress tail should not jump back to shared return block, got ops={ops:?}"
30347        );
30348    }
30349
30350    #[test]
30351    fn with_conditional_bare_return_keeps_return_line_nop_before_exit_cleanup() {
30352        let code = compile_exec(
30353            "\
30354def f(cm, registry, altkey):
30355    with cm:
30356        if registry.get(altkey):
30357            return
30358        registry[altkey] = 1
30359",
30360        );
30361        let f = find_code(&code, "f").expect("missing function code");
30362        let ops: Vec<_> = f
30363            .instructions
30364            .iter()
30365            .map(|unit| unit.op)
30366            .filter(|op| !matches!(op, Instruction::Cache))
30367            .collect();
30368
30369        assert!(
30370            ops.windows(8).any(|window| {
30371                matches!(
30372                    window,
30373                    [
30374                        Instruction::Nop,
30375                        Instruction::LoadConst { .. },
30376                        Instruction::LoadConst { .. },
30377                        Instruction::LoadConst { .. },
30378                        Instruction::Call { .. },
30379                        Instruction::PopTop,
30380                        Instruction::LoadConst { .. },
30381                        Instruction::ReturnValue,
30382                    ]
30383                )
30384            }),
30385            "expected CPython-style return-line NOP before with-exit cleanup return, got ops={ops:?}"
30386        );
30387    }
30388
30389    #[test]
30390    fn multiline_nested_with_return_finally_keeps_inner_cleanup_anchor_nop() {
30391        let code = compile_exec(
30392            "\
30393def f(a, b, path):
30394    try:
30395        with cm(a) as x, \\
30396             cm(b):
30397            return g(x).copy()
30398    finally:
30399        try:
30400            cleanup(path)
30401        except ValueError:
30402            pass
30403",
30404        );
30405        let f = find_code(&code, "f").expect("missing function code");
30406        let ops: Vec<_> = f
30407            .instructions
30408            .iter()
30409            .map(|unit| unit.op)
30410            .filter(|op| !matches!(op, Instruction::Cache))
30411            .collect();
30412
30413        assert!(
30414            ops.windows(7).any(|window| {
30415                matches!(
30416                    window,
30417                    [
30418                        Instruction::Copy { .. },
30419                        Instruction::PopExcept,
30420                        Instruction::Reraise { .. },
30421                        Instruction::Nop,
30422                        Instruction::LoadConst { .. },
30423                        Instruction::LoadConst { .. },
30424                        Instruction::LoadConst { .. },
30425                    ]
30426                )
30427            }),
30428            "multi-line nested with return/finally cleanup should keep CPython's inner item anchor NOP before outer __exit__, got ops={ops:?}"
30429        );
30430    }
30431
30432    #[test]
30433    fn with_return_value_uses_context_expr_location_like_cpython() {
30434        let code = compile_exec(
30435            "\
30436def f(cm, func, args, kwds):
30437    with cm:
30438        return func(*args, **kwds)
30439",
30440        );
30441        let f = find_code(&code, "f").expect("missing function code");
30442        let return_positions: Vec<_> = f
30443            .instructions
30444            .iter()
30445            .zip(&f.locations)
30446            .filter_map(|(unit, (location, end_location))| {
30447                matches!(unit.op, Instruction::ReturnValue).then_some((
30448                    location.line.get(),
30449                    location.character_offset.get(),
30450                    end_location.line.get(),
30451                    end_location.character_offset.get(),
30452                ))
30453            })
30454            .collect();
30455
30456        assert_eq!(
30457            return_positions,
30458            vec![(2, 10, 2, 12), (2, 10, 2, 12)],
30459            "CPython codegen_unwind_fblock(WITH) leaves RETURN_VALUE inheriting the context expression location"
30460        );
30461    }
30462
30463    #[test]
30464    fn with_normal_cleanup_jump_uses_context_expr_location_like_cpython() {
30465        let source = "\
30466with cm:
30467    pass
30468x = 1
30469";
30470        let mut opts = CompileOpts::default();
30471        let source_file = SourceFileBuilder::new("source_path", source).finish();
30472        let parsed = ruff_python_parser::parse(
30473            source_file.source_text(),
30474            ruff_python_parser::Mode::Module.into(),
30475        )
30476        .unwrap();
30477        let mut ast = parsed.into_syntax();
30478        opts.future_features |= preprocess::future_features(&ast);
30479        let future_annotations = opts
30480            .future_features
30481            .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
30482        preprocess::preprocess_mod(&mut ast, opts.optimize, future_annotations, false);
30483        let ast = match ast {
30484            ruff_python_ast::Mod::Module(stmts) => stmts,
30485            _ => unreachable!(),
30486        };
30487        let symbol_table = SymbolTable::scan_program_with_options(
30488            &ast,
30489            source_file.clone(),
30490            opts.allow_top_level_await,
30491            opts.future_features
30492                .contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS),
30493            opts.recursion_limit,
30494        )
30495        .map_err(|e| e.into_codegen_error(source_file.name().to_owned()))
30496        .unwrap();
30497        let mut compiler =
30498            Compiler::new_with_syntax_warning_handler(opts, source_file, "<module>", None);
30499        compiler.compile_program(&ast, symbol_table).unwrap();
30500
30501        let jump_positions = compiler
30502            .current_code_info()
30503            .blocks
30504            .iter()
30505            .flat_map(|block| block.used_instructions())
30506            .filter_map(|info| {
30507                matches!(
30508                    info.instr,
30509                    AnyInstruction::Pseudo(PseudoInstruction::Jump { .. })
30510                )
30511                .then_some((
30512                    (
30513                        info.location.line.get(),
30514                        info.location.character_offset.get(),
30515                        info.end_location.line.get(),
30516                        info.end_location.character_offset.get(),
30517                    ),
30518                    info.lineno_override,
30519                ))
30520            })
30521            .collect::<Vec<_>>();
30522
30523        assert!(
30524            jump_positions
30525                .iter()
30526                .any(|(position, lineno_override)| *position == (1, 6, 1, 8)
30527                    && *lineno_override != Some(ir::NO_LOCATION_OVERRIDE)),
30528            "CPython codegen_with_inner() emits the normal-exit JUMP at LOC(context_expr), not NO_LOCATION; got {jump_positions:?}"
30529        );
30530    }
30531
30532    #[test]
30533    fn async_with_return_value_uses_context_expr_location_like_cpython() {
30534        let code = compile_exec(
30535            "\
30536async def f(cm, func, args, kwds):
30537    async with cm:
30538        return await func(*args, **kwds)
30539",
30540        );
30541        let f = find_code(&code, "f").expect("missing function code");
30542        let return_positions: Vec<_> = f
30543            .instructions
30544            .iter()
30545            .zip(&f.locations)
30546            .filter_map(|(unit, (location, end_location))| {
30547                matches!(unit.op, Instruction::ReturnValue).then_some((
30548                    location.line.get(),
30549                    location.character_offset.get(),
30550                    end_location.line.get(),
30551                    end_location.character_offset.get(),
30552                ))
30553            })
30554            .collect();
30555
30556        assert_eq!(
30557            return_positions,
30558            vec![(2, 16, 2, 18), (2, 16, 2, 18)],
30559            "CPython codegen_unwind_fblock(ASYNC_WITH) leaves RETURN_VALUE inheriting the context expression location"
30560        );
30561    }
30562
30563    #[test]
30564    fn try_finally_conditional_return_duplicates_finally_exit_return() {
30565        let code = compile_exec(
30566            "\
30567def f(flag, data, callback):
30568    try:
30569        if flag:
30570            return
30571        value = 1
30572    finally:
30573        if data:
30574            callback(data)
30575",
30576        );
30577        let f = find_code(&code, "f").expect("missing function code");
30578        let ops: Vec<_> = f
30579            .instructions
30580            .iter()
30581            .map(|unit| unit.op)
30582            .filter(|op| !matches!(op, Instruction::Cache))
30583            .collect();
30584
30585        let return_count = ops
30586            .iter()
30587            .filter(|op| matches!(op, Instruction::ReturnValue))
30588            .count();
30589        assert_eq!(
30590            return_count, 4,
30591            "try-finally return unwind should keep CPython-style distinct true/false finalbody exits, got ops={ops:?}"
30592        );
30593    }
30594
30595    #[test]
30596    fn named_except_conditional_cleanup_is_inlined_per_branch() {
30597        let code = compile_exec(
30598            "\
30599def f(self, logger):
30600    try:
30601        work()
30602    except A as exc:
30603        if not self.closing:
30604            self.fatal(exc, 'msg')
30605        elif self.loop.get_debug():
30606            logger.debug('closing', exc_info=True)
30607    finally:
30608        if self.length > -1:
30609            self.recv()
30610",
30611        );
30612        let f = find_code(&code, "f").expect("missing function code");
30613        let ops: Vec<_> = f
30614            .instructions
30615            .iter()
30616            .map(|unit| unit.op)
30617            .filter(|op| !matches!(op, Instruction::Cache))
30618            .collect();
30619
30620        let cleanup_after_branch_count = ops
30621            .windows(6)
30622            .filter(|window| {
30623                matches!(
30624                    window,
30625                    [
30626                        Instruction::PopTop,
30627                        Instruction::PopExcept,
30628                        Instruction::LoadConst { .. },
30629                        Instruction::StoreFast { .. },
30630                        Instruction::DeleteFast { .. },
30631                        Instruction::JumpBackwardNoInterrupt { .. },
30632                    ]
30633                )
30634            })
30635            .count();
30636        assert_eq!(
30637            cleanup_after_branch_count, 2,
30638            "named except branch exits should inline cleanup like CPython, got ops={ops:?}"
30639        );
30640    }
30641
30642    #[test]
30643    fn try_finally_exception_path_duplicates_conditional_reraise() {
30644        let code = compile_exec(
30645            "\
30646def f(flag, callback):
30647    try:
30648        work()
30649    finally:
30650        if flag:
30651            callback()
30652",
30653        );
30654        let f = find_code(&code, "f").expect("missing function code");
30655        let ops: Vec<_> = f
30656            .instructions
30657            .iter()
30658            .map(|unit| unit.op)
30659            .filter(|op| !matches!(op, Instruction::Cache))
30660            .collect();
30661
30662        let reraise_count = ops
30663            .iter()
30664            .filter(|op| matches!(op, Instruction::Reraise { .. }))
30665            .count();
30666        assert_eq!(
30667            reraise_count, 3,
30668            "try-finally exception finalbody should duplicate CPython no-location RERAISE exits, got ops={ops:?}"
30669        );
30670    }
30671
30672    #[test]
30673    fn genexpr_compare_header_uses_store_fast_load_fast_like_cpython() {
30674        let code = compile_exec(
30675            "\
30676def f(it):
30677    return (offset == (4, 10) for offset in it)
30678",
30679        );
30680        let genexpr = find_code(&code, "<genexpr>").expect("missing <genexpr> code");
30681        let ops: Vec<_> = genexpr
30682            .instructions
30683            .iter()
30684            .map(|unit| unit.op)
30685            .filter(|op| !matches!(op, Instruction::Cache))
30686            .collect();
30687
30688        assert!(
30689            ops.windows(3).any(|window| {
30690                matches!(
30691                    window,
30692                    [
30693                        Instruction::StoreFastLoadFast { .. },
30694                        Instruction::LoadConst { .. },
30695                        Instruction::CompareOp { .. },
30696                    ]
30697                )
30698            }),
30699            "expected CPython-style STORE_FAST_LOAD_FAST compare header, got ops={ops:?}"
30700        );
30701    }
30702
30703    #[test]
30704    fn fstring_adjacent_literals_are_merged() {
30705        let code = compile_exec(
30706            "\
30707def f(cls, proto):
30708    raise TypeError(
30709        f\"cannot pickle {cls.__name__!r} object: \"
30710        f\"a class that defines __slots__ without \"
30711        f\"defining __getstate__ cannot be pickled \"
30712        f\"with protocol {proto}\"
30713    )
30714",
30715        );
30716        let f = find_code(&code, "f").expect("missing function code");
30717        let string_consts = f
30718            .instructions
30719            .iter()
30720            .filter_map(|unit| match unit.op {
30721                Instruction::LoadConst { consti } => {
30722                    Some(&f.constants[consti.get(OpArg::new(u32::from(u8::from(unit.arg))))])
30723                }
30724                _ => None,
30725            })
30726            .filter_map(|constant| match constant {
30727                ConstantData::Str { value } => Some(value.to_string()),
30728                _ => None,
30729            })
30730            .collect::<Vec<_>>();
30731
30732        assert!(
30733            string_consts.iter().any(|value| {
30734                value
30735                    == " object: a class that defines __slots__ without defining __getstate__ cannot be pickled with protocol "
30736            }),
30737            "expected merged trailing f-string literal, got {string_consts:?}"
30738        );
30739        assert!(
30740            !string_consts.iter().any(|value| value == " object: "),
30741            "did not expect split trailing literal, got {string_consts:?}"
30742        );
30743    }
30744
30745    #[test]
30746    fn literal_only_fstring_statement_keeps_const_like_cpython() {
30747        let code = compile_exec(
30748            "\
30749def f():
30750    f'''Not a docstring'''
30751",
30752        );
30753        let f = find_code(&code, "f").expect("missing function code");
30754
30755        assert!(
30756            f.constants.iter().any(|constant| matches!(
30757                constant,
30758                ConstantData::Str { value } if value.to_string() == "Not a docstring"
30759            )),
30760            "constant f-string statement should survive in co_consts like CPython"
30761        );
30762    }
30763
30764    #[test]
30765    fn empty_fstring_literals_are_elided_around_interpolation() {
30766        let code = compile_exec(
30767            "\
30768def f(x):
30769    if '' f'{x}':
30770        return 1
30771    return 2
30772",
30773        );
30774        let f = find_code(&code, "f").expect("missing function code");
30775
30776        let empty_string_loads = f
30777            .instructions
30778            .iter()
30779            .filter_map(|unit| match unit.op {
30780                Instruction::LoadConst { consti } => {
30781                    Some(&f.constants[consti.get(OpArg::new(u32::from(u8::from(unit.arg))))])
30782                }
30783                _ => None,
30784            })
30785            .filter(|constant| {
30786                matches!(
30787                    constant,
30788                    ConstantData::Str { value } if value.is_empty()
30789                )
30790            })
30791            .count();
30792        let build_string_count = f
30793            .instructions
30794            .iter()
30795            .filter(|unit| matches!(unit.op, Instruction::BuildString { .. }))
30796            .count();
30797
30798        assert_eq!(empty_string_loads, 0);
30799        assert_eq!(build_string_count, 0);
30800    }
30801
30802    #[test]
30803    fn large_fstring_uses_join_list_like_cpython() {
30804        let mut source = String::from("def f(x):\n    return f\"");
30805        for _ in 0..=STACK_USE_GUIDELINE {
30806            source.push_str("{x}");
30807        }
30808        source.push_str("\"\n");
30809
30810        let code = compile_exec(&source);
30811        let f = find_code(&code, "f").expect("missing function code");
30812        let build_string_count = f
30813            .instructions
30814            .iter()
30815            .filter(|unit| matches!(unit.op, Instruction::BuildString { .. }))
30816            .count();
30817        let list_append_count = f
30818            .instructions
30819            .iter()
30820            .filter(|unit| matches!(unit.op, Instruction::ListAppend { .. }))
30821            .count();
30822        let join_attr_count = f
30823            .instructions
30824            .iter()
30825            .filter(|unit| match unit.op {
30826                Instruction::LoadAttr { namei } => {
30827                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
30828                    load_attr.is_method()
30829                        && f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
30830                            == "join"
30831                }
30832                _ => false,
30833            })
30834            .count();
30835
30836        assert_eq!(build_string_count, 0);
30837        assert_eq!(
30838            list_append_count,
30839            usize::try_from(STACK_USE_GUIDELINE + 1).unwrap()
30840        );
30841        assert_eq!(join_attr_count, 1);
30842    }
30843
30844    #[test]
30845    fn large_fstring_join_scaffolding_uses_joinedstr_location_like_cpython() {
30846        let mut source = String::from("def f(x):\n    return f\"");
30847        for _ in 0..=STACK_USE_GUIDELINE {
30848            source.push_str("{x}");
30849        }
30850        source.push_str("\"\n");
30851
30852        let code = compile_exec(&source);
30853        let f = find_code(&code, "f").expect("missing function code");
30854        let fstring_end = "    return ".len()
30855            + 3
30856            + 3 * usize::try_from(STACK_USE_GUIDELINE + 1).expect("guideline overflowed")
30857            + 1;
30858        let expected = (2, 12, 2, fstring_end);
30859
30860        for (unit, (location, end_location)) in f.instructions.iter().zip(&f.locations) {
30861            if matches!(
30862                unit.op,
30863                Instruction::BuildList { .. }
30864                    | Instruction::ListAppend { .. }
30865                    | Instruction::Call { .. }
30866            ) {
30867                assert_eq!(
30868                    (
30869                        location.line.get(),
30870                        location.character_offset.get(),
30871                        end_location.line.get(),
30872                        end_location.character_offset.get(),
30873                    ),
30874                    expected,
30875                    "CPython codegen_joined_str() emits join scaffolding at LOC(JoinedStr); this direct codegen path uses the parser's FString range, op={:?}",
30876                    unit.op
30877                );
30878            }
30879        }
30880    }
30881
30882    #[test]
30883    fn large_power_is_not_constant_folded() {
30884        let code = compile_exec("x = 2**100\n");
30885
30886        assert!(code.instructions.iter().any(|unit| match unit.op {
30887            Instruction::BinaryOp { op } => {
30888                op.get(OpArg::new(u32::from(u8::from(unit.arg)))) == oparg::BinaryOperator::Power
30889            }
30890            _ => false,
30891        }));
30892    }
30893
30894    #[test]
30895    fn string_and_bytes_binops_constant_fold_like_cpython() {
30896        let code = compile_exec(
30897            "\
30898x = b'\\\\' + b'u1881'\n\
30899y = 103 * 'a' + 'x'\n",
30900        );
30901
30902        assert!(
30903            !code
30904                .instructions
30905                .iter()
30906                .any(|unit| matches!(unit.op, Instruction::BinaryOp { .. })),
30907            "unexpected runtime BINARY_OP in folded string/bytes constants: {:?}",
30908            code.instructions
30909        );
30910        assert!(code.constants.iter().any(|constant| matches!(
30911            constant,
30912            ConstantData::Bytes { value } if value == b"\\u1881"
30913        )));
30914        let expected = format!("{}x", "a".repeat(103));
30915        assert!(code.constants.iter().any(|constant| matches!(
30916            constant,
30917            ConstantData::Str { value }
30918                if value.to_string() == expected
30919        )));
30920    }
30921
30922    #[test]
30923    fn float_floor_division_constant_folds_like_cpython() {
30924        let code = compile_exec(
30925            "\
30926x = 1.0 // 0.1\n\
30927y = 1.0 % 0.1\n\
30928z = 1e300 * 1e300 * 0\n",
30929        );
30930
30931        assert!(
30932            !code
30933                .instructions
30934                .iter()
30935                .any(|unit| matches!(unit.op, Instruction::BinaryOp { .. })),
30936            "float constant floor-div/mod should fold away, got instructions={:?}",
30937            code.instructions
30938        );
30939        assert!(code.constants.iter().any(|constant| matches!(
30940            constant,
30941            ConstantData::Float { value } if value.to_bits() == 9.0f64.to_bits()
30942        )));
30943        assert!(code.constants.iter().any(|constant| matches!(
30944            constant,
30945            ConstantData::Float { value }
30946                if value.to_bits() == 0.09999999999999995f64.to_bits()
30947        )));
30948        assert!(code.constants.iter().any(|constant| matches!(
30949            constant,
30950            ConstantData::Float { value } if value.is_nan()
30951        )));
30952    }
30953
30954    #[test]
30955    fn float_power_overflow_constant_does_not_fold() {
30956        let code = compile_exec("x = 1e300 ** 2\n");
30957
30958        assert!(
30959            code.instructions.iter().any(|unit| matches!(
30960                unit.op,
30961                Instruction::BinaryOp { op }
30962                    if op.get(OpArg::new(u32::from(u8::from(unit.arg))))
30963                        == oparg::BinaryOperator::Power
30964            )),
30965            "overflowing float power should stay runtime like CPython, got instructions={:?}",
30966            code.instructions
30967        );
30968    }
30969
30970    #[test]
30971    fn large_string_and_bytes_binops_constant_fold_like_cpython() {
30972        let code = compile_exec(
30973            r#"
30974encoded = b'\xff\xfe\x00\x00' + b'\x00\x00\x01\x00' * 1024
30975text = '\U00010000' * 1024
30976"#,
30977        );
30978
30979        assert!(
30980            !code
30981                .instructions
30982                .iter()
30983                .any(|unit| matches!(unit.op, Instruction::BinaryOp { .. })),
30984            "large safe string/bytes constants should fold away, got instructions={:?}",
30985            code.instructions
30986        );
30987        assert!(code.constants.iter().any(|constant| matches!(
30988            constant,
30989            ConstantData::Bytes { value } if value.len() == 4100
30990        )));
30991        assert!(code.constants.iter().any(|constant| matches!(
30992            constant,
30993            ConstantData::Str { value } if value.code_points().count() == 1024
30994        )));
30995    }
30996
30997    #[test]
30998    fn constant_string_subscript_folds_inside_collection() {
30999        let code = compile_exec(
31000            "\
31001values = [item for item in [r\"\\\\'a\\\\'\", r\"\\t3\", r\"\\\\\"[0]]]\n",
31002        );
31003
31004        assert!(
31005            !code
31006                .instructions
31007                .iter()
31008                .any(|unit| matches!(unit.op, Instruction::BinaryOp { .. })),
31009            "unexpected runtime BINARY_OP after constant subscript folding: {:?}",
31010            code.instructions
31011        );
31012        assert!(code.constants.iter().any(|constant| matches!(
31013            constant,
31014            ConstantData::Tuple { elements }
31015                if elements.len() == 3
31016                    && matches!(&elements[2], ConstantData::Str { value } if value.to_string() == "\\")
31017        )));
31018    }
31019
31020    #[test]
31021    fn constant_string_subscript_with_surrogate_skips_lossy_fold() {
31022        let code = compile_exec("value = \"\\ud800\"[0]\n");
31023
31024        assert!(
31025            code.instructions.iter().any(|unit| match unit.op {
31026                Instruction::BinaryOp { op } => {
31027                    op.get(OpArg::new(u32::from(u8::from(unit.arg))))
31028                        == oparg::BinaryOperator::Subscr
31029                }
31030                _ => false,
31031            }),
31032            "expected runtime subscript for surrogate literal, got instructions={:?}",
31033            code.instructions
31034        );
31035    }
31036
31037    #[test]
31038    fn constant_subscript_folds_in_load_context() {
31039        let cases = [
31040            ("value = (1, 2, 3)[0]\n", Some(BigInt::from(1)), None),
31041            ("value = b\"abc\"[0]\n", Some(BigInt::from(97)), None),
31042            ("value = \"abc\"[0]\n", None, Some("a")),
31043        ];
31044
31045        for (source, expected_int, expected_str) in cases {
31046            let code = compile_exec(source);
31047            assert!(
31048                !code.instructions.iter().any(|unit| matches!(
31049                    unit.op,
31050                    Instruction::BinaryOp { op }
31051                        if op.get(OpArg::new(u32::from(u8::from(unit.arg))))
31052                            == oparg::BinaryOperator::Subscr
31053                )),
31054                "expected folded constant subscript for {source:?}, got instructions={:?}",
31055                code.instructions
31056            );
31057
31058            if let Some(expected_int) = expected_int.as_ref() {
31059                let has_small_int = code.instructions.iter().any(|unit| {
31060                    matches!(
31061                        unit.op,
31062                        Instruction::LoadSmallInt { i }
31063                            if BigInt::from(i.get(OpArg::new(u32::from(u8::from(unit.arg)))))
31064                                == *expected_int
31065                    )
31066                });
31067                let has_const_int = code.constants.iter().any(|constant| {
31068                    matches!(constant, ConstantData::Integer { value } if value == expected_int)
31069                });
31070                assert!(
31071                    has_small_int || has_const_int,
31072                    "missing folded integer constant {expected_int} for {source:?}, instructions={:?}",
31073                    code.instructions
31074                );
31075            }
31076
31077            if let Some(expected_str) = expected_str {
31078                assert!(
31079                    code.constants.iter().any(|constant| {
31080                        matches!(constant, ConstantData::Str { value } if value.to_string() == expected_str)
31081                    }),
31082                    "missing folded string constant {expected_str:?} for {source:?}",
31083                );
31084            }
31085        }
31086    }
31087
31088    #[test]
31089    fn constant_subscript_registers_source_const_before_result_like_cpython() {
31090        let code = compile_exec("value = 'string'[3]\n");
31091        let source_index = code
31092            .constants
31093            .iter()
31094            .position(|constant| {
31095                matches!(constant, ConstantData::Str { value } if value.to_string() == "string")
31096            })
31097            .expect("missing source string constant");
31098        let result_index = code
31099            .constants
31100            .iter()
31101            .position(|constant| {
31102                matches!(constant, ConstantData::Str { value } if value.to_string() == "i")
31103            })
31104            .expect("missing folded subscript result");
31105        assert!(
31106            source_index < result_index,
31107            "CPython codegen_subscript emits the source constant before flowgraph.c folds NB_SUBSCR"
31108        );
31109    }
31110
31111    #[test]
31112    fn constant_slice_subscript_folds_in_load_context() {
31113        let code = compile_exec(
31114            "\
31115a = 'hello'[:4]\n\
31116b = b'abcd'[1:3]\n\
31117c = (1, 2, 3)[:2]\n",
31118        );
31119
31120        assert!(
31121            !code.instructions.iter().any(|unit| matches!(
31122                unit.op,
31123                Instruction::BinaryOp { op }
31124                    if op.get(OpArg::new(u32::from(u8::from(unit.arg))))
31125                        == oparg::BinaryOperator::Subscr
31126            )),
31127            "expected folded constant slice subscripts, got instructions={:?}",
31128            code.instructions
31129        );
31130        assert!(code.constants.iter().any(|constant| matches!(
31131            constant,
31132            ConstantData::Str { value } if value.to_string() == "hell"
31133        )));
31134        assert!(code.constants.iter().any(|constant| matches!(
31135            constant,
31136            ConstantData::Bytes { value } if value == b"bc"
31137        )));
31138        assert!(code.constants.iter().any(|constant| matches!(
31139            constant,
31140            ConstantData::Tuple { elements }
31141                if matches!(
31142                    elements.as_slice(),
31143                    [
31144                        ConstantData::Integer { value: a },
31145                        ConstantData::Integer { value: b },
31146                    ] if *a == BigInt::from(1)
31147                        && *b == BigInt::from(2)
31148                )
31149        )));
31150    }
31151
31152    #[test]
31153    fn list_of_constant_tuples_uses_list_extend() {
31154        let code = compile_exec(
31155            "\
31156deprecated_cases = [('a', 'b'), ('c', 'd'), ('e', 'f'), ('g', 'h'), ('i', 'j')]
31157",
31158        );
31159
31160        assert!(
31161            code.instructions
31162                .iter()
31163                .any(|unit| matches!(unit.op, Instruction::ListExtend { .. })),
31164            "expected constant tuple list folding"
31165        );
31166    }
31167
31168    #[test]
31169    fn large_list_of_unary_constants_uses_list_extend() {
31170        let code = compile_exec(
31171            "\
31172values = [-1, not True, ~0, +True, 5]
31173",
31174        );
31175
31176        assert!(
31177            code.instructions
31178                .iter()
31179                .any(|unit| matches!(unit.op, Instruction::ListExtend { .. })),
31180            "expected unary-folded constants to participate in list folding, got instructions={:?}",
31181            code.instructions
31182        );
31183        assert!(code.constants.iter().any(|constant| matches!(
31184            constant,
31185            ConstantData::Tuple { elements }
31186                if elements.len() == 5
31187                    && matches!(&elements[0], ConstantData::Integer { value } if *value == BigInt::from(-1))
31188                    && matches!(&elements[1], ConstantData::Boolean { value } if !value)
31189                    && matches!(&elements[2], ConstantData::Integer { value } if *value == BigInt::from(-1))
31190                    && matches!(&elements[3], ConstantData::Integer { value } if *value == BigInt::from(1))
31191                    && matches!(&elements[4], ConstantData::Integer { value } if *value == BigInt::from(5))
31192        )));
31193    }
31194
31195    #[test]
31196    fn outer_unary_after_binop_folds_before_list_folding() {
31197        let code = compile_exec(
31198            "\
31199values = [2.0**53, -0.5, -2.0**-54]
31200",
31201        );
31202
31203        assert!(
31204            code.instructions
31205                .iter()
31206                .any(|unit| matches!(unit.op, Instruction::ListExtend { .. })),
31207            "expected binop-folded constants to participate in list folding, got instructions={:?}",
31208            code.instructions
31209        );
31210        assert!(
31211            !code.instructions.iter().any(|unit| matches!(
31212                unit.op,
31213                Instruction::BinaryOp { .. } | Instruction::UnaryNegative
31214            )),
31215            "constant expression list should not leave runtime ops, got instructions={:?}",
31216            code.instructions
31217        );
31218        assert!(code.constants.iter().any(|constant| matches!(
31219            constant,
31220            ConstantData::Tuple { elements }
31221                if elements.len() == 3
31222                    && matches!(&elements[0], ConstantData::Float { value } if *value == 9007199254740992.0)
31223                    && matches!(&elements[1], ConstantData::Float { value } if *value == -0.5)
31224                    && matches!(&elements[2], ConstantData::Float { value } if value.is_sign_negative())
31225        )));
31226    }
31227
31228    #[test]
31229    fn negative_integer_power_folds_to_float_constant() {
31230        let code = compile_exec("value = -3.0 * 2**(-333)\n");
31231
31232        assert!(
31233            !code
31234                .instructions
31235                .iter()
31236                .any(|unit| matches!(unit.op, Instruction::BinaryOp { .. })),
31237            "negative integer power should fold through the enclosing multiply, got instructions={:?}",
31238            code.instructions
31239        );
31240        assert!(code.constants.iter().any(|constant| matches!(
31241            constant,
31242            ConstantData::Float { value }
31243                if value.is_sign_negative() && *value < 0.0 && value.abs() < 1.0e-90
31244        )));
31245    }
31246
31247    #[test]
31248    fn complex_power_constants_fold_like_cpython() {
31249        let code = compile_exec(
31250            "\
31251one = 3j ** 0j
31252zero = 0j ** 2
31253",
31254        );
31255
31256        assert!(
31257            !code
31258                .instructions
31259                .iter()
31260                .any(|unit| matches!(unit.op, Instruction::BinaryOp { .. })),
31261            "safe complex power constants should fold away, got instructions={:?}",
31262            code.instructions
31263        );
31264        assert!(code.constants.iter().any(|constant| matches!(
31265            constant,
31266            ConstantData::Complex { value } if value.re == 1.0 && value.im == 0.0
31267        )));
31268        assert!(code.constants.iter().any(|constant| matches!(
31269            constant,
31270            ConstantData::Complex { value } if value.re == 0.0 && value.im == 0.0
31271        )));
31272    }
31273
31274    #[test]
31275    fn folded_nan_constants_are_not_deduplicated_like_cpython() {
31276        let code = compile_exec(
31277            "\
31278def f():
31279    repr(1e300 * 1e300 * 0)
31280    repr(-1e300 * 1e300 * 0)
31281    str(1e300 * 1e300 * 0)
31282    str(-1e300 * 1e300 * 0)
31283",
31284        );
31285        let f = find_code(&code, "f").expect("missing function code");
31286        let nan_count = f
31287            .constants
31288            .iter()
31289            .filter(|constant| matches!(constant, ConstantData::Float { value } if value.is_nan()))
31290            .count();
31291        assert_eq!(
31292            nan_count, 4,
31293            "CPython _PyCode_ConstantKey keeps folded NaN constants distinct"
31294        );
31295    }
31296
31297    #[test]
31298    fn zero_complex_power_exception_constants_do_not_fold() {
31299        let code = compile_exec("value = 0j ** (3 - 2j)\n");
31300
31301        assert!(
31302            code.instructions
31303                .iter()
31304                .any(|unit| matches!(unit.op, Instruction::BinaryOp { .. })),
31305            "zero complex to complex power should stay runtime so ZeroDivisionError is preserved, got instructions={:?}",
31306            code.instructions
31307        );
31308    }
31309
31310    #[test]
31311    fn large_constant_list_keeps_streaming_build() {
31312        let source = format!(
31313            "values = [{}]\n",
31314            (0..31)
31315                .map(|i| format!("'v{i}'"))
31316                .collect::<Vec<_>>()
31317                .join(", ")
31318        );
31319        let code = compile_exec(&source);
31320
31321        assert!(
31322            code.instructions
31323                .iter()
31324                .any(|unit| matches!(unit.op, Instruction::ListAppend { .. })),
31325            "large constant lists should keep LIST_APPEND streaming form, got instructions={:?}",
31326            code.instructions
31327        );
31328        assert!(
31329            !code
31330                .instructions
31331                .iter()
31332                .any(|unit| matches!(unit.op, Instruction::ListExtend { .. })),
31333            "large constant lists should not fold to LIST_EXTEND, got instructions={:?}",
31334            code.instructions
31335        );
31336    }
31337
31338    #[test]
31339    fn large_constant_tuple_stream_folds_to_tuple_const() {
31340        let source = format!(
31341            "values = ({},)\n",
31342            (0..31)
31343                .map(|i| format!("'v{i}'"))
31344                .collect::<Vec<_>>()
31345                .join(", ")
31346        );
31347        let code = compile_exec(&source);
31348
31349        assert!(
31350            !code.instructions.iter().any(|unit| matches!(
31351                unit.op,
31352                Instruction::BuildList { .. }
31353                    | Instruction::ListAppend { .. }
31354                    | Instruction::CallIntrinsic1 { .. }
31355            )),
31356            "large constant tuple should fold the LIST_TO_TUPLE stream, got instructions={:?}",
31357            code.instructions
31358        );
31359        assert!(code.constants.iter().any(|constant| matches!(
31360            constant,
31361            ConstantData::Tuple { elements } if elements.len() == 31
31362        )));
31363    }
31364
31365    #[test]
31366    fn annotation_closure_uses_format_varname() {
31367        let code = compile_exec(
31368            "\
31369class C:
31370    x: int
31371",
31372        );
31373        let annotate = find_code(&code, "__annotate__").expect("missing __annotate__ code");
31374        let varnames = annotate
31375            .varnames
31376            .iter()
31377            .map(|name| name.as_str())
31378            .collect::<Vec<_>>();
31379        assert_eq!(varnames, vec!["format"]);
31380        assert!(annotate.instructions.iter().any(|unit| matches!(
31381            unit.op,
31382            Instruction::LoadFastBorrow { var_num }
31383                if usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg))))) == 0
31384        )));
31385        assert!(!annotate.instructions.iter().any(|unit| matches!(
31386            unit.op,
31387            Instruction::LoadFastCheck { var_num }
31388                if usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg))))) == 0
31389        )));
31390    }
31391
31392    #[test]
31393    fn future_function_signature_annotation_uses_hidden_block_like_cpython() {
31394        let code = compile_exec(
31395            "\
31396from __future__ import annotations
31397def f(x: T): pass
31398",
31399        );
31400        let annotate = find_code(&code, "__annotate__").expect("missing __annotate__ code");
31401        let varnames = annotate
31402            .varnames
31403            .iter()
31404            .map(|name| name.as_str())
31405            .collect::<Vec<_>>();
31406        assert_eq!(varnames, vec!["format"]);
31407        assert!(
31408            find_code(&code, "f").is_some(),
31409            "function body symbol-table cursor must skip the hidden AnnotationBlock"
31410        );
31411    }
31412
31413    #[test]
31414    fn future_generic_class_annotations_do_not_capture_type_params_like_cpython() {
31415        let code = compile_exec(
31416            "\
31417from __future__ import annotations
31418class A[T, *Ts, **P]:
31419    x: T
31420    y: tuple[*Ts]
31421    z: Callable[P, str]
31422",
31423        );
31424        let type_params =
31425            find_code(&code, "<generic parameters of A>").expect("missing type parameter scope");
31426        let class = find_direct_child_code(type_params, "A").expect("missing class body");
31427
31428        assert_eq!(
31429            type_params
31430                .cellvars
31431                .iter()
31432                .map(|name| name.as_str())
31433                .collect::<Vec<_>>(),
31434            [".type_params"]
31435        );
31436        assert_eq!(
31437            class
31438                .freevars
31439                .iter()
31440                .map(|name| name.as_str())
31441                .collect::<Vec<_>>(),
31442            [".type_params"]
31443        );
31444    }
31445
31446    #[test]
31447    fn future_unannotated_function_does_not_hide_next_annotation_block() {
31448        let code = compile_exec(
31449            "\
31450from __future__ import annotations
31451def plain(x): pass
31452def annotated(x: int): pass
31453",
31454        );
31455        let annotate = find_direct_child_code(&code, "__annotate__")
31456            .expect("second function must retain its annotation closure");
31457        assert!(
31458            annotate.constants.iter().any(
31459                |constant| matches!(constant, ConstantData::Str { value } if value.as_str() == Ok("int"))
31460            ),
31461            "annotation closure must belong to the annotated function"
31462        );
31463    }
31464
31465    #[test]
31466    fn deferred_annotation_format_name_does_not_capture_helper_parameter() {
31467        let code = compile_exec(
31468            "\
31469format = object()
31470x: format
31471",
31472        );
31473        let annotate = find_code(&code, "__annotate__").expect("missing __annotate__ code");
31474        let varnames = annotate
31475            .varnames
31476            .iter()
31477            .map(|name| name.as_str())
31478            .collect::<Vec<_>>();
31479        assert_eq!(varnames, vec!["format"]);
31480        assert!(
31481            annotate.names.iter().any(|name| name.as_str() == "format"),
31482            "CPython keeps the helper parameter as internal .format during symbol analysis, so annotation expression `format` must remain a separate name; got names={:?}",
31483            annotate.names
31484        );
31485
31486        let helper_param_loads = annotate
31487            .instructions
31488            .iter()
31489            .filter(|unit| match unit.op {
31490                Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
31491                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
31492                    annotate.varnames[usize::from(var_num.get(arg))].as_str() == "format"
31493                }
31494                _ => false,
31495            })
31496            .count();
31497        assert_eq!(
31498            helper_param_loads, 1,
31499            "only the CPython format-validation prologue should load the helper parameter; annotation expression `format` must not compile as LOAD_FAST"
31500        );
31501    }
31502
31503    #[test]
31504    fn non_simple_class_annotation_is_not_deferred_like_cpython() {
31505        let code = compile_exec(
31506            "\
31507class C:
31508    x.y: list = []
31509    z: int
31510",
31511        );
31512        let annotate = find_code(&code, "__annotate__").expect("missing __annotate__ code");
31513        let names = annotate
31514            .names
31515            .iter()
31516            .map(|name| name.as_str())
31517            .collect::<Vec<_>>();
31518        assert_eq!(names, vec!["int"]);
31519    }
31520
31521    #[test]
31522    fn non_simple_annotation_only_consumes_symbol_table_cursor() {
31523        let code = compile_exec(
31524            "\
31525class C:
31526    x.y: (lambda: str) = []
31527    z: (lambda: int)
31528",
31529        );
31530        let annotate = find_code(&code, "__annotate__").expect("missing __annotate__ code");
31531        let lambdas = annotate
31532            .constants
31533            .iter()
31534            .filter_map(|constant| match constant {
31535                ConstantData::Code { code } if code.obj_name == "<lambda>" => Some(code.as_ref()),
31536                _ => None,
31537            })
31538            .collect::<Vec<_>>();
31539        assert_eq!(lambdas.len(), 1);
31540        assert_eq!(
31541            lambdas[0]
31542                .names
31543                .iter()
31544                .map(|name| name.as_str())
31545                .collect::<Vec<_>>(),
31546            vec!["int"]
31547        );
31548    }
31549
31550    #[test]
31551    fn class_deferred_annotations_guard_only_conditional_entries_like_cpython() {
31552        let code = compile_exec(
31553            "\
31554class C:
31555    x: int
31556    if flag:
31557        y: str
31558    z: float
31559",
31560        );
31561        let class_code = find_code(&code, "C").expect("missing class code");
31562        let class_ops: Vec<_> = class_code
31563            .instructions
31564            .iter()
31565            .map(|unit| unit.op)
31566            .filter(|op| !matches!(op, Instruction::Cache))
31567            .collect();
31568        let class_set_adds = class_ops
31569            .iter()
31570            .filter(|op| matches!(op, Instruction::SetAdd { .. }))
31571            .count();
31572        assert_eq!(
31573            class_set_adds, 1,
31574            "CPython _PyCompile_AddDeferredAnnotation() adds class annotations to __conditional_annotations__ only inside conditional blocks, got ops={class_ops:?}"
31575        );
31576        assert!(
31577            class_code.instructions.iter().any(|unit| match unit.op {
31578                Instruction::LoadDeref { i } => {
31579                    let idx = i.get(OpArg::new(u32::from(u8::from(unit.arg)))).as_usize();
31580                    localsplus_name(class_code, idx) == Some("__conditional_annotations__")
31581                }
31582                _ => false,
31583            }),
31584            "CPython codegen_annassign() emits LOAD_DEREF for class __conditional_annotations__, got ops={class_ops:?}"
31585        );
31586        assert!(
31587            !class_code
31588                .instructions
31589                .iter()
31590                .any(|unit| matches!(unit.op, Instruction::LoadFromDictOrDeref { .. })),
31591            "CPython codegen_annassign() bypasses codegen_nameop for class __conditional_annotations__, got ops={class_ops:?}"
31592        );
31593
31594        let annotate = find_code(class_code, "__annotate__").expect("missing __annotate__ code");
31595        let annotate_ops: Vec<_> = annotate
31596            .instructions
31597            .iter()
31598            .map(|unit| unit.op)
31599            .filter(|op| !matches!(op, Instruction::Cache))
31600            .collect();
31601        let annotation_body = annotate_ops
31602            .iter()
31603            .position(|op| matches!(op, Instruction::BuildMap { .. }))
31604            .map(|idx| &annotate_ops[idx..])
31605            .expect("missing annotation map build");
31606        let guarded_entries = annotation_body
31607            .iter()
31608            .filter(|op| matches!(op, Instruction::PopJumpIfFalse { .. }))
31609            .count();
31610        assert_eq!(
31611            guarded_entries, 1,
31612            "CPython codegen_deferred_annotations_body() guards only conditional class annotations, got ops={annotate_ops:?}"
31613        );
31614    }
31615
31616    #[test]
31617    fn future_annotations_non_simple_target_checks_target_but_not_annotation_like_cpython() {
31618        let code = compile_exec(
31619            "\
31620from __future__ import annotations
31621class C:
31622    target[item]: missing
31623",
31624        );
31625        let class_code = find_code(&code, "C").expect("missing class code");
31626        let loaded_names: Vec<_> = class_code
31627            .instructions
31628            .iter()
31629            .filter_map(|unit| match unit.op {
31630                Instruction::LoadName { namei } => {
31631                    let idx = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
31632                    Some(class_code.names[usize::try_from(idx).unwrap()].as_str())
31633                }
31634                _ => None,
31635            })
31636            .collect();
31637
31638        assert!(
31639            ["target", "item"]
31640                .iter()
31641                .all(|name| loaded_names.contains(name)),
31642            "CPython codegen_annassign() still checks bare complex annotation targets under future annotations, got loaded_names={loaded_names:?}"
31643        );
31644        assert!(
31645            !loaded_names.contains(&"missing"),
31646            "CPython codegen_check_annotation() skips the annotation expression under future annotations, got loaded_names={loaded_names:?}"
31647        );
31648    }
31649
31650    #[test]
31651    fn type_param_evaluator_uses_dot_format_varname() {
31652        let code = compile_exec(
31653            "\
31654class C[T: int]:
31655    pass
31656",
31657        );
31658        let evaluator = find_code(&code, "T").expect("missing type parameter evaluator");
31659        let varnames = evaluator
31660            .varnames
31661            .iter()
31662            .map(|name| name.as_str())
31663            .collect::<Vec<_>>();
31664        assert_eq!(varnames, vec![".format"]);
31665    }
31666
31667    #[test]
31668    fn generic_class_double_star_bases_use_tuple_ex_call_path() {
31669        let code = compile_exec(
31670            "\
31671def f(Base, kwargs):
31672    class C[T](Base, **kwargs):
31673        pass
31674    return C
31675",
31676        );
31677        let type_params =
31678            find_code(&code, "<generic parameters of C>").expect("missing type params code");
31679        let ops: Vec<_> = type_params
31680            .instructions
31681            .iter()
31682            .map(|unit| unit.op)
31683            .filter(|op| !matches!(op, Instruction::Cache))
31684            .collect();
31685
31686        assert!(
31687            !has_intrinsic_1(type_params, IntrinsicFunction1::ListToTuple),
31688            "generic class call with **kwargs but no starred bases should not use list-to-tuple, got ops={ops:?}"
31689        );
31690        assert!(
31691            ops.windows(4).any(|window| {
31692                matches!(
31693                    window,
31694                    [
31695                        Instruction::BuildTuple { .. },
31696                        Instruction::BuildMap { .. },
31697                        Instruction::LoadDeref { .. } | Instruction::LoadFast { .. },
31698                        Instruction::DictMerge { .. },
31699                    ]
31700                )
31701            }),
31702            "expected CPython-style BUILD_TUPLE/BUILD_MAP/DICT_MERGE ex-call path, got ops={ops:?}"
31703        );
31704    }
31705
31706    #[test]
31707    fn generic_function_defaults_call_type_params_like_cpython() {
31708        let code = compile_exec(
31709            "\
31710def func[T](a: T = 'a', *, b: T = 'b'):
31711    return a, b
31712",
31713        );
31714        let ops: Vec<_> = code
31715            .instructions
31716            .iter()
31717            .map(|unit| unit.op)
31718            .filter(|op| !matches!(op, Instruction::Cache))
31719            .collect();
31720
31721        assert!(
31722            ops.windows(5).any(|window| {
31723                matches!(
31724                    window,
31725                    [
31726                        Instruction::Swap { .. },
31727                        Instruction::LoadConst { .. },
31728                        Instruction::MakeFunction,
31729                        Instruction::Swap { .. },
31730                        Instruction::Call { .. },
31731                    ]
31732                )
31733            }),
31734            "expected CPython generic defaults call pattern SWAP/MAKE_FUNCTION/SWAP/CALL, got ops={ops:?}"
31735        );
31736        assert!(
31737            !ops.windows(5).any(|window| {
31738                matches!(
31739                    window,
31740                    [
31741                        Instruction::MakeFunction,
31742                        Instruction::Swap { .. },
31743                        Instruction::Swap { .. },
31744                        Instruction::PushNull,
31745                        Instruction::Swap { .. },
31746                    ]
31747                )
31748            }),
31749            "CPython generic defaults use SWAP/CALL after codegen_make_closure(), not a PUSH_NULL reshuffle, got ops={ops:?}"
31750        );
31751    }
31752
31753    #[test]
31754    fn generic_function_type_params_reserve_defaults_like_cpython() {
31755        let code = compile_exec(
31756            "\
31757def func[T]():
31758    pass
31759",
31760        );
31761        let type_params =
31762            find_code(&code, "<generic parameters of func>").expect("missing type params code");
31763        // The slot is reserved even though nothing is passed into it.
31764        assert_eq!(type_params.arg_count, 0);
31765        assert_eq!(
31766            type_params
31767                .varnames
31768                .iter()
31769                .map(String::as_str)
31770                .collect::<Vec<_>>(),
31771            vec![".defaults", "T"]
31772        );
31773    }
31774
31775    #[test]
31776    fn generic_function_type_params_split_defaults_like_cpython() {
31777        let code = compile_exec(
31778            "\
31779def with_pos[T](a: T = 1):
31780    pass
31781def with_kw[U](*, a: U = 1):
31782    pass
31783",
31784        );
31785        let with_pos =
31786            find_code(&code, "<generic parameters of with_pos>").expect("missing type params code");
31787        let with_kw =
31788            find_code(&code, "<generic parameters of with_kw>").expect("missing type params code");
31789
31790        assert_eq!(
31791            with_pos
31792                .varnames
31793                .iter()
31794                .map(String::as_str)
31795                .collect::<Vec<_>>(),
31796            vec![".defaults"]
31797        );
31798        assert_eq!(with_pos.arg_count, 1);
31799        assert_eq!(
31800            with_kw
31801                .varnames
31802                .iter()
31803                .map(String::as_str)
31804                .collect::<Vec<_>>(),
31805            vec![".defaults", ".kwdefaults"]
31806        );
31807        assert_eq!(with_kw.arg_count, 1);
31808    }
31809
31810    #[test]
31811    fn class_type_param_bound_prefers_classdict_over_outer_function_local() {
31812        let code = compile_exec(
31813            "\
31814def f(self):
31815    class X:
31816        T = int
31817        def foo[U: T](self): ...
31818    T, U = X.foo.__type_params__
31819    return T.__bound__, U.__bound__
31820",
31821        );
31822        let f = find_code(&code, "f").expect("missing f code");
31823        assert!(
31824            !f.cellvars.iter().any(|name| name == "T"),
31825            "class-local type-param bound must not force outer function T cell, got cellvars={:?}",
31826            f.cellvars
31827        );
31828
31829        let class_code = find_code(f, "X").expect("missing X class code");
31830        assert!(
31831            !class_code.freevars.iter().any(|name| name == "T"),
31832            "class body must not close over outer T for class-local bound, got freevars={:?}",
31833            class_code.freevars
31834        );
31835
31836        let type_params =
31837            find_code(class_code, "<generic parameters of foo>").expect("missing type params code");
31838        assert_eq!(
31839            type_params
31840                .freevars
31841                .iter()
31842                .map(String::as_str)
31843                .collect::<Vec<_>>(),
31844            vec!["__classdict__"],
31845            "type params scope should close only over __classdict__, got freevars={:?}",
31846            type_params.freevars
31847        );
31848
31849        let bound = find_code(type_params, "U").expect("missing U bound code");
31850        assert_eq!(
31851            bound
31852                .freevars
31853                .iter()
31854                .map(String::as_str)
31855                .collect::<Vec<_>>(),
31856            vec!["__classdict__"],
31857            "bound evaluator should close only over __classdict__, got freevars={:?}",
31858            bound.freevars
31859        );
31860        assert!(
31861            bound
31862                .instructions
31863                .iter()
31864                .any(|unit| matches!(unit.op, Instruction::LoadFromDictOrGlobals { .. })),
31865            "bound evaluator should use LOAD_FROM_DICT_OR_GLOBALS for class-local T, got instructions={:?}",
31866            bound.instructions
31867        );
31868    }
31869
31870    #[test]
31871    fn class_type_param_bound_respects_class_global_over_outer_function_local() {
31872        let code = compile_exec(
31873            "\
31874def f(self):
31875    T = int
31876    class X:
31877        global T
31878        def foo[U: T](self): ...
31879    return X.foo.__type_params__
31880",
31881        );
31882        let f = find_code(&code, "f").expect("missing f code");
31883        assert!(
31884            f.cellvars.is_empty(),
31885            "class global type-param bound must not force outer function cells, got cellvars={:?}",
31886            f.cellvars
31887        );
31888
31889        let class_code = find_code(f, "X").expect("missing X class code");
31890        let type_params =
31891            find_code(class_code, "<generic parameters of foo>").expect("missing type params code");
31892        let bound = find_code(type_params, "U").expect("missing U bound code");
31893        assert!(
31894            bound
31895                .instructions
31896                .iter()
31897                .any(|unit| matches!(unit.op, Instruction::LoadGlobal { .. })),
31898            "explicit class global should resolve as LOAD_GLOBAL in bound evaluator, got instructions={:?}",
31899            bound.instructions
31900        );
31901        assert!(
31902            !bound.instructions.iter().any(|unit| {
31903                matches!(
31904                    unit.op,
31905                    Instruction::LoadFromDictOrGlobals { .. }
31906                        | Instruction::LoadFromDictOrDeref { .. }
31907                )
31908            }),
31909            "explicit class global should not use classdict/deref lookup, got instructions={:?}",
31910            bound.instructions
31911        );
31912    }
31913
31914    #[test]
31915    fn generic_type_alias_in_class_does_not_capture_module_name() {
31916        let code = compile_exec(
31917            r#"
31918T = U = "global"
31919class C:
31920    T = "class"
31921    U = "class"
31922    type Alias[T] = lambda: (T, U)
31923"#,
31924        );
31925        assert!(
31926            code.cellvars.is_empty(),
31927            "module T must remain a global name, got cellvars={:?}",
31928            code.cellvars
31929        );
31930
31931        let class_code = find_code(&code, "C").expect("missing class code");
31932        assert!(
31933            class_code.freevars.is_empty(),
31934            "plain module-level class must not close over module T, got freevars={:?}",
31935            class_code.freevars
31936        );
31937
31938        let type_params = find_code(class_code, "<generic parameters of Alias>")
31939            .expect("missing alias type params");
31940        assert_eq!(
31941            type_params
31942                .cellvars
31943                .iter()
31944                .map(String::as_str)
31945                .collect::<Vec<_>>(),
31946            vec!["T"],
31947            "alias type parameter T should be local to the type-params scope, got cellvars={:?}",
31948            type_params.cellvars
31949        );
31950        assert_eq!(
31951            type_params
31952                .freevars
31953                .iter()
31954                .map(String::as_str)
31955                .collect::<Vec<_>>(),
31956            vec!["__classdict__"],
31957            "alias type params should close only over the classdict, got freevars={:?}",
31958            type_params.freevars
31959        );
31960
31961        let alias = find_code(type_params, "Alias").expect("missing alias value code");
31962        assert_eq!(
31963            alias
31964                .freevars
31965                .iter()
31966                .map(String::as_str)
31967                .collect::<Vec<_>>(),
31968            vec!["T", "__classdict__"],
31969            "alias value should close over its type parameter and classdict, got freevars={:?}",
31970            alias.freevars
31971        );
31972        let lambda = find_code(alias, "<lambda>").expect("missing alias lambda");
31973        assert_eq!(
31974            lambda
31975                .freevars
31976                .iter()
31977                .map(String::as_str)
31978                .collect::<Vec<_>>(),
31979            vec!["T"],
31980            "lambda should close over alias type parameter T only, got freevars={:?}",
31981            lambda.freevars
31982        );
31983    }
31984
31985    #[test]
31986    fn nested_generic_class_base_child_free_keeps_classdict_lookup() {
31987        for (child_name, base_expr) in [
31988            ("<genexpr>", "make_base(T for _ in (1,))"),
31989            ("<listcomp>", "make_base([T for _ in (1,)])"),
31990            ("<lambda>", "make_base(lambda: T)"),
31991        ] {
31992            let code = compile_exec(&format!(
31993                "\
31994class C[T]:
31995    T = 'class'
31996    class Inner[U]({base_expr}, make_base(T)):
31997        pass
31998"
31999            ));
32000            let type_params = find_code(&code, "<generic parameters of Inner>")
32001                .expect("missing inner type params code");
32002            assert_eq!(
32003                type_params
32004                    .freevars
32005                    .iter()
32006                    .map(String::as_str)
32007                    .collect::<Vec<_>>(),
32008                vec!["T", "__classdict__"],
32009                "inner type params should keep T as a child-only freevar for {child_name}, got freevars={:?}",
32010                type_params.freevars
32011            );
32012            assert!(
32013                type_params
32014                    .instructions
32015                    .iter()
32016                    .any(|unit| matches!(unit.op, Instruction::LoadFromDictOrGlobals { .. })),
32017                "direct class-local T lookup should still use classdict/global path for {child_name}, got instructions={:?}",
32018                type_params.instructions
32019            );
32020
32021            let child = find_code(type_params, child_name).expect("missing child code");
32022            assert_eq!(
32023                child
32024                    .freevars
32025                    .iter()
32026                    .map(String::as_str)
32027                    .collect::<Vec<_>>(),
32028                vec!["T"],
32029                "{child_name} should close over T from the type-params scope, got freevars={:?}",
32030                child.freevars
32031            );
32032        }
32033    }
32034
32035    #[test]
32036    fn non_inlined_listcomp_return_uses_comprehension_location_like_cpython() {
32037        let code = compile_exec(
32038            "\
32039class C[T]:
32040    class Inner[U](
32041        make_base([T for _ in (1,)])
32042    ):
32043        pass
32044",
32045        );
32046        let listcomp = find_code(&code, "<listcomp>").expect("missing listcomp code");
32047        let return_positions: Vec<_> = listcomp
32048            .instructions
32049            .iter()
32050            .zip(&listcomp.locations)
32051            .filter_map(|(unit, (location, end_location))| {
32052                matches!(unit.op, Instruction::ReturnValue).then_some((
32053                    location.line.get(),
32054                    location.character_offset.get(),
32055                    end_location.line.get(),
32056                    end_location.character_offset.get(),
32057                ))
32058            })
32059            .collect();
32060
32061        assert_eq!(
32062            return_positions,
32063            vec![(3, 19, 3, 36)],
32064            "CPython codegen_comprehension() emits non-gen RETURN_VALUE at LOC(e)"
32065        );
32066    }
32067
32068    #[test]
32069    fn class_annotation_global_resolution_matches_cpython() {
32070        let class_global = compile_exec(
32071            "\
32072X = 'global'
32073class C:
32074    locals()['X'] = 'class'
32075    global X
32076    y: X
32077",
32078        );
32079        let annotate =
32080            find_code(&class_global, "__annotate__").expect("missing class __annotate__ code");
32081        assert!(
32082            annotate
32083                .instructions
32084                .iter()
32085                .any(|unit| matches!(unit.op, Instruction::LoadGlobal { .. })),
32086            "expected explicit class global to use LOAD_GLOBAL, got instructions={:?}",
32087            annotate.instructions
32088        );
32089        assert!(
32090            !annotate
32091                .instructions
32092                .iter()
32093                .any(|unit| matches!(unit.op, Instruction::LoadFromDictOrGlobals { .. })),
32094            "did not expect class explicit global to use LOAD_FROM_DICT_OR_GLOBALS, got instructions={:?}",
32095            annotate.instructions
32096        );
32097
32098        let outer_global = compile_exec(
32099            "\
32100def f():
32101    global X
32102    class C:
32103        locals()['X'] = 'class'
32104        y: X
32105",
32106        );
32107        let annotate = find_code(&outer_global, "__annotate__")
32108            .expect("missing nested class __annotate__ code");
32109        assert!(
32110            annotate
32111                .instructions
32112                .iter()
32113                .any(|unit| matches!(unit.op, Instruction::LoadFromDictOrGlobals { .. })),
32114            "expected outer explicit global in class annotation to use LOAD_FROM_DICT_OR_GLOBALS, got instructions={:?}",
32115            annotate.instructions
32116        );
32117    }
32118
32119    #[test]
32120    fn constant_tuple_binops_fold_like_cpython() {
32121        let code = compile_exec("value = (1,) * 17 + ('spam',)\n");
32122
32123        assert!(
32124            !code
32125                .instructions
32126                .iter()
32127                .any(|unit| matches!(unit.op, Instruction::BinaryOp { .. })),
32128            "tuple constant binops should fold away, got instructions={:?}",
32129            code.instructions
32130        );
32131        assert!(code.constants.iter().any(|constant| matches!(
32132            constant,
32133            ConstantData::Tuple { elements }
32134                if elements.len() == 18
32135                    && elements[..17]
32136                        .iter()
32137                        .all(|elt| matches!(elt, ConstantData::Integer { value } if *value == BigInt::from(1)))
32138                    && matches!(&elements[17], ConstantData::Str { value } if value.to_string() == "spam")
32139        )));
32140    }
32141
32142    #[test]
32143    fn tuple_not_keeps_to_bool_unary_not_like_cpython() {
32144        let code = compile_exec(
32145            "\
32146def f():
32147    return not ()
32148",
32149        );
32150        let f = find_code(&code, "f").expect("missing function code");
32151        let ops = f
32152            .instructions
32153            .iter()
32154            .filter(|unit| !matches!(unit.op, Instruction::Cache))
32155            .collect::<Vec<_>>();
32156
32157        assert!(
32158            ops.windows(3).any(|window| {
32159                matches!(window[0].op, Instruction::LoadConst { consti }
32160                if matches!(
32161                    &f.constants[consti.get(OpArg::new(u32::from(u8::from(window[0].arg))))],
32162                    ConstantData::Tuple { elements } if elements.is_empty()
32163                )) && matches!(window[1].op, Instruction::ToBool)
32164                    && matches!(window[2].op, Instruction::UnaryNot)
32165            }),
32166            "CPython codegen emits TO_BOOL; UNARY_NOT for UnaryOp(Not), while flowgraph.c folds tuple literals only after the LOAD_CONST+TO_BOOL pass, got instructions={:?}",
32167            f.instructions
32168        );
32169    }
32170
32171    #[test]
32172    fn tuple_if_test_keeps_to_bool_jump_like_cpython() {
32173        let code = compile_exec(
32174            "\
32175def f():
32176    if ():
32177        return 1
32178    return 2
32179",
32180        );
32181        let f = find_code(&code, "f").expect("missing function code");
32182        let ops = f
32183            .instructions
32184            .iter()
32185            .filter(|unit| !matches!(unit.op, Instruction::Cache))
32186            .collect::<Vec<_>>();
32187
32188        assert!(
32189            ops.windows(3).any(|window| {
32190                matches!(window[0].op, Instruction::LoadConst { consti }
32191                if matches!(
32192                    &f.constants[consti.get(OpArg::new(u32::from(u8::from(window[0].arg))))],
32193                    ConstantData::Tuple { elements } if elements.is_empty()
32194                )) && matches!(window[1].op, Instruction::ToBool)
32195                    && matches!(window[2].op, Instruction::PopJumpIfFalse { .. })
32196            }),
32197            "CPython leaves tuple literal truth tests as LOAD_CONST tuple; TO_BOOL; POP_JUMP_IF_FALSE because tuple folding happens after constant jump folding, got instructions={:?}",
32198            f.instructions
32199        );
32200    }
32201
32202    #[test]
32203    fn constant_list_iterable_uses_tuple() {
32204        let code = compile_exec(
32205            "\
32206def f():
32207    return {x: y for x, y in [(1, 2), ]}
32208",
32209        );
32210        let f = find_code(&code, "f").expect("missing function code");
32211
32212        assert!(
32213            !f.instructions
32214                .iter()
32215                .any(|unit| matches!(unit.op, Instruction::BuildList { .. })),
32216            "constant list iterable should avoid BUILD_LIST before GET_ITER"
32217        );
32218        assert!(f.constants.iter().any(|constant| matches!(
32219            constant,
32220            ConstantData::Tuple { elements }
32221                if matches!(
32222                    elements.as_slice(),
32223                    [ConstantData::Tuple { elements: inner }]
32224                        if matches!(
32225                            inner.as_slice(),
32226                            [
32227                                ConstantData::Integer { .. },
32228                                ConstantData::Integer { .. }
32229                            ]
32230                        )
32231                )
32232        )));
32233    }
32234
32235    #[test]
32236    fn constant_list_iterable_preserves_cpython_const_order() {
32237        let code = compile_exec(
32238            "\
32239def f():
32240    for x in ['a', 'b', 'c']:
32241        pass
32242",
32243        );
32244        let f = find_code(&code, "f").expect("missing function code");
32245        let constants = f.constants.iter().collect::<Vec<_>>();
32246
32247        assert!(
32248            matches!(constants[0], ConstantData::Str { value } if value.to_string() == "a"),
32249            "CPython emits list elements as LOAD_CONST before flowgraph folds GET_ITER lists"
32250        );
32251        assert!(matches!(constants[1], ConstantData::None));
32252        assert!(matches!(
32253            constants[2],
32254            ConstantData::Tuple { elements }
32255                if matches!(
32256                    elements.as_slice(),
32257                    [
32258                        ConstantData::Str { value: first },
32259                        ConstantData::Str { value: second },
32260                        ConstantData::Str { value: third },
32261                    ] if first.to_string() == "a"
32262                        && second.to_string() == "b"
32263                        && third.to_string() == "c"
32264                )
32265        ));
32266    }
32267
32268    #[test]
32269    fn try_except_folded_tuple_consts_follow_cpython_block_order() {
32270        let code = compile_exec(
32271            "\
32272def f(macrelease):
32273    try:
32274        g()
32275    except ValueError:
32276        macrelease = (10, 3)
32277    if macrelease >= (10, 4):
32278        pass
32279",
32280        );
32281        let f = find_code(&code, "f").expect("missing function code");
32282        let constants = f.constants.iter().collect::<Vec<_>>();
32283
32284        assert!(
32285            constants.windows(2).any(|window| {
32286                matches!(
32287                    window,
32288                    [
32289                        ConstantData::Tuple { elements: first },
32290                        ConstantData::Tuple { elements: second },
32291                    ] if matches!(
32292                        (first.as_slice(), second.as_slice()),
32293                        (
32294                            [
32295                                ConstantData::Integer { value: a },
32296                                ConstantData::Integer { value: b },
32297                            ],
32298                            [
32299                                ConstantData::Integer { value: c },
32300                                ConstantData::Integer { value: d },
32301                            ],
32302                        ) if a == &BigInt::from(10)
32303                            && b == &BigInt::from(3)
32304                            && c == &BigInt::from(10)
32305                            && d == &BigInt::from(4)
32306                    )
32307                )
32308            }),
32309            "CPython flowgraph.c walks b_next order, so the except-body tuple is folded before the following if-test tuple; got {constants:?}"
32310        );
32311    }
32312
32313    #[test]
32314    fn small_set_membership_folds_before_later_unary_const_like_cpython() {
32315        let code = compile_exec(
32316            r#"
32317def f(method, n):
32318    if method not in {"linear", "ranked"}:
32319        pass
32320    if method == "ranked":
32321        start = (n - 1) / -2
32322"#,
32323        );
32324        let f = find_code(&code, "f").expect("missing function code");
32325        let constants = f.constants.iter().collect::<Vec<_>>();
32326        let frozenset_index = constants
32327            .iter()
32328            .position(|constant| matches!(constant, ConstantData::Frozenset { .. }))
32329            .expect("missing folded membership frozenset");
32330        let negative_two_index = constants
32331            .iter()
32332            .position(|constant| {
32333                matches!(
32334                    constant,
32335                    ConstantData::Integer { value } if value == &BigInt::from(-2)
32336                )
32337            })
32338            .expect("missing folded -2 constant");
32339
32340        assert!(
32341            frozenset_index < negative_two_index,
32342            "CPython flowgraph.c optimizes BUILD_SET+CONTAINS_OP inline before folding the later unary -2; got {constants:?}"
32343        );
32344    }
32345
32346    #[test]
32347    fn boolop_const_order_keeps_cpython_codegen_constants() {
32348        let code = compile_exec(
32349            "\
32350def or_false(x):
32351    return False or x
32352
32353def zero_or_tuple():
32354    return 0 or (1, -1)
32355
32356def tuple_or_tuple():
32357    return (1, -1) or (-1, 1)
32358",
32359        );
32360
32361        let or_false = find_code(&code, "or_false").expect("missing or_false code");
32362        let constants = or_false.constants.iter().collect::<Vec<_>>();
32363        assert_eq!(constants.len(), 1);
32364        assert!(
32365            matches!(constants[0], ConstantData::Boolean { value: false }),
32366            "CPython registers the skipped boolop literal before flowgraph removes the branch"
32367        );
32368
32369        let zero_or_tuple = find_code(&code, "zero_or_tuple").expect("missing zero_or_tuple code");
32370        let constants = zero_or_tuple.constants.iter().collect::<Vec<_>>();
32371        assert_eq!(constants.len(), 2);
32372        assert!(
32373            matches!(
32374                constants[0],
32375                ConstantData::Integer { value } if value == &BigInt::from(0)
32376            ) && matches!(
32377                constants[1],
32378                ConstantData::Tuple { elements }
32379                    if matches!(
32380                    elements.as_slice(),
32381                    [
32382                        ConstantData::Integer { value: one },
32383                        ConstantData::Integer { value: minus_one },
32384                    ] if one == &BigInt::from(1) && minus_one == &BigInt::from(-1)
32385                )
32386            ),
32387            "CPython keeps the skipped scalar literal before the folded tuple constant"
32388        );
32389
32390        let tuple_or_tuple =
32391            find_code(&code, "tuple_or_tuple").expect("missing tuple_or_tuple code");
32392        let constants = tuple_or_tuple.constants.iter().collect::<Vec<_>>();
32393        assert_eq!(constants.len(), 3);
32394        assert!(
32395            matches!(
32396                constants[0],
32397                ConstantData::Integer { value } if value == &BigInt::from(1)
32398            ) && matches!(
32399                constants[1],
32400                ConstantData::Tuple { elements }
32401                    if matches!(
32402                    elements.as_slice(),
32403                    [
32404                        ConstantData::Integer { value: one },
32405                        ConstantData::Integer { value: minus_one },
32406                    ] if one == &BigInt::from(1) && minus_one == &BigInt::from(-1)
32407                )
32408            ) && matches!(
32409                constants[2],
32410                ConstantData::Tuple { elements }
32411                    if matches!(
32412                    elements.as_slice(),
32413                    [
32414                        ConstantData::Integer { value: minus_one },
32415                        ConstantData::Integer { value: one },
32416                    ] if minus_one == &BigInt::from(-1) && one == &BigInt::from(1)
32417                )
32418            ),
32419            "CPython compiles boolop tuple heads before flowgraph folds them"
32420        );
32421    }
32422
32423    #[test]
32424    fn chained_compare_jump_if_runs_cpython_check_compare_warning() {
32425        let message = first_exec_warning(
32426            "\
32427def f(x):
32428    if 1 is 1 < x:
32429        return x
32430",
32431        );
32432        assert!(
32433            message.contains("\"is\" with 'int' literal"),
32434            "CPython codegen_jump_if() checks chained comparisons before conditional lowering, got {message:?}"
32435        );
32436    }
32437
32438    #[test]
32439    fn lambda_without_body_constants_keeps_none_like_cpython() {
32440        let code = compile_exec("f = lambda x: x");
32441        let lambda = find_code(&code, "<lambda>").expect("missing lambda code");
32442        let constants = lambda.constants.iter().collect::<Vec<_>>();
32443        assert_eq!(constants.len(), 1);
32444
32445        assert!(
32446            matches!(constants[0], ConstantData::None),
32447            "CPython AddReturnAtEnd registers None for constant-free lambdas"
32448        );
32449    }
32450
32451    #[test]
32452    fn generator_lambda_without_body_constants_omits_none_like_cpython() {
32453        let code = compile_exec("f = lambda x: (yield x)");
32454        let lambda = find_code(&code, "<lambda>").expect("missing lambda code");
32455
32456        assert!(
32457            lambda.constants.is_empty(),
32458            "CPython codegen_lambda() assembles generator lambdas with addNone=0"
32459        );
32460    }
32461
32462    #[test]
32463    fn call_function_ex_empty_args_tuple_is_folded_late_like_cpython() {
32464        let code = compile_exec(
32465            "\
32466def f(g, kwargs, ns):
32467    g(**kwargs)
32468    ns['T']
32469",
32470        );
32471        let f = find_code(&code, "f").expect("missing function code");
32472        let constants = f.constants.iter().collect::<Vec<_>>();
32473        assert_eq!(constants.len(), 3);
32474
32475        assert!(
32476            matches!(constants[0], ConstantData::Str { value } if value.to_string() == "T")
32477                && matches!(constants[1], ConstantData::None)
32478                && matches!(constants[2], ConstantData::Tuple { elements } if elements.is_empty()),
32479            "CPython emits BUILD_TUPLE 0 for CALL_FUNCTION_EX args and folds it after earlier constants"
32480        );
32481    }
32482
32483    #[test]
32484    fn large_constant_list_iterable_keeps_streaming_list_build() {
32485        let source = format!(
32486            "def f():\n    for x in [{}]:\n        pass\n",
32487            (0..=STACK_USE_GUIDELINE)
32488                .map(|i| format!("'v{i}'"))
32489                .collect::<Vec<_>>()
32490                .join(", ")
32491        );
32492        let code = compile_exec(&source);
32493        let f = find_code(&code, "f").expect("missing function code");
32494
32495        assert!(
32496            f.instructions
32497                .iter()
32498                .any(|unit| matches!(unit.op, Instruction::BuildList { .. })),
32499            "large list iterable should keep CPython streaming BUILD_LIST form, got instructions={:?}",
32500            f.instructions
32501        );
32502        assert!(
32503            f.instructions
32504                .iter()
32505                .any(|unit| matches!(unit.op, Instruction::ListAppend { .. })),
32506            "large list iterable should use LIST_APPEND streaming form, got instructions={:?}",
32507            f.instructions
32508        );
32509    }
32510
32511    #[test]
32512    fn constant_set_iterable_uses_frozenset_const() {
32513        let code = compile_exec(
32514            "\
32515def f():
32516    return [x for x in {1, 2, 3}]
32517",
32518        );
32519        let f = find_code(&code, "f").expect("missing function code");
32520
32521        assert!(
32522            !f.instructions
32523                .iter()
32524                .any(|unit| matches!(unit.op, Instruction::BuildSet { .. })),
32525            "constant set iterable should avoid BUILD_SET before GET_ITER"
32526        );
32527        assert!(f.constants.iter().any(|constant| matches!(
32528            constant,
32529            ConstantData::Frozenset { elements }
32530                if matches!(
32531                    elements.as_slice(),
32532                    [
32533                        ConstantData::Integer { .. },
32534                        ConstantData::Integer { .. },
32535                        ConstantData::Integer { .. }
32536                    ]
32537                )
32538        )));
32539    }
32540
32541    #[test]
32542    fn constant_list_membership_uses_tuple_const() {
32543        let code = compile_exec(
32544            "\
32545f = lambda x: x in [1, 2, 3]
32546",
32547        );
32548        let lambda = find_code(&code, "<lambda>").expect("missing lambda code");
32549
32550        assert!(
32551            !lambda
32552                .instructions
32553                .iter()
32554                .any(|unit| matches!(unit.op, Instruction::BuildList { .. })),
32555            "constant list membership should avoid BUILD_LIST before CONTAINS_OP"
32556        );
32557        assert!(lambda.constants.iter().any(|constant| matches!(
32558            constant,
32559            ConstantData::Tuple { elements }
32560                if matches!(
32561                    elements.as_slice(),
32562                    [
32563                        ConstantData::Integer { .. },
32564                        ConstantData::Integer { .. },
32565                        ConstantData::Integer { .. }
32566                    ]
32567                )
32568        )));
32569    }
32570
32571    #[test]
32572    fn small_constant_set_membership_uses_frozenset_const() {
32573        let code = compile_exec(
32574            "\
32575f = lambda x: x in {0}
32576",
32577        );
32578        let lambda = find_code(&code, "<lambda>").expect("missing lambda code");
32579
32580        assert!(
32581            !lambda
32582                .instructions
32583                .iter()
32584                .any(|unit| matches!(unit.op, Instruction::BuildSet { .. })),
32585            "constant set membership should avoid BUILD_SET before CONTAINS_OP"
32586        );
32587        assert!(lambda.constants.iter().any(|constant| matches!(
32588            constant,
32589            ConstantData::Frozenset { elements }
32590                if matches!(elements.as_slice(), [ConstantData::Integer { value }] if *value == BigInt::from(0))
32591        )));
32592    }
32593
32594    #[test]
32595    fn frozenset_membership_consts_deduplicate_like_cpython_constant_key() {
32596        let code = compile_exec(
32597            "\
32598def f(x):
32599    return x in {1, 2}, x in {2, 1}, x in {1, 1}
32600",
32601        );
32602        let f = find_code(&code, "f").expect("missing function code");
32603        let frozensets: Vec<_> = f
32604            .constants
32605            .iter()
32606            .filter_map(|constant| match constant {
32607                ConstantData::Frozenset { elements } => Some(elements.as_slice()),
32608                _ => None,
32609            })
32610            .collect();
32611
32612        assert_eq!(
32613            frozensets.len(),
32614            2,
32615            "CPython folds equal frozensets to the same const key and removes duplicate set items"
32616        );
32617        assert!(
32618            frozensets.iter().any(|elements| elements.len() == 2),
32619            "missing shared frozenset constant for {{1, 2}} and {{2, 1}}"
32620        );
32621        assert!(
32622            frozensets.iter().any(|elements| elements.len() == 1),
32623            "missing duplicate-collapsed frozenset constant for {{1, 1}}"
32624        );
32625    }
32626
32627    #[test]
32628    fn nonconstant_list_membership_uses_tuple() {
32629        let code = compile_exec(
32630            "\
32631def f(a, b, c, x):
32632    return x in [a, b, c]
32633",
32634        );
32635        let f = find_code(&code, "f").expect("missing f code");
32636        let ops: Vec<_> = f
32637            .instructions
32638            .iter()
32639            .map(|unit| unit.op)
32640            .filter(|op| !matches!(op, Instruction::Cache))
32641            .collect();
32642
32643        assert!(
32644            ops.windows(2).any(|window| {
32645                matches!(
32646                    window,
32647                    [
32648                        Instruction::BuildTuple { .. },
32649                        Instruction::ContainsOp { .. }
32650                    ]
32651                )
32652            }),
32653            "expected BUILD_TUPLE before CONTAINS_OP for non-constant list membership, got ops={ops:?}"
32654        );
32655    }
32656
32657    #[test]
32658    fn unary_not_membership_and_identity_invert_compare_op() {
32659        let code = compile_exec(
32660            "\
32661def f(a, b, d):
32662    x = not (a in d)
32663    y = not (a is b)
32664    return x, y
32665",
32666        );
32667        let f = find_code(&code, "f").expect("missing f code");
32668        let instructions: Vec<_> = f
32669            .instructions
32670            .iter()
32671            .filter(|unit| !matches!(unit.op, Instruction::Cache))
32672            .collect();
32673        let ops: Vec<_> = instructions.iter().map(|unit| unit.op).collect();
32674
32675        assert!(
32676            !ops.iter().any(|op| matches!(op, Instruction::UnaryNot)),
32677            "CPython folds CONTAINS_OP/IS_OP + UNARY_NOT into inverted op, got ops={ops:?}"
32678        );
32679        assert!(instructions.iter().any(|unit| {
32680            matches!(unit.op, Instruction::ContainsOp { invert } if invert.get(OpArg::new(unit.arg.as_u32())) == Invert::Yes)
32681        }));
32682        assert!(instructions.iter().any(|unit| {
32683            matches!(unit.op, Instruction::IsOp { invert } if invert.get(OpArg::new(unit.arg.as_u32())) == Invert::Yes)
32684        }));
32685    }
32686
32687    #[test]
32688    fn starred_tuple_iterable_drops_list_to_tuple_before_get_iter() {
32689        let code = compile_exec(
32690            "\
32691def f(a, b, c):
32692    for x in *a, *b, *c:
32693        pass
32694",
32695        );
32696        let f = find_code(&code, "f").expect("missing function code");
32697
32698        assert!(
32699            !has_intrinsic_1(f, IntrinsicFunction1::ListToTuple),
32700            "LIST_TO_TUPLE should be removed before GET_ITER in for-iterable context"
32701        );
32702        assert!(
32703            f.instructions
32704                .iter()
32705                .any(|unit| matches!(unit.op, Instruction::GetIter)),
32706            "expected GET_ITER in for loop"
32707        );
32708    }
32709
32710    #[test]
32711    fn comprehension_single_list_iterable_uses_tuple() {
32712        let code = compile_exec(
32713            "\
32714def g():
32715    [x for x in [(yield 1)]]
32716",
32717        );
32718        let g = find_code(&code, "g").expect("missing g code");
32719        let ops: Vec<_> = g
32720            .instructions
32721            .iter()
32722            .map(|unit| unit.op)
32723            .filter(|op| !matches!(op, Instruction::Cache))
32724            .collect();
32725
32726        assert!(
32727            ops.windows(2).any(|window| {
32728                matches!(
32729                    window,
32730                    [Instruction::BuildTuple { .. }, Instruction::GetIter]
32731                )
32732            }),
32733            "expected BUILD_TUPLE before GET_ITER for single-item list iterable in comprehension, got ops={ops:?}"
32734        );
32735    }
32736
32737    #[test]
32738    fn comprehension_list_iterable_build_uses_iter_location_like_cpython() {
32739        let code = compile_exec(
32740            "\
32741async def f(i):
32742    return i
32743
32744async def run_list():
32745    return [await c for c in [f(1), f(41)]]
32746",
32747        );
32748        let run_list = find_code(&code, "run_list").expect("missing run_list code");
32749        assert_eq!(
32750            run_list.linetable.as_ref(),
32751            &[
32752                0xe9, 0x00, 0x80, 0x00, 0xdc, 0x1e, 0x1f, 0xa0, 0x01, 0x9b, 0x64, 0xa4, 0x41, 0xa0,
32753                0x62, 0xa3, 0x45, 0x99, 0x5d, 0xd3, 0x0b, 0x2b, 0x99, 0x5d, 0x98, 0x01, 0x8f, 0x47,
32754                0x8a, 0x47, 0x99, 0x5d, 0xd1, 0x0b, 0x2b, 0xd0, 0x04, 0x2b, 0x89, 0x47, 0xf9, 0xd2,
32755                0x0b, 0x2b, 0xf9,
32756            ],
32757            "CPython codegen_comprehension_iter() emits GET_ITER at LOC(comp->iter)"
32758        );
32759    }
32760
32761    #[test]
32762    fn comprehension_boolop_iter_get_iter_uses_iter_location_like_cpython() {
32763        let code = compile_exec(
32764            "\
32765def f(self):
32766    return any(not w.cancelled() for w in (self._waiters or ()))
32767",
32768        );
32769        let f = find_code(&code, "f").expect("missing f code");
32770        let get_iter_positions: Vec<_> = f
32771            .instructions
32772            .iter()
32773            .zip(&f.locations)
32774            .filter_map(|(unit, (location, end_location))| {
32775                matches!(unit.op, Instruction::GetIter).then_some((
32776                    location.line.get(),
32777                    location.character_offset.get(),
32778                    end_location.line.get(),
32779                    end_location.character_offset.get(),
32780                ))
32781            })
32782            .collect();
32783
32784        assert!(
32785            get_iter_positions.contains(&(2, 44, 2, 63)),
32786            "CPython codegen_comprehension_iter() emits GET_ITER at LOC(comp->iter), got {get_iter_positions:?}"
32787        );
32788    }
32789
32790    #[test]
32791    fn inlined_comprehension_backedges_use_element_location_like_cpython() {
32792        let code = compile_exec(
32793            "\
32794async def f(i):
32795    return i
32796
32797async def run_list():
32798    return [s for c in [f(''), f('abc')] for s in await c]
32799",
32800        );
32801        let run_list = find_code(&code, "run_list").expect("missing run_list code");
32802        assert_eq!(
32803            run_list.linetable.as_ref(),
32804            &[
32805                0xe9, 0x00, 0x80, 0x00, 0xdc, 0x18, 0x19, 0x98, 0x22, 0x9b, 0x05, 0x9c, 0x71, 0xa0,
32806                0x15, 0x9b, 0x78, 0xd1, 0x17, 0x28, 0xd4, 0x0b, 0x3a, 0xd1, 0x17, 0x28, 0x90, 0x21,
32807                0xb7, 0x27, 0xb2, 0x27, 0xa8, 0x51, 0x8a, 0x41, 0xb1, 0x27, 0x89, 0x41, 0xd1, 0x17,
32808                0x28, 0xd2, 0x0b, 0x3a, 0xd0, 0x04, 0x3a, 0xb1, 0x27, 0xf9, 0xd3, 0x0b, 0x3a, 0xf9,
32809            ],
32810            "CPython codegen_sync_comprehension_generator() emits comprehension backedges at elt_loc"
32811        );
32812    }
32813
32814    #[test]
32815    fn nested_dict_comprehension_outer_backedge_uses_key_location_like_cpython() {
32816        let code = compile_exec(
32817            "\
32818def f(items):
32819    return {op: i for i, ops in items for op in ops}
32820",
32821        );
32822        let f = find_code(&code, "f").expect("missing function code");
32823        let backedge_positions: Vec<_> = f
32824            .instructions
32825            .iter()
32826            .zip(&f.locations)
32827            .filter_map(|(unit, (location, end_location))| {
32828                matches!(unit.op, Instruction::JumpBackward { .. }).then_some((
32829                    location.line.get(),
32830                    location.character_offset.get(),
32831                    end_location.line.get(),
32832                    end_location.character_offset.get(),
32833                ))
32834            })
32835            .collect();
32836
32837        assert!(
32838            backedge_positions.contains(&(2, 13, 2, 18)),
32839            "CPython extends only the terminal dict-comprehension MAP_ADD/backedge location from key through value, got {backedge_positions:?}"
32840        );
32841        assert!(
32842            backedge_positions.contains(&(2, 13, 2, 15)),
32843            "CPython keeps outer dict-comprehension generator backedges at LOC(key), got {backedge_positions:?}"
32844        );
32845    }
32846
32847    #[test]
32848    fn inlined_comprehension_filter_jump_uses_element_location_like_cpython() {
32849        let code = compile_exec(
32850            "\
32851def f(self):
32852    return [action for action in self._actions if action.option_strings]
32853",
32854        );
32855        let f = find_code(&code, "f").expect("missing function code");
32856        let filter_jump_position = f
32857            .instructions
32858            .iter()
32859            .zip(&f.locations)
32860            .find_map(|(unit, (location, end_location))| {
32861                matches!(unit.op, Instruction::PopJumpIfTrue { .. }).then_some((
32862                    location.line.get(),
32863                    location.character_offset.get(),
32864                    end_location.line.get(),
32865                    end_location.character_offset.get(),
32866                ))
32867            })
32868            .expect("missing optimized filter jump");
32869        assert_eq!(
32870            filter_jump_position,
32871            (2, 13, 2, 19),
32872            "CPython inlined comprehension filter jump inherits the element/backedge location after CFG cleanup"
32873        );
32874    }
32875
32876    #[test]
32877    fn inlined_comprehension_ifexp_guard_jump_uses_body_location_like_cpython() {
32878        let code = compile_exec(
32879            "\
32880def f(fields):
32881    return [f for f in fields if (f.compare if f.hash is None else f.hash)]
32882",
32883        );
32884        let f = find_code(&code, "f").expect("missing function code");
32885        let jump_forward_position = f
32886            .instructions
32887            .iter()
32888            .zip(&f.locations)
32889            .find_map(|(unit, (location, end_location))| {
32890                matches!(unit.op, Instruction::JumpForward { .. }).then_some((
32891                    location.line.get(),
32892                    location.character_offset.get(),
32893                    end_location.line.get(),
32894                    end_location.character_offset.get(),
32895                ))
32896            })
32897            .expect("missing if-expression body jump");
32898        assert_eq!(
32899            jump_forward_position,
32900            (2, 35, 2, 44),
32901            "CPython flowgraph.c::propagate_line_numbers() copies the if-expression body location onto the NO_LOCATION jump"
32902        );
32903    }
32904
32905    #[test]
32906    fn inlined_async_comprehension_end_async_for_uses_comprehension_location_like_cpython() {
32907        let code = compile_exec(
32908            "\
32909async def f(it):
32910    for i in it:
32911        yield i
32912
32913async def run_list():
32914    return [i + 1 async for i in f([10, 20])]
32915",
32916        );
32917        let run_list = find_code(&code, "run_list").expect("missing run_list code");
32918        assert_eq!(
32919            run_list.linetable.as_ref(),
32920            &[
32921                0xe9, 0x00, 0x80, 0x00, 0xdc, 0x21, 0x22, 0xa0, 0x42, 0xa8, 0x02, 0xa0, 0x38, 0xa4,
32922                0x1b, 0xd7, 0x0b, 0x2d, 0xd3, 0x0b, 0x2d, 0x98, 0x41, 0x90, 0x01, 0x8f, 0x45, 0x88,
32923                0x45, 0xd4, 0x0b, 0x2d, 0xd0, 0x04, 0x2d, 0xf9, 0xd2, 0x0b, 0x2d, 0xf9,
32924            ],
32925            "CPython codegen_async_comprehension_generator() emits END_ASYNC_FOR at comprehension loc"
32926        );
32927    }
32928
32929    #[test]
32930    fn async_for_anext_sequence_uses_statement_location_like_cpython() {
32931        let code = compile_exec(
32932            "\
32933async def f(source, buffer):
32934    async for i1, i2 in source():
32935        buffer.append(i1 + i2)
32936",
32937        );
32938        let f = find_code(&code, "f").expect("missing f code");
32939        assert_eq!(
32940            f.linetable.as_ref(),
32941            &[
32942                0xe9, 0x00, 0x80, 0x00, 0xd9, 0x18, 0x1e, 0x9c, 0x08, 0xf7, 0x00, 0x01, 0x05, 0x1f,
32943                0xf0, 0x00, 0x01, 0x05, 0x1f, 0x89, 0x66, 0x88, 0x62, 0xd8, 0x08, 0x0e, 0x8f, 0x0d,
32944                0x89, 0x0d, 0x90, 0x62, 0x95, 0x67, 0xd6, 0x08, 0x1e, 0xf1, 0x03, 0x01, 0x05, 0x1f,
32945                0x9a, 0x08, 0xf9,
32946            ],
32947            "CPython codegen_async_for() emits GET_ANEXT/yield-from scaffolding at LOC(s)"
32948        );
32949    }
32950
32951    #[test]
32952    fn nested_comprehension_list_iterable_uses_tuple() {
32953        let code = compile_exec(
32954            "\
32955def f():
32956    return [[y for y in [x, x + 1]] for x in [1, 3, 5]]
32957",
32958        );
32959        let f = find_code(&code, "f").expect("missing f code");
32960        let ops: Vec<_> = f
32961            .instructions
32962            .iter()
32963            .map(|unit| unit.op)
32964            .filter(|op| !matches!(op, Instruction::Cache))
32965            .collect();
32966
32967        assert!(
32968            ops.windows(2).any(|window| {
32969                matches!(
32970                    window,
32971                    [Instruction::BuildTuple { .. }, Instruction::GetIter]
32972                )
32973            }),
32974            "expected BUILD_TUPLE before GET_ITER for nested list iterable in comprehension, got ops={ops:?}"
32975        );
32976    }
32977
32978    #[test]
32979    fn comprehension_singleton_sub_iter_uses_assignment_idiom() {
32980        let code = compile_exec(
32981            "\
32982def f():
32983    return {j: j * j for i in range(4) for j in [i + 1]}
32984",
32985        );
32986        let f = find_code(&code, "f").expect("missing f code");
32987        let for_iter_count = f
32988            .instructions
32989            .iter()
32990            .filter(|unit| matches!(unit.op, Instruction::ForIter { .. }))
32991            .count();
32992        let has_map_add_depth_2 = f.instructions.iter().any(|unit| {
32993            matches!(
32994                unit.op,
32995                Instruction::MapAdd { i }
32996                    if i.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 2
32997            )
32998        });
32999
33000        assert_eq!(
33001            for_iter_count, 1,
33002            "singleton sub-iter should not emit its own FOR_ITER, got instructions={:?}",
33003            f.instructions
33004        );
33005        assert!(
33006            has_map_add_depth_2,
33007            "assignment-idiom dictcomp should use MAP_ADD depth 2, got instructions={:?}",
33008            f.instructions
33009        );
33010        assert!(
33011            !f.instructions
33012                .iter()
33013                .any(|unit| matches!(unit.op, Instruction::BuildTuple { .. })),
33014            "singleton sub-iter should not materialize an iterator tuple, got instructions={:?}",
33015            f.instructions
33016        );
33017    }
33018
33019    #[test]
33020    fn constant_comprehension_iterable_with_unary_int_uses_tuple_const() {
33021        let code = compile_exec(
33022            "\
33023l = lambda : [2 < x for x in [-1, 3, 0]]
33024",
33025        );
33026        let lambda = find_code(&code, "<lambda>").expect("missing lambda code");
33027
33028        assert!(
33029            lambda.constants.iter().any(|constant| matches!(
33030                constant,
33031                ConstantData::Tuple { elements }
33032                    if matches!(
33033                        elements.as_slice(),
33034                        [
33035                            ConstantData::Integer { .. },
33036                            ConstantData::Integer { .. },
33037                            ConstantData::Integer { .. }
33038                        ]
33039                    )
33040            )),
33041            "expected folded tuple constant for comprehension iterable"
33042        );
33043    }
33044
33045    #[test]
33046    fn module_scope_listcomp_is_inlined() {
33047        let code = compile_exec("values = [i for i in range(3)]\n");
33048
33049        assert!(
33050            find_code(&code, "<listcomp>").is_none(),
33051            "module-scope list comprehension should be inlined"
33052        );
33053        assert!(
33054            code.instructions
33055                .iter()
33056                .any(|unit| matches!(unit.op, Instruction::LoadFastAndClear { .. })),
33057            "inlined module-scope list comprehension should use LOAD_FAST_AND_CLEAR, got instructions={:?}",
33058            code.instructions
33059        );
33060    }
33061
33062    #[test]
33063    fn module_scope_dictcomp_is_inlined() {
33064        let code = compile_exec("mapping = {i: i for i in range(3)}\n");
33065
33066        assert!(
33067            find_code(&code, "<dictcomp>").is_none(),
33068            "module-scope dict comprehension should be inlined"
33069        );
33070        assert!(
33071            code.instructions
33072                .iter()
33073                .any(|unit| matches!(unit.op, Instruction::LoadFastAndClear { .. })),
33074            "inlined module-scope dict comprehension should use LOAD_FAST_AND_CLEAR, got instructions={:?}",
33075            code.instructions
33076        );
33077    }
33078
33079    #[test]
33080    fn async_dictcomp_in_async_function_is_inlined() {
33081        let code = compile_exec(
33082            "\
33083async def f(items):
33084    return {item: item async for item in items}
33085",
33086        );
33087        let f = find_code(&code, "f").expect("missing f code");
33088        let ops: Vec<_> = f
33089            .instructions
33090            .iter()
33091            .map(|unit| unit.op)
33092            .filter(|op| !matches!(op, Instruction::Cache))
33093            .collect();
33094
33095        assert!(
33096            find_code(&code, "<dictcomp>").is_none(),
33097            "async dict comprehension should be inlined"
33098        );
33099        assert!(
33100            ops.iter().any(|op| matches!(op, Instruction::GetAiter)),
33101            "inlined async dict comprehension should keep GET_AITER in outer code, got ops={ops:?}"
33102        );
33103        assert!(
33104            ops.iter()
33105                .any(|op| matches!(op, Instruction::LoadFastAndClear { .. })),
33106            "inlined async dict comprehension should use LOAD_FAST_AND_CLEAR, got ops={ops:?}"
33107        );
33108        assert!(
33109            !ops.iter().any(|op| matches!(op, Instruction::MakeFunction)),
33110            "inlined async dict comprehension should not materialize MAKE_FUNCTION, got ops={ops:?}"
33111        );
33112    }
33113
33114    #[test]
33115    fn async_inlined_comprehension_inlines_restore_return_into_end_async_for() {
33116        let code = compile_exec(
33117            "\
33118async def f():
33119    return [i + 1 async for i in g([10, 20])]
33120",
33121        );
33122        let f = find_code(&code, "f").expect("missing f code");
33123        let ops: Vec<_> = f
33124            .instructions
33125            .iter()
33126            .map(|unit| unit.op)
33127            .filter(|op| !matches!(op, Instruction::Cache))
33128            .collect();
33129
33130        assert!(
33131            ops.windows(8).any(|window| {
33132                matches!(
33133                    window,
33134                    [
33135                        Instruction::EndAsyncFor,
33136                        Instruction::Swap { .. },
33137                        Instruction::StoreFast { .. },
33138                        Instruction::ReturnValue,
33139                        Instruction::Swap { .. },
33140                        Instruction::PopTop,
33141                        Instruction::Swap { .. },
33142                        Instruction::StoreFast { .. },
33143                    ]
33144                )
33145            }),
33146            "expected CPython-style restore+return inlined into END_ASYNC_FOR before cleanup, got ops={ops:?}"
33147        );
33148        assert!(
33149            !ops.windows(2).any(|window| {
33150                matches!(
33151                    window,
33152                    [
33153                        Instruction::EndAsyncFor,
33154                        Instruction::JumpForward { .. } | Instruction::JumpBackward { .. },
33155                    ]
33156                )
33157            }),
33158            "unexpected jump from END_ASYNC_FOR to the normal restore tail, got ops={ops:?}"
33159        );
33160    }
33161
33162    #[test]
33163    fn await_cleanup_throw_falls_through_until_cold_reorder() {
33164        let code = compile_exec(
33165            "\
33166async def f():
33167    await 1
33168",
33169        );
33170        let f = find_code(&code, "f").expect("missing f code");
33171        let ops: Vec<_> = f
33172            .instructions
33173            .iter()
33174            .map(|unit| unit.op)
33175            .filter(|op| !matches!(op, Instruction::Cache))
33176            .collect();
33177
33178        assert!(
33179            ops.windows(3).any(|window| {
33180                matches!(
33181                    window,
33182                    [
33183                        Instruction::CleanupThrow,
33184                        Instruction::JumpBackwardNoInterrupt { .. },
33185                        Instruction::CallIntrinsic1 { .. },
33186                    ]
33187                )
33188            }),
33189            "expected CPython-style cold CLEANUP_THROW jump before StopIteration handler, got ops={ops:?}"
33190        );
33191        assert!(
33192            !ops.windows(2).any(|window| {
33193                matches!(window, [Instruction::CleanupThrow, Instruction::EndSend])
33194            }),
33195            "CLEANUP_THROW should not inline the normal END_SEND return tail, got ops={ops:?}"
33196        );
33197    }
33198
33199    #[test]
33200    fn match_async_inlined_comprehension_success_jump_layout() {
33201        let code = compile_exec(
33202            "\
33203async def f(name_3, name_5):
33204    match b'':
33205        case True:
33206            pass
33207        case name_5 if f'e':
33208            {name_3: f async for name_2 in name_5}
33209        case []:
33210            pass
33211    [[]]
33212",
33213        );
33214        let f = find_code(&code, "f").expect("missing f code");
33215        let ops: Vec<_> = f
33216            .instructions
33217            .iter()
33218            .map(|unit| unit.op)
33219            .filter(|op| !matches!(op, Instruction::Cache))
33220            .collect();
33221
33222        assert!(
33223            ops.windows(4).any(|window| {
33224                matches!(
33225                    window,
33226                    [
33227                        Instruction::PopTop,
33228                        Instruction::JumpForward { .. },
33229                        Instruction::Copy { .. },
33230                        Instruction::StoreFast { .. },
33231                    ]
33232                )
33233            }),
33234            "expected CPython-style plain match success jump before async comprehension case, got ops={ops:?}"
33235        );
33236        assert!(
33237            ops.windows(3).any(|window| {
33238                matches!(
33239                    window,
33240                    [
33241                        Instruction::StoreFast { .. },
33242                        Instruction::JumpBackwardNoInterrupt { .. },
33243                        Instruction::CallIntrinsic1 { .. },
33244                    ]
33245                )
33246            }),
33247            "CPython codegen_pop_inlined_comprehension_locals() emits JUMP_NO_INTERRUPT before the cleanup path; after flowgraph reordering it remains a backward no-interrupt jump before the StopIteration handler, got ops={ops:?}"
33248        );
33249    }
33250
33251    #[test]
33252    fn for_loop_if_return_reorders_continue_backedge_before_exit_body() {
33253        let code = compile_exec(
33254            "\
33255def f(items, occurrence):
33256    for item in items:
33257        if item:
33258            occurrence -= 1
33259            if not occurrence:
33260                return item
33261    return None
33262",
33263        );
33264        let f = find_code(&code, "f").expect("missing f code");
33265        let ops: Vec<_> = f
33266            .instructions
33267            .iter()
33268            .map(|unit| unit.op)
33269            .filter(|op| !matches!(op, Instruction::Cache))
33270            .collect();
33271
33272        assert!(
33273            ops.windows(3).any(|window| {
33274                matches!(
33275                    window,
33276                    [
33277                        Instruction::PopJumpIfFalse { .. },
33278                        Instruction::NotTaken,
33279                        Instruction::JumpBackward { .. },
33280                    ]
33281                )
33282            }),
33283            "expected CPython-style inverted return guard followed by loop backedge, got ops={ops:?}"
33284        );
33285        assert!(
33286            !ops.windows(3).any(|window| {
33287                matches!(
33288                    window,
33289                    [
33290                        Instruction::PopJumpIfTrue { .. },
33291                        Instruction::NotTaken,
33292                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
33293                    ]
33294                )
33295            }),
33296            "return guard should not fall through into the return body before the loop backedge, got ops={ops:?}"
33297        );
33298    }
33299
33300    #[test]
33301    fn sync_with_after_async_for_keeps_end_async_for_line_marker() {
33302        let code = compile_exec(
33303            "\
33304async def f(cm, source, tgt):
33305    with cm:
33306        async for tgt[0] in source():
33307            pass
33308",
33309        );
33310        let f = find_code(&code, "f").expect("missing f code");
33311        let ops: Vec<_> = f
33312            .instructions
33313            .iter()
33314            .map(|unit| unit.op)
33315            .filter(|op| !matches!(op, Instruction::Cache))
33316            .collect();
33317
33318        assert!(
33319            ops.windows(6).any(|window| {
33320                matches!(
33321                    window,
33322                    [
33323                        Instruction::EndAsyncFor,
33324                        Instruction::Nop,
33325                        Instruction::LoadConst { .. },
33326                        Instruction::LoadConst { .. },
33327                        Instruction::LoadConst { .. },
33328                        Instruction::Call { .. },
33329                    ]
33330                )
33331            }),
33332            "expected CPython-style line-marker NOP between END_ASYNC_FOR and with cleanup, got ops={ops:?}"
33333        );
33334    }
33335
33336    #[test]
33337    fn genexpr_with_async_comprehension_element_is_async_generator() {
33338        let code = compile_exec(
33339            "\
33340async def f():
33341    gen = ([i async for i in asynciter([1, 2])] for j in [10, 20])
33342    return [x async for x in gen]
33343",
33344        );
33345        let genexpr = find_code(&code, "<genexpr>").expect("missing genexpr code");
33346        let units: Vec<_> = genexpr
33347            .instructions
33348            .iter()
33349            .filter(|unit| !matches!(unit.op, Instruction::Cache))
33350            .collect();
33351
33352        assert!(
33353            units.windows(2).any(|window| {
33354                let [wrap, yield_value] = window else {
33355                    return false;
33356                };
33357                matches!(yield_value.op, Instruction::YieldValue { .. })
33358                    && match wrap.op {
33359                        Instruction::CallIntrinsic1 { func } => {
33360                            func.get(OpArg::new(u32::from(u8::from(wrap.arg))))
33361                                == bytecode::IntrinsicFunction1::AsyncGenWrap
33362                        }
33363                        _ => false,
33364                    }
33365            }),
33366            "expected CPython-style ASYNC_GEN_WRAP before genexpr yield, got units={units:?}"
33367        );
33368    }
33369
33370    #[test]
33371    fn async_comprehension_propagates_coroutine_to_enclosing_genexpr_like_cpython() {
33372        let symbol_table = scan_program_symbol_table(
33373            "\
33374async def f():
33375    gen = ([i async for i in asynciter([1, 2])] for j in [10, 20])
33376    return [x async for x in gen]
33377",
33378        );
33379        let genexpr =
33380            find_symbol_table(&symbol_table, "genexpr").expect("missing genexpr symbol table");
33381        assert!(genexpr.is_generator, "expected genexpr symbol table");
33382        assert!(
33383            genexpr.is_coroutine,
33384            "CPython symtable_handle_comprehension() propagates non-generator async comprehension ste_coroutine to the enclosing genexpr"
33385        );
33386    }
33387
33388    #[test]
33389    fn nested_module_scope_dictcomp_symbols_are_local() {
33390        let symbol_table = scan_program_symbol_table(
33391            "\
33392deoptmap = {
33393    specialized: base
33394    for base, family in _specializations.items()
33395    for specialized in family
33396}
33397",
33398        );
33399
33400        for name in ["base", "family", "specialized"] {
33401            let symbol = symbol_table
33402                .lookup(&name.into())
33403                .unwrap_or_else(|| panic!("missing module symbol {name}"));
33404            assert_eq!(
33405                symbol.scope,
33406                SymbolScope::Local,
33407                "expected module-scope inlined comprehension symbol {name} to be Local, got {symbol:?}"
33408            );
33409        }
33410
33411        let comp = symbol_table
33412            .inlined_comprehension_blocks
33413            .first()
33414            .expect("missing comprehension symbol table");
33415        assert!(comp.comp_inlined, "expected comprehension to be inlined");
33416        for name in ["base", "family", "specialized"] {
33417            let symbol = comp
33418                .lookup(&name.into())
33419                .unwrap_or_else(|| panic!("missing comprehension symbol {name}"));
33420            assert_eq!(
33421                symbol.scope,
33422                SymbolScope::Local,
33423                "expected comprehension symbol {name} to be Local, got {symbol:?}"
33424            );
33425        }
33426    }
33427
33428    #[test]
33429    fn nested_module_scope_dictcomp_uses_fast_locals() {
33430        let code = compile_exec(
33431            "\
33432deoptmap = {
33433    specialized: base
33434    for base, family in _specializations.items()
33435    for specialized in family
33436}
33437",
33438        );
33439        let ops: Vec<_> = code
33440            .instructions
33441            .iter()
33442            .map(|unit| unit.op)
33443            .filter(|op| !matches!(op, Instruction::Cache))
33444            .collect();
33445
33446        assert!(
33447            ops.iter()
33448                .any(|op| matches!(op, Instruction::StoreFastStoreFast { .. })),
33449            "expected outer target unpack to use STORE_FAST_STORE_FAST, got ops={ops:?}"
33450        );
33451        assert!(
33452            ops.iter().any(|op| matches!(
33453                op,
33454                Instruction::StoreFastLoadFast { .. }
33455                    | Instruction::LoadFastBorrowLoadFastBorrow { .. }
33456            )),
33457            "expected inner target/store-use path to use fast locals, got ops={ops:?}"
33458        );
33459        assert!(
33460            ops.iter()
33461                .filter(|op| matches!(op, Instruction::LoadName { .. }))
33462                .count()
33463                <= 1,
33464            "unexpected extra LOAD_NAME ops in nested inlined comprehension, got ops={ops:?}"
33465        );
33466        assert!(
33467            ops.iter()
33468                .filter(|op| matches!(op, Instruction::StoreName { .. }))
33469                .count()
33470                <= 1,
33471            "unexpected extra STORE_NAME ops in nested inlined comprehension, got ops={ops:?}"
33472        );
33473    }
33474
33475    #[test]
33476    fn module_scope_inlined_comprehension_keeps_outer_iter_as_name_lookup() {
33477        let code = compile_exec(
33478            "\
33479path_separators = ['/']
33480_pathseps_with_colon = {f':{s}' for s in path_separators}
33481",
33482        );
33483        let ops: Vec<_> = code
33484            .instructions
33485            .iter()
33486            .map(|unit| unit.op)
33487            .filter(|op| !matches!(op, Instruction::Cache))
33488            .collect();
33489
33490        let load_name_path = ops
33491            .windows(2)
33492            .any(|window| matches!(window, [Instruction::LoadName { .. }, Instruction::GetIter]));
33493        assert!(
33494            load_name_path,
33495            "expected outer iterable to stay a NAME lookup before GET_ITER, got ops={ops:?}"
33496        );
33497        assert!(
33498            !ops.windows(2).any(|window| matches!(
33499                window,
33500                [
33501                    Instruction::LoadFast { .. } | Instruction::LoadFastCheck { .. },
33502                    Instruction::GetIter
33503                ]
33504            )),
33505            "module local outer iterable should not become a fast local, got ops={ops:?}"
33506        );
33507        assert!(
33508            ops.iter().any(|op| matches!(
33509                op,
33510                Instruction::StoreFastLoadFast { .. } | Instruction::StoreFast { .. }
33511            )),
33512            "comprehension target should still use fast locals, got ops={ops:?}"
33513        );
33514    }
33515
33516    #[test]
33517    fn function_scope_inlined_comprehension_restore_keeps_swap_before_duplicate_store() {
33518        let code = compile_exec(
33519            "\
33520def f():
33521    a = [1 for a in [0]]
33522    return 1
33523",
33524        );
33525        let f = find_code(&code, "f").expect("missing f code");
33526        let ops: Vec<_> = f
33527            .instructions
33528            .iter()
33529            .map(|unit| unit.op)
33530            .filter(|op| !matches!(op, Instruction::Cache))
33531            .collect();
33532
33533        assert!(
33534            ops.windows(4).any(|window| matches!(
33535                window,
33536                [
33537                    Instruction::PopIter,
33538                    Instruction::Swap { .. },
33539                    Instruction::StoreFast { .. },
33540                    Instruction::StoreFast { .. }
33541                ]
33542            )),
33543            "expected PopIter/SWAP 2/STORE_FAST/STORE_FAST restore tail, got ops={ops:?}"
33544        );
33545    }
33546
33547    #[test]
33548    fn inlined_comprehension_namedexpr_target_stays_parent_fast_local() {
33549        let code = compile_exec(
33550            "\
33551def f(seq, emit):
33552    return [(x, y) for x in seq if (y := emit(x))]
33553",
33554        );
33555        let f = find_code(&code, "f").expect("missing f code");
33556
33557        assert!(f.varnames.iter().any(|name| name == "y"));
33558        assert!(!f.cellvars.iter().any(|name| name == "y"));
33559        assert!(!f.freevars.iter().any(|name| name == "y"));
33560        assert!(
33561            !f.names.iter().any(|name| name == "y"),
33562            "inlined comprehension namedexpr target should not use NAME ops, got names={:?}",
33563            f.names
33564        );
33565    }
33566
33567    #[test]
33568    fn inlined_comprehension_namedexpr_varnames_match_cpython_order() {
33569        let code = compile_exec(
33570            "\
33571def f():
33572    def spam(a):
33573        return a
33574    input_data = [1, 2, 3]
33575    res = [(x, y, x / y) for x in input_data if (y := spam(x)) > 0]
33576    return res
33577",
33578        );
33579        let f = find_code(&code, "f").expect("missing f code");
33580        assert_eq!(
33581            f.varnames.iter().map(String::as_str).collect::<Vec<_>>(),
33582            vec!["spam", "input_data", "x", "y", "res"]
33583        );
33584    }
33585
33586    #[test]
33587    fn global_namedexpr_in_inlined_comprehension_saves_fast_slot() {
33588        let code = compile_exec(
33589            "\
33590def f(seq, value):
33591    global G
33592    [G := value for _ in seq]
33593",
33594        );
33595        let f = find_code(&code, "f").expect("missing f code");
33596
33597        assert!(f.varnames.iter().any(|name| name == "G"));
33598        assert!(f.instructions.iter().any(|unit| match unit.op {
33599            Instruction::LoadFastAndClear { var_num } => {
33600                let idx = var_num.get(OpArg::new(u32::from(u8::from(unit.arg))));
33601                f.varnames[usize::from(idx)] == "G"
33602            }
33603            _ => false,
33604        }));
33605        assert!(f.instructions.iter().any(|unit| match unit.op {
33606            Instruction::StoreGlobal { namei } => {
33607                let idx = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
33608                f.names[usize::try_from(idx).unwrap()] == "G"
33609            }
33610            _ => false,
33611        }));
33612    }
33613
33614    #[test]
33615    fn namedexpr_copy_uses_namedexpr_location_like_cpython() {
33616        let code = compile_exec(
33617            "\
33618def outer():
33619    a = 10
33620    def spam():
33621        nonlocal a
33622        (a := 20)
33623",
33624        );
33625        let spam = find_code(&code, "spam").expect("missing spam code");
33626
33627        // CPython 3.14 NamedExpr_kind emits COPY at LOC(named expression),
33628        // between visiting the value and visiting the target.
33629        assert_eq!(
33630            spam.linetable.as_ref(),
33631            &[0xf8, 0x80, 0x00, 0xe0, 0x0e, 0x10, 0x88, 0x17, 0x8b, 0x11,]
33632        );
33633    }
33634
33635    #[test]
33636    fn genexpr_namedexpr_target_is_cell_not_fast_local() {
33637        let code = compile_exec(
33638            "\
33639def f(seq):
33640    a = 1
33641    return (c := x + a for x in seq)
33642",
33643        );
33644        let f = find_code(&code, "f").expect("missing f code");
33645
33646        assert!(!f.varnames.iter().any(|name| name == "c"));
33647        assert_eq!(
33648            f.cellvars.iter().map(String::as_str).collect::<Vec<_>>(),
33649            ["a", "c"]
33650        );
33651    }
33652
33653    #[test]
33654    fn public_cellvars_follow_cpython_localsplus_order() {
33655        let code = compile_exec(
33656            "\
33657def f():
33658    x = 10
33659    t = False
33660    g = ((i, j) for i in range(x) if t for j in range(x))
33661    [x for x in range(3)]
33662    return g
33663",
33664        );
33665        let f = find_code(&code, "f").expect("missing f code");
33666
33667        assert_eq!(
33668            f.varnames.iter().map(String::as_str).collect::<Vec<_>>(),
33669            ["g", "x"]
33670        );
33671        assert_eq!(
33672            f.cellvars.iter().map(String::as_str).collect::<Vec<_>>(),
33673            ["x", "t"],
33674            "CPython assemble.c exposes co_cellvars in localsplus order: merged local cells before non-local cells"
33675        );
33676    }
33677
33678    #[test]
33679    fn inlined_comprehension_restore_does_not_form_store_fast_load_fast() {
33680        let code = compile_exec(
33681            "\
33682def f(e):
33683    e[1:3] = [g(i) for i in range(2)]
33684
33685def g(datadir):
33686    files = [filename[:-4] for filename in sorted(os.listdir(datadir)) if filename.endswith('.xml')]
33687    input_files = [filename for filename in files if filename.startswith('in')]
33688    return files, input_files
33689",
33690        );
33691        let f = find_code(&code, "f").expect("missing f code");
33692        let ops: Vec<_> = f
33693            .instructions
33694            .iter()
33695            .map(|unit| unit.op)
33696            .filter(|op| !matches!(op, Instruction::Cache))
33697            .collect();
33698
33699        assert!(
33700            ops.windows(7).any(|window| {
33701                matches!(
33702                    window,
33703                    [
33704                        Instruction::EndFor,
33705                        Instruction::PopIter,
33706                        Instruction::Swap { .. },
33707                        Instruction::StoreFast { .. },
33708                        Instruction::LoadFastBorrow { .. },
33709                        Instruction::LoadConst { .. },
33710                        Instruction::StoreSubscr,
33711                    ]
33712                )
33713            }),
33714            "expected CPython-style inlined comprehension restore before slice store, got ops={ops:?}"
33715        );
33716        assert!(
33717            !ops.windows(3).any(|window| {
33718                matches!(
33719                    window,
33720                    [
33721                        Instruction::StoreFastLoadFast { .. },
33722                        Instruction::LoadConst { .. },
33723                        Instruction::StoreSubscr,
33724                    ]
33725                )
33726            }),
33727            "inlined comprehension restore should not be folded into STORE_FAST_LOAD_FAST, got ops={ops:?}"
33728        );
33729
33730        let g = find_code(&code, "g").expect("missing g code");
33731        let g_ops: Vec<_> = g
33732            .instructions
33733            .iter()
33734            .map(|unit| unit.op)
33735            .filter(|op| !matches!(op, Instruction::Cache))
33736            .collect();
33737        assert!(
33738            g_ops.windows(4).any(|window| {
33739                matches!(
33740                    window,
33741                    [
33742                        Instruction::EndFor,
33743                        Instruction::PopIter,
33744                        Instruction::StoreFast { .. },
33745                        Instruction::StoreFast { .. },
33746                    ]
33747                )
33748            }),
33749            "expected CPython-style static swap over STORE_FAST_MAYBE_NULL restore, got ops={g_ops:?}"
33750        );
33751        assert!(
33752            !g_ops.windows(5).any(|window| {
33753                matches!(
33754                    window,
33755                    [
33756                        Instruction::EndFor,
33757                        Instruction::PopIter,
33758                        Instruction::Swap { .. },
33759                        Instruction::StoreFast { .. },
33760                        Instruction::StoreFast { .. },
33761                    ]
33762                )
33763            }),
33764            "inlined comprehension restore should statically remove SWAP before adjacent stores, got ops={g_ops:?}"
33765        );
33766    }
33767
33768    #[test]
33769    fn single_mode_folded_multiline_constant_does_not_leave_nops() {
33770        let code = compile_single(
33771            "\
33772(-
33773 -
33774 -
33775 1)
33776",
33777        );
33778
33779        assert!(
33780            !code
33781                .instructions
33782                .iter()
33783                .any(|unit| matches!(unit.op, Instruction::Nop)),
33784            "expected folded single-mode multiline constant to drop NOP anchors, got instructions={:?}",
33785            code.instructions
33786        );
33787    }
33788
33789    #[test]
33790    fn folded_multiline_tuple_constant_does_not_leave_operand_nops() {
33791        let code = compile_exec(
33792            "\
33793values = (
33794    (1 + 1j, 0 + 0j),
33795    (1 + 1j, 0.0),
33796    (1 + 1j, 0),
33797)
33798",
33799        );
33800
33801        assert!(
33802            !code
33803                .instructions
33804                .iter()
33805                .any(|unit| matches!(unit.op, Instruction::Nop)),
33806            "expected CPython nop_out-style folded tuple operands to have no surviving NOPs, got instructions={:?}",
33807            code.instructions
33808        );
33809    }
33810
33811    #[test]
33812    fn single_mode_returns_none_after_print_like_cpython() {
33813        let code = compile_single("1\n");
33814        let ops = code
33815            .instructions
33816            .iter()
33817            .filter(|unit| !matches!(unit.op, Instruction::Resume { .. }))
33818            .collect::<Vec<_>>();
33819
33820        assert!(
33821            !ops.iter()
33822                .any(|unit| matches!(unit.op, Instruction::Copy { .. })),
33823            "CPython codegen_stmt_expr() prints and pops interactive expressions; it does not preserve the final expression as the code object's return value, got ops={ops:?}"
33824        );
33825        let Some(load_none) = ops.iter().rev().nth(1) else {
33826            panic!("missing final LOAD_CONST None before RETURN_VALUE, got ops={ops:?}");
33827        };
33828        let Instruction::LoadConst { consti } = load_none.op else {
33829            panic!("missing final LOAD_CONST None before RETURN_VALUE, got ops={ops:?}");
33830        };
33831        let constant = &code.constants[consti.get(OpArg::new(u32::from(u8::from(load_none.arg))))];
33832        assert!(matches!(constant, ConstantData::None));
33833        assert!(matches!(
33834            ops.last().map(|unit| unit.op),
33835            Some(Instruction::ReturnValue)
33836        ));
33837    }
33838
33839    #[test]
33840    fn folded_multiline_bytes_binop_does_not_leave_operand_nops() {
33841        let code = compile_exec(
33842            "\
33843def f(self, out):
33844    self.assertIn(
33845        b'gnu' + (b'/123' * 125) + b'/longlink' + (b'/123' * 125) + b'/longname',
33846        out)
33847",
33848        );
33849        let f = find_code(&code, "f").expect("missing f code");
33850
33851        assert!(
33852            !f.instructions
33853                .iter()
33854                .any(|unit| matches!(unit.op, Instruction::Nop)),
33855            "expected CPython nop_out-style folded operands to have no surviving NOPs, got instructions={:?}",
33856            f.instructions
33857        );
33858    }
33859
33860    #[test]
33861    fn folded_binop_at_branch_body_start_does_not_leave_nop() {
33862        let code = compile_exec(
33863            "\
33864def f(sys):
33865    if sys.platform == 'win32':
33866        component = 'd' * 25
33867    return component
33868",
33869        );
33870        let f = find_code(&code, "f").expect("missing f code");
33871        let ops: Vec<_> = f
33872            .instructions
33873            .iter()
33874            .map(|unit| unit.op)
33875            .filter(|op| !matches!(op, Instruction::Cache))
33876            .collect();
33877
33878        assert!(
33879            !ops.windows(3).any(|window| {
33880                matches!(
33881                    window,
33882                    [
33883                        Instruction::NotTaken,
33884                        Instruction::Nop,
33885                        Instruction::LoadConst { .. }
33886                    ]
33887                )
33888            }),
33889            "expected CPython nop_out-style folded branch body to drop operand NOP, got ops={ops:?}",
33890        );
33891    }
33892
33893    #[test]
33894    fn folded_iterable_at_assert_target_does_not_leave_nop() {
33895        let code = compile_exec(
33896            r#"
33897def f(caches, non_caches):
33898    assert 1 / 3 <= caches / non_caches, "this test needs more caches!"
33899    for show_caches in (False, True):
33900        pass
33901"#,
33902        );
33903        let f = find_code(&code, "f").expect("missing f code");
33904        let ops: Vec<_> = f
33905            .instructions
33906            .iter()
33907            .map(|unit| unit.op)
33908            .filter(|op| !matches!(op, Instruction::Cache))
33909            .collect();
33910
33911        assert!(
33912            !ops.windows(2).any(|window| {
33913                matches!(window, [Instruction::Nop, Instruction::LoadConst { .. }])
33914            }),
33915            "expected folded for-iterable at assert target to drop operand NOP, got ops={ops:?}",
33916        );
33917    }
33918
33919    #[test]
33920    fn multiline_unpack_target_uses_element_locations() {
33921        let code = compile_exec(
33922            "\
33923def f(cm):
33924    with cm as (_,
33925                filename_2):
33926        return filename_2
33927",
33928        );
33929        let f = find_code(&code, "f").expect("missing f code");
33930        let ops: Vec<_> = f
33931            .instructions
33932            .iter()
33933            .map(|unit| unit.op)
33934            .filter(|op| !matches!(op, Instruction::Cache))
33935            .collect();
33936
33937        assert!(
33938            !ops.iter()
33939                .any(|op| matches!(op, Instruction::StoreFastStoreFast { .. })),
33940            "expected multiline target elements to keep separate STORE_FAST instructions, got ops={ops:?}",
33941        );
33942    }
33943
33944    #[test]
33945    fn or_condition_in_jump_context_uses_shared_true_fallthrough() {
33946        let code = compile_exec(
33947            "\
33948def f(lines):
33949    for line in lines:
33950        if line.startswith('--') or not line.strip():
33951            continue
33952        return line
33953",
33954        );
33955        let f = find_code(&code, "f").expect("missing f code");
33956        let ops: Vec<_> = f
33957            .instructions
33958            .iter()
33959            .map(|unit| unit.op)
33960            .filter(|op| !matches!(op, Instruction::Cache))
33961            .collect();
33962
33963        let first_pop_jump = ops
33964            .iter()
33965            .find(|op| {
33966                matches!(
33967                    op,
33968                    Instruction::PopJumpIfTrue { .. } | Instruction::PopJumpIfFalse { .. }
33969                )
33970            })
33971            .copied()
33972            .expect("missing conditional jump");
33973        assert!(
33974            matches!(first_pop_jump, Instruction::PopJumpIfTrue { .. }),
33975            "expected first OR branch to jump on true into shared fallthrough, got ops={ops:?}"
33976        );
33977    }
33978
33979    #[test]
33980    fn loop_break_bool_chain_reorders_false_path_to_jump_back() {
33981        let code = compile_exec(
33982            "\
33983def f(filters, text, category, module, lineno, defaultaction):
33984    for item in filters:
33985        action, msg, cat, mod, ln = item
33986        if ((msg is None or msg.match(text)) and
33987            issubclass(category, cat) and
33988            (mod is None or mod.match(module)) and
33989            (ln == 0 or lineno == ln)):
33990            break
33991    else:
33992        action = defaultaction
33993    return action
33994",
33995        );
33996        let f = find_code(&code, "f").expect("missing f code");
33997        let ops: Vec<_> = f
33998            .instructions
33999            .iter()
34000            .map(|unit| unit.op)
34001            .filter(|op| !matches!(op, Instruction::Cache))
34002            .collect();
34003
34004        assert!(
34005            ops.windows(5).any(|window| {
34006                matches!(
34007                    window,
34008                    [
34009                        Instruction::ToBool,
34010                        Instruction::PopJumpIfTrue { .. },
34011                        Instruction::NotTaken,
34012                        Instruction::JumpBackward { .. }
34013                            | Instruction::JumpBackwardNoInterrupt { .. },
34014                        Instruction::LoadGlobal { .. },
34015                    ]
34016                )
34017            }),
34018            "expected CPython-style false path to fall through into loop jump-back, got ops={ops:?}"
34019        );
34020    }
34021
34022    #[test]
34023    fn loop_conditional_body_keeps_duplicate_jump_back_paths() {
34024        let code = compile_exec(
34025            "\
34026def f(new, old):
34027    for replace in ['__module__', '__name__', '__qualname__', '__doc__']:
34028        if hasattr(old, replace):
34029            setattr(new, replace, getattr(old, replace))
34030    return new
34031",
34032        );
34033        let f = find_code(&code, "f").expect("missing f code");
34034        let ops: Vec<_> = f
34035            .instructions
34036            .iter()
34037            .map(|unit| unit.op)
34038            .filter(|op| !matches!(op, Instruction::Cache))
34039            .collect();
34040
34041        let jump_back_count = ops
34042            .iter()
34043            .filter(|op| {
34044                matches!(
34045                    op,
34046                    Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. }
34047                )
34048            })
34049            .count();
34050        assert!(
34051            jump_back_count >= 2,
34052            "expected separate false-path and body jump-back blocks, got ops={ops:?}"
34053        );
34054        assert!(
34055            ops.windows(5).any(|window| {
34056                matches!(
34057                    window,
34058                    [
34059                        Instruction::ToBool,
34060                        Instruction::PopJumpIfTrue { .. },
34061                        Instruction::NotTaken,
34062                        Instruction::JumpBackward { .. }
34063                            | Instruction::JumpBackwardNoInterrupt { .. },
34064                        Instruction::LoadGlobal { .. },
34065                    ]
34066                )
34067            }),
34068            "expected false path to jump back before body, got ops={ops:?}"
34069        );
34070    }
34071
34072    #[test]
34073    fn try_loop_inner_if_keeps_duplicate_jump_back_paths() {
34074        let code = compile_exec(
34075            "\
34076def f(config, logging):
34077    handlers = config.get('handlers', {})
34078    for name in handlers:
34079        if name not in logging._handlers:
34080            raise ValueError('missing')
34081        else:
34082            try:
34083                handler = logging._handlers[name]
34084                handler_config = handlers[name]
34085                level = handler_config.get('level', None)
34086                if level:
34087                    handler.setLevel(logging._checkLevel(level))
34088            except Exception as e:
34089                raise ValueError('bad') from e
34090    loggers = config.get('loggers', {})
34091    for name in loggers:
34092        pass
34093",
34094        );
34095        let f = find_code(&code, "f").expect("missing f code");
34096        let ops: Vec<_> = f
34097            .instructions
34098            .iter()
34099            .map(|unit| unit.op)
34100            .filter(|op| !matches!(op, Instruction::Cache))
34101            .collect();
34102
34103        assert!(
34104            ops.windows(4).any(|window| {
34105                matches!(
34106                    window,
34107                    [
34108                        Instruction::PopTop,
34109                        Instruction::JumpBackward { .. }
34110                            | Instruction::JumpBackwardNoInterrupt { .. },
34111                        Instruction::JumpBackward { .. }
34112                            | Instruction::JumpBackwardNoInterrupt { .. },
34113                        Instruction::EndFor,
34114                    ]
34115                )
34116            }),
34117            "expected CPython-style separate body and false-path jump-back blocks, got ops={ops:?}"
34118        );
34119    }
34120
34121    #[test]
34122    fn try_loop_nested_bool_tail_keeps_duplicate_jump_back_paths() {
34123        let code = compile_exec(
34124            "\
34125def f(obj, flags, writer, value, Error):
34126    while obj.running:
34127        try:
34128            if value == 0:
34129                return
34130            elif obj.ready and obj.active and value == 1:
34131                obj.work()
34132                if flags.verbose and obj.chatty:
34133                    writer.write('trace')
34134            elif value == 2:
34135                obj.other()
34136            else:
34137                obj.default()
34138        except Error as e:
34139            obj.running = False
34140",
34141        );
34142        let f = find_code(&code, "f").expect("missing f code");
34143        let ops: Vec<_> = f
34144            .instructions
34145            .iter()
34146            .map(|unit| unit.op)
34147            .filter(|op| !matches!(op, Instruction::Cache))
34148            .collect();
34149
34150        assert!(
34151            ops.windows(6).any(|window| {
34152                matches!(
34153                    window,
34154                    [
34155                        Instruction::PopTop,
34156                        Instruction::JumpBackward { .. }
34157                            | Instruction::JumpBackwardNoInterrupt { .. },
34158                        Instruction::JumpBackward { .. }
34159                            | Instruction::JumpBackwardNoInterrupt { .. },
34160                        Instruction::JumpBackward { .. }
34161                            | Instruction::JumpBackwardNoInterrupt { .. },
34162                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34163                        Instruction::LoadConst { .. } | Instruction::LoadSmallInt { .. },
34164                    ]
34165                )
34166            }),
34167            "expected CPython-style body and both bool false-path jump-back blocks, got ops={ops:?}"
34168        );
34169    }
34170
34171    #[test]
34172    fn nested_continue_shares_backedge_with_fallthrough_body() {
34173        let code = compile_exec(
34174            "\
34175def f(names, show_empty, keywords, args_buffer, args, cls, object, level):
34176    for name in names:
34177        value = getattr(cls, name)
34178        if not show_empty:
34179            if value == []:
34180                field_type = cls._field_types.get(name, object)
34181                if getattr(field_type, '__origin__', ...) is list:
34182                    if not keywords:
34183                        args_buffer.append(repr(value))
34184                    continue
34185            if not keywords:
34186                args.extend(args_buffer)
34187                args_buffer = []
34188        value, simple = _format(value, level)
34189        if keywords:
34190            args.append('%s=%s' % (name, value))
34191        else:
34192            args.append(value)
34193",
34194        );
34195        let f = find_code(&code, "f").expect("missing f code");
34196        let ops: Vec<_> = f
34197            .instructions
34198            .iter()
34199            .map(|unit| unit.op)
34200            .filter(|op| !matches!(op, Instruction::Cache | Instruction::NotTaken))
34201            .collect();
34202
34203        assert!(
34204            ops.windows(4).any(|window| {
34205                matches!(
34206                    window,
34207                    [
34208                        Instruction::PopTop,
34209                        Instruction::JumpBackward { .. }
34210                            | Instruction::JumpBackwardNoInterrupt { .. },
34211                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34212                        Instruction::ToBool,
34213                    ]
34214                )
34215            }),
34216            "expected CPython-style shared continue backedge before outer condition, got ops={ops:?}"
34217        );
34218        assert!(
34219            !ops.windows(2).any(|window| {
34220                matches!(
34221                    window,
34222                    [
34223                        Instruction::JumpBackward { .. }
34224                            | Instruction::JumpBackwardNoInterrupt { .. },
34225                        Instruction::JumpBackward { .. }
34226                            | Instruction::JumpBackwardNoInterrupt { .. },
34227                    ]
34228                )
34229            }),
34230            "unexpected duplicated continue/backedge jumps, got ops={ops:?}"
34231        );
34232    }
34233
34234    #[test]
34235    fn line_bearing_loop_if_false_backedge_keeps_body_before_jump_back() {
34236        let code = compile_exec(
34237            "\
34238def f(self, replacement_pairs):
34239    for n, d in [(19, '%OC'), (2, '%Ow')]:
34240        if self.LC_alt_digits is None:
34241            s = str(n)
34242            replacement_pairs.append((s, d))
34243            if n < 10:
34244                replacement_pairs.append((s[1], d))
34245        elif len(self.LC_alt_digits) > n:
34246            replacement_pairs.append((self.LC_alt_digits[n], d))
34247        else:
34248            replacement_pairs.append((d, d))
34249",
34250        );
34251        let f = find_code(&code, "f").expect("missing f code");
34252        let ops: Vec<_> = f
34253            .instructions
34254            .iter()
34255            .map(|unit| unit.op)
34256            .filter(|op| !matches!(op, Instruction::Cache))
34257            .collect();
34258
34259        assert!(
34260            ops.windows(5).any(|window| {
34261                matches!(
34262                    window,
34263                    [
34264                        Instruction::CompareOp { .. },
34265                        Instruction::PopJumpIfFalse { .. },
34266                        Instruction::NotTaken,
34267                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34268                        Instruction::LoadAttr { .. },
34269                    ]
34270                )
34271            }),
34272            "expected CPython-style line-bearing false target to keep body before backedge, got ops={ops:?}"
34273        );
34274        assert!(
34275            !ops.windows(6).any(|window| {
34276                matches!(
34277                    window,
34278                    [
34279                        Instruction::CompareOp { .. },
34280                        Instruction::PopJumpIfTrue { .. },
34281                        Instruction::NotTaken,
34282                        Instruction::JumpBackward { .. }
34283                            | Instruction::JumpBackwardNoInterrupt { .. },
34284                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34285                        Instruction::LoadAttr { .. },
34286                    ]
34287                )
34288            }),
34289            "unexpected no-lineno-style inverted loop-if body, got ops={ops:?}"
34290        );
34291    }
34292
34293    #[test]
34294    fn branch_local_implicit_continue_keeps_body_before_jump_back() {
34295        let code = compile_exec(
34296            "\
34297def f(items, outer, cond, sub, out):
34298    for x in items:
34299        if outer:
34300            if cond:
34301                out.append(x)
34302                if sub:
34303                    out.append(1)
34304                out.append(2)
34305        else:
34306            out.append(3)
34307    return out
34308",
34309        );
34310        let f = find_code(&code, "f").expect("missing f code");
34311        let ops: Vec<_> = f
34312            .instructions
34313            .iter()
34314            .map(|unit| unit.op)
34315            .filter(|op| !matches!(op, Instruction::Cache))
34316            .collect();
34317
34318        assert!(
34319            ops.windows(5).any(|window| {
34320                matches!(
34321                    window,
34322                    [
34323                        Instruction::ToBool,
34324                        Instruction::PopJumpIfFalse { .. },
34325                        Instruction::NotTaken,
34326                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34327                        Instruction::LoadAttr { .. },
34328                    ]
34329                )
34330            }),
34331            "expected branch-local implicit continue target to stay after the body, got ops={ops:?}"
34332        );
34333        assert!(
34334            !ops.windows(6).any(|window| {
34335                matches!(
34336                    window,
34337                    [
34338                        Instruction::ToBool,
34339                        Instruction::PopJumpIfTrue { .. },
34340                        Instruction::NotTaken,
34341                        Instruction::JumpBackward { .. }
34342                            | Instruction::JumpBackwardNoInterrupt { .. },
34343                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34344                        Instruction::LoadAttr { .. },
34345                    ]
34346                )
34347            }),
34348            "unexpected direct-loop-body implicit continue layout for branch-local target, got ops={ops:?}"
34349        );
34350    }
34351
34352    #[test]
34353    fn boolop_continue_deduplicates_marker_jump_back() {
34354        let code = compile_exec(
34355            "\
34356def f(ws, seen, more_than):
34357    while ws:
34358        w = ws.pop()
34359        if w in seen or w <= more_than:
34360            continue
34361        seen.add(w)
34362    return seen
34363",
34364        );
34365        let f = find_code(&code, "f").expect("missing f code");
34366        let ops: Vec<_> = f
34367            .instructions
34368            .iter()
34369            .map(|unit| unit.op)
34370            .filter(|op| !matches!(op, Instruction::Cache))
34371            .collect();
34372
34373        assert!(
34374            !ops.windows(2).any(|window| {
34375                matches!(
34376                    window,
34377                    [
34378                        Instruction::JumpBackward { .. }
34379                            | Instruction::JumpBackwardNoInterrupt { .. },
34380                        Instruction::JumpBackward { .. }
34381                            | Instruction::JumpBackwardNoInterrupt { .. },
34382                    ]
34383                )
34384            }),
34385            "expected adjacent equivalent continue backedges to be deduplicated, got ops={ops:?}"
34386        );
34387        assert!(
34388            ops.windows(4).any(|window| {
34389                matches!(
34390                    window,
34391                    [
34392                        Instruction::PopJumpIfFalse { .. },
34393                        Instruction::NotTaken,
34394                        Instruction::JumpBackward { .. }
34395                            | Instruction::JumpBackwardNoInterrupt { .. },
34396                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34397                    ]
34398                )
34399            }),
34400            "expected CPython-style boolop continue fallthrough before body, got ops={ops:?}"
34401        );
34402    }
34403
34404    #[test]
34405    fn loop_elif_nested_if_false_backedge_keeps_body_before_jump_back() {
34406        let code = compile_exec(
34407            "\
34408def f(keys, parse_int, d, ampm, AM, PM):
34409    hour = minute = 0
34410    for group_key in keys:
34411        if group_key == 'I':
34412            hour = parse_int(d['I'])
34413            if ampm in ('', AM):
34414                if hour == 12:
34415                    hour = 0
34416            elif ampm == PM:
34417                if hour != 12:
34418                    hour += 12
34419        elif group_key == 'M':
34420            minute = parse_int(d['M'])
34421    return hour, minute
34422",
34423        );
34424        let f = find_code(&code, "f").expect("missing f code");
34425        let ops: Vec<_> = f
34426            .instructions
34427            .iter()
34428            .map(|unit| unit.op)
34429            .filter(|op| !matches!(op, Instruction::Cache))
34430            .collect();
34431
34432        assert!(
34433            ops.windows(5).any(|window| {
34434                matches!(
34435                    window,
34436                    [
34437                        Instruction::CompareOp { .. },
34438                        Instruction::PopJumpIfFalse { .. },
34439                        Instruction::NotTaken,
34440                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34441                        Instruction::LoadSmallInt { .. },
34442                    ]
34443                )
34444            }),
34445            "expected CPython-style nested elif body before false backedge, got ops={ops:?}"
34446        );
34447        assert!(
34448            !ops.windows(6).any(|window| {
34449                matches!(
34450                    window,
34451                    [
34452                        Instruction::CompareOp { .. },
34453                        Instruction::PopJumpIfTrue { .. },
34454                        Instruction::NotTaken,
34455                        Instruction::JumpBackward { .. }
34456                            | Instruction::JumpBackwardNoInterrupt { .. },
34457                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34458                        Instruction::LoadSmallInt { .. },
34459                    ]
34460                )
34461            }),
34462            "unexpected inverted nested elif false path before body, got ops={ops:?}"
34463        );
34464        assert!(
34465            ops.windows(15).any(|window| {
34466                matches!(
34467                    window,
34468                    [
34469                        Instruction::CompareOp { .. },
34470                        Instruction::PopJumpIfFalse { .. },
34471                        Instruction::NotTaken,
34472                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34473                        Instruction::LoadSmallInt { .. },
34474                        Instruction::CompareOp { .. },
34475                        Instruction::PopJumpIfFalse { .. },
34476                        Instruction::NotTaken,
34477                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34478                        Instruction::LoadSmallInt { .. },
34479                        Instruction::BinaryOp { .. },
34480                        Instruction::StoreFast { .. },
34481                        Instruction::JumpBackward { .. }
34482                            | Instruction::JumpBackwardNoInterrupt { .. },
34483                        Instruction::JumpBackward { .. }
34484                            | Instruction::JumpBackwardNoInterrupt { .. },
34485                        Instruction::JumpBackward { .. }
34486                            | Instruction::JumpBackwardNoInterrupt { .. },
34487                    ]
34488                )
34489            }),
34490            "expected CPython-style duplicated body/false loop exits for nested elif, got ops={ops:?}"
34491        );
34492    }
34493
34494    #[test]
34495    fn loop_nested_if_before_elif_keeps_body_before_false_backedge() {
34496        let code = compile_exec(
34497            "\
34498def f(keys, parse_int, found_dict, locale_time):
34499    hour = minute = 0
34500    for group_key in keys:
34501        if group_key == 'I':
34502            hour = parse_int(found_dict['I'])
34503            ampm = found_dict.get('p', '').lower()
34504            if ampm in ('', locale_time.am_pm[0]):
34505                if hour == 12:
34506                    hour = 0
34507            elif ampm == locale_time.am_pm[1]:
34508                if hour != 12:
34509                    hour += 12
34510        elif group_key == 'M':
34511            minute = parse_int(found_dict['M'])
34512    return hour, minute
34513",
34514        );
34515        let f = find_code(&code, "f").expect("missing f code");
34516        let ops: Vec<_> = f
34517            .instructions
34518            .iter()
34519            .map(|unit| unit.op)
34520            .filter(|op| !matches!(op, Instruction::Cache))
34521            .collect();
34522
34523        assert!(
34524            ops.windows(7).any(|window| {
34525                matches!(
34526                    window,
34527                    [
34528                        Instruction::CompareOp { .. },
34529                        Instruction::PopJumpIfFalse { .. },
34530                        Instruction::NotTaken,
34531                        Instruction::LoadSmallInt { .. },
34532                        Instruction::StoreFast { .. },
34533                        Instruction::JumpBackward { .. }
34534                            | Instruction::JumpBackwardNoInterrupt { .. },
34535                        Instruction::JumpBackward { .. }
34536                            | Instruction::JumpBackwardNoInterrupt { .. },
34537                    ]
34538                )
34539            }),
34540            "expected CPython-style nested if body before false backedge, got ops={ops:?}"
34541        );
34542        assert!(
34543            !ops.windows(5).any(|window| {
34544                matches!(
34545                    window,
34546                    [
34547                        Instruction::CompareOp { .. },
34548                        Instruction::PopJumpIfTrue { .. },
34549                        Instruction::NotTaken,
34550                        Instruction::JumpBackward { .. }
34551                            | Instruction::JumpBackwardNoInterrupt { .. },
34552                        Instruction::LoadSmallInt { .. },
34553                    ]
34554                )
34555            }),
34556            "unexpected inverted nested if body after false backedge, got ops={ops:?}"
34557        );
34558    }
34559
34560    #[test]
34561    fn elif_pass_before_raise_keeps_line_bearing_forward_jump() {
34562        let code = compile_exec(
34563            "\
34564def f(entries, path, self):
34565    if entries == ['.mh_sequences']:
34566        os.remove(os.path.join(path, '.mh_sequences'))
34567    elif entries == []:
34568        pass
34569    else:
34570        raise NotEmptyError('Folder not empty: %s' % self._path)
34571    os.rmdir(path)
34572",
34573        );
34574        let f = find_code(&code, "f").expect("missing f code");
34575        let ops: Vec<_> = f
34576            .instructions
34577            .iter()
34578            .map(|unit| unit.op)
34579            .filter(|op| !matches!(op, Instruction::Cache))
34580            .collect();
34581
34582        assert!(
34583            ops.windows(5).any(|window| {
34584                matches!(
34585                    window,
34586                    [
34587                        Instruction::CompareOp { .. },
34588                        Instruction::PopJumpIfFalse { .. },
34589                        Instruction::NotTaken,
34590                        Instruction::JumpForward { .. },
34591                        Instruction::LoadGlobal { .. },
34592                    ]
34593                )
34594            }),
34595            "expected CPython-style pass branch forward jump before raise body, got ops={ops:?}"
34596        );
34597        assert!(
34598            !ops.windows(4).any(|window| {
34599                matches!(
34600                    window,
34601                    [
34602                        Instruction::CompareOp { .. },
34603                        Instruction::PopJumpIfTrue { .. },
34604                        Instruction::NotTaken,
34605                        Instruction::LoadGlobal { .. },
34606                    ]
34607                )
34608            }),
34609            "unexpected inverted pass branch before raise body, got ops={ops:?}"
34610        );
34611    }
34612
34613    #[test]
34614    fn loop_multiblock_conditional_body_keeps_body_before_jump_back() {
34615        let code = compile_exec(
34616            "\
34617def f(random, d, f):
34618    for dummy in range(100):
34619        k = random.choice('abc')
34620        if random.random() < 0.2:
34621            if k in d:
34622                del d[k]
34623                del f[k]
34624        else:
34625            v = random.choice((1, 2))
34626            d[k] = v
34627            f[k] = v
34628            check(f[k], v)
34629",
34630        );
34631        let f = find_code(&code, "f").expect("missing function code");
34632        let ops: Vec<_> = f
34633            .instructions
34634            .iter()
34635            .map(|unit| unit.op)
34636            .filter(|op| !matches!(op, Instruction::Cache))
34637            .collect();
34638
34639        assert!(
34640            ops.windows(5).any(|window| {
34641                matches!(
34642                    window,
34643                    [
34644                        Instruction::ContainsOp { .. },
34645                        Instruction::PopJumpIfFalse { .. },
34646                        Instruction::NotTaken,
34647                        Instruction::LoadFastBorrowLoadFastBorrow { .. },
34648                        Instruction::DeleteSubscr,
34649                    ]
34650                )
34651            }),
34652            "expected CPython-style multi-block body before false jump-back, got ops={ops:?}"
34653        );
34654    }
34655
34656    #[test]
34657    fn loop_not_conditional_body_threads_true_path_to_jump_back() {
34658        let code = compile_exec(
34659            "\
34660def f(xs):
34661    for x in xs:
34662        if not x:
34663            g(x)
34664",
34665        );
34666        let f = find_code(&code, "f").expect("missing f code");
34667        let ops: Vec<_> = f
34668            .instructions
34669            .iter()
34670            .map(|unit| unit.op)
34671            .filter(|op| !matches!(op, Instruction::Cache))
34672            .collect();
34673
34674        assert!(
34675            ops.windows(5).any(|window| {
34676                matches!(
34677                    window,
34678                    [
34679                        Instruction::ToBool,
34680                        Instruction::PopJumpIfFalse { .. },
34681                        Instruction::NotTaken,
34682                        Instruction::JumpBackward { .. }
34683                            | Instruction::JumpBackwardNoInterrupt { .. },
34684                        Instruction::LoadGlobal { .. },
34685                    ]
34686                )
34687            }),
34688            "expected CPython-style true path to jump back before not-body, got ops={ops:?}"
34689        );
34690    }
34691
34692    #[test]
34693    fn loop_not_in_conditional_body_threads_true_path_to_jump_back() {
34694        let code = compile_exec(
34695            "\
34696def f(native, array):
34697    for k in native:
34698        if k not in 'bBhHiIlLfd':
34699            del array[k]
34700",
34701        );
34702        let f = find_code(&code, "f").expect("missing f code");
34703        let ops: Vec<_> = f
34704            .instructions
34705            .iter()
34706            .map(|unit| unit.op)
34707            .filter(|op| !matches!(op, Instruction::Cache))
34708            .collect();
34709
34710        assert!(
34711            ops.windows(5).any(|window| {
34712                matches!(
34713                    window,
34714                    [
34715                        Instruction::ContainsOp { .. },
34716                        Instruction::PopJumpIfTrue { .. },
34717                        Instruction::NotTaken,
34718                        Instruction::JumpBackward { .. }
34719                            | Instruction::JumpBackwardNoInterrupt { .. },
34720                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
34721                            | Instruction::LoadFastLoadFast { .. },
34722                    ]
34723                )
34724            }),
34725            "expected CPython-style true path to jump back before not-in body, got ops={ops:?}"
34726        );
34727    }
34728
34729    #[test]
34730    fn while_implicit_continue_body_after_jumpback_for_boolop_call_arg() {
34731        let code = compile_exec(
34732            "\
34733def f(source, state, verbose, nested):
34734    items = []
34735    itemsappend = items.append
34736    sourcematch = source.match
34737    while True:
34738        itemsappend(parse(source, state, verbose, nested + 1,
34739                          not nested and not items))
34740        if not sourcematch('|'):
34741            break
34742        if not nested:
34743            verbose = state.flags & 64
34744    return verbose
34745",
34746        );
34747        let f = find_code(&code, "f").expect("missing f code");
34748        let ops: Vec<_> = f
34749            .instructions
34750            .iter()
34751            .map(|unit| unit.op)
34752            .filter(|op| !matches!(op, Instruction::Cache))
34753            .collect();
34754
34755        assert!(
34756            ops.windows(5).any(|window| {
34757                matches!(
34758                    window,
34759                    [
34760                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34761                        Instruction::ToBool,
34762                        Instruction::PopJumpIfFalse { .. },
34763                        Instruction::NotTaken,
34764                        Instruction::JumpBackward { .. }
34765                            | Instruction::JumpBackwardNoInterrupt { .. },
34766                    ]
34767                )
34768            }),
34769            "expected CPython-style while implicit-continue jumpback before conditional body, got ops={ops:?}"
34770        );
34771    }
34772
34773    #[test]
34774    fn multiblock_elif_continue_keeps_next_test_before_backedge() {
34775        let code = compile_exec(
34776            "\
34777def f(source, state, verbose, nested, subpatternappend, start, MAXGROUPS):
34778    sourceget = source.get
34779    sourcematch = source.match
34780    while True:
34781        this = source.next
34782        sourceget()
34783        if this == '(':
34784            if sourcematch('?'):
34785                char = sourceget()
34786                if char in '=!<':
34787                    dir = 1
34788                    if char == '<':
34789                        char = sourceget()
34790                        if char not in '=!':
34791                            raise source.error('unknown extension ?<' + char, len(char) + 2)
34792                        dir = -1
34793                        lookbehindgroups = state.lookbehindgroups
34794                        if lookbehindgroups is None:
34795                            state.lookbehindgroups = state.groups
34796                    p = parse_sub(source, state, verbose, nested + 1)
34797                    if dir < 0:
34798                        if lookbehindgroups is None:
34799                            state.lookbehindgroups = None
34800                    if not sourcematch(')'):
34801                        raise source.error('missing ), unterminated subpattern', source.tell() - start)
34802                    if char == '=':
34803                        subpatternappend(('ASSERT', (dir, p)))
34804                    elif p:
34805                        subpatternappend(('ASSERT_NOT', (dir, p)))
34806                    else:
34807                        subpatternappend(('FAILURE', ()))
34808                    continue
34809                elif char == '(':
34810                    condname = source.getuntil(')', 'group name')
34811                    if not (condname.isdecimal() and condname.isascii()):
34812                        source.checkgroupname(condname, 1)
34813                        condgroup = state.groupdict.get(condname)
34814                        if condgroup is None:
34815                            msg = 'unknown group name %r' % condname
34816                            raise source.error(msg, len(condname) + 1)
34817                    else:
34818                        condgroup = int(condname)
34819                        if not condgroup:
34820                            raise source.error('bad group number', len(condname) + 1)
34821                        if condgroup >= MAXGROUPS:
34822                            msg = 'invalid group reference %d' % condgroup
34823                            raise source.error(msg, len(condname) + 1)
34824                    state.checklookbehindgroup(condgroup, source)
34825                    item_yes = parse(source, state, verbose, nested + 1)
34826                    if source.match('|'):
34827                        item_no = parse(source, state, verbose, nested + 1)
34828                        if source.next == '|':
34829                            raise source.error('conditional backref with more than two branches')
34830                    else:
34831                        item_no = None
34832                    if not source.match(')'):
34833                        raise source.error('missing ), unterminated subpattern', source.tell() - start)
34834                    subpatternappend(('GROUPREF_EXISTS', (condgroup, item_yes, item_no)))
34835                    continue
34836                elif char == '>':
34837                    capture = False
34838        return
34839",
34840        );
34841        let f = find_code(&code, "f").expect("missing f code");
34842        let ops: Vec<_> = f
34843            .instructions
34844            .iter()
34845            .map(|unit| unit.op)
34846            .filter(|op| !matches!(op, Instruction::Cache))
34847            .collect();
34848
34849        assert!(
34850            !ops.windows(2).any(|window| {
34851                matches!(
34852                    window,
34853                    [
34854                        Instruction::JumpBackward { .. }
34855                            | Instruction::JumpBackwardNoInterrupt { .. },
34856                        Instruction::JumpBackward { .. }
34857                            | Instruction::JumpBackwardNoInterrupt { .. },
34858                    ]
34859                )
34860            }),
34861            "expected CPython-style elif test between separate continue backedges, got ops={ops:?}"
34862        );
34863    }
34864
34865    #[test]
34866    fn while_scope_exit_body_keeps_line_backedge_before_raise_body() {
34867        let code = compile_exec(
34868            "\
34869FLAGS = {}
34870TYPE_FLAGS = 0
34871GLOBAL_FLAGS = 0
34872
34873def f(source, state, char):
34874    sourceget = source.get
34875    add_flags = 0
34876    del_flags = 0
34877    if char != '-':
34878        while True:
34879            flag = FLAGS[char]
34880            if source.istext:
34881                if char == 'L':
34882                    msg = 'bad inline flags'
34883                    raise source.error(msg)
34884            else:
34885                if char == 'u':
34886                    msg = 'bad inline flags'
34887                    raise source.error(msg)
34888            add_flags |= flag
34889            if (flag & TYPE_FLAGS) and (add_flags & TYPE_FLAGS) != flag:
34890                msg = 'bad inline flags'
34891                raise source.error(msg)
34892            char = sourceget()
34893            if char is None:
34894                raise source.error('missing -, : or )')
34895            if char in ')-:':
34896                break
34897            if char not in FLAGS:
34898                msg = 'unknown flag' if char.isalpha() else 'missing -, : or )'
34899                raise source.error(msg, len(char))
34900    if char == ')':
34901        state.flags |= add_flags
34902        return None
34903    if add_flags & GLOBAL_FLAGS:
34904        raise source.error('bad inline flags: cannot turn on global flag', 1)
34905    if char == '-':
34906        char = sourceget()
34907        if char is None:
34908            raise source.error('missing flag')
34909        if char not in FLAGS:
34910            msg = 'unknown flag' if char.isalpha() else 'missing flag'
34911            raise source.error(msg, len(char))
34912        while True:
34913            flag = FLAGS[char]
34914            if flag & TYPE_FLAGS:
34915                msg = 'bad inline flags'
34916                raise source.error(msg)
34917            del_flags |= flag
34918            char = sourceget()
34919            if char is None:
34920                raise source.error('missing :')
34921            if char == ':':
34922                break
34923            if char not in FLAGS:
34924                msg = 'unknown flag' if char.isalpha() else 'missing :'
34925                raise source.error(msg, len(char))
34926    return add_flags, del_flags
34927",
34928        );
34929        let f = find_code(&code, "f").expect("missing f code");
34930        let ops: Vec<_> = f
34931            .instructions
34932            .iter()
34933            .map(|unit| unit.op)
34934            .filter(|op| !matches!(op, Instruction::Cache))
34935            .collect();
34936
34937        let cpython_style_not_in_raise_body_count = ops
34938            .windows(6)
34939            .filter(|window| {
34940                matches!(
34941                    window,
34942                    [
34943                        Instruction::ContainsOp { .. },
34944                        Instruction::PopJumpIfTrue { .. },
34945                        Instruction::NotTaken,
34946                        Instruction::JumpBackward { .. }
34947                            | Instruction::JumpBackwardNoInterrupt { .. },
34948                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34949                        Instruction::LoadAttr { .. },
34950                    ]
34951                )
34952            })
34953            .count();
34954        assert!(
34955            cpython_style_not_in_raise_body_count >= 2,
34956            "expected both while-loop not-in checks to put the false-path backedge before the conditional-expression raise body, got ops={ops:?}"
34957        );
34958    }
34959
34960    #[test]
34961    fn call_body_implicit_continue_keeps_cpython_normalized_forward_jump() {
34962        let code = compile_exec(
34963            "\
34964DIGITS = '0123456789'
34965
34966def f(s, sget, lappend, addgroup, this, c):
34967    while True:
34968        if c in DIGITS:
34969            isoctal = False
34970            if s.next in DIGITS:
34971                this += sget()
34972            if not isoctal:
34973                addgroup(int(this[1:]), len(this) - 1)
34974        else:
34975            lappend(this)
34976",
34977        );
34978        let f = find_code(&code, "f").expect("missing f code");
34979        let ops: Vec<_> = f
34980            .instructions
34981            .iter()
34982            .map(|unit| unit.op)
34983            .filter(|op| !matches!(op, Instruction::Cache))
34984            .collect();
34985
34986        assert!(
34987            ops.windows(5).any(|window| {
34988                matches!(
34989                    window,
34990                    [
34991                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34992                        Instruction::ToBool,
34993                        Instruction::PopJumpIfTrue { .. },
34994                        Instruction::NotTaken,
34995                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
34996                    ]
34997                )
34998            }),
34999            "expected CPython-style forward jump over call body, got ops={ops:?}"
35000        );
35001        assert!(
35002            !ops.windows(5).any(|window| {
35003                matches!(
35004                    window,
35005                    [
35006                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
35007                        Instruction::ToBool,
35008                        Instruction::PopJumpIfFalse { .. },
35009                        Instruction::NotTaken,
35010                        Instruction::JumpBackward { .. }
35011                            | Instruction::JumpBackwardNoInterrupt { .. },
35012                    ]
35013                )
35014            }),
35015            "call body should not be moved after an implicit continue backedge, got ops={ops:?}"
35016        );
35017    }
35018
35019    #[test]
35020    fn empty_if_end_label_preserves_cpython_return_anchor_nop() {
35021        let code = compile_exec(
35022            "\
35023SRE_FLAG_LOCALE = 1
35024SRE_FLAG_ASCII = 2
35025SRE_FLAG_UNICODE = 4
35026
35027def f(src, flags):
35028    if isinstance(src, str):
35029        if flags & SRE_FLAG_LOCALE:
35030            raise ValueError('cannot use LOCALE flag with a str pattern')
35031        if not flags & SRE_FLAG_ASCII:
35032            flags |= SRE_FLAG_UNICODE
35033        elif flags & SRE_FLAG_UNICODE:
35034            raise ValueError('ASCII and UNICODE flags are incompatible')
35035    else:
35036        if flags & SRE_FLAG_UNICODE:
35037            raise ValueError('cannot use UNICODE flag with a bytes pattern')
35038        if flags & SRE_FLAG_LOCALE and flags & SRE_FLAG_ASCII:
35039            raise ValueError('ASCII and LOCALE flags are incompatible')
35040    return flags
35041",
35042        );
35043        let f = find_code(&code, "f").expect("missing f code");
35044        let ops: Vec<_> = f
35045            .instructions
35046            .iter()
35047            .map(|unit| unit.op)
35048            .filter(|op| !matches!(op, Instruction::Cache))
35049            .collect();
35050
35051        assert!(
35052            ops.windows(4).any(|window| {
35053                matches!(
35054                    window,
35055                    [
35056                        Instruction::RaiseVarargs { .. },
35057                        Instruction::Nop,
35058                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
35059                        Instruction::ReturnValue,
35060                    ]
35061                )
35062            }),
35063            "expected CPython-style NOP return anchor after elif raise body, got ops={ops:?}"
35064        );
35065    }
35066
35067    #[test]
35068    fn nested_except_normal_exit_return_uses_strong_loads() {
35069        let code = compile_exec(
35070            "\
35071LITERAL = 1
35072
35073def f(source, escape):
35074    try:
35075        c = escape[1:2]
35076        if c == 'N' and source.istext:
35077            try:
35078                c = ord(source.lookup())
35079            except (KeyError, TypeError):
35080                raise source.error() from None
35081            return LITERAL, c
35082    except ValueError:
35083        pass
35084    raise source.error(escape)
35085",
35086        );
35087        let f = find_code(&code, "f").expect("missing f code");
35088        let ops: Vec<_> = f
35089            .instructions
35090            .iter()
35091            .map(|unit| unit.op)
35092            .filter(|op| !matches!(op, Instruction::Cache))
35093            .collect();
35094
35095        assert!(
35096            ops.windows(4).any(|window| {
35097                matches!(
35098                    window,
35099                    [
35100                        Instruction::LoadGlobal { .. },
35101                        Instruction::LoadFast { .. },
35102                        Instruction::BuildTuple { .. },
35103                        Instruction::ReturnValue,
35104                    ]
35105                )
35106            }),
35107            "expected CPython-style strong LOAD_FAST in nested except normal-exit return, got ops={ops:?}"
35108        );
35109    }
35110
35111    #[test]
35112    fn targeted_nop_after_prefix_for_else_uses_strong_for_tail_loads() {
35113        let code = compile_exec(
35114            "\
35115LITERAL = 1
35116IN = 2
35117NEGATE = 3
35118
35119def f(items):
35120    while True:
35121        prefix = None
35122        for item in items:
35123            if not item:
35124                break
35125            if prefix is None:
35126                prefix = item[0]
35127            elif item[0] != prefix:
35128                break
35129        else:
35130            for item in items:
35131                del item[0]
35132            continue
35133        break
35134    set = []
35135    for item in items:
35136        if len(item) != 1:
35137            break
35138        op, av = item[0]
35139        if op is LITERAL:
35140            set.append((op, av))
35141        elif op is IN and av[0][0] is not NEGATE:
35142            set.extend(av)
35143        else:
35144            break
35145    return set
35146",
35147        );
35148        let f = find_code(&code, "f").expect("missing f code");
35149        let ops: Vec<_> = f
35150            .instructions
35151            .iter()
35152            .map(|unit| unit.op)
35153            .filter(|op| !matches!(op, Instruction::Cache))
35154            .collect();
35155
35156        let get_iter_idx = ops
35157            .iter()
35158            .rposition(|op| matches!(op, Instruction::GetIter))
35159            .expect("missing GET_ITER");
35160        assert!(
35161            matches!(ops[get_iter_idx - 1], Instruction::LoadFast { .. }),
35162            "targeted NOP after prefix for/else should use strong iterable LOAD_FAST, got ops={ops:?}"
35163        );
35164        assert!(
35165            ops.windows(4).any(|window| {
35166                matches!(
35167                    window,
35168                    [
35169                        Instruction::LoadFast { .. },
35170                        Instruction::LoadAttr { .. },
35171                        Instruction::LoadFastLoadFast { .. },
35172                        Instruction::BuildTuple { .. },
35173                    ]
35174                )
35175            }),
35176            "targeted NOP after prefix for/else should keep set.append tuple loads strong, got ops={ops:?}"
35177        );
35178    }
35179
35180    #[test]
35181    fn plain_pass_before_for_tail_keeps_borrows() {
35182        let code = compile_exec(
35183            "\
35184def f(xs):
35185    pass
35186    for x in xs:
35187        pass
35188    return xs
35189",
35190        );
35191        let f = find_code(&code, "f").expect("missing f code");
35192        let ops: Vec<_> = f
35193            .instructions
35194            .iter()
35195            .map(|unit| unit.op)
35196            .filter(|op| !matches!(op, Instruction::Cache))
35197            .collect();
35198
35199        let get_iter_idx = ops
35200            .iter()
35201            .position(|op| matches!(op, Instruction::GetIter))
35202            .expect("missing GET_ITER");
35203        assert!(
35204            matches!(ops[get_iter_idx - 1], Instruction::LoadFastBorrow { .. }),
35205            "plain pass before for-tail should keep borrowed iterable load, got ops={ops:?}"
35206        );
35207    }
35208
35209    #[test]
35210    fn targeted_nop_after_return_uses_strong_pair_call_args() {
35211        let code = compile_exec(
35212            "\
35213def f(x, d, count, inner, hi, w1, lo, w2):
35214    if x:
35215        return
35216    if 0:
35217        pass
35218    d[count] += 1
35219    inner(hi, w1)
35220    del hi
35221    inner(lo, w2)
35222",
35223        );
35224        let f = find_code(&code, "f").expect("missing f code");
35225        let ops: Vec<_> = f
35226            .instructions
35227            .iter()
35228            .map(|unit| unit.op)
35229            .filter(|op| !matches!(op, Instruction::Cache))
35230            .collect();
35231
35232        assert!(
35233            ops.windows(3).any(|window| {
35234                matches!(
35235                    window,
35236                    [
35237                        Instruction::LoadFast { .. },
35238                        Instruction::PushNull,
35239                        Instruction::LoadFastLoadFast { .. },
35240                    ]
35241                )
35242            }),
35243            "targeted NOP after return should keep CPython-style strong pair call args, got ops={ops:?}"
35244        );
35245        let nop_idx = ops
35246            .iter()
35247            .position(|op| matches!(op, Instruction::Nop))
35248            .expect("missing targeted NOP");
35249        assert!(
35250            !ops[nop_idx..].iter().any(|op| {
35251                matches!(
35252                    op,
35253                    Instruction::LoadFastBorrow { .. }
35254                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
35255                )
35256            }),
35257            "targeted NOP tail should not reintroduce borrowed fast loads, got ops={ops:?}"
35258        );
35259    }
35260
35261    #[test]
35262    fn loop_if_pass_uses_line_bearing_jump_back_instead_of_nop() {
35263        let code = compile_exec(
35264            "\
35265def f(x, y):
35266    for i in x:
35267        if y:
35268            pass
35269",
35270        );
35271        let f = find_code(&code, "f").expect("missing f code");
35272        let ops: Vec<_> = f
35273            .instructions
35274            .iter()
35275            .map(|unit| unit.op)
35276            .filter(|op| !matches!(op, Instruction::Cache))
35277            .collect();
35278
35279        assert!(
35280            ops.windows(5).any(|window| {
35281                matches!(
35282                    window,
35283                    [
35284                        Instruction::ToBool,
35285                        Instruction::PopJumpIfTrue { .. },
35286                        Instruction::NotTaken,
35287                        Instruction::JumpBackward { .. }
35288                            | Instruction::JumpBackwardNoInterrupt { .. },
35289                        Instruction::JumpBackward { .. }
35290                            | Instruction::JumpBackwardNoInterrupt { .. },
35291                    ]
35292                )
35293            }),
35294            "expected CPython-style synthetic false-path jump-back plus body jump-back, got ops={ops:?}"
35295        );
35296        assert!(
35297            !ops.iter().any(|op| matches!(op, Instruction::Nop)),
35298            "expected pass body line to attach to loop backedge instead of leaving a NOP, got ops={ops:?}"
35299        );
35300    }
35301
35302    #[test]
35303    fn constant_true_while_pass_keeps_loop_header_nop() {
35304        let code = compile_exec(
35305            "\
35306def f():
35307    while 1:
35308        pass
35309",
35310        );
35311        let f = find_code(&code, "f").expect("missing f code");
35312        let ops: Vec<_> = f
35313            .instructions
35314            .iter()
35315            .map(|unit| unit.op)
35316            .filter(|op| !matches!(op, Instruction::Cache))
35317            .collect();
35318
35319        assert!(
35320            ops.windows(2).any(|window| {
35321                matches!(
35322                    window,
35323                    [
35324                        Instruction::Nop,
35325                        Instruction::JumpBackward { .. }
35326                            | Instruction::JumpBackwardNoInterrupt { .. },
35327                    ]
35328                )
35329            }),
35330            "expected CPython-style loop-header NOP before self backedge, got ops={ops:?}"
35331        );
35332    }
35333
35334    #[test]
35335    fn nested_if_shared_jump_back_target_is_duplicated() {
35336        let code = compile_exec(
35337            "\
35338def f(s, size, encodeSetO, encodeWhiteSpace):
35339    inShift = True
35340    base64bits = 0
35341    out = []
35342    for i, ch in enumerate(s):
35343        if base64bits == 0:
35344            if i + 1 < size:
35345                ch2 = s[i + 1]
35346                if E(ch2, encodeSetO, encodeWhiteSpace):
35347                    if B(ch2) or ch2 == '-':
35348                        out.append(b'-')
35349                    inShift = False
35350            else:
35351                out.append(b'-')
35352                inShift = False
35353    return out
35354",
35355        );
35356        let f = find_code(&code, "f").expect("missing f code");
35357        let ops: Vec<_> = f
35358            .instructions
35359            .iter()
35360            .map(|unit| unit.op)
35361            .filter(|op| !matches!(op, Instruction::Cache))
35362            .collect();
35363
35364        assert!(
35365            ops.windows(6).any(|window| {
35366                matches!(
35367                    window,
35368                    [
35369                        Instruction::PopTop,
35370                        Instruction::LoadConst { .. },
35371                        Instruction::StoreFast { .. },
35372                        Instruction::JumpBackward { .. }
35373                            | Instruction::JumpBackwardNoInterrupt { .. },
35374                        Instruction::JumpBackward { .. }
35375                            | Instruction::JumpBackwardNoInterrupt { .. },
35376                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
35377                    ]
35378                )
35379            }),
35380            "expected separate nested-if and outer-if jump-back tails, got ops={ops:?}"
35381        );
35382    }
35383
35384    #[test]
35385    fn exception_cleanup_backedge_target_is_shared() {
35386        let code = compile_exec(
35387            "\
35388def f(enum_class, value, Flag, int_type, is_single_bit):
35389    try:
35390        try:
35391            enum_member = enum_class[value]
35392        except TypeError:
35393            raise KeyError
35394    except KeyError:
35395        if Flag is None or not issubclass(enum_class, Flag):
35396            enum_class.names.append(value)
35397        elif (
35398            Flag is not None
35399            and issubclass(enum_class, Flag)
35400            and isinstance(value, int_type)
35401            and is_single_bit(value)
35402        ):
35403            enum_class.names.append(value)
35404    enum_class.add(enum_member)
35405    return enum_member
35406",
35407        );
35408        let f = find_code(&code, "f").expect("missing f code");
35409        let ops: Vec<_> = f
35410            .instructions
35411            .iter()
35412            .map(|unit| unit.op)
35413            .filter(|op| !matches!(op, Instruction::Cache))
35414            .collect();
35415
35416        assert!(
35417            ops.windows(3).any(|window| {
35418                matches!(
35419                    window,
35420                    [
35421                        Instruction::PopExcept,
35422                        Instruction::JumpBackwardNoInterrupt { .. }
35423                            | Instruction::JumpBackward { .. },
35424                        Instruction::Reraise { .. },
35425                    ]
35426                )
35427            }),
35428            "expected CPython-style shared exception cleanup backedge before reraise, got ops={ops:?}"
35429        );
35430        assert!(
35431            !ops.windows(4).any(|window| {
35432                matches!(
35433                    window,
35434                    [
35435                        Instruction::PopExcept,
35436                        Instruction::JumpBackwardNoInterrupt { .. }
35437                            | Instruction::JumpBackward { .. },
35438                        Instruction::PopExcept,
35439                        Instruction::JumpBackwardNoInterrupt { .. }
35440                            | Instruction::JumpBackward { .. },
35441                    ]
35442                )
35443            }),
35444            "exception cleanup backedge should be shared, not duplicated per conditional edge, got ops={ops:?}"
35445        );
35446    }
35447
35448    #[test]
35449    fn protected_loop_conditional_keeps_forward_body_entry() {
35450        let code = compile_exec(
35451            "\
35452def outer(it, C1):
35453    def f():
35454        for x in it:
35455            try:
35456                if C1:
35457                    yield 2
35458            except OSError:
35459                pass
35460    return f
35461",
35462        );
35463        let outer = find_code(&code, "outer").expect("missing outer code");
35464        let f = find_code(outer, "f").expect("missing f code");
35465        let ops: Vec<_> = f
35466            .instructions
35467            .iter()
35468            .map(|unit| unit.op)
35469            .filter(|op| !matches!(op, Instruction::Cache))
35470            .collect();
35471
35472        assert!(
35473            ops.windows(7).any(|window| {
35474                matches!(
35475                    window,
35476                    [
35477                        Instruction::ToBool,
35478                        Instruction::PopJumpIfFalse { .. },
35479                        Instruction::NotTaken,
35480                        Instruction::LoadSmallInt { .. },
35481                        Instruction::YieldValue { .. },
35482                        Instruction::Resume { .. },
35483                        Instruction::PopTop,
35484                    ]
35485                )
35486            }),
35487            "expected protected conditional to keep CPython-style forward body entry, got ops={ops:?}"
35488        );
35489    }
35490
35491    #[test]
35492    fn nested_except_false_path_duplicates_pop_except_jump_back_tail() {
35493        let code = compile_exec(
35494            "\
35495def f(it, C3):
35496    for x in it:
35497        try:
35498            X = 3
35499        except OSError:
35500            try:
35501                if C3:
35502                    X = 4
35503            except OSError:
35504                pass
35505    return 42
35506",
35507        );
35508        let f = find_code(&code, "f").expect("missing f code");
35509        let ops: Vec<_> = f
35510            .instructions
35511            .iter()
35512            .map(|unit| unit.op)
35513            .filter(|op| !matches!(op, Instruction::Cache))
35514            .collect();
35515
35516        assert!(
35517            ops.windows(6).any(|window| {
35518                matches!(
35519                    window,
35520                    [
35521                        Instruction::LoadSmallInt { .. },
35522                        Instruction::StoreFast { .. },
35523                        Instruction::PopExcept,
35524                        Instruction::JumpBackward { .. }
35525                            | Instruction::JumpBackwardNoInterrupt { .. },
35526                        Instruction::PopExcept,
35527                        Instruction::JumpBackward { .. }
35528                            | Instruction::JumpBackwardNoInterrupt { .. },
35529                    ]
35530                )
35531            }),
35532            "expected CPython-style duplicated false-path exit tail, got ops={ops:?}"
35533        );
35534    }
35535
35536    #[test]
35537    fn more_nested_except_false_paths_duplicate_all_jump_back_tails() {
35538        let code = compile_exec(
35539            "\
35540def f(it, C3, C4):
35541    for x in it:
35542        try:
35543            X = 3
35544        except OSError:
35545            try:
35546                if C3:
35547                    if C4:
35548                        X = 4
35549            except OSError:
35550                try:
35551                    if C3:
35552                        if C4:
35553                            X = 5
35554                except OSError:
35555                    pass
35556    return 42
35557",
35558        );
35559        let f = find_code(&code, "f").expect("missing f code");
35560        let ops: Vec<_> = f
35561            .instructions
35562            .iter()
35563            .map(|unit| unit.op)
35564            .filter(|op| !matches!(op, Instruction::Cache))
35565            .collect();
35566
35567        assert!(
35568            ops.windows(8).any(|window| {
35569                matches!(
35570                    window,
35571                    [
35572                        Instruction::LoadSmallInt { .. },
35573                        Instruction::StoreFast { .. },
35574                        Instruction::PopExcept,
35575                        Instruction::JumpBackward { .. }
35576                            | Instruction::JumpBackwardNoInterrupt { .. },
35577                        Instruction::PopExcept,
35578                        Instruction::JumpBackward { .. }
35579                            | Instruction::JumpBackwardNoInterrupt { .. },
35580                        Instruction::PopExcept,
35581                        Instruction::JumpBackward { .. }
35582                            | Instruction::JumpBackwardNoInterrupt { .. },
35583                    ]
35584                )
35585            }),
35586            "expected CPython-style duplicated nested false-path exit tails, got ops={ops:?}"
35587        );
35588    }
35589
35590    #[test]
35591    fn no_wraparound_jump_keeps_forward_hop_before_loop_backedge() {
35592        let code = compile_exec(
35593            "\
35594def while_not_chained(a, b, c):
35595    while not (a < b < c):
35596        pass
35597",
35598        );
35599        let f = find_code(&code, "while_not_chained").expect("missing while_not_chained code");
35600        let ops: Vec<_> = f
35601            .instructions
35602            .iter()
35603            .map(|unit| unit.op)
35604            .filter(|op| !matches!(op, Instruction::Cache))
35605            .collect();
35606
35607        assert!(
35608            ops.windows(5).any(|window| {
35609                matches!(
35610                    window,
35611                    [
35612                        Instruction::PopJumpIfTrue { .. },
35613                        Instruction::NotTaken,
35614                        Instruction::JumpForward { .. },
35615                        Instruction::PopTop,
35616                        Instruction::JumpBackward { .. }
35617                            | Instruction::JumpBackwardNoInterrupt { .. },
35618                    ]
35619                )
35620            }),
35621            "expected CPython-style no-wraparound forward hop before the loop backedge, got ops={ops:?}"
35622        );
35623    }
35624
35625    #[test]
35626    fn nested_while_chained_compare_break_keeps_break_jump_block() {
35627        let code = compile_exec(
35628            "\
35629def f(start, self, stop, size):
35630    while size > 0:
35631        while True:
35632            if start <= self.position < stop:
35633                break
35634            else:
35635                self.map_index += 1
35636                if self.map_index == len(self.map):
35637                    self.map_index = 0
35638        length = min(size, stop - self.position)
35639        size -= length
35640    return size
35641",
35642        );
35643        let f = find_code(&code, "f").expect("missing f code");
35644        let ops: Vec<_> = f
35645            .instructions
35646            .iter()
35647            .map(|unit| unit.op)
35648            .filter(|op| !matches!(op, Instruction::Cache))
35649            .collect();
35650
35651        assert!(
35652            ops.windows(6).any(|window| {
35653                matches!(
35654                    window,
35655                    [
35656                        Instruction::PopJumpIfFalse { .. },
35657                        Instruction::NotTaken,
35658                        Instruction::JumpForward { .. },
35659                        Instruction::PopTop,
35660                        Instruction::JumpForward { .. },
35661                        Instruction::JumpForward { .. },
35662                    ]
35663                )
35664            }),
35665            "expected CPython-style chained-compare success hop into the break jump block, got ops={ops:?}"
35666        );
35667    }
35668
35669    #[test]
35670    fn while_break_else_keeps_true_edge_into_forward_break_body() {
35671        let code = compile_exec(
35672            "\
35673def f(i):
35674    while i:
35675        i -= 1
35676        if i < 4:
35677            break
35678    else:
35679        print('x')
35680    print('y')
35681",
35682        );
35683        let f = find_code(&code, "f").expect("missing f code");
35684        let ops: Vec<_> = f
35685            .instructions
35686            .iter()
35687            .map(|unit| unit.op)
35688            .filter(|op| !matches!(op, Instruction::Cache))
35689            .collect();
35690
35691        assert!(
35692            ops.windows(4).any(|window| {
35693                matches!(
35694                    window,
35695                    [
35696                        Instruction::PopJumpIfTrue { .. },
35697                        Instruction::NotTaken,
35698                        Instruction::JumpBackward { .. }
35699                            | Instruction::JumpBackwardNoInterrupt { .. },
35700                        Instruction::JumpForward { .. },
35701                    ]
35702                )
35703            }),
35704            "expected CPython-style true edge into forward break body with false path falling into the loop backedge, got ops={ops:?}"
35705        );
35706    }
35707
35708    #[test]
35709    fn nested_if_continue_reorders_false_path_to_loop_backedge() {
35710        let code = compile_exec(
35711            "\
35712def f(items, changes):
35713    for x in items:
35714        if not x:
35715            if x in changes:
35716                raise TypeError
35717            continue
35718",
35719        );
35720        let f = find_code(&code, "f").expect("missing f code");
35721        let ops: Vec<_> = f
35722            .instructions
35723            .iter()
35724            .map(|unit| unit.op)
35725            .filter(|op| !matches!(op, Instruction::Cache))
35726            .collect();
35727
35728        assert!(
35729            ops.windows(7).any(|window| {
35730                matches!(
35731                    window,
35732                    [
35733                        Instruction::ToBool,
35734                        Instruction::PopJumpIfFalse { .. },
35735                        Instruction::NotTaken,
35736                        Instruction::JumpBackward { .. }
35737                            | Instruction::JumpBackwardNoInterrupt { .. },
35738                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
35739                            | Instruction::LoadFastLoadFast { .. },
35740                        Instruction::ContainsOp { .. },
35741                        Instruction::PopJumpIfFalse { .. },
35742                    ]
35743                )
35744            }),
35745            "expected nested if/continue to keep CPython-style false-edge jump-back tails, got ops={ops:?}"
35746        );
35747    }
35748
35749    #[test]
35750    fn loop_assert_keeps_false_edge_into_raise_body() {
35751        let code = compile_exec(
35752            "\
35753def f(bytecode):
35754    for instr, positions in zip(bytecode, bytecode.codeobj.co_positions()):
35755        assert instr.positions == positions
35756",
35757        );
35758        let f = find_code(&code, "f").expect("missing f code");
35759        let ops: Vec<_> = f
35760            .instructions
35761            .iter()
35762            .map(|unit| unit.op)
35763            .filter(|op| !matches!(op, Instruction::Cache))
35764            .collect();
35765
35766        assert!(
35767            ops.windows(6).any(|window| {
35768                matches!(
35769                    window,
35770                    [
35771                        Instruction::CompareOp { .. },
35772                        Instruction::PopJumpIfFalse { .. },
35773                        Instruction::NotTaken,
35774                        Instruction::JumpBackward { .. }
35775                            | Instruction::JumpBackwardNoInterrupt { .. },
35776                        Instruction::LoadCommonConstant { .. },
35777                        Instruction::RaiseVarargs { .. },
35778                    ]
35779                )
35780            }),
35781            "expected loop assert to keep CPython-style false-edge into the raise body, got ops={ops:?}"
35782        );
35783    }
35784
35785    #[test]
35786    fn and_is_not_none_loop_guard_uses_direct_jump_back_false_path() {
35787        let code = compile_exec(
35788            "\
35789def f(code):
35790    last_line = -2
35791    for _, _, line in code.co_lines():
35792        if line is not None and line != last_line:
35793            last_line = line
35794",
35795        );
35796        let f = find_code(&code, "f").expect("missing f code");
35797        let ops: Vec<_> = f
35798            .instructions
35799            .iter()
35800            .map(|unit| unit.op)
35801            .filter(|op| !matches!(op, Instruction::Cache))
35802            .collect();
35803
35804        assert!(
35805            ops.windows(6).any(|window| {
35806                matches!(
35807                    window,
35808                    [
35809                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
35810                        Instruction::PopJumpIfNotNone { .. },
35811                        Instruction::NotTaken,
35812                        Instruction::JumpBackward { .. }
35813                            | Instruction::JumpBackwardNoInterrupt { .. },
35814                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
35815                            | Instruction::LoadFastLoadFast { .. },
35816                        Instruction::CompareOp { .. },
35817                    ]
35818                )
35819            }),
35820            "expected CPython-style direct jump-back false path for 'is not None and ...', got ops={ops:?}"
35821        );
35822    }
35823
35824    #[test]
35825    fn large_is_not_none_loop_guard_uses_direct_jump_back_false_path() {
35826        let code = compile_exec(
35827            "\
35828def f(cls, _FIELDS, _PARAMS):
35829    all_frozen_bases = None
35830    any_frozen_base = False
35831    has_dataclass_bases = False
35832    for b in cls.__mro__[-1:0:-1]:
35833        base_fields = getattr(b, _FIELDS, None)
35834        if base_fields is not None:
35835            has_dataclass_bases = True
35836            for field in base_fields.values():
35837                name = field.name
35838            if all_frozen_bases is None:
35839                all_frozen_bases = True
35840            current_frozen = getattr(b, _PARAMS).frozen
35841            all_frozen_bases = all_frozen_bases and current_frozen
35842            any_frozen_base = any_frozen_base or current_frozen
35843",
35844        );
35845        let f = find_code(&code, "f").expect("missing f code");
35846        let ops: Vec<_> = f
35847            .instructions
35848            .iter()
35849            .map(|unit| unit.op)
35850            .filter(|op| !matches!(op, Instruction::Cache))
35851            .collect();
35852
35853        assert!(
35854            ops.windows(6).any(|window| {
35855                matches!(
35856                    window,
35857                    [
35858                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
35859                        Instruction::PopJumpIfNotNone { .. },
35860                        Instruction::NotTaken,
35861                        Instruction::JumpBackward { .. }
35862                            | Instruction::JumpBackwardNoInterrupt { .. },
35863                        Instruction::LoadConst { .. },
35864                        Instruction::StoreFast { .. },
35865                    ]
35866                )
35867            }),
35868            "expected CPython-style direct jump-back false path for large 'is not None' loop body, got ops={ops:?}"
35869        );
35870    }
35871
35872    #[test]
35873    fn continue_inside_with_keeps_line_marker_nop_before_exit_cleanup() {
35874        let code = compile_exec(
35875            "\
35876def f(it):
35877    for func in it:
35878        with cm():
35879            if cond():
35880                continue
35881",
35882        );
35883        let f = find_code(&code, "f").expect("missing f code");
35884        let ops: Vec<_> = f
35885            .instructions
35886            .iter()
35887            .map(|unit| unit.op)
35888            .filter(|op| !matches!(op, Instruction::Cache))
35889            .collect();
35890
35891        assert!(
35892            ops.windows(9).any(|window| {
35893                matches!(
35894                    window,
35895                    [
35896                        Instruction::PopJumpIfFalse { .. },
35897                        Instruction::NotTaken,
35898                        Instruction::Nop,
35899                        Instruction::LoadConst { .. },
35900                        Instruction::LoadConst { .. },
35901                        Instruction::LoadConst { .. },
35902                        Instruction::Call { .. },
35903                        Instruction::PopTop,
35904                        Instruction::JumpBackward { .. }
35905                            | Instruction::JumpBackwardNoInterrupt { .. },
35906                    ]
35907                )
35908            }),
35909            "expected CPython-style line-marker NOP before with-exit cleanup on continue, got ops={ops:?}"
35910        );
35911    }
35912
35913    #[test]
35914    fn nested_async_with_normal_cleanup_drops_pop_block_nop() {
35915        let code = compile_exec(
35916            "\
35917async def foo():
35918    async with CM():
35919        async with CM():
35920            raise RuntimeError
35921",
35922        );
35923        let f = find_code(&code, "foo").expect("missing foo code");
35924        let ops: Vec<_> = f
35925            .instructions
35926            .iter()
35927            .map(|unit| unit.op)
35928            .filter(|op| !matches!(op, Instruction::Cache))
35929            .collect();
35930
35931        assert!(
35932            ops.windows(5).any(|window| {
35933                matches!(
35934                    window,
35935                    [
35936                        Instruction::LoadConst { .. },
35937                        Instruction::LoadConst { .. },
35938                        Instruction::LoadConst { .. },
35939                        Instruction::Call { .. },
35940                        Instruction::GetAwaitable { .. },
35941                    ]
35942                )
35943            }),
35944            "expected CPython-style async-with normal cleanup without a POP_BLOCK NOP, got ops={ops:?}"
35945        );
35946        assert!(
35947            !ops.windows(6).any(|window| {
35948                matches!(
35949                    window,
35950                    [
35951                        Instruction::Nop,
35952                        Instruction::LoadConst { .. },
35953                        Instruction::LoadConst { .. },
35954                        Instruction::LoadConst { .. },
35955                        Instruction::Call { .. },
35956                        Instruction::GetAwaitable { .. },
35957                    ]
35958                )
35959            }),
35960            "unexpected POP_BLOCK NOP before async-with normal cleanup, got ops={ops:?}"
35961        );
35962    }
35963
35964    #[test]
35965    fn async_with_try_finally_before_outer_sync_with_cleanup_keeps_anchor_nop() {
35966        let code = compile_exec(
35967            "\
35968async def foo(self):
35969    with self.assertRaises(ZeroDivisionError):
35970        async with timeout():
35971            try:
35972                try:
35973                    raise ValueError
35974                finally:
35975                    await sleep(1)
35976            finally:
35977                try:
35978                    raise KeyError
35979                finally:
35980                    1 / 0
35981    after()
35982",
35983        );
35984        let f = find_code(&code, "foo").expect("missing foo code");
35985        let ops: Vec<_> = f
35986            .instructions
35987            .iter()
35988            .map(|unit| unit.op)
35989            .filter(|op| !matches!(op, Instruction::Cache))
35990            .collect();
35991
35992        assert!(
35993            ops.windows(6).any(|window| {
35994                matches!(
35995                    window,
35996                    [
35997                        Instruction::Copy { .. },
35998                        Instruction::PopExcept,
35999                        Instruction::Reraise { .. },
36000                        Instruction::Nop,
36001                        Instruction::LoadConst { .. },
36002                        Instruction::LoadConst { .. },
36003                    ]
36004                )
36005            }),
36006            "expected CPython-style async-with after-block NOP before outer sync-with cleanup, got ops={ops:?}"
36007        );
36008    }
36009
36010    #[test]
36011    fn nested_terminal_with_keeps_outer_cleanup_target_nop() {
36012        let code = compile_exec(
36013            "\
36014def f():
36015    with a():
36016        with b():
36017            raise E()
36018",
36019        );
36020        let f = find_code(&code, "f").expect("missing f code");
36021        let ops: Vec<_> = f
36022            .instructions
36023            .iter()
36024            .map(|unit| unit.op)
36025            .filter(|op| !matches!(op, Instruction::Cache))
36026            .collect();
36027
36028        assert!(
36029            ops.windows(6).any(|window| {
36030                matches!(
36031                    window,
36032                    [
36033                        Instruction::Copy { .. },
36034                        Instruction::PopExcept,
36035                        Instruction::Reraise { .. },
36036                        Instruction::Nop,
36037                        Instruction::LoadConst { .. },
36038                        Instruction::LoadConst { .. },
36039                    ]
36040                )
36041            }),
36042            "expected CPython-style outer with-exit target NOP after terminal nested with cleanup, got ops={ops:?}"
36043        );
36044    }
36045
36046    #[test]
36047    fn nested_nonterminal_with_drops_outer_cleanup_target_nop() {
36048        let code = compile_exec(
36049            "\
36050def f():
36051    with a():
36052        with b():
36053            x()
36054",
36055        );
36056        let f = find_code(&code, "f").expect("missing f code");
36057        let ops: Vec<_> = f
36058            .instructions
36059            .iter()
36060            .map(|unit| unit.op)
36061            .filter(|op| !matches!(op, Instruction::Cache))
36062            .collect();
36063
36064        assert!(
36065            !ops.windows(6).any(|window| {
36066                matches!(
36067                    window,
36068                    [
36069                        Instruction::Copy { .. },
36070                        Instruction::PopExcept,
36071                        Instruction::Reraise { .. },
36072                        Instruction::Nop,
36073                        Instruction::LoadConst { .. },
36074                        Instruction::LoadConst { .. },
36075                    ]
36076                )
36077            }),
36078            "unexpected outer with-exit target NOP for nested with with normal fallthrough, got ops={ops:?}"
36079        );
36080    }
36081
36082    #[test]
36083    fn nested_terminal_with_before_successor_drops_after_block_nop() {
36084        let code = compile_exec(
36085            "\
36086def f(a, b, c):
36087    with a:
36088        with b:
36089            raise c
36090        c()
36091",
36092        );
36093        let f = find_code(&code, "f").expect("missing f code");
36094        let ops: Vec<_> = f
36095            .instructions
36096            .iter()
36097            .map(|unit| unit.op)
36098            .filter(|op| !matches!(op, Instruction::Cache))
36099            .collect();
36100
36101        assert!(
36102            ops.windows(4).any(|window| {
36103                matches!(
36104                    window,
36105                    [
36106                        Instruction::Copy { .. },
36107                        Instruction::PopExcept,
36108                        Instruction::Reraise { .. },
36109                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
36110                    ]
36111                )
36112            }),
36113            "CPython falls through from the terminal inner-with cleanup to the following statement without an after-block NOP, got ops={ops:?}"
36114        );
36115        assert!(
36116            !ops.windows(5).any(|window| {
36117                matches!(
36118                    window,
36119                    [
36120                        Instruction::Copy { .. },
36121                        Instruction::PopExcept,
36122                        Instruction::Reraise { .. },
36123                        Instruction::Nop,
36124                        Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. },
36125                    ]
36126                )
36127            }),
36128            "unexpected inner with after-block NOP before following statement, got ops={ops:?}"
36129        );
36130    }
36131
36132    #[test]
36133    fn try_loop_elif_places_return_before_orelse_tail() {
36134        let code = compile_exec(
36135            "\
36136def f(source, suggest, tb, s):
36137    if source is not None:
36138        try:
36139            tb = tb
36140        except Exception:
36141            suggest = False
36142            tb = None
36143        if tb is not None:
36144            for frame in tb:
36145                s += frame
36146        elif suggest:
36147            s += 'x'
36148    return s
36149",
36150        );
36151        let f = find_code(&code, "f").expect("missing f code");
36152        let ops: Vec<_> = f
36153            .instructions
36154            .iter()
36155            .map(|unit| unit.op)
36156            .filter(|op| !matches!(op, Instruction::Cache))
36157            .collect();
36158
36159        let has_direct_return = ops.windows(8).any(|window| {
36160            matches!(
36161                window,
36162                [
36163                    Instruction::EndFor,
36164                    Instruction::PopIter,
36165                    Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
36166                    Instruction::ReturnValue,
36167                    Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
36168                    Instruction::ToBool,
36169                    Instruction::PopJumpIfFalse { .. },
36170                    Instruction::NotTaken,
36171                ]
36172            )
36173        });
36174        let has_nop_anchored_return = ops.windows(9).any(|window| {
36175            matches!(
36176                window,
36177                [
36178                    Instruction::EndFor,
36179                    Instruction::PopIter,
36180                    Instruction::Nop,
36181                    Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
36182                    Instruction::ReturnValue,
36183                    Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
36184                    Instruction::ToBool,
36185                    Instruction::PopJumpIfFalse { .. },
36186                    Instruction::NotTaken,
36187                ]
36188            )
36189        });
36190        assert!(
36191            has_direct_return || has_nop_anchored_return,
36192            "expected CPython-style duplicated return between loop exit and elif tail, got ops={ops:?}"
36193        );
36194    }
36195
36196    #[test]
36197    fn constant_false_while_else_deopts_post_else_borrows() {
36198        let code = compile_exec(
36199            "\
36200def f(self):
36201    x = 0
36202    while 0:
36203        x = 1
36204    else:
36205        x = 2
36206    self.assertEqual(x, 2)
36207",
36208        );
36209        let f = find_code(&code, "f").expect("missing f code");
36210        let ops: Vec<_> = f
36211            .instructions
36212            .iter()
36213            .map(|unit| unit.op)
36214            .filter(|op| !matches!(op, Instruction::Cache))
36215            .collect();
36216        let assert_idx = ops
36217            .iter()
36218            .position(|op| matches!(op, Instruction::LoadAttr { .. }))
36219            .expect("missing assertEqual call");
36220        let window = &ops[assert_idx.saturating_sub(1)..(assert_idx + 3).min(ops.len())];
36221        assert!(
36222            matches!(
36223                window,
36224                [
36225                    Instruction::LoadFast { .. },
36226                    Instruction::LoadAttr { .. },
36227                    Instruction::LoadFast { .. },
36228                    ..
36229                ]
36230            ),
36231            "expected post-else assertEqual call to use plain LOAD_FAST, got ops={window:?}"
36232        );
36233    }
36234
36235    #[test]
36236    fn single_unpack_assignment_disables_constant_collection_folding() {
36237        let code = compile_exec("a, b, c = 1, 2, 3\n");
36238
36239        assert!(
36240            !code.instructions.iter().any(|unit| {
36241                matches!(unit.op, Instruction::UnpackSequence { .. })
36242                    || matches!(unit.op, Instruction::LoadConst { .. })
36243                        && matches!(
36244                            code.constants.get(usize::from(u8::from(unit.arg))),
36245                            Some(ConstantData::Tuple { .. })
36246                        )
36247            }),
36248            "single unpack assignment should keep builder form for later lowering, got ops={:?}",
36249            code.instructions
36250                .iter()
36251                .map(|unit| unit.op)
36252                .collect::<Vec<_>>()
36253        );
36254        assert!(
36255            code.instructions
36256                .iter()
36257                .filter(|unit| matches!(unit.op, Instruction::LoadSmallInt { .. }))
36258                .count()
36259                >= 3,
36260            "expected individual constant loads before unpack-target stores, got ops={:?}",
36261            code.instructions
36262                .iter()
36263                .map(|unit| unit.op)
36264                .collect::<Vec<_>>()
36265        );
36266    }
36267
36268    #[test]
36269    fn four_item_unpack_assignment_folds_tuple_constant_like_cpython() {
36270        let code = compile_exec("a, b, c, d = 1, 2, 3, 4\n");
36271
36272        assert!(
36273            code.instructions.iter().any(|unit| {
36274                matches!(unit.op, Instruction::LoadConst { .. })
36275                    && matches!(
36276                        code.constants.get(usize::from(u8::from(unit.arg))),
36277                        Some(ConstantData::Tuple { elements }) if elements.len() == 4
36278                    )
36279            }),
36280            "four-item unpack assignment should fold BUILD_TUPLE before UNPACK_SEQUENCE like CPython, got ops={:?}",
36281            code.instructions
36282                .iter()
36283                .map(|unit| unit.op)
36284                .collect::<Vec<_>>()
36285        );
36286        assert!(
36287            code.instructions
36288                .iter()
36289                .any(|unit| matches!(unit.op, Instruction::UnpackSequence { .. })),
36290            "four-item unpack assignment should keep UNPACK_SEQUENCE after tuple folding, got ops={:?}",
36291            code.instructions
36292                .iter()
36293                .map(|unit| unit.op)
36294                .collect::<Vec<_>>()
36295        );
36296    }
36297
36298    #[test]
36299    fn chained_unpack_assignment_keeps_constant_collection_folding() {
36300        let code = compile_exec("(a, b) = c = d = (1, 2)\n");
36301
36302        assert!(
36303            code.instructions
36304                .iter()
36305                .any(|unit| matches!(unit.op, Instruction::LoadConst { .. })),
36306            "chained unpack assignment should keep tuple constant, got ops={:?}",
36307            code.instructions
36308                .iter()
36309                .map(|unit| unit.op)
36310                .collect::<Vec<_>>()
36311        );
36312        assert!(
36313            code.instructions
36314                .iter()
36315                .any(|unit| matches!(unit.op, Instruction::UnpackSequence { .. })),
36316            "chained unpack assignment should still unpack the copied tuple, got ops={:?}",
36317            code.instructions
36318                .iter()
36319                .map(|unit| unit.op)
36320                .collect::<Vec<_>>()
36321        );
36322    }
36323
36324    #[test]
36325    fn constant_true_assert_skips_message_nested_scope() {
36326        let code = compile_exec("assert 1, (lambda x: x + 1)\n");
36327
36328        assert_eq!(
36329            code.constants
36330                .iter()
36331                .filter(|constant| matches!(constant, ConstantData::Code { .. }))
36332                .count(),
36333            0,
36334            "constant-true assert should not compile the skipped message lambda"
36335        );
36336        assert!(
36337            !code
36338                .instructions
36339                .iter()
36340                .any(|unit| matches!(unit.op, Instruction::RaiseVarargs { .. })),
36341            "constant-true assert should be elided, got ops={:?}",
36342            code.instructions
36343                .iter()
36344                .map(|unit| unit.op)
36345                .collect::<Vec<_>>()
36346        );
36347    }
36348
36349    #[test]
36350    fn constant_false_assert_uses_direct_raise_shape() {
36351        let code = compile_exec("assert 0, (lambda x: x + 1)\n");
36352
36353        assert!(
36354            !code.instructions.iter().any(|unit| {
36355                matches!(
36356                    unit.op,
36357                    Instruction::ToBool
36358                        | Instruction::PopJumpIfTrue { .. }
36359                        | Instruction::PopJumpIfFalse { .. }
36360                )
36361            }),
36362            "constant-false assert should use direct raise shape, got ops={:?}",
36363            code.instructions
36364                .iter()
36365                .map(|unit| unit.op)
36366                .collect::<Vec<_>>()
36367        );
36368        assert!(
36369            code.instructions
36370                .iter()
36371                .any(|unit| matches!(unit.op, Instruction::RaiseVarargs { .. })),
36372            "constant-false assert should still raise, got ops={:?}",
36373            code.instructions
36374                .iter()
36375                .map(|unit| unit.op)
36376                .collect::<Vec<_>>()
36377        );
36378        assert_eq!(
36379            code.constants
36380                .iter()
36381                .filter(|constant| matches!(constant, ConstantData::Code { .. }))
36382                .count(),
36383            1,
36384            "constant-false assert should still compile the message lambda"
36385        );
36386    }
36387
36388    #[test]
36389    fn constant_unary_positive_and_invert_fold() {
36390        let code = compile_exec("x = +1\nx = ~1\n");
36391
36392        assert!(
36393            !code.instructions.iter().any(|unit| {
36394                matches!(
36395                    unit.op,
36396                    Instruction::CallIntrinsic1 { .. } | Instruction::UnaryInvert
36397                )
36398            }),
36399            "constant unary ops should fold away, got ops={:?}",
36400            code.instructions
36401                .iter()
36402                .map(|unit| unit.op)
36403                .collect::<Vec<_>>()
36404        );
36405    }
36406
36407    #[test]
36408    fn bool_invert_is_not_const_folded() {
36409        let code = compile_exec("x = ~True\n");
36410
36411        assert!(
36412            code.instructions
36413                .iter()
36414                .any(|unit| matches!(unit.op, Instruction::UnaryInvert)),
36415            "~bool should remain unfurled to match CPython, got ops={:?}",
36416            code.instructions
36417                .iter()
36418                .map(|unit| unit.op)
36419                .collect::<Vec<_>>()
36420        );
36421    }
36422
36423    #[test]
36424    fn optimized_assert_preserves_nested_scope_order() {
36425        compile_exec_optimized(
36426            "\
36427class S:
36428    def f(self, sequence):
36429        _formats = [self._types_mapping[type(item)] for item in sequence]
36430        _list_len = len(_formats)
36431        assert sum(len(fmt) <= 8 for fmt in _formats) == _list_len
36432        _recreation_codes = [self._extract_recreation_code(item) for item in sequence]
36433",
36434        );
36435    }
36436
36437    #[test]
36438    fn optimized_assert_with_nested_scope_in_first_iter() {
36439        // First iterator of a comprehension is evaluated in the enclosing
36440        // scope, so nested scopes inside it (the generator here) must also
36441        // be consumed when the assert is optimized away.
36442        compile_exec_optimized(
36443            "\
36444def f(items):
36445    assert [x for x in (y for y in items)]
36446    return [x for x in items]
36447",
36448        );
36449    }
36450
36451    #[test]
36452    fn optimized_assert_with_lambda_defaults() {
36453        // Lambda default values are evaluated in the enclosing scope,
36454        // so nested scopes inside defaults must be consumed.
36455        compile_exec_optimized(
36456            "\
36457def f(items):
36458    assert (lambda x=[i for i in items]: x)()
36459    return [x for x in items]
36460",
36461        );
36462    }
36463
36464    #[test]
36465    fn try_else_nested_scopes_keep_subtable_cursor_aligned() {
36466        let code = compile_exec(
36467            "\
36468try:
36469    import missing_mod
36470except ImportError:
36471    def fallback():
36472        return 0
36473else:
36474    def impl():
36475        return reversed('abc')
36476",
36477        );
36478
36479        assert!(
36480            find_code(&code, "fallback").is_some(),
36481            "missing fallback code"
36482        );
36483        let impl_code = find_code(&code, "impl").expect("missing impl code");
36484        assert!(
36485            impl_code.instructions.iter().any(|unit| {
36486                matches!(
36487                    unit.op,
36488                    Instruction::LoadGlobal { .. } | Instruction::LoadName { .. }
36489                )
36490            }),
36491            "expected impl to compile global name access, got ops={:?}",
36492            impl_code
36493                .instructions
36494                .iter()
36495                .map(|unit| unit.op)
36496                .collect::<Vec<_>>()
36497        );
36498    }
36499
36500    #[test]
36501    fn nested_try_else_multi_resume_join_keeps_strong_load_fast_tail() {
36502        let code = compile_exec(
36503            "\
36504def f(msg):
36505    s = ''
36506    try:
36507        import a
36508    except Exception:
36509        suggest = False
36510        tb = None
36511    else:
36512        try:
36513            suggest = not t()
36514            tb = g(msg)
36515        except Exception:
36516            suggest = False
36517            tb = None
36518    if tb is not None:
36519        for frame in tb:
36520            s += frame
36521    elif suggest:
36522        s += 'y'
36523    return s
36524",
36525        );
36526        let f = find_code(&code, "f").expect("missing f code");
36527        let ops: Vec<_> = f
36528            .instructions
36529            .iter()
36530            .map(|unit| unit.op)
36531            .filter(|op| !matches!(op, Instruction::Cache))
36532            .collect();
36533
36534        let tail_start = ops
36535            .iter()
36536            .position(|op| matches!(op, Instruction::PopJumpIfNone { .. }))
36537            .expect("missing tail POP_JUMP_IF_NONE")
36538            .saturating_sub(1);
36539        let handler_start = ops
36540            .iter()
36541            .position(|op| matches!(op, Instruction::PushExcInfo))
36542            .expect("missing handler entry");
36543        let tail = &ops[tail_start..handler_start];
36544
36545        assert!(
36546            !tail.iter().any(|op| {
36547                matches!(
36548                    op,
36549                    Instruction::LoadFastBorrow { .. }
36550                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
36551                )
36552            }),
36553            "expected nested try/except else-resume tail to keep strong LOAD_FAST ops, got tail={tail:?}"
36554        );
36555
36556        assert!(
36557            tail.iter()
36558                .any(|op| matches!(op, Instruction::LoadFastLoadFast { .. })),
36559            "expected loop body to keep LOAD_FAST_LOAD_FAST in the resume tail, got tail={tail:?}"
36560        );
36561    }
36562
36563    #[test]
36564    fn protected_conditional_tail_keeps_strong_load_fast() {
36565        let code = compile_exec(
36566            "\
36567def f(m, class_name, category, warning_base):
36568    try:
36569        cat = getattr(m, class_name)
36570    except AttributeError:
36571        raise ValueError(category)
36572    if not issubclass(cat, warning_base):
36573        raise TypeError(category)
36574    return cat
36575",
36576        );
36577        let f = find_code(&code, "f").expect("missing f code");
36578        let ops: Vec<_> = f
36579            .instructions
36580            .iter()
36581            .map(|unit| unit.op)
36582            .filter(|op| !matches!(op, Instruction::Cache))
36583            .collect();
36584
36585        let tail_start = ops
36586            .iter()
36587            .position(|op| matches!(op, Instruction::StoreFast { .. }))
36588            .expect("missing STORE_FAST cat");
36589        let handler_start = ops
36590            .iter()
36591            .position(|op| matches!(op, Instruction::PushExcInfo))
36592            .expect("missing handler entry");
36593        let tail = &ops[tail_start + 1..handler_start];
36594
36595        assert!(
36596            !tail.iter().any(|op| {
36597                matches!(
36598                    op,
36599                    Instruction::LoadFastBorrow { .. }
36600                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
36601                )
36602            }),
36603            "expected protected conditional tail to keep strong LOAD_FAST ops, got tail={tail:?}"
36604        );
36605
36606        assert!(
36607            tail.iter()
36608                .any(|op| matches!(op, Instruction::LoadFastLoadFast { .. })),
36609            "expected protected tail to keep LOAD_FAST_LOAD_FAST for issubclass args, got tail={tail:?}"
36610        );
36611    }
36612
36613    #[test]
36614    fn nonresuming_protected_conditional_tail_keeps_strong_load_fast() {
36615        let code = compile_exec(
36616            "\
36617def f(href, parse='xml'):
36618    try:
36619        data = XINCLUDE[href]
36620    except KeyError:
36621        raise OSError('resource not found')
36622    if parse == 'xml':
36623        data = ET.XML(data)
36624    return data
36625",
36626        );
36627        let f = find_code(&code, "f").expect("missing f code");
36628        let ops: Vec<_> = f
36629            .instructions
36630            .iter()
36631            .map(|unit| unit.op)
36632            .filter(|op| !matches!(op, Instruction::Cache))
36633            .collect();
36634
36635        let tail_start = ops
36636            .iter()
36637            .position(|op| matches!(op, Instruction::StoreFast { .. }))
36638            .expect("missing protected STORE_FAST data");
36639        let handler_start = ops
36640            .iter()
36641            .position(|op| matches!(op, Instruction::PushExcInfo))
36642            .expect("missing handler entry");
36643        let tail = &ops[tail_start + 1..handler_start];
36644
36645        assert!(
36646            !tail.iter().any(|op| {
36647                matches!(
36648                    op,
36649                    Instruction::LoadFastBorrow { .. }
36650                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
36651                )
36652            }),
36653            "expected non-resuming protected conditional tail to keep strong LOAD_FAST ops, got tail={tail:?}"
36654        );
36655    }
36656
36657    #[test]
36658    fn optional_nonresuming_protected_tail_keeps_borrow() {
36659        let code = compile_exec(
36660            "\
36661def f(b):
36662    if type(b) is not bytes:
36663        try:
36664            b = bytes(memoryview(b))
36665        except TypeError:
36666            raise TypeError(f'bad {type(b).__name__}') from None
36667    if b:
36668        sink(b)
36669    return len(b)
36670",
36671        );
36672        let f = find_code(&code, "f").expect("missing f code");
36673        let instructions: Vec<_> = f
36674            .instructions
36675            .iter()
36676            .filter(|unit| !matches!(unit.op, Instruction::Cache))
36677            .collect();
36678        let b_index = f
36679            .varnames
36680            .iter()
36681            .position(|name| name.as_str() == "b")
36682            .expect("missing b varname");
36683        let store_b = instructions
36684            .iter()
36685            .position(|unit| match unit.op {
36686                Instruction::StoreFast { var_num } => {
36687                    usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg))))) == b_index
36688                }
36689                _ => false,
36690            })
36691            .expect("missing protected STORE_FAST b");
36692        let handler_start = instructions
36693            .iter()
36694            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
36695            .expect("missing handler entry");
36696        let tail = &instructions[store_b + 1..handler_start];
36697
36698        assert!(
36699            tail.iter()
36700                .filter(|unit| match unit.op {
36701                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
36702                        usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))))
36703                            == b_index
36704                    }
36705                    _ => false,
36706                })
36707                .all(|unit| matches!(unit.op, Instruction::LoadFastBorrow { .. })),
36708            "optional protected tail should keep CPython-style borrowed b loads, got tail={tail:?}"
36709        );
36710    }
36711
36712    #[test]
36713    fn handled_except_conditional_tail_keeps_borrow() {
36714        let code = compile_exec(
36715            "\
36716def f(self):
36717    try:
36718        if self.active:
36719            self.step()
36720        if self.waiter is not None and self.pending is None:
36721            self.waiter.set_result(None)
36722    except ConnectionResetError as exc:
36723        self.close(exc)
36724    except OSError as exc:
36725        self.fail(exc, 'x')
36726",
36727        );
36728        let f = find_code(&code, "f").expect("missing f code");
36729        let instructions: Vec<_> = f
36730            .instructions
36731            .iter()
36732            .filter(|unit| !matches!(unit.op, Instruction::Cache))
36733            .collect();
36734        let waiter_idx = instructions
36735            .iter()
36736            .position(|unit| match unit.op {
36737                Instruction::LoadAttr { namei } => {
36738                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
36739                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "waiter"
36740                }
36741                _ => false,
36742            })
36743            .expect("missing waiter LOAD_ATTR");
36744        let handler_start = instructions
36745            .iter()
36746            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
36747            .expect("missing handler entry");
36748        let tail = &instructions[waiter_idx.saturating_sub(1)..handler_start];
36749
36750        assert!(
36751            tail.iter().any(|unit| {
36752                matches!(
36753                    unit.op,
36754                    Instruction::LoadFastBorrow { .. }
36755                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
36756                )
36757            }),
36758            "handled-except conditional tail should keep borrowed loads, got tail={tail:?}"
36759        );
36760        assert!(
36761            !tail
36762                .iter()
36763                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
36764            "handled-except conditional tail should not force strong LOAD_FAST, got tail={tail:?}"
36765        );
36766    }
36767
36768    #[test]
36769    fn handled_except_else_tail_keeps_borrow() {
36770        let code = compile_exec(
36771            "\
36772def f(self, fut=None):
36773    try:
36774        if self.closed:
36775            return
36776        item = self.queue.popleft()
36777        self.size -= len(item)
36778        if self.addr is not None:
36779            self.future = self.loop.send(self.sock, item)
36780        else:
36781            self.future = self.loop.sendto(self.sock, item, addr=item)
36782    except OSError as exc:
36783        self.protocol.error_received(exc)
36784    except Exception as exc:
36785        self.fatal(exc, 'x')
36786    else:
36787        self.future.add_done_callback(self.loop_writing)
36788        self.resume()
36789",
36790        );
36791        let f = find_code(&code, "f").expect("missing f code");
36792        let instructions: Vec<_> = f
36793            .instructions
36794            .iter()
36795            .filter(|unit| !matches!(unit.op, Instruction::Cache))
36796            .collect();
36797        let done_callback_idx = instructions
36798            .iter()
36799            .position(|unit| match unit.op {
36800                Instruction::LoadAttr { namei } => {
36801                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
36802                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
36803                        == "add_done_callback"
36804                }
36805                _ => false,
36806            })
36807            .expect("missing add_done_callback LOAD_ATTR");
36808        let handler_start = instructions
36809            .iter()
36810            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
36811            .expect("missing handler entry");
36812        let tail = &instructions[done_callback_idx.saturating_sub(3)..handler_start];
36813
36814        assert!(
36815            tail.iter().any(|unit| {
36816                matches!(
36817                    unit.op,
36818                    Instruction::LoadFastBorrow { .. }
36819                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
36820                )
36821            }),
36822            "handled-except else tail should keep borrowed loads, got tail={tail:?}"
36823        );
36824        assert!(
36825            !tail
36826                .iter()
36827                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
36828            "handled-except else tail should not force strong LOAD_FAST, got tail={tail:?}"
36829        );
36830    }
36831
36832    #[test]
36833    fn reraising_handler_with_handled_returns_keeps_borrow() {
36834        let code = compile_exec(
36835            "\
36836def f(self, fut=None):
36837    try:
36838        if fut is not None:
36839            fut.result()
36840        if self.future is not fut:
36841            return
36842        fut = self.reader.recv(self.sock, 4096)
36843    except CancelledError:
36844        return
36845    except (SystemExit, KeyboardInterrupt):
36846        raise
36847    except BaseException as exc:
36848        self.handle({'exception': exc, 'loop': self})
36849    else:
36850        self.future = fut
36851        fut.add_done_callback(self.loop_reading)
36852",
36853        );
36854        let f = find_code(&code, "f").expect("missing f code");
36855        let instructions: Vec<_> = f
36856            .instructions
36857            .iter()
36858            .filter(|unit| !matches!(unit.op, Instruction::Cache))
36859            .collect();
36860        let recv_idx = instructions
36861            .iter()
36862            .position(|unit| match unit.op {
36863                Instruction::LoadAttr { namei } => {
36864                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
36865                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "recv"
36866                }
36867                _ => false,
36868            })
36869            .expect("missing recv LOAD_ATTR");
36870        let done_callback_idx = instructions
36871            .iter()
36872            .position(|unit| match unit.op {
36873                Instruction::LoadAttr { namei } => {
36874                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
36875                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
36876                        == "add_done_callback"
36877                }
36878                _ => false,
36879            })
36880            .expect("missing add_done_callback LOAD_ATTR");
36881        let handler_start = instructions
36882            .iter()
36883            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
36884            .expect("missing handler entry");
36885        let tail =
36886            &instructions[recv_idx.saturating_sub(3)..handler_start.min(done_callback_idx + 3)];
36887
36888        assert!(
36889            tail.iter().any(|unit| {
36890                matches!(
36891                    unit.op,
36892                    Instruction::LoadFastBorrow { .. }
36893                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
36894                )
36895            }),
36896            "handler chain with handled returns should keep borrowed warm/else loads, got tail={tail:?}"
36897        );
36898        assert!(
36899            !tail.iter().any(|unit| {
36900                matches!(
36901                    unit.op,
36902                    Instruction::LoadFast { .. } | Instruction::LoadFastLoadFast { .. }
36903                )
36904            }),
36905            "handler chain with handled returns should not force strong warm/else loads, got tail={tail:?}"
36906        );
36907    }
36908
36909    #[test]
36910    fn with_protected_conditional_tail_without_exception_match_keeps_borrow() {
36911        let code = compile_exec(
36912            "\
36913def f(self, cm, p, platform):
36914    with cm:
36915        if p.returncode != 0:
36916            if platform.machine() == 'x86_64':
36917                p.check_returncode()
36918            else:
36919                self.skipTest(f'could not compile indirect function: {p}')
36920        done()
36921",
36922        );
36923        let f = find_code(&code, "f").expect("missing f code");
36924        let instructions: Vec<_> = f
36925            .instructions
36926            .iter()
36927            .filter(|unit| !matches!(unit.op, Instruction::Cache))
36928            .collect();
36929
36930        let attr_load_uses_borrow = |name: &str| {
36931            let attr_idx = instructions
36932                .iter()
36933                .position(|unit| match unit.op {
36934                    Instruction::LoadAttr { namei } => {
36935                        let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
36936                        f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == name
36937                    }
36938                    _ => false,
36939                })
36940                .unwrap_or_else(|| panic!("missing {name} attr load"));
36941            matches!(
36942                instructions
36943                    .get(attr_idx.saturating_sub(1))
36944                    .map(|unit| unit.op),
36945                Some(Instruction::LoadFastBorrow { .. })
36946            )
36947        };
36948
36949        assert!(
36950            attr_load_uses_borrow("check_returncode"),
36951            "plain with-protected conditional tail should keep borrowed p load, got instructions={instructions:?}"
36952        );
36953        assert!(
36954            attr_load_uses_borrow("skipTest"),
36955            "plain with-protected conditional tail should keep borrowed self load, got instructions={instructions:?}"
36956        );
36957    }
36958
36959    #[test]
36960    fn listcomp_cleanup_predecessor_does_not_deopt_following_conditional_tail() {
36961        let code = compile_exec(
36962            "\
36963def f(self, compile_snippet):
36964    sizes = [compile_snippet(i).co_stacksize for i in range(2, 5)]
36965    if len(set(sizes)) != 1:
36966        import dis, io
36967        out = io.StringIO()
36968        dis.dis(compile_snippet(1), file=out)
36969        self.fail('%s\\n%s' % (sizes, out.getvalue()))
36970",
36971        );
36972        let f = find_code(&code, "f").expect("missing f code");
36973
36974        let has_strong_load = |name: &str| {
36975            f.instructions.iter().any(|unit| match unit.op {
36976                Instruction::LoadFast { var_num } => {
36977                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
36978                    f.varnames[usize::from(var_num.get(arg))] == name
36979                }
36980                _ => false,
36981            })
36982        };
36983        let has_borrow_load = |name: &str| {
36984            f.instructions.iter().any(|unit| match unit.op {
36985                Instruction::LoadFastBorrow { var_num } => {
36986                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
36987                    f.varnames[usize::from(var_num.get(arg))] == name
36988                }
36989                _ => false,
36990            })
36991        };
36992
36993        for name in ["sizes", "io", "dis", "compile_snippet", "out", "self"] {
36994            assert!(
36995                has_borrow_load(name),
36996                "expected listcomp-following conditional tail to borrow {name}, got instructions={:?}",
36997                f.instructions
36998            );
36999        }
37000        for name in ["sizes", "io", "dis", "compile_snippet", "out", "self"] {
37001            assert!(
37002                !has_strong_load(name),
37003                "listcomp cleanup predecessor should not force strong LOAD_FAST for {name}, got instructions={:?}",
37004                f.instructions
37005            );
37006        }
37007    }
37008
37009    #[test]
37010    fn handler_resume_loop_conditional_tail_keeps_strong_load_fast() {
37011        let code = compile_exec(
37012            "\
37013def f(self):
37014    is_utf8 = (self.ENCODING == 'utf-8')
37015    encode_errors = 'surrogateescape' if is_utf8 else 'strict'
37016    strings = list(self.BYTES_STRINGS)
37017    for text in self.STRINGS:
37018        try:
37019            encoded = text.encode(self.ENCODING, encode_errors)
37020            if encoded not in strings:
37021                strings.append(encoded)
37022        except UnicodeEncodeError:
37023            encoded = None
37024        if is_utf8:
37025            encoded2 = text.encode(self.ENCODING, 'surrogatepass')
37026            if encoded2 != encoded:
37027                strings.append(encoded2)
37028    for encoded in strings:
37029        self.consume(encoded)
37030",
37031        );
37032        let f = find_code(&code, "f").expect("missing f code");
37033
37034        let has_strong_load = |name: &str| {
37035            f.instructions.iter().any(|unit| match unit.op {
37036                Instruction::LoadFast { var_num } => {
37037                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37038                    f.varnames[usize::from(var_num.get(arg))] == name
37039                }
37040                _ => false,
37041            })
37042        };
37043        let has_borrow_load = |name: &str| {
37044            f.instructions.iter().any(|unit| match unit.op {
37045                Instruction::LoadFastBorrow { var_num } => {
37046                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37047                    f.varnames[usize::from(var_num.get(arg))] == name
37048                }
37049                _ => false,
37050            })
37051        };
37052
37053        for name in ["is_utf8", "text", "self", "strings", "encoded2"] {
37054            assert!(
37055                has_strong_load(name),
37056                "expected handler-resume loop tail to use strong LOAD_FAST for {name}, got instructions={:?}",
37057                f.instructions
37058            );
37059        }
37060        assert!(
37061            f.instructions.iter().any(|unit| match unit.op {
37062                Instruction::LoadFastLoadFast { var_nums } => {
37063                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37064                    let (left, right) = var_nums.get(arg).indexes();
37065                    f.varnames[usize::from(left)] == "encoded2"
37066                        && f.varnames[usize::from(right)] == "encoded"
37067                }
37068                _ => false,
37069            }),
37070            "expected encoded2/encoded comparison to use strong LOAD_FAST_LOAD_FAST, got instructions={:?}",
37071            f.instructions
37072        );
37073        assert!(
37074            !f.instructions.iter().any(|unit| match unit.op {
37075                Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
37076                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37077                    let (left, right) = var_nums.get(arg).indexes();
37078                    f.varnames[usize::from(left)] == "encoded2"
37079                        && f.varnames[usize::from(right)] == "encoded"
37080                }
37081                _ => false,
37082            }),
37083            "handler-resume loop tail should not borrow encoded2/encoded comparison, got instructions={:?}",
37084            f.instructions
37085        );
37086        assert!(
37087            has_borrow_load("strings"),
37088            "expected later loop/list uses outside the deopt tail to keep borrowing strings"
37089        );
37090    }
37091
37092    #[test]
37093    fn handler_resume_while_conditional_tail_keeps_borrow_load_fast() {
37094        let code = compile_exec(
37095            "\
37096def f(value):
37097    items = []
37098    while value:
37099        try:
37100            token, value = parse(value)
37101            items.append(token)
37102        except Error:
37103            token, value = recover(value)
37104            items.append(token)
37105        if value and value[0] != ',':
37106            item = items[-1]
37107            token, value = recover(value)
37108            item.extend(token)
37109        if value and value[0] == ',':
37110            items.append(',')
37111            value = value[1:]
37112    return items, value
37113",
37114        );
37115        let f = find_code(&code, "f").expect("missing f code");
37116        let handler_start = f
37117            .instructions
37118            .iter()
37119            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
37120            .expect("missing handler entry");
37121        let normal_region = &f.instructions[..handler_start];
37122        let is_strong_local_load = |unit: &CodeUnit| match unit.op {
37123            Instruction::LoadFast { var_num } => {
37124                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37125                matches!(
37126                    f.varnames[usize::from(var_num.get(arg))].as_str(),
37127                    "value" | "items" | "token" | "item"
37128                )
37129            }
37130            Instruction::LoadFastLoadFast { var_nums } => {
37131                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37132                let (left, right) = var_nums.get(arg).indexes();
37133                let left = f.varnames[usize::from(left)].as_str();
37134                let right = f.varnames[usize::from(right)].as_str();
37135                matches!(left, "value" | "items" | "token" | "item")
37136                    || matches!(right, "value" | "items" | "token" | "item")
37137            }
37138            _ => false,
37139        };
37140
37141        assert!(
37142            !normal_region.iter().any(is_strong_local_load),
37143            "while-loop handler resume tail should keep CPython-style borrowed normal loads, got instructions={:?}",
37144            f.instructions
37145        );
37146    }
37147
37148    #[test]
37149    fn multi_handler_resume_while_tail_keeps_borrow_load_fast() {
37150        let code = compile_exec(
37151            "\
37152def f(value):
37153    items = []
37154    while value and value[0] != ';':
37155        try:
37156            token, value = parse(value)
37157            items.append(token)
37158        except Error:
37159            leader = None
37160            if value[0] in leaders:
37161                leader, value = recover(value)
37162                if not value or value[0] in ',;':
37163                    items.append(leader)
37164                else:
37165                    token, value = recover_invalid(value)
37166                    if leader is not None:
37167                        token[:0] = [leader]
37168                    items.append(token)
37169            elif value[0] == ',':
37170                items.append(',')
37171            else:
37172                token, value = recover_invalid(value)
37173                if leader is not None:
37174                    token[:0] = [leader]
37175                items.append(token)
37176        if value and value[0] not in ',;':
37177            item = items[-1]
37178            token, value = recover_invalid(value)
37179            item.extend(token)
37180        if value and value[0] == ',':
37181            items.append(',')
37182            value = value[1:]
37183    return items, value
37184",
37185        );
37186        let f = find_code(&code, "f").expect("missing f code");
37187        let handler_start = f
37188            .instructions
37189            .iter()
37190            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
37191            .expect("missing handler entry");
37192        let normal_region = &f.instructions[..handler_start];
37193
37194        assert!(
37195            normal_region.iter().any(|unit| {
37196                matches!(
37197                    unit.op,
37198                    Instruction::LoadFastBorrow { .. }
37199                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
37200                )
37201            }),
37202            "expected multi-handler while tail to keep borrowed normal loads, got instructions={:?}",
37203            f.instructions
37204        );
37205        assert!(
37206            !normal_region
37207                .iter()
37208                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
37209            "multi-handler while tail should not be deoptimized to strong LOAD_FAST, got instructions={:?}",
37210            f.instructions
37211        );
37212    }
37213
37214    #[test]
37215    fn multi_handler_resume_before_with_keeps_with_body_borrows() {
37216        let code = compile_exec(
37217            "\
37218def f(self, input, cm):
37219    try:
37220        self.stdin.flush()
37221    except BrokenPipeError:
37222        pass
37223    except ValueError:
37224        if not self.stdin.closed:
37225            raise
37226    if not input:
37227        self.stdin.close()
37228    with cm() as selector:
37229        if self.stdin and self._input:
37230            selector.register(self.stdin, EVENT_WRITE)
37231        while selector.get_map():
37232            ready = selector.select()
37233            for key in ready:
37234                self._fileobj2output[key].append(key)
37235    return self.stdin
37236",
37237        );
37238        let f = find_code(&code, "f").expect("missing f code");
37239        let handler_start = f
37240            .instructions
37241            .iter()
37242            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
37243            .expect("missing handler entry");
37244        let warm_path = &f.instructions[..handler_start];
37245        let self_load_is = |unit: &CodeUnit, borrowed: bool| match unit.op {
37246            Instruction::LoadFast { var_num } if !borrowed => {
37247                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37248                f.varnames[usize::from(var_num.get(arg))] == "self"
37249            }
37250            Instruction::LoadFastBorrow { var_num } if borrowed => {
37251                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37252                f.varnames[usize::from(var_num.get(arg))] == "self"
37253            }
37254            _ => false,
37255        };
37256
37257        assert!(
37258            warm_path.iter().any(|unit| self_load_is(unit, true)),
37259            "expected multi-handler resume before with-body to keep borrowed self loads, got warm_path={warm_path:?}"
37260        );
37261        assert!(
37262            !warm_path.iter().any(|unit| self_load_is(unit, false)),
37263            "multi-handler resume must not deopt with-body self loads to strong LOAD_FAST, got warm_path={warm_path:?}"
37264        );
37265    }
37266
37267    #[test]
37268    fn suppressing_with_and_typed_except_resume_loop_method_tail_keeps_strong_load_fast() {
37269        let code = compile_exec(
37270            "\
37271def f(proc, text):
37272    try:
37273        with proc.stdin as pipe:
37274            try:
37275                pipe.write(text)
37276            except KeyboardInterrupt:
37277                pass
37278    except OSError:
37279        pass
37280    while True:
37281        try:
37282            proc.wait()
37283            break
37284        except KeyboardInterrupt:
37285            pass
37286",
37287        );
37288        let f = find_code(&code, "f").expect("missing f code");
37289        let load_name = |unit: &CodeUnit| match unit.op {
37290            Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
37291                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37292                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
37293            }
37294            _ => None,
37295        };
37296
37297        let wait_receiver_is_strong = f.instructions.windows(2).any(|window| {
37298            matches!(window[0].op, Instruction::LoadFast { .. })
37299                && load_name(&window[0]) == Some("proc")
37300                && matches!(window[1].op, Instruction::LoadAttr { .. })
37301        });
37302        assert!(
37303            wait_receiver_is_strong,
37304            "CPython keeps proc.wait() receiver strong after suppressing-with and typed-except loop resumes, got instructions={:?}",
37305            f.instructions
37306        );
37307    }
37308
37309    #[test]
37310    fn handler_break_join_loop_body_and_tail_keep_strong_load_fast() {
37311        let code = compile_exec(
37312            "\
37313def f(function, stem):
37314    result = []
37315    state = 0
37316    while True:
37317        try:
37318            next = function(stem, state)
37319        except Exception:
37320            break
37321        if not isinstance(next, str):
37322            break
37323        result.append(next)
37324        state += 1
37325    result.sort()
37326    return result
37327",
37328        );
37329        let f = find_code(&code, "f").expect("missing f code");
37330        let load_name = |unit: &CodeUnit| match unit.op {
37331            Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
37332                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37333                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
37334            }
37335            _ => None,
37336        };
37337        let instructions: Vec<_> = f
37338            .instructions
37339            .iter()
37340            .filter(|unit| !matches!(unit.op, Instruction::Cache))
37341            .collect();
37342
37343        let body_method_call_uses_strong_loads = instructions.windows(4).any(|window| {
37344            matches!(window[0].op, Instruction::LoadFast { .. })
37345                && load_name(window[0]) == Some("result")
37346                && matches!(window[1].op, Instruction::LoadAttr { .. })
37347                && matches!(window[2].op, Instruction::LoadFast { .. })
37348                && load_name(window[2]) == Some("next")
37349                && matches!(window[3].op, Instruction::Call { .. })
37350        });
37351        assert!(
37352            body_method_call_uses_strong_loads,
37353            "CPython keeps result.append(next) loads strong after an except-break join, got instructions={:?}",
37354            f.instructions
37355        );
37356
37357        let body_update_uses_strong_load = instructions.windows(3).any(|window| {
37358            matches!(window[0].op, Instruction::LoadFast { .. })
37359                && load_name(window[0]) == Some("state")
37360                && matches!(window[1].op, Instruction::LoadSmallInt { .. })
37361                && matches!(window[2].op, Instruction::BinaryOp { .. })
37362        });
37363        assert!(
37364            body_update_uses_strong_load,
37365            "CPython keeps state += 1 load strong after an except-break join, got instructions={:?}",
37366            f.instructions
37367        );
37368
37369        let tail_method_call_uses_strong_load = instructions.windows(2).any(|window| {
37370            matches!(window[0].op, Instruction::LoadFast { .. })
37371                && load_name(window[0]) == Some("result")
37372                && matches!(window[1].op, Instruction::LoadAttr { .. })
37373        });
37374        assert!(
37375            tail_method_call_uses_strong_load,
37376            "CPython keeps result.sort() receiver strong after an except-break join, got instructions={:?}",
37377            f.instructions
37378        );
37379
37380        let return_uses_strong_load = instructions.windows(2).any(|window| {
37381            matches!(window[0].op, Instruction::LoadFast { .. })
37382                && load_name(window[0]) == Some("result")
37383                && matches!(window[1].op, Instruction::ReturnValue)
37384        });
37385        assert!(
37386            return_uses_strong_load,
37387            "CPython keeps post-loop result return strong after an except-break join, got instructions={:?}",
37388            f.instructions
37389        );
37390    }
37391
37392    #[test]
37393    fn handler_resume_to_loop_header_keeps_loop_header_borrows() {
37394        let code = compile_exec(
37395            "\
37396def f(value, Phrase, get_word, errors, ENDS, DOT):
37397    phrase = Phrase()
37398    try:
37399        token, value = get_word(value)
37400        phrase.append(token)
37401    except errors.HeaderParseError:
37402        phrase.defects.append(errors.InvalidHeaderDefect('bad'))
37403    while value and value[0] not in ENDS:
37404        if value[0] == '.':
37405            phrase.append(DOT)
37406            phrase.defects.append(errors.ObsoleteHeaderDefect('dot'))
37407            value = value[1:]
37408        else:
37409            token, value = get_word(value)
37410            phrase.append(token)
37411    return phrase, value
37412",
37413        );
37414        let f = find_code(&code, "f").expect("missing f code");
37415        let load_name = |unit: &CodeUnit| match unit.op {
37416            Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
37417                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37418                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
37419            }
37420            _ => None,
37421        };
37422        let instructions: Vec<_> = f
37423            .instructions
37424            .iter()
37425            .filter(|unit| !matches!(unit.op, Instruction::Cache))
37426            .collect();
37427
37428        let loop_header_uses_borrow = instructions.windows(3).any(|window| {
37429            matches!(window[0].op, Instruction::LoadFastBorrow { .. })
37430                && load_name(window[0]) == Some("value")
37431                && matches!(window[1].op, Instruction::ToBool)
37432                && matches!(window[2].op, Instruction::PopJumpIfFalse { .. })
37433        });
37434        assert!(
37435            loop_header_uses_borrow,
37436            "CPython keeps loop-header value test borrowed after a pre-loop handler resumes to that header, got instructions={:?}",
37437            f.instructions
37438        );
37439
37440        let return_pair_uses_borrow = instructions.windows(3).any(|window| {
37441            matches!(
37442                window[0].op,
37443                Instruction::LoadFastBorrowLoadFastBorrow { .. }
37444            ) && matches!(window[1].op, Instruction::BuildTuple { .. })
37445                && matches!(window[2].op, Instruction::ReturnValue)
37446        });
37447        assert!(
37448            return_pair_uses_borrow,
37449            "CPython keeps phrase/value return pair borrowed after a pre-loop handler resumes to the loop header, got instructions={:?}",
37450            f.instructions
37451        );
37452    }
37453
37454    #[test]
37455    fn reraising_except_loop_break_tail_keeps_post_loop_borrows() {
37456        let code = compile_exec(
37457            "\
37458def f(flag=1, count=0):
37459    value = 2
37460    while value:
37461        count += 1
37462        try:
37463            if flag and value == 1:
37464                flag -= 1
37465                break
37466            value -= 1
37467            continue
37468        except:
37469            raise
37470    return count > 2 or value != 1
37471",
37472        );
37473        let f = find_code(&code, "f").expect("missing f code");
37474        let instructions: Vec<_> = f
37475            .instructions
37476            .iter()
37477            .filter(|unit| !matches!(unit.op, Instruction::Cache))
37478            .collect();
37479        let return_idx = instructions
37480            .iter()
37481            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
37482            .expect("missing return");
37483        let tail = &instructions[return_idx.saturating_sub(12)..return_idx];
37484
37485        let load_name = |unit: &CodeUnit| match unit.op {
37486            Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
37487                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37488                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
37489            }
37490            _ => None,
37491        };
37492        for name in ["count", "value"] {
37493            assert!(
37494                tail.iter()
37495                    .any(|unit| matches!(unit.op, Instruction::LoadFastBorrow { .. })
37496                        && load_name(unit) == Some(name)),
37497                "post-loop condition should borrow {name}, got tail={tail:?}"
37498            );
37499            assert!(
37500                !tail
37501                    .iter()
37502                    .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })
37503                        && load_name(unit) == Some(name)),
37504                "post-loop condition should not deopt {name} to LOAD_FAST, got tail={tail:?}"
37505            );
37506        }
37507    }
37508
37509    #[test]
37510    fn try_except_continue_keeps_try_line_nop_before_continue_jump() {
37511        let code = compile_exec(
37512            "\
37513def f(done=False):
37514    while not done:
37515        done = True
37516        try:
37517            continue
37518        except:
37519            done = False
37520    return done
37521",
37522        );
37523        let f = find_code(&code, "f").expect("missing f code");
37524        let instructions: Vec<_> = f
37525            .instructions
37526            .iter()
37527            .filter(|unit| !matches!(unit.op, Instruction::Cache))
37528            .collect();
37529        assert!(
37530            instructions.windows(2).any(|window| matches!(
37531                window,
37532                [
37533                    CodeUnit {
37534                        op: Instruction::Nop,
37535                        ..
37536                    },
37537                    CodeUnit {
37538                        op: Instruction::JumpBackward { .. },
37539                        ..
37540                    }
37541                ]
37542            )),
37543            "try/except continue should keep CPython-style try-line NOP before continue jump, got instructions={instructions:?}"
37544        );
37545    }
37546
37547    #[test]
37548    fn for_else_pass_keeps_line_marker_after_pop_iter() {
37549        let code = compile_exec(
37550            "\
37551def f():
37552    for item in ():
37553        pass
37554    else:
37555        pass
37556    marker = 1
37557    return marker
37558",
37559        );
37560        let f = find_code(&code, "f").expect("missing f code");
37561        let instructions: Vec<_> = f
37562            .instructions
37563            .iter()
37564            .filter(|unit| !matches!(unit.op, Instruction::Cache))
37565            .collect();
37566
37567        assert!(
37568            instructions.windows(2).any(|window| matches!(
37569                window,
37570                [
37571                    CodeUnit {
37572                        op: Instruction::PopIter,
37573                        ..
37574                    },
37575                    CodeUnit {
37576                        op: Instruction::Nop,
37577                        ..
37578                    }
37579                ]
37580            )),
37581            "for-else pass should keep CPython-style else-line NOP after POP_ITER, got instructions={instructions:?}"
37582        );
37583    }
37584
37585    #[test]
37586    fn folded_if_chain_after_previous_chain_keeps_final_elif_line_marker() {
37587        let code = compile_exec(
37588            "\
37589def f():
37590    if 0: pass
37591    elif 0: pass
37592    if 0: pass
37593    elif 0: pass
37594    elif 0: pass
37595    elif 0: pass
37596    else: pass
37597",
37598        );
37599        let f = find_code(&code, "f").expect("missing f code");
37600        let nop_count = f
37601            .instructions
37602            .iter()
37603            .filter(|unit| matches!(unit.op, Instruction::Nop))
37604            .count();
37605
37606        assert_eq!(
37607            nop_count, 6,
37608            "folded if chains should preserve CPython-style line-marker NOPs, got instructions={:?}",
37609            f.instructions
37610        );
37611    }
37612
37613    #[test]
37614    fn handler_resume_before_later_loop_keeps_borrowed_tail_loads() {
37615        let code = compile_exec(
37616            "\
37617def f(msg, category):
37618    s = 'x'
37619    if msg.line is None:
37620        try:
37621            import linecache
37622            line = linecache.getline(msg.filename, msg.lineno)
37623        except Exception:
37624            line = None
37625            linecache = None
37626    else:
37627        line = msg.line
37628    if line:
37629        line = line.strip()
37630        s += '  %s\\n' % line
37631    if msg.source is not None:
37632        try:
37633            import tracemalloc
37634        except Exception:
37635            suggest_tracemalloc = False
37636            tb = None
37637        else:
37638            try:
37639                suggest_tracemalloc = not tracemalloc.is_tracing()
37640                tb = tracemalloc.get_object_traceback(msg.source)
37641            except Exception:
37642                suggest_tracemalloc = False
37643                tb = None
37644        if tb is not None:
37645            for frame in tb:
37646                s += frame.filename
37647        elif suggest_tracemalloc:
37648            s += category
37649    return s
37650",
37651        );
37652        let f = find_code(&code, "f").expect("missing f code");
37653        let ops: Vec<_> = f
37654            .instructions
37655            .iter()
37656            .filter(|unit| !matches!(unit.op, Instruction::Cache))
37657            .collect();
37658        let is_borrow_load = |unit: &&bytecode::CodeUnit, name: &str| match unit.op {
37659            Instruction::LoadFastBorrow { var_num } => {
37660                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37661                f.varnames[usize::from(var_num.get(arg))] == name
37662            }
37663            _ => false,
37664        };
37665
37666        assert!(
37667            ops.windows(5).any(|window| {
37668                is_borrow_load(&window[0], "line")
37669                    && matches!(window[1].op, Instruction::ToBool)
37670                    && matches!(window[2].op, Instruction::PopJumpIfFalse { .. })
37671                    && matches!(window[3].op, Instruction::NotTaken)
37672                    && is_borrow_load(&window[4], "line")
37673            }),
37674            "handler resume before a later independent loop should preserve CPython-style borrowed line loads, got instructions={:?}",
37675            f.instructions
37676        );
37677        assert!(
37678            ops.windows(2)
37679                .filter(|window| {
37680                    is_borrow_load(&window[0], "tracemalloc")
37681                        && matches!(window[1].op, Instruction::LoadAttr { .. })
37682                })
37683                .count()
37684                >= 2,
37685            "independent later loop should not deopt tracemalloc loads before the loop, got instructions={:?}",
37686            f.instructions
37687        );
37688        assert!(
37689            ops.windows(2).any(|window| {
37690                is_borrow_load(&window[0], "s") && matches!(window[1].op, Instruction::ReturnValue)
37691            }),
37692            "final return should keep CPython-style borrowed s load, got instructions={:?}",
37693            f.instructions
37694        );
37695    }
37696
37697    #[test]
37698    fn async_early_return_send_tail_uses_strong_load_fast_after_entry() {
37699        let code = compile_exec(
37700            "\
37701class C:
37702    async def _sock_sendfile_native(self, sock, file, offset, count):
37703        try:
37704            fileno = file.fileno()
37705        except (AttributeError, io.UnsupportedOperation) as err:
37706            raise exceptions.SendfileNotAvailableError('not a regular file')
37707        try:
37708            fsize = os.fstat(fileno).st_size
37709        except OSError:
37710            raise exceptions.SendfileNotAvailableError('not a regular file')
37711        blocksize = count if count else fsize
37712        if not blocksize:
37713            return 0
37714        blocksize = min(blocksize, 0xffff_ffff)
37715        end_pos = min(offset + count, fsize) if count else fsize
37716        offset = min(offset, fsize)
37717        total_sent = 0
37718        try:
37719            while True:
37720                blocksize = min(end_pos - offset, blocksize)
37721                if blocksize <= 0:
37722                    return total_sent
37723                await self._proactor.sendfile(sock, file, offset, blocksize)
37724                offset += blocksize
37725                total_sent += blocksize
37726        finally:
37727            if total_sent > 0:
37728                file.seek(offset)
37729",
37730        );
37731        let f = find_code(&code, "_sock_sendfile_native").expect("missing method code");
37732
37733        let names_for_unit = |unit: &bytecode::CodeUnit| -> Vec<String> {
37734            let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37735            match unit.op {
37736                Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
37737                    vec![f.varnames[usize::from(var_num.get(arg))].to_string()]
37738                }
37739                Instruction::LoadFastLoadFast { var_nums }
37740                | Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
37741                    let (left, right) = var_nums.get(arg).indexes();
37742                    vec![
37743                        f.varnames[usize::from(left)].to_string(),
37744                        f.varnames[usize::from(right)].to_string(),
37745                    ]
37746                }
37747                _ => Vec::new(),
37748            }
37749        };
37750
37751        let borrowed_names: Vec<_> = f
37752            .instructions
37753            .iter()
37754            .filter_map(|unit| match unit.op {
37755                Instruction::LoadFastBorrow { .. }
37756                | Instruction::LoadFastBorrowLoadFastBorrow { .. } => Some(names_for_unit(unit)),
37757                _ => None,
37758            })
37759            .collect();
37760        assert_eq!(
37761            borrowed_names,
37762            vec![vec!["file".to_owned()]],
37763            "only the initial file.fileno() receiver should borrow, got instructions={:?}",
37764            f.instructions
37765        );
37766
37767        let has_strong_name = |name: &str| {
37768            f.instructions.iter().any(|unit| {
37769                matches!(
37770                    unit.op,
37771                    Instruction::LoadFast { .. } | Instruction::LoadFastLoadFast { .. }
37772                ) && names_for_unit(unit).iter().any(|loaded| loaded == name)
37773            })
37774        };
37775
37776        for name in [
37777            "fileno",
37778            "count",
37779            "blocksize",
37780            "offset",
37781            "fsize",
37782            "end_pos",
37783            "total_sent",
37784            "file",
37785            "self",
37786            "sock",
37787        ] {
37788            assert!(
37789                has_strong_name(name),
37790                "async early-return send tail should use strong LOAD_FAST for {name}, got instructions={:?}",
37791                f.instructions
37792            );
37793        }
37794    }
37795
37796    #[test]
37797    fn async_with_return_await_after_early_return_keeps_borrow_load_fast() {
37798        let code = compile_exec(
37799            "\
37800async def wait_for(fut, timeout):
37801    if timeout is not None and timeout <= 0:
37802        fut = ensure_future(fut)
37803        if fut.done():
37804            return fut.result()
37805        await cancel_and_wait(fut)
37806        try:
37807            return fut.result()
37808        except CancelledError as exc:
37809            raise TimeoutError from exc
37810    async with timeout_cm(timeout):
37811        return await fut
37812",
37813        );
37814        let f = find_code(&code, "wait_for").expect("missing function code");
37815        let ops: Vec<_> = f
37816            .instructions
37817            .iter()
37818            .filter(|unit| !matches!(unit.op, Instruction::Cache))
37819            .collect();
37820        let loaded_names = |unit: &bytecode::CodeUnit| -> Vec<String> {
37821            let arg = OpArg::new(u32::from(u8::from(unit.arg)));
37822            match unit.op {
37823                Instruction::LoadFastBorrow { var_num } => {
37824                    vec![f.varnames[usize::from(var_num.get(arg))].to_string()]
37825                }
37826                Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
37827                    let (left, right) = var_nums.get(arg).indexes();
37828                    vec![
37829                        f.varnames[usize::from(left)].to_string(),
37830                        f.varnames[usize::from(right)].to_string(),
37831                    ]
37832                }
37833                _ => Vec::new(),
37834            }
37835        };
37836        let borrowed_names: Vec<_> = f
37837            .instructions
37838            .iter()
37839            .filter_map(|unit| {
37840                let names = loaded_names(unit);
37841                (!names.is_empty()).then_some(names)
37842            })
37843            .collect();
37844
37845        assert!(
37846            borrowed_names
37847                .iter()
37848                .flatten()
37849                .any(|name| name == "timeout"),
37850            "CPython preserves borrowed timeout loads in async-with setup, got instructions={:?}",
37851            f.instructions
37852        );
37853        assert!(
37854            ops.windows(4).any(|window| {
37855                matches!(window[0].op, Instruction::LoadFastBorrow { .. })
37856                    && loaded_names(window[0]).iter().any(|name| name == "fut")
37857                    && matches!(window[1].op, Instruction::LoadAttr { namei }
37858                        if f.names[usize::try_from(namei.get(OpArg::new(u32::from(u8::from(window[1].arg)))).name_idx()).unwrap()] == "result")
37859                    && matches!(window[2].op, Instruction::Call { .. })
37860                    && matches!(window[3].op, Instruction::ReturnValue)
37861            }),
37862            "CPython preserves borrowed fut loads for direct try-return method calls, got instructions={:?}",
37863            f.instructions
37864        );
37865    }
37866
37867    #[test]
37868    fn async_nested_try_finally_except_after_await_return_uses_strong_loads() {
37869        let code = compile_exec(
37870            "\
37871async def f(a, b, c, h):
37872    try:
37873        try:
37874            await sleep(1)
37875        finally:
37876            h()
37877    except E:
37878        pass
37879    await sleep(0)
37880    return a, b, c
37881",
37882        );
37883        let f = find_code(&code, "f").expect("missing function code");
37884        let ops: Vec<_> = f
37885            .instructions
37886            .iter()
37887            .filter(|unit| !matches!(unit.op, Instruction::Cache))
37888            .collect();
37889        let return_idx = ops
37890            .iter()
37891            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
37892            .expect("missing return");
37893        let tail = &ops[return_idx.saturating_sub(4)..return_idx];
37894
37895        assert!(
37896            tail.iter().any(|unit| {
37897                matches!(
37898                    unit.op,
37899                    Instruction::LoadFast { .. } | Instruction::LoadFastLoadFast { .. }
37900                )
37901            }),
37902            "nested try/finally inside try/except leaves CPython's empty normal-exit block before the next await, so return loads stay strong, got tail={tail:?}",
37903        );
37904        assert!(
37905            tail.iter().all(|unit| {
37906                !matches!(
37907                    unit.op,
37908                    Instruction::LoadFastBorrow { .. }
37909                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
37910                )
37911            }),
37912            "nested try/finally inside try/except should not borrow final return loads, got tail={tail:?}",
37913        );
37914    }
37915
37916    #[test]
37917    fn async_conditional_raise_finally_except_after_await_return_uses_strong_pair() {
37918        let code = compile_exec(
37919            "\
37920async def f(self, asyncio, sys, task, timeout_handle, sleep):
37921    timed_out = False
37922    structured_block_finished = False
37923    outer_code_reached = False
37924    try:
37925        try:
37926            await asyncio.sleep(sleep)
37927            structured_block_finished = True
37928        finally:
37929            timeout_handle.cancel()
37930            if (
37931                timed_out
37932                and task.uncancel() == 0
37933                and type(sys.exception()) is asyncio.CancelledError
37934            ):
37935                raise TimeoutError
37936    except TimeoutError:
37937        self.assertTrue(timed_out)
37938    outer_code_reached = True
37939    await asyncio.sleep(0)
37940    return timed_out, structured_block_finished, outer_code_reached
37941",
37942        );
37943        let f = find_code(&code, "f").expect("missing function code");
37944        let ops: Vec<_> = f
37945            .instructions
37946            .iter()
37947            .filter(|unit| !matches!(unit.op, Instruction::Cache))
37948            .collect();
37949        let return_idx = ops
37950            .iter()
37951            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
37952            .expect("missing return");
37953        let tail = &ops[return_idx.saturating_sub(5)..return_idx];
37954
37955        assert!(
37956            tail.iter()
37957                .any(|unit| matches!(unit.op, Instruction::LoadFastLoadFast { .. })),
37958            "conditional-raise finally inside try/except should keep CPython-style strong final return pair after await, got tail={tail:?}",
37959        );
37960        assert!(
37961            tail.iter()
37962                .all(|unit| !matches!(unit.op, Instruction::LoadFastBorrowLoadFastBorrow { .. })),
37963            "conditional-raise finally inside try/except should not borrow final return pair after await, got tail={tail:?}",
37964        );
37965    }
37966
37967    #[test]
37968    fn try_else_attribute_probe_end_allows_following_loads_borrow() {
37969        let code = compile_exec(
37970            "\
37971def f(self):
37972    args = (1,)
37973    try:
37974        getstate = self.__getstate__
37975    except AttributeError:
37976        dict = None
37977    else:
37978        dict = getstate()
37979    if dict:
37980        return args, dict
37981    return args
37982",
37983        );
37984        let f = find_code(&code, "f").expect("missing function code");
37985        let pair_arg = {
37986            let args = f
37987                .varnames
37988                .iter()
37989                .position(|name| name == "args")
37990                .and_then(|idx| u8::try_from(idx).ok())
37991                .expect("missing args local");
37992            let dict = f
37993                .varnames
37994                .iter()
37995                .position(|name| name == "dict")
37996                .and_then(|idx| u8::try_from(idx).ok())
37997                .expect("missing dict local");
37998            (args << 4) | dict
37999        };
38000        let ops: Vec<_> = f
38001            .instructions
38002            .iter()
38003            .filter(|unit| !matches!(unit.op, Instruction::Cache))
38004            .collect();
38005
38006        assert!(
38007            ops.iter().any(|unit| matches!(
38008                unit.op,
38009                Instruction::LoadFastBorrowLoadFastBorrow { .. }
38010            ) && u8::from(unit.arg) == pair_arg),
38011            "CPython 3.14 optimize_load_fast() borrows args/dict after the try/else attribute-probe end; got ops={ops:?}",
38012        );
38013    }
38014
38015    #[test]
38016    fn protected_import_tail_keeps_strong_load_fast() {
38017        let code = compile_exec(
38018            "\
38019def f(s, size, pos, errors):
38020    message = 'x'
38021    look = pos
38022    try:
38023        import unicodedata
38024    except ImportError:
38025        return None
38026    if look < size and chr(s[look]) == '{':
38027        while look < size and chr(s[look]) != '}':
38028            look += 1
38029        if look > pos + 1 and look < size and chr(s[look]) == '}':
38030            message = 'y'
38031    return message
38032",
38033        );
38034        let f = find_code(&code, "f").expect("missing f code");
38035        let ops: Vec<_> = f
38036            .instructions
38037            .iter()
38038            .map(|unit| unit.op)
38039            .filter(|op| !matches!(op, Instruction::Cache))
38040            .collect();
38041
38042        let import_idx = ops
38043            .iter()
38044            .position(|op| matches!(op, Instruction::ImportName { .. }))
38045            .expect("missing IMPORT_NAME");
38046        let protected_tail = &ops[import_idx + 1..];
38047
38048        assert!(
38049            !protected_tail.iter().any(|op| {
38050                matches!(
38051                    op,
38052                    Instruction::LoadFastBorrow { .. }
38053                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
38054                )
38055            }),
38056            "expected protected import tail to keep strong LOAD_FAST ops, got tail={protected_tail:?}"
38057        );
38058
38059        assert!(
38060            protected_tail
38061                .iter()
38062                .any(|op| matches!(op, Instruction::LoadFastLoadFast { .. })),
38063            "expected protected import tail to keep LOAD_FAST_LOAD_FAST ops, got tail={protected_tail:?}"
38064        );
38065    }
38066
38067    #[test]
38068    fn nested_protected_import_tail_keeps_strong_load_fast() {
38069        let code = compile_exec(
38070            "\
38071def f(self, mode, compresslevel):
38072    try:
38073        try:
38074            import zlib
38075        except ImportError:
38076            raise RuntimeError from None
38077        self.zlib = zlib
38078        self.crc = zlib.crc32(b'')
38079        if mode == 'r':
38080            self.exception = zlib.error
38081            self._init_read_gz()
38082        else:
38083            self._init_write_gz(compresslevel)
38084    except:
38085        self.closed = True
38086        raise
38087",
38088        );
38089        let f = find_code(&code, "f").expect("missing f code");
38090        let ops: Vec<_> = f
38091            .instructions
38092            .iter()
38093            .map(|unit| unit.op)
38094            .filter(|op| !matches!(op, Instruction::Cache))
38095            .collect();
38096
38097        let import_idx = ops
38098            .iter()
38099            .position(|op| matches!(op, Instruction::ImportName { .. }))
38100            .expect("missing IMPORT_NAME");
38101        let handler_start = ops
38102            .iter()
38103            .position(|op| matches!(op, Instruction::PushExcInfo))
38104            .expect("missing handler entry");
38105        let nested_protected_tail = &ops[import_idx + 1..handler_start];
38106
38107        assert!(
38108            !nested_protected_tail.iter().any(|op| {
38109                matches!(
38110                    op,
38111                    Instruction::LoadFastBorrow { .. }
38112                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
38113                )
38114            }),
38115            "CPython keeps strong LOAD_FAST ops after a nested protected import tail, got tail={nested_protected_tail:?}"
38116        );
38117        assert!(
38118            nested_protected_tail
38119                .iter()
38120                .any(|op| matches!(op, Instruction::LoadFastLoadFast { .. })),
38121            "expected nested protected import tail to keep LOAD_FAST_LOAD_FAST, got tail={nested_protected_tail:?}"
38122        );
38123    }
38124
38125    #[test]
38126    fn unprotected_import_before_with_keeps_borrow() {
38127        let code = compile_exec(
38128            "\
38129def f(self, document):
38130    from xml.etree import ElementInclude
38131    document = self.xinclude_loader('C1.xml')
38132    with self.assertRaises(OSError) as cm:
38133        ElementInclude.include(document, self.xinclude_loader)
38134    self.assertEqual(str(cm.exception), 'resource not found')
38135",
38136        );
38137        let f = find_code(&code, "f").expect("missing f code");
38138        let ops: Vec<_> = f
38139            .instructions
38140            .iter()
38141            .map(|unit| unit.op)
38142            .filter(|op| !matches!(op, Instruction::Cache))
38143            .collect();
38144
38145        let import_idx = ops
38146            .iter()
38147            .position(|op| matches!(op, Instruction::ImportName { .. }))
38148            .expect("missing IMPORT_NAME");
38149        let handler_start = ops
38150            .iter()
38151            .position(|op| matches!(op, Instruction::PushExcInfo))
38152            .expect("missing handler entry");
38153        let warm_path = &ops[import_idx + 1..handler_start];
38154
38155        assert!(
38156            warm_path.iter().any(|op| {
38157                matches!(
38158                    op,
38159                    Instruction::LoadFastBorrow { .. }
38160                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
38161                )
38162            }),
38163            "expected unprotected import before with-block to keep LOAD_FAST_BORROW ops, got warm_path={warm_path:?}"
38164        );
38165        assert!(
38166            warm_path
38167                .iter()
38168                .any(|op| matches!(op, Instruction::LoadFastBorrowLoadFastBorrow { .. })),
38169            "expected with body arguments to keep LOAD_FAST_BORROW_LOAD_FAST_BORROW, got warm_path={warm_path:?}"
38170        );
38171    }
38172
38173    #[test]
38174    fn from_import_after_conditional_store_join_uses_strong_prefix_loads() {
38175        let code = compile_exec(
38176            "\
38177def f(x):
38178    if x is None:
38179        x = 'a'
38180    y = x.rpartition('.')[0]
38181    from pkgutil import get_importer
38182    return y
38183",
38184        );
38185        let f = find_code(&code, "f").expect("missing f code");
38186        let ops: Vec<_> = f
38187            .instructions
38188            .iter()
38189            .filter(|unit| !matches!(unit.op, Instruction::Cache))
38190            .collect();
38191        let import_from_idx = ops
38192            .iter()
38193            .position(|unit| matches!(unit.op, Instruction::ImportFrom { .. }))
38194            .expect("missing IMPORT_FROM");
38195        let rpartition_idx = ops[..import_from_idx]
38196            .iter()
38197            .position(|unit| match unit.op {
38198                Instruction::LoadAttr { namei } => {
38199                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
38200                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "rpartition"
38201                }
38202                _ => false,
38203            })
38204            .expect("missing rpartition LOAD_ATTR");
38205
38206        assert!(
38207            matches!(ops[rpartition_idx - 1].op, Instruction::LoadFast { .. }),
38208            "CPython optimize_load_fast() keeps the conditional-store join receiver strong before IMPORT_FROM, got ops={ops:?}"
38209        );
38210    }
38211
38212    #[test]
38213    fn plain_import_after_conditional_store_join_keeps_borrow_prefix_loads() {
38214        let code = compile_exec(
38215            "\
38216def f(x):
38217    if x is None:
38218        x = 'a'
38219    y = x.rpartition('.')[0]
38220    import pkgutil
38221    return y
38222",
38223        );
38224        let f = find_code(&code, "f").expect("missing f code");
38225        let ops: Vec<_> = f
38226            .instructions
38227            .iter()
38228            .filter(|unit| !matches!(unit.op, Instruction::Cache))
38229            .collect();
38230        let import_name_idx = ops
38231            .iter()
38232            .position(|unit| matches!(unit.op, Instruction::ImportName { .. }))
38233            .expect("missing IMPORT_NAME");
38234        let rpartition_idx = ops[..import_name_idx]
38235            .iter()
38236            .position(|unit| match unit.op {
38237                Instruction::LoadAttr { namei } => {
38238                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
38239                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "rpartition"
38240                }
38241                _ => false,
38242            })
38243            .expect("missing rpartition LOAD_ATTR");
38244
38245        assert!(
38246            matches!(
38247                ops[rpartition_idx - 1].op,
38248                Instruction::LoadFastBorrow { .. }
38249            ),
38250            "plain import after conditional-store join should keep CPython-style borrowed receiver, got ops={ops:?}"
38251        );
38252    }
38253
38254    #[test]
38255    fn unprotected_prefix_before_try_keeps_attr_subscript_borrow() {
38256        let code = compile_exec(
38257            "\
38258def f():
38259    import sys, getopt
38260    usage = f'usage: {sys.argv[0]}'
38261    try:
38262        opts, args = getopt.getopt(sys.argv[1:], 'h')
38263    except getopt.error as msg:
38264        sys.stdout = sys.stderr
38265        print(msg)
38266        print(usage)
38267        sys.exit(2)
38268    return usage
38269",
38270        );
38271        let f = find_code(&code, "f").expect("missing f code");
38272        let first_argv_idx = f
38273            .instructions
38274            .iter()
38275            .position(|unit| match unit.op {
38276                Instruction::LoadAttr { namei } => {
38277                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
38278                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "argv"
38279                }
38280                _ => false,
38281            })
38282            .expect("missing argv attr load");
38283        let receiver = f.instructions[..first_argv_idx]
38284            .iter()
38285            .rev()
38286            .find(|unit| !matches!(unit.op, Instruction::Cache))
38287            .expect("missing argv receiver")
38288            .op;
38289
38290        assert!(
38291            matches!(receiver, Instruction::LoadFastBorrow { .. }),
38292            "unprotected prefix before try should keep CPython-style LOAD_FAST_BORROW receiver, got {receiver:?}"
38293        );
38294    }
38295
38296    #[test]
38297    fn terminal_except_inlined_comprehension_keeps_borrowed_warm_loads() {
38298        let code = compile_exec(
38299            r##"
38300def f(output):
38301    output = re.sub(r"\[[0-9]+ refs\]", "", output)
38302    try:
38303        result = [
38304            row.split("\t")
38305            for row in output.splitlines()
38306            if row and not row.startswith('#')
38307        ]
38308        result.sort(key=lambda row: int(row[0]))
38309        result = [row[1] for row in result]
38310        return "\n".join(result)
38311    except (IndexError, ValueError):
38312        raise AssertionError(
38313            "tracer produced unparsable output:\n{}".format(output)
38314        )
38315"##,
38316        );
38317        let f = find_code(&code, "f").expect("missing f code");
38318        let handler_start = f
38319            .instructions
38320            .iter()
38321            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
38322            .expect("missing handler entry");
38323        let warm_path = &f.instructions[..handler_start];
38324        let load_fast_name = |unit: &bytecode::CodeUnit| match unit.op {
38325            Instruction::LoadFast { var_num } => {
38326                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
38327                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
38328            }
38329            _ => None,
38330        };
38331        let borrow_name = |unit: &bytecode::CodeUnit| match unit.op {
38332            Instruction::LoadFastBorrow { var_num } => {
38333                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
38334                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
38335            }
38336            _ => None,
38337        };
38338
38339        assert!(
38340            warm_path
38341                .iter()
38342                .filter_map(load_fast_name)
38343                .all(|name| name != "row" && name != "result"),
38344            "terminal-except inlined comprehension warm path should keep CPython-style borrowed row/result loads, got warm_path={warm_path:?}"
38345        );
38346        for name in ["row", "result"] {
38347            assert!(
38348                warm_path
38349                    .iter()
38350                    .filter_map(borrow_name)
38351                    .any(|var| var == name),
38352                "expected borrowed {name} load in terminal-except inlined comprehension warm path, got warm_path={warm_path:?}"
38353            );
38354        }
38355    }
38356
38357    #[test]
38358    fn outer_guarded_protected_import_keeps_borrow_tail() {
38359        let code = compile_exec(
38360            "\
38361def f(sys, os, file):
38362    if sys.platform == 'win32':
38363        try:
38364            import nt
38365            if not nt._supports_virtual_terminal():
38366                return False
38367        except (ImportError, AttributeError):
38368            return False
38369    try:
38370        return os.isatty(file.fileno())
38371    except OSError:
38372        return hasattr(file, 'isatty') and file.isatty()
38373",
38374        );
38375        let f = find_code(&code, "f").expect("missing f code");
38376        let borrows_name = |name: &str| {
38377            f.instructions.iter().any(|unit| match unit.op {
38378                Instruction::LoadFastBorrow { var_num } => {
38379                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
38380                    f.varnames[usize::from(var_num.get(arg))] == name
38381                }
38382                Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
38383                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
38384                    let (left, right) = var_nums.get(arg).indexes();
38385                    f.varnames[usize::from(left)] == name || f.varnames[usize::from(right)] == name
38386                }
38387                _ => false,
38388            })
38389        };
38390
38391        for name in ["nt", "os", "file"] {
38392            assert!(
38393                borrows_name(name),
38394                "outer-guarded protected import should keep CPython-style borrow for {name}, got instructions={:?}",
38395                f.instructions
38396            );
38397        }
38398    }
38399
38400    #[test]
38401    fn loop_or_break_continue_orders_break_before_backedge() {
38402        let code = compile_exec(
38403            "\
38404def f(self, quoted):
38405    while True:
38406        if self.state == 'x':
38407            if self.token or (self.posix and quoted):
38408                break
38409            else:
38410                continue
38411        elif self.state == 'y':
38412            self.consume()
38413    x = self.a + self.b + self.c
38414    return x
38415",
38416        );
38417        let f = find_code(&code, "f").expect("missing f code");
38418        let ops: Vec<_> = f
38419            .instructions
38420            .iter()
38421            .filter(|unit| !matches!(unit.op, Instruction::Cache))
38422            .collect();
38423        let quoted_load = ops
38424            .iter()
38425            .position(|unit| match unit.op {
38426                Instruction::LoadFastBorrow { var_num } => {
38427                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
38428                    f.varnames[usize::from(var_num.get(arg))] == "quoted"
38429                }
38430                _ => false,
38431            })
38432            .expect("missing quoted LOAD_FAST_BORROW");
38433        let final_cond = ops[quoted_load + 1..]
38434            .iter()
38435            .position(|unit| {
38436                matches!(
38437                    unit.op,
38438                    Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
38439                )
38440            })
38441            .map(|idx| quoted_load + 1 + idx)
38442            .expect("missing final conditional jump");
38443        assert!(
38444            matches!(ops[final_cond].op, Instruction::PopJumpIfFalse { .. }),
38445            "expected CPython-style inverted final condition, got ops={ops:?}"
38446        );
38447        let break_jump_idx = ops[final_cond + 1..]
38448            .iter()
38449            .position(|unit| matches!(unit.op, Instruction::JumpForward { .. }))
38450            .map(|idx| final_cond + 1 + idx)
38451            .expect("missing break jump after condition");
38452        let jump_back_idx = ops[final_cond + 1..]
38453            .iter()
38454            .position(|unit| matches!(unit.op, Instruction::JumpBackward { .. }))
38455            .map(|idx| final_cond + 1 + idx)
38456            .expect("missing continue backedge");
38457        assert!(
38458            break_jump_idx < jump_back_idx,
38459            "expected break jump before continue backedge, got ops={ops:?}"
38460        );
38461    }
38462
38463    #[test]
38464    fn backward_jump_extended_arg_accounts_for_jump_cache() {
38465        let mut source = String::from("def f(x, items):\n    while x:\n");
38466        for _ in 0..10 {
38467            source.push_str("        x = len(items[-1])\n");
38468        }
38469        for _ in 0..6 {
38470            source.push_str("        len(items)\n");
38471        }
38472        source.push_str("        continue\n");
38473
38474        let code = compile_exec(&source);
38475        let f = find_code(&code, "f").expect("missing f code");
38476        assert!(
38477            f.instructions.windows(2).any(|window| {
38478                matches!(
38479                    (&window[0].op, &window[1].op),
38480                    (
38481                        Instruction::ExtendedArg,
38482                        Instruction::JumpBackward { .. }
38483                            | Instruction::JumpBackwardNoInterrupt { .. }
38484                    )
38485                )
38486            }),
38487            "CPython assemble.c resolves unconditional jumps before jump offsets, so the first offset pass must include JUMP_BACKWARD's inline cache and emit EXTENDED_ARG at this boundary; got instructions={:?}",
38488            f.instructions
38489        );
38490    }
38491
38492    #[test]
38493    fn for_continue_before_return_orders_backedge_before_return_body() {
38494        let code = compile_exec(
38495            "\
38496def f(self):
38497    for version in AllowedVersions:
38498        if not version in self.capabilities:
38499            continue
38500        self.PROTOCOL_VERSION = version
38501        return
38502    raise self.error('x')
38503",
38504        );
38505        let f = find_code(&code, "f").expect("missing f code");
38506        let ops: Vec<_> = f
38507            .instructions
38508            .iter()
38509            .filter(|unit| !matches!(unit.op, Instruction::Cache))
38510            .collect();
38511        let contains_idx = ops
38512            .iter()
38513            .position(|unit| matches!(unit.op, Instruction::ContainsOp { .. }))
38514            .expect("missing containment test");
38515        let cond_idx = ops[contains_idx + 1..]
38516            .iter()
38517            .position(|unit| {
38518                matches!(
38519                    unit.op,
38520                    Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
38521                )
38522            })
38523            .map(|idx| contains_idx + 1 + idx)
38524            .expect("missing conditional jump");
38525        assert!(
38526            matches!(ops[cond_idx].op, Instruction::PopJumpIfTrue { .. }),
38527            "expected CPython-style condition targeting the return body, got ops={ops:?}"
38528        );
38529
38530        let backedge_idx = ops[cond_idx + 1..]
38531            .iter()
38532            .position(|unit| matches!(unit.op, Instruction::JumpBackward { .. }))
38533            .map(|idx| cond_idx + 1 + idx)
38534            .expect("missing continue backedge");
38535        let store_attr_idx = ops[cond_idx + 1..]
38536            .iter()
38537            .position(|unit| match unit.op {
38538                Instruction::StoreAttr { namei } => {
38539                    let namei = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
38540                    f.names[usize::try_from(namei).unwrap()].as_str() == "PROTOCOL_VERSION"
38541                }
38542                _ => false,
38543            })
38544            .map(|idx| cond_idx + 1 + idx)
38545            .expect("missing PROTOCOL_VERSION store");
38546        assert!(
38547            backedge_idx < store_attr_idx,
38548            "expected continue backedge before return body, got ops={ops:?}"
38549        );
38550    }
38551
38552    #[test]
38553    fn while_conditional_return_orders_backedge_before_return_body() {
38554        let code = compile_exec(
38555            "\
38556def f(self, tag):
38557    while self._get_response():
38558        if self.tagged_commands[tag]:
38559            return tag
38560",
38561        );
38562        let f = find_code(&code, "f").expect("missing f code");
38563        let ops: Vec<_> = f
38564            .instructions
38565            .iter()
38566            .filter(|unit| !matches!(unit.op, Instruction::Cache))
38567            .collect();
38568        let subscript_idx = ops
38569            .iter()
38570            .position(|unit| matches!(unit.op, Instruction::BinaryOp { .. }))
38571            .expect("missing tagged_commands subscript");
38572        let cond_idx = ops[subscript_idx + 1..]
38573            .iter()
38574            .position(|unit| {
38575                matches!(
38576                    unit.op,
38577                    Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
38578                )
38579            })
38580            .map(|idx| subscript_idx + 1 + idx)
38581            .expect("missing conditional jump");
38582        assert!(
38583            matches!(ops[cond_idx].op, Instruction::PopJumpIfTrue { .. }),
38584            "expected CPython-style condition targeting return body, got ops={ops:?}"
38585        );
38586        let backedge_idx = ops[cond_idx + 1..]
38587            .iter()
38588            .position(|unit| matches!(unit.op, Instruction::JumpBackward { .. }))
38589            .map(|idx| cond_idx + 1 + idx)
38590            .expect("missing loop backedge");
38591        let return_idx = ops[cond_idx + 1..]
38592            .iter()
38593            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
38594            .map(|idx| cond_idx + 1 + idx)
38595            .expect("missing return");
38596        assert!(
38597            backedge_idx < return_idx,
38598            "expected loop backedge before return body, got ops={ops:?}"
38599        );
38600    }
38601
38602    #[test]
38603    fn while_boolop_conditional_return_splits_backedges_before_return_body() {
38604        let code = compile_exec(
38605            "\
38606def f(flags, A, B, stop):
38607    while True:
38608        if stop:
38609            break
38610        if flags & A and flags & B:
38611            return None
38612    return flags
38613",
38614        );
38615        let f = find_code(&code, "f").expect("missing f code");
38616        let ops: Vec<_> = f
38617            .instructions
38618            .iter()
38619            .map(|unit| unit.op)
38620            .filter(|op| !matches!(op, Instruction::Cache))
38621            .collect();
38622
38623        assert!(
38624            ops.windows(12).any(|window| {
38625                matches!(
38626                    window,
38627                    [
38628                        Instruction::BinaryOp { .. },
38629                        Instruction::ToBool,
38630                        Instruction::PopJumpIfTrue { .. },
38631                        Instruction::NotTaken,
38632                        Instruction::JumpBackward { .. },
38633                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
38634                            | Instruction::LoadFastLoadFast { .. },
38635                        Instruction::BinaryOp { .. },
38636                        Instruction::ToBool,
38637                        Instruction::PopJumpIfTrue { .. },
38638                        Instruction::NotTaken,
38639                        Instruction::JumpBackward { .. },
38640                        Instruction::LoadConst { .. },
38641                    ]
38642                )
38643            }),
38644            "boolop conditional return in a while tail should split CPython-style false backedges before the return body, got ops={ops:?}"
38645        );
38646    }
38647
38648    #[test]
38649    fn for_break_to_return_orders_backedge_before_return() {
38650        let code = compile_exec(
38651            "\
38652def f(it):
38653    best = 10
38654    body = None
38655    for prio, part in it:
38656        if prio < best:
38657            best = prio
38658            body = part
38659            if prio == 0:
38660                break
38661    return body
38662",
38663        );
38664        let f = find_code(&code, "f").expect("missing f code");
38665        let ops: Vec<_> = f
38666            .instructions
38667            .iter()
38668            .filter(|unit| !matches!(unit.op, Instruction::Cache))
38669            .collect();
38670        let compare_idx = ops
38671            .iter()
38672            .enumerate()
38673            .filter(|(_, unit)| matches!(unit.op, Instruction::CompareOp { .. }))
38674            .nth(1)
38675            .map(|(idx, _)| idx)
38676            .expect("missing break comparison");
38677        let cond_idx = ops[compare_idx + 1..]
38678            .iter()
38679            .position(|unit| {
38680                matches!(
38681                    unit.op,
38682                    Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
38683                )
38684            })
38685            .map(|idx| compare_idx + 1 + idx)
38686            .expect("missing break conditional jump");
38687        assert!(
38688            matches!(ops[cond_idx].op, Instruction::PopJumpIfTrue { .. }),
38689            "expected CPython-style true jump to break return path, got ops={ops:?}"
38690        );
38691        let jump_back_idx = ops[cond_idx + 1..]
38692            .iter()
38693            .position(|unit| matches!(unit.op, Instruction::JumpBackward { .. }))
38694            .map(|idx| cond_idx + 1 + idx)
38695            .expect("missing loop backedge before break return");
38696        let return_idx = ops[cond_idx + 1..]
38697            .iter()
38698            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
38699            .map(|idx| cond_idx + 1 + idx)
38700            .expect("missing break return path");
38701        assert!(
38702            jump_back_idx < return_idx,
38703            "expected loop backedge before break return block, got ops={ops:?}"
38704        );
38705    }
38706
38707    #[test]
38708    fn for_conditional_raise_orders_backedge_before_raise() {
38709        let code = compile_exec(
38710            "\
38711def f(items, limit):
38712    found = 0
38713    for item in items:
38714        if item:
38715            found += 1
38716            if found >= limit:
38717                raise ValueError(found)
38718    return found
38719",
38720        );
38721        let f = find_code(&code, "f").expect("missing f code");
38722        let ops: Vec<_> = f
38723            .instructions
38724            .iter()
38725            .filter(|unit| !matches!(unit.op, Instruction::Cache))
38726            .collect();
38727        let compare_idx = ops
38728            .iter()
38729            .enumerate()
38730            .find(|(_, unit)| matches!(unit.op, Instruction::CompareOp { .. }))
38731            .map(|(idx, _)| idx)
38732            .expect("missing raise comparison");
38733        let cond_idx = ops[compare_idx + 1..]
38734            .iter()
38735            .position(|unit| {
38736                matches!(
38737                    unit.op,
38738                    Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
38739                )
38740            })
38741            .map(|idx| compare_idx + 1 + idx)
38742            .expect("missing raise conditional jump");
38743        assert!(
38744            matches!(ops[cond_idx].op, Instruction::PopJumpIfTrue { .. }),
38745            "expected CPython-style true jump to raise path, got ops={ops:?}"
38746        );
38747        let jump_back_idx = ops[cond_idx + 1..]
38748            .iter()
38749            .position(|unit| matches!(unit.op, Instruction::JumpBackward { .. }))
38750            .map(|idx| cond_idx + 1 + idx)
38751            .expect("missing loop backedge before raise");
38752        let raise_idx = ops[cond_idx + 1..]
38753            .iter()
38754            .position(|unit| matches!(unit.op, Instruction::RaiseVarargs { .. }))
38755            .map(|idx| cond_idx + 1 + idx)
38756            .expect("missing raise path");
38757        assert!(
38758            jump_back_idx < raise_idx,
38759            "expected loop backedge before conditional raise block, got ops={ops:?}"
38760        );
38761    }
38762
38763    #[test]
38764    fn simple_for_conditional_raise_orders_backedge_before_raise() {
38765        let code = compile_exec(
38766            "\
38767def f(kw):
38768    for k in ('stdout', 'check'):
38769        if k in kw:
38770            raise ValueError(f'{k} argument not allowed, it will be overridden.')
38771",
38772        );
38773        let f = find_code(&code, "f").expect("missing f code");
38774        let ops: Vec<_> = f
38775            .instructions
38776            .iter()
38777            .map(|unit| unit.op)
38778            .filter(|op| !matches!(op, Instruction::Cache))
38779            .collect();
38780
38781        assert!(
38782            ops.windows(5).any(|window| {
38783                matches!(
38784                    window,
38785                    [
38786                        Instruction::ContainsOp { .. },
38787                        Instruction::PopJumpIfTrue { .. },
38788                        Instruction::NotTaken,
38789                        Instruction::JumpBackward { .. },
38790                        Instruction::LoadGlobal { .. },
38791                    ]
38792                )
38793            }),
38794            "expected CPython-style true jump to raise path after loop backedge, got ops={ops:?}"
38795        );
38796        assert!(
38797            !ops.windows(4).any(|window| {
38798                matches!(
38799                    window,
38800                    [
38801                        Instruction::ContainsOp { .. },
38802                        Instruction::PopJumpIfFalse { .. },
38803                        Instruction::NotTaken,
38804                        Instruction::LoadGlobal { .. },
38805                    ]
38806                )
38807            }),
38808            "unexpected conditional raise body before loop backedge, got ops={ops:?}"
38809        );
38810    }
38811
38812    #[test]
38813    fn loop_nested_boolop_exit_keeps_cpython_backedge_line_order() {
38814        let code = compile_exec(
38815            "\
38816def f(found, value, m, done, name, renamed_variables, keep_unresolved, variables):
38817    for _ in [0]:
38818        if m is not None:
38819            if found:
38820                if '$' in value:
38821                    done[name] = value
38822                else:
38823                    try:
38824                        value = int(value)
38825                    except ValueError:
38826                        done[name] = value.strip()
38827                    else:
38828                        done[name] = value
38829                    variables.remove(name)
38830                    if name.startswith('PY_') \\
38831                    and name[3:] in renamed_variables:
38832                        name = name[3:]
38833                        if name not in done:
38834                            done[name] = value
38835        else:
38836            if keep_unresolved:
38837                done[name] = value
38838            variables.remove(name)
38839",
38840        );
38841        let f = find_code(&code, "f").expect("missing f code");
38842        let ops_lines: Vec<_> = f
38843            .instructions
38844            .iter()
38845            .zip(&f.locations)
38846            .filter_map(|(unit, (location, _))| {
38847                (!matches!(unit.op, Instruction::Cache)).then_some((unit.op, location.line.get()))
38848            })
38849            .collect();
38850
38851        assert!(
38852            ops_lines.windows(9).any(|window| {
38853                matches!(
38854                    window,
38855                    [
38856                        (Instruction::StoreSubscr, 19),
38857                        (Instruction::JumpBackward { .. }, 19),
38858                        (Instruction::JumpBackward { .. }, 18),
38859                        (Instruction::JumpBackward { .. }, 16),
38860                        (Instruction::JumpBackward { .. }, 15),
38861                        (Instruction::JumpBackward { .. }, 4),
38862                        (
38863                            Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
38864                            21
38865                        ),
38866                        (Instruction::ToBool, 21),
38867                        (Instruction::PopJumpIfFalse { .. }, 21),
38868                    ]
38869                )
38870            }),
38871            "expected CPython-style nested boolop backedge line order before enclosing else, got ops_lines={ops_lines:?}"
38872        );
38873    }
38874
38875    #[test]
38876    fn loop_conditional_raise_before_elif_keeps_raise_before_backedge() {
38877        let code = compile_exec(
38878            "\
38879def f(checks, missing, named):
38880    for check in checks:
38881        if check == 1:
38882            if missing:
38883                raise ValueError('x')
38884        elif check is named:
38885            pass
38886    return checks
38887",
38888        );
38889        let f = find_code(&code, "f").expect("missing f code");
38890        let ops: Vec<_> = f
38891            .instructions
38892            .iter()
38893            .map(|unit| unit.op)
38894            .filter(|op| !matches!(op, Instruction::Cache))
38895            .collect();
38896
38897        assert!(
38898            ops.windows(5).any(|window| {
38899                matches!(
38900                    window,
38901                    [
38902                        Instruction::ToBool,
38903                        Instruction::PopJumpIfFalse { .. },
38904                        Instruction::NotTaken,
38905                        Instruction::LoadGlobal { .. },
38906                        Instruction::LoadConst { .. },
38907                    ]
38908                )
38909            }),
38910            "expected CPython-style false edge into raise body before following elif chain, got ops={ops:?}"
38911        );
38912        assert!(
38913            !ops.windows(4).any(|window| {
38914                matches!(
38915                    window,
38916                    [
38917                        Instruction::ToBool,
38918                        Instruction::PopJumpIfTrue { .. },
38919                        Instruction::NotTaken,
38920                        Instruction::JumpBackward { .. },
38921                    ]
38922                )
38923            }),
38924            "unexpected loop backedge before conditional raise body in if/elif chain, got ops={ops:?}"
38925        );
38926    }
38927
38928    #[test]
38929    fn protected_for_is_none_raise_threads_backedge_before_raise() {
38930        let code = compile_exec(
38931            "\
38932def f(stacklevel, frame, skip_file_prefixes):
38933    try:
38934        for x in range(stacklevel - 1):
38935            frame = _next_external_frame(frame, skip_file_prefixes)
38936            if frame is None:
38937                raise ValueError
38938    except ValueError:
38939        frame = None
38940    return frame
38941",
38942        );
38943        let f = find_code(&code, "f").expect("missing f code");
38944        let ops: Vec<_> = f
38945            .instructions
38946            .iter()
38947            .map(|unit| unit.op)
38948            .filter(|op| !matches!(op, Instruction::Cache))
38949            .collect();
38950
38951        assert!(
38952            ops.windows(5).any(|window| {
38953                matches!(
38954                    window,
38955                    [
38956                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
38957                        Instruction::PopJumpIfNone { .. },
38958                        Instruction::NotTaken,
38959                        Instruction::JumpBackward { .. },
38960                        Instruction::LoadGlobal { .. },
38961                    ]
38962                )
38963            }),
38964            "expected protected is-None raise path to match CPython's backedge-before-raise layout, got ops={ops:?}"
38965        );
38966    }
38967
38968    #[test]
38969    fn exception_handler_loop_conditional_raise_orders_backedge_before_raise() {
38970        let code = compile_exec(
38971            "\
38972def f(chunk, dec, i):
38973    try:
38974        for c in chunk:
38975            acc = dec[c]
38976    except TypeError:
38977        for j, c in enumerate(chunk):
38978            if dec[c] is None:
38979                raise ValueError('%d' % (i + j)) from None
38980        raise
38981",
38982        );
38983        let f = find_code(&code, "f").expect("missing f code");
38984        let ops: Vec<_> = f
38985            .instructions
38986            .iter()
38987            .map(|unit| unit.op)
38988            .filter(|op| !matches!(op, Instruction::Cache))
38989            .collect();
38990
38991        assert!(
38992            ops.windows(5).any(|window| {
38993                matches!(
38994                    window,
38995                    [
38996                        Instruction::BinaryOp { .. },
38997                        Instruction::PopJumpIfNone { .. },
38998                        Instruction::NotTaken,
38999                        Instruction::JumpBackward { .. }
39000                            | Instruction::JumpBackwardNoInterrupt { .. },
39001                        Instruction::LoadGlobal { .. },
39002                    ]
39003                )
39004            }),
39005            "expected exception-handler loop false path to jump back before raise body, got ops={ops:?}"
39006        );
39007        assert!(
39008            !ops.windows(4).any(|window| {
39009                matches!(
39010                    window,
39011                    [
39012                        Instruction::BinaryOp { .. },
39013                        Instruction::PopJumpIfNotNone { .. },
39014                        Instruction::NotTaken,
39015                        Instruction::LoadGlobal { .. },
39016                    ]
39017                )
39018            }),
39019            "unexpected exception-handler loop raise body before backedge, got ops={ops:?}"
39020        );
39021    }
39022
39023    #[test]
39024    fn exception_handler_loop_conditional_return_orders_backedge_before_return() {
39025        let code = compile_exec(
39026            "\
39027def f(cls, value):
39028    try:
39029        return cls[value]
39030    except TypeError:
39031        for name, values in cls.items():
39032            if value in values:
39033                return cls[name]
39034        for name, member in cls.items():
39035            if value == member.value:
39036                return cls[name]
39037",
39038        );
39039        let f = find_code(&code, "f").expect("missing f code");
39040        let ops: Vec<_> = f
39041            .instructions
39042            .iter()
39043            .map(|unit| unit.op)
39044            .filter(|op| !matches!(op, Instruction::Cache))
39045            .collect();
39046
39047        assert!(
39048            ops.windows(5).any(|window| {
39049                matches!(
39050                    window,
39051                    [
39052                        Instruction::ContainsOp { .. },
39053                        Instruction::PopJumpIfTrue { .. },
39054                        Instruction::NotTaken,
39055                        Instruction::JumpBackward { .. }
39056                            | Instruction::JumpBackwardNoInterrupt { .. },
39057                        Instruction::LoadFastLoadFast { .. }
39058                            | Instruction::LoadFastBorrowLoadFastBorrow { .. },
39059                    ]
39060                )
39061            }),
39062            "expected exception-handler loop false path to jump back before return body, got ops={ops:?}"
39063        );
39064        assert!(
39065            !ops.windows(4).any(|window| {
39066                matches!(
39067                    window,
39068                    [
39069                        Instruction::ContainsOp { .. },
39070                        Instruction::PopJumpIfFalse { .. },
39071                        Instruction::NotTaken,
39072                        Instruction::LoadFastLoadFast { .. }
39073                            | Instruction::LoadFastBorrowLoadFastBorrow { .. },
39074                    ]
39075                )
39076            }),
39077            "unexpected exception-handler loop return body before backedge, got ops={ops:?}"
39078        );
39079    }
39080
39081    #[test]
39082    fn loop_if_body_keeps_fallthrough_before_implicit_continue_backedge() {
39083        let code = compile_exec(
39084            "\
39085def f(b, curr, curr_append, decoded_append, packI, curr_clear):
39086    for x in b:
39087        if 33 <= x <= 117:
39088            curr_append(x)
39089            if len(curr) == 5:
39090                acc = 0
39091                for x in curr:
39092                    acc = 85 * acc + (x - 33)
39093                decoded_append(packI(acc))
39094                curr_clear()
39095        elif x == 122:
39096            decoded_append(0)
39097",
39098        );
39099        let f = find_code(&code, "f").expect("missing f code");
39100        let ops: Vec<_> = f
39101            .instructions
39102            .iter()
39103            .map(|unit| unit.op)
39104            .filter(|op| !matches!(op, Instruction::Cache))
39105            .collect();
39106
39107        assert!(
39108            ops.windows(5).any(|window| {
39109                matches!(
39110                    window,
39111                    [
39112                        Instruction::CompareOp { .. },
39113                        Instruction::PopJumpIfFalse { .. },
39114                        Instruction::NotTaken,
39115                        Instruction::LoadSmallInt { .. },
39116                        Instruction::StoreFast { .. },
39117                    ]
39118                )
39119            }),
39120            "expected CPython-style conditional body fallthrough before implicit continue backedge, got ops={ops:?}"
39121        );
39122        assert!(
39123            !ops.windows(6).any(|window| {
39124                matches!(
39125                    window,
39126                    [
39127                        Instruction::CompareOp { .. },
39128                        Instruction::PopJumpIfTrue { .. },
39129                        Instruction::NotTaken,
39130                        Instruction::JumpBackward { .. }
39131                            | Instruction::JumpBackwardNoInterrupt { .. },
39132                        Instruction::LoadSmallInt { .. },
39133                        Instruction::StoreFast { .. },
39134                    ]
39135                )
39136            }),
39137            "unexpected inverted conditional with implicit continue backedge before body, got ops={ops:?}"
39138        );
39139    }
39140
39141    #[test]
39142    fn if_not_continue_before_conditional_listcomp_body_keeps_cpython_layout() {
39143        let code = compile_exec(
39144            "\
39145def f(data, use):
39146    for line in data:
39147        line = line.strip()
39148        if not line:
39149            continue
39150        if line.startswith('@'):
39151            continue
39152        values = [use(x) for x in line]
39153        use(values)
39154",
39155        );
39156        let f = find_code(&code, "f").expect("missing f code");
39157        let ops: Vec<_> = f
39158            .instructions
39159            .iter()
39160            .map(|unit| unit.op)
39161            .filter(|op| !matches!(op, Instruction::Cache))
39162            .collect();
39163
39164        assert!(
39165            ops.windows(6).any(|window| {
39166                matches!(
39167                    window,
39168                    [
39169                        Instruction::ToBool,
39170                        Instruction::PopJumpIfTrue { .. },
39171                        Instruction::NotTaken,
39172                        Instruction::JumpBackward { .. }
39173                            | Instruction::JumpBackwardNoInterrupt { .. },
39174                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
39175                        Instruction::LoadAttr { .. },
39176                    ]
39177                )
39178            }),
39179            "if-not continue should keep CPython's forward true edge over the continue backedge, got ops={ops:?}"
39180        );
39181    }
39182
39183    #[test]
39184    fn chained_compare_continue_does_not_duplicate_cleanup_backedge() {
39185        let code = compile_exec(
39186            "\
39187def f(items):
39188    offsets = []
39189    ranges = [(0, 10), (20, 30)]
39190    for item in items:
39191        trans_time, offset_before, offset_after = item
39192        for dt_min, dt_max in ranges:
39193            if trans_time is not None and not (dt_min <= trans_time <= dt_max):
39194                continue
39195            if offset_before not in offsets:
39196                offsets.append(offset_before)
39197            if offset_after not in offsets:
39198                offsets.append(offset_after)
39199",
39200        );
39201        let f = find_code(&code, "f").expect("missing f code");
39202        let ops: Vec<_> = f
39203            .instructions
39204            .iter()
39205            .map(|unit| unit.op)
39206            .filter(|op| !matches!(op, Instruction::Cache))
39207            .collect();
39208
39209        assert!(
39210            ops.windows(4).any(|window| {
39211                matches!(
39212                    window,
39213                    [
39214                        Instruction::PopTop,
39215                        Instruction::JumpBackward { .. }
39216                            | Instruction::JumpBackwardNoInterrupt { .. },
39217                        Instruction::LoadFastBorrow { .. }
39218                            | Instruction::LoadFast { .. }
39219                            | Instruction::LoadFastBorrowLoadFastBorrow { .. }
39220                            | Instruction::LoadFastLoadFast { .. },
39221                        Instruction::ContainsOp { .. }
39222                            | Instruction::LoadFastBorrow { .. }
39223                            | Instruction::LoadFast { .. },
39224                    ]
39225                )
39226            }),
39227            "chained-compare continue cleanup should fall through to the following body after one backedge, got ops={ops:?}"
39228        );
39229        assert!(
39230            !ops.windows(3).any(|window| {
39231                matches!(
39232                    window,
39233                    [
39234                        Instruction::PopTop,
39235                        Instruction::JumpBackward { .. }
39236                            | Instruction::JumpBackwardNoInterrupt { .. },
39237                        Instruction::JumpBackward { .. }
39238                            | Instruction::JumpBackwardNoInterrupt { .. },
39239                    ]
39240                )
39241            }),
39242            "chained-compare continue cleanup should not duplicate the loop backedge, got ops={ops:?}"
39243        );
39244    }
39245
39246    #[test]
39247    fn try_else_loop_if_body_keeps_cpython_fallthrough_before_backedge() {
39248        let code = compile_exec(
39249            "\
39250def f(self, ready, selector, key, input_view, os, BrokenPipeError):
39251    for key, events in ready:
39252        if key.fileobj is self.stdin:
39253            chunk = input_view[self._input_offset:self._input_offset + 1]
39254            try:
39255                self._input_offset += os.write(key.fd, chunk)
39256            except BrokenPipeError:
39257                selector.unregister(key.fileobj)
39258                key.fileobj.close()
39259            else:
39260                if self._input_offset >= len(input_view):
39261                    selector.unregister(key.fileobj)
39262                    key.fileobj.close()
39263        elif key.fileobj in (self.stdout, self.stderr):
39264            self.read(key)
39265",
39266        );
39267        let f = find_code(&code, "f").expect("missing f code");
39268        let ops: Vec<_> = f
39269            .instructions
39270            .iter()
39271            .map(|unit| unit.op)
39272            .filter(|op| !matches!(op, Instruction::Cache))
39273            .collect();
39274
39275        assert!(
39276            ops.windows(5).any(|window| {
39277                matches!(
39278                    window,
39279                    [
39280                        Instruction::CompareOp { .. },
39281                        Instruction::PopJumpIfFalse { .. },
39282                        Instruction::NotTaken,
39283                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
39284                        Instruction::LoadAttr { .. },
39285                    ]
39286                )
39287            }),
39288            "expected CPython-style try-else if body fallthrough before loop backedge, got ops={ops:?}"
39289        );
39290        assert!(
39291            !ops.windows(6).any(|window| {
39292                matches!(
39293                    window,
39294                    [
39295                        Instruction::CompareOp { .. },
39296                        Instruction::PopJumpIfTrue { .. },
39297                        Instruction::NotTaken,
39298                        Instruction::JumpBackward { .. }
39299                            | Instruction::JumpBackwardNoInterrupt { .. },
39300                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
39301                        Instruction::LoadAttr { .. },
39302                    ]
39303                )
39304            }),
39305            "unexpected inverted try-else conditional with loop backedge before body, got ops={ops:?}"
39306        );
39307    }
39308
39309    #[test]
39310    fn try_else_after_conditional_raise_keeps_loop_if_body_before_backedge() {
39311        let code = compile_exec(
39312            "\
39313def f(seq, flag, stat, OSError, pred, SpecialFileError):
39314    for i in seq:
39315        try:
39316            st = stat(i)
39317        except OSError:
39318            pass
39319        else:
39320            if pred(st.mode):
39321                raise SpecialFileError(i)
39322            if flag and i == 0:
39323                x = st.real
39324",
39325        );
39326        let f = find_code(&code, "f").expect("missing function code");
39327        let ops: Vec<_> = f
39328            .instructions
39329            .iter()
39330            .map(|unit| unit.op)
39331            .filter(|op| !matches!(op, Instruction::Cache))
39332            .collect();
39333
39334        assert!(
39335            ops.windows(7).any(|window| {
39336                matches!(
39337                    window,
39338                    [
39339                        Instruction::CompareOp { .. },
39340                        Instruction::PopJumpIfFalse { .. },
39341                        Instruction::NotTaken,
39342                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
39343                        Instruction::LoadAttr { .. },
39344                        Instruction::StoreFast { .. },
39345                        Instruction::JumpBackward { .. }
39346                            | Instruction::JumpBackwardNoInterrupt { .. },
39347                    ]
39348                )
39349            }),
39350            "try-else tail after conditional raise should keep CPython body-before-backedge layout, got ops={ops:?}"
39351        );
39352        assert!(
39353            !ops.windows(5).any(|window| {
39354                matches!(
39355                    window,
39356                    [
39357                        Instruction::CompareOp { .. },
39358                        Instruction::PopJumpIfTrue { .. },
39359                        Instruction::NotTaken,
39360                        Instruction::JumpBackward { .. }
39361                            | Instruction::JumpBackwardNoInterrupt { .. },
39362                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
39363                    ]
39364                )
39365            }),
39366            "try-else tail should not invert the inner condition before its body, got ops={ops:?}"
39367        );
39368    }
39369
39370    #[test]
39371    fn explicit_continue_after_return_orders_return_before_backedge() {
39372        let code = compile_exec(
39373            "\
39374def f(j, n):
39375    while j < n:
39376        if j < 0:
39377            return j
39378        continue
39379    return -1
39380",
39381        );
39382        let f = find_code(&code, "f").expect("missing f code");
39383        let ops: Vec<_> = f
39384            .instructions
39385            .iter()
39386            .filter(|unit| !matches!(unit.op, Instruction::Cache))
39387            .collect();
39388        let compare_idx = ops
39389            .iter()
39390            .enumerate()
39391            .filter(|(_, unit)| matches!(unit.op, Instruction::CompareOp { .. }))
39392            .nth(1)
39393            .map(|(idx, _)| idx)
39394            .expect("missing inner comparison");
39395        let cond_idx = ops[compare_idx + 1..]
39396            .iter()
39397            .position(|unit| {
39398                matches!(
39399                    unit.op,
39400                    Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
39401                )
39402            })
39403            .map(|idx| compare_idx + 1 + idx)
39404            .expect("missing conditional jump");
39405        assert!(
39406            matches!(ops[cond_idx].op, Instruction::PopJumpIfFalse { .. }),
39407            "expected CPython-style false jump to explicit continue, got ops={ops:?}"
39408        );
39409        let return_idx = ops[cond_idx + 1..]
39410            .iter()
39411            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
39412            .map(|idx| cond_idx + 1 + idx)
39413            .expect("missing return path");
39414        let jump_back_idx = ops[cond_idx + 1..]
39415            .iter()
39416            .position(|unit| matches!(unit.op, Instruction::JumpBackward { .. }))
39417            .map(|idx| cond_idx + 1 + idx)
39418            .expect("missing explicit continue backedge");
39419        assert!(
39420            return_idx < jump_back_idx,
39421            "expected return block before explicit continue backedge, got ops={ops:?}"
39422        );
39423    }
39424
39425    #[test]
39426    fn while_break_tail_does_not_duplicate_loop_false_return_epilogue() {
39427        let code = compile_exec(
39428            "\
39429def f(waiters):
39430    while waiters:
39431        waiter = waiters.popleft()
39432        if not waiter.done():
39433            waiter.set_result(None)
39434            break
39435",
39436        );
39437        let f = find_code(&code, "f").expect("missing f code");
39438        let ops: Vec<_> = f
39439            .instructions
39440            .iter()
39441            .filter(|unit| !matches!(unit.op, Instruction::Cache))
39442            .collect();
39443        let returns = ops
39444            .iter()
39445            .filter(|unit| matches!(unit.op, Instruction::ReturnValue))
39446            .count();
39447        assert_eq!(
39448            returns, 2,
39449            "CPython codegen_while() reuses the empty anchor block for USE_LABEL(end) when orelse is empty, so only the break fallthrough and loop-false epilogues remain, got ops={ops:?}",
39450        );
39451    }
39452
39453    #[test]
39454    fn implicit_while_tail_return_orders_backedge_before_return() {
39455        let code = compile_exec(
39456            "\
39457def f(self, j, n):
39458    while j < n:
39459        name, j = self.scan(j)
39460        if j < 0:
39461            return j
39462    return -1
39463",
39464        );
39465        let f = find_code(&code, "f").expect("missing f code");
39466        let ops: Vec<_> = f
39467            .instructions
39468            .iter()
39469            .filter(|unit| !matches!(unit.op, Instruction::Cache))
39470            .collect();
39471        let compare_idx = ops
39472            .iter()
39473            .enumerate()
39474            .filter(|(_, unit)| matches!(unit.op, Instruction::CompareOp { .. }))
39475            .nth(1)
39476            .map(|(idx, _)| idx)
39477            .expect("missing inner comparison");
39478        let cond_idx = ops[compare_idx + 1..]
39479            .iter()
39480            .position(|unit| {
39481                matches!(
39482                    unit.op,
39483                    Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
39484                )
39485            })
39486            .map(|idx| compare_idx + 1 + idx)
39487            .expect("missing conditional jump");
39488        assert!(
39489            matches!(ops[cond_idx].op, Instruction::PopJumpIfTrue { .. }),
39490            "expected CPython-style true jump to return, got ops={ops:?}"
39491        );
39492        let jump_back_idx = ops[cond_idx + 1..]
39493            .iter()
39494            .position(|unit| matches!(unit.op, Instruction::JumpBackward { .. }))
39495            .map(|idx| cond_idx + 1 + idx)
39496            .expect("missing implicit backedge");
39497        let return_idx = ops[cond_idx + 1..]
39498            .iter()
39499            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
39500            .map(|idx| cond_idx + 1 + idx)
39501            .expect("missing return path");
39502        assert!(
39503            jump_back_idx < return_idx,
39504            "expected implicit loop backedge before return block, got ops={ops:?}"
39505        );
39506    }
39507
39508    #[test]
39509    fn branch_arm_implicit_continue_keeps_return_before_backedge() {
39510        let code = compile_exec(
39511            "\
39512def f(self, j, n, c):
39513    while j < n:
39514        if c == 'x':
39515            j = self.step(j)
39516            if j < 0:
39517                return j
39518        elif c == 'y':
39519            j = j + 1
39520    return -1
39521",
39522        );
39523        let f = find_code(&code, "f").expect("missing f code");
39524        let ops: Vec<_> = f
39525            .instructions
39526            .iter()
39527            .filter(|unit| !matches!(unit.op, Instruction::Cache))
39528            .collect();
39529        let compare_idx = ops
39530            .iter()
39531            .enumerate()
39532            .filter(|(_, unit)| matches!(unit.op, Instruction::CompareOp { .. }))
39533            .nth(2)
39534            .map(|(idx, _)| idx)
39535            .expect("missing branch-arm return comparison");
39536        let cond_idx = ops[compare_idx + 1..]
39537            .iter()
39538            .position(|unit| {
39539                matches!(
39540                    unit.op,
39541                    Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
39542                )
39543            })
39544            .map(|idx| compare_idx + 1 + idx)
39545            .expect("missing branch-arm conditional jump");
39546        assert!(
39547            matches!(ops[cond_idx].op, Instruction::PopJumpIfFalse { .. }),
39548            "expected CPython-style false jump to branch-arm continuation, got ops={ops:?}"
39549        );
39550        let return_idx = ops[cond_idx + 1..]
39551            .iter()
39552            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
39553            .map(|idx| cond_idx + 1 + idx)
39554            .expect("missing branch-arm return path");
39555        let jump_back_idx = ops[cond_idx + 1..]
39556            .iter()
39557            .position(|unit| matches!(unit.op, Instruction::JumpBackward { .. }))
39558            .map(|idx| cond_idx + 1 + idx)
39559            .expect("missing branch-arm loop backedge");
39560        assert!(
39561            return_idx < jump_back_idx,
39562            "expected branch-arm return before loop backedge, got ops={ops:?}"
39563        );
39564    }
39565
39566    #[test]
39567    fn nested_implicit_while_tail_return_orders_backedge_before_return() {
39568        let code = compile_exec(
39569            "\
39570def f(self, rawdata, j, match):
39571    while 1:
39572        c = rawdata[j:j + 1]
39573        if c in \"'\\\"\":
39574            m = match(rawdata, j)
39575            if not m:
39576                return -1
39577            j = m.end()
39578        else:
39579            name, j = self.scan(j)
39580            if j < 0:
39581                return j
39582",
39583        );
39584        let f = find_code(&code, "f").expect("missing f code");
39585        let ops: Vec<_> = f
39586            .instructions
39587            .iter()
39588            .filter(|unit| !matches!(unit.op, Instruction::Cache))
39589            .collect();
39590        let compare_idx = ops
39591            .iter()
39592            .enumerate()
39593            .rfind(|(_, unit)| matches!(unit.op, Instruction::CompareOp { .. }))
39594            .map(|(idx, _)| idx)
39595            .expect("missing nested tail comparison");
39596        let cond_idx = ops[compare_idx + 1..]
39597            .iter()
39598            .position(|unit| {
39599                matches!(
39600                    unit.op,
39601                    Instruction::PopJumpIfFalse { .. } | Instruction::PopJumpIfTrue { .. }
39602                )
39603            })
39604            .map(|idx| compare_idx + 1 + idx)
39605            .expect("missing nested tail conditional jump");
39606        assert!(
39607            matches!(ops[cond_idx].op, Instruction::PopJumpIfTrue { .. }),
39608            "expected CPython-style true jump to nested return path, got ops={ops:?}"
39609        );
39610        let jump_back_idx = ops[cond_idx + 1..]
39611            .iter()
39612            .position(|unit| matches!(unit.op, Instruction::JumpBackward { .. }))
39613            .map(|idx| cond_idx + 1 + idx)
39614            .expect("missing nested tail loop backedge");
39615        let return_idx = ops[cond_idx + 1..]
39616            .iter()
39617            .position(|unit| matches!(unit.op, Instruction::ReturnValue))
39618            .map(|idx| cond_idx + 1 + idx)
39619            .expect("missing nested tail return path");
39620        assert!(
39621            jump_back_idx < return_idx,
39622            "expected nested implicit loop backedge before return block, got ops={ops:?}"
39623        );
39624    }
39625
39626    #[test]
39627    fn join_store_global_before_import_keeps_strong_load_fast() {
39628        let code = compile_exec(
39629            "\
39630def f(module=None):
39631    global ET
39632    if module is None:
39633        module = pyET
39634    ET = module
39635    from xml.etree import ElementPath
39636",
39637        );
39638        let f = find_code(&code, "f").expect("missing f code");
39639        let ops: Vec<_> = f
39640            .instructions
39641            .iter()
39642            .map(|unit| unit.op)
39643            .filter(|op| !matches!(op, Instruction::Cache))
39644            .collect();
39645
39646        assert!(
39647            ops.windows(2).any(|window| {
39648                matches!(
39649                    window,
39650                    [
39651                        Instruction::LoadFast { .. },
39652                        Instruction::StoreGlobal { .. },
39653                    ]
39654                )
39655            }),
39656            "expected CPython-style strong LOAD_FAST before join STORE_GLOBAL followed by import, got ops={ops:?}"
39657        );
39658    }
39659
39660    #[test]
39661    fn handler_resume_join_keeps_borrow_in_common_tail() {
39662        let code = compile_exec(
39663            "\
39664def f(p, errors, s, pos, look, final, escape_start, st):
39665    try:
39666        chr_codec = unicodedata.lookup('%s' % st)
39667    except LookupError as e:
39668        x = unicode_call_errorhandler(
39669            errors, 'unicodeescape', 'unknown Unicode character name', s, pos - 1, look + 1
39670        )
39671    else:
39672        x = chr_codec, look + 1
39673    p.append(x[0])
39674    pos = x[1]
39675    if not final:
39676        pos = escape_start
39677        return p, pos
39678    return unicode_call_errorhandler(
39679        errors, 'unicodeescape', 'unknown Unicode character name', s, pos - 1, look + 1
39680    )
39681",
39682        );
39683        let f = find_code(&code, "f").expect("missing f code");
39684        let append_idx = f
39685            .instructions
39686            .iter()
39687            .position(|unit| match unit.op {
39688                Instruction::LoadAttr { namei } => {
39689                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
39690                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "append"
39691                }
39692                _ => false,
39693            })
39694            .expect("missing append tail");
39695        let tail: Vec<_> = f.instructions[append_idx.saturating_sub(1)..]
39696            .iter()
39697            .map(|unit| unit.op)
39698            .filter(|op| !matches!(op, Instruction::Cache))
39699            .collect();
39700
39701        assert!(
39702            matches!(
39703                tail.as_slice(),
39704                [
39705                    Instruction::LoadFastBorrow { .. },
39706                    Instruction::LoadAttr { .. },
39707                    Instruction::LoadFastBorrow { .. },
39708                    ..,
39709                ]
39710            ),
39711            "expected handler resume common tail to start with borrowed append receiver/arg loads, got tail={tail:?}"
39712        );
39713        assert!(
39714            tail.iter().any(|op| {
39715                matches!(
39716                    op,
39717                    Instruction::LoadFastBorrowLoadFastBorrow { .. }
39718                        | Instruction::LoadFastBorrow { .. }
39719                )
39720            }),
39721            "expected handler resume common tail to keep borrowed LOAD_FAST ops, got tail={tail:?}"
39722        );
39723    }
39724
39725    #[test]
39726    fn multi_handler_guarded_resume_tail_keeps_borrow() {
39727        let code = compile_exec(
39728            "\
39729def f(a):
39730    try:
39731        g()
39732    except ValueError:
39733        pass
39734    except TypeError:
39735        pass
39736    if a:
39737        return a.x
39738    return 0
39739",
39740        );
39741        let f = find_code(&code, "f").expect("missing f code");
39742        let ops: Vec<_> = f
39743            .instructions
39744            .iter()
39745            .map(|unit| unit.op)
39746            .filter(|op| !matches!(op, Instruction::Cache))
39747            .collect();
39748
39749        assert!(
39750            ops.windows(5).any(|window| {
39751                matches!(
39752                    window,
39753                    [
39754                        Instruction::LoadFastBorrow { .. },
39755                        Instruction::ToBool,
39756                        Instruction::PopJumpIfFalse { .. },
39757                        Instruction::NotTaken,
39758                        Instruction::LoadFastBorrow { .. },
39759                    ]
39760                )
39761            }),
39762            "expected guarded resume tail to keep borrowed guard/body loads, got ops={ops:?}"
39763        );
39764        assert!(
39765            ops.windows(2).any(|window| {
39766                matches!(
39767                    window,
39768                    [
39769                        Instruction::LoadFastBorrow { .. },
39770                        Instruction::LoadAttr { .. }
39771                    ]
39772                )
39773            }),
39774            "expected guarded resume tail attr access to keep borrowed receiver, got ops={ops:?}"
39775        );
39776    }
39777
39778    #[test]
39779    fn multi_handler_method_tail_keeps_borrow() {
39780        let code = compile_exec(
39781            "\
39782def f(self, xs):
39783    for vals, expected in xs:
39784        try:
39785            actual = g(vals)
39786        except OverflowError:
39787            self.fail(expected)
39788        except ValueError:
39789            self.fail(expected)
39790        self.assertEqual(actual, expected)
39791",
39792        );
39793        let f = find_code(&code, "f").expect("missing f code");
39794        let ops: Vec<_> = f
39795            .instructions
39796            .iter()
39797            .map(|unit| unit.op)
39798            .filter(|op| !matches!(op, Instruction::Cache))
39799            .collect();
39800
39801        let assert_equal_idx = ops
39802            .iter()
39803            .position(|op| matches!(op, Instruction::LoadAttr { .. }))
39804            .expect("missing assertEqual LOAD_ATTR");
39805        let tail = &ops[assert_equal_idx.saturating_sub(1)..];
39806
39807        assert!(
39808            matches!(tail.first(), Some(Instruction::LoadFastBorrow { .. })),
39809            "expected multi-handler method-call tail receiver to keep LOAD_FAST_BORROW, got tail={tail:?}"
39810        );
39811        assert!(
39812            tail.iter()
39813                .any(|op| matches!(op, Instruction::LoadFastBorrow { .. })),
39814            "expected multi-handler method-call tail args to keep borrowed loads, got tail={tail:?}"
39815        );
39816    }
39817
39818    #[test]
39819    fn named_except_cleanup_loop_header_keeps_borrow_in_for_loop() {
39820        let code = compile_exec(
39821            "\
39822def f(args):
39823    for arg in args:
39824        try:
39825            _wm._setoption(arg)
39826        except _wm._OptionError as msg:
39827            print('Invalid -W option ignored:', msg, file=sys.stderr)
39828",
39829        );
39830        let f = find_code(&code, "f").expect("missing f code");
39831        let attr_idx = f
39832            .instructions
39833            .iter()
39834            .position(|unit| match unit.op {
39835                Instruction::LoadAttr { namei } => {
39836                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
39837                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "_setoption"
39838                }
39839                _ => false,
39840            })
39841            .expect("missing _setoption attr load");
39842        let window: Vec<_> = f.instructions[attr_idx + 1..]
39843            .iter()
39844            .map(|unit| unit.op)
39845            .filter(|op| !matches!(op, Instruction::Cache))
39846            .take(3)
39847            .collect();
39848        assert!(
39849            matches!(
39850                window.as_slice(),
39851                [
39852                    Instruction::LoadFastBorrow { .. },
39853                    Instruction::Call { .. },
39854                    Instruction::PopTop
39855                ]
39856            ),
39857            "expected loop body call to keep borrowed arg load after named-except cleanup, got window={window:?}"
39858        );
39859    }
39860
39861    #[test]
39862    fn multi_named_except_loop_header_keeps_borrow_for_normal_path() {
39863        let code = compile_exec(
39864            "\
39865def f(self):
39866    for badval in ['illegal', -1, 1 << 32]:
39867        class A:
39868            def __len__(self):
39869                return badval
39870        try:
39871            bool(A())
39872        except (Exception) as e_bool:
39873            try:
39874                len(A())
39875            except (Exception) as e_len:
39876                self.assertEqual(str(e_bool), str(e_len))
39877",
39878        );
39879        let f = find_code(&code, "f").expect("missing f code");
39880        let ops: Vec<_> = f
39881            .instructions
39882            .iter()
39883            .map(|unit| unit.op)
39884            .filter(|op| !matches!(op, Instruction::Cache))
39885            .collect();
39886
39887        assert!(
39888            ops.windows(4).any(|window| {
39889                matches!(
39890                    window,
39891                    [
39892                        Instruction::LoadBuildClass,
39893                        Instruction::PushNull,
39894                        Instruction::LoadFastBorrow { .. },
39895                        Instruction::BuildTuple { .. },
39896                    ]
39897                )
39898            }),
39899            "expected class closure setup in loop header to borrow badval, got ops={ops:?}"
39900        );
39901        assert!(
39902            ops.windows(5).any(|window| {
39903                matches!(
39904                    window,
39905                    [
39906                        Instruction::LoadFastBorrow { .. },
39907                        Instruction::PushNull,
39908                        Instruction::Call { .. },
39909                        Instruction::Call { .. },
39910                        Instruction::PopTop,
39911                    ]
39912                )
39913            }),
39914            "expected normal bool(A()) path in loop header to borrow A, got ops={ops:?}"
39915        );
39916    }
39917
39918    #[test]
39919    fn named_except_cleanup_simple_resume_tail_keeps_borrow() {
39920        let code = compile_exec(
39921            "\
39922def f(self):
39923    try:
39924        1 / 0
39925    except Exception as e:
39926        tb = e.__traceback__
39927    self.get_disassemble_as_string(tb.tb_frame.f_code, tb.tb_lasti)
39928",
39929        );
39930        let f = find_code(&code, "f").expect("missing f code");
39931        let instructions: Vec<_> = f
39932            .instructions
39933            .iter()
39934            .filter(|unit| !matches!(unit.op, Instruction::Cache))
39935            .collect();
39936        let attr_idx = instructions
39937            .iter()
39938            .position(|unit| match unit.op {
39939                Instruction::LoadAttr { namei } => {
39940                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
39941                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
39942                        == "get_disassemble_as_string"
39943                }
39944                _ => false,
39945            })
39946            .expect("missing LOAD_ATTR for get_disassemble_as_string");
39947        let ops: Vec<_> = instructions.iter().map(|unit| unit.op).collect();
39948        assert!(
39949            matches!(
39950                ops.get(attr_idx - 1),
39951                Some(Instruction::LoadFastBorrow { .. })
39952            ),
39953            "expected named-except resume tail to keep borrowed self load, got ops={ops:?}"
39954        );
39955        assert!(
39956            matches!(
39957                ops.get(attr_idx + 4),
39958                Some(Instruction::LoadFastBorrow { .. })
39959            ),
39960            "expected named-except resume tail to keep borrowed tb load, got ops={ops:?}"
39961        );
39962    }
39963
39964    #[test]
39965    fn named_except_cleanup_conditional_raise_tail_keeps_borrow() {
39966        let code = compile_exec(
39967            "\
39968def f(self):
39969    try:
39970        output = self.trace()
39971        output = output.strip()
39972    except (A, B, C) as fnfe:
39973        output = str(fnfe)
39974    if output != 'probe: success':
39975        raise E('{} {}'.format(self.command[0], output))
39976",
39977        );
39978        let f = find_code(&code, "f").expect("missing f code");
39979        let instructions: Vec<_> = f
39980            .instructions
39981            .iter()
39982            .filter(|unit| !matches!(unit.op, Instruction::Cache))
39983            .collect();
39984        let raise_idx = instructions
39985            .iter()
39986            .position(|unit| matches!(unit.op, Instruction::RaiseVarargs { .. }))
39987            .expect("missing conditional raise");
39988        let handler_start = instructions
39989            .iter()
39990            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
39991            .expect("missing handler entry");
39992        let tail = &instructions[..handler_start.min(raise_idx)];
39993
39994        assert!(
39995            tail.iter().any(|unit| {
39996                matches!(
39997                    unit.op,
39998                    Instruction::LoadFastBorrow { .. }
39999                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
40000                )
40001            }),
40002            "named-except cleanup conditional raise tail should keep borrowed loads, got tail={tail:?}"
40003        );
40004        assert!(
40005            !tail
40006                .iter()
40007                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
40008            "named-except cleanup conditional raise tail should not force strong LOAD_FAST, got tail={tail:?}"
40009        );
40010    }
40011
40012    #[test]
40013    fn with_suppress_named_except_resume_tail_uses_strong_loads() {
40014        let code = compile_exec(
40015            "\
40016def f(self, cm, E):
40017    try:
40018        with cm:
40019            pass
40020    except E as e:
40021        frames = e
40022    self.x(frames)
40023    self.y(frames)
40024",
40025        );
40026        let f = find_code(&code, "f").expect("missing f code");
40027        let instructions: Vec<_> = f
40028            .instructions
40029            .iter()
40030            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40031            .collect();
40032        let first_tail_attr = instructions
40033            .iter()
40034            .position(|unit| match unit.op {
40035                Instruction::LoadAttr { namei } => {
40036                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
40037                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "x"
40038                }
40039                _ => false,
40040            })
40041            .expect("missing x attr load");
40042        let handler_start = instructions
40043            .iter()
40044            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
40045            .expect("missing handler entry");
40046        let tail = &instructions[first_tail_attr.saturating_sub(1)..handler_start];
40047
40048        assert!(
40049            tail.iter()
40050                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
40051            "expected with-suppress/named-except resume tail to use strong LOAD_FAST, got tail={tail:?}"
40052        );
40053        assert!(
40054            tail.iter().all(|unit| {
40055                !matches!(
40056                    unit.op,
40057                    Instruction::LoadFastBorrow { .. }
40058                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
40059                )
40060            }),
40061            "expected with-suppress/named-except resume tail not to borrow, got tail={tail:?}"
40062        );
40063    }
40064
40065    #[test]
40066    fn with_named_except_return_value_keeps_borrow() {
40067        let code = compile_exec(
40068            "\
40069def f(self, b, BlockingIOError):
40070    with self._write_lock:
40071        written = len(self._write_buf)
40072        if len(self._write_buf) > self.buffer_size:
40073            try:
40074                self._flush_unlocked()
40075            except BlockingIOError as e:
40076                if len(self._write_buf) > self.buffer_size:
40077                    overage = len(self._write_buf) - self.buffer_size
40078                    written -= overage
40079                    raise BlockingIOError(e.errno, e.strerror, written)
40080        return written
40081",
40082        );
40083        let f = find_code(&code, "f").expect("missing f code");
40084        let instructions: Vec<_> = f
40085            .instructions
40086            .iter()
40087            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40088            .collect();
40089        let with_exit_start = instructions
40090            .windows(3)
40091            .position(|window| {
40092                matches!(
40093                    window,
40094                    [
40095                        CodeUnit {
40096                            op: Instruction::Swap { .. },
40097                            ..
40098                        },
40099                        CodeUnit {
40100                            op: Instruction::Swap { .. },
40101                            ..
40102                        },
40103                        CodeUnit {
40104                            op: Instruction::LoadConst { .. },
40105                            ..
40106                        },
40107                    ]
40108                )
40109            })
40110            .expect("missing with-exit cleanup");
40111        let return_value_load = instructions
40112            .get(with_exit_start.saturating_sub(1))
40113            .expect("missing return value load");
40114        let arg = OpArg::new(u32::from(u8::from(return_value_load.arg)));
40115
40116        assert!(
40117            matches!(
40118                return_value_load.op,
40119                Instruction::LoadFastBorrow { var_num }
40120                    if f.varnames[usize::from(var_num.get(arg))] == "written"
40121            ),
40122            "return value loaded through with-exit cleanup should keep borrowed written, got instructions={instructions:?}"
40123        );
40124    }
40125
40126    #[test]
40127    fn with_final_conditional_return_preserves_fallthrough_cleanup_nop() {
40128        let code = compile_exec(
40129            "\
40130def f(self):
40131    with self.lock:
40132        if self.raw is None or self.closed:
40133            return
40134    self.flush()
40135",
40136        );
40137        let f = find_code(&code, "f").expect("missing f code");
40138        let ops: Vec<_> = f
40139            .instructions
40140            .iter()
40141            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40142            .map(|unit| unit.op)
40143            .collect();
40144
40145        assert!(
40146            ops.windows(6).any(|window| {
40147                matches!(
40148                    window,
40149                    [
40150                        Instruction::Nop,
40151                        Instruction::LoadConst { .. },
40152                        Instruction::LoadConst { .. },
40153                        Instruction::LoadConst { .. },
40154                        Instruction::Call { .. },
40155                        Instruction::PopTop,
40156                    ]
40157                )
40158            }),
40159            "with fallthrough cleanup should preserve the CPython POP_BLOCK NOP, got ops={ops:?}"
40160        );
40161    }
40162
40163    #[test]
40164    fn with_while_fallthrough_preserves_cleanup_nop() {
40165        let code = compile_exec(
40166            "\
40167def f(cm, source):
40168    with cm as out:
40169        s = source.read()
40170        while s:
40171            out.write(s)
40172            s = source.read()
40173    source.close()
40174",
40175        );
40176        let f = find_code(&code, "f").expect("missing f code");
40177        let ops: Vec<_> = f
40178            .instructions
40179            .iter()
40180            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40181            .map(|unit| unit.op)
40182            .collect();
40183
40184        assert!(
40185            ops.windows(6).any(|window| {
40186                matches!(
40187                    window,
40188                    [
40189                        Instruction::Nop,
40190                        Instruction::LoadConst { .. },
40191                        Instruction::LoadConst { .. },
40192                        Instruction::LoadConst { .. },
40193                        Instruction::Call { .. },
40194                        Instruction::PopTop,
40195                    ]
40196                )
40197            }),
40198            "with cleanup after while fallthrough should preserve the CPython POP_BLOCK NOP, got ops={ops:?}"
40199        );
40200    }
40201
40202    #[test]
40203    fn with_for_fallthrough_drops_cleanup_nop() {
40204        let code = compile_exec(
40205            "\
40206def f(cm, xs, g):
40207    with cm:
40208        for x in xs:
40209            g(x)
40210    return None
40211",
40212        );
40213        let f = find_code(&code, "f").expect("missing f code");
40214        let ops: Vec<_> = f
40215            .instructions
40216            .iter()
40217            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40218            .map(|unit| unit.op)
40219            .collect();
40220
40221        assert!(
40222            ops.windows(6).any(|window| {
40223                matches!(
40224                    window,
40225                    [
40226                        Instruction::EndFor,
40227                        Instruction::PopIter,
40228                        Instruction::LoadConst { .. },
40229                        Instruction::LoadConst { .. },
40230                        Instruction::LoadConst { .. },
40231                        Instruction::Call { .. },
40232                    ]
40233                )
40234            }),
40235            "with cleanup after for fallthrough should directly follow END_FOR/POP_ITER like CPython, got ops={ops:?}"
40236        );
40237        assert!(
40238            !ops.windows(7).any(|window| {
40239                matches!(
40240                    window,
40241                    [
40242                        Instruction::EndFor,
40243                        Instruction::PopIter,
40244                        Instruction::Nop,
40245                        Instruction::LoadConst { .. },
40246                        Instruction::LoadConst { .. },
40247                        Instruction::LoadConst { .. },
40248                        Instruction::Call { .. },
40249                    ]
40250                )
40251            }),
40252            "with cleanup after for fallthrough should not preserve a POP_BLOCK NOP, got ops={ops:?}"
40253        );
40254    }
40255
40256    #[test]
40257    fn with_while_true_break_drops_cleanup_nop() {
40258        let code = compile_exec(
40259            "\
40260def f(cm, source):
40261    with cm as out:
40262        while True:
40263            data = source.read()
40264            if not data:
40265                break
40266            out.write(data)
40267    source.close()
40268",
40269        );
40270        let f = find_code(&code, "f").expect("missing f code");
40271        let ops: Vec<_> = f
40272            .instructions
40273            .iter()
40274            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40275            .map(|unit| unit.op)
40276            .collect();
40277
40278        assert!(
40279            !ops.windows(5).any(|window| {
40280                matches!(
40281                    window,
40282                    [
40283                        Instruction::Nop,
40284                        Instruction::LoadConst { .. },
40285                        Instruction::LoadConst { .. },
40286                        Instruction::LoadConst { .. },
40287                        Instruction::Call { .. },
40288                    ]
40289                )
40290            }),
40291            "with cleanup after while True break should not preserve a POP_BLOCK NOP, got ops={ops:?}"
40292        );
40293    }
40294
40295    #[test]
40296    fn multi_with_while_true_try_except_drops_outer_cleanup_nop() {
40297        let code = compile_exec(
40298            "\
40299def f(cm1, cm2, g, E):
40300    with cm1, cm2:
40301        while True:
40302            try:
40303                g()
40304            except E:
40305                pass
40306",
40307        );
40308        let f = find_code(&code, "f").expect("missing f code");
40309        let ops: Vec<_> = f
40310            .instructions
40311            .iter()
40312            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40313            .map(|unit| unit.op)
40314            .collect();
40315
40316        assert!(
40317            ops.windows(6).any(|window| {
40318                matches!(
40319                    window,
40320                    [
40321                        Instruction::Copy { .. },
40322                        Instruction::PopExcept,
40323                        Instruction::Reraise { .. },
40324                        Instruction::LoadConst { .. },
40325                        Instruction::LoadConst { .. },
40326                        Instruction::LoadConst { .. },
40327                    ]
40328                )
40329            }),
40330            "outer with cleanup after an infinite inner with body should follow the inner cleanup directly like CPython, got ops={ops:?}"
40331        );
40332        assert!(
40333            !ops.windows(7).any(|window| {
40334                matches!(
40335                    window,
40336                    [
40337                        Instruction::Copy { .. },
40338                        Instruction::PopExcept,
40339                        Instruction::Reraise { .. },
40340                        Instruction::Nop,
40341                        Instruction::LoadConst { .. },
40342                        Instruction::LoadConst { .. },
40343                        Instruction::LoadConst { .. },
40344                    ]
40345                )
40346            }),
40347            "outer with cleanup after an infinite inner with body should not keep a POP_BLOCK NOP, got ops={ops:?}"
40348        );
40349    }
40350
40351    #[test]
40352    fn with_final_assert_preserves_cleanup_nop() {
40353        let code = compile_exec(
40354            "\
40355def f(cm, dst):
40356    with cm:
40357        assert not dst.closed
40358    return dst
40359",
40360        );
40361        let f = find_code(&code, "f").expect("missing f code");
40362        let ops: Vec<_> = f
40363            .instructions
40364            .iter()
40365            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40366            .map(|unit| unit.op)
40367            .collect();
40368
40369        assert!(
40370            ops.windows(5).any(|window| {
40371                matches!(
40372                    window,
40373                    [
40374                        Instruction::Nop,
40375                        Instruction::LoadConst { .. },
40376                        Instruction::LoadConst { .. },
40377                        Instruction::LoadConst { .. },
40378                        Instruction::Call { .. },
40379                    ]
40380                )
40381            }),
40382            "with cleanup after a final assert should preserve CPython's POP_BLOCK NOP anchor, got ops={ops:?}"
40383        );
40384    }
40385
40386    #[test]
40387    fn named_except_conditional_reraise_final_store_attr_keeps_borrow() {
40388        let code = compile_exec(
40389            "\
40390def f(self, fd, file, closefd, owned_fd, OSError, AttributeError, errno, os, stat, _setmode):
40391    try:
40392        self._closefd = closefd
40393        self._stat_atopen = os.fstat(fd)
40394        try:
40395            if stat.S_ISDIR(self._stat_atopen.st_mode):
40396                raise IsADirectoryError(errno.EISDIR, os.strerror(errno.EISDIR), file)
40397        except AttributeError:
40398            pass
40399        if _setmode:
40400            _setmode(fd, os.O_BINARY)
40401        self.name = file
40402        if self._appending:
40403            try:
40404                os.lseek(fd, 0, SEEK_END)
40405            except OSError as e:
40406                if e.errno != errno.ESPIPE:
40407                    raise
40408    except:
40409        self._stat_atopen = None
40410        if owned_fd is not None:
40411            os.close(owned_fd)
40412        raise
40413    self._fd = fd
40414",
40415        );
40416        let f = find_code(&code, "f").expect("missing f code");
40417        let instructions: Vec<_> = f
40418            .instructions
40419            .iter()
40420            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40421            .collect();
40422        let load_attr_name = |unit: &&bytecode::CodeUnit, expected: &str| match unit.op {
40423            Instruction::LoadAttr { namei } => {
40424                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
40425                let load_attr = namei.get(arg);
40426                f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == expected
40427            }
40428            _ => false,
40429        };
40430        let lseek_attr = instructions
40431            .iter()
40432            .position(|unit| load_attr_name(unit, "lseek"))
40433            .expect("missing lseek load");
40434        let store_attr = instructions
40435            .iter()
40436            .position(|unit| match unit.op {
40437                Instruction::StoreAttr { namei } => {
40438                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
40439                    f.names[usize::try_from(namei.get(arg)).unwrap()].as_str() == "_fd"
40440                }
40441                _ => false,
40442            })
40443            .expect("missing _fd store");
40444
40445        assert!(
40446            matches!(
40447                instructions
40448                    .get(lseek_attr.saturating_sub(1))
40449                    .map(|unit| unit.op),
40450                Some(Instruction::LoadFastBorrow { .. })
40451            ),
40452            "nested conditional reraise try body should keep borrowed os receiver, got instructions={instructions:?}"
40453        );
40454        assert!(
40455            matches!(
40456                instructions.get(lseek_attr + 1).map(|unit| unit.op),
40457                Some(Instruction::LoadFastBorrow { .. })
40458            ),
40459            "nested conditional reraise try body should keep borrowed fd argument, got instructions={instructions:?}"
40460        );
40461        assert!(
40462            matches!(
40463                instructions
40464                    .get(store_attr.saturating_sub(1))
40465                    .map(|unit| unit.op),
40466                Some(Instruction::LoadFastBorrowLoadFastBorrow { .. })
40467            ),
40468            "conditional reraise named-except final store tail should keep borrowed pair loads, got instructions={instructions:?}"
40469        );
40470    }
40471
40472    #[test]
40473    fn with_except_else_with_resume_loop_tail_uses_strong_loads() {
40474        let code = compile_exec(
40475            "\
40476def f(self, cm, E):
40477    with cm:
40478        try:
40479            g()
40480        except E:
40481            pass
40482        else:
40483            with self.z(E):
40484                h()
40485        for _ in support.sleeping_retry(support.SHORT_TIMEOUT, 'not ready'):
40486            if self.x:
40487                break
40488        self.y()
40489",
40490        );
40491        let f = find_code(&code, "f").expect("missing f code");
40492        let instructions: Vec<_> = f
40493            .instructions
40494            .iter()
40495            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40496            .collect();
40497        let get_iter = instructions
40498            .iter()
40499            .position(|unit| matches!(unit.op, Instruction::GetIter))
40500            .expect("missing loop iterator");
40501        let handler_start = instructions
40502            .iter()
40503            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
40504            .expect("missing handler entry");
40505        let tail = &instructions[get_iter.saturating_sub(1)..handler_start];
40506
40507        assert!(
40508            tail.iter()
40509                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
40510            "expected with except/else-with resume loop tail to use strong LOAD_FAST ops, got tail={tail:?}"
40511        );
40512        assert!(
40513            tail.iter().all(|unit| {
40514                !matches!(
40515                    unit.op,
40516                    Instruction::LoadFastBorrow { .. }
40517                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
40518                )
40519            }),
40520            "with except/else-with resume loop tail should not borrow LOAD_FAST ops, got tail={tail:?}"
40521        );
40522    }
40523
40524    #[test]
40525    fn final_with_try_except_resume_loop_tail_uses_strong_loads() {
40526        let code = compile_exec(
40527            r#"
40528def f(resources, valid_zones, TZPATH, os):
40529    try:
40530        with resources.open("r") as f:
40531            pass
40532    except Exception:
40533        pass
40534    for tz_root in TZPATH:
40535        if not os.path.exists(tz_root):
40536            continue
40537        valid_zones.add(tz_root)
40538    return valid_zones
40539"#,
40540        );
40541        let f = find_code(&code, "f").expect("missing f code");
40542        let instructions: Vec<_> = f
40543            .instructions
40544            .iter()
40545            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40546            .collect();
40547        let get_iter = instructions
40548            .iter()
40549            .position(|unit| matches!(unit.op, Instruction::GetIter))
40550            .expect("missing post-try loop iterator");
40551        let tail = &instructions[get_iter.saturating_sub(1)..];
40552        let load_fast_name = |unit: &&bytecode::CodeUnit| match unit.op {
40553            Instruction::LoadFast { var_num } => {
40554                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
40555                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
40556            }
40557            _ => None,
40558        };
40559        let borrowed_name = |unit: &&bytecode::CodeUnit| match unit.op {
40560            Instruction::LoadFastBorrow { var_num } => {
40561                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
40562                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
40563            }
40564            _ => None,
40565        };
40566
40567        for name in ["TZPATH", "os", "tz_root", "valid_zones"] {
40568            assert!(
40569                tail.iter()
40570                    .filter_map(load_fast_name)
40571                    .any(|loaded| loaded == name),
40572                "expected CPython-style strong LOAD_FAST for {name} after final with/except resume, got tail={tail:?}",
40573            );
40574            assert!(
40575                tail.iter()
40576                    .filter_map(borrowed_name)
40577                    .all(|loaded| loaded != name),
40578                "final with/except resume loop tail should not borrow {name}, got tail={tail:?}",
40579            );
40580        }
40581    }
40582
40583    #[test]
40584    fn finally_ending_try_except_resume_tail_uses_strong_loads() {
40585        let code = compile_exec(
40586            r#"
40587def f(self, fobj, unlink, TESTFN, C):
40588    try:
40589        fobj.write(1)
40590    finally:
40591        fobj.close()
40592        try:
40593            unlink(TESTFN)
40594        except OSError:
40595            pass
40596    a, b = C(2), C(3)
40597    self.assertEqual((a, b), (1, 2))
40598"#,
40599        );
40600        let f = find_code(&code, "f").expect("missing f code");
40601        let instructions: Vec<_> = f
40602            .instructions
40603            .iter()
40604            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40605            .collect();
40606        let assert_equal = instructions
40607            .iter()
40608            .position(|unit| match unit.op {
40609                Instruction::LoadAttr { namei } => {
40610                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
40611                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
40612                        == "assertEqual"
40613                }
40614                _ => false,
40615            })
40616            .expect("missing assertEqual load");
40617        let handler_start = instructions
40618            .iter()
40619            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
40620            .expect("missing exception path");
40621        let tail = &instructions[assert_equal.saturating_sub(1)..handler_start];
40622        let is_strong_pair = |unit: &&bytecode::CodeUnit, left_name: &str, right_name: &str| {
40623            let Instruction::LoadFastLoadFast { var_nums } = unit.op else {
40624                return false;
40625            };
40626            let arg = OpArg::new(u32::from(u8::from(unit.arg)));
40627            let (left, right) = var_nums.get(arg).indexes();
40628            f.varnames[usize::from(left)] == left_name
40629                && f.varnames[usize::from(right)] == right_name
40630        };
40631
40632        assert!(
40633            tail.iter()
40634                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
40635            "expected finally/try-except resume tail to use strong LOAD_FAST ops, got tail={tail:?}"
40636        );
40637        assert!(
40638            tail.iter().any(|unit| is_strong_pair(unit, "a", "b")),
40639            "expected finally/try-except resume tuple to use strong LOAD_FAST_LOAD_FAST, got tail={tail:?}"
40640        );
40641        assert!(
40642            tail.iter().all(|unit| {
40643                !matches!(
40644                    unit.op,
40645                    Instruction::LoadFastBorrow { .. }
40646                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
40647                )
40648            }),
40649            "finally/try-except resume tail should not borrow LOAD_FAST ops, got tail={tail:?}"
40650        );
40651    }
40652
40653    #[test]
40654    fn try_finally_bare_reraise_handler_resume_tail_uses_strong_loads() {
40655        let code = compile_exec(
40656            "\
40657def f(self, os, alive_r, alive_w, address, pid):
40658    try:
40659        pid = g()
40660    except:
40661        os.close(alive_w)
40662        raise
40663    finally:
40664        os.close(alive_r)
40665    self.address = address
40666    self.alive_w = alive_w
40667    self.pid = pid
40668",
40669        );
40670        let f = find_code(&code, "f").expect("missing f code");
40671        let instructions: Vec<_> = f
40672            .instructions
40673            .iter()
40674            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40675            .collect();
40676        let close_attr = instructions
40677            .iter()
40678            .position(|unit| match unit.op {
40679                Instruction::LoadAttr { namei } => {
40680                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
40681                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "close"
40682                }
40683                _ => false,
40684            })
40685            .expect("missing close load");
40686        let handler_start = instructions
40687            .iter()
40688            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
40689            .expect("missing exception path");
40690        let tail = &instructions[close_attr.saturating_sub(1)..handler_start];
40691
40692        assert!(
40693            tail.iter()
40694                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
40695            "bare-reraise try/finally resume tail should use strong LOAD_FAST ops, got tail={tail:?}"
40696        );
40697        assert!(
40698            tail.iter().all(|unit| {
40699                !matches!(
40700                    unit.op,
40701                    Instruction::LoadFastBorrow { .. }
40702                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
40703                )
40704            }),
40705            "bare-reraise try/finally resume tail should not borrow LOAD_FAST ops, got tail={tail:?}"
40706        );
40707    }
40708
40709    #[test]
40710    fn typed_except_return_resume_tail_uses_strong_loads() {
40711        let code = compile_exec(
40712            "\
40713def f(resource, desired_fds, max_fds):
40714    try:
40715        import math
40716    except ImportError:
40717        return None
40718    fd_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
40719    if fd_limit < desired_fds and fd_limit < max_fds:
40720        return desired_fds, max_fds
40721    return fd_limit
40722",
40723        );
40724        let f = find_code(&code, "f").expect("missing f code");
40725        let instructions: Vec<_> = f
40726            .instructions
40727            .iter()
40728            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40729            .collect();
40730        let getrlimit_attr = instructions
40731            .iter()
40732            .position(|unit| match unit.op {
40733                Instruction::LoadAttr { namei } => {
40734                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
40735                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "getrlimit"
40736                }
40737                _ => false,
40738            })
40739            .expect("missing getrlimit load");
40740        let handler_start = instructions
40741            .iter()
40742            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
40743            .expect("missing exception path");
40744        let tail = &instructions[getrlimit_attr.saturating_sub(1)..handler_start];
40745
40746        assert!(
40747            tail.iter()
40748                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
40749            "typed except-return resume tail should use strong LOAD_FAST ops, got tail={tail:?}"
40750        );
40751        assert!(
40752            tail.iter().all(|unit| {
40753                !matches!(
40754                    unit.op,
40755                    Instruction::LoadFastBorrow { .. }
40756                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
40757                )
40758            }),
40759            "typed except-return resume tail should not borrow LOAD_FAST ops, got tail={tail:?}"
40760        );
40761    }
40762
40763    #[test]
40764    fn resuming_except_before_try_preserves_next_try_entry_barrier() {
40765        let code = compile_exec(
40766            "\
40767def f(scan_once, s, end, _ws, _w):
40768    try:
40769        if s[end] in _ws:
40770            end = _w(s, end + 1).end()
40771    except IndexError:
40772        pass
40773    try:
40774        value, end = scan_once(s, end)
40775    except StopIteration as err:
40776        raise ValueError(s, err.value) from None
40777    return value, end
40778",
40779        );
40780        let f = find_code(&code, "f").expect("missing f code");
40781        let instructions: Vec<_> = f
40782            .instructions
40783            .iter()
40784            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40785            .collect();
40786        let scan_once_load = instructions
40787            .iter()
40788            .position(|unit| match unit.op {
40789                Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
40790                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
40791                    f.varnames[usize::from(var_num.get(arg))].as_str() == "scan_once"
40792                }
40793                _ => false,
40794            })
40795            .expect("missing scan_once load");
40796        let scan_tail = &instructions[scan_once_load..scan_once_load + 4];
40797
40798        assert!(
40799            scan_tail.iter().any(|unit| {
40800                matches!(
40801                    unit.op,
40802                    Instruction::LoadFast { .. } | Instruction::LoadFastLoadFast { .. }
40803                )
40804            }),
40805            "resuming except before another try should enter next try with strong LOAD_FAST ops, got scan_tail={scan_tail:?}"
40806        );
40807        assert!(
40808            scan_tail.iter().all(|unit| {
40809                !matches!(
40810                    unit.op,
40811                    Instruction::LoadFastBorrow { .. }
40812                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
40813                )
40814            }),
40815            "resuming except before another try should not borrow next try entry loads, got scan_tail={scan_tail:?}"
40816        );
40817    }
40818
40819    #[test]
40820    fn simple_except_before_try_keeps_next_try_entry_borrowed() {
40821        let code = compile_exec(
40822            "\
40823def f(scan_once, s, end):
40824    try:
40825        g(s, end)
40826    except IndexError:
40827        pass
40828    try:
40829        value, end = scan_once(s, end)
40830    except StopIteration:
40831        pass
40832    return value, end
40833",
40834        );
40835        let f = find_code(&code, "f").expect("missing f code");
40836        let instructions: Vec<_> = f
40837            .instructions
40838            .iter()
40839            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40840            .collect();
40841        let scan_once_load = instructions
40842            .iter()
40843            .position(|unit| match unit.op {
40844                Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
40845                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
40846                    f.varnames[usize::from(var_num.get(arg))].as_str() == "scan_once"
40847                }
40848                _ => false,
40849            })
40850            .expect("missing scan_once load");
40851        let scan_tail = &instructions[scan_once_load..scan_once_load + 4];
40852
40853        assert!(
40854            scan_tail.iter().any(|unit| {
40855                matches!(
40856                    unit.op,
40857                    Instruction::LoadFastBorrow { .. }
40858                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
40859                )
40860            }),
40861            "simple except before another try should keep next try entry borrowed, got scan_tail={scan_tail:?}"
40862        );
40863        assert!(
40864            !scan_tail.iter().any(|unit| {
40865                matches!(
40866                    unit.op,
40867                    Instruction::LoadFast { .. } | Instruction::LoadFastLoadFast { .. }
40868                )
40869            }),
40870            "simple except before another try should not force strong LOAD_FAST, got scan_tail={scan_tail:?}"
40871        );
40872    }
40873
40874    #[test]
40875    fn loop_break_except_before_try_preserves_next_try_entry_barrier() {
40876        let code = compile_exec(
40877            "\
40878def f(scan_once, seq1, seq2, n):
40879    for i in range(n):
40880        try:
40881            item1 = seq1[i]
40882        except (TypeError, IndexError, NotImplementedError):
40883            break
40884        try:
40885            item2 = seq2[i]
40886        except (TypeError, IndexError, NotImplementedError):
40887            break
40888    return item1, item2
40889",
40890        );
40891        let f = find_code(&code, "f").expect("missing f code");
40892        let instructions: Vec<_> = f
40893            .instructions
40894            .iter()
40895            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40896            .collect();
40897        let second_subscript = instructions
40898            .iter()
40899            .position(|unit| {
40900                matches!(
40901                    unit.op,
40902                    Instruction::BinaryOp { op }
40903                        if op.get(OpArg::new(u32::from(u8::from(unit.arg))))
40904                            == oparg::BinaryOperator::Subscr
40905                )
40906            })
40907            .expect("missing first subscript");
40908        let second_subscript = instructions[second_subscript + 1..]
40909            .iter()
40910            .position(|unit| {
40911                matches!(
40912                    unit.op,
40913                    Instruction::BinaryOp { op }
40914                        if op.get(OpArg::new(u32::from(u8::from(unit.arg))))
40915                            == oparg::BinaryOperator::Subscr
40916                )
40917            })
40918            .map(|idx| idx + second_subscript + 1)
40919            .expect("missing second subscript");
40920        let scan_tail = &instructions[second_subscript.saturating_sub(2)..second_subscript];
40921
40922        assert!(
40923            scan_tail.iter().any(|unit| {
40924                matches!(
40925                    unit.op,
40926                    Instruction::LoadFast { .. } | Instruction::LoadFastLoadFast { .. }
40927                )
40928            }),
40929            "loop break except before another try should keep next try entry strong, got scan_tail={scan_tail:?}"
40930        );
40931        assert!(
40932            !scan_tail.iter().any(|unit| {
40933                matches!(
40934                    unit.op,
40935                    Instruction::LoadFastBorrow { .. }
40936                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
40937                )
40938            }),
40939            "loop break except before another try should not borrow next try entry loads, got scan_tail={scan_tail:?}"
40940        );
40941    }
40942
40943    #[test]
40944    fn plain_with_then_global_loop_tail_keeps_borrow() {
40945        let code = compile_exec(
40946            "\
40947def f(self, cm):
40948    with cm:
40949        self.x()
40950    for value in ITEMS:
40951        self.y(value)
40952",
40953        );
40954        let f = find_code(&code, "f").expect("missing f code");
40955        let instructions: Vec<_> = f
40956            .instructions
40957            .iter()
40958            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40959            .collect();
40960        let y_attr = instructions
40961            .iter()
40962            .position(|unit| match unit.op {
40963                Instruction::LoadAttr { namei } => {
40964                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
40965                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "y"
40966                }
40967                _ => false,
40968            })
40969            .expect("missing y attr load");
40970
40971        assert!(
40972            matches!(
40973                instructions
40974                    .get(y_attr.saturating_sub(1))
40975                    .map(|unit| unit.op),
40976                Some(Instruction::LoadFastBorrow { .. })
40977            ),
40978            "plain with/global-loop tail should keep CPython-style borrowed self load, got instructions={instructions:?}"
40979        );
40980    }
40981
40982    #[test]
40983    fn context_manager_for_join_tail_keeps_borrow() {
40984        let code = compile_exec(
40985            "\
40986def f(self, factory):
40987    with factory() as e:
40988        executor = e
40989        self.x(e)
40990    for t in executor._threads:
40991        t.join()
40992",
40993        );
40994        let f = find_code(&code, "f").expect("missing f code");
40995        let instructions: Vec<_> = f
40996            .instructions
40997            .iter()
40998            .filter(|unit| !matches!(unit.op, Instruction::Cache))
40999            .collect();
41000        let join_attr = instructions
41001            .iter()
41002            .position(|unit| match unit.op {
41003                Instruction::LoadAttr { namei } => {
41004                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
41005                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "join"
41006                }
41007                _ => false,
41008            })
41009            .expect("missing join attr load");
41010
41011        assert!(
41012            matches!(
41013                instructions
41014                    .get(join_attr.saturating_sub(1))
41015                    .map(|unit| unit.op),
41016                Some(Instruction::LoadFastBorrow { .. })
41017            ),
41018            "context-manager for-join tail should keep CPython-style borrowed t load, got instructions={instructions:?}"
41019        );
41020    }
41021
41022    #[test]
41023    fn with_except_resume_normal_tail_uses_strong_loads() {
41024        let code = compile_exec(
41025            "\
41026def f(self, cm, E):
41027    try:
41028        with self.assertRaises(E):
41029            with cm:
41030                h()
41031    except TimeoutError:
41032        self._fail_on_deadlock(cm)
41033    cm.shutdown(wait=True)
41034",
41035        );
41036        let f = find_code(&code, "f").expect("missing f code");
41037        let instructions: Vec<_> = f
41038            .instructions
41039            .iter()
41040            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41041            .collect();
41042        let shutdown_attr = instructions
41043            .iter()
41044            .position(|unit| match unit.op {
41045                Instruction::LoadAttr { namei } => {
41046                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
41047                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "shutdown"
41048                }
41049                _ => false,
41050            })
41051            .expect("missing shutdown attr load");
41052
41053        assert!(
41054            matches!(
41055                instructions
41056                    .get(shutdown_attr.saturating_sub(1))
41057                    .map(|unit| unit.op),
41058                Some(Instruction::LoadFast { .. })
41059            ),
41060            "with/except resume normal tail should keep CPython-style strong cm load, got instructions={instructions:?}"
41061        );
41062    }
41063
41064    #[test]
41065    fn with_except_else_attr_subscript_tail_keeps_borrow() {
41066        let code = compile_exec(
41067            "\
41068def f(self, cm, E, obj):
41069    try:
41070        with cm:
41071            pass
41072    except E as exc:
41073        self.x(exc)
41074    else:
41075        self.fail('Expected')
41076    inner = obj.saved_details[1]
41077    self.x(inner)
41078",
41079        );
41080        let f = find_code(&code, "f").expect("missing f code");
41081        let instructions: Vec<_> = f
41082            .instructions
41083            .iter()
41084            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41085            .collect();
41086        let saved_details = instructions
41087            .iter()
41088            .position(|unit| match unit.op {
41089                Instruction::LoadAttr { namei } => {
41090                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
41091                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
41092                        == "saved_details"
41093                }
41094                _ => false,
41095            })
41096            .expect("missing saved_details attr load");
41097        let handler_start = instructions
41098            .iter()
41099            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41100            .expect("missing handler entry");
41101        let tail = &instructions[saved_details.saturating_sub(1)..handler_start];
41102
41103        assert!(
41104            tail.iter().any(|unit| matches!(
41105                unit.op,
41106                Instruction::LoadFastBorrow { .. }
41107                    | Instruction::LoadFastBorrowLoadFastBorrow { .. }
41108            )),
41109            "expected except-else attr-subscript tail to keep borrowed LOAD_FAST ops, got tail={tail:?}"
41110        );
41111        assert!(
41112            tail.iter()
41113                .all(|unit| !matches!(unit.op, Instruction::LoadFast { .. })),
41114            "except-else attr-subscript tail should not be deoptimized to strong LOAD_FAST, got tail={tail:?}"
41115        );
41116    }
41117
41118    #[test]
41119    fn with_suppress_attr_subscript_tail_keeps_borrow() {
41120        let code = compile_exec(
41121            "\
41122def f(self, cm):
41123    stack = self.exit_stack()
41124    with self.assertRaisesRegex(TypeError, 'the context manager'):
41125        stack.enter_context(cm)
41126    stack.push(cm)
41127    self.assertIs(stack._exit_callbacks[-1][1], cm)
41128",
41129        );
41130        let f = find_code(&code, "f").expect("missing f code");
41131        let instructions: Vec<_> = f
41132            .instructions
41133            .iter()
41134            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41135            .collect();
41136        let exit_callbacks = instructions
41137            .iter()
41138            .position(|unit| match unit.op {
41139                Instruction::LoadAttr { namei } => {
41140                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
41141                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
41142                        == "_exit_callbacks"
41143                }
41144                _ => false,
41145            })
41146            .expect("missing _exit_callbacks attr load");
41147
41148        assert!(
41149            matches!(
41150                instructions
41151                    .get(exit_callbacks.saturating_sub(1))
41152                    .map(|unit| unit.op),
41153                Some(Instruction::LoadFastBorrow { .. })
41154            ),
41155            "with-suppress attr-subscript tail should keep CPython-style borrowed stack load, got instructions={instructions:?}"
41156        );
41157    }
41158
41159    #[test]
41160    fn named_except_conditional_reraise_deopts_with_chain_tail() {
41161        let code = compile_exec(
41162            "\
41163def f(self, arc, tmp_filename, new_mode):
41164    try:
41165        os.chmod(tmp_filename, new_mode)
41166    except OSError as exc:
41167        if exc.errno == ERR:
41168            self.skipTest()
41169        else:
41170            raise
41171    with self.check_context(arc.open(), 'fully_trusted'):
41172        self.expect_file('a')
41173    with self.check_context(arc.open(), 'tar'):
41174        self.expect_file('b')
41175",
41176        );
41177        let f = find_code(&code, "f").expect("missing f code");
41178        let instructions: Vec<_> = f
41179            .instructions
41180            .iter()
41181            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41182            .collect();
41183        let first_check_context = instructions
41184            .iter()
41185            .position(|unit| match unit.op {
41186                Instruction::LoadAttr { namei } => {
41187                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
41188                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
41189                        == "check_context"
41190                }
41191                _ => false,
41192            })
41193            .expect("missing check_context load");
41194        let first_handler = instructions
41195            .iter()
41196            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41197            .unwrap_or(instructions.len());
41198        let warm_tail = &instructions[first_check_context.saturating_sub(1)..first_handler];
41199
41200        assert!(
41201            warm_tail
41202                .iter()
41203                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
41204            "expected conditional named-except reraise tail to use strong LOAD_FAST ops, got tail={warm_tail:?}"
41205        );
41206        assert!(
41207            warm_tail.iter().all(|unit| {
41208                !matches!(
41209                    unit.op,
41210                    Instruction::LoadFastBorrow { .. }
41211                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
41212                )
41213            }),
41214            "expected all warm with-chain tail loads to stay strong after named-except reraise, got tail={warm_tail:?}"
41215        );
41216    }
41217
41218    #[test]
41219    fn terminal_bare_reraise_successor_join_keeps_final_store_borrow() {
41220        let code = compile_exec(
41221            "\
41222def f(self, fd, appending, errno):
41223    try:
41224        if appending:
41225            try:
41226                seek(fd)
41227            except OSError as e:
41228                if e.errno != errno:
41229                    raise
41230    except:
41231        self.stat = None
41232        raise
41233    self._fd = fd
41234",
41235        );
41236        let f = find_code(&code, "f").expect("missing f code");
41237        let instructions: Vec<_> = f
41238            .instructions
41239            .iter()
41240            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41241            .collect();
41242        let store_fd = instructions
41243            .iter()
41244            .position(|unit| match unit.op {
41245                Instruction::StoreAttr { namei } => {
41246                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41247                    f.names[usize::try_from(namei.get(arg)).unwrap()].as_str() == "_fd"
41248                }
41249                _ => false,
41250            })
41251            .expect("missing _fd STORE_ATTR");
41252
41253        assert!(
41254            matches!(
41255                instructions[store_fd - 1].op,
41256                Instruction::LoadFastBorrowLoadFastBorrow { .. }
41257            ),
41258            "terminal bare-reraise body successor join should keep CPython-style borrowed final store pair, got instructions={instructions:?}"
41259        );
41260    }
41261
41262    #[test]
41263    fn terminal_except_before_with_deopts_with_body_borrows() {
41264        let code = compile_exec(
41265            "\
41266def f(self, cm):
41267    try:
41268        g()
41269    except OSError:
41270        raise Exception('skip')
41271    with cm:
41272        self.x()
41273        self.y()
41274",
41275        );
41276        let f = find_code(&code, "f").expect("missing f code");
41277        let instructions: Vec<_> = f
41278            .instructions
41279            .iter()
41280            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41281            .collect();
41282        let first_tail_attr = instructions
41283            .iter()
41284            .position(|unit| match unit.op {
41285                Instruction::LoadAttr { namei } => {
41286                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
41287                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "x"
41288                }
41289                _ => false,
41290            })
41291            .expect("missing x attr load");
41292        let handler_start = instructions
41293            .iter()
41294            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41295            .expect("missing handler entry");
41296        let with_tail = &instructions[first_tail_attr.saturating_sub(1)..handler_start];
41297
41298        assert!(
41299            with_tail
41300                .iter()
41301                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
41302            "expected terminal-except before with to use strong LOAD_FAST ops, got tail={with_tail:?}"
41303        );
41304        assert!(
41305            with_tail.iter().all(|unit| {
41306                !matches!(
41307                    unit.op,
41308                    Instruction::LoadFastBorrow { .. }
41309                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
41310                )
41311            }),
41312            "terminal-except before with should not borrow protected with body loads, got tail={with_tail:?}"
41313        );
41314    }
41315
41316    #[test]
41317    fn terminal_except_resume_tail_uses_strong_loads() {
41318        let code = compile_exec(
41319            "\
41320def f(re, proc, unittest):
41321    try:
41322        version = proc.communicate()
41323    except OSError:
41324        raise unittest.SkipTest('x')
41325    match = re.search('pat', version)
41326    if match is None:
41327        raise unittest.SkipTest(f'Unable to parse readelf version: {version}')
41328    return int(match.group(1)), int(match.group(2))
41329",
41330        );
41331        let f = find_code(&code, "f").expect("missing f code");
41332        let instructions: Vec<_> = f
41333            .instructions
41334            .iter()
41335            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41336            .collect();
41337        let search_attr = instructions
41338            .iter()
41339            .position(|unit| match unit.op {
41340                Instruction::LoadAttr { namei } => {
41341                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
41342                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "search"
41343                }
41344                _ => false,
41345            })
41346            .expect("missing re.search attr load");
41347        let handler_start = instructions
41348            .iter()
41349            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41350            .expect("missing handler entry");
41351        let tail = &instructions[search_attr.saturating_sub(1)..handler_start];
41352
41353        let strong_loads_name = |name: &str| {
41354            tail.iter().any(|unit| match unit.op {
41355                Instruction::LoadFast { var_num } => {
41356                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41357                    f.varnames[usize::from(var_num.get(arg))] == name
41358                }
41359                Instruction::LoadFastLoadFast { var_nums } => {
41360                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41361                    let (left, right) = var_nums.get(arg).indexes();
41362                    f.varnames[usize::from(left)] == name || f.varnames[usize::from(right)] == name
41363                }
41364                _ => false,
41365            })
41366        };
41367        let borrows_name = |name: &str| {
41368            tail.iter().any(|unit| match unit.op {
41369                Instruction::LoadFastBorrow { var_num } => {
41370                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41371                    f.varnames[usize::from(var_num.get(arg))] == name
41372                }
41373                Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
41374                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41375                    let (left, right) = var_nums.get(arg).indexes();
41376                    f.varnames[usize::from(left)] == name || f.varnames[usize::from(right)] == name
41377                }
41378                _ => false,
41379            })
41380        };
41381
41382        for name in ["re", "version", "match"] {
41383            assert!(
41384                strong_loads_name(name),
41385                "terminal-except resume tail should use strong LOAD_FAST for {name}, got tail={tail:?}"
41386            );
41387            assert!(
41388                !borrows_name(name),
41389                "terminal-except resume tail should not borrow {name}, got tail={tail:?}"
41390            );
41391        }
41392    }
41393
41394    #[test]
41395    fn terminal_except_conditional_return_tail_uses_strong_loads() {
41396        let code = compile_exec(
41397            "\
41398def f(param, value, quote):
41399    try:
41400        value.encode('ascii')
41401    except UnicodeEncodeError:
41402        return param
41403    if quote:
41404        return param
41405    return value
41406",
41407        );
41408        let f = find_code(&code, "f").expect("missing f code");
41409        let instructions: Vec<_> = f
41410            .instructions
41411            .iter()
41412            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41413            .collect();
41414        let handler_start = instructions
41415            .iter()
41416            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41417            .expect("missing handler entry");
41418        let quote_idx = instructions[..handler_start]
41419            .iter()
41420            .position(|unit| match unit.op {
41421                Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
41422                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41423                    f.varnames[usize::from(var_num.get(arg))] == "quote"
41424                }
41425                _ => false,
41426            })
41427            .expect("missing quote guard load");
41428        let tail = &instructions[quote_idx..handler_start];
41429
41430        let strong_loads_name = |name: &str| {
41431            tail.iter().any(|unit| match unit.op {
41432                Instruction::LoadFast { var_num } => {
41433                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41434                    f.varnames[usize::from(var_num.get(arg))] == name
41435                }
41436                _ => false,
41437            })
41438        };
41439        let borrows_name = |name: &str| {
41440            tail.iter().any(|unit| match unit.op {
41441                Instruction::LoadFastBorrow { var_num } => {
41442                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41443                    f.varnames[usize::from(var_num.get(arg))] == name
41444                }
41445                _ => false,
41446            })
41447        };
41448
41449        for name in ["quote", "param", "value"] {
41450            assert!(
41451                strong_loads_name(name),
41452                "terminal-except conditional tail should use strong LOAD_FAST for {name}, got tail={tail:?}"
41453            );
41454            assert!(
41455                !borrows_name(name),
41456                "terminal-except conditional tail should not borrow {name}, got tail={tail:?}"
41457            );
41458        }
41459    }
41460
41461    #[test]
41462    fn terminal_except_successor_call_tail_uses_strong_load() {
41463        let code = compile_exec(
41464            "\
41465def f(curr, decoded_append, packI, curr_clear, Error):
41466    if len(curr) == 5:
41467        acc = 0
41468        for x in curr:
41469            acc = 85 * acc + (x - 33)
41470        try:
41471            decoded_append(packI(acc))
41472        except Error:
41473            raise ValueError('overflow') from None
41474        curr_clear()
41475",
41476        );
41477        let f = find_code(&code, "f").expect("missing f code");
41478        let instructions: Vec<_> = f
41479            .instructions
41480            .iter()
41481            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41482            .collect();
41483        let handler_start = instructions
41484            .iter()
41485            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41486            .expect("missing handler entry");
41487        let curr_clear_load = instructions[..handler_start]
41488            .iter()
41489            .rev()
41490            .find(|unit| match unit.op {
41491                Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
41492                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41493                    f.varnames[usize::from(var_num.get(arg))] == "curr_clear"
41494                }
41495                _ => false,
41496            })
41497            .expect("missing curr_clear load");
41498
41499        assert!(
41500            matches!(curr_clear_load.op, Instruction::LoadFast { .. }),
41501            "terminal except successor call tail should use strong LOAD_FAST for curr_clear, got instructions={instructions:?}"
41502        );
41503    }
41504
41505    #[test]
41506    fn loop_terminal_except_continue_if_tail_keeps_borrowed_loads() {
41507        let code = compile_exec(
41508            "\
41509def f(self, parser, opt, accum, rest, section, map, path, depth):
41510    while rest:
41511        rawval = rest.pop()
41512        try:
41513            if len(path) == 1:
41514                opt = parser.optionxform(path[0])
41515                v = map[opt]
41516            elif len(path) == 2:
41517                sect = path[0]
41518                opt = parser.optionxform(path[1])
41519                v = parser.get(sect, opt, raw=True)
41520            else:
41521                raise InterpolationSyntaxError(option, section, 'x')
41522        except (KeyError, NoSectionError, NoOptionError):
41523            raise InterpolationMissingOptionError(option, section, rawval, ':'.join(path)) from None
41524        if v is None:
41525            continue
41526        if '$' in v:
41527            self._interpolate_some(parser, opt, accum, v, sect, dict(parser.items(sect, raw=True)), depth + 1)
41528        else:
41529            accum.append(v)
41530",
41531        );
41532        let f = find_code(&code, "f").expect("missing f code");
41533        let ops: Vec<_> = f
41534            .instructions
41535            .iter()
41536            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41537            .collect();
41538        let handler_start = ops
41539            .iter()
41540            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41541            .expect("missing handler entry");
41542        let warm_path = &ops[..handler_start];
41543        let post_try_if = warm_path
41544            .iter()
41545            .position(|unit| matches!(unit.op, Instruction::PopJumpIfNotNone { .. }))
41546            .expect("missing post-try if");
41547        let tail = &warm_path[post_try_if.saturating_sub(1)..];
41548
41549        let mentions_name = |unit: &CodeUnit, name: &str| match unit.op {
41550            Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
41551                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41552                f.varnames[usize::from(var_num.get(arg))] == name
41553            }
41554            Instruction::LoadFastLoadFast { var_nums }
41555            | Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
41556                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41557                let (left, right) = var_nums.get(arg).indexes();
41558                f.varnames[usize::from(left)] == name || f.varnames[usize::from(right)] == name
41559            }
41560            _ => false,
41561        };
41562        let borrows_name = |name: &str| {
41563            tail.iter().any(|unit| {
41564                matches!(
41565                    unit.op,
41566                    Instruction::LoadFastBorrow { .. }
41567                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
41568                ) && mentions_name(unit, name)
41569            })
41570        };
41571        let strong_loads_name = |name: &str| {
41572            tail.iter().any(|unit| {
41573                matches!(
41574                    unit.op,
41575                    Instruction::LoadFast { .. } | Instruction::LoadFastLoadFast { .. }
41576                ) && mentions_name(unit, name)
41577            })
41578        };
41579
41580        for name in ["v", "self", "parser", "opt", "accum", "depth"] {
41581            assert!(
41582                borrows_name(name),
41583                "CPython keeps loop terminal-except continue-if tail borrowed for {name}, got tail={tail:?}"
41584            );
41585            assert!(
41586                !strong_loads_name(name),
41587                "loop terminal-except continue-if tail should not deopt {name}, got tail={tail:?}"
41588            );
41589        }
41590    }
41591
41592    #[test]
41593    fn method_call_try_return_handler_keeps_following_receiver_borrowed() {
41594        let code = compile_exec(
41595            "\
41596def f(charset, failobj, E):
41597    try:
41598        charset.encode('us-ascii')
41599    except E:
41600        return failobj
41601    return charset.lower()
41602",
41603        );
41604        let f = find_code(&code, "f").expect("missing f code");
41605        let ops: Vec<_> = f
41606            .instructions
41607            .iter()
41608            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41609            .collect();
41610        let lower_attr = ops
41611            .iter()
41612            .position(|unit| match unit.op {
41613                Instruction::LoadAttr { namei } => {
41614                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
41615                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "lower"
41616                }
41617                _ => false,
41618            })
41619            .expect("missing lower attr");
41620        let receiver = &ops[lower_attr - 1];
41621
41622        assert!(
41623            matches!(receiver.op, Instruction::LoadFastBorrow { .. }),
41624            "CPython codegen_try_except() does not leave a load-fast barrier after method-call try body when the handler returns, got ops={ops:?}"
41625        );
41626    }
41627
41628    #[test]
41629    fn typed_terminal_method_call_try_deopts_successor_call_args() {
41630        let code = compile_exec(
41631            "\
41632def f(events, callback, args, self, sig, signal):
41633    try:
41634        signal.set_wakeup_fd(self._csock.fileno())
41635    except ValueError:
41636        raise RuntimeError('bad signal') from None
41637    handle = events.Handle(callback, args, self, None)
41638    self._signal_handlers[sig] = handle
41639",
41640        );
41641        let f = find_code(&code, "f").expect("missing f code");
41642        let ops: Vec<_> = f
41643            .instructions
41644            .iter()
41645            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41646            .collect();
41647        let handle_store = ops
41648            .iter()
41649            .position(|unit| {
41650                matches!(
41651                    unit.op,
41652                    Instruction::StoreFast { var_num }
41653                        if f.varnames[usize::from(
41654                            var_num.get(OpArg::new(u32::from(u8::from(unit.arg))))
41655                        )] == "handle"
41656                )
41657            })
41658            .expect("missing handle store");
41659        let handle_call_window = &ops[handle_store.saturating_sub(4)..handle_store];
41660
41661        assert!(
41662            matches!(
41663                handle_call_window,
41664                [
41665                    bytecode::CodeUnit {
41666                        op: Instruction::LoadFastLoadFast { .. },
41667                        ..
41668                    },
41669                    bytecode::CodeUnit {
41670                        op: Instruction::LoadFast { .. },
41671                        ..
41672                    },
41673                    bytecode::CodeUnit {
41674                        op: Instruction::LoadConst { .. },
41675                        ..
41676                    },
41677                    bytecode::CodeUnit {
41678                        op: Instruction::Call { .. },
41679                        ..
41680                    },
41681                ]
41682            ),
41683            "CPython codegen_try_except() leaves a USE_LABEL(end) continuation after the terminal typed handler; successor call args should be strong loads, got window={handle_call_window:?}; ops={ops:?}"
41684        );
41685    }
41686
41687    #[test]
41688    fn typed_terminal_unpack_call_try_deopts_successor_call_args() {
41689        let code = compile_exec(
41690            "\
41691def f(self, OSError):
41692    try:
41693        request, client_address = self.get_request()
41694    except OSError:
41695        return
41696    if self.verify_request(request, client_address):
41697        self.process_request(request, client_address)
41698",
41699        );
41700        let f = find_code(&code, "f").expect("missing f code");
41701        let ops: Vec<_> = f
41702            .instructions
41703            .iter()
41704            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41705            .collect();
41706        let verify_attr = ops
41707            .iter()
41708            .position(|unit| match unit.op {
41709                Instruction::LoadAttr { namei } => {
41710                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41711                    f.names[usize::try_from(namei.get(arg).name_idx()).unwrap()].as_str()
41712                        == "verify_request"
41713                }
41714                _ => false,
41715            })
41716            .expect("missing verify_request attr");
41717        let request_pair = &ops[verify_attr + 1];
41718
41719        assert!(
41720            matches!(
41721                request_pair.op,
41722                Instruction::LoadFastLoadFast { var_nums }
41723                    if {
41724                        let arg = OpArg::new(u32::from(u8::from(request_pair.arg)));
41725                        let pair = var_nums.get(arg).as_u32();
41726                        f.varnames[(pair >> 4) as usize].as_str() == "request"
41727                            && f.varnames[(pair & 0xF) as usize].as_str() == "client_address"
41728                    }
41729            ),
41730            "CPython codegen_try_except() keeps successor call args strong after terminal typed unpack-call try body, got ops={ops:?}"
41731        );
41732    }
41733
41734    #[test]
41735    fn terminal_except_following_if_tail_uses_strong_loads() {
41736        let code = compile_exec(
41737            "\
41738def f(s):
41739    try:
41740        t = s[1:]
41741        d = g(s)
41742    except ValueError:
41743        raise ValueError('bad') from None
41744    if t:
41745        try:
41746            a, b, c = h(t)
41747        except ValueError:
41748            raise ValueError('bad') from None
41749        else:
41750            if b:
41751                x, y, z = d
41752                if y <= 12 and z <= q(x, y):
41753                    z += 1
41754                d = [x, y, z]
41755    else:
41756        a = [0]
41757    return k(*(d + a))
41758",
41759        );
41760        let f = find_code(&code, "f").expect("missing f code");
41761        let ops: Vec<_> = f
41762            .instructions
41763            .iter()
41764            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41765            .collect();
41766        let handler_start = ops
41767            .iter()
41768            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41769            .expect("missing handler entry");
41770        let tail_start = ops
41771            .iter()
41772            .position(|unit| {
41773                matches!(
41774                    unit.op,
41775                    Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
41776                        if f.varnames[usize::from(
41777                            var_num.get(OpArg::new(u32::from(u8::from(unit.arg))))
41778                        )] == "t"
41779                )
41780            })
41781            .expect("missing post-try t load");
41782        let tail = &ops[tail_start..handler_start];
41783
41784        assert!(
41785            !tail.iter().any(|unit| {
41786                matches!(
41787                    unit.op,
41788                    Instruction::LoadFastBorrow { .. }
41789                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
41790                )
41791            }),
41792            "terminal except following conditional tail should use CPython-style strong LOAD_FAST ops, got tail={tail:?}"
41793        );
41794    }
41795
41796    #[test]
41797    fn bare_except_internal_condition_keeps_try_body_borrows() {
41798        let code = compile_exec(
41799            "\
41800def f(buffering, raw, binary, result, BufferedReader):
41801    try:
41802        line_buffering = False
41803        if buffering == 1 or buffering < 0 and raw._isatty_open_only():
41804            buffering = -1
41805            line_buffering = True
41806        if buffering < 0:
41807            buffering = max(min(raw._blksize, 8192 * 1024), 8192)
41808        if buffering < 0:
41809            raise ValueError('invalid buffering size')
41810        if buffering == 0:
41811            if binary:
41812                return result
41813            raise ValueError(\"can't have unbuffered text I/O\")
41814        buffer = BufferedReader(raw, buffering)
41815        result = buffer
41816        if binary:
41817            return result
41818        return result
41819    except:
41820        result.close()
41821        raise
41822",
41823        );
41824        let f = find_code(&code, "f").expect("missing f code");
41825        let ops: Vec<_> = f
41826            .instructions
41827            .iter()
41828            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41829            .collect();
41830        let handler_start = ops
41831            .iter()
41832            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41833            .expect("missing handler entry");
41834        let warm_path = &ops[..handler_start];
41835
41836        let borrows_name = |name: &str| {
41837            warm_path.iter().any(|unit| match unit.op {
41838                Instruction::LoadFastBorrow { var_num } => {
41839                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41840                    f.varnames[usize::from(var_num.get(arg))] == name
41841                }
41842                Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
41843                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41844                    let (left, right) = var_nums.get(arg).indexes();
41845                    f.varnames[usize::from(left)] == name || f.varnames[usize::from(right)] == name
41846                }
41847                _ => false,
41848            })
41849        };
41850        let strong_loads_name = |name: &str| {
41851            warm_path.iter().any(|unit| match unit.op {
41852                Instruction::LoadFast { var_num } => {
41853                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41854                    f.varnames[usize::from(var_num.get(arg))] == name
41855                }
41856                Instruction::LoadFastLoadFast { var_nums } => {
41857                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41858                    let (left, right) = var_nums.get(arg).indexes();
41859                    f.varnames[usize::from(left)] == name || f.varnames[usize::from(right)] == name
41860                }
41861                _ => false,
41862            })
41863        };
41864
41865        for name in ["buffering", "raw", "binary", "result", "BufferedReader"] {
41866            assert!(
41867                borrows_name(name),
41868                "CPython keeps {name} borrowed inside the same bare-except protected region, got warm_path={warm_path:?}"
41869            );
41870            assert!(
41871                !strong_loads_name(name),
41872                "same protected-region conditional tail should not be terminal-except deoptimized for {name}, got warm_path={warm_path:?}"
41873            );
41874        }
41875    }
41876
41877    #[test]
41878    fn try_except_else_terminal_handler_conditional_tail_uses_strong_loads() {
41879        let code = compile_exec(
41880            "\
41881def f(self, pos, whence):
41882    try:
41883        pos_index = pos.__index__
41884    except AttributeError:
41885        raise TypeError(f'{pos!r} is not an integer')
41886    else:
41887        pos = pos_index()
41888    if whence == 0:
41889        if pos < 0:
41890            raise ValueError(f'negative {pos!r}')
41891        self._pos = pos
41892    elif whence == 1:
41893        self._pos = max(0, self._pos + pos)
41894    return self._pos
41895",
41896        );
41897        let f = find_code(&code, "f").expect("missing f code");
41898        let ops: Vec<_> = f
41899            .instructions
41900            .iter()
41901            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41902            .collect();
41903        let handler_start = ops
41904            .iter()
41905            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41906            .expect("missing handler entry");
41907        let warm_path = &ops[..handler_start];
41908
41909        let op_mentions_name = |unit: &CodeUnit, name: &str| match unit.op {
41910            Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
41911                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41912                f.varnames[usize::from(var_num.get(arg))] == name
41913            }
41914            Instruction::LoadFastLoadFast { var_nums }
41915            | Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
41916                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
41917                let (left, right) = var_nums.get(arg).indexes();
41918                f.varnames[usize::from(left)] == name || f.varnames[usize::from(right)] == name
41919            }
41920            _ => false,
41921        };
41922        let is_borrow_for_name = |unit: &CodeUnit, name: &str| match unit.op {
41923            Instruction::LoadFastBorrow { .. }
41924            | Instruction::LoadFastBorrowLoadFastBorrow { .. } => op_mentions_name(unit, name),
41925            _ => false,
41926        };
41927        let is_strong_for_name = |unit: &CodeUnit, name: &str| match unit.op {
41928            Instruction::LoadFast { .. } | Instruction::LoadFastLoadFast { .. } => {
41929                op_mentions_name(unit, name)
41930            }
41931            _ => false,
41932        };
41933
41934        assert!(
41935            warm_path
41936                .iter()
41937                .any(|unit| is_borrow_for_name(unit, "pos_index")),
41938            "try-else call should remain borrowed before the terminal-handler tail, got warm_path={warm_path:?}"
41939        );
41940
41941        let tail_start = warm_path
41942            .iter()
41943            .position(|unit| {
41944                is_strong_for_name(unit, "whence") || is_borrow_for_name(unit, "whence")
41945            })
41946            .expect("missing post-try conditional tail");
41947        let post_try_tail = &warm_path[tail_start..];
41948        for name in ["whence", "pos", "self"] {
41949            assert!(
41950                post_try_tail
41951                    .iter()
41952                    .any(|unit| is_strong_for_name(unit, name)),
41953                "terminal except conditional tail should use strong loads for {name}, got tail={post_try_tail:?}"
41954            );
41955            assert!(
41956                !post_try_tail
41957                    .iter()
41958                    .any(|unit| is_borrow_for_name(unit, name)),
41959                "terminal except conditional tail should not borrow {name}, got tail={post_try_tail:?}"
41960            );
41961        }
41962    }
41963
41964    #[test]
41965    fn try_except_else_outer_join_keeps_borrowed_loads() {
41966        let code = compile_exec(
41967            "\
41968def f(self, pos=None):
41969    if self.closed:
41970        raise ValueError('closed')
41971    if pos is None:
41972        pos = self._pos
41973    else:
41974        try:
41975            pos_index = pos.__index__
41976        except AttributeError:
41977            raise TypeError(f'{pos!r} is not an integer')
41978        else:
41979            pos = pos_index()
41980        if pos < 0:
41981            raise ValueError(f'negative {pos!r}')
41982    del self._buffer[pos:]
41983    return pos
41984",
41985        );
41986        let f = find_code(&code, "f").expect("missing f code");
41987        let ops: Vec<_> = f
41988            .instructions
41989            .iter()
41990            .filter(|unit| !matches!(unit.op, Instruction::Cache))
41991            .collect();
41992        let handler_start = ops
41993            .iter()
41994            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
41995            .expect("missing handler entry");
41996        let warm_path = &ops[..handler_start];
41997
41998        let mentions_name = |unit: &CodeUnit, name: &str| match unit.op {
41999            Instruction::LoadFast { var_num }
42000            | Instruction::LoadFastBorrow { var_num }
42001            | Instruction::StoreFast { var_num } => {
42002                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
42003                f.varnames[usize::from(var_num.get(arg))] == name
42004            }
42005            _ => false,
42006        };
42007        let else_store = warm_path
42008            .iter()
42009            .position(|unit| {
42010                matches!(unit.op, Instruction::StoreFast { .. }) && mentions_name(unit, "pos")
42011            })
42012            .expect("missing try-else pos store");
42013        let delete_subscr = warm_path
42014            .iter()
42015            .position(|unit| matches!(unit.op, Instruction::DeleteSubscr))
42016            .expect("missing join delete");
42017        let else_tail = &warm_path[else_store + 1..delete_subscr];
42018
42019        assert!(
42020            else_tail
42021                .iter()
42022                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })
42023                    && mentions_name(unit, "pos")),
42024            "terminal try-else conditional should use strong pos loads, got tail={else_tail:?}"
42025        );
42026
42027        let join_tail = &warm_path[delete_subscr.saturating_sub(6)..];
42028        for name in ["self", "pos"] {
42029            assert!(
42030                join_tail
42031                    .iter()
42032                    .any(|unit| matches!(unit.op, Instruction::LoadFastBorrow { .. })
42033                        && mentions_name(unit, name)),
42034                "outer if/else join should keep borrowed {name} loads, got tail={join_tail:?}"
42035            );
42036        }
42037        assert!(
42038            !join_tail
42039                .iter()
42040                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })
42041                    && (mentions_name(unit, "self") || mentions_name(unit, "pos"))),
42042            "outer if/else join should not inherit terminal try-else deopts, got tail={join_tail:?}"
42043        );
42044    }
42045
42046    #[test]
42047    fn terminal_except_else_final_store_attr_tail_uses_strong_loads() {
42048        let code = compile_exec(
42049            "\
42050def f(self, E, Event):
42051    try:
42052        decoded = bytes(self.buf).decode(self.encoding)
42053    except E:
42054        return
42055    else:
42056        self.insert(Event('key', decoded, self.flush_buf()))
42057    self.keymap = self.compiled_keymap
42058",
42059        );
42060        let f = find_code(&code, "f").expect("missing f code");
42061        let instructions: Vec<_> = f
42062            .instructions
42063            .iter()
42064            .filter(|unit| !matches!(unit.op, Instruction::Cache))
42065            .collect();
42066        let store_idx = instructions
42067            .iter()
42068            .position(|unit| match unit.op {
42069                Instruction::StoreAttr { namei } => {
42070                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
42071                    f.names[usize::try_from(namei.get(arg)).unwrap()].as_str() == "keymap"
42072                }
42073                _ => false,
42074            })
42075            .expect("missing keymap STORE_ATTR");
42076        let insert_attr_idx = instructions
42077            .iter()
42078            .position(|unit| match unit.op {
42079                Instruction::LoadAttr { namei } => {
42080                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
42081                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "insert"
42082                }
42083                _ => false,
42084            })
42085            .expect("missing insert LOAD_ATTR");
42086
42087        assert!(
42088            matches!(
42089                instructions[insert_attr_idx - 1].op,
42090                Instruction::LoadFastBorrow { .. }
42091            ),
42092            "terminal except should not deopt the try/else method receiver before the final tail, got instructions={instructions:?}"
42093        );
42094        assert!(
42095            matches!(
42096                (
42097                    instructions[store_idx - 3].op,
42098                    instructions[store_idx - 1].op
42099                ),
42100                (Instruction::LoadFast { .. }, Instruction::LoadFast { .. })
42101            ),
42102            "terminal except final STORE_ATTR tail should use CPython-style strong LOAD_FAST ops, got instructions={instructions:?}"
42103        );
42104    }
42105
42106    #[test]
42107    fn except_break_try_else_loop_tail_keeps_else_borrows() {
42108        let code = compile_exec(
42109            "\
42110def f(self):
42111    self.setup()
42112    prompt = 'Hit Return for more, or q (and Return) to quit: '
42113    lineno = 0
42114    while 1:
42115        try:
42116            for i in range(lineno, lineno + self.MAXLINES):
42117                print(self.lines[i])
42118        except IndexError:
42119            break
42120        else:
42121            lineno += self.MAXLINES
42122            key = None
42123            while key is None:
42124                key = input(prompt)
42125                if key not in ('', 'q'):
42126                    key = None
42127            if key == 'q':
42128                break
42129",
42130        );
42131        let f = find_code(&code, "f").expect("missing f code");
42132        let instructions: Vec<_> = f
42133            .instructions
42134            .iter()
42135            .filter(|unit| !matches!(unit.op, Instruction::Cache))
42136            .collect();
42137        let borrowed_loads = instructions
42138            .iter()
42139            .filter(|unit| {
42140                matches!(
42141                    unit.op,
42142                    Instruction::LoadFastBorrow { .. }
42143                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
42144                )
42145            })
42146            .count();
42147        let strong_loads_in_else_tail = instructions.iter().any(|unit| {
42148            matches!(
42149                unit.op,
42150                Instruction::LoadFast { .. } | Instruction::LoadFastLoadFast { .. }
42151            )
42152        });
42153
42154        assert!(
42155            borrowed_loads >= 7,
42156            "except-break try/else loop tail should keep CPython-style borrowed loads, got instructions={instructions:?}"
42157        );
42158        assert!(
42159            !strong_loads_in_else_tail,
42160            "except-break try/else loop tail must not be deoptimized as terminal return/raise, got instructions={instructions:?}"
42161        );
42162    }
42163
42164    #[test]
42165    fn protected_method_call_after_terminal_except_tail_uses_strong_loads() {
42166        let code = compile_exec(
42167            "\
42168def f(items, chunk, out, packI, Error):
42169    for i in items:
42170        acc = 0
42171        try:
42172            for c in chunk:
42173                acc = acc * 85 + c
42174        except TypeError:
42175            raise
42176        try:
42177            out.append(packI(acc))
42178        except Error:
42179            raise ValueError from None
42180",
42181        );
42182        let f = find_code(&code, "f").expect("missing f code");
42183        let instructions: Vec<_> = f
42184            .instructions
42185            .iter()
42186            .filter(|unit| !matches!(unit.op, Instruction::Cache))
42187            .collect();
42188        let handler_start = instructions
42189            .iter()
42190            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
42191            .expect("missing handler entry");
42192        let append_attr = instructions[..handler_start]
42193            .iter()
42194            .position(|unit| match unit.op {
42195                Instruction::LoadAttr { namei } => {
42196                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
42197                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "append"
42198                }
42199                _ => false,
42200            })
42201            .expect("missing append LOAD_ATTR");
42202        let tail = &instructions[append_attr.saturating_sub(1)..handler_start];
42203
42204        let strong_loads_name = |name: &str| {
42205            tail.iter().any(|unit| match unit.op {
42206                Instruction::LoadFast { var_num } => {
42207                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
42208                    f.varnames[usize::from(var_num.get(arg))] == name
42209                }
42210                _ => false,
42211            })
42212        };
42213        let borrows_name = |name: &str| {
42214            tail.iter().any(|unit| match unit.op {
42215                Instruction::LoadFastBorrow { var_num } => {
42216                    let arg = OpArg::new(u32::from(u8::from(unit.arg)));
42217                    f.varnames[usize::from(var_num.get(arg))] == name
42218                }
42219                _ => false,
42220            })
42221        };
42222
42223        for name in ["out", "packI", "acc"] {
42224            assert!(
42225                strong_loads_name(name),
42226                "protected method-call after terminal except tail should use strong LOAD_FAST for {name}, got tail={tail:?}"
42227            );
42228            assert!(
42229                !borrows_name(name),
42230                "protected method-call after terminal except tail should not borrow {name}, got tail={tail:?}"
42231            );
42232        }
42233    }
42234
42235    #[test]
42236    fn terminal_reraising_handler_keeps_try_body_method_borrows() {
42237        let code = compile_exec(
42238            "\
42239def f(self):
42240    try:
42241        self.console.prepare()
42242        self.arg = None
42243        self.finished = False
42244        del self.buffer[:]
42245        self.pos = 0
42246        self.dirty = True
42247        self.last_command = None
42248        self.calc_screen()
42249    except BaseException:
42250        self.restore()
42251        raise
42252    while self.scheduled_commands:
42253        cmd = self.scheduled_commands.pop()
42254        self.do_cmd((cmd, []))
42255",
42256        );
42257        let f = find_code(&code, "f").expect("missing f code");
42258        let instructions: Vec<_> = f
42259            .instructions
42260            .iter()
42261            .filter(|unit| !matches!(unit.op, Instruction::Cache))
42262            .collect();
42263        let first_handler = instructions
42264            .iter()
42265            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
42266            .expect("missing handler entry");
42267        let try_body = &instructions[..first_handler];
42268        let self_borrows = try_body
42269            .iter()
42270            .filter(|unit| {
42271                matches!(unit.op, Instruction::LoadFastBorrow { var_num }
42272                    if f.varnames[usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))))]
42273                        == "self")
42274            })
42275            .count();
42276
42277        assert!(
42278            self_borrows >= 8,
42279            "terminal reraising handler should keep try-body self loads borrowed, got try_body={try_body:?}"
42280        );
42281    }
42282
42283    #[test]
42284    fn terminal_except_loop_successor_augassign_uses_strong_load_pair() {
42285        let code = compile_exec(
42286            "\
42287def f(items, decoded, b32rev):
42288    for i in range(0, len(items), 8):
42289        quanta = items[i:i + 8]
42290        acc = 0
42291        try:
42292            for c in quanta:
42293                acc = (acc << 5) + b32rev[c]
42294        except KeyError:
42295            raise ValueError from None
42296        decoded += acc.to_bytes(5)
42297    return decoded
42298",
42299        );
42300        let f = find_code(&code, "f").expect("missing f code");
42301        let instructions: Vec<_> = f
42302            .instructions
42303            .iter()
42304            .filter(|unit| !matches!(unit.op, Instruction::Cache))
42305            .collect();
42306        let to_bytes_attr = instructions
42307            .iter()
42308            .position(|unit| match unit.op {
42309                Instruction::LoadAttr { namei } => {
42310                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
42311                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "to_bytes"
42312                }
42313                _ => false,
42314            })
42315            .expect("missing to_bytes LOAD_ATTR");
42316        let pair = instructions[to_bytes_attr.saturating_sub(1)].op;
42317
42318        assert!(
42319            matches!(pair, Instruction::LoadFastLoadFast { .. }),
42320            "terminal-except loop successor augassign should use strong LOAD_FAST_LOAD_FAST, got instructions={instructions:?}"
42321        );
42322    }
42323
42324    #[test]
42325    fn terminal_except_loop_backedge_keeps_header_borrows() {
42326        let code = compile_exec(
42327            "\
42328def f(self, value, start=0, stop=None):
42329    i = start
42330    while stop is None or i < stop:
42331        try:
42332            v = self[i]
42333        except IndexError:
42334            break
42335        if v is value or v == value:
42336            return i
42337        i += 1
42338    raise ValueError
42339",
42340        );
42341        let f = find_code(&code, "f").expect("missing f code");
42342        let instructions: Vec<_> = f
42343            .instructions
42344            .iter()
42345            .filter(|unit| !matches!(unit.op, Instruction::Cache))
42346            .collect();
42347
42348        assert!(
42349            instructions.windows(7).any(|window| {
42350                matches!(window[0].op, Instruction::StoreFast { .. })
42351                    && matches!(window[1].op, Instruction::LoadFastBorrow { .. })
42352                    && matches!(window[2].op, Instruction::PopJumpIfNone { .. })
42353                    && matches!(window[3].op, Instruction::NotTaken)
42354                    && matches!(
42355                        window[4].op,
42356                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
42357                    )
42358                    && matches!(window[5].op, Instruction::CompareOp { .. })
42359                    && matches!(window[6].op, Instruction::PopJumpIfFalse { .. })
42360            }),
42361            "terminal-except loop backedge deopt should not cross into the loop header, got instructions={instructions:?}"
42362        );
42363    }
42364
42365    #[test]
42366    fn one_line_protected_infinite_while_body_uses_strong_pair() {
42367        let code = compile_exec(
42368            "\
42369def f():
42370    items = range(1, 4)
42371    try:
42372        i = 0
42373        while 1: i = items[i]
42374    except IndexError:
42375        pass
42376",
42377        );
42378        let f = find_code(&code, "f").expect("missing f code");
42379        let instructions: Vec<_> = f
42380            .instructions
42381            .iter()
42382            .filter(|unit| !matches!(unit.op, Instruction::Cache))
42383            .collect();
42384
42385        assert!(
42386            instructions
42387                .iter()
42388                .any(|unit| matches!(unit.op, Instruction::LoadFastLoadFast { .. })),
42389            "one-line protected infinite while body should match CPython strong pair, got instructions={instructions:?}"
42390        );
42391        assert!(
42392            !instructions
42393                .iter()
42394                .any(|unit| matches!(unit.op, Instruction::LoadFastBorrowLoadFastBorrow { .. })),
42395            "one-line protected infinite while body should not borrow the loop body pair, got instructions={instructions:?}"
42396        );
42397    }
42398
42399    #[test]
42400    fn multiline_protected_infinite_while_body_keeps_borrow_pair() {
42401        let code = compile_exec(
42402            "\
42403def f(items):
42404    try:
42405        i = 0
42406        while 1:
42407            i = items[i]
42408    except IndexError:
42409        pass
42410",
42411        );
42412        let f = find_code(&code, "f").expect("missing f code");
42413        let instructions: Vec<_> = f
42414            .instructions
42415            .iter()
42416            .filter(|unit| !matches!(unit.op, Instruction::Cache))
42417            .collect();
42418
42419        assert!(
42420            instructions
42421                .iter()
42422                .any(|unit| matches!(unit.op, Instruction::LoadFastBorrowLoadFastBorrow { .. })),
42423            "multiline protected infinite while body should keep CPython borrowed pair, got instructions={instructions:?}"
42424        );
42425    }
42426
42427    #[test]
42428    fn one_line_protected_infinite_while_method_call_keeps_borrow_receiver() {
42429        let code = compile_exec(
42430            "\
42431def f(self):
42432    try:
42433        while 1: self.x()
42434    except IndexError:
42435        pass
42436",
42437        );
42438        let f = find_code(&code, "f").expect("missing f code");
42439        let instructions: Vec<_> = f
42440            .instructions
42441            .iter()
42442            .filter(|unit| !matches!(unit.op, Instruction::Cache))
42443            .collect();
42444
42445        assert!(
42446            instructions
42447                .iter()
42448                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
42449            "CPython 3.14 keeps the one-line protected bound-method receiver strong, got instructions={instructions:?}"
42450        );
42451    }
42452
42453    #[test]
42454    fn loop_if_implicit_continue_places_body_after_jumpback() {
42455        let code = compile_exec(
42456            "\
42457def f(_config_vars, _INITPRE):
42458    for k in list(_config_vars):
42459        if k.startswith(_INITPRE):
42460            del _config_vars[k]
42461",
42462        );
42463        let f = find_code(&code, "f").expect("missing function code");
42464        let ops: Vec<_> = f
42465            .instructions
42466            .iter()
42467            .map(|unit| unit.op)
42468            .filter(|op| !matches!(op, Instruction::Cache))
42469            .collect();
42470
42471        assert!(
42472            ops.windows(7).any(|window| {
42473                matches!(
42474                    window,
42475                    [
42476                        Instruction::PopJumpIfTrue { .. },
42477                        Instruction::NotTaken,
42478                        Instruction::JumpBackward { .. }
42479                            | Instruction::JumpBackwardNoInterrupt { .. },
42480                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
42481                            | Instruction::LoadFastLoadFast { .. },
42482                        Instruction::DeleteSubscr,
42483                        Instruction::JumpBackward { .. }
42484                            | Instruction::JumpBackwardNoInterrupt { .. },
42485                        Instruction::EndFor,
42486                    ]
42487                )
42488            }),
42489            "loop if with implicit continue should use CPython body-after-jumpback layout, got ops={ops:?}"
42490        );
42491    }
42492
42493    #[test]
42494    fn loop_if_call_body_implicit_continue_places_body_after_jumpback() {
42495        let code = compile_exec(
42496            "\
42497def f(seq, db):
42498    count = 0
42499    for c in seq:
42500        dec = db.value(c, -1)
42501        if dec != -1:
42502            db.check(dec, c)
42503            count += 1
42504    return count
42505",
42506        );
42507        let f = find_code(&code, "f").expect("missing function code");
42508        let ops: Vec<_> = f
42509            .instructions
42510            .iter()
42511            .map(|unit| unit.op)
42512            .filter(|op| !matches!(op, Instruction::Cache))
42513            .collect();
42514
42515        assert!(
42516            ops.windows(8).any(|window| {
42517                matches!(
42518                    window,
42519                    [
42520                        Instruction::CompareOp { .. },
42521                        Instruction::PopJumpIfTrue { .. },
42522                        Instruction::NotTaken,
42523                        Instruction::JumpBackward { .. }
42524                            | Instruction::JumpBackwardNoInterrupt { .. },
42525                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
42526                        Instruction::LoadAttr { .. },
42527                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
42528                            | Instruction::LoadFastLoadFast { .. },
42529                        Instruction::Call { .. },
42530                    ]
42531                )
42532            }),
42533            "no-else loop call body should use CPython body-after-jumpback layout, got ops={ops:?}"
42534        );
42535    }
42536
42537    #[test]
42538    fn nested_loop_if_try_body_implicit_continue_places_body_after_jumpback() {
42539        let code = compile_exec(
42540            "\
42541def f(seq, broken, codecs, LookupError, s, Queue, bytes):
42542    for encoding in seq:
42543        if encoding not in broken:
42544            q = Queue(b'')
42545            writer = codecs.getwriter(encoding)(q)
42546            encodedresult = b''
42547            for c in s:
42548                writer.write(c)
42549                chunk = q.read()
42550                encodedresult += chunk
42551            q = Queue(b'')
42552            reader = codecs.getreader(encoding)(q)
42553            decodedresult = ''
42554            for c in encodedresult:
42555                q.write(bytes([c]))
42556                decodedresult += reader.read()
42557        if encoding not in broken:
42558            try:
42559                encoder = codecs.getincrementalencoder(encoding)()
42560            except LookupError:
42561                pass
42562            else:
42563                encoder.encode('x')
42564            if encoding not in ('idna', 'mbcs'):
42565                try:
42566                    encoder = codecs.getincrementalencoder(encoding)('ignore')
42567                except LookupError:
42568                    pass
42569                else:
42570                    encodedresult = b''.join(encoder.encode(c) for c in s)
42571                    decoder = codecs.getincrementaldecoder(encoding)('ignore')
42572                    decodedresult = ''.join(decoder.decode(bytes([c])) for c in encodedresult)
42573",
42574        );
42575        let f = find_code(&code, "f").expect("missing function code");
42576        let ops: Vec<_> = f
42577            .instructions
42578            .iter()
42579            .map(|unit| unit.op)
42580            .filter(|op| !matches!(op, Instruction::Cache))
42581            .collect();
42582
42583        assert!(
42584            ops.windows(8).any(|window| {
42585                matches!(
42586                    window,
42587                    [
42588                        Instruction::ContainsOp { .. },
42589                        Instruction::PopJumpIfTrue { .. },
42590                        Instruction::NotTaken,
42591                        Instruction::JumpBackward { .. }
42592                            | Instruction::JumpBackwardNoInterrupt { .. },
42593                        Instruction::Nop,
42594                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
42595                        Instruction::LoadAttr { .. },
42596                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
42597                    ]
42598                )
42599            }),
42600            "nested final if with try body should put false backedge before body, got ops={ops:?}"
42601        );
42602        assert!(
42603            !ops.windows(6).any(|window| {
42604                matches!(
42605                    window,
42606                    [
42607                        Instruction::ContainsOp { .. },
42608                        Instruction::PopJumpIfFalse { .. },
42609                        Instruction::NotTaken,
42610                        Instruction::Nop,
42611                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
42612                        Instruction::LoadAttr { .. },
42613                    ]
42614                )
42615            }),
42616            "nested final if with try body should not leave body before false backedge, got ops={ops:?}"
42617        );
42618    }
42619
42620    #[test]
42621    fn loop_branch_raise_before_elif_keeps_body_before_backedge() {
42622        let code = compile_exec(
42623            "\
42624def f(checks, UNIQUE, CONTINUOUS, ValueError):
42625    for check in checks:
42626        if check is UNIQUE:
42627            duplicates = []
42628            for name in checks:
42629                if name:
42630                    duplicates.append(name)
42631            if duplicates:
42632                detail = ', '.join(str(name) for name in duplicates)
42633                raise ValueError('aliases: %s' % detail)
42634        elif check is CONTINUOUS:
42635            value = 1
42636    return value
42637",
42638        );
42639        let f = find_code(&code, "f").expect("missing function code");
42640        let ops: Vec<_> = f
42641            .instructions
42642            .iter()
42643            .map(|unit| unit.op)
42644            .filter(|op| !matches!(op, Instruction::Cache))
42645            .collect();
42646
42647        assert!(
42648            ops.windows(5).any(|window| {
42649                matches!(
42650                    window,
42651                    [
42652                        Instruction::ToBool,
42653                        Instruction::PopJumpIfFalse { .. },
42654                        Instruction::NotTaken,
42655                        Instruction::LoadConst { .. },
42656                        Instruction::LoadAttr { .. },
42657                    ]
42658                )
42659            }),
42660            "raise body before an elif chain should stay before the branch backedge, got ops={ops:?}"
42661        );
42662        assert!(
42663            !ops.windows(5).any(|window| {
42664                matches!(
42665                    window,
42666                    [
42667                        Instruction::ToBool,
42668                        Instruction::PopJumpIfTrue { .. },
42669                        Instruction::NotTaken,
42670                        Instruction::JumpBackward { .. }
42671                            | Instruction::JumpBackwardNoInterrupt { .. },
42672                        Instruction::LoadConst { .. },
42673                    ]
42674                )
42675            }),
42676            "raise body before an elif chain should not be moved after the branch backedge, got ops={ops:?}"
42677        );
42678    }
42679
42680    #[test]
42681    fn loop_nested_raise_then_append_places_body_after_false_backedge() {
42682        let code = compile_exec(
42683            "\
42684def f(args, parameters, enforce_default_ordering, type_var_tuple_encountered, default_encountered, TypeError):
42685    for t in args:
42686        if t not in parameters:
42687            if enforce_default_ordering:
42688                if type_var_tuple_encountered and t.has_default():
42689                    raise TypeError('a')
42690                if t.has_default():
42691                    default_encountered = True
42692                elif default_encountered:
42693                    raise TypeError('b')
42694            parameters.append(t)
42695    return parameters
42696",
42697        );
42698        let f = find_code(&code, "f").expect("missing function code");
42699        let ops: Vec<_> = f
42700            .instructions
42701            .iter()
42702            .map(|unit| unit.op)
42703            .filter(|op| !matches!(op, Instruction::Cache))
42704            .collect();
42705
42706        assert!(
42707            ops.windows(6).any(|window| {
42708                matches!(
42709                    window,
42710                    [
42711                        Instruction::ContainsOp { .. },
42712                        Instruction::PopJumpIfTrue { .. },
42713                        Instruction::NotTaken,
42714                        Instruction::JumpBackward { .. }
42715                            | Instruction::JumpBackwardNoInterrupt { .. },
42716                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
42717                        Instruction::ToBool,
42718                    ]
42719                )
42720            }),
42721            "CPython places the nested raise/append body after the false backedge for this loop tail, got ops={ops:?}"
42722        );
42723        assert!(
42724            !ops.windows(5).any(|window| {
42725                matches!(
42726                    window,
42727                    [
42728                        Instruction::ContainsOp { .. },
42729                        Instruction::PopJumpIfFalse { .. },
42730                        Instruction::NotTaken,
42731                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
42732                        Instruction::ToBool,
42733                    ]
42734                )
42735            }),
42736            "this case should not keep the body before the false backedge; CPython emits the split backedge first, got ops={ops:?}"
42737        );
42738    }
42739
42740    #[test]
42741    fn loop_break_before_adjacent_break_keeps_body_before_backedge() {
42742        let code = compile_exec(
42743            "\
42744def f(pattern, prefix, get_prefix):
42745    for op, av in pattern:
42746        if op == 1:
42747            prefix.append(av)
42748        elif op == 2:
42749            prefix1, got_all = get_prefix(av)
42750            prefix.extend(prefix1)
42751            if not got_all:
42752                break
42753        else:
42754            break
42755    else:
42756        return prefix, True
42757    return prefix, False
42758",
42759        );
42760        let f = find_code(&code, "f").expect("missing f code");
42761        let ops: Vec<_> = f
42762            .instructions
42763            .iter()
42764            .map(|unit| unit.op)
42765            .filter(|op| !matches!(op, Instruction::Cache))
42766            .collect();
42767
42768        assert!(
42769            ops.windows(7).any(|window| {
42770                matches!(
42771                    window,
42772                    [
42773                        Instruction::ToBool,
42774                        Instruction::PopJumpIfTrue { .. },
42775                        Instruction::NotTaken,
42776                        Instruction::PopTop,
42777                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
42778                        Instruction::LoadConst { .. },
42779                        Instruction::BuildTuple { .. },
42780                    ]
42781                )
42782            }),
42783            "break before an adjacent break exit should stay before the loop backedge, got ops={ops:?}"
42784        );
42785        assert!(
42786            !ops.windows(5).any(|window| {
42787                matches!(
42788                    window,
42789                    [
42790                        Instruction::ToBool,
42791                        Instruction::PopJumpIfFalse { .. },
42792                        Instruction::NotTaken,
42793                        Instruction::JumpBackward { .. }
42794                            | Instruction::JumpBackwardNoInterrupt { .. },
42795                        Instruction::PopTop,
42796                    ]
42797                )
42798            }),
42799            "break before an adjacent break exit should not be moved after the loop backedge, got ops={ops:?}"
42800        );
42801    }
42802
42803    #[test]
42804    fn loop_elif_and_pass_keeps_shared_false_backedge_after_body() {
42805        let code = compile_exec(
42806            "\
42807def f(methods, simple_keys, checked_keys, checked_enum, simple_enum, failed):
42808    for method in methods:
42809        if method in simple_keys and method in checked_keys:
42810            continue
42811        elif method not in simple_keys and method not in checked_keys:
42812            checked_method = getattr(checked_enum, method, None)
42813            simple_method = getattr(simple_enum, method, None)
42814            if hasattr(checked_method, '__func__'):
42815                checked_method = checked_method.__func__
42816                simple_method = simple_method.__func__
42817            if checked_method != simple_method:
42818                failed.append(method)
42819        else:
42820            pass
42821",
42822        );
42823        let f = find_code(&code, "f").expect("missing function code");
42824        let ops: Vec<_> = f
42825            .instructions
42826            .iter()
42827            .map(|unit| unit.op)
42828            .filter(|op| !matches!(op, Instruction::Cache))
42829            .collect();
42830
42831        assert!(
42832            ops.windows(9).any(|window| {
42833                matches!(
42834                    window,
42835                    [
42836                        Instruction::ContainsOp { .. },
42837                        Instruction::PopJumpIfFalse { .. },
42838                        Instruction::NotTaken,
42839                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
42840                            | Instruction::LoadFastLoadFast { .. },
42841                        Instruction::ContainsOp { .. },
42842                        Instruction::PopJumpIfFalse { .. },
42843                        Instruction::NotTaken,
42844                        Instruction::LoadGlobal { .. },
42845                        _,
42846                    ]
42847                )
42848            }),
42849            "elif-and body should stay before the shared false backedge, got ops={ops:?}"
42850        );
42851        assert!(
42852            !ops.windows(5).any(|window| {
42853                matches!(
42854                    window,
42855                    [
42856                        Instruction::ContainsOp { .. },
42857                        Instruction::PopJumpIfTrue { .. },
42858                        Instruction::NotTaken,
42859                        Instruction::JumpBackward { .. }
42860                            | Instruction::JumpBackwardNoInterrupt { .. },
42861                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
42862                    ]
42863                )
42864            }),
42865            "elif-and shared false backedge should not be split before the body, got ops={ops:?}"
42866        );
42867    }
42868
42869    #[test]
42870    fn loop_nested_if_delete_slice_places_body_after_jumpback() {
42871        let code = compile_exec(
42872            "\
42873def f(compiler_so):
42874    for idx in reversed(range(len(compiler_so))):
42875        if compiler_so[idx] == '-arch' and compiler_so[idx + 1] == 'arm64':
42876            del compiler_so[idx:idx + 2]
42877",
42878        );
42879        let f = find_code(&code, "f").expect("missing function code");
42880        let ops: Vec<_> = f
42881            .instructions
42882            .iter()
42883            .map(|unit| unit.op)
42884            .filter(|op| !matches!(op, Instruction::Cache))
42885            .collect();
42886
42887        assert!(
42888            ops.windows(15).any(|window| {
42889                matches!(
42890                    window,
42891                    [
42892                        Instruction::CompareOp { .. },
42893                        Instruction::PopJumpIfTrue { .. },
42894                        Instruction::NotTaken,
42895                        Instruction::JumpBackward { .. }
42896                            | Instruction::JumpBackwardNoInterrupt { .. },
42897                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
42898                            | Instruction::LoadFastLoadFast { .. },
42899                        Instruction::LoadSmallInt { .. },
42900                        Instruction::BinaryOp { .. },
42901                        Instruction::BinaryOp { .. },
42902                        Instruction::LoadConst { .. },
42903                        Instruction::CompareOp { .. },
42904                        Instruction::PopJumpIfTrue { .. },
42905                        Instruction::NotTaken,
42906                        Instruction::JumpBackward { .. }
42907                            | Instruction::JumpBackwardNoInterrupt { .. },
42908                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
42909                            | Instruction::LoadFastLoadFast { .. },
42910                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
42911                    ]
42912                )
42913            }),
42914            "nested loop delete-slice condition should put false jump-back before body, got ops={ops:?}"
42915        );
42916    }
42917
42918    #[test]
42919    fn loop_if_subscr_store_delete_places_body_after_jumpback() {
42920        let code = compile_exec(
42921            "\
42922def f(chunks):
42923    for k in range(len(chunks)-1, 0, -1):
42924        if chunks[k-1][-1] > chunks[k][0]:
42925            chunks[k-1] = chunks[k-1][:-1] + chunks[k][1:]
42926            del chunks[k]
42927",
42928        );
42929        let f = find_code(&code, "f").expect("missing function code");
42930        let ops: Vec<_> = f
42931            .instructions
42932            .iter()
42933            .map(|unit| unit.op)
42934            .filter(|op| !matches!(op, Instruction::Cache))
42935            .collect();
42936
42937        assert!(
42938            ops.windows(7).any(|window| {
42939                matches!(
42940                    window,
42941                    [
42942                        Instruction::CompareOp { .. },
42943                        Instruction::PopJumpIfTrue { .. },
42944                        Instruction::NotTaken,
42945                        Instruction::JumpBackward { .. }
42946                            | Instruction::JumpBackwardNoInterrupt { .. },
42947                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
42948                            | Instruction::LoadFastLoadFast { .. },
42949                        Instruction::LoadSmallInt { .. },
42950                        Instruction::BinaryOp { .. },
42951                    ]
42952                )
42953            }),
42954            "loop if with subscript store/delete body should put false jump-back before body, got ops={ops:?}"
42955        );
42956    }
42957
42958    #[test]
42959    fn final_elif_implicit_continue_places_jumpback_before_body() {
42960        let code = compile_exec(
42961            "\
42962def f(state, nextchar, whitespace, token, posix, quoted, debug):
42963    while True:
42964        if state is None:
42965            break
42966        elif state == ' ':
42967            if not nextchar:
42968                state = None
42969                break
42970            elif nextchar in whitespace:
42971                if debug >= 2:
42972                    print('x')
42973                if token or (posix and quoted):
42974                    break
42975                else:
42976                    continue
42977        elif state in ('a', 'c'):
42978            if not nextchar:
42979                state = None
42980                break
42981            elif nextchar in whitespace:
42982                if debug >= 2:
42983                    print('y')
42984                state = ' '
42985                if token or (posix and quoted):
42986                    break
42987                else:
42988                    continue
42989    return token
42990",
42991        );
42992        let f = find_code(&code, "f").expect("missing function code");
42993        let ops: Vec<_> = f
42994            .instructions
42995            .iter()
42996            .map(|unit| unit.op)
42997            .filter(|op| !matches!(op, Instruction::Cache))
42998            .collect();
42999
43000        assert!(
43001            ops.windows(5).any(|window| {
43002                matches!(
43003                    window,
43004                    [
43005                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
43006                            | Instruction::LoadFastLoadFast { .. },
43007                        Instruction::ContainsOp { .. },
43008                        Instruction::PopJumpIfTrue { .. },
43009                        Instruction::NotTaken,
43010                        Instruction::JumpBackward { .. }
43011                            | Instruction::JumpBackwardNoInterrupt { .. },
43012                    ]
43013                )
43014            }),
43015            "final elif with implicit continue should put false jump-back before body, got ops={ops:?}"
43016        );
43017    }
43018
43019    #[test]
43020    fn final_attribute_elif_implicit_continue_places_jumpback_before_body() {
43021        let code = compile_exec(
43022            "\
43023def f(self, nextchar, quoted):
43024    while True:
43025        if self.state is None:
43026            break
43027        elif self.state in ('a', 'c'):
43028            if not nextchar:
43029                self.state = None
43030                break
43031            elif nextchar in self.whitespace:
43032                self.state = ' '
43033                if self.token or (self.posix and quoted):
43034                    break
43035                else:
43036                    continue
43037    return self.token
43038",
43039        );
43040        let f = find_code(&code, "f").expect("missing function code");
43041        let ops: Vec<_> = f
43042            .instructions
43043            .iter()
43044            .map(|unit| unit.op)
43045            .filter(|op| !matches!(op, Instruction::Cache))
43046            .collect();
43047
43048        assert!(
43049            ops.windows(6).any(|window| {
43050                matches!(
43051                    window,
43052                    [
43053                        Instruction::LoadFastBorrowLoadFastBorrow { .. }
43054                            | Instruction::LoadFastLoadFast { .. },
43055                        Instruction::LoadAttr { .. },
43056                        Instruction::ContainsOp { .. },
43057                        Instruction::PopJumpIfTrue { .. },
43058                        Instruction::NotTaken,
43059                        Instruction::JumpBackward { .. }
43060                            | Instruction::JumpBackwardNoInterrupt { .. },
43061                    ]
43062                )
43063            }),
43064            "final attribute elif with implicit continue should put false jump-back before body, got ops={ops:?}"
43065        );
43066    }
43067
43068    #[test]
43069    fn inner_if_implicit_continue_keeps_line_bearing_body_before_backedge() {
43070        let code = compile_exec(
43071            "\
43072def f(self, nextchar, quoted):
43073    while True:
43074        if self.state is None:
43075            break
43076        elif self.state in ('a', 'c'):
43077            if not nextchar:
43078                self.state = None
43079                break
43080            elif nextchar in self.whitespace:
43081                self.state = ' '
43082                if self.token or (self.posix and quoted):
43083                    break
43084                else:
43085                    continue
43086            elif nextchar in self.commenters:
43087                self.instream.readline()
43088                self.lineno += 1
43089                if self.posix:
43090                    self.state = ' '
43091                    if self.token or (self.posix and quoted):
43092                        break
43093                    else:
43094                        continue
43095            elif self.state == 'c':
43096                break
43097    return self.token
43098",
43099        );
43100        let f = find_code(&code, "f").expect("missing function code");
43101        let ops: Vec<_> = f
43102            .instructions
43103            .iter()
43104            .map(|unit| unit.op)
43105            .filter(|op| !matches!(op, Instruction::Cache))
43106            .collect();
43107
43108        assert!(
43109            ops.windows(7).any(|window| {
43110                matches!(
43111                    window,
43112                    [
43113                        Instruction::LoadAttr { .. },
43114                        Instruction::ToBool,
43115                        Instruction::PopJumpIfFalse { .. },
43116                        Instruction::NotTaken,
43117                        Instruction::LoadConst { .. },
43118                        Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. },
43119                        Instruction::StoreAttr { .. },
43120                    ]
43121                )
43122            }),
43123            "line-bearing inner if with implicit continue should keep body before backedge, got ops={ops:?}"
43124        );
43125    }
43126
43127    #[test]
43128    fn except_handler_with_conditional_raise_and_resume_keeps_borrow() {
43129        let code = compile_exec(
43130            "\
43131def f(formatstr, args, output, overflowok):
43132    try:
43133        result = formatstr % args
43134    except OverflowError:
43135        if not overflowok:
43136            raise
43137        print('overflow')
43138    else:
43139        if output and result != output:
43140            raise AssertionError(result, output)
43141",
43142        );
43143        let f = find_code(&code, "f").expect("missing f code");
43144        let instructions: Vec<_> = f
43145            .instructions
43146            .iter()
43147            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43148            .collect();
43149        let assertion_error = instructions
43150            .iter()
43151            .position(|unit| match unit.op {
43152                Instruction::LoadGlobal { namei } => {
43153                    let load_global = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
43154                    f.names[usize::try_from(load_global >> 1).unwrap()].as_str() == "AssertionError"
43155                }
43156                _ => false,
43157            })
43158            .expect("missing AssertionError raise");
43159        let handler_start = instructions
43160            .iter()
43161            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
43162            .expect("missing handler entry");
43163        let tail = &instructions[..handler_start.min(assertion_error)];
43164
43165        assert!(
43166            tail.iter().any(|unit| {
43167                matches!(
43168                    unit.op,
43169                    Instruction::LoadFastBorrow { .. }
43170                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
43171                )
43172            }),
43173            "conditional-raise handler with a resume path should keep borrowed loads, got tail={tail:?}"
43174        );
43175        assert!(
43176            tail.iter()
43177                .all(|unit| !matches!(unit.op, Instruction::LoadFast { .. })),
43178            "conditional-raise handler with a resume path should not force strong LOAD_FAST, got tail={tail:?}"
43179        );
43180    }
43181
43182    #[test]
43183    fn typed_except_resume_import_warning_tail_keeps_borrows() {
43184        let code = compile_exec(
43185            r#"
43186def f(mod_name, error, sys, RuntimeWarning):
43187    pkg_name, _, _ = mod_name.rpartition(".")
43188    if pkg_name:
43189        try:
43190            __import__(pkg_name)
43191        except ImportError as e:
43192            if e.name is None or (e.name != pkg_name and
43193                    not pkg_name.startswith(e.name + ".")):
43194                raise
43195        existing = sys.modules.get(mod_name)
43196        if existing is not None and not hasattr(existing, "__path__"):
43197            from warnings import warn
43198            msg = "{mod_name!r} found in sys.modules after import of " \
43199                "package {pkg_name!r}, but prior to execution of " \
43200                "{mod_name!r}; this may result in unpredictable " \
43201                "behaviour".format(mod_name=mod_name, pkg_name=pkg_name)
43202            warn(RuntimeWarning(msg))
43203    return mod_name
43204"#,
43205        );
43206        let f = find_code(&code, "f").expect("missing f code");
43207        let arg = |unit: &bytecode::CodeUnit| OpArg::new(u32::from(u8::from(unit.arg)));
43208        let strong_names = |unit: &bytecode::CodeUnit| -> Vec<&str> {
43209            match unit.op {
43210                Instruction::LoadFast { var_num } => {
43211                    vec![f.varnames[usize::from(var_num.get(arg(unit)))].as_str()]
43212                }
43213                Instruction::LoadFastLoadFast { var_nums } => {
43214                    let (left, right) = var_nums.get(arg(unit)).indexes();
43215                    vec![
43216                        f.varnames[usize::from(left)].as_str(),
43217                        f.varnames[usize::from(right)].as_str(),
43218                    ]
43219                }
43220                _ => Vec::new(),
43221            }
43222        };
43223        let borrowed_names = |unit: &bytecode::CodeUnit| -> Vec<&str> {
43224            match unit.op {
43225                Instruction::LoadFastBorrow { var_num } => {
43226                    vec![f.varnames[usize::from(var_num.get(arg(unit)))].as_str()]
43227                }
43228                Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
43229                    let (left, right) = var_nums.get(arg(unit)).indexes();
43230                    vec![
43231                        f.varnames[usize::from(left)].as_str(),
43232                        f.varnames[usize::from(right)].as_str(),
43233                    ]
43234                }
43235                _ => Vec::new(),
43236            }
43237        };
43238
43239        let handler_start = f
43240            .instructions
43241            .iter()
43242            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
43243            .expect("missing handler entry");
43244        let normal_path = &f.instructions[..handler_start];
43245        for name in ["mod_name", "existing", "warn", "msg"] {
43246            assert!(
43247                normal_path
43248                    .iter()
43249                    .flat_map(borrowed_names)
43250                    .any(|borrowed| borrowed == name),
43251                "CPython keeps {name} borrowed after typed-except resume, got ops={:?}",
43252                normal_path.iter().map(|unit| unit.op).collect::<Vec<_>>()
43253            );
43254            assert!(
43255                !normal_path
43256                    .iter()
43257                    .flat_map(strong_names)
43258                    .any(|strong| strong == name),
43259                "typed-except resume should not force strong {name} loads, got ops={:?}",
43260                normal_path.iter().map(|unit| unit.op).collect::<Vec<_>>()
43261            );
43262        }
43263    }
43264
43265    #[test]
43266    fn reraising_except_else_tail_keeps_borrow() {
43267        let code = compile_exec(
43268            "\
43269def f(self, data, length):
43270    if self._paused:
43271        self._pending_data_length = length
43272        return
43273    if length == 0:
43274        self._eof_received()
43275        return
43276    if isinstance(self._protocol, protocols.BufferedProtocol):
43277        try:
43278            protocols._feed_data_to_buffered_proto(self._protocol, data)
43279        except (SystemExit, KeyboardInterrupt):
43280            raise
43281        except BaseException as exc:
43282            self._fatal_error(exc, 'x')
43283            return
43284    else:
43285        self._protocol.data_received(data)
43286",
43287        );
43288        let f = find_code(&code, "f").expect("missing f code");
43289        let instructions: Vec<_> = f
43290            .instructions
43291            .iter()
43292            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43293            .collect();
43294        let data_received_attr = instructions
43295            .iter()
43296            .position(|unit| match unit.op {
43297                Instruction::LoadAttr { namei } => {
43298                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
43299                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
43300                        == "data_received"
43301                }
43302                _ => false,
43303            })
43304            .expect("missing data_received LOAD_ATTR");
43305        let ops: Vec<_> = instructions.iter().map(|unit| unit.op).collect();
43306
43307        assert!(
43308            matches!(
43309                ops.get(data_received_attr - 2),
43310                Some(Instruction::LoadFastBorrow { .. })
43311            ),
43312            "normal else tail after reraising handler should keep borrowed self load, got ops={ops:?}"
43313        );
43314        assert!(
43315            matches!(
43316                ops.get(data_received_attr + 1),
43317                Some(Instruction::LoadFastBorrow { .. })
43318            ),
43319            "normal else tail after reraising handler should keep borrowed data load, got ops={ops:?}"
43320        );
43321    }
43322
43323    #[test]
43324    fn try_else_finally_with_keeps_context_manager_borrow() {
43325        let code = compile_exec(
43326            "\
43327def f(i):
43328    try:
43329        1 / 0
43330    except ZeroDivisionError:
43331        pass
43332    else:
43333        with i as dodgy:
43334            pass
43335    finally:
43336        pass
43337",
43338        );
43339        let f = find_code(&code, "f").expect("missing f code");
43340        let instructions: Vec<_> = f
43341            .instructions
43342            .iter()
43343            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43344            .collect();
43345        let first_with_exit = instructions
43346            .iter()
43347            .position(|unit| match unit.op {
43348                Instruction::LoadSpecial { method } => {
43349                    method.get(OpArg::new(u32::from(u8::from(unit.arg)))) == SpecialMethod::Exit
43350                }
43351                _ => false,
43352            })
43353            .expect("missing __exit__ load");
43354
43355        assert!(
43356            matches!(
43357                instructions
43358                    .get(first_with_exit.saturating_sub(2))
43359                    .map(|unit| unit.op),
43360                Some(Instruction::LoadFastBorrow { .. })
43361            ),
43362            "try/except/else/finally with setup should keep CPython-style borrowed context manager load, got instructions={instructions:?}"
43363        );
43364    }
43365
43366    #[test]
43367    fn except_star_handler_pop_block_does_not_leave_nop_before_with_exit() {
43368        let code = compile_exec(
43369            "\
43370def f(self):
43371    with self.assertRaises(TypeError):
43372        try:
43373            raise OSError('blah')
43374        except* ExceptionGroup as e:
43375            pass
43376",
43377        );
43378        let f = find_code(&code, "f").expect("missing f code");
43379        let ops: Vec<_> = f
43380            .instructions
43381            .iter()
43382            .map(|unit| unit.op)
43383            .filter(|op| !matches!(op, Instruction::Cache))
43384            .collect();
43385
43386        assert!(
43387            !ops.windows(5).any(|window| {
43388                matches!(
43389                    window,
43390                    [
43391                        Instruction::Reraise { .. },
43392                        Instruction::Nop,
43393                        Instruction::LoadConst { .. },
43394                        Instruction::LoadConst { .. },
43395                        Instruction::LoadConst { .. },
43396                    ]
43397                )
43398            }),
43399            "except* handler cleanup should not leave an extra NOP before with-exit None loads, got ops={ops:?}"
43400        );
43401    }
43402
43403    #[test]
43404    fn except_star_body_to_else_jump_drops_without_line_nop() {
43405        let code = compile_exec(
43406            "\
43407async def f(self, cm):
43408    try:
43409        async with cm:
43410            pass
43411    except* Exception:
43412        pass
43413    else:
43414        self.fail()
43415",
43416        );
43417        let f = find_code(&code, "f").expect("missing f code");
43418        let instructions: Vec<_> = f
43419            .instructions
43420            .iter()
43421            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43422            .collect();
43423        let fail_attr = instructions
43424            .iter()
43425            .position(|unit| match unit.op {
43426                Instruction::LoadAttr { namei } => {
43427                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
43428                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "fail"
43429                }
43430                _ => false,
43431            })
43432            .expect("missing self.fail load");
43433
43434        assert!(
43435            !matches!(
43436                instructions
43437                    .get(fail_attr.saturating_sub(2))
43438                    .map(|unit| unit.op),
43439                Some(Instruction::Nop)
43440            ),
43441            "body-to-else jump should be no-location and disappear, got instructions={instructions:?}"
43442        );
43443    }
43444
43445    #[test]
43446    fn resuming_except_before_with_keeps_with_body_borrows() {
43447        let code = compile_exec(
43448            "\
43449def f(self, cm):
43450    try:
43451        g()
43452    except OSError:
43453        pass
43454    with cm:
43455        self.x()
43456",
43457        );
43458        let f = find_code(&code, "f").expect("missing f code");
43459        let instructions: Vec<_> = f
43460            .instructions
43461            .iter()
43462            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43463            .collect();
43464        let first_tail_attr = instructions
43465            .iter()
43466            .position(|unit| match unit.op {
43467                Instruction::LoadAttr { namei } => {
43468                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
43469                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "x"
43470                }
43471                _ => false,
43472            })
43473            .expect("missing x attr load");
43474        let handler_start = instructions
43475            .iter()
43476            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
43477            .expect("missing handler entry");
43478        let with_tail = &instructions[first_tail_attr.saturating_sub(1)..handler_start];
43479
43480        assert!(
43481            with_tail.iter().any(|unit| {
43482                matches!(
43483                    unit.op,
43484                    Instruction::LoadFastBorrow { .. }
43485                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
43486                )
43487            }),
43488            "resuming except before with should keep borrowed LOAD_FAST ops, got tail={with_tail:?}"
43489        );
43490    }
43491
43492    #[test]
43493    fn nested_finally_except_resume_loop_uses_strong_loads() {
43494        let code = compile_exec(
43495            "\
43496def f(self, xs):
43497    try:
43498        try:
43499            g()
43500        finally:
43501            h()
43502    except OSError:
43503        self.skipTest('x')
43504    for x in xs:
43505        self.x(x)
43506",
43507        );
43508        let f = find_code(&code, "f").expect("missing f code");
43509        let instructions: Vec<_> = f
43510            .instructions
43511            .iter()
43512            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43513            .collect();
43514        let for_iter = instructions
43515            .iter()
43516            .position(|unit| matches!(unit.op, Instruction::ForIter { .. }))
43517            .expect("missing FOR_ITER");
43518        let handler_start = instructions
43519            .iter()
43520            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
43521            .expect("missing handler entry");
43522        let loop_tail = &instructions[for_iter.saturating_sub(2)..handler_start];
43523
43524        assert!(
43525            loop_tail
43526                .iter()
43527                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
43528            "expected nested finally/except resume loop to use strong LOAD_FAST ops, got tail={loop_tail:?}"
43529        );
43530        assert!(
43531            loop_tail.iter().all(|unit| {
43532                !matches!(
43533                    unit.op,
43534                    Instruction::LoadFastBorrow { .. }
43535                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
43536                )
43537            }),
43538            "nested finally/except resume loop should not borrow LOAD_FAST ops, got tail={loop_tail:?}"
43539        );
43540    }
43541
43542    #[test]
43543    fn finally_protected_loop_without_except_resume_keeps_borrows() {
43544        let code = compile_exec(
43545            "\
43546def f(self, obj, expected, buf):
43547    try:
43548        lines = obj.readlines()
43549    except ValueError:
43550        self.fail('x')
43551    if lines != expected:
43552        self.fail('bad')
43553    obj.close()
43554    obj = self.open()
43555    try:
43556        for line in obj:
43557            pass
43558        try:
43559            obj.readline()
43560            obj.readinto(buf)
43561        except ValueError:
43562            self.fail('inner')
43563    finally:
43564        obj.close()
43565",
43566        );
43567        let f = find_code(&code, "f").expect("missing f code");
43568        let instructions: Vec<_> = f
43569            .instructions
43570            .iter()
43571            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43572            .collect();
43573        let get_iter = instructions
43574            .iter()
43575            .position(|unit| matches!(unit.op, Instruction::GetIter))
43576            .expect("missing GET_ITER");
43577        assert!(
43578            matches!(
43579                instructions
43580                    .get(get_iter.saturating_sub(1))
43581                    .map(|unit| unit.op),
43582                Some(Instruction::LoadFastBorrow { .. })
43583            ),
43584            "finally-protected loop without except resume should keep borrowed iterable load, got instructions={instructions:?}"
43585        );
43586
43587        for attr_name in ["close", "open"] {
43588            let attr_idx = instructions[..get_iter]
43589                .iter()
43590                .rposition(|unit| match unit.op {
43591                    Instruction::LoadAttr { namei } => {
43592                        let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
43593                        f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str()
43594                            == attr_name
43595                    }
43596                    _ => false,
43597                })
43598                .unwrap_or_else(|| panic!("missing {attr_name} attr load before loop"));
43599            assert!(
43600                matches!(
43601                    instructions
43602                        .get(attr_idx.saturating_sub(1))
43603                        .map(|unit| unit.op),
43604                    Some(Instruction::LoadFastBorrow { .. })
43605                ),
43606                "pre-loop {attr_name} call should keep borrowed receiver load, got instructions={instructions:?}"
43607            );
43608        }
43609    }
43610
43611    #[test]
43612    fn plain_except_resume_loop_keeps_borrows() {
43613        let code = compile_exec(
43614            "\
43615def f(self, xs):
43616    try:
43617        g()
43618    except OSError:
43619        self.skipTest('x')
43620    for x in xs:
43621        self.x(x)
43622",
43623        );
43624        let f = find_code(&code, "f").expect("missing f code");
43625        let instructions: Vec<_> = f
43626            .instructions
43627            .iter()
43628            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43629            .collect();
43630        let for_iter = instructions
43631            .iter()
43632            .position(|unit| matches!(unit.op, Instruction::ForIter { .. }))
43633            .expect("missing FOR_ITER");
43634        let handler_start = instructions
43635            .iter()
43636            .position(|unit| matches!(unit.op, Instruction::PushExcInfo))
43637            .expect("missing handler entry");
43638        let loop_tail = &instructions[for_iter.saturating_sub(2)..handler_start];
43639
43640        assert!(
43641            loop_tail.iter().any(|unit| {
43642                matches!(
43643                    unit.op,
43644                    Instruction::LoadFastBorrow { .. }
43645                        | Instruction::LoadFastBorrowLoadFastBorrow { .. }
43646                )
43647            }),
43648            "plain except resume loop should keep borrowed LOAD_FAST ops, got tail={loop_tail:?}"
43649        );
43650    }
43651
43652    #[test]
43653    fn except_pass_resume_loop_branch_keeps_borrows() {
43654        let code = compile_exec(
43655            r#"
43656def f(self, cls, fns):
43657    for name, fn in zip(self.names, fns):
43658        try:
43659            annotation_fields, return_type = self.method_annotations[name]
43660        except KeyError:
43661            pass
43662        else:
43663            annotate_fn = _make_annotate_function(cls, name, annotation_fields, return_type)
43664            fn.__annotate__ = annotate_fn
43665        if self.unconditional_adds.get(name, False):
43666            setattr(cls, name, fn)
43667        else:
43668            already_exists = _set_new_attribute(cls, name, fn)
43669            if already_exists and (msg_extra := self.overwrite_errors.get(name)):
43670                error_msg = (f'Cannot overwrite attribute {fn.__name__} '
43671                             f'in class {cls.__name__}')
43672                if not msg_extra is True:
43673                    error_msg = f'{error_msg} {msg_extra}'
43674                raise TypeError(error_msg)
43675"#,
43676        );
43677        let f = find_code(&code, "f").expect("missing f code");
43678        let instructions: Vec<_> = f
43679            .instructions
43680            .iter()
43681            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43682            .collect();
43683        let load_global_name = |unit: &&CodeUnit, name: &str| match unit.op {
43684            Instruction::LoadGlobal { namei } => {
43685                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
43686                let load_global = namei.get(arg) >> 1;
43687                f.names[usize::try_from(load_global).unwrap()].as_str() == name
43688            }
43689            _ => false,
43690        };
43691        let load_name = |unit: &&CodeUnit| match unit.op {
43692            Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num } => {
43693                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
43694                Some(f.varnames[usize::from(var_num.get(arg))].as_str())
43695            }
43696            _ => None,
43697        };
43698        let load_pair_names = |unit: &&CodeUnit| match unit.op {
43699            Instruction::LoadFastLoadFast { var_nums }
43700            | Instruction::LoadFastBorrowLoadFastBorrow { var_nums } => {
43701                let arg = OpArg::new(u32::from(u8::from(unit.arg)));
43702                let (left, right) = var_nums.get(arg).indexes();
43703                Some((
43704                    f.varnames[usize::from(left)].as_str(),
43705                    f.varnames[usize::from(right)].as_str(),
43706                ))
43707            }
43708            _ => None,
43709        };
43710
43711        let set_new_attribute = instructions
43712            .iter()
43713            .position(|unit| load_global_name(unit, "_set_new_attribute"))
43714            .expect("missing _set_new_attribute call");
43715        let tail = &instructions[set_new_attribute..instructions.len()];
43716        assert!(
43717            tail.iter().any(|unit| {
43718                matches!(unit.op, Instruction::LoadFastBorrowLoadFastBorrow { .. })
43719                    && load_pair_names(unit) == Some(("cls", "name"))
43720            }) && tail.iter().any(|unit| {
43721                matches!(unit.op, Instruction::LoadFastBorrow { .. })
43722                    && load_name(unit) == Some("fn")
43723            }),
43724            "except-pass resume should keep CPython-style borrowed loads in loop branch, got tail={tail:?}"
43725        );
43726        assert!(
43727            !tail.iter().any(|unit| {
43728                matches!(unit.op, Instruction::LoadFastLoadFast { .. })
43729                    && load_pair_names(unit) == Some(("cls", "name"))
43730            }) && !tail.iter().any(|unit| {
43731                matches!(unit.op, Instruction::LoadFast { .. })
43732                    && matches!(
43733                        load_name(unit),
43734                        Some("fn" | "already_exists" | "msg_extra" | "error_msg")
43735                    )
43736            }),
43737            "except-pass resume must not deopt the independent loop branch, got tail={tail:?}"
43738        );
43739    }
43740
43741    #[test]
43742    fn named_except_cleanup_deopts_same_guard_fallbacks_not_outer_tail() {
43743        let code = compile_exec(
43744            r#"
43745def f(s, size, errors, final):
43746    found_invalid_escape = False
43747    p = []
43748    pos = 0
43749    while pos < size:
43750        ch = chr(s[pos])
43751        pos += 1
43752        if ch == "N":
43753            message = "malformed \\N character escape"
43754            look = pos
43755            try:
43756                import unicodedata
43757            except ImportError:
43758                message = "\\N escapes not supported (can't load unicodedata module)"
43759                unicode_call_errorhandler(
43760                    errors, "unicodeescape", message, s, pos - 1, size
43761                )
43762                continue
43763            if look < size and chr(s[look]) == "{":
43764                while look < size and chr(s[look]) != "}":
43765                    look += 1
43766                if look > pos + 1 and look < size and chr(s[look]) == "}":
43767                    message = "unknown Unicode character name"
43768                    st = s[pos + 1 : look]
43769                    try:
43770                        chr_codec = unicodedata.lookup("%s" % st)
43771                    except LookupError as e:
43772                        x = unicode_call_errorhandler(
43773                            errors, "unicodeescape", message, s, pos - 1, look + 1
43774                        )
43775                    else:
43776                        x = chr_codec, look + 1
43777                    p.append(x[0])
43778                    pos = x[1]
43779                else:
43780                    if not final:
43781                        pos = 0
43782                        break
43783                    x = unicode_call_errorhandler(
43784                        errors, "unicodeescape", message, s, pos - 1, look + 1
43785                    )
43786                    p.append(x[0])
43787                    pos = x[1]
43788            else:
43789                if not final:
43790                    pos = 0
43791                    break
43792                x = unicode_call_errorhandler(
43793                    errors, "unicodeescape", message, s, pos - 1, look + 1
43794                )
43795                p.append(x[0])
43796                pos = x[1]
43797        else:
43798            if not found_invalid_escape:
43799                found_invalid_escape = True
43800                warnings.warn(
43801                    "invalid escape sequence '\\%c'" % ch, DeprecationWarning, 2
43802                )
43803            p.append("\\")
43804            p.append(ch)
43805    return p, pos
43806"#,
43807        );
43808        let f = find_code(&code, "f").expect("missing f code");
43809
43810        let mut saw_strong_final = false;
43811        let mut saw_borrow_p_after_warn = false;
43812        let mut saw_borrow_ch_after_warn = false;
43813        let mut after_warn_attr = false;
43814
43815        for unit in f.instructions.iter() {
43816            match unit.op {
43817                Instruction::LoadAttr { namei } => {
43818                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
43819                    let name = f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str();
43820                    if name == "warn" {
43821                        after_warn_attr = true;
43822                    }
43823                }
43824                Instruction::LoadFast { var_num } => {
43825                    let idx = usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))));
43826                    let name = f.varnames[idx].as_str();
43827                    if name == "final" {
43828                        saw_strong_final = true;
43829                    }
43830                }
43831                Instruction::LoadFastBorrow { var_num } => {
43832                    let idx = usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))));
43833                    let name = f.varnames[idx].as_str();
43834                    if after_warn_attr && name == "p" {
43835                        saw_borrow_p_after_warn = true;
43836                    }
43837                    if after_warn_attr && name == "ch" {
43838                        saw_borrow_ch_after_warn = true;
43839                    }
43840                }
43841                _ => {}
43842            }
43843        }
43844
43845        assert!(
43846            saw_strong_final,
43847            "expected named-except fallback guards to deopt final to strong LOAD_FAST"
43848        );
43849        assert!(
43850            saw_borrow_p_after_warn && saw_borrow_ch_after_warn,
43851            "expected outer invalid-escape tail to keep borrowed p/ch loads"
43852        );
43853    }
43854
43855    #[test]
43856    fn imap_idle_status_debug_tail_keeps_borrow() {
43857        let code = compile_exec(
43858            "\
43859def f(self, exc_type, CRLF, OSError):
43860    imap = self._imap
43861    try:
43862        imap.send(b'DONE' + CRLF)
43863        status, [msg] = imap._command_complete('IDLE', self._tag)
43864        if __debug__ and imap.debug >= 4:
43865            imap._mesg(f'idle status: {status} {msg!r}')
43866    except OSError:
43867        if not exc_type:
43868            raise
43869    return False
43870",
43871        );
43872        let f = find_code(&code, "f").expect("missing f code");
43873        let instructions: Vec<_> = f
43874            .instructions
43875            .iter()
43876            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43877            .collect();
43878        let mesg_attr = instructions
43879            .iter()
43880            .position(|unit| match unit.op {
43881                Instruction::LoadAttr { namei } => {
43882                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
43883                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "_mesg"
43884                }
43885                _ => false,
43886            })
43887            .expect("missing _mesg attr load");
43888        let tail = &instructions[mesg_attr.saturating_sub(1)..];
43889
43890        assert!(
43891            tail.iter()
43892                .any(|unit| matches!(unit.op, Instruction::LoadFastBorrow { .. })),
43893            "expected idle status debug tail to keep borrowed loads, got tail={tail:?}"
43894        );
43895    }
43896
43897    #[test]
43898    fn match_async_comprehension_iter_keeps_capture_borrow() {
43899        let code = compile_exec(
43900            r#"
43901async def name_4():
43902    match b'':
43903        case True:
43904            pass
43905        case name_5 if f'e':
43906            {name_3: name_4 async for name_2 in name_5}
43907        case []:
43908            pass
43909    [[]]
43910"#,
43911        );
43912        let name_4 = find_code(&code, "name_4").expect("missing name_4 code");
43913        let Some(get_aiter_pos) = name_4
43914            .instructions
43915            .iter()
43916            .position(|unit| matches!(unit.op, Instruction::GetAiter))
43917        else {
43918            panic!("missing GET_AITER in name_4");
43919        };
43920        let prev = &name_4.instructions[get_aiter_pos - 1];
43921        assert!(
43922            matches!(
43923                prev.op,
43924                Instruction::LoadFastBorrow { var_num }
43925                    if name_4.varnames[usize::from(var_num.get(OpArg::new(u32::from(u8::from(prev.arg)))))] == "name_5"
43926            ),
43927            "expected async comprehension iterator capture to borrow name_5 before GET_AITER, got {prev:?}"
43928        );
43929    }
43930
43931    #[test]
43932    fn match_fail_cleanup_label_reuse_keeps_post_match_borrow() {
43933        let code = compile_exec(
43934            r#"
43935def f(self):
43936    match 0:
43937        case 0:
43938            x = True
43939    self.assertIs(x, True)
43940"#,
43941        );
43942        let f = find_code(&code, "f").expect("missing f code");
43943        let instructions: Vec<_> = f
43944            .instructions
43945            .iter()
43946            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43947            .collect();
43948        let assert_is_attr = instructions
43949            .iter()
43950            .position(|unit| match unit.op {
43951                Instruction::LoadAttr { namei } => {
43952                    let load_attr = namei.get(OpArg::new(u32::from(u8::from(unit.arg))));
43953                    f.names[usize::try_from(load_attr.name_idx()).unwrap()].as_str() == "assertIs"
43954                }
43955                _ => false,
43956            })
43957            .expect("missing assertIs attr load");
43958        let receiver = &instructions[assert_is_attr - 1];
43959        assert!(
43960            matches!(
43961                receiver.op,
43962                Instruction::LoadFastBorrow { var_num }
43963                    if f.varnames[usize::from(var_num.get(OpArg::new(u32::from(u8::from(receiver.arg)))))] == "self"
43964            ),
43965            "CPython codegen_match_inner uses USE_LABEL(c, end), so post-match receiver should stay borrowed; got {receiver:?}"
43966        );
43967    }
43968
43969    #[test]
43970    fn match_or_preserves_explicit_success_jumps() {
43971        let code = compile_exec(
43972            r#"
43973def f(w):
43974    match w:
43975        case 1 | 2 | 3:
43976            out = locals()
43977            del out["w"]
43978            return out
43979"#,
43980        );
43981        let f = find_code(&code, "f").expect("missing f code");
43982        let instructions: Vec<_> = f
43983            .instructions
43984            .iter()
43985            .filter(|unit| !matches!(unit.op, Instruction::Cache))
43986            .collect();
43987        let locals_pos = instructions
43988            .iter()
43989            .position(|unit| match unit.op {
43990                Instruction::LoadGlobal { namei } => {
43991                    let name = namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) >> 1;
43992                    f.names[usize::try_from(name).unwrap()].as_str() == "locals"
43993                }
43994                _ => false,
43995            })
43996            .expect("missing locals load");
43997        let pattern_prefix = &instructions[..locals_pos];
43998        let false_jumps = pattern_prefix
43999            .iter()
44000            .filter(|unit| matches!(unit.op, Instruction::PopJumpIfFalse { .. }))
44001            .count();
44002        let success_jumps = pattern_prefix
44003            .iter()
44004            .filter(|unit| matches!(unit.op, Instruction::JumpForward { .. }))
44005            .count();
44006        assert_eq!(
44007            false_jumps, 3,
44008            "CPython codegen_pattern_or() keeps each alternative as false-jump plus success jump; got prefix={pattern_prefix:?}"
44009        );
44010        assert_eq!(
44011            success_jumps, 3,
44012            "CPython codegen_pattern_or() keeps explicit success JUMPs for all alternatives; got prefix={pattern_prefix:?}"
44013        );
44014    }
44015
44016    #[test]
44017    fn with_protected_generator_tail_after_cleanup_uses_strong_loads() {
44018        let code = compile_exec(
44019            r#"
44020def f(scandir, fspath, path, reversed, top, OSError, topdown=True, followlinks=False):
44021    stack = [fspath(top)]
44022    islink, join = path.islink, path.join
44023    while stack:
44024        top = stack.pop()
44025        dirs = []
44026        nondirs = []
44027        walk_dirs = []
44028        try:
44029            with scandir(top) as entries:
44030                for entry in entries:
44031                    try:
44032                        is_dir = entry.is_dir()
44033                    except OSError:
44034                        is_dir = False
44035                    if is_dir:
44036                        dirs.append(entry.name)
44037                    else:
44038                        nondirs.append(entry.name)
44039                    if not topdown and is_dir:
44040                        if followlinks:
44041                            walk_into = True
44042                        else:
44043                            try:
44044                                is_symlink = entry.is_symlink()
44045                            except OSError:
44046                                is_symlink = False
44047                            walk_into = not is_symlink
44048                        if walk_into:
44049                            walk_dirs.append(entry.path)
44050        except OSError:
44051            continue
44052        if topdown:
44053            yield top, dirs, nondirs
44054            for dirname in reversed(dirs):
44055                new_path = join(top, dirname)
44056                if not followlinks and islink(new_path):
44057                    continue
44058                stack.append(new_path)
44059        else:
44060            stack.append((top, dirs, nondirs))
44061            for new_path in reversed(walk_dirs):
44062                stack.append(new_path)
44063"#,
44064        );
44065        let f = find_code(&code, "f").expect("missing f code");
44066        let instructions: Vec<_> = f
44067            .instructions
44068            .iter()
44069            .filter(|unit| !matches!(unit.op, Instruction::Cache))
44070            .collect();
44071        let yield_pos = instructions
44072            .iter()
44073            .position(|unit| matches!(unit.op, Instruction::YieldValue { .. }))
44074            .expect("missing YIELD_VALUE");
44075        assert!(
44076            matches!(
44077                instructions[yield_pos - 3].op,
44078                Instruction::LoadFastLoadFast { .. }
44079            ) && matches!(instructions[yield_pos - 2].op, Instruction::LoadFast { .. }),
44080            "CPython keeps the yielded tuple inputs strong after with cleanup, got {:?} {:?}",
44081            instructions[yield_pos - 3],
44082            instructions[yield_pos - 2],
44083        );
44084        assert!(
44085            instructions[yield_pos + 1..]
44086                .iter()
44087                .take(30)
44088                .any(|unit| matches!(unit.op, Instruction::LoadFast { .. })),
44089            "expected post-yield traversal tail to contain strong LOAD_FAST ops"
44090        );
44091    }
44092
44093    #[test]
44094    fn yield_from_finally_cleanup_keeps_normal_path_borrows() {
44095        let code = compile_exec(
44096            r#"
44097def f(_fwalk, stack, isbytes, topdown, onerror, follow_symlinks, close):
44098    try:
44099        while stack:
44100            yield from _fwalk(stack, isbytes, topdown, onerror, follow_symlinks)
44101    finally:
44102        while stack:
44103            action, value = stack.pop()
44104            if action == 2:
44105                close(value)
44106"#,
44107        );
44108        let f = find_code(&code, "f").expect("missing f code");
44109        let instructions: Vec<_> = f
44110            .instructions
44111            .iter()
44112            .filter(|unit| !matches!(unit.op, Instruction::Cache))
44113            .collect();
44114        let send_pos = instructions
44115            .iter()
44116            .position(|unit| matches!(unit.op, Instruction::Send { .. }))
44117            .expect("missing SEND");
44118        assert!(
44119            instructions[..send_pos]
44120                .iter()
44121                .any(|unit| matches!(unit.op, Instruction::LoadFastBorrow { .. })),
44122            "CPython keeps yield-from normal path loads borrowed, got prefix={:?}",
44123            &instructions[..send_pos]
44124        );
44125        let cleanup_pop = instructions
44126            .iter()
44127            .position(|unit| matches!(unit.op, Instruction::StoreFastStoreFast { .. }))
44128            .expect("missing finally cleanup unpack");
44129        assert!(
44130            instructions[..cleanup_pop]
44131                .iter()
44132                .rev()
44133                .take(12)
44134                .any(|unit| matches!(unit.op, Instruction::LoadFastBorrow { .. })),
44135            "CPython keeps normal finally cleanup loads borrowed, got cleanup prefix={:?}",
44136            &instructions[cleanup_pop.saturating_sub(12)..cleanup_pop]
44137        );
44138    }
44139}