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
Next Next commit
Resolve number slots via MRO in PyNumber and operator, ensure inherit…
…ed and dynamically added methods are found.

Use class().mro_find_map() to mimic the same behaviour as CPython.

Signed-off-by: Yash Suthar <yashsuthar983@gmail.com>
  • Loading branch information
YashSuthar983 committed Oct 27, 2025
commit 21c7e159ec5e4f931508309257fcd4c1d94564ca
192 changes: 105 additions & 87 deletions vm/src/protocol/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,117 +443,135 @@ impl<'a> PyNumber<'a> {

// PyNumber_Check
pub fn check(obj: &PyObject) -> bool {
let methods = &obj.class().slots.as_number;
methods.int.load().is_some()
|| methods.index.load().is_some()
|| methods.float.load().is_some()
|| obj.downcastable::<PyComplex>()
let cls = &obj.class();
Comment thread
youknowone marked this conversation as resolved.
let has_number = cls
.mro_find_map(|x| {
let methods = &x.slots.as_number;
if methods.int.load().is_some()
|| methods.index.load().is_some()
|| methods.float.load().is_some()
{
Some(())
} else {
None
}
})
.is_some();
has_number || obj.downcastable::<PyComplex>()
}
}

impl PyNumber<'_> {
// PyIndex_Check
pub fn is_index(self) -> bool {
self.class().slots.as_number.index.load().is_some()
self.class()
.mro_find_map(|x| x.slots.as_number.index.load())
.is_some()
}

#[inline]
pub fn int(self, vm: &VirtualMachine) -> Option<PyResult<PyIntRef>> {
self.class().slots.as_number.int.load().map(|f| {
let ret = f(self, vm)?;

if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) {
return Ok(ret.to_owned());
}

let ret_class = ret.class().to_owned();
if let Some(ret) = ret.downcast_ref::<PyInt>() {
warnings::warn(
vm.ctx.exceptions.deprecation_warning,
format!(
"__int__ returned non-int (type {ret_class}). \
self.class()
.mro_find_map(|x| x.slots.as_number.int.load())
.map(|f| {
let ret = f(self, vm)?;

if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) {
return Ok(ret.to_owned());
}

let ret_class = ret.class().to_owned();
if let Some(ret) = ret.downcast_ref::<PyInt>() {
warnings::warn(
vm.ctx.exceptions.deprecation_warning,
format!(
"__int__ returned non-int (type {ret_class}). \
The ability to return an instance of a strict subclass of int \
is deprecated, and may be removed in a future version of Python."
),
1,
vm,
)?;

Ok(ret.to_owned())
} else {
Err(vm.new_type_error(format!(
"{}.__int__ returned non-int(type {})",
self.class(),
ret_class
)))
}
})
),
1,
vm,
)?;

Ok(ret.to_owned())
} else {
Err(vm.new_type_error(format!(
"{}.__int__ returned non-int(type {})",
self.class(),
ret_class
)))
}
})
}

#[inline]
pub fn index(self, vm: &VirtualMachine) -> Option<PyResult<PyIntRef>> {
self.class().slots.as_number.index.load().map(|f| {
let ret = f(self, vm)?;

if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) {
return Ok(ret.to_owned());
}

let ret_class = ret.class().to_owned();
if let Some(ret) = ret.downcast_ref::<PyInt>() {
warnings::warn(
vm.ctx.exceptions.deprecation_warning,
format!(
"__index__ returned non-int (type {ret_class}). \
self.class()
.mro_find_map(|x| x.slots.as_number.index.load())
.map(|f| {
let ret = f(self, vm)?;

if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) {
return Ok(ret.to_owned());
}

let ret_class = ret.class().to_owned();
if let Some(ret) = ret.downcast_ref::<PyInt>() {
warnings::warn(
vm.ctx.exceptions.deprecation_warning,
format!(
"__index__ returned non-int (type {ret_class}). \
The ability to return an instance of a strict subclass of int \
is deprecated, and may be removed in a future version of Python."
),
1,
vm,
)?;

Ok(ret.to_owned())
} else {
Err(vm.new_type_error(format!(
"{}.__index__ returned non-int(type {})",
self.class(),
ret_class
)))
}
})
),
1,
vm,
)?;

Ok(ret.to_owned())
} else {
Err(vm.new_type_error(format!(
"{}.__index__ returned non-int(type {})",
self.class(),
ret_class
)))
}
})
}

