diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index a8e266cde..8aed20c34 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -564,10 +564,15 @@ where let slot_name = slot_ident.to_string(); let tokens = { const NON_ATOMIC_SLOTS: &[&str] = &["as_buffer"]; + const POINTER_SLOTS: &[&str] = &["as_number"]; if NON_ATOMIC_SLOTS.contains(&slot_name.as_str()) { quote_spanned! { span => slots.#slot_ident = Some(Self::#ident as _); } + } else if POINTER_SLOTS.contains(&slot_name.as_str()) { + quote_spanned! { span => + slots.#slot_ident.store(Some(PointerSlot::from(Self::#ident()))); + } } else { quote_spanned! { span => slots.#slot_ident.store(Some(Self::#ident as _)); diff --git a/stdlib/src/bisect.rs b/stdlib/src/bisect.rs index 66084c068..0caab7c1d 100644 --- a/stdlib/src/bisect.rs +++ b/stdlib/src/bisect.rs @@ -25,7 +25,7 @@ mod _bisect { vm: &VirtualMachine, ) -> PyResult> { arg.into_option() - .map(|v| vm.to_index(&v)?.try_to_primitive(vm)) + .map(|v| v.try_index(vm)?.try_to_primitive(vm)) .transpose() } diff --git a/stdlib/src/math.rs b/stdlib/src/math.rs index 08f450323..d4ad0f55c 100644 --- a/stdlib/src/math.rs +++ b/stdlib/src/math.rs @@ -199,7 +199,7 @@ mod math { #[pyfunction] fn isqrt(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let index = vm.to_index(&x)?; + let index = x.try_index(vm)?; let value = index.as_bigint(); if value.is_negative() { diff --git a/stdlib/src/select.rs b/stdlib/src/select.rs index 29a053832..1c72c7fd9 100644 --- a/stdlib/src/select.rs +++ b/stdlib/src/select.rs @@ -347,7 +347,7 @@ mod decl { Some(ms) => { let ms = if let Some(float) = ms.payload::() { float.to_f64().to_i32() - } else if let Some(int) = vm.to_index_opt(ms.clone()) { + } else if let Some(int) = ms.try_index_opt(vm) { int?.as_bigint().to_i32() } else { return Err(vm.new_type_error(format!( diff --git a/stdlib/src/socket.rs b/stdlib/src/socket.rs index 729363d2b..313c55ca0 100644 --- a/stdlib/src/socket.rs +++ b/stdlib/src/socket.rs @@ -734,8 +734,8 @@ mod _socket { if obj.fast_isinstance(vm.ctx.types.float_type) { return Err(vm.new_type_error("integer argument expected, got float".to_owned())); } - let int = vm - .to_index_opt(obj) + let int = obj + .try_index_opt(vm) .unwrap_or_else(|| Err(vm.new_type_error("an integer is required".to_owned())))?; int.try_to_primitive::(vm) .map(|sock| sock as RawSocket) @@ -1382,7 +1382,7 @@ mod _socket { let (flags, address) = match arg3 { OptionalArg::Present(arg3) => { // should just be i32::try_from_obj but tests check for error message - let int = vm.to_index_opt(arg2).unwrap_or_else(|| { + let int = arg2.try_index_opt(vm).unwrap_or_else(|| { Err(vm.new_type_error("an integer is required".to_owned())) })?; let flags = int.try_to_primitive::(vm)?; diff --git a/vm/src/buffer.rs b/vm/src/buffer.rs index f2e05e828..f053c510c 100644 --- a/vm/src/buffer.rs +++ b/vm/src/buffer.rs @@ -491,15 +491,15 @@ fn get_int_or_index(vm: &VirtualMachine, arg: PyObjectRef) -> PyResult where T: PrimInt + for<'a> TryFrom<&'a BigInt>, { - match vm.to_index_opt(arg) { - Some(index) => index? - .try_to_primitive(vm) - .map_err(|_| new_struct_error(vm, "argument out of range".to_owned())), - None => Err(new_struct_error( + let index = arg.try_index_opt(vm).unwrap_or_else(|| { + Err(new_struct_error( vm, "required argument is not an integer".to_owned(), - )), - } + )) + })?; + index + .try_to_primitive(vm) + .map_err(|_| new_struct_error(vm, "argument out of range".to_owned())) } make_pack_primint!(i8); diff --git a/vm/src/builtins/bytes.rs b/vm/src/builtins/bytes.rs index 733504dc2..ff1d3d7b7 100644 --- a/vm/src/builtins/bytes.rs +++ b/vm/src/builtins/bytes.rs @@ -3,6 +3,7 @@ use super::{ }; use crate::{ anystr::{self, AnyStr}, + atomic_func, bytesinner::{ bytes_decode, ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerSplitOptions, ByteInnerTranslateOptions, DecodeArgs, PyBytesInner, @@ -608,14 +609,17 @@ impl AsSequence for PyBytes { } impl AsNumber for PyBytes { - const AS_NUMBER: PyNumberMethods = PyNumberMethods { - remainder: Some(|number, other, vm| { - Self::number_downcast(number) - .mod_(other.to_owned(), vm) - .to_pyresult(vm) - }), - ..PyNumberMethods::NOT_IMPLEMENTED - }; + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + remainder: atomic_func!(|number, other, vm| { + PyBytes::number_downcast(number) + .mod_(other.to_owned(), vm) + .to_pyresult(vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } } impl Hashable for PyBytes { diff --git a/vm/src/builtins/complex.rs b/vm/src/builtins/complex.rs index 8805c6247..333269e4d 100644 --- a/vm/src/builtins/complex.rs +++ b/vm/src/builtins/complex.rs @@ -1,5 +1,6 @@ use super::{float, PyStr, PyType, PyTypeRef}; use crate::{ + atomic_func, class::PyClassImpl, convert::{ToPyObject, ToPyResult}, function::{ @@ -421,30 +422,44 @@ impl Hashable for PyComplex { } impl AsNumber for PyComplex { - const AS_NUMBER: PyNumberMethods = PyNumberMethods { - add: Some(|number, other, vm| Self::number_complex_op(number, other, |a, b| a + b, vm)), - subtract: Some(|number, other, vm| { - Self::number_complex_op(number, other, |a, b| a - b, vm) - }), - multiply: Some(|number, other, vm| { - Self::number_complex_op(number, other, |a, b| a * b, vm) - }), - power: Some(|number, other, vm| Self::number_general_op(number, other, inner_pow, vm)), - negative: Some(|number, vm| { - let value = Self::number_downcast(number).value; - (-value).to_pyresult(vm) - }), - positive: Some(|number, vm| Self::number_complex(number, vm).to_pyresult(vm)), - absolute: Some(|number, vm| { - let value = Self::number_downcast(number).value; - value.norm().to_pyresult(vm) - }), - boolean: Some(|number, _vm| Ok(Self::number_downcast(number).value.is_zero())), - true_divide: Some(|number, other, vm| { - Self::number_general_op(number, other, inner_div, vm) - }), - ..PyNumberMethods::NOT_IMPLEMENTED - }; + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + add: atomic_func!(|number, other, vm| PyComplex::number_complex_op( + number, + other, + |a, b| a + b, + vm + )), + subtract: atomic_func!(|number, other, vm| { + PyComplex::number_complex_op(number, other, |a, b| a - b, vm) + }), + multiply: atomic_func!(|number, other, vm| { + PyComplex::number_complex_op(number, other, |a, b| a * b, vm) + }), + power: atomic_func!(|number, other, vm| PyComplex::number_general_op( + number, other, inner_pow, vm + )), + negative: atomic_func!(|number, vm| { + let value = PyComplex::number_downcast(number).value; + (-value).to_pyresult(vm) + }), + positive: atomic_func!( + |number, vm| PyComplex::number_complex(number, vm).to_pyresult(vm) + ), + absolute: atomic_func!(|number, vm| { + let value = PyComplex::number_downcast(number).value; + value.norm().to_pyresult(vm) + }), + boolean: atomic_func!(|number, _vm| Ok(PyComplex::number_downcast(number) + .value + .is_zero())), + true_divide: atomic_func!(|number, other, vm| { + PyComplex::number_general_op(number, other, inner_div, vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } } impl PyComplex { diff --git a/vm/src/builtins/float.rs b/vm/src/builtins/float.rs index 9758f56dc..ee4837090 100644 --- a/vm/src/builtins/float.rs +++ b/vm/src/builtins/float.rs @@ -2,6 +2,7 @@ use super::{ try_bigint_to_f64, PyByteArray, PyBytes, PyInt, PyIntRef, PyStr, PyStrRef, PyType, PyTypeRef, }; use crate::{ + atomic_func, class::PyClassImpl, common::{float_ops, hash}, convert::{ToPyObject, ToPyResult}, @@ -57,16 +58,6 @@ impl From for PyFloat { } } -impl PyObject { - pub fn try_float_opt(&self, vm: &VirtualMachine) -> PyResult>> { - PyNumber::new(self, vm).float_opt(vm) - } - - pub fn try_float(&self, vm: &VirtualMachine) -> PyResult> { - PyNumber::new(self, vm).float(vm) - } -} - pub(crate) fn to_op_float(obj: &PyObject, vm: &VirtualMachine) -> PyResult> { let v = if let Some(float) = obj.payload_if_subclass::(vm) { Some(float.value) @@ -553,36 +544,63 @@ impl Hashable for PyFloat { } impl AsNumber for PyFloat { - const AS_NUMBER: PyNumberMethods = PyNumberMethods { - add: Some(|number, other, vm| Self::number_float_op(number, other, |a, b| a + b, vm)), - subtract: Some(|number, other, vm| Self::number_float_op(number, other, |a, b| a - b, vm)), - multiply: Some(|number, other, vm| Self::number_float_op(number, other, |a, b| a * b, vm)), - remainder: Some(|number, other, vm| Self::number_general_op(number, other, inner_mod, vm)), - divmod: Some(|number, other, vm| Self::number_general_op(number, other, inner_divmod, vm)), - power: Some(|number, other, vm| Self::number_general_op(number, other, float_pow, vm)), - negative: Some(|number, vm| { - let value = Self::number_downcast(number).value; - (-value).to_pyresult(vm) - }), - positive: Some(|number, vm| Self::number_float(number, vm).to_pyresult(vm)), - absolute: Some(|number, vm| { - let value = Self::number_downcast(number).value; - value.abs().to_pyresult(vm) - }), - boolean: Some(|number, _vm| Ok(Self::number_downcast(number).value.is_zero())), - int: Some(|number, vm| { - let value = Self::number_downcast(number).value; - try_to_bigint(value, vm).map(|x| vm.ctx.new_int(x)) - }), - float: Some(|number, vm| Ok(Self::number_float(number, vm))), - floor_divide: Some(|number, other, vm| { - Self::number_general_op(number, other, inner_floordiv, vm) - }), - true_divide: Some(|number, other, vm| { - Self::number_general_op(number, other, inner_div, vm) - }), - ..PyNumberMethods::NOT_IMPLEMENTED - }; + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + add: atomic_func!(|num, other, vm| PyFloat::number_float_op( + num, + other, + |a, b| a + b, + vm + )), + subtract: atomic_func!(|num, other, vm| PyFloat::number_float_op( + num, + other, + |a, b| a - b, + vm + )), + multiply: atomic_func!(|num, other, vm| PyFloat::number_float_op( + num, + other, + |a, b| a * b, + vm + )), + remainder: atomic_func!(|num, other, vm| PyFloat::number_general_op( + num, other, inner_mod, vm + )), + divmod: atomic_func!(|num, other, vm| PyFloat::number_general_op( + num, + other, + inner_divmod, + vm + )), + power: atomic_func!(|num, other, vm| PyFloat::number_general_op( + num, other, float_pow, vm + )), + negative: atomic_func!(|num, vm| { + let value = PyFloat::number_downcast(num).value; + (-value).to_pyresult(vm) + }), + positive: atomic_func!(|num, vm| PyFloat::number_float(num, vm).to_pyresult(vm)), + absolute: atomic_func!(|num, vm| { + let value = PyFloat::number_downcast(num).value; + value.abs().to_pyresult(vm) + }), + boolean: atomic_func!(|num, _vm| Ok(PyFloat::number_downcast(num).value.is_zero())), + int: atomic_func!(|num, vm| { + let value = PyFloat::number_downcast(num).value; + try_to_bigint(value, vm).map(|x| vm.ctx.new_int(x)) + }), + float: atomic_func!(|num, vm| Ok(PyFloat::number_float(num, vm))), + floor_divide: atomic_func!(|num, other, vm| { + PyFloat::number_general_op(num, other, inner_floordiv, vm) + }), + true_divide: atomic_func!(|num, other, vm| { + PyFloat::number_general_op(num, other, inner_div, vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } } impl PyFloat { diff --git a/vm/src/builtins/int.rs b/vm/src/builtins/int.rs index 3d64af457..991b3d7ed 100644 --- a/vm/src/builtins/int.rs +++ b/vm/src/builtins/int.rs @@ -1,5 +1,6 @@ use super::{float, PyByteArray, PyBytes, PyStr, PyStrRef, PyType, PyTypeRef}; use crate::{ + atomic_func, bytesinner::PyBytesInner, class::PyClassImpl, common::hash, @@ -68,7 +69,7 @@ impl PyPayload for PyInt { } fn special_retrieve(vm: &VirtualMachine, obj: &PyObject) -> Option>> { - Some(vm.to_index(obj)) + Some(obj.try_index(vm)) } } @@ -244,8 +245,8 @@ impl Constructor for PyInt { fn py_new(cls: PyTypeRef, options: Self::Args, vm: &VirtualMachine) -> PyResult { let value = if let OptionalArg::Present(val) = options.val_options { if let OptionalArg::Present(base) = options.base { - let base = vm - .to_index(&base)? + let base = base + .try_index(vm)? .as_bigint() .to_u32() .filter(|&v| v == 0 || (2..=36).contains(&v)) @@ -265,9 +266,7 @@ impl Constructor for PyInt { val }; - PyNumber::new(val.as_ref(), vm) - .int(vm) - .map(|x| x.as_bigint().clone()) + val.try_int(vm).map(|x| x.as_bigint().clone()) } } else if let OptionalArg::Present(_) = options.base { Err(vm.new_type_error("int() missing string argument".to_owned())) @@ -756,37 +755,76 @@ impl Hashable for PyInt { } impl AsNumber for PyInt { - const AS_NUMBER: PyNumberMethods = PyNumberMethods { - add: Some(|number, other, vm| Self::number_int_op(number, other, |a, b| a + b, vm)), - subtract: Some(|number, other, vm| Self::number_int_op(number, other, |a, b| a - b, vm)), - multiply: Some(|number, other, vm| Self::number_int_op(number, other, |a, b| a * b, vm)), - remainder: Some(|number, other, vm| Self::number_general_op(number, other, inner_mod, vm)), - divmod: Some(|number, other, vm| Self::number_general_op(number, other, inner_divmod, vm)), - power: Some(|number, other, vm| Self::number_general_op(number, other, inner_pow, vm)), - negative: Some(|number, vm| (&Self::number_downcast(number).value).neg().to_pyresult(vm)), - positive: Some(|number, vm| Ok(Self::number_int(number, vm).into())), - absolute: Some(|number, vm| Self::number_downcast(number).value.abs().to_pyresult(vm)), - boolean: Some(|number, _vm| Ok(Self::number_downcast(number).value.is_zero())), - invert: Some(|number, vm| (&Self::number_downcast(number).value).not().to_pyresult(vm)), - lshift: Some(|number, other, vm| Self::number_general_op(number, other, inner_lshift, vm)), - rshift: Some(|number, other, vm| Self::number_general_op(number, other, inner_rshift, vm)), - and: Some(|number, other, vm| Self::number_int_op(number, other, |a, b| a & b, vm)), - xor: Some(|number, other, vm| Self::number_int_op(number, other, |a, b| a ^ b, vm)), - or: Some(|number, other, vm| Self::number_int_op(number, other, |a, b| a | b, vm)), - int: Some(|number, other| Ok(Self::number_int(number, other))), - float: Some(|number, vm| { - let zelf = Self::number_downcast(number); - try_to_float(&zelf.value, vm).map(|x| vm.ctx.new_float(x)) - }), - floor_divide: Some(|number, other, vm| { - Self::number_general_op(number, other, inner_floordiv, vm) - }), - true_divide: Some(|number, other, vm| { - Self::number_general_op(number, other, inner_truediv, vm) - }), - index: Some(|number, vm| Ok(Self::number_int(number, vm))), - ..PyNumberMethods::NOT_IMPLEMENTED - }; + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + add: atomic_func!(|num, other, vm| PyInt::number_int_op(num, other, |a, b| a + b, vm)), + subtract: atomic_func!(|num, other, vm| PyInt::number_int_op( + num, + other, + |a, b| a - b, + vm + )), + multiply: atomic_func!(|num, other, vm| PyInt::number_int_op( + num, + other, + |a, b| a * b, + vm + )), + remainder: atomic_func!(|num, other, vm| PyInt::number_general_op( + num, other, inner_mod, vm + )), + divmod: atomic_func!(|num, other, vm| PyInt::number_general_op( + num, + other, + inner_divmod, + vm + )), + power: atomic_func!(|num, other, vm| PyInt::number_general_op( + num, other, inner_pow, vm + )), + negative: atomic_func!(|num, vm| (&PyInt::number_downcast(num).value) + .neg() + .to_pyresult(vm)), + positive: atomic_func!(|num, vm| Ok(PyInt::number_int(num, vm).into())), + absolute: atomic_func!(|num, vm| PyInt::number_downcast(num) + .value + .abs() + .to_pyresult(vm)), + boolean: atomic_func!(|num, _vm| Ok(PyInt::number_downcast(num).value.is_zero())), + invert: atomic_func!(|num, vm| (&PyInt::number_downcast(num).value) + .not() + .to_pyresult(vm)), + lshift: atomic_func!(|num, other, vm| PyInt::number_general_op( + num, + other, + inner_lshift, + vm + )), + rshift: atomic_func!(|num, other, vm| PyInt::number_general_op( + num, + other, + inner_rshift, + vm + )), + and: atomic_func!(|num, other, vm| PyInt::number_int_op(num, other, |a, b| a & b, vm)), + xor: atomic_func!(|num, other, vm| PyInt::number_int_op(num, other, |a, b| a ^ b, vm)), + or: atomic_func!(|num, other, vm| PyInt::number_int_op(num, other, |a, b| a | b, vm)), + int: atomic_func!(|num, other| Ok(PyInt::number_int(num, other))), + float: atomic_func!(|num, vm| { + let zelf = PyInt::number_downcast(num); + try_to_float(&zelf.value, vm).map(|x| vm.ctx.new_float(x)) + }), + floor_divide: atomic_func!(|num, other, vm| { + PyInt::number_general_op(num, other, inner_floordiv, vm) + }), + true_divide: atomic_func!(|num, other, vm| { + PyInt::number_general_op(num, other, inner_truediv, vm) + }), + index: atomic_func!(|num, vm| Ok(PyInt::number_int(num, vm))), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } } impl PyInt { diff --git a/vm/src/builtins/mappingproxy.rs b/vm/src/builtins/mappingproxy.rs index 788d100aa..173650169 100644 --- a/vm/src/builtins/mappingproxy.rs +++ b/vm/src/builtins/mappingproxy.rs @@ -1,5 +1,6 @@ use super::{PyDict, PyDictRef, PyGenericAlias, PyList, PyTuple, PyType, PyTypeRef}; use crate::{ + atomic_func, class::PyClassImpl, convert::ToPyObject, function::{ArgMapping, OptionalArg, PyComparisonValue}, @@ -217,11 +218,18 @@ impl AsSequence for PyMappingProxy { } impl AsNumber for PyMappingProxy { - const AS_NUMBER: PyNumberMethods = PyNumberMethods { - or: Some(|num, args, vm| Self::number_downcast(num).or(args.to_pyobject(vm), vm)), - inplace_or: Some(|num, args, vm| Self::number_downcast(num).ior(args.to_pyobject(vm), vm)), - ..PyNumberMethods::NOT_IMPLEMENTED - }; + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + or: atomic_func!(|num, args, vm| { + PyMappingProxy::number_downcast(num).or(args.to_pyobject(vm), vm) + }), + inplace_or: atomic_func!(|num, args, vm| { + PyMappingProxy::number_downcast(num).ior(args.to_pyobject(vm), vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } } impl Iterable for PyMappingProxy { diff --git a/vm/src/builtins/memory.rs b/vm/src/builtins/memory.rs index 58f6decd1..4549285d9 100644 --- a/vm/src/builtins/memory.rs +++ b/vm/src/builtins/memory.rs @@ -904,7 +904,7 @@ impl TryFromObject for SubscriptNeedle { Ok(Self::Index(i.try_to_primitive(vm)?)) } else if obj.payload_is::() { Ok(Self::Slice(unsafe { obj.downcast_unchecked::() })) - } else if let Ok(i) = vm.to_index(&obj) { + } else if let Ok(i) = obj.try_index(vm) { Ok(Self::Index(i.try_to_primitive(vm)?)) } else { if let Some(tuple) = obj.payload::() { diff --git a/vm/src/builtins/range.rs b/vm/src/builtins/range.rs index f71f98a1b..6275f4d21 100644 --- a/vm/src/builtins/range.rs +++ b/vm/src/builtins/range.rs @@ -682,7 +682,7 @@ impl TryFromObject for RangeIndex { i @ PyInt => Ok(RangeIndex::Int(i)), s @ PySlice => Ok(RangeIndex::Slice(s)), obj => { - let val = vm.to_index(&obj).map_err(|_| vm.new_type_error(format!( + let val = obj.try_index(vm).map_err(|_| vm.new_type_error(format!( "sequence indices be integers or slices or classes that override __index__ operator, not '{}'", obj.class().name() )))?; diff --git a/vm/src/builtins/singletons.rs b/vm/src/builtins/singletons.rs index f67ca2b85..3f510bed8 100644 --- a/vm/src/builtins/singletons.rs +++ b/vm/src/builtins/singletons.rs @@ -1,5 +1,6 @@ use super::{PyType, PyTypeRef}; use crate::{ + atomic_func, class::PyClassImpl, convert::ToPyObject, protocol::PyNumberMethods, @@ -56,10 +57,13 @@ impl PyNone { } impl AsNumber for PyNone { - const AS_NUMBER: PyNumberMethods = PyNumberMethods { - boolean: Some(|_number, _vm| Ok(false)), - ..PyNumberMethods::NOT_IMPLEMENTED - }; + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: atomic_func!(|_number, _vm| Ok(false)), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } } #[pyclass(module = false, name = "NotImplementedType")] diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index 6647cdba7..4bc294171 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -12,12 +12,13 @@ use crate::{ class::{PyClassImpl, StaticType}, function::{FuncArgs, KwArgs, OptionalArg}, identifier, + protocol::PyNumberMethods, types::{Callable, GetAttr, PyTypeFlags, PyTypeSlots, SetAttr}, AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, }; use indexmap::{map::Entry, IndexMap}; use itertools::Itertools; -use std::{borrow::Borrow, collections::HashSet, fmt, ops::Deref}; +use std::{borrow::Borrow, collections::HashSet, fmt, ops::Deref, pin::Pin, ptr::NonNull}; /// type(object_or_name, bases, dict) /// type(object) -> the object's type @@ -30,10 +31,62 @@ pub struct PyType { pub subclasses: PyRwLock>>, pub attributes: PyRwLock, pub slots: PyTypeSlots, + pub heaptype_ext: Option>>, +} + +#[derive(Default)] +pub struct HeapTypeExt { + pub number_methods: PyNumberMethods, +} + +pub struct PointerSlot(NonNull); + +impl PointerSlot { + pub unsafe fn borrow_static(&self) -> &'static T { + self.0.as_ref() + } +} + +impl Clone for PointerSlot { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for PointerSlot {} + +impl From<&'static T> for PointerSlot { + fn from(x: &'static T) -> Self { + Self(NonNull::from(x)) + } +} + +impl AsRef for PointerSlot { + fn as_ref(&self) -> &T { + unsafe { self.0.as_ref() } + } +} + +impl PointerSlot { + pub unsafe fn from_heaptype(typ: &PyType, f: F) -> Option + where + F: FnOnce(&HeapTypeExt) -> &T, + { + typ.heaptype_ext + .as_ref() + .map(|ext| Self(NonNull::from(f(ext)))) + } } pub type PyTypeRef = PyRef; +cfg_if::cfg_if! { + if #[cfg(feature = "threading")] { + unsafe impl Send for PyType {} + unsafe impl Sync for PyType {} + } +} + /// For attributes we do not use a dict, but an IndexMap, which is an Hash Table /// that maintains order and is compatible with the standard HashMap This is probably /// faster and only supports strings as keys. @@ -112,6 +165,7 @@ impl PyType { subclasses: PyRwLock::default(), attributes: PyRwLock::new(attrs), slots, + heaptype_ext: Some(Pin::new(Box::new(HeapTypeExt::default()))), }, metaclass, None, diff --git a/vm/src/byte.rs b/vm/src/byte.rs index 4933b2b3a..42455bd27 100644 --- a/vm/src/byte.rs +++ b/vm/src/byte.rs @@ -20,7 +20,7 @@ pub fn bytes_from_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult PyResult { - vm.to_index(obj)? + obj.try_index(vm)? .as_bigint() .to_u8() .ok_or_else(|| vm.new_value_error("byte must be in range(0, 256)".to_owned())) diff --git a/vm/src/convert/try_from.rs b/vm/src/convert/try_from.rs index cd1489695..1319cabe5 100644 --- a/vm/src/convert/try_from.rs +++ b/vm/src/convert/try_from.rs @@ -108,7 +108,7 @@ impl TryFromObject for std::time::Duration { use std::time::Duration; if let Some(float) = obj.payload::() { Ok(Duration::from_secs_f64(float.to_f64())) - } else if let Some(int) = vm.to_index_opt(obj.clone()) { + } else if let Some(int) = obj.try_index_opt(vm) { let sec = int? .as_bigint() .to_u64() diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index d9f68a797..d7a5c054d 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -717,7 +717,7 @@ impl DictKey for PyObject { } #[inline] fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult { - vm.to_index(self)?.try_to_primitive(vm) + self.try_index(vm)?.try_to_primitive(vm) } } diff --git a/vm/src/function/number.rs b/vm/src/function/number.rs index 6c6eac2de..12f6d70cc 100644 --- a/vm/src/function/number.rs +++ b/vm/src/function/number.rs @@ -1,4 +1,4 @@ -use crate::{protocol::PyNumber, AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine}; +use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine}; use num_complex::Complex64; use std::ops::Deref; @@ -82,7 +82,7 @@ impl Deref for ArgIntoFloat { impl TryFromObject for ArgIntoFloat { // Equivalent to PyFloat_AsDouble. fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - let value = PyNumber::new(obj.as_ref(), vm).float(vm)?.to_f64(); + let value = obj.try_float(vm)?.to_f64(); Ok(ArgIntoFloat { value }) } } diff --git a/vm/src/object/core.rs b/vm/src/object/core.rs index c3a67ccfc..8868a68e9 100644 --- a/vm/src/object/core.rs +++ b/vm/src/object/core.rs @@ -1105,6 +1105,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { subclasses: PyRwLock::default(), attributes: PyRwLock::new(Default::default()), slots: PyType::make_slots(), + heaptype_ext: None, }; let object_payload = PyType { base: None, @@ -1113,6 +1114,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { subclasses: PyRwLock::default(), attributes: PyRwLock::new(Default::default()), slots: object::PyBaseObject::make_slots(), + heaptype_ext: None, }; let type_type_ptr = Box::into_raw(Box::new(partially_init!( PyInner:: { @@ -1173,6 +1175,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { subclasses: PyRwLock::default(), attributes: PyRwLock::default(), slots: PyWeak::make_slots(), + heaptype_ext: None, }; let weakref_type = PyRef::new_ref(weakref_type, type_type.clone(), None); diff --git a/vm/src/protocol/number.rs b/vm/src/protocol/number.rs index e8f1e9041..da144dfae 100644 --- a/vm/src/protocol/number.rs +++ b/vm/src/protocol/number.rs @@ -1,192 +1,48 @@ use crate::{ - builtins::{int, PyByteArray, PyBytes, PyComplex, PyFloat, PyInt, PyIntRef, PyStr}, + builtins::{ + int, type_::PointerSlot, PyByteArray, PyBytes, PyComplex, PyFloat, PyInt, PyIntRef, PyStr, + }, function::ArgBytesLike, stdlib::warnings, - AsObject, PyObject, PyPayload, PyRef, PyResult, TryFromBorrowedObject, VirtualMachine, + AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromBorrowedObject, + VirtualMachine, }; +use crossbeam_utils::atomic::AtomicCell; -#[allow(clippy::type_complexity)] -#[derive(Clone)] -pub struct PyNumberMethods { - /* Number implementations must check *both* - arguments for proper type and implement the necessary conversions - in the slot functions themselves. */ - pub add: Option PyResult>, - pub subtract: Option PyResult>, - pub multiply: Option PyResult>, - pub remainder: Option PyResult>, - pub divmod: Option PyResult>, - pub power: Option PyResult>, - pub negative: Option PyResult>, - pub positive: Option PyResult>, - pub absolute: Option PyResult>, - pub boolean: Option PyResult>, - pub invert: Option PyResult>, - pub lshift: Option PyResult>, - pub rshift: Option PyResult>, - pub and: Option PyResult>, - pub xor: Option PyResult>, - pub or: Option PyResult>, - pub int: Option PyResult>, - pub float: Option PyResult>>, +type UnaryFunc = AtomicCell PyResult>>; +type BinaryFunc = + AtomicCell PyResult>>; - pub inplace_add: Option PyResult>, - pub inplace_subtract: Option PyResult>, - pub inplace_multiply: Option PyResult>, - pub inplace_remainder: Option PyResult>, - pub inplace_divmod: Option PyResult>, - pub inplace_power: Option PyResult>, - pub inplace_lshift: Option PyResult>, - pub inplace_rshift: Option PyResult>, - pub inplace_and: Option PyResult>, - pub inplace_xor: Option PyResult>, - pub inplace_or: Option PyResult>, - - pub floor_divide: Option PyResult>, - pub true_divide: Option PyResult>, - pub inplace_floor_divide: Option PyResult>, - pub inplace_true_divide: Option PyResult>, - - pub index: Option PyResult>, - - pub matrix_multiply: Option PyResult>, - pub inplace_matrix_multiply: Option PyResult>, -} - -impl PyNumberMethods { - pub const NOT_IMPLEMENTED: PyNumberMethods = PyNumberMethods { - add: None, - subtract: None, - multiply: None, - remainder: None, - divmod: None, - power: None, - negative: None, - positive: None, - absolute: None, - boolean: None, - invert: None, - lshift: None, - rshift: None, - and: None, - xor: None, - or: None, - int: None, - float: None, - inplace_add: None, - inplace_subtract: None, - inplace_multiply: None, - inplace_remainder: None, - inplace_divmod: None, - inplace_power: None, - inplace_lshift: None, - inplace_rshift: None, - inplace_and: None, - inplace_xor: None, - inplace_or: None, - floor_divide: None, - true_divide: None, - inplace_floor_divide: None, - inplace_true_divide: None, - index: None, - matrix_multiply: None, - inplace_matrix_multiply: None, - }; - - pub(crate) fn generic( - has_int: bool, - has_float: bool, - has_index: bool, - ) -> &'static PyNumberMethods { - static METHODS: &[PyNumberMethods] = &[ - new_generic(false, false, false), - new_generic(true, false, false), - new_generic(false, true, false), - new_generic(true, true, false), - new_generic(false, false, true), - new_generic(true, false, true), - new_generic(false, true, true), - new_generic(true, true, true), - ]; - - fn int(num: &PyNumber, vm: &VirtualMachine) -> PyResult> { - let ret = vm.call_special_method(num.obj.to_owned(), identifier!(vm, __int__), ())?; - ret.downcast::().map_err(|obj| { - vm.new_type_error(format!("__int__ returned non-int (type {})", obj.class())) - }) - } - fn float(num: &PyNumber, vm: &VirtualMachine) -> PyResult> { - let ret = vm.call_special_method(num.obj.to_owned(), identifier!(vm, __float__), ())?; - ret.downcast::().map_err(|obj| { - vm.new_type_error(format!( - "__float__ returned non-float (type {})", - obj.class() - )) - }) - } - fn index(num: &PyNumber, vm: &VirtualMachine) -> PyResult> { - let ret = vm.call_special_method(num.obj.to_owned(), identifier!(vm, __index__), ())?; - ret.downcast::().map_err(|obj| { - vm.new_type_error(format!("__index__ returned non-int (type {})", obj.class())) - }) - } - - const fn new_generic(has_int: bool, has_float: bool, has_index: bool) -> PyNumberMethods { - PyNumberMethods { - int: if has_int { Some(int) } else { None }, - float: if has_float { Some(float) } else { None }, - index: if has_index { Some(index) } else { None }, - ..PyNumberMethods::NOT_IMPLEMENTED - } - } - - let key = (has_int as usize) | ((has_float as usize) << 1) | ((has_index as usize) << 2); - - &METHODS[key] - } -} - -pub struct PyNumber<'a> { - pub obj: &'a PyObject, - // some fast path do not need methods, so we do lazy initialize - pub methods: Option<&'static PyNumberMethods>, -} - -impl<'a> PyNumber<'a> { - pub fn new(obj: &'a PyObject, vm: &VirtualMachine) -> Self { - Self { - obj, - methods: Self::find_methods(obj, vm), - } - } -} - -impl PyNumber<'_> { - pub fn find_methods(obj: &PyObject, vm: &VirtualMachine) -> Option<&'static PyNumberMethods> { - let as_number = obj.class().mro_find_map(|x| x.slots.as_number.load()); - as_number.map(|f| f(obj, vm)) +impl PyObject { + #[inline] + pub fn to_number(&self) -> PyNumber<'_> { + PyNumber::from(self) } - pub fn methods(&self) -> &'static PyNumberMethods { - self.methods.unwrap_or(&PyNumberMethods::NOT_IMPLEMENTED) - } - - // PyNumber_Check - pub fn check(obj: &PyObject, vm: &VirtualMachine) -> bool { - Self::find_methods(obj, vm).map_or(false, |methods| { - methods.int.is_some() - || methods.index.is_some() - || methods.float.is_some() - || obj.payload_is::() + pub fn try_index_opt(&self, vm: &VirtualMachine) -> Option> { + #[allow(clippy::question_mark)] + Some(if let Some(i) = self.downcast_ref_if_exact::(vm) { + Ok(i.to_owned()) + } else if let Some(i) = self.payload::() { + Ok(vm.ctx.new_bigint(i.as_bigint())) + } else if let Some(i) = self.to_number().index(vm).transpose() { + i + } else { + return None; }) } - // PyIndex_Check - pub fn is_index(&self) -> bool { - self.methods().index.is_some() + #[inline] + pub fn try_index(&self, vm: &VirtualMachine) -> PyResult { + self.try_index_opt(vm).transpose()?.ok_or_else(|| { + vm.new_type_error(format!( + "'{}' object cannot be interpreted as an integer", + self.class() + )) + }) } - pub fn int(&self, vm: &VirtualMachine) -> PyResult { + pub fn try_int(&self, vm: &VirtualMachine) -> PyResult { fn try_convert(obj: &PyObject, lit: &[u8], vm: &VirtualMachine) -> PyResult { let base = 10; match int::bytes_to_int(lit, base) { @@ -199,11 +55,216 @@ impl PyNumber<'_> { } } - if let Some(i) = self.obj.downcast_ref_if_exact::(vm) { + if let Some(i) = self.downcast_ref_if_exact::(vm) { Ok(i.to_owned()) - } else if let Some(f) = self.methods().int { + } else { + let number = self.to_number(); + if let Some(i) = number.int(vm)? { + Ok(i) + } else if let Some(i) = self.try_index_opt(vm) { + i + } else if let Ok(Ok(f)) = + vm.get_special_method(self.to_owned(), identifier!(vm, __trunc__)) + { + // TODO: Deprecate in 3.11 + // warnings::warn( + // vm.ctx.exceptions.deprecation_warning.clone(), + // "The delegation of int() to __trunc__ is deprecated.".to_owned(), + // 1, + // vm, + // )?; + let ret = f.invoke((), vm)?; + ret.try_index(vm).map_err(|_| { + vm.new_type_error(format!( + "__trunc__ returned non-Integral (type {})", + ret.class() + )) + }) + } else if let Some(s) = self.payload::() { + try_convert(self, s.as_str().as_bytes(), vm) + } else if let Some(bytes) = self.payload::() { + try_convert(self, bytes, vm) + } else if let Some(bytearray) = self.payload::() { + try_convert(self, &bytearray.borrow_buf(), vm) + } else if let Ok(buffer) = ArgBytesLike::try_from_borrowed_object(vm, self) { + // TODO: replace to PyBuffer + try_convert(self, &buffer.borrow_buf(), vm) + } else { + Err(vm.new_type_error(format!( + "int() argument must be a string, a bytes-like object or a real number, not '{}'", + self.class() + ))) + } + } + } + + pub fn try_float_opt(&self, vm: &VirtualMachine) -> PyResult>> { + let value = if let Some(float) = self.downcast_ref_if_exact::(vm) { + Some(float.to_owned()) + } else { + let number = self.to_number(); + #[allow(clippy::manual_map)] + if let Some(f) = number.float(vm)? { + Some(f) + } else if let Some(i) = self.try_index_opt(vm) { + let value = int::try_to_float(i?.as_bigint(), vm)?; + Some(vm.ctx.new_float(value)) + } else if let Some(value) = self.downcast_ref::() { + Some(vm.ctx.new_float(value.to_f64())) + } else { + None + } + }; + Ok(value) + } + + #[inline] + pub fn try_float(&self, vm: &VirtualMachine) -> PyResult> { + self.try_float_opt(vm)? + .ok_or_else(|| vm.new_type_error(format!("must be real number, not {}", self.class()))) + } +} + +#[derive(Default)] +pub struct PyNumberMethods { + /* Number implementations must check *both* + arguments for proper type and implement the necessary conversions + in the slot functions themselves. */ + pub add: BinaryFunc, + pub subtract: BinaryFunc, + pub multiply: BinaryFunc, + pub remainder: BinaryFunc, + pub divmod: BinaryFunc, + pub power: BinaryFunc, + pub negative: UnaryFunc, + pub positive: UnaryFunc, + pub absolute: UnaryFunc, + pub boolean: UnaryFunc, + pub invert: UnaryFunc, + pub lshift: BinaryFunc, + pub rshift: BinaryFunc, + pub and: BinaryFunc, + pub xor: BinaryFunc, + pub or: BinaryFunc, + pub int: UnaryFunc>, + pub float: UnaryFunc>, + + pub inplace_add: BinaryFunc, + pub inplace_subtract: BinaryFunc, + pub inplace_multiply: BinaryFunc, + pub inplace_remainder: BinaryFunc, + pub inplace_divmod: BinaryFunc, + pub inplace_power: BinaryFunc, + pub inplace_lshift: BinaryFunc, + pub inplace_rshift: BinaryFunc, + pub inplace_and: BinaryFunc, + pub inplace_xor: BinaryFunc, + pub inplace_or: BinaryFunc, + + pub floor_divide: BinaryFunc, + pub true_divide: BinaryFunc, + pub inplace_floor_divide: BinaryFunc, + pub inplace_true_divide: BinaryFunc, + + pub index: UnaryFunc>, + + pub matrix_multiply: BinaryFunc, + pub inplace_matrix_multiply: BinaryFunc, +} + +impl PyNumberMethods { + /// this is NOT a global variable + // TODO: weak order read for performance + #[allow(clippy::declare_interior_mutable_const)] + pub const NOT_IMPLEMENTED: PyNumberMethods = PyNumberMethods { + add: AtomicCell::new(None), + subtract: AtomicCell::new(None), + multiply: AtomicCell::new(None), + remainder: AtomicCell::new(None), + divmod: AtomicCell::new(None), + power: AtomicCell::new(None), + negative: AtomicCell::new(None), + positive: AtomicCell::new(None), + absolute: AtomicCell::new(None), + boolean: AtomicCell::new(None), + invert: AtomicCell::new(None), + lshift: AtomicCell::new(None), + rshift: AtomicCell::new(None), + and: AtomicCell::new(None), + xor: AtomicCell::new(None), + or: AtomicCell::new(None), + int: AtomicCell::new(None), + float: AtomicCell::new(None), + inplace_add: AtomicCell::new(None), + inplace_subtract: AtomicCell::new(None), + inplace_multiply: AtomicCell::new(None), + inplace_remainder: AtomicCell::new(None), + inplace_divmod: AtomicCell::new(None), + inplace_power: AtomicCell::new(None), + inplace_lshift: AtomicCell::new(None), + inplace_rshift: AtomicCell::new(None), + inplace_and: AtomicCell::new(None), + inplace_xor: AtomicCell::new(None), + inplace_or: AtomicCell::new(None), + floor_divide: AtomicCell::new(None), + true_divide: AtomicCell::new(None), + inplace_floor_divide: AtomicCell::new(None), + inplace_true_divide: AtomicCell::new(None), + index: AtomicCell::new(None), + matrix_multiply: AtomicCell::new(None), + inplace_matrix_multiply: AtomicCell::new(None), + }; +} + +pub struct PyNumber<'a> { + pub obj: &'a PyObject, + methods: &'a PyNumberMethods, +} + +impl<'a> From<&'a PyObject> for PyNumber<'a> { + fn from(obj: &'a PyObject) -> Self { + static GLOBAL_NOT_IMPLEMENTED: PyNumberMethods = PyNumberMethods::NOT_IMPLEMENTED; + Self { + obj, + methods: Self::find_methods(obj) + .map_or(&GLOBAL_NOT_IMPLEMENTED, |m| unsafe { m.borrow_static() }), + } + } +} + +impl PyNumber<'_> { + fn find_methods(obj: &PyObject) -> Option> { + obj.class().mro_find_map(|x| x.slots.as_number.load()) + } + + pub fn methods(&self) -> &PyNumberMethods { + self.methods + } + + // PyNumber_Check + pub fn check(obj: &PyObject) -> bool { + let methods = if let Some(m) = Self::find_methods(obj) { + m + } else { + return false; + }; + let methods = methods.as_ref(); + methods.int.load().is_some() + || methods.index.load().is_some() + || methods.float.load().is_some() + || obj.payload_is::() + } + + // PyIndex_Check + pub fn is_index(&self) -> bool { + self.methods().index.load().is_some() + } + + #[inline] + pub fn int(&self, vm: &VirtualMachine) -> PyResult> { + Ok(if let Some(f) = self.methods().int.load() { let ret = f(self, vm)?; - if !ret.class().is(PyInt::class(vm)) { + Some(if !ret.class().is(PyInt::class(vm)) { warnings::warn( vm.ctx.exceptions.deprecation_warning, format!( @@ -215,52 +276,18 @@ impl PyNumber<'_> { 1, vm, )?; - Ok(vm.ctx.new_bigint(ret.as_bigint())) + vm.ctx.new_bigint(ret.as_bigint()) } else { - Ok(ret) - } - } else if self.methods().index.is_some() { - self.index(vm) - } else if let Ok(Ok(f)) = - vm.get_special_method(self.obj.to_owned(), identifier!(vm, __trunc__)) - { - // TODO: Deprecate in 3.11 - // warnings::warn( - // vm.ctx.exceptions.deprecation_warning.clone(), - // "The delegation of int() to __trunc__ is deprecated.".to_owned(), - // 1, - // vm, - // )?; - let ret = f.invoke((), vm)?; - PyNumber::new(ret.as_ref(), vm).index(vm).map_err(|_| { - vm.new_type_error(format!( - "__trunc__ returned non-Integral (type {})", - ret.class() - )) + ret }) - } else if let Some(s) = self.obj.payload::() { - try_convert(self.obj, s.as_str().as_bytes(), vm) - } else if let Some(bytes) = self.obj.payload::() { - try_convert(self.obj, bytes, vm) - } else if let Some(bytearray) = self.obj.payload::() { - try_convert(self.obj, &bytearray.borrow_buf(), vm) - } else if let Ok(buffer) = ArgBytesLike::try_from_borrowed_object(vm, self.obj) { - // TODO: replace to PyBuffer - try_convert(self.obj, &buffer.borrow_buf(), vm) } else { - Err(vm.new_type_error(format!( - "int() argument must be a string, a bytes-like object or a real number, not '{}'", - self.obj.class() - ))) - } + None + }) } - pub fn index_opt(&self, vm: &VirtualMachine) -> PyResult> { - if let Some(i) = self.obj.downcast_ref_if_exact::(vm) { - Ok(Some(i.to_owned())) - } else if let Some(i) = self.obj.payload::() { - Ok(Some(vm.ctx.new_bigint(i.as_bigint()))) - } else if let Some(f) = self.methods().index { + #[inline] + pub fn index(&self, vm: &VirtualMachine) -> PyResult> { + if let Some(f) = self.methods().index.load() { let ret = f(self, vm)?; if !ret.class().is(PyInt::class(vm)) { warnings::warn( @@ -283,21 +310,11 @@ impl PyNumber<'_> { } } - pub fn index(&self, vm: &VirtualMachine) -> PyResult { - self.index_opt(vm)?.ok_or_else(|| { - vm.new_type_error(format!( - "'{}' object cannot be interpreted as an integer", - self.obj.class() - )) - }) - } - - pub fn float_opt(&self, vm: &VirtualMachine) -> PyResult>> { - if let Some(float) = self.obj.downcast_ref_if_exact::(vm) { - Ok(Some(float.to_owned())) - } else if let Some(f) = self.methods().float { + #[inline] + pub fn float(&self, vm: &VirtualMachine) -> PyResult>> { + Ok(if let Some(f) = self.methods().float.load() { let ret = f(self, vm)?; - if !ret.class().is(PyFloat::class(vm)) { + Some(if !ret.class().is(PyFloat::class(vm)) { warnings::warn( vm.ctx.exceptions.deprecation_warning, format!( @@ -309,24 +326,12 @@ impl PyNumber<'_> { 1, vm, )?; - Ok(Some(vm.ctx.new_float(ret.to_f64()))) + vm.ctx.new_float(ret.to_f64()) } else { - Ok(Some(ret)) - } - } else if self.methods().index.is_some() { - let i = self.index(vm)?; - let value = int::try_to_float(i.as_bigint(), vm)?; - Ok(Some(vm.ctx.new_float(value))) - } else if let Some(value) = self.obj.downcast_ref::() { - Ok(Some(vm.ctx.new_float(value.to_f64()))) + ret + }) } else { - Ok(None) - } - } - - pub fn float(&self, vm: &VirtualMachine) -> PyResult> { - self.float_opt(vm)?.ok_or_else(|| { - vm.new_type_error(format!("must be real number, not {}", self.obj.class())) + None }) } } diff --git a/vm/src/sliceable.rs b/vm/src/sliceable.rs index 6f62e76f3..26c7c4ac0 100644 --- a/vm/src/sliceable.rs +++ b/vm/src/sliceable.rs @@ -275,7 +275,7 @@ impl SequenceIndex { .map(Self::Int) } else if let Some(slice) = obj.payload::() { slice.to_saturated(vm).map(Self::Slice) - } else if let Some(i) = vm.to_index_opt(obj.to_owned()) { + } else if let Some(i) = obj.try_index_opt(vm) { // TODO: __index__ for indices is no more supported? i?.try_to_primitive(vm) .map_err(|_| { @@ -465,7 +465,7 @@ fn to_isize_index(vm: &VirtualMachine, obj: &PyObject) -> PyResult if vm.is_none(obj) { return Ok(None); } - let result = vm.to_index_opt(obj.to_owned()).unwrap_or_else(|| { + let result = obj.try_index_opt(vm).unwrap_or_else(|| { Err(vm.new_type_error( "slice indices must be integers or None or have an __index__ method".to_owned(), )) diff --git a/vm/src/stdlib/builtins.rs b/vm/src/stdlib/builtins.rs index 67d402b86..8f3d99e47 100644 --- a/vm/src/stdlib/builtins.rs +++ b/vm/src/stdlib/builtins.rs @@ -719,7 +719,7 @@ mod builtins { })?; match ndigits.flatten() { Some(obj) => { - let ndigits = vm.to_index(&obj)?; + let ndigits = obj.try_index(vm)?; meth.invoke((ndigits,), vm) } None => { diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index f8abab5a3..40152444d 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -1336,7 +1336,7 @@ mod _io { } pub fn get_offset(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let int = vm.to_index(&obj)?; + let int = obj.try_index(vm)?; int.as_bigint().try_into().map_err(|_| { vm.new_value_error(format!( "cannot fit '{}' into an offset-sized integer", diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index a90ac5d71..3366bc21e 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -196,7 +196,7 @@ mod decl { ) -> PyResult { let start = start.into_option().unwrap_or_else(|| vm.new_pyobj(0)); let step = step.into_option().unwrap_or_else(|| vm.new_pyobj(1)); - if !PyNumber::check(&start, vm) || !PyNumber::check(&step, vm) { + if !PyNumber::check(&start) || !PyNumber::check(&step) { return Err(vm.new_type_error("a number is required".to_owned())); } diff --git a/vm/src/stdlib/operator.rs b/vm/src/stdlib/operator.rs index 2ee0a516a..fdfe04587 100644 --- a/vm/src/stdlib/operator.rs +++ b/vm/src/stdlib/operator.rs @@ -110,7 +110,7 @@ mod _operator { /// Return a converted to an integer. Equivalent to a.__index__(). #[pyfunction] fn index(a: PyObjectRef, vm: &VirtualMachine) -> PyResult { - vm.to_index(&a) + a.try_index(vm) } /// Return the bitwise inverse of the number obj. This is equivalent to ~obj. diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index ed4049a8d..c2bfe1743 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -349,7 +349,7 @@ impl FromArgs for DirFd { Some(o) if vm.is_none(&o) => DEFAULT_DIR_FD, None => DEFAULT_DIR_FD, Some(o) => { - let fd = vm.to_index_opt(o.clone()).unwrap_or_else(|| { + let fd = o.try_index_opt(vm).unwrap_or_else(|| { Err(vm.new_type_error(format!( "argument should be integer or None, not {}", o.class().name() @@ -1364,8 +1364,8 @@ pub(super) mod _os { divmod.class().name() )) })?; - let secs = vm.to_index(&div)?.try_to_primitive(vm)?; - let ns = vm.to_index(&rem)?.try_to_primitive(vm)?; + let secs = div.try_index(vm)?.try_to_primitive(vm)?; + let ns = rem.try_index(vm)?.try_to_primitive(vm)?; Ok(Duration::new(secs, ns)) }; // TODO: do validation to make sure this doesn't.. underflow? diff --git a/vm/src/stdlib/sre.rs b/vm/src/stdlib/sre.rs index 2b6b88d9f..a4bb7b01a 100644 --- a/vm/src/stdlib/sre.rs +++ b/vm/src/stdlib/sre.rs @@ -745,7 +745,7 @@ mod _sre { } fn get_index(&self, group: PyObjectRef, vm: &VirtualMachine) -> Option { - let i = if let Ok(i) = vm.to_index(&group) { + let i = if let Ok(i) = group.try_index(vm) { i } else { self.pattern diff --git a/vm/src/types/slot.rs b/vm/src/types/slot.rs index f05483886..b9c021e1f 100644 --- a/vm/src/types/slot.rs +++ b/vm/src/types/slot.rs @@ -1,6 +1,6 @@ use crate::common::{hash::PyHash, lock::PyRwLock}; use crate::{ - builtins::{PyInt, PyStrInterned, PyStrRef, PyType, PyTypeRef}, + builtins::{type_::PointerSlot, PyFloat, PyInt, PyStrInterned, PyStrRef, PyType, PyTypeRef}, bytecode::ComparisonOperator, convert::ToPyResult, function::Either, @@ -17,6 +17,13 @@ use crossbeam_utils::atomic::AtomicCell; use num_traits::{Signed, ToPrimitive}; use std::{borrow::Borrow, cmp::Ordering}; +#[macro_export] +macro_rules! atomic_func { + ($x:expr) => { + crossbeam_utils::atomic::AtomicCell::new(Some($x)) + }; +} + // The corresponding field in CPython is `tp_` prefixed. // e.g. name -> tp_name #[derive(Default)] @@ -30,7 +37,7 @@ pub struct PyTypeSlots { // Methods to implement standard operations // Method suites for standard classes - pub as_number: AtomicCell>, + pub as_number: AtomicCell>>, pub as_sequence: AtomicCell>, pub as_mapping: AtomicCell>, @@ -139,7 +146,6 @@ impl Default for PyTypeFlags { pub(crate) type GenericMethod = fn(&PyObject, FuncArgs, &VirtualMachine) -> PyResult; pub(crate) type AsMappingFunc = fn(&PyObject, &VirtualMachine) -> &'static PyMappingMethods; -pub(crate) type AsNumberFunc = fn(&PyObject, &VirtualMachine) -> &'static PyNumberMethods; pub(crate) type HashFunc = fn(&PyObject, &VirtualMachine) -> PyResult; // CallFunc = GenericMethod pub(crate) type GetattroFunc = fn(&PyObject, PyStrRef, &VirtualMachine) -> PyResult; @@ -205,13 +211,28 @@ fn slot_as_sequence(zelf: &PyObject, vm: &VirtualMachine) -> &'static PySequence PySequenceMethods::generic(has_length, has_ass_item) } -fn slot_as_number(zelf: &PyObject, vm: &VirtualMachine) -> &'static PyNumberMethods { - let (has_int, has_float, has_index) = ( - zelf.class().has_attr(identifier!(vm, __int__)), - zelf.class().has_attr(identifier!(vm, __float__)), - zelf.class().has_attr(identifier!(vm, __index__)), - ); - PyNumberMethods::generic(has_int, has_float, has_index) +fn int_wrapper(num: &PyNumber, vm: &VirtualMachine) -> PyResult> { + let ret = vm.call_special_method(num.obj.to_owned(), identifier!(vm, __int__), ())?; + ret.downcast::().map_err(|obj| { + vm.new_type_error(format!("__int__ returned non-int (type {})", obj.class())) + }) +} + +fn index_wrapper(num: &PyNumber, vm: &VirtualMachine) -> PyResult> { + let ret = vm.call_special_method(num.obj.to_owned(), identifier!(vm, __index__), ())?; + ret.downcast::().map_err(|obj| { + vm.new_type_error(format!("__index__ returned non-int (type {})", obj.class())) + }) +} + +fn float_wrapper(num: &PyNumber, vm: &VirtualMachine) -> PyResult> { + let ret = vm.call_special_method(num.obj.to_owned(), identifier!(vm, __float__), ())?; + ret.downcast::().map_err(|obj| { + vm.new_type_error(format!( + "__float__ returned non-float (type {})", + obj.class() + )) + }) } fn hash_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult { @@ -327,21 +348,35 @@ impl PyType { debug_assert!(name.as_str().starts_with("__")); debug_assert!(name.as_str().ends_with("__")); - macro_rules! update_slot { + macro_rules! toggle_slot { ($name:ident, $func:expr) => {{ self.slots.$name.store(if add { Some($func) } else { None }); }}; } + + macro_rules! update_slot { + ($name:ident, $func:expr) => {{ + self.slots.$name.store(Some($func)); + }}; + } + + macro_rules! update_pointer_slot { + ($name:ident, $pointed:ident) => {{ + self.slots + .$name + .store(unsafe { PointerSlot::from_heaptype(self, |ext| &ext.$pointed) }); + }}; + } match name.as_str() { "__len__" | "__getitem__" | "__setitem__" | "__delitem__" => { update_slot!(as_mapping, slot_as_mapping); update_slot!(as_sequence, slot_as_sequence); } "__hash__" => { - update_slot!(hash, hash_wrapper); + toggle_slot!(hash, hash_wrapper); } "__call__" => { - update_slot!(call, call_wrapper); + toggle_slot!(call, call_wrapper); } "__getattr__" | "__getattribute__" => { update_slot!(getattro, getattro_wrapper); @@ -353,28 +388,52 @@ impl PyType { update_slot!(richcompare, richcompare_wrapper); } "__iter__" => { - update_slot!(iter, iter_wrapper); + toggle_slot!(iter, iter_wrapper); } "__next__" => { - update_slot!(iternext, iternext_wrapper); + toggle_slot!(iternext, iternext_wrapper); } "__get__" => { - update_slot!(descr_get, descr_get_wrapper); + toggle_slot!(descr_get, descr_get_wrapper); } "__set__" | "__delete__" => { update_slot!(descr_set, descr_set_wrapper); } "__init__" => { - update_slot!(init, init_wrapper); + toggle_slot!(init, init_wrapper); } "__new__" => { - update_slot!(new, new_wrapper); + toggle_slot!(new, new_wrapper); } "__del__" => { - update_slot!(del, del_wrapper); + toggle_slot!(del, del_wrapper); } - "__int__" | "__index__" | "__float__" => { - update_slot!(as_number, slot_as_number); + "__int__" => { + self.heaptype_ext + .as_ref() + .unwrap() + .number_methods + .int + .store(Some(int_wrapper)); + update_pointer_slot!(as_number, number_methods); + } + "__index__" => { + self.heaptype_ext + .as_ref() + .unwrap() + .number_methods + .index + .store(Some(index_wrapper)); + update_pointer_slot!(as_number, number_methods); + } + "__float__" => { + self.heaptype_ext + .as_ref() + .unwrap() + .number_methods + .float + .store(Some(float_wrapper)); + update_pointer_slot!(as_number, number_methods); } _ => {} } @@ -880,13 +939,8 @@ pub trait AsSequence: PyPayload { #[pyimpl] pub trait AsNumber: PyPayload { - const AS_NUMBER: PyNumberMethods; - - #[inline] #[pyslot] - fn as_number(_zelf: &PyObject, _vm: &VirtualMachine) -> &'static PyNumberMethods { - &Self::AS_NUMBER - } + fn as_number() -> &'static PyNumberMethods; fn number_downcast<'a>(number: &'a PyNumber) -> &'a Py { unsafe { number.obj.downcast_unchecked_ref() } diff --git a/vm/src/vm/vm_ops.rs b/vm/src/vm/vm_ops.rs index f8af60b7a..12f940fb0 100644 --- a/vm/src/vm/vm_ops.rs +++ b/vm/src/vm/vm_ops.rs @@ -3,22 +3,12 @@ use crate::{ builtins::{PyInt, PyIntRef, PyStrInterned}, function::PyArithmeticValue, object::{AsObject, PyObject, PyObjectRef, PyResult}, - protocol::{PyIterReturn, PyNumber}, + protocol::PyIterReturn, types::PyComparisonOp, }; /// Collection of operators impl VirtualMachine { - pub fn to_index_opt(&self, obj: PyObjectRef) -> Option> { - PyNumber::new(obj.as_ref(), self) - .index_opt(self) - .transpose() - } - - pub fn to_index(&self, obj: &PyObject) -> PyResult { - PyNumber::new(obj, self).index(self) - } - #[inline] pub fn bool_eq(&self, a: &PyObject, b: &PyObject) -> PyResult { a.rich_compare_bool(b, PyComparisonOp::Eq, self)