diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index e73f4b79ae0..6f072542547 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -326,66 +326,6 @@ impl PyBaseObject { Ok(res) } - /// Return self==value. - #[pymethod] - fn __eq__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - Self::cmp(&zelf, &value, PyComparisonOp::Eq, vm) - } - - /// Return self!=value. - #[pymethod] - fn __ne__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - Self::cmp(&zelf, &value, PyComparisonOp::Ne, vm) - } - - /// Return self PyResult { - Self::cmp(&zelf, &value, PyComparisonOp::Lt, vm) - } - - /// Return self<=value. - #[pymethod] - fn __le__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - Self::cmp(&zelf, &value, PyComparisonOp::Le, vm) - } - - /// Return self>=value. - #[pymethod] - fn __ge__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - Self::cmp(&zelf, &value, PyComparisonOp::Ge, vm) - } - - /// Return self>value. - #[pymethod] - fn __gt__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - Self::cmp(&zelf, &value, PyComparisonOp::Gt, vm) - } - /// Implement setattr(self, name, value). #[pymethod] fn __setattr__( @@ -450,12 +390,6 @@ impl PyBaseObject { } } - /// Return repr(self). - #[pymethod] - fn __repr__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Self::slot_repr(&zelf, vm) - } - #[pyclassmethod] fn __subclasshook__(_args: FuncArgs, vm: &VirtualMachine) -> PyObjectRef { vm.ctx.not_implemented() @@ -590,12 +524,6 @@ impl PyBaseObject { Ok(zelf.get_id() as _) } - /// Return hash(self). - #[pymethod] - fn __hash__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Self::slot_hash(&zelf, vm) - } - #[pymethod] fn __sizeof__(zelf: PyObjectRef) -> usize { zelf.class().slots.basicsize diff --git a/crates/vm/src/builtins/range.rs b/crates/vm/src/builtins/range.rs index ab84c977ccd..fac5206b82d 100644 --- a/crates/vm/src/builtins/range.rs +++ b/crates/vm/src/builtins/range.rs @@ -8,9 +8,9 @@ use crate::{ class::PyClassImpl, common::hash::PyHash, function::{ArgIndex, FuncArgs, OptionalArg, PyComparisonValue}, - protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods}, + protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, types::{ - AsMapping, AsSequence, Comparable, Hashable, IterNext, Iterable, PyComparisonOp, + AsMapping, AsNumber, AsSequence, Comparable, Hashable, IterNext, Iterable, PyComparisonOp, Representable, SelfIter, }, }; @@ -176,6 +176,7 @@ pub fn init(context: &Context) { with( Py, AsMapping, + AsNumber, AsSequence, Hashable, Comparable, @@ -269,11 +270,6 @@ impl PyRange { self.compute_length() } - #[pymethod] - fn __bool__(&self) -> bool { - !self.is_empty() - } - #[pymethod] fn __reduce__(&self, vm: &VirtualMachine) -> (PyTypeRef, PyTupleRef) { let range_parameters: Vec = [&self.start, &self.stop, &self.step] @@ -426,6 +422,19 @@ impl AsSequence for PyRange { } } +impl AsNumber for PyRange { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|number, _vm| { + let zelf = number.obj.downcast_ref::().unwrap(); + Ok(!zelf.is_empty()) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + impl Hashable for PyRange { fn hash(zelf: &Py, vm: &VirtualMachine) -> PyResult { let length = zelf.compute_length(); diff --git a/crates/vm/src/builtins/singletons.rs b/crates/vm/src/builtins/singletons.rs index bdc032cc865..61ab1968a45 100644 --- a/crates/vm/src/builtins/singletons.rs +++ b/crates/vm/src/builtins/singletons.rs @@ -50,12 +50,7 @@ impl Constructor for PyNone { } #[pyclass(with(Constructor, AsNumber, Representable))] -impl PyNone { - #[pymethod] - const fn __bool__(&self) -> bool { - false - } -} +impl PyNone {} impl Representable for PyNone { #[inline] @@ -103,22 +98,27 @@ impl Constructor for PyNotImplemented { } } -#[pyclass(with(Constructor))] +#[pyclass(with(Constructor, AsNumber, Representable))] impl PyNotImplemented { - // TODO: As per https://bugs.python.org/issue35712, using NotImplemented - // in boolean contexts will need to raise a DeprecationWarning in 3.9 - // and, eventually, a TypeError. - #[pymethod] - const fn __bool__(&self) -> bool { - true - } - #[pymethod] fn __reduce__(&self, vm: &VirtualMachine) -> PyStrRef { vm.ctx.names.NotImplemented.to_owned() } } +impl AsNumber for PyNotImplemented { + fn as_number() -> &'static PyNumberMethods { + // TODO: As per https://bugs.python.org/issue35712, using NotImplemented + // in boolean contexts will need to raise a DeprecationWarning in 3.9 + // and, eventually, a TypeError. + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|_number, _vm| Ok(true)), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + impl Representable for PyNotImplemented { #[inline] fn repr(_zelf: &Py, vm: &VirtualMachine) -> PyResult { diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index 70e4e20e405..e0b8009e892 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -10,12 +10,12 @@ use crate::{ convert::{ToPyObject, TransmuteFromObject}, function::{ArgSize, FuncArgs, OptionalArg, PyArithmeticValue, PyComparisonValue}, iter::PyExactSizeIterator, - protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods}, + protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, recursion::ReprGuard, sequence::{OptionalRangeArgs, SequenceExt}, sliceable::{SequenceIndex, SliceableSequenceOp}, types::{ - AsMapping, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable, + AsMapping, AsNumber, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable, PyComparisonOp, Representable, SelfIter, }, utils::collection_repr, @@ -260,7 +260,7 @@ impl PyTuple> { #[pyclass( itemsize = core::mem::size_of::(), flags(BASETYPE, SEQUENCE, _MATCH_SELF), - with(AsMapping, AsSequence, Hashable, Comparable, Iterable, Constructor, Representable) + with(AsMapping, AsNumber, AsSequence, Hashable, Comparable, Iterable, Constructor, Representable) )] impl PyTuple { #[pymethod] @@ -286,11 +286,6 @@ impl PyTuple { PyArithmeticValue::from_option(added.ok()) } - #[pymethod] - const fn __bool__(&self) -> bool { - !self.elements.is_empty() - } - #[pymethod] fn count(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { let mut count: usize = 0; @@ -423,6 +418,19 @@ impl AsSequence for PyTuple { } } +impl AsNumber for PyTuple { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|number, _vm| { + let zelf = number.obj.downcast_ref::().unwrap(); + Ok(!zelf.elements.is_empty()) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + impl Hashable for PyTuple { #[inline] fn hash(zelf: &Py, vm: &VirtualMachine) -> PyResult { diff --git a/crates/vm/src/builtins/weakproxy.rs b/crates/vm/src/builtins/weakproxy.rs index 94c54b5459e..51120c02c01 100644 --- a/crates/vm/src/builtins/weakproxy.rs +++ b/crates/vm/src/builtins/weakproxy.rs @@ -4,11 +4,11 @@ use crate::{ class::PyClassImpl, common::hash::PyHash, function::{OptionalArg, PyComparisonValue, PySetterValue}, - protocol::{PyIter, PyIterReturn, PyMappingMethods, PySequenceMethods}, + protocol::{PyIter, PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, stdlib::builtins::reversed, types::{ - AsMapping, AsSequence, Comparable, Constructor, GetAttr, Hashable, IterNext, Iterable, - PyComparisonOp, Representable, SetAttr, + AsMapping, AsNumber, AsSequence, Comparable, Constructor, GetAttr, Hashable, IterNext, + Iterable, PyComparisonOp, Representable, SetAttr, }, }; use std::sync::LazyLock; @@ -68,6 +68,7 @@ crate::common::static_cell! { SetAttr, Constructor, Comparable, + AsNumber, AsSequence, AsMapping, Representable, @@ -87,11 +88,6 @@ impl PyWeakProxy { self.try_upgrade(vm)?.length(vm) } - #[pymethod] - fn __bool__(&self, vm: &VirtualMachine) -> PyResult { - self.try_upgrade(vm)?.is_true(vm) - } - #[pymethod] fn __bytes__(&self, vm: &VirtualMachine) -> PyResult { self.try_upgrade(vm)?.bytes(vm) @@ -171,6 +167,19 @@ impl SetAttr for PyWeakProxy { } } +impl AsNumber for PyWeakProxy { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: LazyLock = LazyLock::new(|| PyNumberMethods { + boolean: Some(|number, vm| { + let zelf = number.obj.downcast_ref::().unwrap(); + zelf.try_upgrade(vm)?.is_true(vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }); + &AS_NUMBER + } +} + impl Comparable for PyWeakProxy { fn cmp( zelf: &Py, diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 98dc6fd2ed2..4590b62d503 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -34,6 +34,13 @@ pub fn add_operators(class: &'static Py, ctx: &Context) { continue; } + // __getattr__ should only have a wrapper if the type explicitly defines it. + // Unlike __getattribute__, __getattr__ is not present on object by default. + // Both map to TpGetattro, but only __getattribute__ gets a wrapper from the slot. + if def.name == "__getattr__" { + continue; + } + // Get the slot function wrapped in SlotFunc let Some(slot_func) = def.accessor.get_slot_func_with_op(&class.slots, def.op) else { continue; diff --git a/crates/vm/src/stdlib/collections.rs b/crates/vm/src/stdlib/collections.rs index 1249fa9315d..67e8e1f2734 100644 --- a/crates/vm/src/stdlib/collections.rs +++ b/crates/vm/src/stdlib/collections.rs @@ -12,13 +12,13 @@ mod _collections { common::lock::{PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}, function::{KwArgs, OptionalArg, PyComparisonValue}, iter::PyExactSizeIterator, - protocol::{PyIterReturn, PySequenceMethods}, + protocol::{PyIterReturn, PyNumberMethods, PySequenceMethods}, recursion::ReprGuard, sequence::{MutObjectSequenceOp, OptionalRangeArgs}, sliceable::SequenceIndexOp, types::{ - AsSequence, Comparable, Constructor, DefaultConstructor, Initializer, IterNext, - Iterable, PyComparisonOp, Representable, SelfIter, + AsNumber, AsSequence, Comparable, Constructor, DefaultConstructor, Initializer, + IterNext, Iterable, PyComparisonOp, Representable, SelfIter, }, utils::collection_repr, }; @@ -60,6 +60,7 @@ mod _collections { with( Constructor, Initializer, + AsNumber, AsSequence, Comparable, Iterable, @@ -354,11 +355,6 @@ mod _collections { self.borrow_deque().len() } - #[pymethod] - fn __bool__(&self) -> bool { - !self.borrow_deque().is_empty() - } - #[pymethod] fn __add__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.concat(&other, vm) @@ -496,6 +492,19 @@ mod _collections { } } + impl AsNumber for PyDeque { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|number, _vm| { + let zelf = number.obj.downcast_ref::().unwrap(); + Ok(!zelf.borrow_deque().is_empty()) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } + } + impl AsSequence for PyDeque { fn as_sequence() -> &'static PySequenceMethods { static AS_SEQUENCE: PySequenceMethods = PySequenceMethods { diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 5906bc91cd4..295e6fd137d 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -12,8 +12,8 @@ use crate::{ builtins::{PyBytes, PyDict, PyNone, PyStr, PyTuple, PyType, PyTypeRef}, class::StaticType, function::FuncArgs, - protocol::{BufferDescriptor, PyBuffer}, - types::{AsBuffer, Callable, Constructor, Initializer, Representable}, + protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}, + types::{AsBuffer, AsNumber, Callable, Constructor, Initializer, Representable}, vm::thread::with_current_vm, }; use alloc::borrow::Cow; @@ -1772,7 +1772,10 @@ impl AsBuffer for PyCFuncPtr { } } -#[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable, AsBuffer))] +#[pyclass( + flags(BASETYPE), + with(Callable, Constructor, AsNumber, Representable, AsBuffer) +)] impl PyCFuncPtr { // restype getter/setter #[pygetset] @@ -1848,11 +1851,18 @@ impl PyCFuncPtr { .map(|stg| stg.flags.bits()) .unwrap_or(StgInfoFlags::empty().bits()) } +} - // bool conversion - check if function pointer is set - #[pymethod] - fn __bool__(&self) -> bool { - self.get_func_ptr() != 0 +impl AsNumber for PyCFuncPtr { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|number, _vm| { + let zelf = number.obj.downcast_ref::().unwrap(); + Ok(zelf.get_func_ptr() != 0) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER } } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index f564fd1965c..aad21f8fe45 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -261,7 +261,7 @@ impl Initializer for PyCPointer { #[pyclass( flags(BASETYPE, IMMUTABLETYPE), - with(Constructor, Initializer, AsBuffer) + with(Constructor, Initializer, AsNumber, AsBuffer) )] impl PyCPointer { /// Get the pointer value stored in buffer as usize @@ -279,12 +279,6 @@ impl PyCPointer { } } - /// Pointer_bool: returns True if pointer is not NULL - #[pymethod] - fn __bool__(&self) -> bool { - self.get_ptr_value() != 0 - } - /// contents getter - reads address from b_ptr and creates an instance of the pointed-to type #[pygetset] fn contents(zelf: &Py, vm: &VirtualMachine) -> PyResult { @@ -779,6 +773,19 @@ impl PyCPointer { } } +impl AsNumber for PyCPointer { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|number, _vm| { + let zelf = number.obj.downcast_ref::().unwrap(); + Ok(zelf.get_ptr_value() != 0) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + impl AsBuffer for PyCPointer { fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { let stg_info = zelf diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index 7bcfa203b02..17d3aa17ad1 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -1133,14 +1133,6 @@ impl PyCSimple { self.0.base.read().clone() } - /// return True if any byte in buffer is non-zero - #[pymethod] - fn __bool__(&self) -> bool { - let buffer = self.0.buffer.read(); - // Simple_bool: memcmp(self->b_ptr, zeros, self->b_size) - buffer.iter().any(|&b| b != 0) - } - #[pygetset] pub fn value(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { let zelf: &Py = instance diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index f3d8ca10768..400cab44210 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -212,11 +212,6 @@ mod winreg { Ok(old_hkey as usize) } - #[pymethod] - fn __bool__(&self) -> bool { - !self.hkey.load().is_null() - } - #[pymethod] fn __enter__(zelf: PyRef, _vm: &VirtualMachine) -> PyResult> { Ok(zelf) @@ -268,13 +263,9 @@ mod winreg { negative: Some(|_a, vm| PyHkey::unary_fail(vm)), positive: Some(|_a, vm| PyHkey::unary_fail(vm)), absolute: Some(|_a, vm| PyHkey::unary_fail(vm)), - boolean: Some(|a, vm| { - if let Some(a) = a.downcast_ref::() { - Ok(a.__bool__()) - } else { - PyHkey::unary_fail(vm)?; - unreachable!() - } + boolean: Some(|a, _vm| { + let zelf = a.obj.downcast_ref::().unwrap(); + Ok(!zelf.hkey.load().is_null()) }), invert: Some(|_a, vm| PyHkey::unary_fail(vm)), lshift: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 085cfea5cee..d7ead562063 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -601,6 +601,7 @@ impl PyType { /// Update slots based on dunder method changes /// /// Iterates SLOT_DEFS to find all slots matching the given name and updates them. + /// Also recursively updates subclasses that don't have their own definition. pub(crate) fn update_slot(&self, name: &'static PyStrInterned, ctx: &Context) { debug_assert!(name.as_str().starts_with("__")); debug_assert!(name.as_str().ends_with("__")); @@ -609,6 +610,36 @@ impl PyType { for def in find_slot_defs_by_name(name.as_str()) { self.update_one_slot::(&def.accessor, name, ctx); } + + // Recursively update subclasses that don't have their own definition + self.update_subclasses::(name, ctx); + } + + /// Recursively update subclasses' slots + /// recurse_down_subclasses + fn update_subclasses(&self, name: &'static PyStrInterned, ctx: &Context) { + let subclasses = self.subclasses.read(); + for weak_ref in subclasses.iter() { + let Some(subclass) = weak_ref.upgrade() else { + continue; + }; + let Some(subclass) = subclass.downcast_ref::() else { + continue; + }; + + // Skip if subclass has its own definition for this attribute + if subclass.attributes.read().contains_key(name) { + continue; + } + + // Update subclass's slots + for def in find_slot_defs_by_name(name.as_str()) { + subclass.update_one_slot::(&def.accessor, name, ctx); + } + + // Recurse into subclass's subclasses + subclass.update_subclasses::(name, ctx); + } } /// Update a single slot @@ -706,7 +737,44 @@ impl PyType { } } SlotAccessor::TpDel => update_main_slot!(del, del_wrapper, Del), - SlotAccessor::TpGetattro => update_main_slot!(getattro, getattro_wrapper, GetAttro), + SlotAccessor::TpGetattro => { + // __getattribute__ and __getattr__ both map to TpGetattro. + // If __getattr__ is defined anywhere in MRO, we must use the wrapper + // because the native slot won't call __getattr__. + let __getattr__ = identifier!(ctx, __getattr__); + let has_getattr = { + let attrs = self.attributes.read(); + let in_self = attrs.contains_key(__getattr__); + drop(attrs); + // mro[0] is self, so skip it + in_self + || self + .mro + .read() + .iter() + .skip(1) + .any(|cls| cls.attributes.read().contains_key(__getattr__)) + }; + + if has_getattr { + // Must use wrapper to handle __getattr__ + self.slots.getattro.store(Some(getattro_wrapper)); + } else if ADD { + if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| { + if let SlotFunc::GetAttro(f) = sf { + Some(*f) + } else { + None + } + }) { + self.slots.getattro.store(Some(func)); + } else { + self.slots.getattro.store(Some(getattro_wrapper)); + } + } else { + accessor.inherit_from_mro(self); + } + } SlotAccessor::TpSetattro => { // __setattr__ and __delattr__ share the same slot if ADD { diff --git a/crates/vm/src/types/slot_defs.rs b/crates/vm/src/types/slot_defs.rs index 958adb2f27e..1fd493f685e 100644 --- a/crates/vm/src/types/slot_defs.rs +++ b/crates/vm/src/types/slot_defs.rs @@ -986,6 +986,12 @@ pub static SLOT_DEFS: &[SlotDef] = &[ op: None, doc: "Return getattr(self, name).", }, + SlotDef { + name: "__getattr__", + accessor: SlotAccessor::TpGetattro, + op: None, + doc: "Implement getattr(self, name).", + }, SlotDef { name: "__setattr__", accessor: SlotAccessor::TpSetattro, @@ -1419,6 +1425,12 @@ pub static SLOT_DEFS: &[SlotDef] = &[ op: None, doc: "Return self*value.", }, + SlotDef { + name: "__rmul__", + accessor: SlotAccessor::SqRepeat, + op: None, + doc: "Return value*self.", + }, SlotDef { name: "__iadd__", accessor: SlotAccessor::SqInplaceConcat,