mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
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:
4 files changed
+105
-6
No files matched your search
Vendored
-1
@@ -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))
|
||||
|
||||
Vendored
-1
@@ -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>__...
|
||||
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user