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
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-10-27 10:07:06 +09:00
1 parent 6934845753
commit 20042568fc
6 files changed
+112 -16

No files matched your search

+22 -2
View File
@@ -965,7 +965,17 @@ pub(crate) mod _asyncio {
future: PyRwLock<Option<PyObjectRef>>,
}
#[pyclass(with(IterNext, Iterable))]
impl Constructor for PyFutureIter {
type Args = ();
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self {
future: PyRwLock::new(None),
})
}
}
#[pyclass(with(Constructor, IterNext, Iterable))]
impl PyFutureIter {
#[pymethod]
fn send(zelf: &Py<Self>, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
@@ -2926,7 +2936,17 @@ pub(crate) mod _asyncio {
task: PyRwLock<Option<PyObjectRef>>,
}
#[pyclass(with(Callable, Representable))]
impl Constructor for TaskStepMethWrapper {
type Args = ();
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self {
task: PyRwLock::new(None),
})
}
}
#[pyclass(with(Constructor, Callable, Representable))]
impl TaskStepMethWrapper {
fn new(task: PyObjectRef) -> Self {
Self {
+33 -6
View File
@@ -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::<true>(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<Self>>) {
/// 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<Self>>, 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>) {
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());
}
+8 -2
View File
@@ -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
+1 -1
View File
@@ -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]
+24 -4
View File
@@ -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<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self::default())
}
}
#[pyclass(
flags(HAS_WEAKREF),
with(Constructor, AsMapping, AsSequence, Comparable, Iterable)
)]
impl Py<Hamt> {
#[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<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self::empty())
}
}
#[cfg(feature = "codegen")]
#[pyclass(with(Constructor, Py))]
impl PyInstructionSequence {}
#[cfg(feature = "codegen")]
+24 -1
View File
@@ -706,7 +706,30 @@ mod decl {
tgtkey: PyObjectRef,
}
#[pyclass(with(IterNext, Iterable), flags(HAS_WEAKREF))]
#[derive(FromArgs)]
struct GrouperArgs {
#[pyarg(positional)]
parent: PyRef<PyItertoolsGroupBy>,
#[pyarg(positional)]
tgtkey: PyObjectRef,
}
impl Constructor for PyItertoolsGrouper {
type Args = GrouperArgs;
fn py_new(
_cls: &Py<PyType>,
Self::Args { parent, tgtkey }: Self::Args,
_vm: &VirtualMachine,
) -> PyResult<Self> {
Ok(Self {
groupby: parent,
tgtkey,
})
}
}
#[pyclass(with(Constructor, IterNext, Iterable), flags(HAS_WEAKREF))]
impl PyItertoolsGrouper {}
impl SelfIter for PyItertoolsGrouper {}