From 20042568fc238bb1e555c5a9050102129e449f8e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:07:06 +0900 Subject: [PATCH] Decide tp_new inheritance of static types at type initialization (#9386) A static type with no tp_new of its own whose base is object now gets DISALLOW_INSTANTIATION instead of inheriting object's tp_new, following type_ready_set_new. Such types no longer expose an inherited __new__. A static type that inherits a non-NULL tp_new from its base must have the same basicsize as that base; type initialization panics otherwise. Types flagged HEAPTYPE are exempt, like classes created at runtime. _IOBase is flagged HEAPTYPE so that it keeps inheriting object's tp_new. FutureIter, TaskStepMethWrapper, hamt, InstructionSequence and itertools._grouper get constructors of their own. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 --- crates/stdlib/src/_asyncio.rs | 24 ++++++++++++-- crates/vm/src/builtins/type.rs | 39 +++++++++++++++++++---- crates/vm/src/class.rs | 10 ++++-- crates/vm/src/stdlib/_io.rs | 2 +- crates/vm/src/stdlib/_testinternalcapi.rs | 28 +++++++++++++--- crates/vm/src/stdlib/itertools.rs | 25 ++++++++++++++- 6 files changed, 112 insertions(+), 16 deletions(-) diff --git a/crates/stdlib/src/_asyncio.rs b/crates/stdlib/src/_asyncio.rs index 8da4ab38d..dd001557a 100644 --- a/crates/stdlib/src/_asyncio.rs +++ b/crates/stdlib/src/_asyncio.rs @@ -965,7 +965,17 @@ pub(crate) mod _asyncio { future: PyRwLock>, } - #[pyclass(with(IterNext, Iterable))] + impl Constructor for PyFutureIter { + type Args = (); + + fn py_new(_cls: &Py, _args: Self::Args, _vm: &VirtualMachine) -> PyResult { + Ok(Self { + future: PyRwLock::new(None), + }) + } + } + + #[pyclass(with(Constructor, IterNext, Iterable))] impl PyFutureIter { #[pymethod] fn send(zelf: &Py, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -2926,7 +2936,17 @@ pub(crate) mod _asyncio { task: PyRwLock>, } - #[pyclass(with(Callable, Representable))] + impl Constructor for TaskStepMethWrapper { + type Args = (); + + fn py_new(_cls: &Py, _args: Self::Args, _vm: &VirtualMachine) -> PyResult { + Ok(Self { + task: PyRwLock::new(None), + }) + } + } + + #[pyclass(with(Constructor, Callable, Representable))] impl TaskStepMethWrapper { fn new(task: PyObjectRef) -> Self { Self { diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 8b0ceea13..da7cb64e5 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1219,7 +1219,7 @@ impl PyType { // Note: inherit_slots is called in PyClassImpl::init_class after // slots are fully initialized by make_slots() - Self::set_new(&new_type.slots, new_type.base.deref()); + Self::set_new(&new_type.slots, new_type.base.deref(), false); Self::set_alloc(&new_type.slots, new_type.base.deref()); let weakref_type = super::PyWeak::static_type(); @@ -1271,7 +1271,7 @@ impl PyType { self.update_slot::(attr_name, ctx); } - Self::set_new(&self.slots, self.base.deref()); + Self::set_new(&self.slots, self.base.deref(), true); Self::set_alloc(&self.slots, self.base.deref()); } @@ -1295,11 +1295,38 @@ impl PyType { } } - fn set_new(slots: &PyTypeSlots, base: Option<&Py>) { + /// type_ready_set_new: a static type with no tp_new whose base is object + /// gets DISALLOW_INSTANTIATION and keeps tp_new unset. Heap types inherit + /// the base's tp_new. A static type that inherits a non-NULL tp_new must + /// have the same basicsize as that base, because the inherited function + /// allocates the base payload. Heap types, including native types flagged + /// HEAPTYPE, reuse the base payload like classes created at runtime. + fn set_new(slots: &PyTypeSlots, base: Option<&Py>, heap: bool) { + let is_heap = heap || slots.flags.has_feature(PyTypeFlags::HEAPTYPE); + + if !is_heap + && slots.new.load().is_none() + && base.is_some_and(|b| core::ptr::eq(b, object::PyBaseObject::static_type())) + { + slots.flags.insert(PyTypeFlags::DISALLOW_INSTANTIATION); + } + if slots.flags.has_feature(PyTypeFlags::DISALLOW_INSTANTIATION) { - slots.new.store(None) + slots.new.store(None); } else if slots.new.load().is_none() { - slots.new.store(base.and_then(|base| base.slots.new.load())) + let inherited = base.and_then(|base| base.slots.new.load()); + if !is_heap + && inherited.is_some() + && let Some(base) = base + && slots.basicsize != base.slots.basicsize + { + panic!( + "static type '{}' inherits tp_new from '{}' but basicsize {} != {}; \ + the inherited tp_new allocates the base payload", + slots.name, base.slots.name, slots.basicsize, base.slots.basicsize + ); + } + slots.new.store(inherited); } } @@ -1312,7 +1339,7 @@ impl PyType { } pub(crate) fn finalize_bootstrap_static(typ: &Py) { - Self::set_new(&typ.slots, typ.base.deref()); + Self::set_new(&typ.slots, typ.base.deref(), false); Self::set_alloc(&typ.slots, typ.base.deref()); } diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index fe6559686..24579d554 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -367,8 +367,14 @@ pub trait PyClassImpl: PyClassDef { // 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() { + // Exception: object itself should have __new__ in its dict. + // DISALLOW_INSTANTIATION types must not expose an inherited __new__. + if !class + .slots() + .flags + .has_feature(PyTypeFlags::DISALLOW_INSTANTIATION) + && let Some(slot_new) = class.slots().new.load() + { let object_new = ctx.types.object_type.slots().new.load(); let is_object_itself = core::ptr::eq(class, ctx.types.object_type); let is_inherited_from_object = !is_object_itself diff --git a/crates/vm/src/stdlib/_io.rs b/crates/vm/src/stdlib/_io.rs index b831e4f22..6ce8a6453 100644 --- a/crates/vm/src/stdlib/_io.rs +++ b/crates/vm/src/stdlib/_io.rs @@ -520,7 +520,7 @@ mod _io { #[pyclass( with(IterNext, Iterable, Destructor), - flags(BASETYPE, HAS_DICT, HAS_WEAKREF) + flags(BASETYPE, HAS_DICT, HAS_WEAKREF, HEAPTYPE) )] impl _IOBase { #[pymethod] diff --git a/crates/vm/src/stdlib/_testinternalcapi.rs b/crates/vm/src/stdlib/_testinternalcapi.rs index 945a7aaae..c31db7747 100644 --- a/crates/vm/src/stdlib/_testinternalcapi.rs +++ b/crates/vm/src/stdlib/_testinternalcapi.rs @@ -42,8 +42,8 @@ mod _testinternalcapi { protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods}, stdlib::time::pytime::{self, Round}, types::{ - AsMapping, AsSequence, Comparable, IterNext, Iterable, PyComparisonOp, PyTypeFlags, - SelfIter, + AsMapping, AsSequence, Comparable, Constructor, IterNext, Iterable, PyComparisonOp, + PyTypeFlags, SelfIter, }, }; #[cfg(any(unix, windows))] @@ -779,7 +779,18 @@ mod _testinternalcapi { } } - #[pyclass(flags(HAS_WEAKREF), with(AsMapping, AsSequence, Comparable, Iterable))] + impl Constructor for Hamt { + type Args = (); + + fn py_new(_cls: &Py, _args: Self::Args, _vm: &VirtualMachine) -> PyResult { + Ok(Self::default()) + } + } + + #[pyclass( + flags(HAS_WEAKREF), + with(Constructor, AsMapping, AsSequence, Comparable, Iterable) + )] impl Py { #[pymethod] fn set( @@ -1122,7 +1133,16 @@ mod _testinternalcapi { } #[cfg(feature = "codegen")] - #[pyclass(with(Py))] + impl Constructor for PyInstructionSequence { + type Args = (); + + fn py_new(_cls: &Py, _args: Self::Args, _vm: &VirtualMachine) -> PyResult { + Ok(Self::empty()) + } + } + + #[cfg(feature = "codegen")] + #[pyclass(with(Constructor, Py))] impl PyInstructionSequence {} #[cfg(feature = "codegen")] diff --git a/crates/vm/src/stdlib/itertools.rs b/crates/vm/src/stdlib/itertools.rs index d314779cf..8bef6a113 100644 --- a/crates/vm/src/stdlib/itertools.rs +++ b/crates/vm/src/stdlib/itertools.rs @@ -706,7 +706,30 @@ mod decl { tgtkey: PyObjectRef, } - #[pyclass(with(IterNext, Iterable), flags(HAS_WEAKREF))] + #[derive(FromArgs)] + struct GrouperArgs { + #[pyarg(positional)] + parent: PyRef, + #[pyarg(positional)] + tgtkey: PyObjectRef, + } + + impl Constructor for PyItertoolsGrouper { + type Args = GrouperArgs; + + fn py_new( + _cls: &Py, + Self::Args { parent, tgtkey }: Self::Args, + _vm: &VirtualMachine, + ) -> PyResult { + Ok(Self { + groupby: parent, + tgtkey, + }) + } + } + + #[pyclass(with(Constructor, IterNext, Iterable), flags(HAS_WEAKREF))] impl PyItertoolsGrouper {} impl SelfIter for PyItertoolsGrouper {}