diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 27d1394e516..08830c25ae7 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -529,7 +529,6 @@ def f(x):
self.assertEqual(len(returns), 1)
self.check_lnotab(f)
- @unittest.expectedFailure # TODO: RUSTPYTHON; KeyError: 20
def test_elim_jump_to_return(self):
# JUMP_FORWARD to RETURN --> RETURN
def f(cond, true_value, false_value):
diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs
index e3f1d187370..e0debb48cd2 100644
--- a/crates/codegen/src/compile.rs
+++ b/crates/codegen/src/compile.rs
@@ -2481,15 +2481,10 @@ impl Compiler {
idx: bytecode::CommonConstant::AssertionError
}
);
- emit!(self, Instruction::PushNull);
- match msg {
- Some(e) => {
- self.compile_expression(e)?;
- emit!(self, Instruction::Call { argc: 1 });
- }
- None => {
- emit!(self, Instruction::Call { argc: 0 });
- }
+ if let Some(e) = msg {
+ emit!(self, Instruction::PushNull);
+ self.compile_expression(e)?;
+ emit!(self, Instruction::Call { argc: 1 });
}
emit!(
self,
@@ -3221,20 +3216,19 @@ impl Compiler {
emit!(self, PseudoInstruction::PopBlock);
}
- // Create a block for normal path continuation (after handler body succeeds)
- let handler_normal_exit = self.new_block();
- emit!(
- self,
- PseudoInstruction::JumpNoInterrupt {
- delta: handler_normal_exit,
- }
- );
-
// cleanup_end block for named handler
// IMPORTANT: In CPython, cleanup_end is within outer SETUP_CLEANUP scope.
// so when RERAISE is executed, it goes to the cleanup block which does POP_EXCEPT.
// We MUST compile cleanup_end BEFORE popping ExceptionHandler so RERAISE routes to cleanup_block.
if let Some(cleanup_end) = handler_cleanup_block {
+ let handler_normal_exit = self.new_block();
+ emit!(
+ self,
+ PseudoInstruction::JumpNoInterrupt {
+ delta: handler_normal_exit,
+ }
+ );
+
self.switch_to_block(cleanup_end);
if let Some(alias) = name {
// name = None; del name; before RERAISE
@@ -3247,10 +3241,10 @@ impl Compiler {
// This RERAISE is within ExceptionHandler scope, so it routes to cleanup_block
// which does COPY 3; POP_EXCEPT; RERAISE
emit!(self, Instruction::Reraise { depth: 1 });
- }
- // Switch to normal exit block - this is where handler body success continues
- self.switch_to_block(handler_normal_exit);
+ // Switch to normal exit block - this is where handler body success continues
+ self.switch_to_block(handler_normal_exit);
+ }
// PopBlock for outer SETUP_CLEANUP (ExceptionHandler)
emit!(self, PseudoInstruction::PopBlock);
@@ -6789,6 +6783,89 @@ impl Compiler {
Ok(())
}
+ fn compile_jump_if_compare(
+ &mut self,
+ left: &ast::Expr,
+ ops: &[ast::CmpOp],
+ comparators: &[ast::Expr],
+ condition: bool,
+ target_block: BlockIdx,
+ ) -> CompileResult<()> {
+ let compare_range = self.current_source_range;
+ let (last_op, mid_ops) = ops.split_last().unwrap();
+ let (last_comparator, mid_comparators) = comparators.split_last().unwrap();
+
+ self.compile_expression(left)?;
+
+ if mid_comparators.is_empty() {
+ self.compile_expression(last_comparator)?;
+ self.set_source_range(compare_range);
+ self.compile_addcompare(last_op);
+ if condition {
+ emit!(
+ self,
+ Instruction::PopJumpIfTrue {
+ delta: target_block
+ }
+ );
+ } else {
+ emit!(
+ self,
+ Instruction::PopJumpIfFalse {
+ delta: target_block,
+ }
+ );
+ }
+ return Ok(());
+ }
+
+ let cleanup = self.new_block();
+ let end = self.new_block();
+
+ for (op, comparator) in mid_ops.iter().zip(mid_comparators) {
+ self.compile_expression(comparator)?;
+ self.set_source_range(compare_range);
+ emit!(self, Instruction::Swap { i: 2 });
+ emit!(self, Instruction::Copy { i: 2 });
+ self.compile_addcompare(op);
+ emit!(self, Instruction::PopJumpIfFalse { delta: cleanup });
+ }
+
+ self.compile_expression(last_comparator)?;
+ self.set_source_range(compare_range);
+ self.compile_addcompare(last_op);
+ if condition {
+ emit!(
+ self,
+ Instruction::PopJumpIfTrue {
+ delta: target_block
+ }
+ );
+ } else {
+ emit!(
+ self,
+ Instruction::PopJumpIfFalse {
+ delta: target_block,
+ }
+ );
+ }
+ emit!(self, PseudoInstruction::Jump { delta: end });
+
+ self.switch_to_block(cleanup);
+ emit!(self, Instruction::PopTop);
+ if !condition {
+ emit!(
+ self,
+ PseudoInstruction::Jump {
+ delta: target_block
+ }
+ );
+ }
+
+ self.switch_to_block(end);
+ Ok(())
+ }
+
fn compile_annotation(&mut self, annotation: &ast::Expr) -> CompileResult<()> {
if self.future_annotations {
self.emit_load_const(ConstantData::Str {
@@ -7066,8 +7143,11 @@ impl Compiler {
condition: bool,
target_block: BlockIdx,
) -> CompileResult<()> {
+ let prev_source_range = self.current_source_range;
+ self.set_source_range(expression.range());
+
// Compile expression for test, and jump to label if false
- match &expression {
+ let result = match &expression {
ast::Expr::BoolOp(ast::ExprBoolOp { op, values, .. }) => {
match op {
ast::BoolOp::And => {
@@ -7113,13 +7193,20 @@ impl Compiler {
}
}
}
+ Ok(())
}
ast::Expr::UnaryOp(ast::ExprUnaryOp {
op: ast::UnaryOp::Not,
operand,
..
- }) => {
- self.compile_jump_if(operand, !condition, target_block)?;
+ }) => self.compile_jump_if(operand, !condition, target_block),
+ ast::Expr::Compare(ast::ExprCompare {
+ left,
+ ops,
+ comparators,
+ ..
+ }) if ops.len() > 1 => {
+ self.compile_jump_if_compare(left, ops, comparators, condition, target_block)
}
// `x is None` / `x is not None` → POP_JUMP_IF_NONE / POP_JUMP_IF_NOT_NONE
ast::Expr::Compare(ast::ExprCompare {
@@ -7154,6 +7241,7 @@ impl Compiler {
}
);
}
+ Ok(())
}
_ => {
// Fall back case which always will work!
@@ -7177,9 +7265,12 @@ impl Compiler {
}
);
}
+ Ok(())
}
- }
- Ok(())
+ };
+
+ self.set_source_range(prev_source_range);
+ result
}
/// Compile a boolean operation as an expression.
@@ -7575,8 +7666,11 @@ impl Compiler {
lower, upper, step, ..
}) => {
// Try constant slice folding first
- if self.try_fold_constant_slice(lower.as_deref(), upper.as_deref(), step.as_deref())
- {
+ if self.try_fold_constant_slice(
+ lower.as_deref(),
+ upper.as_deref(),
+ step.as_deref(),
+ )? {
return Ok(());
}
let mut compile_bound = |bound: Option<&ast::Expr>| match bound {
@@ -7991,12 +8085,15 @@ impl Compiler {
// Save the call expression's source range so CALL instructions use the
// call start line, not the last argument's line.
let call_range = self.current_source_range;
+ let uses_ex_call = self.call_uses_ex_call(args);
// Method call: obj → LOAD_ATTR_METHOD → [method, self_or_null] → args → CALL
// Regular call: func → PUSH_NULL → args → CALL
if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = &func {
// Check for super() method call optimization
- if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
+ if !uses_ex_call
+ && let Some(super_type) = self.can_optimize_super_call(value, attr.as_str())
+ {
// super().method() or super(cls, self).method() optimization
// Stack: [global_super, class, self] → LOAD_SUPER_METHOD → [method, self]
// Set source range to the super() call for LOAD_GLOBAL/LOAD_DEREF/etc.
@@ -8021,12 +8118,12 @@ impl Compiler {
} else {
self.compile_expression(value)?;
let idx = self.name(attr.as_str());
- // Imported names use plain LOAD_ATTR + PUSH_NULL;
- // other names use method call mode LOAD_ATTR.
+ // Imported names and CALL_FUNCTION_EX-style calls use plain
+ // LOAD_ATTR + PUSH_NULL; other names use method-call mode.
// Check current scope and enclosing scopes for IMPORTED flag.
let is_import = matches!(value.as_ref(), ast::Expr::Name(ast::ExprName { id, .. })
if self.is_name_imported(id.as_str()));
- if is_import {
+ if is_import || uses_ex_call {
self.emit_load_attr(idx);
emit!(self, Instruction::PushNull);
} else {
@@ -8044,6 +8141,16 @@ impl Compiler {
Ok(())
}
+ fn call_uses_ex_call(&self, arguments: &ast::Arguments) -> bool {
+ let has_starred = arguments
+ .args
+ .iter()
+ .any(|arg| matches!(arg, ast::Expr::Starred(_)));
+ let has_double_star = arguments.keywords.iter().any(|k| k.arg.is_none());
+ let too_big = arguments.args.len() + arguments.keywords.len() > 15;
+ has_starred || has_double_star || too_big
+ }
+
/// Compile subkwargs: emit key-value pairs for BUILD_MAP
fn codegen_subkwargs(
&mut self,
@@ -8929,6 +9036,28 @@ impl Compiler {
}
}
+ fn compile_fstring_literal_value(
+ &self,
+ string: &ast::InterpolatedStringLiteralElement,
+ flags: ast::FStringFlags,
+ ) -> Wtf8Buf {
+ if string.value.contains(char::REPLACEMENT_CHARACTER) {
+ let source = self.source_file.slice(string.range);
+ crate::string_parser::parse_fstring_literal_element(source.into(), flags.into()).into()
+ } else {
+ string.value.to_string().into()
+ }
+ }
+
+ fn compile_fstring_part_literal_value(&self, string: &ast::StringLiteral) -> Wtf8Buf {
+ if string.value.contains(char::REPLACEMENT_CHARACTER) {
+ let source = self.source_file.slice(string.range);
+ crate::string_parser::parse_string_literal(source, string.flags.into()).into()
+ } else {
+ string.value.to_string().into()
+ }
+ }
+
fn arg_constant(&mut self, constant: ConstantData) -> oparg::ConstIdx {
let info = self.current_code_info();
info.metadata.consts.insert_full(constant).0.to_u32().into()
@@ -8942,48 +9071,40 @@ impl Compiler {
) -> CompileResult