Fix subclass right-op dispatch for Python classes (#7462)

* Fix subclass right-op dispatch for Python classes

* Separate fallback queueing from subclass priority in op dispatch
This commit is contained in:
김은빈 authored and GitHub committed 2026-03-19 09:51:56 +09:00
1 parent 2f181efed8
commit 79e17cb1cf
4 files changed
+105 -6

No files matched your search

-1
View File
@@ -262,7 +262,6 @@ class TestChainMap(unittest.TestCase):
d = c.new_child(b=20, c=30)
self.assertEqual(d.maps, [{'b': 20, 'c': 30}, {'a': 1, 'b': 2}])
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <class 'test.test_collections.TestChainMap.test_union_operators.<locals>.Subclass'> is not <class 'collections.ChainMap'>
def test_union_operators(self):
cm1 = ChainMap(dict(a=1, b=2), dict(c=3, d=4))
cm2 = ChainMap(dict(a=10, e=5), dict(b=20, d=4))
-1
View File
@@ -4318,7 +4318,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
C.__name__ = Nasty("abc")
C.__name__ = "normal"
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_subclass_right_op(self):
# Testing correct dispatch of subclass overloading __r<op>__...
+51
View File
@@ -229,12 +229,63 @@ pub enum PyNumberBinaryOp {
InplaceMatrixMultiply,
}
impl PyNumberBinaryOp {
/// Returns `None` for in-place ops which don't have right-side variants.
pub fn right_method_name(
self,
vm: &VirtualMachine,
) -> Option<&'static crate::builtins::PyStrInterned> {
use PyNumberBinaryOp::*;
Some(match self {
Add => identifier!(vm, __radd__),
Subtract => identifier!(vm, __rsub__),
Multiply => identifier!(vm, __rmul__),
Remainder => identifier!(vm, __rmod__),
Divmod => identifier!(vm, __rdivmod__),
Lshift => identifier!(vm, __rlshift__),
Rshift => identifier!(vm, __rrshift__),
And => identifier!(vm, __rand__),
Xor => identifier!(vm, __rxor__),
Or => identifier!(vm, __ror__),
FloorDivide => identifier!(vm, __rfloordiv__),
TrueDivide => identifier!(vm, __rtruediv__),
MatrixMultiply => identifier!(vm, __rmatmul__),
// In-place ops don't have right-side variants
InplaceAdd
| InplaceSubtract
| InplaceMultiply
| InplaceRemainder
| InplaceLshift
| InplaceRshift
| InplaceAnd
| InplaceXor
| InplaceOr
| InplaceFloorDivide
| InplaceTrueDivide
| InplaceMatrixMultiply => return None,
})
}
}
#[derive(Copy, Clone)]
pub enum PyNumberTernaryOp {
Power,
InplacePower,
}
impl PyNumberTernaryOp {
/// Returns `None` for in-place ops which don't have right-side variants.
pub fn right_method_name(
self,
vm: &VirtualMachine,
) -> Option<&'static crate::builtins::PyStrInterned> {
Some(match self {
PyNumberTernaryOp::Power => identifier!(vm, __rpow__),
PyNumberTernaryOp::InplacePower => return None,
})
}
}
#[derive(Default)]
pub struct PyNumberSlots {
pub add: AtomicCell<Option<PyNumberBinaryFunc>>,
+54 -4
View File
@@ -1,14 +1,32 @@
use super::VirtualMachine;
use crate::stdlib::_warnings;
use crate::{
PyRef,
builtins::{PyInt, PyStr, PyStrRef, PyUtf8Str},
Py, PyRef,
builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyUtf8Str},
object::{AsObject, PyObject, PyObjectRef, PyResult},
protocol::{PyNumberBinaryOp, PyNumberTernaryOp},
types::PyComparisonOp,
};
use num_traits::ToPrimitive;
/// [CPython `method_is_overloaded`](https://github.com/python/cpython/blob/v3.14.3/Objects/typeobject.c#L9849-L9879)
fn method_is_overloaded(
class_a: &Py<PyType>,
class_b: &Py<PyType>,
rop_name: Option<&'static PyStrInterned>,
vm: &VirtualMachine,
) -> PyResult<bool> {
let Some(rop_name) = rop_name else {
return Ok(false);
};
let Some(method_b) = class_b.get_attr(rop_name) else {
return Ok(false);
};
class_a.get_attr(rop_name).map_or(Ok(true), |method_a| {
vm.identical_or_equal(&method_a, &method_b).map(|eq| !eq)
})
}
macro_rules! binary_func {
($fn:ident, $op_slot:ident, $op:expr) => {
pub fn $fn(&self, a: &PyObject, b: &PyObject) -> PyResult {
@@ -162,18 +180,34 @@ impl VirtualMachine {
// Number slots are inherited, direct access is O(1)
let slot_a = class_a.slots.as_number.left_binary_op(op_slot);
let slot_a_addr = slot_a.map(|x| x as usize);
let mut slot_b = None;
let left_b_addr;
if !class_a.is(class_b) {
let slot_bb = class_b.slots.as_number.right_binary_op(op_slot);
if slot_bb.map(|x| x as usize) != slot_a.map(|x| x as usize) {
if slot_bb.map(|x| x as usize) != slot_a_addr {
slot_b = slot_bb;
}
left_b_addr = class_b
.slots
.as_number
.left_binary_op(op_slot)
.map(|x| x as usize);
} else {
left_b_addr = slot_a_addr;
}
if let Some(slot_a) = slot_a {
if let Some(slot_bb) = slot_b
&& class_b.fast_issubclass(class_a)
&& (slot_a_addr != left_b_addr
|| method_is_overloaded(
class_a,
class_b,
op_slot.right_method_name(self),
self,
)?)
{
let ret = slot_bb(a, b, self)?;
if !ret.is(&self.ctx.not_implemented) {
@@ -269,18 +303,34 @@ impl VirtualMachine {
// Number slots are inherited, direct access is O(1)
let slot_a = class_a.slots.as_number.left_ternary_op(op_slot);
let slot_a_addr = slot_a.map(|x| x as usize);
let mut slot_b = None;
let left_b_addr;
if !class_a.is(class_b) {
let slot_bb = class_b.slots.as_number.right_ternary_op(op_slot);
if slot_bb.map(|x| x as usize) != slot_a.map(|x| x as usize) {
if slot_bb.map(|x| x as usize) != slot_a_addr {
slot_b = slot_bb;
}
left_b_addr = class_b
.slots
.as_number
.left_ternary_op(op_slot)
.map(|x| x as usize);
} else {
left_b_addr = slot_a_addr;
}
if let Some(slot_a) = slot_a {
if let Some(slot_bb) = slot_b
&& class_b.fast_issubclass(class_a)
&& (slot_a_addr != left_b_addr
|| method_is_overloaded(
class_a,
class_b,
op_slot.right_method_name(self),
self,
)?)
{
let ret = slot_bb(a, b, c, self)?;
if !ret.is(&self.ctx.not_implemented) {