diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index a2848f79d8f..a009cb8cadd 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -4813,6 +4813,8 @@ def test_finalize(self): result = [obj for obj in iter(conn.recv, 'STOP')] self.assertEqual(result, ['a', 'b', 'd10', 'd03', 'd02', 'd01', 'e']) + # TODO: RUSTPYTHON; SIGSEGV due to dict thread-safety issue under aggressive GC + @unittest.skip("TODO: RUSTPYTHON") @support.requires_resource('cpu') def test_thread_safety(self): # bpo-24484: _run_finalizers() should be thread-safe diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 6bb4249b1ce..4d9713a9027 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -645,7 +645,6 @@ def containtest(): self.assertEqual(count_instr_recursively(containtest, 'BUILD_LIST'), 0) self.check_lnotab(containtest) - @unittest.expectedFailure # TODO: RUSTPYTHON; no BUILD_LIST to BUILD_TUPLE optimization def test_iterate_literal_list(self): def forloop(): for x in [a, b]: diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index a04d8ff0837..1c09d6bb463 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1074,12 +1074,12 @@ impl Compiler { .filter(|(_, s)| { s.scope == SymbolScope::Cell || s.flags.contains(SymbolFlags::COMP_CELL) }) - .map(|(name, _)| name.clone()) + .map(|(name, sym)| (name.clone(), sym.flags)) .collect(); let mut param_cells = Vec::new(); let mut nonparam_cells = Vec::new(); - for name in cell_symbols { - if varname_cache.contains(&name) { + for (name, flags) in cell_symbols { + if flags.contains(SymbolFlags::PARAMETER) { param_cells.push(name); } else { nonparam_cells.push(name); @@ -1110,8 +1110,9 @@ impl Compiler { } // Handle implicit __conditional_annotations__ cell if needed - // Only for class scope - module scope uses NAME operations, not DEREF - if ste.has_conditional_annotations && scope_type == CompilerScope::Class { + if ste.has_conditional_annotations + && matches!(scope_type, CompilerScope::Class | CompilerScope::Module) + { cellvar_cache.insert("__conditional_annotations__".to_string()); } @@ -1794,8 +1795,27 @@ impl Compiler { let size_before = self.code_stack.len(); // Set future_annotations from symbol table (detected during symbol table scan) self.future_annotations = symbol_table.future_annotations; + + // Module-level __conditional_annotations__ cell + let has_module_cond_ann = symbol_table.has_conditional_annotations; + if has_module_cond_ann { + self.current_code_info() + .metadata + .cellvars + .insert("__conditional_annotations__".to_string()); + } + self.symbol_table_stack.push(symbol_table); + // Emit MAKE_CELL for module-level cells (before RESUME) + if has_module_cond_ann { + let ncells = self.code_stack.last().unwrap().metadata.cellvars.len(); + for i in 0..ncells { + let i_varnum: oparg::VarNum = u32::try_from(i).expect("too many cellvars").into(); + emit!(self, Instruction::MakeCell { i: i_varnum }); + } + } + self.emit_resume_for_scope(CompilerScope::Module, 1); let (doc, statements) = split_doc(&body.body, &self.opts); @@ -5437,7 +5457,25 @@ impl Compiler { let mut end_async_for_target = BlockIdx::NULL; // The thing iterated: - self.compile_expression(iter)?; + // Optimize: `for x in [a, b, c]` → use tuple instead of list + // (list creation is wasteful for iteration) + // Skip optimization if any element is starred (e.g., `[a, *b, c]`) + if !is_async + && let ast::Expr::List(ast::ExprList { elts, .. }) = iter + && !elts.iter().any(|e| matches!(e, ast::Expr::Starred(_))) + { + for elt in elts { + self.compile_expression(elt)?; + } + emit!( + self, + Instruction::BuildTuple { + count: u32::try_from(elts.len()).expect("too many elements"), + } + ); + } else { + self.compile_expression(iter)?; + } if is_async { if self.ctx.func != FunctionContext::AsyncFunction { @@ -7033,6 +7071,7 @@ impl Compiler { /// For `And`, emits `PopJumpIfFalse`; for `Or`, emits `PopJumpIfTrue`. fn emit_short_circuit_test(&mut self, op: &ast::BoolOp, target: BlockIdx) { emit!(self, Instruction::Copy { i: 1 }); + emit!(self, Instruction::ToBool); match op { ast::BoolOp::And => { emit!(self, Instruction::PopJumpIfFalse { delta: target }); @@ -8554,11 +8593,11 @@ impl Compiler { // fn block_done() - /// Convert a string literal AST node to Wtf8Buf, handling surrogates correctly. + /// Convert a string literal AST node to Wtf8Buf, handling surrogate literals correctly. fn compile_string_value(&self, string: &ast::ExprStringLiteral) -> Wtf8Buf { let value = string.value.to_str(); if value.contains(char::REPLACEMENT_CHARACTER) { - // Might have a surrogate literal; reparse from source to preserve them + // Might have a surrogate literal; reparse from source to preserve them. string .value .iter() @@ -8601,8 +8640,9 @@ impl Compiler { } }, ast::Expr::StringLiteral(s) => { - let value = self.compile_string_value(s); - constants.push(ConstantData::Str { value }); + constants.push(ConstantData::Str { + value: self.compile_string_value(s), + }); } ast::Expr::BytesLiteral(b) => { constants.push(ConstantData::Bytes { diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index b9d30975064..dc3daa14955 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -2172,11 +2172,19 @@ pub(crate) fn label_exception_targets(blocks: &mut [Block]) { blocks[bi].instructions[i].except_handler = handler_info; // Track YIELD_VALUE except stack depth - if matches!( - blocks[bi].instructions[i].instr.real(), - Some(Instruction::YieldValue { .. }) - ) { - last_yield_except_depth = stack.len() as i32; + // Only count for direct yield (arg=0), not yield-from/await (arg=1) + // The yield-from's internal SETUP_FINALLY is not an external except depth + if let Some(Instruction::YieldValue { .. }) = + blocks[bi].instructions[i].instr.real() + { + let yield_arg = u32::from(blocks[bi].instructions[i].arg); + if yield_arg == 0 { + // Direct yield: count actual except depth + last_yield_except_depth = stack.len() as i32; + } else { + // yield-from/await: subtract 1 for the internal SETUP_FINALLY + last_yield_except_depth = (stack.len() as i32) - 1; + } } // Set RESUME DEPTH1 flag based on last yield's except depth diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_bool_op.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_bool_op.snap index a6db9ca4bdb..bc9268b45bd 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_bool_op.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_bool_op.snap @@ -1,5 +1,6 @@ --- source: crates/codegen/src/compile.rs +assertion_line: 9688 expression: "compile_exec(\"\\\nx = Test() and False or False\n\")" --- 1 0 RESUME (0) @@ -9,18 +10,26 @@ expression: "compile_exec(\"\\\nx = Test() and False or False\n\")" 4 CACHE 5 CACHE 6 CACHE - >> 7 COPY (1) - 8 POP_JUMP_IF_FALSE (7) + 7 COPY (1) + 8 TO_BOOL 9 CACHE - 10 NOT_TAKEN - 11 POP_TOP - 12 LOAD_CONST (False) - 13 COPY (1) - 14 POP_JUMP_IF_TRUE (3) - 15 CACHE - 16 NOT_TAKEN - 17 POP_TOP - 18 LOAD_CONST (False) - 19 STORE_NAME (1, x) - 20 LOAD_CONST (None) - 21 RETURN_VALUE + 10 CACHE + >> 11 CACHE + 12 POP_JUMP_IF_FALSE (11) + 13 CACHE + 14 NOT_TAKEN + 15 POP_TOP + 16 LOAD_CONST (False) + 17 COPY (1) + 18 TO_BOOL + 19 CACHE + 20 CACHE + 21 CACHE + 22 POP_JUMP_IF_TRUE (3) + 23 CACHE + 24 NOT_TAKEN + 25 POP_TOP + 26 LOAD_CONST (False) + 27 STORE_NAME (1, x) + 28 LOAD_CONST (None) + 29 RETURN_VALUE diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index 7844efa4a88..be7d6e77390 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -297,7 +297,8 @@ fn drop_class_free(symbol_table: &mut SymbolTable, newfree: &mut IndexSet SymbolTableResult { + self.scan_annotation_inner(annotation, false) + } + + /// Scan an annotation from an AnnAssign statement (can be conditional) + fn scan_ann_assign_annotation(&mut self, annotation: &ast::Expr) -> SymbolTableResult { + self.scan_annotation_inner(annotation, true) + } + + fn scan_annotation_inner( + &mut self, + annotation: &ast::Expr, + is_ann_assign: bool, + ) -> SymbolTableResult { let current_scope = self.tables.last().map(|t| t.typ); - // PEP 649: Check if this is a conditional annotation - // Module-level: always conditional (module may be partially executed) - // Class-level: conditional only when inside if/for/while/etc. - if !self.future_annotations { + // PEP 649: Only AnnAssign annotations can be conditional. + // Function parameter/return annotations are never conditional. + if is_ann_assign && !self.future_annotations { let is_conditional = matches!(current_scope, Some(CompilerScope::Module)) || (matches!(current_scope, Some(CompilerScope::Class)) && self.in_conditional_block); if is_conditional && !self.tables.last().unwrap().has_conditional_annotations { self.tables.last_mut().unwrap().has_conditional_annotations = true; - // Register __conditional_annotations__ as both Assigned and Used so that - // it becomes a Cell variable in class scope (children reference it as Free) self.register_name( "__conditional_annotations__", SymbolUsage::Assigned, @@ -1571,7 +1601,7 @@ impl SymbolTableBuilder { // sub_tables that cause mismatch in the annotation scope's sub_table index. let is_simple_name = *simple && matches!(&**target, Expr::Name(_)); if is_simple_name { - self.scan_annotation(annotation)?; + self.scan_ann_assign_annotation(annotation)?; } else { // Still validate annotation for forbidden expressions // (yield, await, named) even for non-simple targets.