diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index f592a88fc2c..8c711207fae 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1788,7 +1788,6 @@ class D(C): self.assertEqual(b.foo, 3) self.assertEqual(b.__class__, D) - @unittest.expectedFailure def test_bad_new(self): self.assertRaises(TypeError, object.__new__) self.assertRaises(TypeError, object.__new__, '') diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 5060dced2b0..f784a2e2a76 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -954,7 +954,10 @@ where } else if let Ok(f) = args.item.function_or_method() { (&f.sig().ident, f.span()) } else { - return Err(self.new_syn_error(args.item.span(), "can only be on a method")); + return Err(self.new_syn_error( + args.item.span(), + "can only be on a method or const function pointer", + )); }; let item_attr = args.attrs.remove(self.index()); @@ -1496,7 +1499,9 @@ impl SlotItemMeta { } } else { let ident_str = self.inner().item_name(); - let name = if let Some(stripped) = ident_str.strip_prefix("slot_") { + // Convert to lowercase to handle both SLOT_NEW and slot_new + let ident_lower = ident_str.to_lowercase(); + let name = if let Some(stripped) = ident_lower.strip_prefix("slot_") { proc_macro2::Ident::new(stripped, inner.item_ident.span()) } else { inner.item_ident.clone() @@ -1609,7 +1614,6 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result { @@ -1634,9 +1638,6 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result::__extend_py_class), quote!(::__OWN_METHOD_DEFS), @@ -1689,11 +1690,6 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result bail_span!(attr, "Unknown pyimpl attribute"), } } - // TODO: DISALLOW_INSTANTIATION check is required - let _ = has_constructor; - // if !withs.is_empty() && !has_constructor { - // bail_span!(item, "#[pyclass(with(...))] does not have a Constructor. Either #[pyclass(with(Constructor, ...))] or #[pyclass(with(Unconstructible, ...))] is mandatory. Consider to add `impl DefaultConstructor for T {{}}` or `impl Unconstructible for T {{}}`.") - // } Ok(ExtractedImplAttrs { payload, diff --git a/crates/stdlib/src/array.rs b/crates/stdlib/src/array.rs index 4fcba1f8725..49ca4a89037 100644 --- a/crates/stdlib/src/array.rs +++ b/crates/stdlib/src/array.rs @@ -1399,7 +1399,7 @@ mod array { internal: PyMutex>, } - #[pyclass(with(IterNext, Iterable), flags(HAS_DICT))] + #[pyclass(with(IterNext, Iterable), flags(HAS_DICT, DISALLOW_INSTANTIATION))] impl PyArrayIter { #[pymethod] fn __setstate__(&self, state: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index 3c7cc2ff807..792402e0580 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -908,7 +908,7 @@ mod _csv { } } - #[pyclass(with(IterNext, Iterable))] + #[pyclass(with(IterNext, Iterable), flags(DISALLOW_INSTANTIATION))] impl Reader { #[pygetset] fn line_num(&self) -> u64 { @@ -1059,7 +1059,7 @@ mod _csv { } } - #[pyclass] + #[pyclass(flags(DISALLOW_INSTANTIATION))] impl Writer { #[pygetset(name = "dialect")] const fn get_dialect(&self, _vm: &VirtualMachine) -> PyDialect { diff --git a/crates/stdlib/src/pystruct.rs b/crates/stdlib/src/pystruct.rs index 798e5f5de80..0a006f5a0f2 100644 --- a/crates/stdlib/src/pystruct.rs +++ b/crates/stdlib/src/pystruct.rs @@ -16,7 +16,7 @@ pub(crate) mod _struct { function::{ArgBytesLike, ArgMemoryBuffer, PosArgs}, match_class, protocol::PyIterReturn, - types::{Constructor, IterNext, Iterable, Representable, SelfIter, Unconstructible}, + types::{Constructor, IterNext, Iterable, Representable, SelfIter}, }; use crossbeam_utils::atomic::AtomicCell; @@ -189,7 +189,7 @@ pub(crate) mod _struct { } } - #[pyclass(with(Unconstructible, IterNext, Iterable))] + #[pyclass(with(IterNext, Iterable), flags(DISALLOW_INSTANTIATION))] impl UnpackIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -197,7 +197,7 @@ pub(crate) mod _struct { } } impl SelfIter for UnpackIterator {} - impl Unconstructible for UnpackIterator {} + impl IterNext for UnpackIterator { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { let size = zelf.format_spec.size; diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index deff3c3a66a..bc84cffbf80 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -75,7 +75,7 @@ mod _sqlite { sliceable::{SaturatedSliceIter, SliceableSequenceOp}, types::{ AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable, - Initializer, IterNext, Iterable, PyComparisonOp, SelfIter, Unconstructible, + Initializer, IterNext, Iterable, PyComparisonOp, SelfIter, }, utils::ToCString, }; @@ -2197,8 +2197,6 @@ mod _sqlite { inner: PyMutex>, } - impl Unconstructible for Blob {} - #[derive(Debug)] struct BlobInner { blob: SqliteBlob, @@ -2211,7 +2209,7 @@ mod _sqlite { } } - #[pyclass(with(AsMapping, Unconstructible, AsNumber, AsSequence))] + #[pyclass(flags(DISALLOW_INSTANTIATION), with(AsMapping, AsNumber, AsSequence))] impl Blob { #[pymethod] fn close(&self) { @@ -2592,9 +2590,7 @@ mod _sqlite { } } - impl Unconstructible for Statement {} - - #[pyclass(with(Unconstructible))] + #[pyclass(flags(DISALLOW_INSTANTIATION))] impl Statement { fn new( connection: &Connection, diff --git a/crates/stdlib/src/unicodedata.rs b/crates/stdlib/src/unicodedata.rs index 46e18357260..68d9a17e575 100644 --- a/crates/stdlib/src/unicodedata.rs +++ b/crates/stdlib/src/unicodedata.rs @@ -105,7 +105,7 @@ mod unicodedata { } } - #[pyclass] + #[pyclass(flags(DISALLOW_INSTANTIATION))] impl Ucd { #[pymethod] fn category(&self, character: PyStrRef, vm: &VirtualMachine) -> PyResult { diff --git a/crates/stdlib/src/zlib.rs b/crates/stdlib/src/zlib.rs index 328452ae9d5..9ca94939f78 100644 --- a/crates/stdlib/src/zlib.rs +++ b/crates/stdlib/src/zlib.rs @@ -225,7 +225,7 @@ mod zlib { inner: PyMutex, } - #[pyclass] + #[pyclass(flags(DISALLOW_INSTANTIATION))] impl PyDecompress { #[pygetset] fn eof(&self) -> bool { @@ -383,7 +383,7 @@ mod zlib { inner: PyMutex>, } - #[pyclass] + #[pyclass(flags(DISALLOW_INSTANTIATION))] impl PyCompress { #[pymethod] fn compress(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult> { diff --git a/crates/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs index 073513184ff..483ba6a7f96 100644 --- a/crates/vm/src/builtins/asyncgenerator.rs +++ b/crates/vm/src/builtins/asyncgenerator.rs @@ -8,7 +8,7 @@ use crate::{ frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, - types::{IterNext, Iterable, Representable, SelfIter, Unconstructible}, + types::{IterNext, Iterable, Representable, SelfIter}, }; use crossbeam_utils::atomic::AtomicCell; @@ -32,7 +32,7 @@ impl PyPayload for PyAsyncGen { } } -#[pyclass(with(PyRef, Unconstructible, Representable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(PyRef, Representable))] impl PyAsyncGen { pub const fn as_coro(&self) -> &Coro { &self.inner @@ -201,8 +201,6 @@ impl Representable for PyAsyncGen { } } -impl Unconstructible for PyAsyncGen {} - #[pyclass(module = false, name = "async_generator_wrapped_value")] #[derive(Debug)] pub(crate) struct PyAsyncGenWrappedValue(pub PyObjectRef); diff --git a/crates/vm/src/builtins/builtin_func.rs b/crates/vm/src/builtins/builtin_func.rs index d1ce107e374..d25188affd2 100644 --- a/crates/vm/src/builtins/builtin_func.rs +++ b/crates/vm/src/builtins/builtin_func.rs @@ -5,7 +5,7 @@ use crate::{ common::wtf8::Wtf8, convert::TryFromObject, function::{FuncArgs, PyComparisonValue, PyMethodDef, PyMethodFlags, PyNativeFn}, - types::{Callable, Comparable, PyComparisonOp, Representable, Unconstructible}, + types::{Callable, Comparable, PyComparisonOp, Representable}, }; use std::fmt; @@ -74,7 +74,7 @@ impl Callable for PyNativeFunction { } } -#[pyclass(with(Callable, Unconstructible), flags(HAS_DICT))] +#[pyclass(with(Callable), flags(HAS_DICT, DISALLOW_INSTANTIATION))] impl PyNativeFunction { #[pygetset] fn __module__(zelf: NativeFunctionOrMethod) -> Option<&'static PyStrInterned> { @@ -145,8 +145,6 @@ impl Representable for PyNativeFunction { } } -impl Unconstructible for PyNativeFunction {} - // `PyCMethodObject` in CPython #[pyclass(name = "builtin_method", module = false, base = PyNativeFunction, ctx = "builtin_method_type")] pub struct PyNativeMethod { @@ -155,8 +153,8 @@ pub struct PyNativeMethod { } #[pyclass( - with(Unconstructible, Callable, Comparable, Representable), - flags(HAS_DICT) + with(Callable, Comparable, Representable), + flags(HAS_DICT, DISALLOW_INSTANTIATION) )] impl PyNativeMethod { #[pygetset] @@ -246,8 +244,6 @@ impl Representable for PyNativeMethod { } } -impl Unconstructible for PyNativeMethod {} - pub fn init(context: &Context) { PyNativeFunction::extend_class(context, context.types.builtin_function_or_method_type); PyNativeMethod::extend_class(context, context.types.builtin_method_type); diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index 32eaa2b3e27..c5861befb73 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -33,7 +33,7 @@ use crate::{ types::{ AsBuffer, AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, DefaultConstructor, Initializer, IterNext, Iterable, PyComparisonOp, Representable, - SelfIter, Unconstructible, + SelfIter, }, }; use bstr::ByteSlice; @@ -865,7 +865,7 @@ impl PyPayload for PyByteArrayIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyByteArrayIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -886,8 +886,6 @@ impl PyByteArrayIterator { } } -impl Unconstructible for PyByteArrayIterator {} - impl SelfIter for PyByteArrayIterator {} impl IterNext for PyByteArrayIterator { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index 70a33401271..f782c035f86 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -25,7 +25,7 @@ use crate::{ sliceable::{SequenceIndex, SliceableSequenceOp}, types::{ AsBuffer, AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable, - IterNext, Iterable, PyComparisonOp, Representable, SelfIter, Unconstructible, + IterNext, Iterable, PyComparisonOp, Representable, SelfIter, }, }; use bstr::ByteSlice; @@ -749,7 +749,7 @@ impl PyPayload for PyBytesIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyBytesIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -770,7 +770,6 @@ impl PyBytesIterator { .set_state(state, |obj, pos| pos.min(obj.len()), vm) } } -impl Unconstructible for PyBytesIterator {} impl SelfIter for PyBytesIterator {} impl IterNext for PyBytesIterator { diff --git a/crates/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs index 0909cdfb444..21405448693 100644 --- a/crates/vm/src/builtins/coroutine.rs +++ b/crates/vm/src/builtins/coroutine.rs @@ -6,7 +6,7 @@ use crate::{ frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, - types::{IterNext, Iterable, Representable, SelfIter, Unconstructible}, + types::{IterNext, Iterable, Representable, SelfIter}, }; use crossbeam_utils::atomic::AtomicCell; @@ -24,7 +24,7 @@ impl PyPayload for PyCoroutine { } } -#[pyclass(with(Py, Unconstructible, IterNext, Representable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(Py, IterNext, Representable))] impl PyCoroutine { pub const fn as_coro(&self) -> &Coro { &self.inner @@ -123,8 +123,6 @@ impl Py { } } -impl Unconstructible for PyCoroutine {} - impl Representable for PyCoroutine { #[inline] fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index bc3aded3253..cdcc456edfc 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -4,7 +4,7 @@ use crate::{ builtins::{PyTypeRef, builtin_func::PyNativeMethod, type_}, class::PyClassImpl, function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue}, - types::{Callable, GetDescriptor, Representable, Unconstructible}, + types::{Callable, GetDescriptor, Representable}, }; use rustpython_common::lock::PyRwLock; @@ -105,8 +105,8 @@ impl PyMethodDescriptor { } #[pyclass( - with(GetDescriptor, Callable, Unconstructible, Representable), - flags(METHOD_DESCRIPTOR) + with(GetDescriptor, Callable, Representable), + flags(METHOD_DESCRIPTOR, DISALLOW_INSTANTIATION) )] impl PyMethodDescriptor { #[pygetset] @@ -159,8 +159,6 @@ impl Representable for PyMethodDescriptor { } } -impl Unconstructible for PyMethodDescriptor {} - #[derive(Debug)] pub enum MemberKind { Bool = 14, @@ -246,7 +244,10 @@ fn calculate_qualname(descr: &PyDescriptorOwned, vm: &VirtualMachine) -> PyResul } } -#[pyclass(with(GetDescriptor, Unconstructible, Representable), flags(BASETYPE))] +#[pyclass( + with(GetDescriptor, Representable), + flags(BASETYPE, DISALLOW_INSTANTIATION) +)] impl PyMemberDescriptor { #[pygetset] fn __doc__(&self) -> Option { @@ -339,8 +340,6 @@ fn set_slot_at_object( Ok(()) } -impl Unconstructible for PyMemberDescriptor {} - impl Representable for PyMemberDescriptor { #[inline] fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 77126d4ee62..b34299d4170 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -19,7 +19,7 @@ use crate::{ recursion::ReprGuard, types::{ AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, DefaultConstructor, - Initializer, IterNext, Iterable, PyComparisonOp, Representable, SelfIter, Unconstructible, + Initializer, IterNext, Iterable, PyComparisonOp, Representable, SelfIter, }, vm::VirtualMachine, }; @@ -848,7 +848,7 @@ macro_rules! dict_view { } } - #[pyclass(with(Unconstructible, IterNext, Iterable))] + #[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl $iter_name { fn new(dict: PyDictRef) -> Self { $iter_name { @@ -878,8 +878,6 @@ macro_rules! dict_view { } } - impl Unconstructible for $iter_name {} - impl SelfIter for $iter_name {} impl IterNext for $iter_name { #[allow(clippy::redundant_closure_call)] @@ -923,7 +921,7 @@ macro_rules! dict_view { } } - #[pyclass(with(Unconstructible, IterNext, Iterable))] + #[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl $reverse_iter_name { fn new(dict: PyDictRef) -> Self { let size = dict.size(); @@ -957,8 +955,6 @@ macro_rules! dict_view { .rev_length_hint(|_| self.size.entries_size) } } - impl Unconstructible for $reverse_iter_name {} - impl SelfIter for $reverse_iter_name {} impl IterNext for $reverse_iter_name { #[allow(clippy::redundant_closure_call)] @@ -1126,16 +1122,18 @@ trait ViewSetOps: DictView { } impl ViewSetOps for PyDictKeys {} -#[pyclass(with( - DictView, - Unconstructible, - Comparable, - Iterable, - ViewSetOps, - AsSequence, - AsNumber, - Representable -))] +#[pyclass( + flags(DISALLOW_INSTANTIATION), + with( + DictView, + Comparable, + Iterable, + ViewSetOps, + AsSequence, + AsNumber, + Representable + ) +)] impl PyDictKeys { #[pymethod] fn __contains__(zelf: PyObjectRef, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -1147,7 +1145,6 @@ impl PyDictKeys { PyMappingProxy::from(zelf.dict().clone()) } } -impl Unconstructible for PyDictKeys {} impl Comparable for PyDictKeys { fn cmp( @@ -1190,16 +1187,18 @@ impl AsNumber for PyDictKeys { } impl ViewSetOps for PyDictItems {} -#[pyclass(with( - DictView, - Unconstructible, - Comparable, - Iterable, - ViewSetOps, - AsSequence, - AsNumber, - Representable -))] +#[pyclass( + flags(DISALLOW_INSTANTIATION), + with( + DictView, + Comparable, + Iterable, + ViewSetOps, + AsSequence, + AsNumber, + Representable + ) +)] impl PyDictItems { #[pymethod] fn __contains__(zelf: PyObjectRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -1210,7 +1209,6 @@ impl PyDictItems { PyMappingProxy::from(zelf.dict().clone()) } } -impl Unconstructible for PyDictItems {} impl Comparable for PyDictItems { fn cmp( @@ -1264,14 +1262,16 @@ impl AsNumber for PyDictItems { } } -#[pyclass(with(DictView, Unconstructible, Iterable, AsSequence, Representable))] +#[pyclass( + flags(DISALLOW_INSTANTIATION), + with(DictView, Iterable, AsSequence, Representable) +)] impl PyDictValues { #[pygetset] fn mapping(zelf: PyRef) -> PyMappingProxy { PyMappingProxy::from(zelf.dict().clone()) } } -impl Unconstructible for PyDictValues {} impl AsSequence for PyDictValues { fn as_sequence() -> &'static PySequenceMethods { diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 17dc88ac042..6ccc594d338 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -8,7 +8,7 @@ use crate::{ class::PyClassImpl, frame::{Frame, FrameRef}, function::PySetterValue, - types::{Representable, Unconstructible}, + types::Representable, }; use num_traits::Zero; @@ -16,8 +16,6 @@ pub fn init(context: &Context) { Frame::extend_class(context, context.types.frame_type); } -impl Unconstructible for Frame {} - impl Representable for Frame { #[inline] fn repr(_zelf: &Py, vm: &VirtualMachine) -> PyResult { @@ -31,7 +29,7 @@ impl Representable for Frame { } } -#[pyclass(with(Unconstructible, Py))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(Py))] impl Frame { #[pymethod] const fn clear(&self) { diff --git a/crates/vm/src/builtins/generator.rs b/crates/vm/src/builtins/generator.rs index da981b5a6c2..04cd7dd3456 100644 --- a/crates/vm/src/builtins/generator.rs +++ b/crates/vm/src/builtins/generator.rs @@ -10,7 +10,7 @@ use crate::{ frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, - types::{IterNext, Iterable, Representable, SelfIter, Unconstructible}, + types::{IterNext, Iterable, Representable, SelfIter}, }; #[pyclass(module = false, name = "generator")] @@ -26,7 +26,7 @@ impl PyPayload for PyGenerator { } } -#[pyclass(with(Py, Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(Py, IterNext, Iterable))] impl PyGenerator { pub const fn as_coro(&self) -> &Coro { &self.inner @@ -114,8 +114,6 @@ impl Py { } } -impl Unconstructible for PyGenerator {} - impl Representable for PyGenerator { #[inline] fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { diff --git a/crates/vm/src/builtins/getset.rs b/crates/vm/src/builtins/getset.rs index 4b966bbc31b..f56191f5f8b 100644 --- a/crates/vm/src/builtins/getset.rs +++ b/crates/vm/src/builtins/getset.rs @@ -7,7 +7,7 @@ use crate::{ builtins::type_::PointerSlot, class::PyClassImpl, function::{IntoPyGetterFunc, IntoPySetterFunc, PyGetterFunc, PySetterFunc, PySetterValue}, - types::{GetDescriptor, Unconstructible}, + types::GetDescriptor, }; #[pyclass(module = false, name = "getset_descriptor")] @@ -96,7 +96,7 @@ impl PyGetSet { } } -#[pyclass(with(GetDescriptor, Unconstructible))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(GetDescriptor))] impl PyGetSet { // Descriptor methods @@ -152,7 +152,6 @@ impl PyGetSet { Ok(unsafe { zelf.class.borrow_static() }.to_owned().into()) } } -impl Unconstructible for PyGetSet {} pub(crate) fn init(context: &Context) { PyGetSet::extend_class(context, context.types.getset_type); diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index b2927462cac..0683381f38a 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -15,7 +15,7 @@ use crate::{ sliceable::{SequenceIndex, SliceableSequenceMutOp, SliceableSequenceOp}, types::{ AsMapping, AsSequence, Comparable, Constructor, Initializer, IterNext, Iterable, - PyComparisonOp, Representable, SelfIter, Unconstructible, + PyComparisonOp, Representable, SelfIter, }, utils::collection_repr, vm::VirtualMachine, @@ -544,7 +544,7 @@ impl PyPayload for PyListIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyListIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -565,7 +565,6 @@ impl PyListIterator { .builtins_iter_reduce(|x| x.clone().into(), vm) } } -impl Unconstructible for PyListIterator {} impl SelfIter for PyListIterator {} impl IterNext for PyListIterator { @@ -590,7 +589,7 @@ impl PyPayload for PyListReverseIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyListReverseIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -611,7 +610,6 @@ impl PyListReverseIterator { .builtins_reversed_reduce(|x| x.clone().into(), vm) } } -impl Unconstructible for PyListReverseIterator {} impl SelfIter for PyListReverseIterator {} impl IterNext for PyListReverseIterator { diff --git a/crates/vm/src/builtins/memory.rs b/crates/vm/src/builtins/memory.rs index c1b1496e8c6..4e895f92b7e 100644 --- a/crates/vm/src/builtins/memory.rs +++ b/crates/vm/src/builtins/memory.rs @@ -23,7 +23,7 @@ use crate::{ sliceable::SequenceIndexOp, types::{ AsBuffer, AsMapping, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable, - PyComparisonOp, Representable, SelfIter, Unconstructible, + PyComparisonOp, Representable, SelfIter, }, }; use crossbeam_utils::atomic::AtomicCell; @@ -1132,7 +1132,7 @@ impl PyPayload for PyMemoryViewIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyMemoryViewIterator { #[pymethod] fn __reduce__(&self, vm: &VirtualMachine) -> PyTupleRef { @@ -1141,7 +1141,6 @@ impl PyMemoryViewIterator { .builtins_iter_reduce(|x| x.clone().into(), vm) } } -impl Unconstructible for PyMemoryViewIterator {} impl SelfIter for PyMemoryViewIterator {} impl IterNext for PyMemoryViewIterator { diff --git a/crates/vm/src/builtins/range.rs b/crates/vm/src/builtins/range.rs index 3edd130ee28..9f79f8efb2d 100644 --- a/crates/vm/src/builtins/range.rs +++ b/crates/vm/src/builtins/range.rs @@ -11,7 +11,7 @@ use crate::{ protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods}, types::{ AsMapping, AsSequence, Comparable, Hashable, IterNext, Iterable, PyComparisonOp, - Representable, SelfIter, Unconstructible, + Representable, SelfIter, }, }; use crossbeam_utils::atomic::AtomicCell; @@ -548,7 +548,7 @@ impl PyPayload for PyLongRangeIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyLongRangeIterator { #[pymethod] fn __length_hint__(&self) -> BigInt { @@ -577,7 +577,6 @@ impl PyLongRangeIterator { ) } } -impl Unconstructible for PyLongRangeIterator {} impl SelfIter for PyLongRangeIterator {} impl IterNext for PyLongRangeIterator { @@ -614,7 +613,7 @@ impl PyPayload for PyRangeIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyRangeIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -640,7 +639,6 @@ impl PyRangeIterator { ) } } -impl Unconstructible for PyRangeIterator {} impl SelfIter for PyRangeIterator {} impl IterNext for PyRangeIterator { diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index 7fde8d32781..5582ff3323c 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -18,7 +18,7 @@ use crate::{ types::AsNumber, types::{ AsSequence, Comparable, Constructor, DefaultConstructor, Hashable, Initializer, IterNext, - Iterable, PyComparisonOp, Representable, SelfIter, Unconstructible, + Iterable, PyComparisonOp, Representable, SelfIter, }, utils::collection_repr, vm::VirtualMachine, @@ -1304,7 +1304,7 @@ impl PyPayload for PySetIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PySetIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -1330,7 +1330,6 @@ impl PySetIterator { )) } } -impl Unconstructible for PySetIterator {} impl SelfIter for PySetIterator {} impl IterNext for PySetIterator { diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 9b05e195722..279b84362a6 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -21,7 +21,7 @@ use crate::{ sliceable::{SequenceIndex, SliceableSequenceOp}, types::{ AsMapping, AsNumber, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable, - PyComparisonOp, Representable, SelfIter, Unconstructible, + PyComparisonOp, Representable, SelfIter, }, }; use ascii::{AsciiChar, AsciiStr, AsciiString}; @@ -282,7 +282,7 @@ impl PyPayload for PyStrIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyStrIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -307,8 +307,6 @@ impl PyStrIterator { } } -impl Unconstructible for PyStrIterator {} - impl SelfIter for PyStrIterator {} impl IterNext for PyStrIterator { diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index fada8840bb1..adc5b483de3 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -16,7 +16,7 @@ use crate::{ sliceable::{SequenceIndex, SliceableSequenceOp}, types::{ AsMapping, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable, - PyComparisonOp, Representable, SelfIter, Unconstructible, + PyComparisonOp, Representable, SelfIter, }, utils::collection_repr, vm::VirtualMachine, @@ -533,7 +533,7 @@ impl PyPayload for PyTupleIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyTupleIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -554,7 +554,6 @@ impl PyTupleIterator { .builtins_iter_reduce(|x| x.clone().into(), vm) } } -impl Unconstructible for PyTupleIterator {} impl SelfIter for PyTupleIterator {} impl IterNext for PyTupleIterator { diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 0f619b1399a..d014cdf015e 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -191,13 +191,16 @@ impl PyType { name: &str, bases: Vec>, attrs: PyAttributes, - slots: PyTypeSlots, + mut slots: PyTypeSlots, metaclass: PyRef, ctx: &Context, ) -> Result, String> { // TODO: ensure clean slot name // assert_eq!(slots.name.borrow(), ""); + // Set HEAPTYPE flag for heap-allocated types + slots.flags |= PyTypeFlags::HEAPTYPE; + let name = ctx.new_str(name); let heaptype_ext = HeapTypeExt { name: PyRwLock::new(name.clone()), @@ -401,6 +404,8 @@ impl PyType { None, ); + Self::set_new(&new_type.slots, &new_type.base); + let weakref_type = super::PyWeak::static_type(); for base in new_type.bases.read().iter() { base.subclasses.write().push( @@ -420,9 +425,6 @@ impl PyType { for cls in self.mro.read().iter() { for &name in cls.attributes.read().keys() { - if name == identifier!(ctx, __new__) { - continue; - } if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") { slot_name_set.insert(name); } @@ -436,6 +438,20 @@ impl PyType { for attr_name in slot_name_set { self.update_slot::(attr_name, ctx); } + + Self::set_new(&self.slots, &self.base); + } + + fn set_new(slots: &PyTypeSlots, base: &Option) { + if slots.flags.contains(PyTypeFlags::DISALLOW_INSTANTIATION) { + slots.new.store(None) + } else if slots.new.load().is_none() { + slots.new.store( + base.as_ref() + .map(|base| base.slots.new.load()) + .unwrap_or(None), + ) + } } // This is used for class initialization where the vm is not yet available. @@ -1563,15 +1579,28 @@ impl Callable for PyType { type Args = FuncArgs; fn call(zelf: &Py, args: FuncArgs, vm: &VirtualMachine) -> PyResult { vm_trace!("type_call: {:?}", zelf); - let obj = call_slot_new(zelf.to_owned(), zelf.to_owned(), args.clone(), vm)?; - if (zelf.is(vm.ctx.types.type_type) && args.kwargs.is_empty()) || !obj.fast_isinstance(zelf) - { + if zelf.is(vm.ctx.types.type_type) { + let num_args = args.args.len(); + if num_args == 1 && args.kwargs.is_empty() { + return Ok(args.args[0].obj_type()); + } + if num_args != 3 { + return Err(vm.new_type_error("type() takes 1 or 3 arguments".to_owned())); + } + } + + let obj = if let Some(slot_new) = zelf.slots.new.load() { + slot_new(zelf.to_owned(), args.clone(), vm)? + } else { + return Err(vm.new_type_error(format!("cannot create '{}' instances", zelf.slots.name))); + }; + + if !obj.class().fast_issubclass(zelf) { return Ok(obj); } - let init = obj.class().mro_find_map(|cls| cls.slots.init.load()); - if let Some(init_method) = init { + if let Some(init_method) = obj.class().slots.init.load() { init_method(obj.clone(), args, vm)?; } Ok(obj) @@ -1700,6 +1729,40 @@ pub(crate) fn call_slot_new( args: FuncArgs, vm: &VirtualMachine, ) -> PyResult { + // Check DISALLOW_INSTANTIATION flag on subtype (the type being instantiated) + if subtype + .slots + .flags + .has_feature(PyTypeFlags::DISALLOW_INSTANTIATION) + { + return Err(vm.new_type_error(format!("cannot create '{}' instances", subtype.slot_name()))); + } + + // "is not safe" check (tp_new_wrapper logic) + // Check that the user doesn't do something silly and unsafe like + // object.__new__(dict). To do this, we check that the most derived base + // that's not a heap type is this type. + let mut staticbase = subtype.clone(); + while staticbase.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) { + if let Some(base) = staticbase.base.as_ref() { + staticbase = base.clone(); + } else { + break; + } + } + + // Check if staticbase's tp_new differs from typ's tp_new + let typ_new = typ.slots.new.load(); + let staticbase_new = staticbase.slots.new.load(); + if typ_new.map(|f| f as usize) != staticbase_new.map(|f| f as usize) { + return Err(vm.new_type_error(format!( + "{}.__new__({}) is not safe, use {}.__new__()", + typ.slot_name(), + subtype.slot_name(), + staticbase.slot_name() + ))); + } + let slot_new = typ .deref() .mro_find_map(|cls| cls.slots.new.load()) diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 6b5a02dea73..6a366385702 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -116,13 +116,23 @@ pub trait PyClassImpl: PyClassDef { ); } - if class.slots.new.load().is_some() { - let bound_new = Context::genesis().slot_new_wrapper.build_bound_method( - ctx, - class.to_owned().into(), - class, - ); - class.set_attr(identifier!(ctx, __new__), bound_new.into()); + // Don't add __new__ attribute if slot_new is inherited from object + // (Python doesn't add __new__ to __dict__ for inherited slots) + // Exception: object itself should have __new__ in its dict + if let Some(slot_new) = class.slots.new.load() { + let object_new = ctx.types.object_type.slots.new.load(); + let is_object_itself = std::ptr::eq(class, ctx.types.object_type); + let is_inherited_from_object = !is_object_itself + && object_new.is_some_and(|obj_new| slot_new as usize == obj_new as usize); + + if !is_inherited_from_object { + let bound_new = Context::genesis().slot_new_wrapper.build_bound_method( + ctx, + class.to_owned().into(), + class, + ); + class.set_attr(identifier!(ctx, __new__), bound_new.into()); + } } if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize { diff --git a/crates/vm/src/protocol/buffer.rs b/crates/vm/src/protocol/buffer.rs index 1b1a4a14df5..1dafda203d9 100644 --- a/crates/vm/src/protocol/buffer.rs +++ b/crates/vm/src/protocol/buffer.rs @@ -9,7 +9,6 @@ use crate::{ }, object::PyObjectPayload, sliceable::SequenceIndexOp, - types::Unconstructible, }; use itertools::Itertools; use std::{borrow::Cow, fmt::Debug, ops::Range}; @@ -402,7 +401,7 @@ pub struct VecBuffer { data: PyMutex>, } -#[pyclass(flags(BASETYPE), with(Unconstructible))] +#[pyclass(flags(BASETYPE, DISALLOW_INSTANTIATION))] impl VecBuffer { pub fn take(&self) -> Vec { std::mem::take(&mut self.data.lock()) @@ -417,8 +416,6 @@ impl From> for VecBuffer { } } -impl Unconstructible for VecBuffer {} - impl PyRef { pub fn into_pybuffer(self, readonly: bool) -> PyBuffer { let len = self.data.lock().len(); diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index e760f07d035..659255f3329 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -1,6 +1,6 @@ use crate::builtins::PyType; use crate::function::PySetterValue; -use crate::types::{GetDescriptor, Representable, Unconstructible}; +use crate::types::{GetDescriptor, Representable}; use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; use num_traits::ToPrimitive; @@ -85,8 +85,6 @@ impl Representable for PyCField { } } -impl Unconstructible for PyCField {} - impl GetDescriptor for PyCField { fn descr_get( zelf: PyObjectRef, @@ -184,7 +182,7 @@ impl PyCField { #[pyclass( flags(DISALLOW_INSTANTIATION, IMMUTABLETYPE), - with(Unconstructible, Representable, GetDescriptor) + with(Representable, GetDescriptor) )] impl PyCField { #[pyslot] diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index f6ffd66759d..2bf6c6cb4d1 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -165,7 +165,7 @@ pub(super) mod _os { ospath::{IOErrorBuilder, OsPath, OsPathOrFd, OutputMode}, protocol::PyIterReturn, recursion::ReprGuard, - types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter, Unconstructible}, + types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter}, utils::ToCString, vm::VirtualMachine, }; @@ -568,7 +568,7 @@ pub(super) mod _os { ino: AtomicCell>, } - #[pyclass(with(Representable, Unconstructible))] + #[pyclass(flags(DISALLOW_INSTANTIATION), with(Representable))] impl DirEntry { #[pygetset] fn name(&self, vm: &VirtualMachine) -> PyResult { @@ -758,8 +758,6 @@ pub(super) mod _os { } } } - impl Unconstructible for DirEntry {} - #[pyattr] #[pyclass(name = "ScandirIter")] #[derive(Debug, PyPayload)] @@ -768,7 +766,7 @@ pub(super) mod _os { mode: OutputMode, } - #[pyclass(with(IterNext, Iterable, Unconstructible))] + #[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl ScandirIterator { #[pymethod] fn close(&self) { @@ -786,7 +784,6 @@ pub(super) mod _os { zelf.close() } } - impl Unconstructible for ScandirIterator {} impl SelfIter for ScandirIterator {} impl IterNext for ScandirIterator { fn next(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index f52e7296a7b..5deb593818e 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -922,15 +922,6 @@ pub trait DefaultConstructor: PyPayload + Default + std::fmt::Debug { } } -/// For types that cannot be instantiated through Python code. -#[pyclass] -pub trait Unconstructible: PyPayload { - #[pyslot] - fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(format!("cannot create '{}' instances", cls.slot_name()))) - } -} - impl Constructor for T where T: DefaultConstructor,