Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
MATCH_SELF
  • Loading branch information
youknowone committed Aug 26, 2025
commit 8ae2dc75f6d6720b252a462e0cf91ddc1524db36
14 changes: 7 additions & 7 deletions compiler/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use rustpython_compiler_core::{
Mode, OneIndexed, SourceFile, SourceLocation,
bytecode::{
self, Arg as OpArgMarker, BinaryOperator, CodeObject, ComparisonOperator, ConstantData,
Instruction, OpArg, OpArgType, TestOperator, UnpackExArgs,
Instruction, OpArg, OpArgType, UnpackExArgs,
},
};
use rustpython_wtf8::Wtf8Buf;
Expand Down Expand Up @@ -3484,7 +3484,7 @@ impl Compiler {

// Process each sub-pattern.
for subpattern in patterns.iter().chain(kwd_patterns.iter()) {
// Decrement the on_top counter BEFORE processing each sub-pattern
// Decrement the on_top counter before processing each sub-pattern
pc.on_top -= 1;

// Check if this is a true wildcard (underscore pattern without name binding)
Expand Down Expand Up @@ -3557,13 +3557,15 @@ impl Compiler {
// Check if the mapping has at least 'size' keys
emit!(self, Instruction::GetLen);
self.emit_load_const(ConstantData::Integer { value: size.into() });
// Stack: [subject, len, size]
emit!(
self,
Instruction::CompareOperation {
op: ComparisonOperator::GreaterOrEqual
}
);
self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
// Stack: [subject]
}

// Check for overflow (INT_MAX < size - 1)
Expand Down Expand Up @@ -3606,9 +3608,8 @@ impl Compiler {

self.compile_expression(key)?;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// Stack: [subject, key1, key2, ..., keyn]
}
// Stack: [subject] if size==0, or [subject, key1, ..., keyn] if size>0
// Stack: [subject, key1, key2, ..., key_n]

// Build tuple of keys (empty tuple if size==0)
emit!(self, Instruction::BuildTuple { size });
Expand All @@ -3628,7 +3629,7 @@ impl Compiler {
emit!(
self,
Instruction::TestOperation {
op: TestOperator::IsNot
op: bytecode::TestOperator::IsNot
}
);
// Stack: [subject, keys_tuple, values_tuple, bool]
Expand Down Expand Up @@ -3924,8 +3925,7 @@ impl Compiler {
Singleton::False => ConstantData::Boolean { value: false },
Singleton::True => ConstantData::Boolean { value: true },
});
// Compare using the "Is" operator (identity check, not equality).
// This is important: False should not match 0, even though False == 0
// Compare using the "Is" operator.
emit!(
self,
Instruction::TestOperation {
Expand Down
2 changes: 1 addition & 1 deletion vm/src/builtins/bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ impl Constructor for PyBool {
}
}

#[pyclass(with(Constructor, AsNumber, Representable))]
#[pyclass(with(Constructor, AsNumber, Representable), flags(_MATCH_SELF))]
impl PyBool {
#[pymethod]
fn __format__(obj: PyObjectRef, spec: PyStrRef, vm: &VirtualMachine) -> PyResult<String> {
Expand Down
2 changes: 1 addition & 1 deletion vm/src/builtins/bytearray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ impl PyByteArray {
}

#[pyclass(
flags(BASETYPE),
flags(BASETYPE, _MATCH_SELF),
with(
Py,
PyRef,
Expand Down
2 changes: 1 addition & 1 deletion vm/src/builtins/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ impl PyRef<PyBytes> {
}

#[pyclass(
flags(BASETYPE),
flags(BASETYPE, _MATCH_SELF),
with(
Py,
PyRef,
Expand Down
2 changes: 1 addition & 1 deletion vm/src/builtins/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ impl PyDict {
AsMapping,
Representable
),
flags(BASETYPE, MAPPING)
flags(BASETYPE, MAPPING, _MATCH_SELF)
)]
impl PyDict {
#[pyclassmethod]
Expand Down
2 changes: 1 addition & 1 deletion vm/src/builtins/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ fn float_from_string(val: PyObjectRef, vm: &VirtualMachine) -> PyResult<f64> {
}

#[pyclass(
flags(BASETYPE),
flags(BASETYPE, _MATCH_SELF),
with(Comparable, Hashable, Constructor, AsNumber, Representable)
)]
impl PyFloat {
Expand Down
2 changes: 1 addition & 1 deletion vm/src/builtins/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ impl PyInt {
}

#[pyclass(
flags(BASETYPE),
flags(BASETYPE, _MATCH_SELF),
with(PyRef, Comparable, Hashable, Constructor, AsNumber, Representable)
)]
impl PyInt {
Expand Down
2 changes: 1 addition & 1 deletion vm/src/builtins/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub type PyListRef = PyRef<PyList>;
AsSequence,
Representable
),
flags(BASETYPE, SEQUENCE)
flags(BASETYPE, SEQUENCE, _MATCH_SELF)
)]
impl PyList {
#[pymethod]
Expand Down
4 changes: 2 additions & 2 deletions vm/src/builtins/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ fn reduce_set(
AsNumber,
Representable
),
flags(BASETYPE)
flags(BASETYPE, _MATCH_SELF)
)]
impl PySet {
#[pymethod]
Expand Down Expand Up @@ -946,7 +946,7 @@ impl Constructor for PyFrozenSet {
}

#[pyclass(
flags(BASETYPE),
flags(BASETYPE, _MATCH_SELF),
with(
Constructor,
AsSequence,
Expand Down
2 changes: 1 addition & 1 deletion vm/src/builtins/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ impl Py<PyStr> {
}

#[pyclass(
flags(BASETYPE),
flags(BASETYPE, _MATCH_SELF),
with(
PyRef,
AsMapping,
Expand Down
2 changes: 1 addition & 1 deletion vm/src/builtins/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ impl<T> PyTuple<PyRef<T>> {
}

#[pyclass(
flags(BASETYPE, SEQUENCE),
flags(BASETYPE, SEQUENCE, _MATCH_SELF),
with(
AsMapping,
AsSequence,
Expand Down
30 changes: 10 additions & 20 deletions vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,19 +702,17 @@ impl ExecutingFrame<'_> {
}
bytecode::Instruction::Swap { index } => {
let len = self.state.stack.len();
if len == 0 {
return Err(vm.new_runtime_error("stack underflow in SWAP".to_string()));
}
debug_assert!(len > 0, "stack underflow in SWAP");
let i = len - 1; // TOS index
let index_val = index.get(arg) as usize;
// CPython: SWAP(n) swaps TOS with PEEK(n) where PEEK(n) = stack_pointer[-n]
// This means swap TOS with the element at index (len - n)
if index_val > len {
return Err(vm.new_runtime_error(format!(
"SWAP index {} exceeds stack size {}",
index_val, len
)));
}
debug_assert!(
index_val <= len,
"SWAP index {} exceeds stack size {}",
index_val,
len
);
let j = len - index_val;
Comment on lines +703 to 714
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

SWAP index guard is incomplete; add lower-bound check and a release-mode safety path.

SWAP(0) would compute j = len - 0 and panic in release. Compiler should not emit 0, but add a debug lower-bound assertion and a release-mode guard to prevent hard-to-diagnose panics.

Apply this diff:

             let len = self.state.stack.len();
-            debug_assert!(len > 0, "stack underflow in SWAP");
+            debug_assert!(len > 0, "stack underflow in SWAP");
             let i = len - 1; // TOS index
             let index_val = index.get(arg) as usize;
             // CPython: SWAP(n) swaps TOS with PEEK(n) where PEEK(n) = stack_pointer[-n]
             // This means swap TOS with the element at index (len - n)
-            debug_assert!(
-                index_val <= len,
-                "SWAP index {} exceeds stack size {}",
-                index_val,
-                len
-            );
+            debug_assert!(index_val >= 1, "SWAP index must be >= 1");
+            debug_assert!(
+                index_val <= len,
+                "SWAP index {} exceeds stack size {}",
+                index_val,
+                len
+            );
+            if index_val == 0 || index_val > len {
+                self.fatal("SWAP index out of range");
+            }
             let j = len - index_val;
             self.state.stack.swap(i, j);
🤖 Prompt for AI Agents
In vm/src/frame.rs around lines 705–716, the SWAP index check only asserts upper
bound and can produce a release-mode panic for SWAP(0); add a lower-bound check
and an explicit runtime guard: insert debug_assert!(index_val > 0, "SWAP index
must be >= 1, got {}", index_val) and replace the unchecked subtraction with an
explicit runtime check that handles index_val == 0 safely (e.g. return a clear
error or panic with a descriptive message like "invalid SWAP(0)" instead of
allowing len - 0 to underflow), keeping the existing upper-bound assertion.

self.state.stack.swap(i, j);
Ok(None)
Expand Down Expand Up @@ -1433,17 +1431,9 @@ impl ExecutingFrame<'_> {
// No __match_args__, check if this is a type with MATCH_SELF behavior
// For built-in types like bool, int, str, list, tuple, dict, etc.
// they match the subject itself as the single positional argument
let is_match_self_type = cls.is(vm.ctx.types.bool_type)
|| cls.is(vm.ctx.types.int_type)
|| cls.is(vm.ctx.types.float_type)
|| cls.is(vm.ctx.types.str_type)
|| cls.is(vm.ctx.types.bytes_type)
|| cls.is(vm.ctx.types.bytearray_type)
|| cls.is(vm.ctx.types.list_type)
|| cls.is(vm.ctx.types.tuple_type)
|| cls.is(vm.ctx.types.dict_type)
|| cls.is(vm.ctx.types.set_type)
|| cls.is(vm.ctx.types.frozenset_type);
let is_match_self_type = cls
.downcast::<PyType>()
.is_ok_and(|t| t.slots.flags.contains(PyTypeFlags::_MATCH_SELF));

if is_match_self_type {
if nargs_val == 1 {
Expand Down
4 changes: 4 additions & 0 deletions vm/src/types/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ bitflags! {
const HEAPTYPE = 1 << 9;
const BASETYPE = 1 << 10;
const METHOD_DESCRIPTOR = 1 << 17;
// For built-in types that match the subject itself in pattern matching
// (bool, int, float, str, bytes, bytearray, list, tuple, dict, set, frozenset)
// This is not a stable API
const _MATCH_SELF = 1 << 22;
const HAS_DICT = 1 << 40;

#[cfg(debug_assertions)]
Expand Down