mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
Bytecode parity (#7504)
* Match CPython LOAD_SPECIAL stack semantics for with/async-with LOAD_SPECIAL now pushes (callable, self_or_null) matching CPython's CALL convention, instead of a single bound method: - Function descriptors: push (func, self) - Plain attributes: push (bound, NULL) Updated all with-statement paths: - Entry: add SWAP 3 after SWAP 2, remove PUSH_NULL before CALL 0 - Normal exit: remove PUSH_NULL before CALL 3 - Exception handler (WITH_EXCEPT_START): read exit_func at TOS-4 and self_or_null at TOS-3 - Suppress block: 3 POP_TOPs after POP_EXCEPT (was 2) - FBlock exit (preserve_tos): SWAP 3 + SWAP 2 rotation - UnwindAction::With: remove PUSH_NULL Stack effects updated: LoadSpecial (2,1), WithExceptStart (7,6) * Normalize LOAD_FAST_CHECK and JUMP_BACKWARD_NO_INTERRUPT Add LOAD_FAST_CHECK → LOAD_FAST and JUMP_BACKWARD_NO_INTERRUPT → JUMP_BACKWARD to opname normalization in dis_dump.py. These are optimization variants with identical semantics. * Add EXTENDED_ARG to SKIP_OPS, normalize LOAD_FAST_CHECK and JUMP_BACKWARD_NO_INTERRUPT * Remove duplicate return-None when block already has return Skip duplicate_end_returns for blocks that already end with LOAD_CONST + RETURN_VALUE. Run DCE + unreachable elimination after duplication to remove the now-unreachable original return block. * Improve __static_attributes__ collection accuracy - Support tuple/list unpacking targets: (self.x, self.y) = val - Skip @staticmethod and @classmethod decorated methods - Use scan_target_for_attrs helper for recursive target scanning * Use method mode for function-local import attribute calls Function-local imports (scope is Local+IMPORTED) should use method mode LOAD_ATTR like regular names, not plain mode. Only module/class scope imports use plain LOAD_ATTR + PUSH_NULL. * Optimize constant iterable before GET_ITER to LOAD_CONST tuple Convert BUILD_LIST/SET 0 + LOAD_CONST + LIST_EXTEND/SET_UPDATE + GET_ITER to just LOAD_CONST (tuple) + GET_ITER, matching CPython's optimization for constant list/set literals in for-loop iterables. Also fix is_name_imported to use method mode for function-local imports, and improve __static_attributes__ accuracy (skip @classmethod/@staticmethod, handle tuple/list unpacking targets). * Fix cell variable ordering: parameters first, then alphabetical CPython orders cell variables with parameter cells first (in parameter definition order), then non-parameter cells sorted alphabetically. Previously all cells were sorted alphabetically. Also add for-loop iterable optimization: constant BUILD_LIST/SET before GET_ITER is folded to just LOAD_CONST tuple. * Emit COPY_FREE_VARS before MAKE_CELL matching CPython order CPython emits COPY_FREE_VARS first, then MAKE_CELL instructions. Previously RustPython emitted them in reverse order. * Fix RESUME AfterYield encoding to match CPython 3.14 (value 5) CPython 3.14 uses RESUME arg=5 for after-yield, not 1. Also reorder COPY_FREE_VARS before MAKE_CELL and fix cell variable ordering (parameters first, then alphabetical). * Address code review feedback from #7481 - Set is_generator flag for generator expressions in scan_comprehension - Fix posonlyargs priority in collect_static_attributes first param - Add match statement support to scan_store_attrs - Fix stale decorator stack comment - Reorder NOP removal after fold_unary_negative for better collection folding * Fold constant list/set/tuple literals in compiler When all elements of a list/set/tuple literal are constants and there are 3+ elements, fold them into a single constant: - list: BUILD_LIST 0 + LOAD_CONST (tuple) + LIST_EXTEND 1 - set: BUILD_SET 0 + LOAD_CONST (tuple) + SET_UPDATE 1 - tuple: LOAD_CONST (tuple) This matches CPython's compiler optimization and fixes the most common bytecode difference (92/200 sampled files). Also add bytecode comparison scripts (dis_dump.py, compare_bytecode.py) for systematic parity tracking. * Use BUILD_MAP 0 + MAP_ADD for large dicts (>= 16 pairs) Match CPython's compiler behavior: dicts with 16+ key-value pairs use BUILD_MAP 0 followed by MAP_ADD for each pair, instead of pushing all keys/values on the stack and calling BUILD_MAP N. * Fix clippy warnings and cargo fmt * fix surrogate
This commit is contained in:
1 parent
44cd8d53a1
commit
1bf829a456
10 files changed
+371
-200
No files matched your search
+211
-106
@@ -564,6 +564,31 @@ impl Compiler {
|
||||
_ => n > 4,
|
||||
};
|
||||
|
||||
// Fold all-constant collections (>= 3 elements) regardless of size
|
||||
if !seen_star
|
||||
&& pushed == 0
|
||||
&& n >= 3
|
||||
&& elts.iter().all(|e| e.is_constant())
|
||||
&& let Some(folded) = self.try_fold_constant_collection(elts)?
|
||||
{
|
||||
match collection_type {
|
||||
CollectionType::Tuple => {
|
||||
self.emit_load_const(folded);
|
||||
}
|
||||
CollectionType::List => {
|
||||
emit!(self, Instruction::BuildList { count: 0 });
|
||||
self.emit_load_const(folded);
|
||||
emit!(self, Instruction::ListExtend { i: 1 });
|
||||
}
|
||||
CollectionType::Set => {
|
||||
emit!(self, Instruction::BuildSet { count: 0 });
|
||||
self.emit_load_const(folded);
|
||||
emit!(self, Instruction::SetUpdate { i: 1 });
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// If no stars and not too big, compile all elements and build once
|
||||
if !seen_star && !big {
|
||||
for elt in elts {
|
||||
@@ -694,13 +719,21 @@ impl Compiler {
|
||||
|
||||
/// Check if a name is imported in current scope or any enclosing scope.
|
||||
fn is_name_imported(&self, name: &str) -> bool {
|
||||
if let Some(sym) = self.current_symbol_table().symbols.get(name) {
|
||||
let current = self.current_symbol_table();
|
||||
if let Some(sym) = current.symbols.get(name) {
|
||||
if sym.flags.contains(SymbolFlags::IMPORTED) {
|
||||
return true;
|
||||
} else if sym.scope == SymbolScope::Local {
|
||||
// Module/class scope imports use plain LOAD_ATTR
|
||||
// Function-local imports use method mode (scope is Local)
|
||||
return !matches!(
|
||||
current.typ,
|
||||
CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda
|
||||
);
|
||||
}
|
||||
if sym.scope == SymbolScope::Local {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check enclosing scopes for module-level imports accessed as globals
|
||||
self.symbol_table_stack.iter().rev().skip(1).any(|table| {
|
||||
table
|
||||
.symbols
|
||||
@@ -1033,7 +1066,9 @@ impl Compiler {
|
||||
|
||||
// Build cellvars using dictbytype (CELL scope or COMP_CELL flag, sorted)
|
||||
let mut cellvar_cache = IndexSet::default();
|
||||
let mut cell_names: Vec<_> = ste
|
||||
// CPython ordering: parameter cells first (in parameter order),
|
||||
// then non-parameter cells (alphabetically sorted)
|
||||
let cell_symbols: Vec<_> = ste
|
||||
.symbols
|
||||
.iter()
|
||||
.filter(|(_, s)| {
|
||||
@@ -1041,8 +1076,22 @@ impl Compiler {
|
||||
})
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect();
|
||||
cell_names.sort();
|
||||
for name in cell_names {
|
||||
let mut param_cells = Vec::new();
|
||||
let mut nonparam_cells = Vec::new();
|
||||
for name in cell_symbols {
|
||||
if varname_cache.contains(&name) {
|
||||
param_cells.push(name);
|
||||
} else {
|
||||
nonparam_cells.push(name);
|
||||
}
|
||||
}
|
||||
// param_cells are already in parameter order (from varname_cache insertion order)
|
||||
param_cells.sort_by_key(|n| varname_cache.get_index_of(n.as_str()).unwrap_or(usize::MAX));
|
||||
nonparam_cells.sort();
|
||||
for name in param_cells {
|
||||
cellvar_cache.insert(name);
|
||||
}
|
||||
for name in nonparam_cells {
|
||||
cellvar_cache.insert(name);
|
||||
}
|
||||
|
||||
@@ -1168,16 +1217,7 @@ impl Compiler {
|
||||
self.set_qualname();
|
||||
}
|
||||
|
||||
// Emit MAKE_CELL for each cell variable (before RESUME)
|
||||
{
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
// Emit COPY_FREE_VARS if there are free variables (before RESUME)
|
||||
// Emit COPY_FREE_VARS first, then MAKE_CELL (CPython order)
|
||||
{
|
||||
let nfrees = self.code_stack.last().unwrap().metadata.freevars.len();
|
||||
if nfrees > 0 {
|
||||
@@ -1189,6 +1229,13 @@ impl Compiler {
|
||||
);
|
||||
}
|
||||
}
|
||||
{
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
// Emit RESUME (handles async preamble and module lineno 0)
|
||||
// CPython: LOCATION(lineno, lineno, 0, 0), then loc.lineno = 0 for module
|
||||
@@ -1476,25 +1523,20 @@ impl Compiler {
|
||||
}
|
||||
|
||||
FBlockType::With | FBlockType::AsyncWith => {
|
||||
// Stack when entering: [..., __exit__, return_value (if preserve_tos)]
|
||||
// Need to call __exit__(None, None, None)
|
||||
|
||||
// Stack: [..., exit_func, self_exit, return_value (if preserve_tos)]
|
||||
emit!(self, PseudoInstruction::PopBlock);
|
||||
|
||||
// If preserving return value, swap it below __exit__
|
||||
if preserve_tos {
|
||||
emit!(self, Instruction::Swap { i: 2 });
|
||||
// Rotate return value below the exit pair
|
||||
// [exit_func, self_exit, value] → [value, exit_func, self_exit]
|
||||
emit!(self, Instruction::Swap { i: 3 }); // [value, self_exit, exit_func]
|
||||
emit!(self, Instruction::Swap { i: 2 }); // [value, exit_func, self_exit]
|
||||
}
|
||||
// Stack after swap: [..., return_value, __exit__] or [..., __exit__]
|
||||
|
||||
// Call __exit__(None, None, None)
|
||||
// Call protocol: [callable, self_or_null, arg1, arg2, arg3]
|
||||
emit!(self, Instruction::PushNull);
|
||||
// Stack: [..., __exit__, NULL]
|
||||
// Call exit_func(self_exit, None, None, None)
|
||||
self.emit_load_const(ConstantData::None);
|
||||
self.emit_load_const(ConstantData::None);
|
||||
self.emit_load_const(ConstantData::None);
|
||||
// Stack: [..., __exit__, NULL, None, None, None]
|
||||
emit!(self, Instruction::Call { argc: 3 });
|
||||
|
||||
// For async with, await the result
|
||||
@@ -2756,7 +2798,7 @@ impl Compiler {
|
||||
}
|
||||
|
||||
/// Push decorators onto the stack in source order.
|
||||
/// For @dec1 @dec2 def foo(): stack becomes [dec1, NULL, dec2, NULL]
|
||||
/// For @dec1 @dec2 def foo(): stack becomes [dec1, dec2]
|
||||
fn prepare_decorators(&mut self, decorator_list: &[ast::Decorator]) -> CompileResult<()> {
|
||||
for decorator in decorator_list {
|
||||
self.compile_expression(&decorator.expression)?;
|
||||
@@ -4514,22 +4556,55 @@ impl Compiler {
|
||||
fn collect_static_attributes(body: &[ast::Stmt], attrs: Option<&mut IndexSet<String>>) {
|
||||
let Some(attrs) = attrs else { return };
|
||||
for stmt in body {
|
||||
// Only scan def/async def at class body level
|
||||
let (params, func_body) = match stmt {
|
||||
ast::Stmt::FunctionDef(f) => (&f.parameters, &f.body),
|
||||
let f = match stmt {
|
||||
ast::Stmt::FunctionDef(f) => f,
|
||||
_ => continue,
|
||||
};
|
||||
// Get first parameter name (usually "self" or "cls")
|
||||
let first_param = params
|
||||
.args
|
||||
// Skip @staticmethod and @classmethod decorated functions
|
||||
let dominated_by_special = 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 {
|
||||
continue;
|
||||
}
|
||||
let first_param = f
|
||||
.parameters
|
||||
.posonlyargs
|
||||
.first()
|
||||
.or(params.posonlyargs.first())
|
||||
.or(f.parameters.args.first())
|
||||
.map(|p| &p.parameter.name);
|
||||
let Some(self_name) = first_param else {
|
||||
continue;
|
||||
};
|
||||
// Scan function body for self.xxx = ... (STORE_ATTR on first param)
|
||||
Self::scan_store_attrs(func_body, self_name.as_str(), attrs);
|
||||
Self::scan_store_attrs(&f.body, self_name.as_str(), attrs);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract self.attr patterns from an assignment target expression.
|
||||
fn scan_target_for_attrs(target: &ast::Expr, name: &str, attrs: &mut IndexSet<String>) {
|
||||
match target {
|
||||
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
|
||||
if let ast::Expr::Name(n) = value.as_ref()
|
||||
&& n.id.as_str() == name
|
||||
{
|
||||
attrs.insert(attr.to_string());
|
||||
}
|
||||
}
|
||||
ast::Expr::Tuple(t) => {
|
||||
for elt in &t.elts {
|
||||
Self::scan_target_for_attrs(elt, name, attrs);
|
||||
}
|
||||
}
|
||||
ast::Expr::List(l) => {
|
||||
for elt in &l.elts {
|
||||
Self::scan_target_for_attrs(elt, name, attrs);
|
||||
}
|
||||
}
|
||||
ast::Expr::Starred(s) => {
|
||||
Self::scan_target_for_attrs(&s.value, name, attrs);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4539,22 +4614,11 @@ impl Compiler {
|
||||
match stmt {
|
||||
ast::Stmt::Assign(a) => {
|
||||
for target in &a.targets {
|
||||
if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = target
|
||||
&& let ast::Expr::Name(n) = value.as_ref()
|
||||
&& n.id.as_str() == name
|
||||
{
|
||||
attrs.insert(attr.to_string());
|
||||
}
|
||||
Self::scan_target_for_attrs(target, name, attrs);
|
||||
}
|
||||
}
|
||||
ast::Stmt::AnnAssign(a) => {
|
||||
if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) =
|
||||
a.target.as_ref()
|
||||
&& let ast::Expr::Name(n) = value.as_ref()
|
||||
&& n.id.as_str() == name
|
||||
{
|
||||
attrs.insert(attr.to_string());
|
||||
}
|
||||
Self::scan_target_for_attrs(&a.target, name, attrs);
|
||||
}
|
||||
ast::Stmt::AugAssign(a) => {
|
||||
if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) =
|
||||
@@ -4591,6 +4655,11 @@ impl Compiler {
|
||||
ast::Stmt::With(s) => {
|
||||
Self::scan_store_attrs(&s.body, name, attrs);
|
||||
}
|
||||
ast::Stmt::Match(s) => {
|
||||
for case in &s.cases {
|
||||
Self::scan_store_attrs(&case.body, name, attrs);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -5180,16 +5249,16 @@ impl Compiler {
|
||||
Instruction::LoadSpecial {
|
||||
method: SpecialMethod::AExit
|
||||
}
|
||||
); // [cm, bound_aexit]
|
||||
emit!(self, Instruction::Swap { i: 2 }); // [bound_aexit, cm]
|
||||
); // [cm, aexit_func, self_ae]
|
||||
emit!(self, Instruction::Swap { i: 2 }); // [cm, self_ae, aexit_func]
|
||||
emit!(self, Instruction::Swap { i: 3 }); // [aexit_func, self_ae, cm]
|
||||
emit!(
|
||||
self,
|
||||
Instruction::LoadSpecial {
|
||||
method: SpecialMethod::AEnter
|
||||
}
|
||||
); // [bound_aexit, bound_aenter]
|
||||
emit!(self, Instruction::PushNull);
|
||||
emit!(self, Instruction::Call { argc: 0 }); // [bound_aexit, awaitable]
|
||||
); // [aexit_func, self_ae, aenter_func, self_an]
|
||||
emit!(self, Instruction::Call { argc: 0 }); // [aexit_func, self_ae, awaitable]
|
||||
emit!(self, Instruction::GetAwaitable { r#where: 1 });
|
||||
self.emit_load_const(ConstantData::None);
|
||||
let _ = self.compile_yield_from_sequence(true)?;
|
||||
@@ -5200,16 +5269,16 @@ impl Compiler {
|
||||
Instruction::LoadSpecial {
|
||||
method: SpecialMethod::Exit
|
||||
}
|
||||
); // [cm, bound_exit]
|
||||
emit!(self, Instruction::Swap { i: 2 }); // [bound_exit, cm]
|
||||
); // [cm, exit_func, self_exit]
|
||||
emit!(self, Instruction::Swap { i: 2 }); // [cm, self_exit, exit_func]
|
||||
emit!(self, Instruction::Swap { i: 3 }); // [exit_func, self_exit, cm]
|
||||
emit!(
|
||||
self,
|
||||
Instruction::LoadSpecial {
|
||||
method: SpecialMethod::Enter
|
||||
}
|
||||
); // [bound_exit, bound_enter]
|
||||
emit!(self, Instruction::PushNull);
|
||||
emit!(self, Instruction::Call { argc: 0 }); // [bound_exit, enter_result]
|
||||
); // [exit_func, self_exit, enter_func, self_enter]
|
||||
emit!(self, Instruction::Call { argc: 0 }); // [exit_func, self_exit, result]
|
||||
}
|
||||
|
||||
// Stack: [..., __exit__, enter_result]
|
||||
@@ -5263,10 +5332,9 @@ impl Compiler {
|
||||
});
|
||||
|
||||
// ===== Normal exit path =====
|
||||
// Stack: [..., bound_exit]
|
||||
// Call bound_exit(None, None, None)
|
||||
// Stack: [..., exit_func, self_exit]
|
||||
// Call exit_func(self_exit, None, None, None)
|
||||
self.set_source_range(with_range);
|
||||
emit!(self, Instruction::PushNull);
|
||||
self.emit_load_const(ConstantData::None);
|
||||
self.emit_load_const(ConstantData::None);
|
||||
self.emit_load_const(ConstantData::None);
|
||||
@@ -5280,11 +5348,10 @@ impl Compiler {
|
||||
emit!(self, PseudoInstruction::Jump { delta: after_block });
|
||||
|
||||
// ===== Exception handler path =====
|
||||
// Stack at entry (after unwind): [..., __exit__, lasti, exc]
|
||||
// PUSH_EXC_INFO -> [..., __exit__, lasti, prev_exc, exc]
|
||||
// Stack at entry: [..., exit_func, self_exit, lasti, exc]
|
||||
// PUSH_EXC_INFO -> [..., exit_func, self_exit, lasti, prev_exc, exc]
|
||||
self.switch_to_block(exc_handler_block);
|
||||
|
||||
// Create blocks for exception handling
|
||||
let cleanup_block = self.new_block();
|
||||
let suppress_block = self.new_block();
|
||||
|
||||
@@ -5296,12 +5363,10 @@ impl Compiler {
|
||||
);
|
||||
self.push_fblock(FBlockType::ExceptionHandler, exc_handler_block, after_block)?;
|
||||
|
||||
// PUSH_EXC_INFO: [exc] -> [prev_exc, exc]
|
||||
emit!(self, Instruction::PushExcInfo);
|
||||
|
||||
// WITH_EXCEPT_START: call __exit__(type, value, tb)
|
||||
// Stack: [..., __exit__, lasti, prev_exc, exc]
|
||||
// __exit__ is at TOS-3, call with exception info
|
||||
// WITH_EXCEPT_START: call exit_func(self_exit, type, value, tb)
|
||||
// Stack: [..., exit_func, self_exit, lasti, prev_exc, exc]
|
||||
emit!(self, Instruction::WithExceptStart);
|
||||
|
||||
if is_async {
|
||||
@@ -5310,7 +5375,6 @@ impl Compiler {
|
||||
let _ = self.compile_yield_from_sequence(true)?;
|
||||
}
|
||||
|
||||
// TO_BOOL + POP_JUMP_IF_TRUE: check if exception is suppressed
|
||||
emit!(self, Instruction::ToBool);
|
||||
emit!(
|
||||
self,
|
||||
@@ -5319,25 +5383,19 @@ impl Compiler {
|
||||
}
|
||||
);
|
||||
|
||||
// Pop the nested fblock BEFORE RERAISE so that RERAISE's exception
|
||||
// handler points to the outer handler (try-except), not cleanup_block.
|
||||
// This is critical: when RERAISE propagates the exception, the exception
|
||||
// table should route it to the outer try-except, not back to cleanup.
|
||||
emit!(self, PseudoInstruction::PopBlock);
|
||||
self.pop_fblock(FBlockType::ExceptionHandler);
|
||||
|
||||
// Not suppressed: RERAISE 2
|
||||
emit!(self, Instruction::Reraise { depth: 2 });
|
||||
|
||||
// ===== Suppress block =====
|
||||
// Exception was suppressed, clean up stack
|
||||
// Stack: [..., __exit__, lasti, prev_exc, exc, True]
|
||||
// Need to pop: True, exc, prev_exc, __exit__
|
||||
// Stack: [..., exit_func, self_exit, lasti, prev_exc, exc, True]
|
||||
self.switch_to_block(suppress_block);
|
||||
emit!(self, Instruction::PopTop); // pop True (TO_BOOL result)
|
||||
emit!(self, Instruction::PopExcept); // pop exc and restore prev_exc
|
||||
emit!(self, Instruction::PopTop); // pop __exit__
|
||||
emit!(self, Instruction::PopTop); // pop True
|
||||
emit!(self, Instruction::PopExcept); // pop exc, restore prev_exc
|
||||
emit!(self, Instruction::PopTop); // pop lasti
|
||||
emit!(self, Instruction::PopTop); // pop self_exit
|
||||
emit!(self, Instruction::PopTop); // pop exit_func
|
||||
emit!(self, PseudoInstruction::Jump { delta: after_block });
|
||||
|
||||
// ===== Cleanup block (for nested exception during __exit__) =====
|
||||
@@ -6989,10 +7047,7 @@ impl Compiler {
|
||||
let has_unpacking = items.iter().any(|item| item.key.is_none());
|
||||
|
||||
if !has_unpacking {
|
||||
// STACK_USE_GUIDELINE: for large dicts (16+ pairs), use
|
||||
// BUILD_MAP 0 + MAP_ADD to avoid excessive stack usage
|
||||
let big = items.len() * 2 > 30; // ~15 pairs threshold
|
||||
if big {
|
||||
if items.len() >= 16 {
|
||||
emit!(self, Instruction::BuildMap { count: 0 });
|
||||
for item in items {
|
||||
self.compile_expression(item.key.as_ref().unwrap())?;
|
||||
@@ -7567,23 +7622,8 @@ impl Compiler {
|
||||
self.compile_expr_tstring(tstring)?;
|
||||
}
|
||||
ast::Expr::StringLiteral(string) => {
|
||||
let value = string.value.to_str();
|
||||
if value.contains(char::REPLACEMENT_CHARACTER) {
|
||||
let value = string
|
||||
.value
|
||||
.iter()
|
||||
.map(|lit| {
|
||||
let source = self.source_file.slice(lit.range);
|
||||
crate::string_parser::parse_string_literal(source, lit.flags.into())
|
||||
})
|
||||
.collect();
|
||||
// might have a surrogate literal; should reparse to be sure
|
||||
self.emit_load_const(ConstantData::Str { value });
|
||||
} else {
|
||||
self.emit_load_const(ConstantData::Str {
|
||||
value: value.into(),
|
||||
});
|
||||
}
|
||||
let value = self.compile_string_value(string);
|
||||
self.emit_load_const(ConstantData::Str { value });
|
||||
}
|
||||
ast::Expr::BytesLiteral(bytes) => {
|
||||
let iter = bytes.value.iter().flat_map(|x| x.iter().copied());
|
||||
@@ -8514,11 +8554,78 @@ impl Compiler {
|
||||
|
||||
// fn block_done()
|
||||
|
||||
/// Convert a string literal AST node to Wtf8Buf, handling surrogates 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
|
||||
string
|
||||
.value
|
||||
.iter()
|
||||
.map(|lit| {
|
||||
let source = self.source_file.slice(lit.range);
|
||||
crate::string_parser::parse_string_literal(source, lit.flags.into())
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
value.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()
|
||||
}
|
||||
|
||||
/// Try to fold a collection of constant expressions into a single ConstantData::Tuple.
|
||||
/// Returns None if any element cannot be folded.
|
||||
fn try_fold_constant_collection(
|
||||
&mut self,
|
||||
elts: &[ast::Expr],
|
||||
) -> CompileResult<Option<ConstantData>> {
|
||||
let mut constants = Vec::with_capacity(elts.len());
|
||||
for elt in elts {
|
||||
match elt {
|
||||
ast::Expr::NumberLiteral(num) => match &num.value {
|
||||
ast::Number::Int(int) => {
|
||||
let value = ruff_int_to_bigint(int).map_err(|e| self.error(e))?;
|
||||
constants.push(ConstantData::Integer { value });
|
||||
}
|
||||
ast::Number::Float(f) => {
|
||||
constants.push(ConstantData::Float { value: *f });
|
||||
}
|
||||
ast::Number::Complex { real, imag } => {
|
||||
constants.push(ConstantData::Complex {
|
||||
value: Complex::new(*real, *imag),
|
||||
});
|
||||
}
|
||||
},
|
||||
ast::Expr::StringLiteral(s) => {
|
||||
let value = self.compile_string_value(s);
|
||||
constants.push(ConstantData::Str { value });
|
||||
}
|
||||
ast::Expr::BytesLiteral(b) => {
|
||||
constants.push(ConstantData::Bytes {
|
||||
value: b.value.bytes().collect(),
|
||||
});
|
||||
}
|
||||
ast::Expr::BooleanLiteral(b) => {
|
||||
constants.push(ConstantData::Boolean { value: b.value });
|
||||
}
|
||||
ast::Expr::NoneLiteral(_) => {
|
||||
constants.push(ConstantData::None);
|
||||
}
|
||||
ast::Expr::EllipsisLiteral(_) => {
|
||||
constants.push(ConstantData::Ellipsis);
|
||||
}
|
||||
_ => return Ok(None),
|
||||
}
|
||||
}
|
||||
Ok(Some(ConstantData::Tuple {
|
||||
elements: constants,
|
||||
}))
|
||||
}
|
||||
|
||||
fn emit_load_const(&mut self, constant: ConstantData) {
|
||||
let idx = self.arg_constant(constant);
|
||||
self.emit_arg(idx, |consti| Instruction::LoadConst { consti })
|
||||
@@ -8740,10 +8847,8 @@ impl Compiler {
|
||||
for action in unwind_actions {
|
||||
match action {
|
||||
UnwindAction::With { is_async } => {
|
||||
// codegen_unwind_fblock(WITH/ASYNC_WITH)
|
||||
// Stack: [..., exit_func, self_exit]
|
||||
emit!(self, PseudoInstruction::PopBlock);
|
||||
// compiler_call_exit_with_nones
|
||||
emit!(self, Instruction::PushNull);
|
||||
self.emit_load_const(ConstantData::None);
|
||||
self.emit_load_const(ConstantData::None);
|
||||
self.emit_load_const(ConstantData::None);
|
||||
|
||||
@@ -191,9 +191,12 @@ impl CodeInfo {
|
||||
) -> crate::InternalResult<CodeObject> {
|
||||
// Constant folding passes
|
||||
self.fold_unary_negative();
|
||||
self.remove_nops(); // remove NOPs from unary folding so tuple/list/set see contiguous LOADs
|
||||
self.fold_tuple_constants();
|
||||
self.fold_list_constants();
|
||||
self.fold_set_constants();
|
||||
self.remove_nops(); // remove NOPs from collection folding
|
||||
self.fold_const_iterable_for_iter();
|
||||
self.convert_to_load_small_int();
|
||||
self.remove_unused_consts();
|
||||
self.remove_nops();
|
||||
@@ -214,6 +217,8 @@ impl CodeInfo {
|
||||
self.dce(); // re-run within-block DCE after normalize_jumps creates new instructions
|
||||
self.eliminate_unreachable_blocks();
|
||||
duplicate_end_returns(&mut self.blocks);
|
||||
self.dce(); // truncate after terminal in blocks that got return duplicated
|
||||
self.eliminate_unreachable_blocks(); // remove now-unreachable last block
|
||||
self.optimize_load_global_push_null();
|
||||
|
||||
let max_stackdepth = self.max_stackdepth()?;
|
||||
@@ -876,6 +881,49 @@ impl CodeInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert constant list/set construction before GET_ITER to just LOAD_CONST tuple.
|
||||
/// BUILD_LIST 0 + LOAD_CONST (tuple) + LIST_EXTEND 1 + GET_ITER
|
||||
/// → LOAD_CONST (tuple) + GET_ITER
|
||||
/// Also handles BUILD_SET 0 + LOAD_CONST + SET_UPDATE 1 + GET_ITER.
|
||||
fn fold_const_iterable_for_iter(&mut self) {
|
||||
for block in &mut self.blocks {
|
||||
let mut i = 0;
|
||||
while i + 3 < block.instructions.len() {
|
||||
let is_build = matches!(
|
||||
block.instructions[i].instr.real(),
|
||||
Some(Instruction::BuildList { .. } | Instruction::BuildSet { .. })
|
||||
) && u32::from(block.instructions[i].arg) == 0;
|
||||
|
||||
let is_const = matches!(
|
||||
block.instructions[i + 1].instr.real(),
|
||||
Some(Instruction::LoadConst { .. })
|
||||
);
|
||||
|
||||
let is_extend = matches!(
|
||||
block.instructions[i + 2].instr.real(),
|
||||
Some(Instruction::ListExtend { .. } | Instruction::SetUpdate { .. })
|
||||
) && u32::from(block.instructions[i + 2].arg) == 1;
|
||||
|
||||
let is_iter = matches!(
|
||||
block.instructions[i + 3].instr.real(),
|
||||
Some(Instruction::GetIter)
|
||||
);
|
||||
|
||||
if is_build && is_const && is_extend && is_iter {
|
||||
// Replace: BUILD_X 0 → NOP, keep LOAD_CONST, LIST_EXTEND → NOP
|
||||
let loc = block.instructions[i].location;
|
||||
block.instructions[i].instr = Instruction::Nop.into();
|
||||
block.instructions[i].location = loc;
|
||||
block.instructions[i + 2].instr = Instruction::Nop.into();
|
||||
block.instructions[i + 2].location = loc;
|
||||
i += 4;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fold constant set literals: LOAD_CONST* + BUILD_SET N →
|
||||
/// BUILD_SET 0 + LOAD_CONST (frozenset-as-tuple) + SET_UPDATE 1
|
||||
fn fold_set_constants(&mut self) {
|
||||
@@ -1987,6 +2035,7 @@ fn duplicate_end_returns(blocks: &mut [Block]) {
|
||||
// Check if the last block ends with LOAD_CONST + RETURN_VALUE (the implicit return)
|
||||
let last_insts = &blocks[last_block.idx()].instructions;
|
||||
// Only apply when the last block is EXACTLY a return-None epilogue
|
||||
// AND the return instructions have no explicit line number (lineno <= 0)
|
||||
let is_return_block = last_insts.len() == 2
|
||||
&& matches!(
|
||||
last_insts[0].instr,
|
||||
@@ -2010,12 +2059,22 @@ fn duplicate_end_returns(blocks: &mut [Block]) {
|
||||
let block = &blocks[current.idx()];
|
||||
if current != last_block && block.next == last_block && !block.cold && !block.except_handler
|
||||
{
|
||||
let has_fallthrough = block
|
||||
.instructions
|
||||
.last()
|
||||
let last_ins = block.instructions.last();
|
||||
let has_fallthrough = last_ins
|
||||
.map(|ins| !ins.instr.is_scope_exit() && !ins.instr.is_unconditional_jump())
|
||||
.unwrap_or(true);
|
||||
if has_fallthrough {
|
||||
// Don't duplicate if block already ends with the same return pattern
|
||||
let already_has_return = block.instructions.len() >= 2 && {
|
||||
let n = block.instructions.len();
|
||||
matches!(
|
||||
block.instructions[n - 2].instr,
|
||||
AnyInstruction::Real(Instruction::LoadConst { .. })
|
||||
) && matches!(
|
||||
block.instructions[n - 1].instr,
|
||||
AnyInstruction::Real(Instruction::ReturnValue)
|
||||
)
|
||||
};
|
||||
if has_fallthrough && !already_has_return {
|
||||
blocks_to_fix.push(current);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/codegen/src/compile.rs
|
||||
assertion_line: 9458
|
||||
assertion_line: 9553
|
||||
expression: "compile_exec(\"\\\nif True and False and False:\n pass\n\")"
|
||||
---
|
||||
1 0 RESUME (0)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/codegen/src/compile.rs
|
||||
assertion_line: 9468
|
||||
assertion_line: 9563
|
||||
expression: "compile_exec(\"\\\nif (True and False) or (False and True):\n pass\n\")"
|
||||
---
|
||||
1 0 RESUME (0)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/codegen/src/compile.rs
|
||||
assertion_line: 9448
|
||||
assertion_line: 9543
|
||||
expression: "compile_exec(\"\\\nif True or False or False:\n pass\n\")"
|
||||
---
|
||||
1 0 RESUME (0)
|
||||
|
||||
Generated
+66
-65
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/codegen/src/compile.rs
|
||||
assertion_line: 9496
|
||||
assertion_line: 9591
|
||||
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)
|
||||
@@ -23,8 +23,8 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
|
||||
15 CACHE
|
||||
16 CACHE
|
||||
17 CACHE
|
||||
>> 18 LOAD_CONST ("ham")
|
||||
19 CALL (1)
|
||||
18 LOAD_CONST ("ham")
|
||||
>> 19 CALL (1)
|
||||
20 CACHE
|
||||
21 CACHE
|
||||
22 CACHE
|
||||
@@ -67,8 +67,8 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
|
||||
58 COPY (1)
|
||||
59 LOAD_SPECIAL (__exit__)
|
||||
60 SWAP (2)
|
||||
61 LOAD_SPECIAL (__enter__)
|
||||
62 PUSH_NULL
|
||||
61 SWAP (3)
|
||||
62 LOAD_SPECIAL (__enter__)
|
||||
63 CALL (0)
|
||||
64 CACHE
|
||||
65 CACHE
|
||||
@@ -89,8 +89,8 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
|
||||
78 COPY (1)
|
||||
79 LOAD_SPECIAL (__aexit__)
|
||||
80 SWAP (2)
|
||||
81 LOAD_SPECIAL (__aenter__)
|
||||
82 PUSH_NULL
|
||||
81 SWAP (3)
|
||||
82 LOAD_SPECIAL (__aenter__)
|
||||
83 CALL (0)
|
||||
84 CACHE
|
||||
85 CACHE
|
||||
@@ -138,31 +138,31 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
|
||||
124 POP_EXCEPT
|
||||
125 POP_TOP
|
||||
126 POP_TOP
|
||||
127 JUMP_FORWARD (3)
|
||||
128 COPY (3)
|
||||
129 POP_EXCEPT
|
||||
130 RERAISE (1)
|
||||
131 JUMP_FORWARD (46)
|
||||
132 PUSH_EXC_INFO
|
||||
127 POP_TOP
|
||||
128 JUMP_FORWARD (3)
|
||||
129 COPY (3)
|
||||
130 POP_EXCEPT
|
||||
131 RERAISE (1)
|
||||
132 JUMP_FORWARD (46)
|
||||
133 PUSH_EXC_INFO
|
||||
|
||||
7 133 LOAD_GLOBAL (12, Exception)
|
||||
134 CACHE
|
||||
7 134 LOAD_GLOBAL (12, Exception)
|
||||
135 CACHE
|
||||
136 CACHE
|
||||
137 CACHE
|
||||
138 CHECK_EXC_MATCH
|
||||
139 POP_JUMP_IF_FALSE (33)
|
||||
140 CACHE
|
||||
141 NOT_TAKEN
|
||||
142 STORE_FAST (1, ex)
|
||||
138 CACHE
|
||||
139 CHECK_EXC_MATCH
|
||||
140 POP_JUMP_IF_FALSE (33)
|
||||
141 CACHE
|
||||
142 NOT_TAKEN
|
||||
143 STORE_FAST (1, ex)
|
||||
|
||||
8 143 LOAD_GLOBAL (4, self)
|
||||
144 CACHE
|
||||
8 144 LOAD_GLOBAL (4, self)
|
||||
145 CACHE
|
||||
146 CACHE
|
||||
147 CACHE
|
||||
148 LOAD_ATTR (15, assertIs, method=true)
|
||||
149 CACHE
|
||||
148 CACHE
|
||||
149 LOAD_ATTR (15, assertIs, method=true)
|
||||
150 CACHE
|
||||
151 CACHE
|
||||
152 CACHE
|
||||
@@ -171,34 +171,34 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
|
||||
155 CACHE
|
||||
156 CACHE
|
||||
157 CACHE
|
||||
158 LOAD_FAST_LOAD_FAST (ex, stop_exc)
|
||||
159 CALL (2)
|
||||
160 CACHE
|
||||
158 CACHE
|
||||
159 LOAD_FAST_LOAD_FAST (ex, stop_exc)
|
||||
160 CALL (2)
|
||||
161 CACHE
|
||||
162 CACHE
|
||||
163 POP_TOP
|
||||
164 JUMP_FORWARD (4)
|
||||
165 LOAD_CONST (None)
|
||||
166 STORE_FAST (1, ex)
|
||||
167 DELETE_FAST (1, ex)
|
||||
168 RERAISE (1)
|
||||
169 POP_EXCEPT
|
||||
170 LOAD_CONST (None)
|
||||
171 STORE_FAST (1, ex)
|
||||
172 DELETE_FAST (1, ex)
|
||||
173 JUMP_FORWARD (28)
|
||||
174 RERAISE (0)
|
||||
175 COPY (3)
|
||||
176 POP_EXCEPT
|
||||
177 RERAISE (1)
|
||||
163 CACHE
|
||||
164 POP_TOP
|
||||
165 JUMP_FORWARD (4)
|
||||
166 LOAD_CONST (None)
|
||||
167 STORE_FAST (1, ex)
|
||||
168 DELETE_FAST (1, ex)
|
||||
169 RERAISE (1)
|
||||
170 POP_EXCEPT
|
||||
171 LOAD_CONST (None)
|
||||
172 STORE_FAST (1, ex)
|
||||
173 DELETE_FAST (1, ex)
|
||||
174 JUMP_FORWARD (28)
|
||||
175 RERAISE (0)
|
||||
176 COPY (3)
|
||||
177 POP_EXCEPT
|
||||
178 RERAISE (1)
|
||||
|
||||
10 178 LOAD_GLOBAL (4, self)
|
||||
179 CACHE
|
||||
10 179 LOAD_GLOBAL (4, self)
|
||||
180 CACHE
|
||||
181 CACHE
|
||||
182 CACHE
|
||||
183 LOAD_ATTR (17, fail, method=true)
|
||||
184 CACHE
|
||||
183 CACHE
|
||||
184 LOAD_ATTR (17, fail, method=true)
|
||||
185 CACHE
|
||||
186 CACHE
|
||||
187 CACHE
|
||||
@@ -207,27 +207,27 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
|
||||
190 CACHE
|
||||
191 CACHE
|
||||
192 CACHE
|
||||
193 LOAD_FAST_BORROW (0, stop_exc)
|
||||
194 FORMAT_SIMPLE
|
||||
195 LOAD_CONST (" was suppressed")
|
||||
196 BUILD_STRING (2)
|
||||
197 CALL (1)
|
||||
198 CACHE
|
||||
193 CACHE
|
||||
194 LOAD_FAST_BORROW (0, stop_exc)
|
||||
195 FORMAT_SIMPLE
|
||||
196 LOAD_CONST (" was suppressed")
|
||||
197 BUILD_STRING (2)
|
||||
198 CALL (1)
|
||||
199 CACHE
|
||||
200 CACHE
|
||||
201 POP_TOP
|
||||
202 NOP
|
||||
201 CACHE
|
||||
202 POP_TOP
|
||||
203 NOP
|
||||
|
||||
3 203 PUSH_NULL
|
||||
204 LOAD_CONST (None)
|
||||
3 204 LOAD_CONST (None)
|
||||
205 LOAD_CONST (None)
|
||||
206 LOAD_CONST (None)
|
||||
207 CALL (3)
|
||||
>> 208 CACHE
|
||||
209 CACHE
|
||||
208 CACHE
|
||||
>> 209 CACHE
|
||||
210 CACHE
|
||||
211 POP_TOP
|
||||
212 JUMP_FORWARD (18)
|
||||
212 JUMP_FORWARD (19)
|
||||
213 PUSH_EXC_INFO
|
||||
214 WITH_EXCEPT_START
|
||||
215 TO_BOOL
|
||||
@@ -242,12 +242,13 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
|
||||
224 POP_EXCEPT
|
||||
225 POP_TOP
|
||||
226 POP_TOP
|
||||
227 JUMP_FORWARD (3)
|
||||
228 COPY (3)
|
||||
229 POP_EXCEPT
|
||||
230 RERAISE (1)
|
||||
231 JUMP_BACKWARD (208)
|
||||
232 CACHE
|
||||
227 POP_TOP
|
||||
228 JUMP_FORWARD (3)
|
||||
229 COPY (3)
|
||||
230 POP_EXCEPT
|
||||
231 RERAISE (1)
|
||||
232 JUMP_BACKWARD (209)
|
||||
233 CACHE
|
||||
|
||||
2 MAKE_FUNCTION
|
||||
3 STORE_NAME (0, test)
|
||||
|
||||
@@ -2136,6 +2136,8 @@ impl SymbolTableBuilder {
|
||||
CompilerScope::Comprehension,
|
||||
self.line_index_start(range),
|
||||
);
|
||||
// Generator expressions need the is_generator flag
|
||||
self.tables.last_mut().unwrap().is_generator = is_generator;
|
||||
|
||||
// PEP 709: Mark non-generator comprehensions for inlining,
|
||||
// but only inside function-like scopes (fastlocals).
|
||||
|
||||
@@ -1020,7 +1020,7 @@ impl InstructionMetadata for Instruction {
|
||||
Self::LoadLocals => (1, 0),
|
||||
Self::LoadName { .. } => (1, 0),
|
||||
Self::LoadSmallInt { .. } => (1, 0),
|
||||
Self::LoadSpecial { .. } => (1, 1),
|
||||
Self::LoadSpecial { .. } => (2, 1),
|
||||
Self::LoadSuperAttr { .. } => (1 + (oparg & 1), 3),
|
||||
Self::LoadSuperAttrAttr => (1, 3),
|
||||
Self::LoadSuperAttrMethod => (2, 3),
|
||||
@@ -1085,7 +1085,7 @@ impl InstructionMetadata for Instruction {
|
||||
Self::UnpackSequenceList => (oparg, 1),
|
||||
Self::UnpackSequenceTuple => (oparg, 1),
|
||||
Self::UnpackSequenceTwoTuple => (2, 1),
|
||||
Self::WithExceptStart => (6, 5),
|
||||
Self::WithExceptStart => (7, 6),
|
||||
Self::YieldValue { .. } => (1, 1),
|
||||
};
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ impl From<u32> for ResumeType {
|
||||
fn from(value: u32) -> Self {
|
||||
match value {
|
||||
0 => Self::AtFuncStart,
|
||||
1 => Self::AfterYield,
|
||||
5 => Self::AfterYield,
|
||||
2 => Self::AfterYieldFrom,
|
||||
3 => Self::AfterAwait,
|
||||
_ => Self::Other(value),
|
||||
@@ -302,7 +302,7 @@ impl From<ResumeType> for u32 {
|
||||
fn from(typ: ResumeType) -> Self {
|
||||
match typ {
|
||||
ResumeType::AtFuncStart => 0,
|
||||
ResumeType::AfterYield => 1,
|
||||
ResumeType::AfterYield => 5,
|
||||
ResumeType::AfterYieldFrom => 2,
|
||||
ResumeType::AfterAwait => 3,
|
||||
ResumeType::Other(v) => v,
|
||||
|
||||
+22
-18
@@ -2946,22 +2946,23 @@ impl ExecutingFrame<'_> {
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::LoadSpecial { method } => {
|
||||
// Stack effect: 0 (replaces TOS with bound method)
|
||||
// Input: [..., obj]
|
||||
// Output: [..., bound_method]
|
||||
// Pops obj, pushes (callable, self_or_null) for CALL convention.
|
||||
// Push order: callable first (deeper), self_or_null on top.
|
||||
use crate::vm::PyMethod;
|
||||
|
||||
let obj = self.pop_value();
|
||||
let oparg = method.get(arg);
|
||||
let method_name = get_special_method_name(oparg, vm);
|
||||
|
||||
let bound = match vm.get_special_method(&obj, method_name)? {
|
||||
match vm.get_special_method(&obj, method_name)? {
|
||||
Some(PyMethod::Function { target, func }) => {
|
||||
crate::builtins::PyBoundMethod::new(target, func)
|
||||
.into_ref(&vm.ctx)
|
||||
.into()
|
||||
self.push_value(func); // callable (deeper)
|
||||
self.push_value(target); // self (TOS)
|
||||
}
|
||||
Some(PyMethod::Attribute(bound)) => {
|
||||
self.push_value(bound); // callable (deeper)
|
||||
self.push_null(); // NULL (TOS)
|
||||
}
|
||||
Some(PyMethod::Attribute(bound)) => bound,
|
||||
None => {
|
||||
return Err(vm.new_type_error(get_special_method_error_msg(
|
||||
oparg,
|
||||
@@ -2970,7 +2971,6 @@ impl ExecutingFrame<'_> {
|
||||
)));
|
||||
}
|
||||
};
|
||||
self.push_value(bound);
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::MakeFunction => self.execute_make_function(vm),
|
||||
@@ -3522,24 +3522,28 @@ impl ExecutingFrame<'_> {
|
||||
self.unpack_sequence(expected, vm)
|
||||
}
|
||||
Instruction::WithExceptStart => {
|
||||
// Stack: [..., __exit__, lasti, prev_exc, exc]
|
||||
// Call __exit__(type, value, tb) and push result
|
||||
// __exit__ is at TOS-3 (below lasti, prev_exc, and exc)
|
||||
// Stack: [..., exit_func, self_or_null, lasti, prev_exc, exc]
|
||||
// exit_func at TOS-4, self_or_null at TOS-3
|
||||
let exc = vm.current_exception();
|
||||
|
||||
let stack_len = self.localsplus.stack_len();
|
||||
let exit = expect_unchecked(
|
||||
self.localsplus.stack_index(stack_len - 4).clone(),
|
||||
"WithExceptStart: __exit__ is NULL",
|
||||
let exit_func = expect_unchecked(
|
||||
self.localsplus.stack_index(stack_len - 5).clone(),
|
||||
"WithExceptStart: exit_func is NULL",
|
||||
);
|
||||
let self_or_null = self.localsplus.stack_index(stack_len - 4).clone();
|
||||
|
||||
let args = if let Some(ref exc) = exc {
|
||||
let (tp, val, tb) = if let Some(ref exc) = exc {
|
||||
vm.split_exception(exc.clone())
|
||||
} else {
|
||||
(vm.ctx.none(), vm.ctx.none(), vm.ctx.none())
|
||||
};
|
||||
let exit_res = exit.call(args, vm)?;
|
||||
// Push result on top of stack
|
||||
|
||||
let exit_res = if let Some(self_exit) = self_or_null {
|
||||
exit_func.call((self_exit.to_pyobj(), tp, val, tb), vm)?
|
||||
} else {
|
||||
exit_func.call((tp, val, tb), vm)?
|
||||
};
|
||||
self.push_value(exit_res);
|
||||
|
||||
Ok(None)
|
||||
|
||||
Reference in new issue
Block a user