Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Lib/test/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1882,7 +1882,6 @@ class D(dict):
D.__getitem__ = dict.__getitem__
self.assertIs(d[None], None)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <class 'tuple'> != <class 'test.test_types.ClassCreationTests.test_tu[41 chars]ass'>
def test_tuple_subclass_as_bases(self):
# gh-132176: it used to crash on using
# tuple subclass for as base classes.
Expand Down
17 changes: 17 additions & 0 deletions crates/vm/src/builtins/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,23 @@ impl PyTuple<PyObjectRef> {
}

impl<T> PyTuple<PyRef<T>> {
pub(crate) fn new_ref_typed_with_type(
elements: Vec<PyRef<T>>,
tuple_type: PyTypeRef,
) -> PyRef<Self> {
// SAFETY: PyRef<T> has the same layout as PyObjectRef.
unsafe {
let elements: Vec<PyObjectRef> =
core::mem::transmute::<Vec<PyRef<T>>, Vec<PyObjectRef>>(elements);
let tuple = PyRef::new_ref(
PyTuple::new_unchecked(elements.into_boxed_slice()),
tuple_type,
None,
);
core::mem::transmute::<PyRef<PyTuple>, PyRef<Self>>(tuple)
}
}

pub fn new_ref_typed(elements: Vec<PyRef<T>>, ctx: &Context) -> PyRef<Self> {
// SAFETY: PyRef<T> has the same layout as PyObjectRef
unsafe {
Expand Down
89 changes: 50 additions & 39 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ use num_traits::ToPrimitive;
use rustpython_common::wtf8::Wtf8;
use std::collections::HashSet;

pub(crate) type PyTypeTupleRef = PyRef<PyTuple<PyTypeRef>>;

#[pyclass(module = false, name = "type", traverse = "manual")]
pub struct PyType {
/// tp_base. Written under the type lock (see `set_bases`); read lock-free.
pub base: PyAtomicRef<Option<Self>>,
pub bases: PyRwLock<Vec<PyTypeRef>>,
pub bases: PyRwLock<PyTypeTupleRef>,
pub mro: PyRwLock<Vec<PyTypeRef>>,
pub subclasses: PyRwLock<Vec<PyRef<PyWeak>>>,
pub attributes: PyRwLock<PyAttributes>,
Expand Down Expand Up @@ -242,7 +244,7 @@ unsafe impl crate::object::Traverse for PyType {
if let Some(base) = self.base.deref() {
tracer_fn(base.as_object());
}
self.bases.traverse(tracer_fn);
tracer_fn(self.bases.read_recursive().as_untyped().as_object());
self.mro.traverse(tracer_fn);
self.subclasses.traverse(tracer_fn);
self.attributes
Expand All @@ -261,10 +263,10 @@ unsafe impl crate::object::Traverse for PyType {
if let Some(base) = unsafe { self.base.swap(None) } {
out.push(base.into());
}
if let Some(mut guard) = self.bases.try_write() {
for base in guard.drain(..) {
out.push(base.into());
}
if let Some(mut bases) = self.bases.try_write() {
let empty = object::PyBaseObject::static_type().bases.read().clone();
let old_bases = core::mem::replace(&mut *bases, empty);
out.push(old_bases.into_untyped().into());
}
if let Some(mut guard) = self.mro.try_write() {
for typ in guard.drain(..) {
Expand Down Expand Up @@ -590,6 +592,7 @@ impl PyType {
type_data: PyRwLock::new(None),
specialization_cache: TypeSpecializationCache::new(),
};
let bases = PyTuple::new_ref_typed(bases, ctx);
let base = bases[0].clone();

Self::new_heap_inner(base, bases, attrs, slots, heaptype_ext, metaclass, ctx)
Expand Down Expand Up @@ -758,7 +761,7 @@ impl PyType {
#[allow(clippy::too_many_arguments)]
fn new_heap_inner(
base: PyRef<Self>,
bases: Vec<PyRef<Self>>,
bases: PyTypeTupleRef,
attrs: PyAttributes,
mut slots: PyTypeSlots,
heaptype_ext: HeapTypeExt,
Expand Down Expand Up @@ -872,13 +875,14 @@ impl PyType {
}

let inherited_abc_tpflags = Self::inherited_abc_tpflags(core::slice::from_ref(&base));
let bases = PyRwLock::new(vec![base.clone()]);
let bases =
PyTuple::new_ref_typed_with_type(vec![base.clone()], PyTuple::static_type().to_owned());
let mro = base.mro_map_collect(|x| x.to_owned());

let new_type = PyRef::new_ref(
Self {
base: Some(base).into(),
bases,
bases: PyRwLock::new(bases),
mro: PyRwLock::new(mro),
subclasses: PyRwLock::default(),
attributes: PyRwLock::new(attrs),
Expand Down Expand Up @@ -991,6 +995,11 @@ impl PyType {
}
}

pub(crate) fn finalize_bootstrap_static(typ: &Py<Self>) {
Self::set_new(&typ.slots, typ.base.deref());
Self::set_alloc(&typ.slots, typ.base.deref());
}

/// Inherit readonly slots from base type at creation time.
/// These slots are not AtomicCell and must be set before the type is used.
fn inherit_readonly_slots(slots: &mut PyTypeSlots, base: &Self) {
Expand Down Expand Up @@ -1466,16 +1475,10 @@ impl Py<PyType> {
impl PyType {
#[pygetset]
fn __bases__(&self, vm: &VirtualMachine) -> PyTupleRef {
vm.ctx.new_tuple(
self.bases
.read()
.iter()
.map(|x| x.as_object().to_owned())
.collect(),
)
Self::with_type_lock(vm, || self.bases.read().clone().into_untyped())
}
#[pygetset(setter, name = "__bases__")]
fn set_bases(zelf: &Py<Self>, bases: Vec<PyTypeRef>, vm: &VirtualMachine) -> PyResult<()> {
fn set_bases(zelf: &Py<Self>, bases_tuple: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> {
// TODO: Assigning to __bases__ is only used in typing.NamedTupleMeta.__new__
// Rather than correctly re-initializing the class, we are skipping a few steps for now
if zelf.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
Expand All @@ -1484,12 +1487,22 @@ impl PyType {
zelf.name()
)));
}
if bases.is_empty() {
if bases_tuple.is_empty() {
return Err(vm.new_type_error(format!(
"can only assign non-empty tuple to {}.__bases__, not ()",
zelf.name()
)));
}
for base in bases_tuple.iter() {
if base.downcast_ref::<Self>().is_none() {
return Err(vm.new_type_error(format!(
"{}.__bases__ must be tuple of classes, not '{}'",
zelf.name(),
base.class().name()
)));
}
}
let bases = bases_tuple.try_into_typed::<Self>(vm)?;

// TODO: check for mro cycles

Expand Down Expand Up @@ -1595,7 +1608,7 @@ impl PyType {
keep_alive(failed_base, &mut retired);
}
register_subclasses(&zelf.bases.read());
retired.extend(failed_bases.into_iter().map(Into::into));
retired.push(failed_bases.into_untyped().into());
zelf.modified_inner();
return Err(err);
}
Expand All @@ -1605,7 +1618,7 @@ impl PyType {
retired.extend(old_mro.into_iter().map(Into::into));
retired.push(cls.into());
}
retired.extend(old_bases.into_iter().map(Into::into));
retired.push(old_bases.into_untyped().into());
if let Some(old_base) = old_base {
keep_alive(old_base, &mut retired);
}
Expand Down Expand Up @@ -2107,26 +2120,24 @@ impl Constructor for PyType {

let (metatype, base, bases, base_is_type) = if bases.is_empty() {
let base = vm.ctx.types.object_type.to_owned();
(metatype, base.clone(), vec![base], false)
let bases = PyTuple::new_ref_typed(vec![base.clone()], &vm.ctx);
(metatype, base, bases, false)
} else {
let bases = bases
.iter()
.map(|obj| {
obj.clone().downcast::<Self>().or_else(|obj| {
if vm
.get_attribute_opt(obj, identifier!(vm, __mro_entries__))?
.is_some()
{
Err(vm.new_type_error(
"type() doesn't support MRO entry resolution; \
use types.new_class()",
))
} else {
Err(vm.new_type_error("bases must be types"))
}
})
})
.collect::<PyResult<Vec<_>>>()?;
for obj in bases.iter() {
if obj.downcast_ref::<Self>().is_none() {
if vm
.get_attribute_opt(obj.clone(), identifier!(vm, __mro_entries__))?
.is_some()
{
return Err(vm.new_type_error(
"type() doesn't support MRO entry resolution; \
use types.new_class()",
));
}
return Err(vm.new_type_error("bases must be types"));
}
}
let bases = bases.try_into_typed::<Self>(vm)?;

// Search the bases for the proper metatype to deal with this:
let winner = calculate_meta_class(metatype.clone(), &bases, vm)?;
Expand Down
Loading
Loading