1#![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
44trait ExprExt {
46 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#[derive(Debug, Clone)]
87pub enum FBlockDatum {
88 None,
89 FinallyBody(Vec<ast::Stmt>),
91 ExceptionName(String),
93}
94
95#[derive(Debug, Clone)]
97enum SuperCallType<'a> {
98 TwoArg {
100 class_arg: &'a ast::Expr,
101 self_arg: &'a ast::Expr,
102 },
103 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 pub(crate) fb_block: ir::InstructionSequenceLabel,
119 pub(crate) fb_exit: ir::InstructionSequenceLabel,
121 pub fb_range: TextRange,
122 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}
174struct Compiler<'a> {
176 code_stack: Vec<ir::CodeInfo>,
177 symbol_table_stack: Vec<SymbolTable>,
178 source_file: SourceFile,
179 current_source_range: TextRange,
181 future_features: bytecode::CodeFlags,
182 future_annotations: bool,
183 ctx: CompileContext,
184 opts: CompileOpts,
185 in_annotation: bool,
186 interactive: bool,
189 do_not_emit_bytecode: u32,
193 disable_warning: u32,
195 syntax_warning_handler: Option<&'a mut SyntaxWarningHandler<'a>>,
196 save_nested_seqs: bool,
198}
199
200#[derive(Clone, Copy, Debug, Eq, PartialEq)]
206pub enum FutureFeature {
207 AbsoluteImport,
211
212 Annotations,
216
217 BarryAsFLUFL,
221
222 Braces,
226
227 Division,
231
232 GeneratorStop,
236
237 Generators,
241
242 NestedScopes,
246
247 PrintFunction,
251
252 UnicodeLiterals,
256
257 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 pub optimize: u8,
302 pub debug_ranges: bool,
304 pub int_max_str_digits: usize,
306 pub allow_top_level_await: bool,
308 pub future_features: bytecode::CodeFlags,
310 pub dont_imply_dedent: bool,
312 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 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
388pub 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
456pub 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
465pub 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
546pub 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
606pub 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 ($c:expr, $enum:ident :: $op:ident { $arg:ident $(,)? } $(,)?) => {
690 $c.emit_arg($arg, |x| $enum::$op { $arg: x })
691 };
692
693 ($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 ($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 ($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#[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#[derive(Clone)]
762pub struct PatternContext {
763 pub stores: Vec<Name>,
765 pub allow_irrefutable: bool,
767 pub fail_pop: Vec<BlockIdx>,
769 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#[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 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, 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_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 fn compile_slice_two_parts(&mut self, s: &ast::ExprSlice) -> CompileResult<()> {
1270 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 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 fn compile_subscript(
1291 &mut self,
1292 value: &ast::Expr,
1293 slice: &ast::Expr,
1294 ctx: ast::ExprContext,
1295 ) -> CompileResult<()> {
1296 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 self.compile_expression(value)?;
1305
1306 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 self.compile_expression(slice)?;
1319 }
1320
1321 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 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 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 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 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 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 self.compile_expression(elt)?;
1455
1456 if sequence_built {
1457 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 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 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 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 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 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 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 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 fn push_symbol_table(&mut self) -> CompileResult<&SymbolTable> {
1614 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 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 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 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 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 fn pop_annotation_symbol_table(&mut self) {
1760 self.symbol_table_stack.pop().expect("compiler bug");
1761 }
1762
1763 fn pop_symbol_table(&mut self) -> SymbolTable {
1765 self.symbol_table_stack.pop().expect("compiler bug")
1766 }
1767
1768 fn can_optimize_super_call<'a>(
1771 &self,
1772 value: &'a ast::Expr,
1773 attr: &str,
1774 ) -> Option<SuperCallType<'a>> {
1775 let ast::Expr::Call(ast::ExprCall {
1777 func, arguments, ..
1778 }) = value
1779 else {
1780 return None;
1781 };
1782
1783 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 if attr == "__class__" {
1793 return None;
1794 }
1795
1796 if !arguments.keywords.is_empty() {
1798 return None;
1799 }
1800
1801 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 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 let args = &arguments.args;
1818
1819 if args.iter().any(|arg| matches!(arg, ast::Expr::Starred(_))) {
1821 return None;
1822 }
1823
1824 match args.len() {
1825 2 => {
1826 Some(SuperCallType::TwoArg {
1828 class_arg: &args[0],
1829 self_arg: &args[1],
1830 })
1831 }
1832 0 => {
1833 let info = self.code_stack.last()?;
1836 if info.metadata.argcount == 0 && info.metadata.posonlyargcount == 0 {
1837 return None;
1838 }
1839
1840 {
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, }
1855 }
1856
1857 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 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 self.compile_expression(class_arg)?;
1876 self.compile_expression(self_arg)?;
1877 }
1878 SuperCallType::ZeroArg => {
1879 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 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 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 fn enter_scope(
1931 &mut self,
1932 name: &str,
1933 scope_type: CompilerScope,
1934 key: usize, lineno: u32,
1936 ) -> CompileResult<()> {
1937 let source_path = self.source_file.name().to_owned();
1941
1942 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 let varname_cache: IndexSet<Name> = ste.varnames.iter().cloned().collect();
1954 let nparams = ste.varnames.len();
1955
1956 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 if ste.needs_class_closure {
1973 debug_assert_eq!(scope_type, CompilerScope::Class);
1975 cellvar_cache.insert("__class__".to_string());
1976 }
1977
1978 if ste.needs_classdict {
1980 debug_assert_eq!(scope_type, CompilerScope::Class);
1982 cellvar_cache.insert("__classdict__".to_string());
1983 }
1984
1985 if Self::scope_needs_conditional_annotations_cell(ste) {
1987 cellvar_cache.insert("__conditional_annotations__".to_string());
1988 }
1989
1990 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 if scope_type == CompilerScope::Annotation && ste.has_conditional_annotations {
2035 freevar_cache.insert("__conditional_annotations__".to_string());
2036 }
2037
2038 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, 0, 0, ),
2048 CompilerScope::Comprehension => (
2049 bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
2050 0,
2051 1, 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, 0,
2064 0,
2065 ),
2066 CompilerScope::TypeAlias | CompilerScope::TypeVariable => (
2067 bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED,
2068 1, 0,
2070 0,
2071 ),
2072 };
2073
2074 if ste.is_method {
2075 flags |= bytecode::CodeFlags::METHOD;
2076 }
2077
2078 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 let private = if !self.code_stack.is_empty() {
2101 self.code_stack.last().unwrap().private.clone()
2102 } else {
2103 None
2104 };
2105
2106 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, 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 self.code_stack.push(code_info);
2147
2148 if scope_type != CompilerScope::Module {
2150 self.set_qualname();
2151 }
2152
2153 self.emit_resume_for_scope(scope_type, lineno);
2156
2157 Ok(())
2158 }
2159
2160 fn emit_resume_for_scope(&mut self, scope_type: CompilerScope, lineno: u32) {
2163 let lineno_override = if scope_type == CompilerScope::Module {
2166 Some(0)
2167 } else {
2168 None
2169 };
2170
2171 let location = SourceLocation {
2173 line: OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN),
2174 character_offset: OneIndexed::MIN, };
2176 let end_location = location; 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 let table = self.push_symbol_table()?;
2203 let scope_type = table.typ;
2204
2205 let key = self.symbol_table_stack.len() - 1;
2207
2208 let lineno = self.get_source_line_number().get();
2210
2211 self.enter_scope(obj_name, scope_type, key, lineno.to_u32())?;
2213
2214 if let Some(info) = self.code_stack.last_mut() {
2218 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 fn exit_scope(&mut self) -> CodeObject {
2240 self.pop_symbol_table();
2241 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 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 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 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 self.set_annotation_qualname(func_name);
2323
2324 self.configure_annotation_format_parameter();
2327
2328 self.emit_format_validation();
2331
2332 Ok(Some(saved_ctx))
2333 }
2334
2335 fn emit_format_validation(&mut self) {
2338 emit!(
2340 self,
2341 Instruction::LoadFast {
2342 var_num: oparg::VarNum::from_u32(0)
2343 }
2344 );
2345
2346 self.emit_load_const(ConstantData::Integer { value: 2.into() });
2348
2349 emit!(
2351 self,
2352 Instruction::CompareOp {
2353 opname: ComparisonOperator::Greater
2354 }
2355 );
2356
2357 let body_block = self.new_block();
2359 emit!(self, Instruction::PopJumpIfFalse { delta: body_block });
2360
2361 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 self.use_cpython_label_block(body_block);
2377 }
2378
2379 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 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 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 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 }
2485
2486 FBlockType::ForLoop => {
2487 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 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 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 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); 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 *loc = Some(info.fb_range);
2581 self.set_unwind_source_range(*loc);
2582 emit!(self, PseudoInstruction::PopBlock);
2583
2584 if preserve_tos {
2585 self.set_unwind_source_range(*loc);
2588 emit!(self, Instruction::Swap { i: 3 }); self.set_unwind_source_range(*loc);
2590 emit!(self, Instruction::Swap { i: 2 }); }
2592
2593 self.set_unwind_source_range(*loc);
2594 self.compile_call_exit_with_nones();
2595
2596 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 self.set_unwind_source_range(*loc);
2607 emit!(self, Instruction::PopTop);
2608 *loc = None;
2609 }
2610
2611 FBlockType::HandlerCleanup => {
2612 if let FBlockDatum::ExceptionName(_) = info.fb_datum {
2614 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 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 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 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 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(©, 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 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 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 fn set_qualname(&mut self) -> String {
2745 self.set_qualname_for_function(None)
2746 }
2747
2748 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 stack_size <= 1 {
2770 return current_obj_name;
2771 }
2772
2773 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 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 return current_obj_name;
2797 }
2798 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 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 if let Some(parent_table) = self.symbol_table_stack.last()
2825 && let Some(symbol) = parent_table.lookup(¤t_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 let target = ¤t_obj_name.clone().into();
2833 let mangled_name = self.mangle(target);
2834
2835 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 let base = if force_global {
2849 None
2851 } else {
2852 let parent_obj_name = &parent.metadata.name;
2854
2855 let is_function_parent = matches!(
2857 parent_scope,
2858 Some(
2859 CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda
2860 )
2861 );
2862
2863 let parent_qualname = parent.metadata.qualname.as_ref().unwrap_or(parent_obj_name);
2865
2866 if is_function_parent {
2867 Some(format!("{parent_qualname}.<locals>"))
2869 } else if parent_qualname == "<module>" {
2870 None
2872 } else {
2873 Some(parent_qualname.clone())
2875 }
2876 };
2877
2878 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 fn compile_name(&mut self, name: &Name, usage: NameUsage) -> CompileResult<()> {
3255 enum NameOp {
3256 Fast,
3257 Global,
3258 Deref,
3259 Name,
3260 DictOrGlobals, }
3262
3263 let name = self.mangle(name);
3264
3265 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 let is_function_like = self.ctx.in_func();
3275
3276 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 let symbol = current_table.lookup(&name);
3289
3290 let symbol = if symbol.is_none() && (is_typeparams || is_annotation) {
3292 self.symbol_table_stack
3293 .get(self.symbol_table_stack.len() - 2) .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 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 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 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 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 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 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 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 } 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 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 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 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 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 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 for name in names {
3600 let name = &name;
3601 let idx = self.name(name.name.as_str());
3602 emit!(self, Instruction::ImportFrom { namei: idx });
3604
3605 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 emit!(self, Instruction::PopTop);
3615 }
3616 }
3617 ast::Stmt::Expr(ast::StmtExpr { value, .. }) => {
3618 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 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 }
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 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 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 self.compile_break_continue(statement.range(), true)?;
3798 }
3799 ast::Stmt::Continue(_) => {
3800 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 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 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); }
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 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(¶meters.posonlyargs)
4068 .chain(¶meters.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 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 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 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 self.push_symbol_table()?;
4120
4121 let key = self.symbol_table_stack.len() - 1;
4123 let lineno = self.get_source_line_number().get().to_u32();
4124
4125 self.enter_scope(name, CompilerScope::TypeVariable, key, lineno)?;
4127
4128 self.configure_annotation_format_parameter();
4129
4130 self.emit_format_validation();
4131
4132 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 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 self.set_source_range(expr_range);
4153 emit!(self, Instruction::ReturnValue);
4154 self.emit_return_const_no_location(ConstantData::None);
4155
4156 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 fn compile_type_params(&mut self, type_params: &ast::TypeParams) -> CompileResult<()> {
4212 let mut seen_default = false;
4213 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 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 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 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 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 self.use_cpython_label_block(handler_block);
4770 emit!(
4773 self,
4774 PseudoInstruction::SetupCleanup {
4775 delta: cleanup_block
4776 }
4777 );
4778 self.set_no_location();
4779
4780 emit!(self, Instruction::PushExcInfo);
4782 self.set_no_location();
4783 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 emit!(self, Instruction::BuildList { count: 0 });
4813 emit!(self, Instruction::Copy { i: 2 });
4814 }
4815
4816 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 emit!(self, Instruction::CheckEgMatch);
4827 emit!(self, Instruction::Copy { i: 1 });
4832 emit!(
4833 self,
4834 Instruction::PopJumpIfNone {
4835 delta: no_match_block
4836 }
4837 );
4838 }
4839
4840 let cleanup_end_block = self.new_block();
4844 let cleanup_body_block = self.new_block();
4845
4846 if let Some(alias) = name {
4848 self.store_name(alias.id())?;
4849 } else {
4850 emit!(self, Instruction::PopTop); }
4852 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 self.compile_statements(body)?;
4876
4877 self.pop_fblock_label(FBlockType::HandlerCleanup, cleanup_body_label);
4879 emit!(self, PseudoInstruction::PopBlock);
4880 self.set_no_location();
4881
4882 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 self.use_cpython_label_block(cleanup_end_block);
4897 if let Some(alias) = name {
4902 self.emit_no_location_exception_name_cleanup(alias.id())?;
4903 }
4904
4905 emit!(self, Instruction::ListAppend { i: 3 });
4911 self.set_no_location();
4912 emit!(self, Instruction::PopTop);
4916 self.set_no_location();
4917 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); 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 self.pop_fblock_label(
4960 FBlockType::ExceptionGroupHandler,
4961 ir::InstructionSequenceLabel::NO_LABEL,
4962 );
4963 let reraise_block = self.new_block();
4964
4965 self.use_cpython_label_block(reraise_star_block);
4967 emit!(
4972 self,
4973 Instruction::CallIntrinsic2 {
4974 func: bytecode::IntrinsicFunction2::PrepReraiseStar
4975 }
4976 );
4977 self.set_no_location();
4978 emit!(self, Instruction::Copy { i: 1 });
4982 self.set_no_location();
4983 emit!(
4987 self,
4988 Instruction::PopJumpIfNotNone {
4989 delta: reraise_block
4990 }
4991 );
4992 self.set_no_location();
4993 emit!(self, Instruction::PopTop);
4998 self.set_no_location();
4999 emit!(self, PseudoInstruction::PopBlock);
5002 self.set_no_location();
5003 emit!(self, Instruction::PopExcept);
5005 self.set_no_location();
5006 emit!(
5009 self,
5010 PseudoInstruction::JumpNoInterrupt { delta: end_block }
5011 );
5012 self.set_no_location();
5013
5014 self.use_cpython_label_block(reraise_block);
5016
5017 emit!(self, PseudoInstruction::PopBlock);
5019 self.set_no_location();
5020 emit!(self, Instruction::Swap { i: 2 });
5021 self.set_no_location();
5022 emit!(self, Instruction::PopExcept);
5026 self.set_no_location();
5027 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 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 let defaults: Vec<_> = core::iter::empty()
5060 .chain(¶meters.posonlyargs)
5061 .chain(¶meters.args)
5062 .filter_map(|x| x.default.as_deref())
5063 .collect();
5064
5065 if !defaults.is_empty() {
5066 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 let mut kw_with_defaults = vec![];
5082 for kwonlyarg in ¶meters.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 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 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 self.enter_function(name, parameters)?;
5123 self.current_code_info()
5124 .flags
5125 .set(bytecode::CodeFlags::COROUTINE, is_async);
5126
5127 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 in_async_scope: is_async,
5138 };
5139
5140 self.set_qualname();
5142
5143 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 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 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 self.compile_statements(body)?;
5176
5177 if stop_iteration_block.is_some() {
5178 self.emit_return_const_no_location(ConstantData::None);
5179 }
5180
5181 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 let code = self.exit_scope();
5199 self.ctx = prev_ctx;
5200
5201 self.set_source_range(closure_range);
5202
5203 self.make_closure(code, funcflags)?;
5205
5206 Ok(())
5210 }
5211
5212 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 if self.push_annotation_symbol_table() {
5229 self.pop_annotation_symbol_table();
5230 }
5231 return Ok(false);
5232 }
5233
5234 let Some(saved_ctx) = self.enter_annotation_scope(func_name, func_range)? else {
5236 return Ok(false);
5237 };
5238
5239 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 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) = ¶m.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 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 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 let annotate_code = self.exit_annotation_scope(saved_ctx);
5296
5297 self.set_source_range(func_range);
5299 self.make_closure(annotate_code, bytecode::MakeFunctionFlags::new())?;
5300
5301 Ok(true)
5302 }
5303
5304 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 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 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 let has_conditional = self.current_symbol_table().has_conditional_annotations;
5406
5407 if !self.push_current_annotation_symbol_table() {
5409 return Ok(false);
5410 }
5411
5412 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 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 self.configure_annotation_format_parameter();
5434
5435 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 let annotation_table = self.pop_symbol_table();
5523 self.symbol_table_stack
5525 .last_mut()
5526 .expect("no module symbol table")
5527 .annotation_block = Some(Box::new(annotation_table));
5528 self.ctx = saved_ctx;
5530 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 self.set_source_range(loc);
5540 self.make_closure(annotate_code, bytecode::MakeFunctionFlags::new())?;
5541
5542 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 #[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>, is_async: bool,
5565 type_params: Option<&ast::TypeParams>,
5566 preserve_value_before_store: bool,
5567 ) -> CompileResult<()> {
5568 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 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 let firstlineno_range = decorator_list
5591 .first()
5592 .map_or(stmt_source_range, |decorator| decorator.expression.range());
5593
5594 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 let saved_ctx = self.ctx;
5603
5604 if is_generic {
5605 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 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 self.ctx = CompileContext {
5630 in_class: saved_ctx.in_class,
5631 func: FunctionContext::Function,
5632 in_async_scope: false,
5633 };
5634
5635 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 self.compile_type_params(type_params.unwrap())?;
5654
5655 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 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 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 if is_generic {
5684 self.set_source_range(def_source_range);
5687 emit!(self, Instruction::Swap { i: 2 });
5688
5689 self.set_source_range(def_source_range);
5691 emit!(
5692 self,
5693 Instruction::CallIntrinsic2 {
5694 func: bytecode::IntrinsicFunction2::SetFunctionTypeParams,
5695 }
5696 );
5697
5698 emit!(self, Instruction::ReturnValue);
5700 self.set_no_location();
5701
5702 self.current_code_info().metadata.argcount = num_typeparam_args;
5704 self.current_code_info().nparams = num_typeparam_args as usize;
5705
5706 let type_params_code = self.exit_scope();
5708 self.ctx = saved_ctx;
5709
5710 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 self.set_source_range(def_source_range);
5732 emit!(self, Instruction::PushNull);
5733 self.set_source_range(def_source_range);
5735 emit!(self, Instruction::Call { argc: 0 });
5736 }
5737 }
5738
5739 self.apply_decorators(decorator_list);
5741
5742 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 fn get_ref_type(&self, name: &Name) -> Result<SymbolScope, CodegenErrorType> {
5755 let table = self.symbol_table_stack.last().unwrap();
5756
5757 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 fn make_closure(
5785 &mut self,
5786 code: CodeObject,
5787 flags: bytecode::MakeFunctionFlags,
5788 ) -> CompileResult<()> {
5789 let has_freevars = !code.freevars.is_empty();
5791 if has_freevars {
5792 for var in &code.freevars {
5795 let ref_type = self
5803 .get_ref_type(&var.as_str().into())
5804 .map_err(|e| self.error(e))?;
5805
5806 let parent_code = self.code_stack.last().unwrap();
5808 let cellvars_len = parent_code.metadata.cellvars.len();
5809
5810 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 emit!(
5851 self,
5852 Instruction::BuildTuple {
5853 count: code.freevars.len().to_u32(),
5854 }
5855 );
5856 }
5857
5858 self.emit_load_const(ConstantData::Code {
5860 code: Box::new(code),
5861 });
5862
5863 emit!(self, Instruction::MakeFunction);
5865
5866 if has_freevars {
5871 emit!(
5872 self,
5873 Instruction::SetFunctionAttribute {
5874 flag: bytecode::MakeFunctionFlag::Closure
5875 }
5876 );
5877 }
5878
5879 if flags.contains(&bytecode::MakeFunctionFlag::Annotations) {
5881 emit!(
5882 self,
5883 Instruction::SetFunctionAttribute {
5884 flag: bytecode::MakeFunctionFlag::Annotations
5885 }
5886 );
5887 }
5888
5889 if flags.contains(&bytecode::MakeFunctionFlag::Annotate) {
5891 emit!(
5892 self,
5893 Instruction::SetFunctionAttribute {
5894 flag: bytecode::MakeFunctionFlag::Annotate
5895 }
5896 );
5897 }
5898
5899 if flags.contains(&bytecode::MakeFunctionFlag::KwOnlyDefaults) {
5901 emit!(
5902 self,
5903 Instruction::SetFunctionAttribute {
5904 flag: bytecode::MakeFunctionFlag::KwOnlyDefaults
5905 }
5906 );
5907 }
5908
5909 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 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 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 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 let qualname = self.set_qualname();
5957
5958 self.code_stack.last_mut().unwrap().private = Some(name.to_owned());
5960
5961 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 self.load_name(&"__name__".into())?;
5968 self.store_name(&"__module__".into())?;
5969
5970 self.emit_load_const(ConstantData::Str {
5972 value: qualname.into(),
5973 });
5974 self.store_name(&"__qualname__".into())?;
5975
5976 self.emit_load_const(ConstantData::Integer {
5978 value: BigInt::from(firstlineno),
5979 });
5980 self.store_name(&"__firstlineno__".into())?;
5981
5982 if type_params.is_some() {
5985 self.load_name(&".type_params".into())?;
5986 self.store_name(&"__type_params__".into())?;
5987 }
5988
5989 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 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 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 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 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 {
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 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 self.emit_return_value();
6085 self.set_no_location();
6086 self.emit_return_const_no_location(ConstantData::None);
6087
6088 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 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 let saved_ctx = self.ctx;
6130
6131 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 self.code_stack.last_mut().unwrap().private = Some(name.as_str().to_owned());
6145
6146 self.ctx = CompileContext {
6148 in_class: saved_ctx.in_class,
6149 func: FunctionContext::Function,
6150 in_async_scope: false,
6151 };
6152
6153 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 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 if is_generic {
6176 emit!(self, Instruction::LoadBuildClass);
6178 emit!(self, Instruction::PushNull);
6179
6180 self.make_closure(class_code, bytecode::MakeFunctionFlags::new())?;
6183 self.emit_load_const(ConstantData::Str {
6184 value: name.as_str().into(),
6185 });
6186
6187 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 self.emit_return_value();
6214 self.set_no_location();
6215
6216 let type_params_code = self.exit_scope();
6218 self.ctx = saved_ctx;
6219
6220 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 emit!(self, Instruction::LoadBuildClass);
6230 emit!(self, Instruction::PushNull);
6231
6232 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 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 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 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 self.compile_expression(&item.context_expr)?;
6390 self.set_source_range(with_range);
6391
6392 emit!(self, Instruction::Copy { i: 1 }); 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 emit!(
6403 self,
6404 Instruction::LoadSpecial {
6405 method: SpecialMethod::AExit
6406 }
6407 ); emit!(self, Instruction::Swap { i: 2 }); emit!(self, Instruction::Swap { i: 3 }); emit!(
6411 self,
6412 Instruction::LoadSpecial {
6413 method: SpecialMethod::AEnter
6414 }
6415 ); emit!(self, Instruction::Call { argc: 0 }); 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 emit!(
6423 self,
6424 Instruction::LoadSpecial {
6425 method: SpecialMethod::Exit
6426 }
6427 ); emit!(self, Instruction::Swap { i: 2 }); emit!(self, Instruction::Swap { i: 3 }); emit!(
6431 self,
6432 Instruction::LoadSpecial {
6433 method: SpecialMethod::Enter
6434 }
6435 ); emit!(self, Instruction::Call { argc: 0 }); }
6438
6439 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 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 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 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); emit!(self, PseudoInstruction::Jump { delta: after_block });
6507
6508 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 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 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 self.push_fblock_labels(
6568 FBlockType::ForLoop,
6569 for_label,
6570 after_label,
6571 FBlockDatum::None,
6572 )?;
6573 }
6574
6575 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 self.push_fblock_labels(
6592 FBlockType::ForLoop,
6593 for_label,
6594 after_label,
6595 FBlockDatum::None,
6596 )?;
6597
6598 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 emit!(self, PseudoInstruction::PopBlock);
6607 emit!(self, Instruction::NotTaken);
6608
6609 self.compile_store(target)?;
6611 } else {
6612 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 self.pop_fblock_label(FBlockType::ForLoop, for_label);
6640 }
6641
6642 self.use_cpython_label_block(else_block);
6643
6644 if is_async {
6646 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 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 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 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 let pops = pc.on_top + pc.stores.len();
6714 self.ensure_fail_pop(pc, pops);
6716 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 fn emit_and_reset_fail_pop(&mut self, pc: &mut PatternContext, loc: TextRange) {
6740 if pc.fail_pop.is_empty() {
6742 debug_assert!(pc.fail_pop.is_empty());
6743 return;
6744 }
6745 for &label in pc.fail_pop.iter().skip(1).rev() {
6747 self.use_cpython_label_block(label);
6749 self.set_source_range(loc);
6751 emit!(self, Instruction::PopTop);
6752 }
6753 self.use_cpython_label_block(pc.fail_pop[0]);
6756 pc.fail_pop.clear();
6757 pc.fail_pop.shrink_to_fit();
6759 }
6760
6761 fn pattern_helper_rotate(&mut self, loc: TextRange, mut count: usize) {
6763 while count > 1 {
6769 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 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 None => {
6796 self.set_source_range(loc);
6797 emit!(self, Instruction::PopTop);
6798 Ok(())
6799 }
6800 Some(name) => {
6801 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 let rotations = pc.on_top + pc.stores.len() + 1;
6812 self.pattern_helper_rotate(loc, rotations);
6813
6814 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 self.pattern_unpack_helper(loc, patterns)?;
6885 let size = patterns.len();
6886 pc.on_top += size;
6888 for pattern in patterns {
6890 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 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 debug_assert!(Self::pattern_wildcard_star_check(pattern));
6913 continue;
6914 }
6915 self.set_source_range(loc);
6917 emit!(self, Instruction::Copy { i: 1 });
6918 if i < star {
6919 self.set_source_range(loc);
6921 self.emit_load_const(ConstantData::Integer { value: i.into() });
6922 } else {
6923 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 self.set_source_range(loc);
6933 emit!(
6934 self,
6935 Instruction::BinaryOp {
6936 op: BinaryOperator::Subtract
6937 }
6938 );
6939 }
6940 self.set_source_range(loc);
6942 emit!(
6943 self,
6944 Instruction::BinaryOp {
6945 op: BinaryOperator::Subscr
6946 }
6947 );
6948 self.compile_pattern_subpattern(pattern, pc)?;
6950 }
6951 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 let old_allow_irrefutable = pc.allow_irrefutable;
6965 pc.allow_irrefutable = true;
6967 self.compile_pattern(p, pc)?;
6969 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 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 return Err(
6990 self.error_ranged(CodegenErrorType::UnreachableWildcardPattern, p.range)
6991 );
6992 }
6993 return self.pattern_helper_store_name(p.range, p.name.as_ref(), pc);
6995 }
6996
6997 pc.on_top += 1;
6999 self.set_source_range(p.range);
7000 emit!(self, Instruction::Copy { i: 1 });
7001 self.compile_pattern(p.pattern.as_ref().unwrap(), pc)?;
7003 pc.on_top -= 1;
7005 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 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 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 let match_class = p;
7050 let patterns = &match_class.arguments.patterns;
7051
7052 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 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 if n_attrs != 0 {
7088 self.validate_kwd_attrs(&kwd_attrs, &kwd_patterns)?;
7089 }
7090
7091 self.compile_expression(&match_class.cls)?;
7093 self.set_source_range(p.range);
7094
7095 let mut attr_names = vec![];
7097 for name in &kwd_attrs {
7098 attr_names.push(ConstantData::Str {
7100 value: name.as_str().to_string().into(),
7101 });
7102 }
7103
7104 self.emit_load_const(ConstantData::Tuple {
7107 elements: attr_names,
7108 });
7109 emit!(
7111 self,
7112 Instruction::MatchClass {
7113 count: u32::try_from(nargs).unwrap()
7114 }
7115 );
7116 emit!(self, Instruction::Copy { i: 1 });
7118 self.emit_load_const(ConstantData::None);
7120 emit!(
7122 self,
7123 Instruction::IsOp {
7124 invert: Invert::Yes
7125 }
7126 );
7127
7128 pc.on_top += 1;
7130 self.set_source_range(p.range);
7131 self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7132
7133 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 for subpattern in patterns.iter().chain(kwd_patterns.iter()) {
7149 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; }
7157
7158 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 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 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 pc.on_top += 1;
7201
7202 self.set_source_range(p.range);
7203 emit!(self, Instruction::MatchMapping);
7204 self.set_source_range(p.range);
7207 self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7208 if size == 0 && star_target.is_none() {
7212 pc.on_top -= 1;
7214 emit!(self, Instruction::PopTop);
7215 return Ok(());
7216 }
7217
7218 if size > 0 {
7220 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 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 }
7237
7238 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 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 emit!(self, Instruction::BuildTuple { count: size });
7259 emit!(self, Instruction::MatchKeys);
7263 pc.on_top += 2; self.set_source_range(p.range);
7268 emit!(self, Instruction::Copy { i: 1 });
7269 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 self.set_source_range(p.range);
7284 self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse);
7285 emit!(self, Instruction::UnpackSequence { count: size });
7289 if size == 0 {
7291 pc.on_top -= 1;
7292 } else {
7293 pc.on_top += size as usize - 1;
7294 }
7295
7296 for i in 0..size {
7298 pc.on_top -= 1;
7299 self.compile_pattern_subpattern(&patterns[i as usize], pc)?;
7300 }
7301
7302 pc.on_top -= 2;
7306
7307 if let Some(rest_name) = star_target {
7309 self.set_source_range(p.range);
7314 emit!(self, Instruction::BuildMap { count: 0 });
7315 self.set_source_range(p.range);
7317 emit!(self, Instruction::Swap { i: 3 });
7318 self.set_source_range(p.range);
7320 emit!(self, Instruction::DictUpdate { i: 2 });
7321 self.set_source_range(p.range);
7326 emit!(self, Instruction::UnpackSequence { count: size });
7327 let mut remaining = size;
7332 while remaining > 0 {
7333 self.set_source_range(p.range);
7335 emit!(self, Instruction::Copy { i: 1 + remaining });
7336 self.set_source_range(p.range);
7338 emit!(self, Instruction::Swap { i: 2 });
7339 self.set_source_range(p.range);
7341 emit!(self, Instruction::DeleteSubscr);
7342 remaining -= 1;
7344 }
7345 self.pattern_helper_store_name(p.range, Some(rest_name), pc)?;
7350 } else {
7351 self.set_source_range(p.range);
7355 emit!(self, Instruction::PopTop); self.set_source_range(p.range);
7357 emit!(self, Instruction::PopTop); }
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 let end = self.new_block(); 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 let old_pc = pc.clone();
7575 pc.stores = pc.stores.clone();
7577 let mut control: Option<Vec<Name>> = None; for (i, alt) in p.patterns.iter().enumerate() {
7581 pc.stores = Vec::new();
7583 pc.allow_irrefutable = (i == size - 1) && old_pc.allow_irrefutable;
7585 pc.fail_pop.clear();
7587 pc.on_top = 0;
7588 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 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 for i_control in (0..n_stores).rev() {
7606 let name = &control_vec[i_control];
7607 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 assert!(i_stores < i_control, "expected i_stores < i_control");
7618 let rotations = i_stores + 1;
7619 let rotated = pc.stores[0..rotations].to_vec();
7621 for _ in 0..rotations {
7623 pc.stores.remove(0);
7624 }
7625 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 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 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 *pc = old_pc;
7648 pc.stores = pc.stores.clone();
7650 self.set_source_range(p.range());
7655 emit!(self, Instruction::PopTop);
7656 self.jump_to_fail_pop(pc, JumpOp::Jump);
7657
7658 self.use_cpython_label_block(end);
7661
7662 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 self.set_source_range(p.range());
7668 self.pattern_helper_rotate(p.range(), n_rots);
7669 let name = &control.as_ref().unwrap()[i];
7670 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 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 let patterns = &p.patterns; 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 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 = Self::pattern_wildcard_star_check(pattern);
7710 only_wildcard &= star_wildcard;
7711 star = Some(i);
7712 continue;
7713 }
7714 only_wildcard &= Self::pattern_wildcard_check(pattern);
7716 }
7717
7718 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 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 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 pc.on_top -= 1;
7761 if only_wildcard {
7762 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 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 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 self.set_source_range(p.range);
7830 emit!(self, Instruction::IsOp { invert: Invert::No });
7831 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 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 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 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 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 fn compile_compare(
8043 &mut self,
8044 left: &ast::Expr,
8045 ops: &[ast::CmpOp],
8046 comparators: &[ast::Expr],
8047 ) -> CompileResult<()> {
8048 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 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 (op, comparator) in mid_ops.iter().zip(mid_comparators) {
8069 self.compile_expression(comparator)?;
8070
8071 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 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 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 let result = match annotation {
8194 ast::Expr::Starred(ast::ExprStarred { value, .. }) => {
8195 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 if let Some(value) = value {
8254 self.compile_expression(value)?;
8255 self.compile_store(target)?;
8256 }
8257
8258 if simple
8260 && !self.ctx.in_func()
8261 && let ast::Expr::Name(ast::ExprName { id, .. }) = target
8262 {
8263 if self.future_annotations {
8264 self.compile_annotation(annotation)?;
8267 self.set_source_range(loc);
8268 let annotations_name = self.name("__annotations__");
8270 emit!(
8271 self,
8272 Instruction::LoadName {
8273 namei: annotations_name
8274 }
8275 );
8276 self.set_source_range(loc);
8278 self.emit_load_const(ConstantData::Str {
8279 value: self.mangle(id).as_str().into(),
8280 });
8281 self.set_source_range(loc);
8283 emit!(self, Instruction::StoreSubscr);
8284 } else {
8285 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 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 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 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 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 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 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 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 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 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 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 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 self.use_cpython_label_block(send_block);
8789 emit!(self, Instruction::Send { delta: exit_block });
8790
8791 emit!(self, PseudoInstruction::SetupFinally { delta: fail_block });
8793
8794 emit!(self, Instruction::YieldValue { arg: 1 });
8796
8797 emit!(self, PseudoInstruction::PopBlock);
8799 self.set_no_location();
8800
8801 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 emit!(
8815 self,
8816 PseudoInstruction::JumpNoInterrupt { delta: send_block }
8817 );
8818
8819 self.use_cpython_label_block(fail_block);
8826 emit!(self, Instruction::CleanupThrow);
8827
8828 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 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 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 if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
8908 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 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 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 let defaults: Vec<_> = core::iter::empty()
9079 .chain(¶ms.posonlyargs)
9080 .chain(¶ms.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 let mut kw_with_defaults = vec![];
9096 for kwonlyarg in ¶ms.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 self.set_qualname();
9132
9133 self.ctx = CompileContext {
9134 in_class: prev_ctx.in_class,
9135 func: FunctionContext::Function,
9136 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 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 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 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 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 self.compile_expression(body)?;
9306 emit!(
9307 self,
9308 PseudoInstruction::JumpNoInterrupt { delta: after_block }
9309 );
9310 self.set_no_location();
9311
9312 self.use_cpython_label_block(else_block);
9314 self.compile_expression(orelse)?;
9315
9316 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 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 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 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 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 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 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 if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
9637 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 self.set_source_range(attr_access_range);
9668 emit!(self, Instruction::Nop);
9669 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 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 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 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 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 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 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 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 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 let too_big = nelts + nkwelts * 2 > STACK_USE_GUIDELINE as usize;
9855
9856 if !has_starred && !has_double_star && !too_big {
9857 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 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 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 if additional_positional == 0 && nelts == 1 && matches!(args[0], ast::Expr::Starred(_))
9899 {
9900 if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = &args[0] {
9903 self.compile_expression(value)?;
9904 }
9905 } else {
9906 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 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 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 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 if let Some(params) = parameters.as_deref() {
10141 for default in params
10142 .posonlyargs
10143 .iter()
10144 .chain(¶ms.args)
10145 .chain(¶ms.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(¶meters.args)
10218 .chain(¶meters.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(¶meters.args)
10258 .chain(¶meters.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 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 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 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 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 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 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 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 self.push_output_with_symbol_table(comp_table, flags, 0, 1, 0, name)?;
10706
10707 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 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 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 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 emit!(self, Instruction::LoadFast { var_num: arg0 });
10771 } else {
10772 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 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 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 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 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 self.set_source_range(comprehension_range);
10890 self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
10891
10892 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 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 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 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 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; }
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 for (name, comp_sym) in &comp_table.symbols {
10992 if comp_sym.flags.contains(SymbolFlags::DEF_PARAM) {
10993 continue; }
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 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 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 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 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 if let Some(init_collection) = init_collection {
11091 self._emit(init_collection, OpArg::new(0), BlockIdx::NULL);
11092 emit!(self, Instruction::Swap { i: 2 });
11094 }
11095
11096 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 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 for if_condition in &generator.ifs {
11183 self.compile_jump_if(if_condition, false, if_cleanup_block)?;
11184 }
11185 }
11186
11187 compile_element(self, real_loop_depth + 1)?;
11189
11190 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 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 let end_block = self.new_block();
11238 emit!(
11239 self,
11240 PseudoInstruction::JumpNoInterrupt { delta: end_block }
11241 );
11242 self.set_no_location();
11243
11244 self.use_cpython_label_block(cleanup_block);
11246 emit!(self, Instruction::Swap { i: 2 });
11248 self.set_no_location();
11249 emit!(self, Instruction::PopTop); self.set_no_location();
11251
11252 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 emit!(self, Instruction::Reraise { depth: 0 });
11266 self.set_no_location();
11267
11268 self.use_cpython_label_block(end_block);
11270 self.set_source_range(comprehension_range);
11271 }
11272
11273 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 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 }
11345 }
11346 }
11347 Ok(())
11348 }
11349
11350 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 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 fn set_no_location(&mut self) {
11470 self.set_last_emitted_lineno_override(-1);
11471 }
11472
11473 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 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 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 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 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 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 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 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 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 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 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 fn enter_conditional_block(&mut self) {
12202 self.current_code_info().in_conditional_block += 1;
12203 }
12204
12205 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 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 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 if is_break {
12265 self.unwind_fblock(&loop_fblock, false, &mut unwind_loc)?;
12266 }
12267
12268 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 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 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 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 fn cpython_cfg_builder_new_block(&mut self) -> BlockIdx {
12411 self.new_unlabeled_block()
12412 }
12413
12414 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 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 fn generators_contain_await(generators: &[ast::Comprehension]) -> bool {
12542 for (i, generator) in generators.iter().enumerate() {
12543 if i > 0 && Self::contains_await(&generator.iter) {
12545 return true;
12546 }
12547 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 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 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 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 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
13439fn clean_doc(doc: &str) -> String {
13444 let doc = expandtabs(doc, 8);
13445 let margin = doc
13448 .split('\n')
13449 .skip(1) .filter(|line| line.chars().any(|c| c != ' ')) .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 if let Some(first_line) = doc.split('\n').next() {
13458 let trimmed = first_line.trim_start();
13459 if trimmed.len() == first_line.len() && margin == 0 {
13461 return doc.to_owned();
13462 }
13463 cleaned.push_str(trimmed);
13464 }
13465 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
13475fn 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 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
13546fn parse_big_integer(int: &ast::Int) -> Result<BigInt, CodegenErrorType> {
13549 let s = format!("{int}");
13552 let mut s = s.as_str();
13553 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
13577trait 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]
13595 fn fstring_contains_await() {
13596 let range = TextRange::default();
13597 let flags = ast::FStringFlags::empty();
13598
13599 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}