diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 26d0dcb654..7a57ba722e 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -262,7 +262,6 @@ def __contains__(self, key): 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: .Subclass'> is not 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)) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 1f7c5452c4..3a66cdcdea 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4318,7 +4318,6 @@ class C: C.__name__ = Nasty("abc") C.__name__ = "normal" - @unittest.expectedFailure # TODO: RUSTPYTHON def test_subclass_right_op(self): # Testing correct dispatch of subclass overloading __r__... diff --git a/crates/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs index 36dbd5b884..77e57293f1 100644 --- a/crates/vm/src/protocol/number.rs +++ b/crates/vm/src/protocol/number.rs @@ -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>, diff --git a/crates/vm/src/vm/vm_ops.rs b/crates/vm/src/vm/vm_ops.rs index d5c70f8738..abce0ea678 100644 --- a/crates/vm/src/vm/vm_ops.rs +++ b/crates/vm/src/vm/vm_ops.rs @@ -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, + class_b: &Py, + rop_name: Option<&'static PyStrInterned>, + vm: &VirtualMachine, +) -> PyResult { + 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) {