Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions Lib/_opcode_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,6 @@
'SETUP_FINALLY': 264,
'SETUP_WITH': 265,
'STORE_FAST_MAYBE_NULL': 266,
'LOAD_ATTR_METHOD': 267,
'LOAD_SUPER_METHOD': 268,
'LOAD_ZERO_SUPER_ATTR': 269,
'LOAD_ZERO_SUPER_METHOD': 270,
}

# CPython 3.13 compatible: opcodes < 44 have no argument
Expand Down
65 changes: 53 additions & 12 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use rustpython_compiler_core::{
self, AnyInstruction, Arg as OpArgMarker, BinaryOperator, BuildSliceArgCount, CodeObject,
ComparisonOperator, ConstantData, ConvertValueOparg, Instruction, IntrinsicFunction1,
Invert, OpArg, OpArgType, PseudoInstruction, SpecialMethod, UnpackExArgs,
encode_load_attr_arg, encode_load_super_attr_arg,
},
};
use rustpython_wtf8::Wtf8Buf;
Expand Down Expand Up @@ -2108,7 +2109,7 @@ impl Compiler {
if let Some(alias) = &name.asname {
for part in name.name.split('.').skip(1) {
let idx = self.name(part);
emit!(self, Instruction::LoadAttr { idx });
self.emit_load_attr(idx);
}
self.store_name(alias.as_str())?
} else {
Expand Down Expand Up @@ -6280,7 +6281,7 @@ impl Compiler {
self.compile_expression(value)?;
emit!(self, Instruction::Copy { index: 1_u32 });
let idx = self.name(attr);
emit!(self, Instruction::LoadAttr { idx });
self.emit_load_attr(idx);
AugAssignKind::Attr { idx }
}
_ => {
Expand Down Expand Up @@ -6615,19 +6616,17 @@ impl Compiler {
let idx = self.name(attr.as_str());
match super_type {
SuperCallType::TwoArg { .. } => {
// LoadSuperAttr (pseudo) - will be converted to real LoadSuperAttr
// with flags=0b10 (has_class=true, load_method=false) in ir.rs
emit!(self, Instruction::LoadSuperAttr { arg: idx });
self.emit_load_super_attr(idx);
}
SuperCallType::ZeroArg => {
emit!(self, PseudoInstruction::LoadZeroSuperAttr { idx });
self.emit_load_zero_super_attr(idx);
}
}
} else {
// Normal attribute access
self.compile_expression(value)?;
let idx = self.name(attr.as_str());
emit!(self, Instruction::LoadAttr { idx });
self.emit_load_attr(idx);
}
}
ast::Expr::Compare(ast::ExprCompare {
Expand Down Expand Up @@ -7070,19 +7069,19 @@ impl Compiler {
let idx = self.name(attr.as_str());
match super_type {
SuperCallType::TwoArg { .. } => {
emit!(self, PseudoInstruction::LoadSuperMethod { idx });
self.emit_load_super_method(idx);
}
SuperCallType::ZeroArg => {
emit!(self, PseudoInstruction::LoadZeroSuperMethod { idx });
self.emit_load_zero_super_method(idx);
}
}
self.codegen_call_helper(0, args)?;
} else {
// Normal method call: compile object, then LOAD_ATTR_METHOD
// LOAD_ATTR_METHOD pushes [method, self_or_null] on stack
// Normal method call: compile object, then LOAD_ATTR with method flag
// LOAD_ATTR(method=1) pushes [method, self_or_null] on stack
self.compile_expression(value)?;
let idx = self.name(attr.as_str());
emit!(self, PseudoInstruction::LoadAttrMethod { idx });
self.emit_load_attr_method(idx);
self.codegen_call_helper(0, args)?;
}
} else {
Expand Down Expand Up @@ -7782,6 +7781,48 @@ impl Compiler {
emit!(self, Instruction::ReturnValue)
}

/// Emit LOAD_ATTR for attribute access (method=false).
/// Encodes: (name_idx << 1) | 0
fn emit_load_attr(&mut self, name_idx: u32) {
let encoded = encode_load_attr_arg(name_idx, false);
self.emit_arg(encoded, |arg| Instruction::LoadAttr { idx: arg })
}

/// Emit LOAD_ATTR with method flag set (for method calls).
/// Encodes: (name_idx << 1) | 1
fn emit_load_attr_method(&mut self, name_idx: u32) {
let encoded = encode_load_attr_arg(name_idx, true);
self.emit_arg(encoded, |arg| Instruction::LoadAttr { idx: arg })
}

/// Emit LOAD_SUPER_ATTR for 2-arg super().attr access.
/// Encodes: (name_idx << 2) | 0b10 (method=0, class=1)
fn emit_load_super_attr(&mut self, name_idx: u32) {
let encoded = encode_load_super_attr_arg(name_idx, false, true);
self.emit_arg(encoded, |arg| Instruction::LoadSuperAttr { arg })
}

/// Emit LOAD_SUPER_ATTR for 2-arg super().method() call.
/// Encodes: (name_idx << 2) | 0b11 (method=1, class=1)
fn emit_load_super_method(&mut self, name_idx: u32) {
let encoded = encode_load_super_attr_arg(name_idx, true, true);
self.emit_arg(encoded, |arg| Instruction::LoadSuperAttr { arg })
}

/// Emit LOAD_SUPER_ATTR for 0-arg super().attr access.
/// Encodes: (name_idx << 2) | 0b00 (method=0, class=0)
fn emit_load_zero_super_attr(&mut self, name_idx: u32) {
let encoded = encode_load_super_attr_arg(name_idx, false, false);
self.emit_arg(encoded, |arg| Instruction::LoadSuperAttr { arg })
}

/// Emit LOAD_SUPER_ATTR for 0-arg super().method() call.
/// Encodes: (name_idx << 2) | 0b01 (method=1, class=0)
fn emit_load_zero_super_method(&mut self, name_idx: u32) {
let encoded = encode_load_super_attr_arg(name_idx, true, false);
self.emit_arg(encoded, |arg| Instruction::LoadSuperAttr { arg })
}

fn emit_return_value(&mut self) {
emit!(self, Instruction::ReturnValue)
}
Expand Down
55 changes: 4 additions & 51 deletions crates/codegen/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ use rustpython_compiler_core::{
bytecode::{
AnyInstruction, Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData,
ExceptionTableEntry, InstrDisplayContext, Instruction, InstructionMetadata, Label, OpArg,
PseudoInstruction, PyCodeLocationInfoKind, encode_exception_table, encode_load_attr_arg,
encode_load_super_attr_arg,
PseudoInstruction, PyCodeLocationInfoKind, encode_exception_table,
},
varint::{write_signed_varint, write_varint},
};
Expand Down Expand Up @@ -207,62 +206,16 @@ impl CodeInfo {
.filter(|b| b.next != BlockIdx::NULL || !b.instructions.is_empty())
{
for info in &mut block.instructions {
// Special case for:
// - `Instruction::LoadAttr`
// - `Instruction::LoadSuperAttr`

if let Some(instr) = info.instr.real() {
match instr {
// LOAD_ATTR → encode with method flag=0
Instruction::LoadAttr { idx } => {
let encoded = encode_load_attr_arg(idx.get(info.arg), false);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadAttr { idx: Arg::marker() }.into();
}
// LOAD_SUPER_ATTR → encode with flags=0b10 (method=0, class=1)
Instruction::LoadSuperAttr { arg: idx } => {
let encoded =
encode_load_super_attr_arg(idx.get(info.arg), false, true);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
}
_ => {}
}

// Real instructions are already encoded by compile.rs
let Some(instr) = info.instr.pseudo() else {
continue;
}

let instr = info.instr.expect_pseudo();
};

match instr {
// LOAD_ATTR_METHOD pseudo → LOAD_ATTR (with method flag=1)
PseudoInstruction::LoadAttrMethod { idx } => {
let encoded = encode_load_attr_arg(idx.get(info.arg), true);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadAttr { idx: Arg::marker() }.into();
}
// POP_BLOCK pseudo → NOP
PseudoInstruction::PopBlock => {
info.instr = Instruction::Nop.into();
}
// LOAD_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b11: method=1, class=1)
PseudoInstruction::LoadSuperMethod { idx } => {
let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, true);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
}
// LOAD_ZERO_SUPER_ATTR pseudo → LOAD_SUPER_ATTR (flags=0b00: method=0, class=0)
PseudoInstruction::LoadZeroSuperAttr { idx } => {
let encoded = encode_load_super_attr_arg(idx.get(info.arg), false, false);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
}
// LOAD_ZERO_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b01: method=1, class=0)
PseudoInstruction::LoadZeroSuperMethod { idx } => {
let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, false);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
}
// LOAD_CLOSURE pseudo → LOAD_FAST (with varnames offset)
PseudoInstruction::LoadClosure(idx) => {
let varnames_len = varname_cache.len() as u32;
Expand Down
51 changes: 15 additions & 36 deletions crates/compiler-core/src/bytecode/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ impl InstructionMetadata for Instruction {
| Self::JumpForward { target: l }
| Self::PopJumpIfTrue { target: l }
| Self::PopJumpIfFalse { target: l }
| Self::PopJumpIfNone { target: l }
| Self::PopJumpIfNotNone { target: l }
| Self::ForIter { target: l }
| Self::Send { target: l } => Some(*l),
_ => None,
Expand Down Expand Up @@ -472,7 +474,13 @@ impl InstructionMetadata for Instruction {
Self::LoadFromDictOrDeref(_) => 1,
Self::StoreSubscr => -3,
Self::DeleteSubscr => -2,
Self::LoadAttr { .. } => 0,
Self::LoadAttr { idx } => {
// Stack effect depends on method flag in encoded oparg
// method=false: pop obj, push attr → effect = 0
// method=true: pop obj, push (method, self_or_null) → effect = +1
let (_, is_method) = decode_load_attr_arg(idx.get(arg));
if is_method { 1 } else { 0 }
}
Comment on lines 474 to +483
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate all LoadAttr construction sites and encode/decode usage
echo "=== LoadAttr construction/usage sites ==="
rg -n -C2 --type=rs 'LoadAttr\s*\{|Instruction::LoadAttr\b' crates/

echo -e "\n=== encode_load_attr_arg and decode_load_attr_arg usage ==="
rg -n -C2 --type=rs 'encode_load_attr_arg|decode_load_attr_arg' crates/

Repository: RustPython/RustPython

Length of output: 238


🏁 Script executed:

#!/bin/bash
# Locate all LoadAttr construction sites and encode/decode usage
echo "=== LoadAttr construction/usage sites ==="
rg -n -C2 'LoadAttr\s*\{|Instruction::LoadAttr' crates/

echo -e "\n=== encode_load_attr_arg and decode_load_attr_arg usage ==="
rg -n -C2 'encode_load_attr_arg|decode_load_attr_arg' crates/

echo -e "\n=== Files containing these terms ==="
rg -l 'LoadAttr|encode_load_attr_arg|decode_load_attr_arg' crates/

Repository: RustPython/RustPython

Length of output: 7418


🏁 Script executed:

#!/bin/bash
# Search for any direct LoadAttr construction without going through emit functions
echo "=== Search for direct LoadAttr { idx construction (potential bypass) ==="
rg -n 'LoadAttr\s*\{\s*idx\s*:' crates/

echo -e "\n=== Check emit_load_attr calls to confirm all usages go through encoding ==="
rg -n 'emit_load_attr' crates/

echo -e "\n=== Check Arg type definition ==="
rg -n 'pub struct Arg' crates/ -A 5

echo -e "\n=== Check if there are any other places calling Instruction::LoadAttr ==="
rg -n 'Instruction::LoadAttr' crates/ -C 3

Repository: RustPython/RustPython

Length of output: 8611


🏁 Script executed:

#!/bin/bash
# Get more context on Arg<T> and OpArgType definition
echo "=== Arg<T> definition and usage ==="
rg -n 'impl.*Arg<' crates/compiler-core/src/bytecode/instruction.rs -A 10 | head -50

echo -e "\n=== OpArgType trait definition ==="
rg -n 'pub trait OpArgType|pub enum OpArgType' crates/ -A 5

echo -e "\n=== NameIdx type definition ==="
rg -n 'type NameIdx|pub.*NameIdx' crates/compiler-core/src/bytecode/instruction.rs | head -10

echo -e "\n=== LoadAttr field type vs actual usage ==="
sed -n '167,169p' crates/compiler-core/src/bytecode/instruction.rs
echo "---"
sed -n '842,846p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 1779


🏁 Script executed:

#!/bin/bash
# Search for NameIdx definition
echo "=== NameIdx definition ==="
rg -n 'type NameIdx\s*=|pub.*NameIdx\s*=' crates/ -A 2

echo -e "\n=== Arg.get method implementation ==="
rg -n 'pub fn get\|pub const fn get' crates/compiler-core/src/bytecode/instruction.rs -A 8

echo -e "\n=== How idx.get is used in all LoadAttr contexts ==="
rg -n 'idx\.get\(' crates/ -B 2 -A 1

echo -e "\n=== Check if NameIdx implements OpArgType ==="
rg -n 'impl.*OpArgType.*NameIdx\|NameIdx.*OpArgType' crates/

Repository: RustPython/RustPython

Length of output: 13278


🏁 Script executed:

#!/bin/bash
# Find the .get() method on Arg<T>
echo "=== Search for .get method on Arg<T> ==="
rg -n '\.get\s*\(' crates/compiler-core/src/bytecode/instruction.rs -B 5 | head -40

echo -e "\n=== Full Arg implementation ==="
sed -n '1177,1230p' crates/compiler-core/src/bytecode/instruction.rs

echo -e "\n=== Check for OpArgType impl for NameIdx/u32 ==="
rg -n 'impl.*OpArgType' crates/compiler-core/src/bytecode/oparg.rs -A 10 | head -80

Repository: RustPython/RustPython

Length of output: 5623


Type safety for encoded LOAD_ATTR args is currently correct but could be improved.

All 2 emission sites (emit_load_attr, emit_load_attr_method) properly use encode_load_attr_arg, and all 3 consumption sites (stack_effect, disassembly, load_attr runtime) properly decode before using the name index. However, the field type Arg<NameIdx> is misleading since it contains an encoded value, not a raw name index. Since Arg<u32>.get() just returns the raw u32 without decoding, a future maintainer could naively use it as a direct index. Consider introducing a dedicated EncodedLoadAttrArg type implementing OpArgType to make the encoding requirement explicit in the type system.

🤖 Prompt for AI Agents
In `@crates/compiler-core/src/bytecode/instruction.rs` around lines 474 - 483, The
Arg<NameIdx> used for the LoadAttr operand currently holds an encoded value
which is confusing and error-prone; introduce a new OpArgType called
EncodedLoadAttrArg and replace uses of Arg<NameIdx> for LoadAttr so the type
signals that decoding is required. Update the emitter sites (emit_load_attr,
emit_load_attr_method) to produce EncodedLoadAttrArg (using
encode_load_attr_arg), update consumers (stack_effect implementation for
LoadAttr, disassembly, and the load_attr runtime) to accept EncodedLoadAttrArg
and call decode_load_attr_arg before extracting the name index, and adjust any
trait impls or type aliases so Arg<EncodedLoadAttrArg> integrates with the
existing OpArgType system.

Self::StoreAttr { .. } => -2,
Self::DeleteAttr { .. } => -1,
Self::LoadCommonConstant { .. } => 1,
Expand Down Expand Up @@ -921,47 +929,22 @@ impl InstructionMetadata for Instruction {

/// Instructions used by the compiler. They are not executed by the VM.
///
/// CPython 3.14.2 aligned (256-266), RustPython-specific variants start at 267.
/// CPython 3.14.2 aligned (256-266).
#[derive(Clone, Copy, Debug)]
#[repr(u16)]
pub enum PseudoInstruction {
// CPython 3.14.2 pseudo instructions (256-266)
AnnotationsPlaceholder = 256,
Jump {
target: Arg<Label>,
} = 257,
JumpIfFalse {
target: Arg<Label>,
} = 258,
JumpIfTrue {
target: Arg<Label>,
} = 259,
JumpNoInterrupt {
target: Arg<Label>,
} = 260,
Jump { target: Arg<Label> } = 257,
JumpIfFalse { target: Arg<Label> } = 258,
JumpIfTrue { target: Arg<Label> } = 259,
JumpNoInterrupt { target: Arg<Label> } = 260,
LoadClosure(Arg<NameIdx>) = 261,
PopBlock = 262,
SetupCleanup = 263,
SetupFinally = 264,
SetupWith = 265,
StoreFastMaybeNull(Arg<NameIdx>) = 266,

// RustPython-specific pseudo instructions (267+)
LoadAttrMethod {
idx: Arg<NameIdx>,
} = 267,
// "Zero" variants are for 0-arg super() calls (has_class=false).
// Non-"Zero" variants are for 2-arg super(cls, self) calls (has_class=true).
/// 2-arg super(cls, self).method() - has_class=true, load_method=true
LoadSuperMethod {
idx: Arg<NameIdx>,
} = 268,
LoadZeroSuperAttr {
idx: Arg<NameIdx>,
} = 269,
LoadZeroSuperMethod {
idx: Arg<NameIdx>,
} = 270,
}

const _: () = assert!(mem::size_of::<PseudoInstruction>() == 2);
Expand All @@ -980,7 +963,7 @@ impl TryFrom<u16> for PseudoInstruction {
#[inline]
fn try_from(value: u16) -> Result<Self, MarshalError> {
let start = u16::from(Self::AnnotationsPlaceholder);
let end = u16::from(Self::LoadZeroSuperMethod { idx: Arg::marker() });
let end = u16::from(Self::StoreFastMaybeNull(Arg::marker()));

if (start..=end).contains(&value) {
Ok(unsafe { mem::transmute::<u16, Self>(value) })
Expand Down Expand Up @@ -1022,10 +1005,6 @@ impl InstructionMetadata for PseudoInstruction {
Self::SetupFinally => 0,
Self::SetupWith => 0,
Self::StoreFastMaybeNull(_) => -1,
Self::LoadAttrMethod { .. } => 1, // pop obj, push method + self_or_null
Self::LoadSuperMethod { .. } => -3 + 2, // pop 3, push [method, self_or_null]
Self::LoadZeroSuperAttr { .. } => -3 + 1, // pop 3, push [attr]
Self::LoadZeroSuperMethod { .. } => -3 + 2, // pop 3, push [method, self_or_null]
}
}

Expand Down
25 changes: 20 additions & 5 deletions crates/stdlib/src/_opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ mod _opcode {
}

impl Opcode {
// https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/opcode_ids.h#L238
const HAVE_ARGUMENT: i32 = 44;
// https://github.com/python/cpython/blob/v3.14.2/Include/opcode_ids.h#L252
const HAVE_ARGUMENT: i32 = 43;

pub fn try_from_pyint(raw: PyIntRef, vm: &VirtualMachine) -> PyResult<Self> {
let instruction = raw
Expand Down Expand Up @@ -92,7 +92,7 @@ mod _opcode {
| Instruction::StoreAttr { .. }
| Instruction::StoreGlobal(_)
| Instruction::StoreName(_)
) | AnyInstruction::Pseudo(PseudoInstruction::LoadAttrMethod { .. }))
))
)
}

Expand Down Expand Up @@ -148,8 +148,11 @@ mod _opcode {
}
}

// prepare specialization
#[pyattr]
const ENABLE_SPECIALIZATION: i8 = 1;
#[allow(dead_code)]
const ENABLE_SPECIALIZATION_FT: i8 = 1;

#[derive(FromArgs)]
struct StackEffectArgs {
Expand Down Expand Up @@ -303,6 +306,7 @@ mod _opcode {
("NB_INPLACE_SUBTRACT", "-="),
("NB_INPLACE_TRUE_DIVIDE", "/="),
("NB_INPLACE_XOR", "^="),
("NB_SUBSCR", "[]"),
]
.into_iter()
.map(|(a, b)| {
Expand All @@ -314,8 +318,19 @@ mod _opcode {
}

#[pyfunction]
fn get_executor(_code: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
// TODO
fn get_special_method_names(vm: &VirtualMachine) -> Vec<PyObjectRef> {
["__enter__", "__exit__", "__aenter__", "__aexit__"]
.into_iter()
.map(|x| vm.ctx.new_str(x).into())
.collect()
}
Comment on lines 320 to +326
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n crates/stdlib/src/_opcode.rs | sed -n '310,330p'

Repository: RustPython/RustPython

Length of output: 754


🏁 Script executed:

cd crates/stdlib && cargo clippy --all-targets 2>&1 | head -100

Repository: RustPython/RustPython

Length of output: 174


🏁 Script executed:

rg "get_special_method_names\|__enter__\|__exit__" crates/stdlib/src/_opcode.rs -A 2 -B 2

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

rg "get_intrinsic1_descs|get_intrinsic2_descs" crates/stdlib/src/_opcode.rs -A 6

Repository: RustPython/RustPython

Length of output: 639


🏁 Script executed:

rg "class.*Manager|__enter__|__exit__|__aenter__|__aexit__" --type rs | head -30

Repository: RustPython/RustPython

Length of output: 91


🏁 Script executed:

cat -n crates/stdlib/src/_opcode.rs | sed -n '1,50p'

Repository: RustPython/RustPython

Length of output: 1893


🏁 Script executed:

rg "get_special_method_names" crates/stdlib/

Repository: RustPython/RustPython

Length of output: 168


🏁 Script executed:

rg "__enter__|__exit__|__aenter__|__aexit__" crates/stdlib/src/_opcode.rs

Repository: RustPython/RustPython

Length of output: 125


🏁 Script executed:

git log --oneline -10 crates/stdlib/src/_opcode.rs

Repository: RustPython/RustPython

Length of output: 99


🏁 Script executed:

find crates -name "*test*" -type f | xargs grep -l "special_method\|__enter__\|__exit__" 2>/dev/null | head -5

Repository: RustPython/RustPython

Length of output: 159


🏁 Script executed:

rg "def get_special_method_names|special_method" crates/stdlib/src/_opcode.rs -B 5 -A 10

Repository: RustPython/RustPython

Length of output: 446


🏁 Script executed:

cat -n crates/stdlib/src/_opcode.rs | sed -n '320,350p'

Repository: RustPython/RustPython

Length of output: 776


🌐 Web query:

CPython _opcode module get_special_method_names function

💡 Result:

In CPython, _opcode.get_special_method_names() is a CPython-internal helper that returns a Python list of the interpreter’s “special method” (dunder) names.

Implementation (CPython 3.14): it allocates a list of length SPECIAL_MAX + 1 and fills it with _Py_SpecialMethods[i].name for i = 0..SPECIAL_MAX. The returned list index corresponds to CPython’s internal “special method” id. [1]

API docstring in the generated clinic header: “Return a list of special method names.” [2]

Use:

import _opcode
names = _opcode.get_special_method_names()

Sources: [1] [2]


🏁 Script executed:

rg "ASYNC_WITH|WITH_CLEANUP|context.*manager" crates/ --type-list | head -10

Repository: RustPython/RustPython

Length of output: 454


🏁 Script executed:

cat -n crates/stdlib/src/_opcode.rs | sed -n '280,330p'

Repository: RustPython/RustPython

Length of output: 2069


Incomplete implementation: Returns only context manager special methods, not all special methods.

The function syntactically follows the established pattern (array → .into_iter().map().collect()), but does not match CPython's get_special_method_names(). CPython's implementation returns a comprehensive list of all special method names (indexed from an internal _Py_SpecialMethods array), while this implementation returns only the four context manager methods: __enter__, __exit__, __aenter__, __aexit__. This implementation is incomplete relative to CPython and may cause compatibility issues if callers expect the full set of special method names.

🤖 Prompt for AI Agents
In `@crates/stdlib/src/_opcode.rs` around lines 320 - 326, The current
get_special_method_names function only returns the four context-manager names;
replace or extend that array to match CPython's full _Py_SpecialMethods list so
callers receive the complete set of special method names. Locate or create a
static Rust slice/const (e.g., SPECIAL_METHODS or _PY_SPECIAL_METHODS)
containing all CPython special method strings, then change
get_special_method_names to iterate over that slice (using the existing
.into_iter().map(|x| vm.ctx.new_str(x).into()).collect()) to produce the
Vec<PyObjectRef>; ensure the symbol name get_special_method_names remains
unchanged and the returned type stays Vec<PyObjectRef>.


#[pyfunction]
fn get_executor(
_code: PyObjectRef,
_offset: i32,
vm: &VirtualMachine,
) -> PyResult<PyObjectRef> {
Ok(vm.ctx.none())
}

Expand Down
Loading