Skip to content

Commit 5e65683

Browse files
dependabot[bot]youknowone
authored andcommitted
Bytecode parity phase 3
Compiler: - Emit TO_BOOL in and/or short-circuit evaluation - Add module-level __conditional_annotations__ cell (PEP 649) - Only set conditional annotations for AnnAssign, not function params - Skip __classdict__ cell when future annotations are active - Convert list literals to tuples in for-loop iterables - Fix cell ordering: parameters first, then alphabetical - Fix RESUME DEPTH1: only count non-preserve_lasti except handlers - Don't propagate __classdict__/__conditional_annotations__ freevar through regular functions - Fix __static_attributes__: skip implicit classmethods, scan "self" in __new__ bodies - Stub add_generator_stop_handler (disabled: needs async comp fix) VM: - Fix _varname_from_oparg: skip parameter cells in localsplus mapping - Fix richcompare panic: return NotImplemented when slot is None
1 parent e3ac1bf commit 5e65683

5 files changed

Lines changed: 73 additions & 16 deletions

File tree

‎crates/codegen/src/compile.rs‎

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4581,11 +4581,16 @@ impl Compiler {
45814581
_ => continue,
45824582
};
45834583
// Skip @staticmethod and @classmethod decorated functions
4584-
let dominated_by_special = f.decorator_list.iter().any(|d| {
4584+
let has_special_decorator = f.decorator_list.iter().any(|d| {
45854585
matches!(&d.expression, ast::Expr::Name(n)
45864586
if n.id.as_str() == "staticmethod" || n.id.as_str() == "classmethod")
45874587
});
4588-
if dominated_by_special {
4588+
if has_special_decorator {
4589+
continue;
4590+
}
4591+
// Skip implicit classmethods (__init_subclass__, __class_getitem__)
4592+
let fname = f.name.as_str();
4593+
if fname == "__init_subclass__" || fname == "__class_getitem__" {
45894594
continue;
45904595
}
45914596
let first_param = f
@@ -4597,7 +4602,13 @@ impl Compiler {
45974602
let Some(self_name) = first_param else {
45984603
continue;
45994604
};
4600-
Self::scan_store_attrs(&f.body, self_name.as_str(), attrs);
4605+
// For __new__, first param is cls → scan for "self" instead
4606+
// (CPython scans for the literal name "self" in __new__ bodies)
4607+
if fname == "__new__" {
4608+
Self::scan_store_attrs(&f.body, "self", attrs);
4609+
} else {
4610+
Self::scan_store_attrs(&f.body, self_name.as_str(), attrs);
4611+
}
46014612
}
46024613
}
46034614

@@ -5460,8 +5471,7 @@ impl Compiler {
54605471
// Optimize: `for x in [a, b, c]` → use tuple instead of list
54615472
// (list creation is wasteful for iteration)
54625473
// Skip optimization if any element is starred (e.g., `[a, *b, c]`)
5463-
if !is_async
5464-
&& let ast::Expr::List(ast::ExprList { elts, .. }) = iter
5474+
if let ast::Expr::List(ast::ExprList { elts, .. }) = iter
54655475
&& !elts.iter().any(|e| matches!(e, ast::Expr::Starred(_)))
54665476
{
54675477
for elt in elts {
@@ -7369,6 +7379,14 @@ impl Compiler {
73697379
Some(expression) => self.compile_expression(expression)?,
73707380
Option::None => self.emit_load_const(ConstantData::None),
73717381
};
7382+
if self.ctx.func == FunctionContext::AsyncFunction {
7383+
emit!(
7384+
self,
7385+
Instruction::CallIntrinsic1 {
7386+
func: bytecode::IntrinsicFunction1::AsyncGenWrap
7387+
}
7388+
);
7389+
}
73727390
// arg=0: direct yield (wrapped for async generators)
73737391
emit!(self, Instruction::YieldValue { arg: 0 });
73747392
emit!(
@@ -7889,6 +7907,22 @@ impl Compiler {
78897907
if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = &arguments.args[0] {
78907908
self.compile_expression(value)?;
78917909
}
7910+
} else if !has_starred {
7911+
for arg in &arguments.args {
7912+
self.compile_expression(arg)?;
7913+
}
7914+
self.set_source_range(call_range);
7915+
let positional_count = additional_positional + nelts.to_u32();
7916+
if positional_count == 0 {
7917+
self.emit_load_const(ConstantData::Tuple { elements: vec![] });
7918+
} else {
7919+
emit!(
7920+
self,
7921+
Instruction::BuildTuple {
7922+
count: positional_count
7923+
}
7924+
);
7925+
}
78927926
} else {
78937927
// Use starunpack_helper to build a list, then convert to tuple
78947928
self.starunpack_helper(
@@ -8234,7 +8268,6 @@ impl Compiler {
82348268

82358269
// Create comprehension function with closure
82368270
self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
8237-
emit!(self, Instruction::PushNull);
82388271

82398272
// Evaluate iterated item:
82408273
self.compile_expression(&generators[0].iter)?;
@@ -8248,7 +8281,7 @@ impl Compiler {
82488281
};
82498282

82508283
// Call just created <listcomp> function:
8251-
emit!(self, Instruction::Call { argc: 1 });
8284+
emit!(self, Instruction::Call { argc: 0 });
82528285
if is_async_list_set_dict_comprehension {
82538286
emit!(self, Instruction::GetAwaitable { r#where: 0 });
82548287
self.emit_load_const(ConstantData::None);

‎crates/codegen/src/ir.rs‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ impl CodeInfo {
209209
// Always apply LOAD_FAST_BORROW optimization
210210
self.optimize_load_fast_borrow();
211211

212+
// Add StopIteration handler for generators/coroutines (before label_exception_targets)
213+
add_generator_stop_handler(self.flags, &mut self.blocks);
214+
212215
// Post-codegen CFG analysis passes (flowgraph.c pipeline)
213216
mark_except_handlers(&mut self.blocks);
214217
label_exception_targets(&mut self.blocks);
@@ -2023,6 +2026,17 @@ fn normalize_jumps(blocks: &mut [Block]) {
20232026
/// to the final return block. Matches CPython's behavior of ensuring every
20242027
/// code path that reaches the end of a function/module has its own explicit
20252028
/// return instruction.
2029+
/// Add a StopIteration → RuntimeError handler for generators/coroutines.
2030+
/// CPython: flowgraph.c add_stopiteration_handler()
2031+
/// Appends a handler block that wraps the entire generator body.
2032+
fn add_generator_stop_handler(_flags: CodeFlags, _blocks: &mut Vec<Block>) {
2033+
// TODO: Implement generator StopIteration handler.
2034+
// CPython adds PUSH_EXC_INFO + CALL_INTRINSIC_1(STOPITERATION_ERROR) + RERAISE 1
2035+
// as a handler wrapping the entire generator body.
2036+
// Currently disabled: the handler breaks async comprehension execution paths
2037+
// because it interferes with the exception table ordering.
2038+
}
2039+
20262040
fn duplicate_end_returns(blocks: &mut [Block]) {
20272041
// Walk the block chain to find the last block
20282042
let mut last_block = BlockIdx(0);

‎crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/vm/src/builtins/code.rs‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,22 +1290,30 @@ impl PyCode {
12901290
let idx = usize::try_from(opcode).map_err(|_| idx_err(vm))?;
12911291

12921292
let varnames_len = self.code.varnames.len();
1293-
let cellvars_len = self.code.cellvars.len();
1293+
// Non-parameter cells: cellvars that are NOT also in varnames
1294+
let varnames_set: std::collections::HashSet<String> =
1295+
self.code.varnames.iter().map(|s| s.to_string()).collect();
1296+
let nonparam_cellvars: Vec<_> = self
1297+
.code
1298+
.cellvars
1299+
.iter()
1300+
.filter(|s| !varnames_set.contains(&s.to_string()))
1301+
.collect();
1302+
let nonparam_len = nonparam_cellvars.len();
12941303

12951304
let name = if idx < varnames_len {
1296-
// Index in varnames
1305+
// Index in varnames (includes parameter cells)
12971306
self.code.varnames.get(idx).ok_or_else(|| idx_err(vm))?
1298-
} else if idx < varnames_len + cellvars_len {
1299-
// Index in cellvars
1300-
self.code
1301-
.cellvars
1307+
} else if idx < varnames_len + nonparam_len {
1308+
// Index in non-parameter cellvars
1309+
*nonparam_cellvars
13021310
.get(idx - varnames_len)
13031311
.ok_or_else(|| idx_err(vm))?
13041312
} else {
13051313
// Index in freevars
13061314
self.code
13071315
.freevars
1308-
.get(idx - varnames_len - cellvars_len)
1316+
.get(idx - varnames_len - nonparam_len)
13091317
.ok_or_else(|| idx_err(vm))?
13101318
};
13111319
Ok(name.to_object())

‎crates/vm/src/protocol/object.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,9 @@ impl PyObject {
298298
) -> PyResult<Either<PyObjectRef, bool>> {
299299
let swapped = op.swapped();
300300
let call_cmp = |obj: &Self, other: &Self, op| {
301-
let cmp = obj.class().slots.richcompare.load().unwrap();
301+
let Some(cmp) = obj.class().slots.richcompare.load() else {
302+
return Ok(PyArithmeticValue::NotImplemented);
303+
};
302304
let r = match cmp(obj, other, op, vm)? {
303305
Either::A(obj) => PyArithmeticValue::from_object(vm, obj).map(Either::A),
304306
Either::B(arithmetic) => arithmetic.map(Either::B),

0 commit comments

Comments
 (0)