Merge pull request #7622 from youknowone/bytecode-parity-string

This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-04-18 17:52:34 +09:00
commit 4de2268ccb
9 files changed
+1024 -262

No files matched your search

-2
View File
@@ -2576,7 +2576,6 @@ class TestExpressionStackSize(unittest.TestCase):
def test_binop(self):
self.check_stack_size("x + " * self.N + "x")
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 101 not less than or equal to 6
def test_list(self):
self.check_stack_size("[" + "x, " * self.N + "x]")
@@ -2584,7 +2583,6 @@ class TestExpressionStackSize(unittest.TestCase):
def test_tuple(self):
self.check_stack_size("(" + "x, " * self.N + "x)")
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 101 not less than or equal to 6
def test_set(self):
self.check_stack_size("{" + "x, " * self.N + "x}")
-1
View File
@@ -441,7 +441,6 @@ class TestTranforms(BytecodeTestCase):
self.check_lnotab(code)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_constant_folding_remove_nop_location(self):
sources = [
"""
-1
View File
@@ -111,7 +111,6 @@ class TestTString(unittest.TestCase, TStringBaseCase):
with self.assertRaises(SyntaxError):
eval("t'{num!z}'")
@unittest.expectedFailure # TODO: RUSTPYTHON; ? ++++++
def test_debug_specifier(self):
# Test debug specifier
value = 42
-1
View File
@@ -4697,7 +4697,6 @@ class GenericTests(BaseTestCase):
with self.assertRaises(TypeError):
D[()]
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_generic_init_subclass_not_called_error(self):
notes = ["Note: this exception may have been caused by "
r"'GenericTests.test_generic_init_subclass_not_called_error.<locals>.Base.__init_subclass__' "
File diff suppressed because it is too large. Load diff
+292 -34
View File
@@ -257,7 +257,7 @@ impl CodeInfo {
self.eliminate_unreachable_blocks();
resolve_line_numbers(&mut self.blocks);
redirect_empty_block_targets(&mut self.blocks);
duplicate_end_returns(&mut self.blocks);
duplicate_end_returns(&mut self.blocks, &self.metadata);
self.dce(); // truncate after terminal in blocks that got return duplicated
self.eliminate_unreachable_blocks(); // remove now-unreachable last block
self.remove_redundant_const_pop_top_pairs();
@@ -271,6 +271,10 @@ impl CodeInfo {
reorder_jump_over_exception_cleanup_blocks(&mut self.blocks);
self.eliminate_unreachable_blocks();
remove_redundant_nops_and_jumps(&mut self.blocks);
inline_with_suppress_return_blocks(&mut self.blocks);
inline_pop_except_return_blocks(&mut self.blocks);
duplicate_named_except_cleanup_returns(&mut self.blocks, &self.metadata);
self.eliminate_unreachable_blocks();
// Late CFG cleanup can create new same-line STORE_FAST/LOAD_FAST and
// STORE_FAST/STORE_FAST adjacencies in match/capture code paths that
// did not exist during the earlier flowgraph-like pass.
@@ -301,6 +305,7 @@ impl CodeInfo {
self.deoptimize_store_fast_store_fast_after_cleanup();
self.apply_static_swaps();
self.insert_superinstructions();
self.deoptimize_store_fast_store_fast_after_cleanup();
self.optimize_load_global_push_null();
self.reorder_entry_prefix_cell_setup();
self.remove_unused_consts();
@@ -1193,6 +1198,43 @@ impl CodeInfo {
value: result.into(),
})
}
(ConstantData::Integer { value: n }, ConstantData::Str { value: s })
if matches!(op, BinOp::Multiply) =>
{
let n: usize = n.try_into().ok()?;
if n > 4096 {
return None;
}
let result = s.to_string().repeat(n);
Some(ConstantData::Str {
value: result.into(),
})
}
(ConstantData::Bytes { value: l }, ConstantData::Bytes { value: r })
if matches!(op, BinOp::Add) =>
{
let mut result = l.clone();
result.extend_from_slice(r);
Some(ConstantData::Bytes { value: result })
}
(ConstantData::Bytes { value: b }, ConstantData::Integer { value: n })
if matches!(op, BinOp::Multiply) =>
{
let n: usize = n.try_into().ok()?;
if n > 4096 {
return None;
}
Some(ConstantData::Bytes { value: b.repeat(n) })
}
(ConstantData::Integer { value: n }, ConstantData::Bytes { value: b })
if matches!(op, BinOp::Multiply) =>
{
let n: usize = n.try_into().ok()?;
if n > 4096 {
return None;
}
Some(ConstantData::Bytes { value: b.repeat(n) })
}
_ => None,
}
}
@@ -1201,6 +1243,7 @@ impl CodeInfo {
match c {
ConstantData::Integer { value } => value.bits() > 4096 * 8,
ConstantData::Str { value } => value.len() > 4096,
ConstantData::Bytes { value } => value.len() > 4096,
_ => false,
}
}
@@ -1886,14 +1929,12 @@ impl CodeInfo {
/// Eliminate dead stores in STORE_FAST sequences (apply_static_swaps).
///
/// In sequences of consecutive STORE_FAST instructions (from tuple unpacking),
/// if the same variable is stored to more than once, only the first store
/// (which gets TOS — the rightmost value) matters. Later stores to the
/// same variable are dead and replaced with POP_TOP.
/// Simplified apply_static_swaps (CPython flowgraph.c):
/// In STORE_FAST sequences that follow UNPACK_SEQUENCE / UNPACK_EX,
/// replace duplicate stores to the same variable with POP_TOP.
/// UNPACK pushes values so stores execute left-to-right; the LAST
/// store to a variable carries the final value, earlier ones are dead.
/// only collapse directly adjacent duplicate targets.
///
/// CPython preserves non-adjacent duplicates such as `_, expr, _` so the
/// store layout still reflects the original unpack order. Replacing the
/// first `_` with POP_TOP there changes the emitted superinstructions and
/// bytecode shape even though the final value is the same.
fn eliminate_dead_stores(&mut self) {
for block in &mut self.blocks {
let instructions = &mut block.instructions;
@@ -1921,18 +1962,18 @@ impl CodeInfo {
run_end += 1;
}
if run_end - run_start >= 2 {
// Pass 1: find the LAST occurrence of each variable
let mut last_occurrence = std::collections::HashMap::new();
for (j, instr) in instructions[run_start..run_end].iter().enumerate() {
last_occurrence.insert(u32::from(instr.arg), j);
}
// Pass 2: non-last stores to the same variable are dead
for (j, instr) in instructions[run_start..run_end].iter_mut().enumerate() {
let idx = u32::from(instr.arg);
if last_occurrence[&idx] != j {
let mut j = run_start;
while j < run_end {
let arg = u32::from(instructions[j].arg);
let mut group_end = j + 1;
while group_end < run_end && u32::from(instructions[group_end].arg) == arg {
group_end += 1;
}
for instr in &mut instructions[j..group_end.saturating_sub(1)] {
instr.instr = Opcode::PopTop.into();
instr.arg = OpArg::new(0);
}
j = group_end;
}
}
i = run_end.max(i + 1);
@@ -2353,6 +2394,14 @@ impl CodeInfo {
continue;
}
let prev_real = block.instructions[..i]
.iter()
.rev()
.find_map(|info| info.instr.real());
let next_real = block.instructions[(j + 1)..]
.iter()
.find_map(|info| info.instr.real());
match (curr.instr.real(), next.instr.real()) {
(Some(Instruction::LoadFast { .. }), Some(Instruction::LoadFast { .. })) => {
let idx1 = u32::from(curr.arg);
@@ -2373,6 +2422,13 @@ impl CodeInfo {
Some(Instruction::StoreFast { .. }),
Some(Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. }),
) => {
if self.flags.contains(CodeFlags::GENERATOR)
&& matches!(prev_real, Some(Instruction::ForIter { .. }))
&& !matches!(next_real, Some(Instruction::ToBool))
{
i += 1;
continue;
}
let store_idx = u32::from(curr.arg);
let load_idx = u32::from(next.arg);
if store_idx >= 16 || load_idx >= 16 {
@@ -3479,7 +3535,7 @@ impl CodeInfo {
self.debug_block_dump(),
));
duplicate_end_returns(&mut self.blocks);
duplicate_end_returns(&mut self.blocks, &self.metadata);
trace.push((
"after_duplicate_end_returns".to_owned(),
self.debug_block_dump(),
@@ -4169,8 +4225,14 @@ fn jump_threading_impl(blocks: &mut [Block], include_conditional: bool) {
if target == BlockIdx::NULL {
continue;
}
// Check if target block's first instruction is an unconditional jump
let target_jump = blocks[target.idx()].instructions.first().copied();
// Thread through blocks that are only leading NOPs followed by an
// unconditional jump so late line anchors do not leave
// JUMP_FORWARD -> NOP -> JUMP_BACKWARD chains behind.
let target_jump = blocks[target.idx()]
.instructions
.iter()
.find(|info| !matches!(info.instr.real(), Some(Instruction::Nop)))
.copied();
if let Some(target_ins) = target_jump
&& target_ins.instr.is_unconditional_jump()
&& target_ins.target != BlockIdx::NULL
@@ -4495,7 +4557,10 @@ fn remove_redundant_nops_in_blocks(blocks: &mut [Block]) -> usize {
if lineno < 0 || prev_lineno == lineno {
remove = true;
} else if src < src_instructions.len() - 1 {
if src_instructions[src + 1].folded_from_nonliteral_expr {
if src_instructions[src + 1].instr.is_unconditional_jump() {
src_instructions[src + 1].lineno_override = Some(lineno);
remove = true;
} else if src_instructions[src + 1].folded_from_nonliteral_expr {
remove = true;
} else {
let next_lineno = instruction_lineno(&src_instructions[src + 1]);
@@ -4671,6 +4736,14 @@ fn next_nonempty_block(blocks: &[Block], mut idx: BlockIdx) -> BlockIdx {
idx
}
fn is_load_const_none(instr: &InstructionInfo, metadata: &CodeUnitMetadata) -> bool {
matches!(instr.instr.real(), Some(Instruction::LoadConst { .. }))
&& matches!(
metadata.consts.get_index(u32::from(instr.arg) as usize),
Some(ConstantData::None)
)
}
fn instruction_lineno(instr: &InstructionInfo) -> i32 {
instr
.lineno_override
@@ -5268,7 +5341,7 @@ fn find_layout_predecessor(blocks: &[Block], target: BlockIdx) -> BlockIdx {
/// Duplicate `LOAD_CONST None + RETURN_VALUE` for blocks that fall through
/// to the final return block.
fn duplicate_end_returns(blocks: &mut Vec<Block>) {
fn duplicate_end_returns(blocks: &mut Vec<Block>, metadata: &CodeUnitMetadata) {
// Walk the block chain and keep the last non-cold non-empty block.
// After cold exception handlers are pushed to the end, the mainline
// return epilogue can sit before trailing cold blocks.
@@ -5292,12 +5365,13 @@ fn duplicate_end_returns(blocks: &mut Vec<Block>) {
}
let last_insts = &blocks[last_block.idx()].instructions;
// Only apply when the last block is EXACTLY a return-None epilogue
// Only apply when the last block is EXACTLY a return-None epilogue.
let is_return_block = last_insts.len() == 2
&& matches!(
last_insts[0].instr,
AnyInstruction::Real(Instruction::LoadConst { .. })
)
&& is_load_const_none(&last_insts[0], metadata)
&& matches!(
last_insts[1].instr,
AnyInstruction::Real(Instruction::ReturnValue)
@@ -5319,7 +5393,7 @@ fn duplicate_end_returns(blocks: &mut Vec<Block>) {
while current != BlockIdx::NULL {
let block = &blocks[current.idx()];
let next = next_nonempty_block(blocks, block.next);
if current != last_block && !block.cold && !block.except_handler {
if current != last_block && !block.cold {
let last_ins = block.instructions.last();
let has_fallthrough = last_ins
.map(|ins| !ins.instr.is_scope_exit() && !ins.instr.is_unconditional_jump())
@@ -5335,20 +5409,28 @@ fn duplicate_end_returns(blocks: &mut Vec<Block>) {
AnyInstruction::Real(Instruction::ReturnValue)
)
};
if next == last_block
if !block.except_handler
&& next == last_block
&& has_fallthrough
&& trailing_conditional_jump_index(block).is_none()
&& !already_has_return
{
fallthrough_blocks_to_fix.push(current);
}
if predecessors[last_block.idx()] > 1
&& let Some(last) = block.instructions.last()
&& last.instr.is_unconditional_jump()
&& last.target != BlockIdx::NULL
&& next_nonempty_block(blocks, last.target) == last_block
{
jump_targets_to_fix.push((current, block.instructions.len() - 1));
let jump_idx = trailing_conditional_jump_index(block).or_else(|| {
block.instructions.last().and_then(|last| {
(last.instr.is_unconditional_jump() && last.target != BlockIdx::NULL)
.then_some(block.instructions.len() - 1)
})
});
if let Some(jump_idx) = jump_idx {
let jump = &block.instructions[jump_idx];
if jump.target != BlockIdx::NULL
&& next_nonempty_block(blocks, jump.target) == last_block
&& (is_conditional_jump(&jump.instr) || predecessors[last_block.idx()] > 1)
{
jump_targets_to_fix.push((current, jump_idx));
}
}
}
current = blocks[current.idx()].next;
@@ -5371,7 +5453,7 @@ fn duplicate_end_returns(blocks: &mut Vec<Block>) {
// Clone the final return block for jump predecessors so their target layout
// matches CPython's duplicated exit blocks.
for (block_idx, instr_idx) in jump_targets_to_fix {
for (block_idx, instr_idx) in jump_targets_to_fix.into_iter().rev() {
let jump = blocks[block_idx.idx()].instructions[instr_idx];
let mut cloned_return = return_insts.clone();
if let Some(first) = cloned_return.first_mut() {
@@ -5404,6 +5486,182 @@ fn duplicate_end_returns(blocks: &mut Vec<Block>) {
}
}
fn inline_with_suppress_return_blocks(blocks: &mut [Block]) {
fn has_with_suppress_prefix(block: &Block, jump_idx: usize) -> bool {
let tail: Vec<_> = block.instructions[..jump_idx]
.iter()
.filter_map(|info| info.instr.real())
.rev()
.take(5)
.collect();
matches!(
tail.as_slice(),
[
Instruction::PopTop,
Instruction::PopTop,
Instruction::PopTop,
Instruction::PopExcept,
Instruction::PopTop,
]
)
}
for block_idx in 0..blocks.len() {
let Some(jump_idx) = blocks[block_idx].instructions.len().checked_sub(1) else {
continue;
};
let jump = blocks[block_idx].instructions[jump_idx];
if !jump.instr.is_unconditional_jump() || jump.target == BlockIdx::NULL {
continue;
}
if !has_with_suppress_prefix(&blocks[block_idx], jump_idx) {
continue;
}
let target = next_nonempty_block(blocks, jump.target);
if target == BlockIdx::NULL || !is_const_return_block(&blocks[target.idx()]) {
continue;
}
let mut cloned_return = blocks[target.idx()].instructions.clone();
for instr in &mut cloned_return {
overwrite_location(instr, jump.location, jump.end_location);
}
blocks[block_idx].instructions.pop();
blocks[block_idx].instructions.extend(cloned_return);
}
}
fn is_named_except_cleanup_return_block(block: &Block, metadata: &CodeUnitMetadata) -> bool {
matches!(
block.instructions.as_slice(),
[pop_except, load_none1, store, delete, load_none2, ret]
if matches!(pop_except.instr.real(), Some(Instruction::PopExcept))
&& is_load_const_none(load_none1, metadata)
&& matches!(
store.instr.real(),
Some(Instruction::StoreFast { .. } | Instruction::StoreName { .. })
)
&& matches!(
delete.instr.real(),
Some(Instruction::DeleteFast { .. } | Instruction::DeleteName { .. })
)
&& is_load_const_none(load_none2, metadata)
&& matches!(ret.instr.real(), Some(Instruction::ReturnValue))
)
}
fn duplicate_named_except_cleanup_returns(blocks: &mut Vec<Block>, metadata: &CodeUnitMetadata) {
let predecessors = compute_predecessors(blocks);
let mut clones = Vec::new();
for target in 0..blocks.len() {
let target = BlockIdx(target as u32);
if !is_named_except_cleanup_return_block(&blocks[target.idx()], metadata) {
continue;
}
let layout_pred = find_layout_predecessor(blocks, target);
if layout_pred == BlockIdx::NULL
|| next_nonempty_block(blocks, blocks[layout_pred.idx()].next) != target
{
continue;
}
let fallthroughs_into_target = blocks[layout_pred.idx()]
.instructions
.last()
.map(|ins| !ins.instr.is_scope_exit() && !ins.instr.is_unconditional_jump())
.unwrap_or(true);
if !fallthroughs_into_target || predecessors[target.idx()] < 2 {
continue;
}
for block_idx in 0..blocks.len() {
if block_idx == target.idx() {
continue;
}
let Some(instr_idx) = trailing_conditional_jump_index(&blocks[block_idx]) else {
continue;
};
if next_nonempty_block(blocks, blocks[block_idx].instructions[instr_idx].target)
!= target
{
continue;
}
clones.push((BlockIdx(block_idx as u32), instr_idx, target));
}
}
for (block_idx, instr_idx, target) in clones.into_iter().rev() {
let jump = blocks[block_idx.idx()].instructions[instr_idx];
let mut cloned = blocks[target.idx()].instructions.clone();
if let Some(first) = cloned.first_mut() {
overwrite_location(first, jump.location, jump.end_location);
}
let new_idx = BlockIdx(blocks.len() as u32);
let next = blocks[target.idx()].next;
blocks.push(Block {
cold: blocks[target.idx()].cold,
except_handler: blocks[target.idx()].except_handler,
disable_load_fast_borrow: blocks[target.idx()].disable_load_fast_borrow,
instructions: cloned,
next,
..Block::default()
});
blocks[target.idx()].next = new_idx;
blocks[block_idx.idx()].instructions[instr_idx].target = new_idx;
}
}
fn is_const_return_block(block: &Block) -> bool {
block.instructions.len() == 2
&& matches!(
block.instructions[0].instr.real(),
Some(Instruction::LoadConst { .. })
)
&& matches!(
block.instructions[1].instr.real(),
Some(Instruction::ReturnValue)
)
}
fn inline_pop_except_return_blocks(blocks: &mut [Block]) {
for block_idx in 0..blocks.len() {
let Some(jump_idx) = blocks[block_idx].instructions.len().checked_sub(1) else {
continue;
};
let jump = blocks[block_idx].instructions[jump_idx];
if !jump.instr.is_unconditional_jump() || jump.target == BlockIdx::NULL {
continue;
}
let Some(last_real_before_jump) = blocks[block_idx].instructions[..jump_idx]
.iter()
.rev()
.find_map(|info| info.instr.real())
else {
continue;
};
if !matches!(last_real_before_jump, Instruction::PopExcept) {
continue;
}
let target = next_nonempty_block(blocks, jump.target);
if target == BlockIdx::NULL || !is_const_return_block(&blocks[target.idx()]) {
continue;
}
let mut cloned_return = blocks[target.idx()].instructions.clone();
for instr in &mut cloned_return {
overwrite_location(instr, jump.location, jump.end_location);
}
blocks[block_idx].instructions.pop();
blocks[block_idx].instructions.extend(cloned_return);
}
}
/// Label exception targets: walk CFG with except stack, set per-instruction
/// handler info and block preserve_lasti flag. Converts POP_BLOCK to NOP.
/// flowgraph.c label_exception_targets + push_except_block
+35 -17
View File
@@ -1077,6 +1077,9 @@ impl SymbolTableBuilder {
// Register .type_params as a SET symbol (it will be converted to cell variable later)
self.register_name(".type_params", SymbolUsage::Assigned, TextRange::default())?;
if for_class {
self.register_name(".generic_base", SymbolUsage::Assigned, TextRange::default())?;
}
Ok(())
}
@@ -1263,27 +1266,38 @@ impl SymbolTableBuilder {
is_ann_assign: bool,
) -> SymbolTableResult {
let current_scope = self.tables.last().map(|t| t.typ);
let needs_future_annotation_bookkeeping = is_ann_assign
&& self.future_annotations
&& matches!(
current_scope,
Some(CompilerScope::Module | CompilerScope::Class)
);
let needs_non_future_conditional_annotations = is_ann_assign
&& !self.future_annotations
&& (matches!(current_scope, Some(CompilerScope::Module))
|| (matches!(current_scope, Some(CompilerScope::Class))
&& self.in_conditional_block));
let should_register_conditional_annotations = needs_future_annotation_bookkeeping
|| (needs_non_future_conditional_annotations
&& !self.tables.last().unwrap().has_conditional_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 needs_non_future_conditional_annotations {
self.tables.last_mut().unwrap().has_conditional_annotations = true;
}
if is_conditional && !self.tables.last().unwrap().has_conditional_annotations {
self.tables.last_mut().unwrap().has_conditional_annotations = true;
self.register_name(
"__conditional_annotations__",
SymbolUsage::Assigned,
annotation.range(),
)?;
self.register_name(
"__conditional_annotations__",
SymbolUsage::Used,
annotation.range(),
)?;
}
if should_register_conditional_annotations {
self.register_name(
"__conditional_annotations__",
SymbolUsage::Assigned,
annotation.range(),
)?;
self.register_name(
"__conditional_annotations__",
SymbolUsage::Used,
annotation.range(),
)?;
}
// Create annotation scope for deferred evaluation
@@ -1437,6 +1451,10 @@ impl SymbolTableBuilder {
self.register_name("__qualname__", SymbolUsage::Assigned, *range)?;
self.register_name("__doc__", SymbolUsage::Assigned, *range)?;
self.register_name("__class__", SymbolUsage::Assigned, *range)?;
if type_params.is_some() {
self.register_name(".type_params", SymbolUsage::Used, *range)?;
self.register_name("__type_params__", SymbolUsage::Assigned, *range)?;
}
self.scan_statements(body)?;
self.leave_scope();
self.in_conditional_block = saved_in_conditional;
+2 -1
View File
@@ -663,7 +663,8 @@ impl Py<PyDict> {
) -> PyResult<Option<PyObjectRef>> {
if self.exact_dict(vm) {
self.entries.get(vm, key)
// FIXME: check __missing__?
// Match CPython's exact-dict fast path: __missing__ only participates
// for dict subclasses through the generic mapping lookup path below.
} else {
match self.as_object().get_item(key, vm) {
Ok(value) => Ok(Some(value)),
+23 -22
View File
@@ -2725,16 +2725,7 @@ impl ExecutingFrame<'_> {
let class_dict = self.pop_value();
let idx = i.get(arg).as_usize();
let name = self.localsplus_name(idx);
// Only treat KeyError as "not found", propagate other exceptions
let value = if let Some(dict_obj) = class_dict.downcast_ref::<PyDict>() {
dict_obj.get_item_opt(name, vm)?
} else {
match class_dict.get_item(name, vm) {
Ok(v) => Some(v),
Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => None,
Err(e) => return Err(e),
}
};
let value = self.mapping_get_optional(&class_dict, name, vm)?;
self.push_value(match value {
Some(v) => v,
None => self
@@ -2748,18 +2739,7 @@ impl ExecutingFrame<'_> {
// PEP 649: Pop dict from stack (classdict), check there first, then globals
let dict = self.pop_value();
let name = self.code.names[idx.get(arg) as usize];
// Only treat KeyError as "not found", propagate other exceptions
let value = if let Some(dict_obj) = dict.downcast_ref::<PyDict>() {
dict_obj.get_item_opt(name, vm)?
} else {
// Not an exact dict, use mapping protocol
match dict.get_item(name, vm) {
Ok(v) => Some(v),
Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => None,
Err(e) => return Err(e),
}
};
let value = self.mapping_get_optional(&dict, name, vm)?;
self.push_value(match value {
Some(v) => v,
@@ -6118,6 +6098,27 @@ impl ExecutingFrame<'_> {
}
}
#[inline]
fn mapping_get_optional(
&self,
mapping: &PyObjectRef,
name: &Py<PyStr>,
vm: &VirtualMachine,
) -> PyResult<Option<PyObjectRef>> {
if mapping.class().is(vm.ctx.types.dict_type) {
let dict = mapping
.downcast_ref::<PyDict>()
.expect("exact dict must have a PyDict payload");
dict.get_item_opt(name, vm)
} else {
match mapping.get_item(name, vm) {
Ok(value) => Ok(Some(value)),
Err(err) if err.fast_isinstance(vm.ctx.exceptions.key_error) => Ok(None),
Err(err) => Err(err),
}
}
}
#[inline]
fn load_global_or_builtin(&self, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
if let Some(builtins_dict) = self.builtins_dict {