#[inline]
pub fn float(self, vm: &VirtualMachine) -> Option<PyResult<PyRef<PyFloat>>> {
self.class().slots.as_number.float.load().map(|f| {
let ret = f(self, vm)?;

if let Some(ret) = ret.downcast_ref_if_exact::<PyFloat>(vm) {
return Ok(ret.to_owned());
}

let ret_class = ret.class().to_owned();
if let Some(ret) = ret.downcast_ref::<PyFloat>() {
warnings::warn(
vm.ctx.exceptions.deprecation_warning,
format!(
"__float__ returned non-float (type {ret_class}). \
self.class()
.mro_find_map(|x| x.slots.as_number.float.load())
.map(|f| {
let ret = f(self, vm)?;

if let Some(ret) = ret.downcast_ref_if_exact::<PyFloat>(vm) {
return Ok(ret.to_owned());
}

let ret_class = ret.class().to_owned();
if let Some(ret) = ret.downcast_ref::<PyFloat>() {
warnings::warn(
vm.ctx.exceptions.deprecation_warning,
format!(
"__float__ returned non-float (type {ret_class}). \
The ability to return an instance of a strict subclass of float \
is deprecated, and may be removed in a future version of Python."
),
1,
vm,
)?;

Ok(ret.to_owned())
} else {
Err(vm.new_type_error(format!(
"{}.__float__ returned non-float(type {})",
self.class(),
ret_class
)))
}
})
),
1,
vm,
)?;

Ok(ret.to_owned())
} else {
Err(vm.new_type_error(format!(
"{}.__float__ returned non-float(type {})",
self.class(),
ret_class
)))
}
})
}
}

Expand Down
22 changes: 15 additions & 7 deletions vm/src/vm/vm_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,12 @@ impl VirtualMachine {
let class_a = a.class();
let class_b = b.class();

let slot_a = class_a.slots.as_number.left_binary_op(op_slot);
// Look up number slots across MRO for inheritance
let slot_a = class_a.mro_find_map(|x| x.slots.as_number.left_binary_op(op_slot));
let mut slot_b = None;

if !class_a.is(class_b) {
let slot_bb = class_b.slots.as_number.right_binary_op(op_slot);
let slot_bb = class_b.mro_find_map(|x| x.slots.as_number.right_binary_op(op_slot));
if slot_bb.map(|x| x as usize) != slot_a.map(|x| x as usize) {
slot_b = slot_bb;
}
Expand Down Expand Up @@ -230,7 +231,10 @@ impl VirtualMachine {
iop_slot: PyNumberBinaryOp,
op_slot: PyNumberBinaryOp,
) -> PyResult {
if let Some(slot) = a.class().slots.as_number.left_binary_op(iop_slot) {
if let Some(slot) = a
.class()
.mro_find_map(|x| x.slots.as_number.left_binary_op(iop_slot))
{
let x = slot(a, b, self)?;
if !x.is(&self.ctx.not_implemented) {
return Ok(x);
Expand Down Expand Up @@ -266,11 +270,12 @@ impl VirtualMachine {
let class_b = b.class();
let class_c = c.class();

let slot_a = class_a.slots.as_number.left_ternary_op(op_slot);
// Look up number slots across MRO for inheritance
let slot_a = class_a.mro_find_map(|x| x.slots.as_number.left_ternary_op(op_slot));
let mut slot_b = None;

if !class_a.is(class_b) {
let slot_bb = class_b.slots.as_number.right_ternary_op(op_slot);
let slot_bb = class_b.mro_find_map(|x| x.slots.as_number.right_ternary_op(op_slot));
if slot_bb.map(|x| x as usize) != slot_a.map(|x| x as usize) {
slot_b = slot_bb;
}
Expand Down Expand Up @@ -299,7 +304,7 @@ impl VirtualMachine {
}
}

if let Some(slot_c) = class_c.slots.as_number.left_ternary_op(op_slot)
if let Some(slot_c) = class_c.mro_find_map(|x| x.slots.as_number.left_ternary_op(op_slot))
&& slot_a.is_some_and(|slot_a| !std::ptr::fn_addr_eq(slot_a, slot_c))
&& slot_b.is_some_and(|slot_b| !std::ptr::fn_addr_eq(slot_b, slot_c))
{
Expand Down Expand Up @@ -338,7 +343,10 @@ impl VirtualMachine {
op_slot: PyNumberTernaryOp,
op_str: &str,
) -> PyResult {
if let Some(slot) = a.class().slots.as_number.left_ternary_op(iop_slot) {
if let Some(slot) = a
.class()
.mro_find_map(|x| x.slots.as_number.left_ternary_op(iop_slot))
{
let x = slot(a, b, c, self)?;
if !x.is(&self.ctx.not_implemented) {
return Ok(x);
Expand Down