From 40cab9c745b0c7e35dce409a2a022fed4a6a5716 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 12:44:55 +0900 Subject: [PATCH] Bytecode parity --- crates/codegen/src/compile.rs | 52 ++++++++++++++++--- ...pile__tests__nested_double_async_with.snap | 2 +- crates/vm/src/builtins/code.rs | 26 +++++++--- crates/vm/src/frame.rs | 21 +------- crates/vm/src/protocol/object.rs | 4 +- 5 files changed, 71 insertions(+), 34 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index ca63c091cf3..92b959debde 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4581,11 +4581,21 @@ impl Compiler { _ => continue, }; // Skip @staticmethod and @classmethod decorated functions - let dominated_by_special = f.decorator_list.iter().any(|d| { + let has_special_decorator = f.decorator_list.iter().any(|d| { matches!(&d.expression, ast::Expr::Name(n) if n.id.as_str() == "staticmethod" || n.id.as_str() == "classmethod") }); - if dominated_by_special { + if has_special_decorator { + continue; + } + // Skip implicit classmethods (__init_subclass__, __class_getitem__) + let fname = f.name.as_str(); + if fname == "__init_subclass__" || fname == "__class_getitem__" { + continue; + } + // For __new__, scan for "self" (not the first param "cls") + if fname == "__new__" { + Self::scan_store_attrs(&f.body, "self", attrs); continue; } let first_param = f @@ -5458,8 +5468,7 @@ impl Compiler { // The thing iterated: // 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]`) + // Skip for async-for (GET_AITER expects the original type) if !is_async && let ast::Expr::List(ast::ExprList { elts, .. }) = iter && !elts.iter().any(|e| matches!(e, ast::Expr::Starred(_))) @@ -7369,6 +7378,14 @@ impl Compiler { Some(expression) => self.compile_expression(expression)?, Option::None => self.emit_load_const(ConstantData::None), }; + if self.ctx.func == FunctionContext::AsyncFunction { + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::AsyncGenWrap + } + ); + } // arg=0: direct yield (wrapped for async generators) emit!(self, Instruction::YieldValue { arg: 0 }); emit!( @@ -7591,6 +7608,14 @@ impl Compiler { compiler.compile_comprehension_element(elt)?; compiler.mark_generator(); + if compiler.ctx.func == FunctionContext::AsyncFunction { + emit!( + compiler, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::AsyncGenWrap + } + ); + } // arg=0: direct yield (wrapped for async generators) emit!(compiler, Instruction::YieldValue { arg: 0 }); emit!( @@ -7891,6 +7916,22 @@ impl Compiler { if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = &arguments.args[0] { self.compile_expression(value)?; } + } else if !has_starred { + for arg in &arguments.args { + self.compile_expression(arg)?; + } + self.set_source_range(call_range); + let positional_count = additional_positional + nelts.to_u32(); + if positional_count == 0 { + self.emit_load_const(ConstantData::Tuple { elements: vec![] }); + } else { + emit!( + self, + Instruction::BuildTuple { + count: positional_count + } + ); + } } else { // Use starunpack_helper to build a list, then convert to tuple self.starunpack_helper( @@ -8236,7 +8277,6 @@ impl Compiler { // Create comprehension function with closure self.make_closure(code, bytecode::MakeFunctionFlags::new())?; - emit!(self, Instruction::PushNull); // Evaluate iterated item: self.compile_expression(&generators[0].iter)?; @@ -8250,7 +8290,7 @@ impl Compiler { }; // Call just created function: - emit!(self, Instruction::Call { argc: 1 }); + emit!(self, Instruction::Call { argc: 0 }); if is_async_list_set_dict_comprehension { emit!(self, Instruction::GetAwaitable { r#where: 0 }); self.emit_load_const(ConstantData::None); diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index dae3014339b..bfdddf4a722 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -1,6 +1,6 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9591 +assertion_line: 9780 expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")" --- 1 0 RESUME (0) diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index 43a88de273e..9d1083b1063 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -1290,22 +1290,34 @@ impl PyCode { let idx = usize::try_from(opcode).map_err(|_| idx_err(vm))?; let varnames_len = self.code.varnames.len(); - let cellvars_len = self.code.cellvars.len(); + // Non-parameter cells: cellvars that are NOT also in varnames + let nonparam_cellvars: Vec<_> = self + .code + .cellvars + .iter() + .filter(|s| { + let s_str: &str = s.as_ref(); + !self.code.varnames.iter().any(|v| { + let v_str: &str = v.as_ref(); + v_str == s_str + }) + }) + .collect(); + let nonparam_len = nonparam_cellvars.len(); let name = if idx < varnames_len { - // Index in varnames + // Index in varnames (includes parameter cells) self.code.varnames.get(idx).ok_or_else(|| idx_err(vm))? - } else if idx < varnames_len + cellvars_len { - // Index in cellvars - self.code - .cellvars + } else if idx < varnames_len + nonparam_len { + // Index in non-parameter cellvars + *nonparam_cellvars .get(idx - varnames_len) .ok_or_else(|| idx_err(vm))? } else { // Index in freevars self.code .freevars - .get(idx - varnames_len - cellvars_len) + .get(idx - varnames_len - nonparam_len) .ok_or_else(|| idx_err(vm))? }; Ok(name.to_object()) diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 21ef5fc791f..132fd8e26ec 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -10,7 +10,6 @@ use crate::{ PyBaseException, PyBaseExceptionRef, PyBaseObject, PyCode, PyCoroutine, PyDict, PyDictRef, PyFloat, PyFrozenSet, PyGenerator, PyInt, PyInterpolation, PyList, PyModule, PyProperty, PySet, PySlice, PyStr, PyStrInterned, PyTemplate, PyTraceback, PyType, PyUtf8Str, - asyncgenerator::PyAsyncGenWrappedValue, builtin_func::PyNativeFunction, descriptor::{MemberGetter, PyMemberDescriptor, PyMethodDescriptor}, frame::stack_analysis, @@ -3548,7 +3547,7 @@ impl ExecutingFrame<'_> { Ok(None) } - Instruction::YieldValue { arg: oparg } => { + Instruction::YieldValue { .. } => { debug_assert!( self.localsplus .stack_as_slice() @@ -3557,16 +3556,7 @@ impl ExecutingFrame<'_> { .all(|sr| !sr.is_borrowed()), "borrowed refs on stack at yield point" ); - let value = self.pop_value(); - // arg=0: direct yield (wrapped for async generators) - // arg=1: yield from await/yield-from (NOT wrapped) - let wrap = oparg.get(arg) == 0; - let value = if wrap && self.code.flags.contains(bytecode::CodeFlags::COROUTINE) { - PyAsyncGenWrappedValue(value).into_pyobject(vm) - } else { - value - }; - Ok(Some(ExecutionResult::Yield(value))) + Ok(Some(ExecutionResult::Yield(self.pop_value()))) } Instruction::Send { .. } => { // (receiver, v -- receiver, retval) @@ -5800,13 +5790,6 @@ impl ExecutingFrame<'_> { let offset = (self.lasti() - 1) * 2; monitoring::fire_py_yield(vm, self.code, offset, &value)?; } - let oparg = u32::from(arg); - let wrap = oparg == 0; - let value = if wrap && self.code.flags.contains(bytecode::CodeFlags::COROUTINE) { - PyAsyncGenWrappedValue(value).into_pyobject(vm) - } else { - value - }; Ok(Some(ExecutionResult::Yield(value))) } Instruction::InstrumentedCall => { diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index 871c1514c31..e59a1f15a6f 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -298,7 +298,9 @@ impl PyObject { ) -> PyResult> { let swapped = op.swapped(); let call_cmp = |obj: &Self, other: &Self, op| { - let cmp = obj.class().slots.richcompare.load().unwrap(); + let Some(cmp) = obj.class().slots.richcompare.load() else { + return Ok(PyArithmeticValue::NotImplemented); + }; let r = match cmp(obj, other, op, vm)? { Either::A(obj) => PyArithmeticValue::from_object(vm, obj).map(Either::A), Either::B(arithmetic) => arithmetic.map(Either::B),