Skip to main content

rustpython_vm/builtins/
type.rs

1use super::{
2    PyClassMethod, PyDict, PyDictRef, PyList, PyStaticMethod, PyStr, PyStrInterned, PyStrRef,
3    PyTupleRef, PyUtf8StrRef, PyWeak, mappingproxy::PyMappingProxy, object, union_,
4};
5use crate::{
6    AsObject, Context, Py, PyAtomicRef, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
7    TryFromObject, VirtualMachine,
8    builtins::{
9        PyBaseExceptionRef,
10        descriptor::{
11            MemberAccess, MemberKind, PyDescriptorOwned, PyMemberDef, PyMemberDescriptor,
12            PyMemberFlags,
13        },
14        function::{PyCellRef, PyFunction},
15        tuple::{IntoPyTuple, PyTuple},
16    },
17    class::{PyClassDef, PyClassImpl, StaticType},
18    common::{
19        borrow::BorrowedValue,
20        lock::{PyRwLock, PyRwLockReadGuard},
21    },
22    function::{
23        ArgumentError, FromArgs, FuncArgs, ItemDoc, KwArgs, Param, PyMethodDef, PySetterValue,
24        db_doc,
25    },
26    object::{Traverse, TraverseFn},
27    protocol::{PyIterReturn, PyNumberMethods},
28    types::{
29        AsNumber, Callable, Constructor, GetAttr, Initializer, NewFunc, PyTypeFlags, PyTypeSlots,
30        Representable, SLOT_DEFS, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot, fn_addr,
31        new_wrapper,
32    },
33};
34use core::{
35    any::Any,
36    borrow::Borrow,
37    cell::Cell,
38    ops::Deref,
39    pin::Pin,
40    sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering},
41};
42use indexmap::{IndexMap, map::Entry};
43use itertools::Itertools;
44use num_traits::ToPrimitive;
45use rustpython_common::wtf8::Wtf8;
46use std::collections::HashSet;
47
48pub(crate) type PyTypeTupleRef = PyRef<PyTuple<PyTypeRef>>;
49
50#[pyclass(module = false, name = "type", traverse = "manual")]
51pub struct PyType {
52    // tp_base. Written under the type lock (see `set_bases`); read lock-free.
53    #[pymember(name = "__base__", doc = false)]
54    pub base: PyAtomicRef<Option<Self>>,
55    pub bases: PyRwLock<PyTypeTupleRef>,
56    pub mro: PyRwLock<Vec<PyTypeRef>>,
57    pub subclasses: PyRwLock<Vec<PyRef<PyWeak>>>,
58    pub attributes: TypeNamespace,
59    #[pymember(name = "__itemsize__", path = "itemsize")]
60    #[pymember(name = "__basicsize__", path = "basicsize")]
61    #[pymember(name = "__flags__", path = "flags")]
62    pub slots: PyTypeSlots,
63    pub heaptype_ext: Option<Pin<Box<HeapTypeExt>>>,
64    /// Type version tag for inline caching. 0 means unassigned/invalidated.
65    pub tp_version_tag: AtomicU32,
66}
67
68/// Monotonic counter for type version tags. Once it reaches `u32::MAX`,
69/// version assignment returns 0 permanently, disabling new inline-cache
70/// entries but not invalidating correctness (cache misses fall back to the
71/// generic path).
72static NEXT_TYPE_VERSION: AtomicU32 = AtomicU32::new(1);
73
74const MANAGED_DICT_FLAGS: PyTypeFlags =
75    PyTypeFlags::from_slice(&[PyTypeFlags::HAS_DICT, PyTypeFlags::MANAGED_DICT]);
76
77const MANAGED_DICT_INLINE_FLAGS: PyTypeFlags = PyTypeFlags::from_slice(&[
78    PyTypeFlags::HAS_DICT,
79    PyTypeFlags::MANAGED_DICT,
80    PyTypeFlags::INLINE_VALUES,
81]);
82
83const WEAKREF_FLAGS: PyTypeFlags =
84    PyTypeFlags::from_slice(&[PyTypeFlags::HAS_WEAKREF, PyTypeFlags::MANAGED_WEAKREF]);
85
86// Method cache (type_cache / MCACHE): direct-mapped cache keyed by
87// (tp_version_tag, interned_name_ptr).
88//
89// Uses a lock-free SeqLock pattern for the read/write protocol:
90//  - Readers validate sequence/version/name before and after the value read.
91//  - Writers bracket updates with sequence odd/even transitions.
92// No mutex needed on the hot path (cache hit).
93
94const TYPE_CACHE_SIZE_EXP: u32 = 12;
95const TYPE_CACHE_SIZE: usize = 1 << TYPE_CACHE_SIZE_EXP;
96const TYPE_CACHE_MASK: usize = TYPE_CACHE_SIZE - 1;
97
98struct TypeCacheEntry {
99    /// Sequence lock (odd = write in progress, even = quiescent).
100    sequence: AtomicU32,
101    /// tp_version_tag at cache time. 0 = empty/invalid.
102    version: AtomicU32,
103    /// Interned attribute name pointer (pointer equality check).
104    name: AtomicPtr<PyStrInterned>,
105    /// Cached lookup result as raw pointer. null = empty.
106    /// The cache holds a **borrowed** pointer (no refcount increment).
107    /// Safety: `type_cache_clear()` nullifies all entries during GC,
108    /// and `type_cache_clear_version()` nullifies entries when a type
109    /// is modified — both before the source dict entry is removed.
110    /// Types are always part of reference cycles (via `mro` self-reference)
111    /// so they are always collected by the cyclic GC (never refcount-freed).
112    value: AtomicPtr<PyObject>,
113}
114
115// SAFETY: TypeCacheEntry is thread-safe:
116// - All fields use atomic operations
117// - Value pointer is valid as long as version matches (SeqLock pattern)
118// - PyObjectRef uses atomic reference counting
119unsafe impl Send for TypeCacheEntry {}
120unsafe impl Sync for TypeCacheEntry {}
121
122impl TypeCacheEntry {
123    fn new() -> Self {
124        Self {
125            sequence: AtomicU32::new(0),
126            version: AtomicU32::new(0),
127            name: AtomicPtr::new(core::ptr::null_mut()),
128            value: AtomicPtr::new(core::ptr::null_mut()),
129        }
130    }
131
132    #[inline]
133    fn begin_write(&self) {
134        let mut seq = self.sequence.load(Ordering::Acquire);
135        loop {
136            while (seq & 1) != 0 {
137                core::hint::spin_loop();
138                seq = self.sequence.load(Ordering::Acquire);
139            }
140            match self.sequence.compare_exchange_weak(
141                seq,
142                seq.wrapping_add(1),
143                Ordering::AcqRel,
144                Ordering::Acquire,
145            ) {
146                Ok(_) => {
147                    core::sync::atomic::fence(Ordering::Release);
148                    break;
149                }
150                Err(observed) => {
151                    core::hint::spin_loop();
152                    seq = observed;
153                }
154            }
155        }
156    }
157
158    #[inline]
159    fn end_write(&self) {
160        self.sequence.fetch_add(1, Ordering::Release);
161    }
162
163    #[inline]
164    fn begin_read(&self) -> u32 {
165        let mut sequence = self.sequence.load(Ordering::Acquire);
166        while (sequence & 1) != 0 {
167            core::hint::spin_loop();
168            sequence = self.sequence.load(Ordering::Acquire);
169        }
170        sequence
171    }
172
173    #[inline]
174    fn end_read(&self, previous: u32) -> bool {
175        core::sync::atomic::fence(Ordering::Acquire);
176        self.sequence.load(Ordering::Relaxed) == previous
177    }
178
179    /// Null out the cached value pointer.
180    /// Caller must ensure no concurrent reads can observe this entry
181    /// (version should be set to 0 first).
182    fn clear_value(&self) {
183        self.value.store(core::ptr::null_mut(), Ordering::Relaxed);
184    }
185}
186
187// std::sync::LazyLock is used here (not crate::common::lock::LazyLock)
188// because TYPE_CACHE is a global shared across test threads. The common
189// LazyLock delegates to LazyCell in non-threading mode, which is !Sync.
190static TYPE_CACHE: std::sync::LazyLock<Box<[TypeCacheEntry]>> = std::sync::LazyLock::new(|| {
191    (0..TYPE_CACHE_SIZE)
192        .map(|_| TypeCacheEntry::new())
193        .collect::<Vec<_>>()
194        .into_boxed_slice()
195});
196
197/// When true, find_name_in_mro skips populating the cache.
198/// Set during GC's type_cache_clear to prevent re-population from drops.
199static TYPE_CACHE_CLEARING: AtomicBool = AtomicBool::new(false);
200
201/// MCACHE_HASH: XOR of version and name pointer hash, masked to cache size.
202#[inline]
203fn type_cache_hash(version: u32, name: &'static PyStrInterned) -> usize {
204    let name_hash = (name as *const PyStrInterned as usize >> 3) as u32;
205    ((version ^ name_hash) as usize) & TYPE_CACHE_MASK
206}
207
208/// Invalidate cache entries for a specific version tag.
209/// Called from modified() when a type is changed.
210fn type_cache_clear_version(version: u32) {
211    for entry in TYPE_CACHE.iter() {
212        if entry.version.load(Ordering::Relaxed) == version {
213            entry.begin_write();
214            if entry.version.load(Ordering::Relaxed) == version {
215                entry.version.store(0, Ordering::Release);
216                entry.clear_value();
217            }
218            entry.end_write();
219        }
220    }
221}
222
223/// Clear all method cache entries (_PyType_ClearCache).
224/// Called during GC collection to nullify borrowed pointers before
225/// the collector breaks cycles.
226///
227/// Sets TYPE_CACHE_CLEARING to suppress cache re-population during the
228/// entire operation, preventing concurrent lookups from repopulating
229/// entries while we're clearing them.
230pub(crate) fn type_cache_clear() {
231    TYPE_CACHE_CLEARING.store(true, Ordering::Release);
232    for entry in TYPE_CACHE.iter() {
233        entry.begin_write();
234        entry.version.store(0, Ordering::Release);
235        entry.clear_value();
236        entry.end_write();
237    }
238    TYPE_CACHE_CLEARING.store(false, Ordering::Release);
239}
240
241/// Repair type-cache SeqLock state in the post-fork child.
242///
243/// If fork happens while a writer holds an entry SeqLock, the child inherits
244/// the odd sequence value with no surviving writer to release it. Clear only
245/// those in-progress entries, matching `_PyTypes_AfterFork()`.
246#[cfg(all(feature = "host_env", unix))]
247pub(crate) unsafe fn type_cache_after_fork() {
248    for entry in TYPE_CACHE.iter() {
249        let seq = entry.sequence.load(Ordering::Relaxed);
250        if (seq & 1) == 0 {
251            continue;
252        }
253        entry.value.store(core::ptr::null_mut(), Ordering::Relaxed);
254        entry.name.store(core::ptr::null_mut(), Ordering::Relaxed);
255        entry.version.store(0, Ordering::Relaxed);
256        entry.sequence.store(0, Ordering::Relaxed);
257    }
258}
259
260unsafe impl crate::object::Traverse for PyType {
261    fn traverse(&self, tracer_fn: &mut crate::object::TraverseFn<'_>) {
262        if let Some(base) = self.base.deref() {
263            tracer_fn(base.as_object());
264        }
265        // Skip when a writer holds `bases` (same rule as `Traverse for PyRwLock`).
266        if let Some(bases) = self.bases.try_read_recursive() {
267            tracer_fn(bases.as_untyped().as_object());
268        }
269        self.mro.traverse(tracer_fn);
270        self.subclasses.traverse(tracer_fn);
271        self.attributes.traverse(tracer_fn);
272        if let Some(ext) = self.heaptype_ext.as_ref() {
273            ext.specialization_cache.traverse(tracer_fn);
274        }
275    }
276
277    /// type_clear: break reference cycles in type objects
278    fn clear(&mut self, out: &mut Vec<crate::PyObjectRef>) {
279        // SAFETY: tp_clear runs with exclusive access to the type object.
280        if let Some(base) = unsafe { self.base.swap(None) } {
281            out.push(base.into());
282        }
283        // Clone the empty tuple before taking the write lock: `object`'s
284        // `bases` is this same lock, and `.read()` while exclusive panics
285        // on CellRwLock (and hangs on parking_lot).
286        let empty = object::PyBaseObject::static_type().bases.read().clone();
287        if let Some(mut bases) = self.bases.try_write() {
288            let old_bases = core::mem::replace(&mut *bases, empty);
289            out.push(old_bases.into_untyped().into());
290        }
291        if let Some(mut guard) = self.mro.try_write() {
292            for typ in guard.drain(..) {
293                out.push(typ.into());
294            }
295        }
296        if let Some(mut guard) = self.subclasses.try_write() {
297            for weak in guard.drain(..) {
298                out.push(weak.into());
299            }
300        }
301        self.attributes.drain_into(out);
302        if let Some(ext) = self.heaptype_ext.as_ref() {
303            ext.specialization_cache.clear_into(out);
304        }
305    }
306}
307
308// PyHeapTypeObject in CPython
309pub struct HeapTypeExt {
310    pub name: PyRwLock<PyUtf8StrRef>,
311    pub qualname: PyRwLock<PyStrRef>,
312    pub slots: Option<PyRef<PyTuple<PyStrRef>>>,
313    pub type_data: PyRwLock<Option<TypeDataSlot>>,
314    pub specialization_cache: TypeSpecializationCache,
315    /// The interpreter this type was created in, or `None` for the types the
316    /// shared context builds before any interpreter exists.
317    pub interpreter_id: Option<i64>,
318}
319
320impl HeapTypeExt {
321    /// The interpreter a type created right now belongs to.
322    fn creating_interpreter_id() -> Option<i64> {
323        crate::vm::thread::try_with_current_vm(|vm| vm.state.interpreter_id)
324    }
325}
326
327pub struct TypeSpecializationCache {
328    pub init: PyAtomicRef<Option<PyFunction>>,
329    pub init_version: AtomicU32,
330    pub getitem: PyAtomicRef<Option<PyFunction>>,
331    pub getitem_version: AtomicU32,
332}
333
334impl TypeSpecializationCache {
335    fn new() -> Self {
336        Self {
337            init: PyAtomicRef::from(None::<PyRef<PyFunction>>),
338            init_version: AtomicU32::new(0),
339            getitem: PyAtomicRef::from(None::<PyRef<PyFunction>>),
340            getitem_version: AtomicU32::new(0),
341        }
342    }
343
344    #[inline]
345    fn swap_init(&self, new_init: Option<PyRef<PyFunction>>) {
346        if let Some(new) = &new_init {
347            new.as_object().mark_cache_published();
348        }
349        // SAFETY: reclamation of published objects is deferred via QSBR;
350        // racing load_owned readers never touch freed memory.
351        let old = unsafe { self.init.swap(new_init) };
352        if let Some(old) = old {
353            // Dropping may run arbitrary Python; defer past the type lock.
354            rustpython_common::refcount::try_defer_drop(move || drop(old));
355        }
356    }
357
358    #[inline]
359    fn swap_getitem(&self, new_getitem: Option<PyRef<PyFunction>>) {
360        if let Some(new) = &new_getitem {
361            new.as_object().mark_cache_published();
362        }
363        // SAFETY: as in swap_init.
364        let old = unsafe { self.getitem.swap(new_getitem) };
365        if let Some(old) = old {
366            rustpython_common::refcount::try_defer_drop(move || drop(old));
367        }
368    }
369
370    #[inline]
371    fn invalidate_for_type_modified(&self) {
372        self.swap_init(None);
373        self.init_version.store(0, Ordering::Release);
374        self.swap_getitem(None);
375        self.getitem_version.store(0, Ordering::Release);
376    }
377
378    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
379        if let Some(init) = self.init.deref() {
380            tracer_fn(init.as_object());
381        }
382        if let Some(getitem) = self.getitem.deref() {
383            tracer_fn(getitem.as_object());
384        }
385    }
386
387    fn clear_into(&self, out: &mut Vec<PyObjectRef>) {
388        let old_init = unsafe { self.init.swap(None) };
389        if let Some(old_init) = old_init {
390            out.push(old_init.into());
391        }
392        self.init_version.store(0, Ordering::Release);
393        let old_getitem = unsafe { self.getitem.swap(None) };
394        if let Some(old_getitem) = old_getitem {
395            out.push(old_getitem.into());
396        }
397        self.getitem_version.store(0, Ordering::Release);
398    }
399}
400
401pub type PyTypeRef = PyRef<PyType>;
402
403cfg_select! {
404    feature = "threading" => {
405        unsafe impl Send for PyType {}
406        unsafe impl Sync for PyType {}
407    }
408    _ => {}
409}
410
411/// For attributes we do not use a dict, but an IndexMap, which is an Hash Table
412/// that maintains order and is compatible with the standard HashMap  This is probably
413/// faster and only supports strings as keys.
414pub(crate) type PyAttributes =
415    IndexMap<&'static PyStrInterned, PyObjectRef, rapidhash::quality::RandomState>;
416
417unsafe impl Traverse for PyAttributes {
418    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
419        self.values().for_each(|v| v.traverse(tracer_fn));
420    }
421}
422
423/// A type's namespace, `tp_dict`.
424///
425/// Types created while an interpreter is running own a real dict, so the object
426/// `__classdictcell__` hands to annotation scopes is the type's own storage.
427/// The types `Context::genesis` builds cannot have one: hashing a string needs
428/// a VM and none exists yet, so they keep an interned-key map instead.
429pub enum TypeNamespace {
430    Attributes(PyRwLock<PyAttributes>),
431    Dict(PyDictRef),
432}
433
434unsafe impl Traverse for TypeNamespace {
435    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
436        match self {
437            Self::Attributes(attrs) => {
438                if let Some(attrs) = attrs.try_read_recursive() {
439                    attrs.traverse(tracer_fn);
440                }
441            }
442            Self::Dict(dict) => tracer_fn(dict.as_object()),
443        }
444    }
445}
446
447impl Default for TypeNamespace {
448    fn default() -> Self {
449        Self::Attributes(PyRwLock::default())
450    }
451}
452
453impl From<PyAttributes> for TypeNamespace {
454    fn from(attrs: PyAttributes) -> Self {
455        Self::Attributes(PyRwLock::new(attrs))
456    }
457}
458
459impl TypeNamespace {
460    /// The namespace as a dict, for the types that have one.
461    pub fn as_dict(&self) -> Option<&Py<PyDict>> {
462        match self {
463            Self::Attributes(_) => None,
464            Self::Dict(dict) => Some(dict),
465        }
466    }
467
468    /// Build a dict-backed namespace holding `attrs`, or fall back to an
469    /// interned-key map when no VM is running to hash the keys with.
470    fn new(attrs: PyAttributes, ctx: &Context) -> Self {
471        let built = crate::vm::thread::try_with_current_vm(|vm| {
472            let dict = ctx.new_dict();
473            for (key, value) in &attrs {
474                dict.set_item(*key, value.clone(), vm)?;
475            }
476            PyResult::Ok(dict)
477        });
478        match built {
479            Some(Ok(dict)) => Self::Dict(dict),
480            _ => attrs.into(),
481        }
482    }
483
484    pub fn get(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> {
485        match self {
486            Self::Attributes(attrs) => attrs.read().get(name).cloned(),
487            Self::Dict(dict) => dict_get(dict, name),
488        }
489    }
490
491    pub fn contains(&self, name: &'static PyStrInterned) -> bool {
492        match self {
493            Self::Attributes(attrs) => attrs.read().contains_key(name),
494            Self::Dict(dict) => {
495                match crate::vm::thread::try_with_current_vm(|vm| dict.contains_key(name, vm)) {
496                    Some(found) => found,
497                    None => dict_get(dict, name).is_some(),
498                }
499            }
500        }
501    }
502
503    /// Bind `name` to `value`, dropping whatever it displaced.
504    pub fn set(&self, name: &'static PyStrInterned, value: PyObjectRef) {
505        drop(self.insert(name, value));
506    }
507
508    /// Bind `name` to `value` and hand back what it displaced, so the caller
509    /// can decide where the old value is dropped.
510    pub fn insert(&self, name: &'static PyStrInterned, value: PyObjectRef) -> Option<PyObjectRef> {
511        match self {
512            Self::Attributes(attrs) => attrs.write().insert(name, value),
513            Self::Dict(dict) => {
514                let previous = dict_get(dict, name);
515                if let Some(Err(_)) | None =
516                    crate::vm::thread::try_with_current_vm(|vm| dict.set_item(name, value, vm))
517                {
518                    debug_assert!(false, "type namespace write without a running VM");
519                }
520                previous
521            }
522        }
523    }
524
525    pub fn remove(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> {
526        match self {
527            Self::Attributes(attrs) => attrs.write().shift_remove(name),
528            Self::Dict(dict) => {
529                let previous = dict_get(dict, name)?;
530                crate::vm::thread::try_with_current_vm(|vm| dict.del_item(name, vm).ok());
531                Some(previous)
532            }
533        }
534    }
535
536    /// The namespace contents in insertion order.
537    pub fn entries(&self) -> Vec<(PyObjectRef, PyObjectRef)> {
538        match self {
539            Self::Attributes(attrs) => attrs
540                .read()
541                .iter()
542                .map(|(name, value)| ((*name).to_object(), value.clone()))
543                .collect(),
544            Self::Dict(dict) => dict.into_iter().collect(),
545        }
546    }
547
548    /// The namespace keyed by interned name; dict keys that are not strings
549    /// are left out, since `PyAttributes` has nowhere to put them.
550    pub fn attributes(&self, ctx: &Context) -> PyAttributes {
551        match self {
552            Self::Attributes(attrs) => attrs.read().clone(),
553            Self::Dict(dict) => dict
554                .into_iter()
555                .filter_map(|(key, value)| {
556                    let key = key.downcast_ref::<PyStr>()?;
557                    Some((ctx.intern_str(key.as_wtf8()), value))
558                })
559                .collect(),
560        }
561    }
562
563    /// The interned names in the namespace; dict keys that are not strings are
564    /// left out.
565    pub fn interned_names(&self, ctx: &Context) -> Vec<&'static PyStrInterned> {
566        match self {
567            Self::Attributes(attrs) => attrs.read().keys().copied().collect(),
568            Self::Dict(dict) => dict
569                .into_iter()
570                .filter_map(|(key, _)| {
571                    let key = key.downcast_ref::<PyStr>()?;
572                    Some(ctx.intern_str(key.as_wtf8()))
573                })
574                .collect(),
575        }
576    }
577
578    /// Empty the namespace, handing the values to the caller. Used by tp_clear.
579    fn drain_into(&mut self, out: &mut Vec<PyObjectRef>) {
580        match self {
581            Self::Attributes(attrs) => {
582                if let Some(mut guard) = attrs.try_write() {
583                    out.extend(guard.drain(..).map(|(_, value)| value));
584                }
585            }
586            Self::Dict(dict) => {
587                out.extend((&**dict).into_iter().map(|(_, value)| value));
588                Py::<PyDict>::clear(dict);
589            }
590        }
591    }
592}
593
594/// Look a name up in a dict-backed namespace. Falls back to a scan when no VM
595/// is running, since hashing the key needs one.
596fn dict_get(dict: &Py<PyDict>, name: &'static PyStrInterned) -> Option<PyObjectRef> {
597    match crate::vm::thread::try_with_current_vm(|vm| dict.get_item_opt(name, vm)) {
598        Some(found) => found.ok().flatten(),
599        None => dict
600            .into_iter()
601            .find(|(key, _)| {
602                key.downcast_ref::<PyStr>()
603                    .is_some_and(|key| key.as_wtf8() == name.as_wtf8())
604            })
605            .map(|(_, value)| value),
606    }
607}
608
609impl core::fmt::Display for PyType {
610    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
611        core::fmt::Display::fmt(&self.name(), f)
612    }
613}
614
615impl core::fmt::Debug for PyType {
616    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
617        write!(f, "[PyType {}]", self.name())
618    }
619}
620
621impl PyPayload for PyType {
622    #[inline]
623    fn class(ctx: &Context) -> &'static Py<PyType> {
624        ctx.types.type_type
625    }
626}
627
628/// The name a class is built under, which the namespace it is built from may
629/// carry and which is faulted for not being a string. = type_new_set_attrs
630fn downcast_qualname(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
631    match value.downcast::<PyStr>() {
632        Ok(value) => Ok(value),
633        Err(value) => Err(vm.new_type_error(format!(
634            "type __qualname__ must be a str, not {}",
635            value.class().name()
636        ))),
637    }
638}
639
640fn is_subtype_with_mro(a_mro: &[PyTypeRef], a: &Py<PyType>, b: &Py<PyType>) -> bool {
641    if a_mro.is_empty() {
642        let mut current = Some(a);
643        while let Some(typ) = current {
644            if typ.is(b) {
645                return true;
646            }
647            current = typ.base.deref();
648        }
649        return false;
650    }
651    a_mro.iter().any(|item| item.is(b))
652}
653
654impl PyType {
655    #[inline]
656    fn with_type_lock<R>(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R {
657        // Drops deferred via try_defer_drop inside the critical section run
658        // after the guard is released, outside the lock.
659        //
660        // The lock is reentrant on the same thread so a custom mro() can
661        // assign __bases__ (and re-enter here) without deadlocking.
662        thread_local! {
663            static HELD: Cell<bool> = const { Cell::new(false) };
664        }
665        rustpython_common::refcount::with_deferred_drops(|| {
666            HELD.with(|held| {
667                if held.get() {
668                    f()
669                } else {
670                    let _guard = vm.state.type_mutex.lock();
671                    held.set(true);
672                    let result = f();
673                    held.set(false);
674                    result
675                }
676            })
677        })
678    }
679
680    /// Assign a fresh version tag. Returns 0 if the version counter has been
681    /// exhausted, in which case no new cache entries can be created.
682    fn assign_version_tag_inner(&self) -> u32 {
683        let v = self.tp_version_tag.load(Ordering::Acquire);
684        if v != 0 {
685            return v;
686        }
687
688        // Assign versions to all direct bases first (MRO invariant).
689        for base in self.bases.read().as_slice() {
690            if base.assign_version_tag_inner() == 0 {
691                return 0;
692            }
693        }
694
695        loop {
696            let current = NEXT_TYPE_VERSION.load(Ordering::Relaxed);
697            let Some(next) = current.checked_add(1) else {
698                return 0; // Overflow: version space exhausted
699            };
700            if NEXT_TYPE_VERSION
701                .compare_exchange_weak(current, next, Ordering::Relaxed, Ordering::Relaxed)
702                .is_ok()
703            {
704                self.tp_version_tag.store(current, Ordering::Release);
705                return current;
706            }
707        }
708    }
709
710    pub(crate) fn version_for_specialization(&self, vm: &VirtualMachine) -> u32 {
711        let version = self.tp_version_tag.load(Ordering::Acquire);
712        if version != 0 {
713            return version;
714        }
715        Self::with_type_lock(vm, || {
716            let version = self.tp_version_tag.load(Ordering::Acquire);
717            if version == 0 {
718                self.assign_version_tag_inner()
719            } else {
720                version
721            }
722        })
723    }
724
725    /// Invalidate this type's version tag and cascade to all subclasses.
726    fn modified_inner(&self) {
727        let old_version = self.tp_version_tag.load(Ordering::Acquire);
728        if old_version == 0 {
729            return;
730        }
731        let subclasses = self.subclasses.read();
732        for weak_ref in subclasses.iter() {
733            if let Some(sub) = weak_ref.upgrade() {
734                sub.downcast_ref::<Self>().unwrap().modified_inner();
735            }
736        }
737        self.tp_version_tag.store(0, Ordering::SeqCst);
738        // Nullify borrowed pointers in cache entries for this version
739        // so they don't dangle after the dict is modified.
740        type_cache_clear_version(old_version);
741        if let Some(ext) = self.heaptype_ext.as_ref() {
742            ext.specialization_cache.invalidate_for_type_modified();
743        }
744    }
745
746    /// Store a specific `tp_version_tag` without going through assignment.
747    pub(crate) fn assign_specific_version(&self, version: u32) {
748        self.tp_version_tag.store(version, Ordering::Release);
749    }
750
751    pub fn modified(&self) {
752        if self.tp_version_tag.load(Ordering::Acquire) == 0 {
753            return;
754        }
755        if let Some(()) = crate::vm::thread::try_with_current_vm(|vm| {
756            Self::with_type_lock(vm, || self.modified_inner());
757        }) {
758            return;
759        }
760        self.modified_inner();
761    }
762
763    /// Whether the interpreter with `interpreter_id` can see this type.
764    ///
765    /// Interpreters share the context, so a subclass of a shared type is
766    /// recorded on an object every interpreter reaches. Only the interpreter
767    /// that created it can name it, so only that one lists it.
768    pub fn is_visible_to_interpreter(&self, interpreter_id: i64) -> bool {
769        match self
770            .heaptype_ext
771            .as_ref()
772            .and_then(|ext| ext.interpreter_id)
773        {
774            Some(owner) => owner == interpreter_id,
775            None => true,
776        }
777    }
778
779    pub fn new_simple_heap(
780        name: &str,
781        base: &Py<Self>,
782        ctx: &Context,
783    ) -> Result<PyRef<Self>, String> {
784        Self::new_heap(
785            name,
786            vec![base.to_owned()],
787            Default::default(),
788            Default::default(),
789            Self::static_type().to_owned(),
790            ctx,
791        )
792    }
793    pub fn new_heap(
794        name: &str,
795        bases: Vec<PyRef<Self>>,
796        attrs: PyAttributes,
797        slots: PyTypeSlots,
798        metaclass: PyRef<Self>,
799        ctx: &Context,
800    ) -> Result<PyRef<Self>, String> {
801        // TODO: ensure clean slot name
802        // assert_eq!(slots.name.borrow(), "");
803
804        // Set HEAPTYPE flag for heap-allocated types
805        slots.flags.insert(PyTypeFlags::HEAPTYPE);
806
807        let name_utf8 = ctx.new_utf8_str(name);
808        let name = name_utf8.clone().into_wtf8();
809        let heaptype_ext = HeapTypeExt {
810            name: PyRwLock::new(name_utf8),
811            qualname: PyRwLock::new(name),
812            slots: None,
813            type_data: PyRwLock::new(None),
814            specialization_cache: TypeSpecializationCache::new(),
815            interpreter_id: HeapTypeExt::creating_interpreter_id(),
816        };
817        let bases = PyTuple::new_ref_typed(bases, ctx);
818        let base = bases.as_slice()[0].clone();
819
820        Self::new_heap_inner(
821            base,
822            bases,
823            attrs,
824            slots,
825            heaptype_ext,
826            metaclass,
827            ctx,
828            false,
829        )
830    }
831
832    /// Equivalent to CPython's PyType_Check macro
833    /// Checks if obj is an instance of type (or its subclass)
834    pub(crate) fn check(obj: &PyObject) -> Option<&Py<Self>> {
835        obj.downcast_ref::<Self>()
836    }
837
838    fn resolve_mro(bases: &[PyRef<Self>]) -> Result<Vec<PyTypeRef>, String> {
839        // Check for duplicates in bases.
840        let mut unique_bases = HashSet::new();
841        for base in bases {
842            if !unique_bases.insert(base.get_id()) {
843                return Err(format!("duplicate base class {}", base.name()));
844            }
845        }
846
847        let mros = bases
848            .iter()
849            .map(|base| base.mro_map_collect(|t| t.to_owned()))
850            .collect();
851        linearise_mro(mros)
852    }
853
854    /// Inherit SEQUENCE and MAPPING flags from base classes
855    /// Check all bases in order and inherit the first SEQUENCE or MAPPING flag found
856    fn inherit_patma_flags(slots: &mut PyTypeSlots, bases: &[PyRef<Self>]) {
857        // If flags are already set, don't override
858        if !slots.flags.load().is_disjoint(&PyTypeFlags::COLLECTION) {
859            return;
860        }
861
862        // Check each base in order and inherit the first collection flag found
863        for base in bases {
864            let base_flags = base.slots.flags.load() & PyTypeFlags::COLLECTION;
865            if !base_flags.is_empty() {
866                slots.flags |= base_flags;
867                return;
868            }
869        }
870    }
871
872    pub fn has_patma_collection_flag(&self, flag: u8) -> bool {
873        debug_assert!(flag == PyTypeFlags::SEQUENCE || flag == PyTypeFlags::MAPPING);
874        self.slots.flags.has_feature(flag)
875    }
876
877    pub fn set_is_abstract(&self, is_abstract: bool) {
878        if is_abstract {
879            self.slots.flags.insert(PyTypeFlags::IS_ABSTRACT);
880        } else {
881            self.slots.flags.remove(PyTypeFlags::IS_ABSTRACT);
882        }
883        self.modified();
884    }
885
886    pub fn set_abc_collection_flags_recursive(&self, flags: PyTypeFlags) {
887        let flags = flags & PyTypeFlags::COLLECTION;
888        if flags.is_empty() {
889            return;
890        }
891        self.slots
892            .flags
893            .replace_masked(PyTypeFlags::COLLECTION, flags);
894        self.modified();
895        for weak_ref in self.subclasses.read().iter() {
896            if let Some(subclass) = weak_ref.upgrade()
897                && let Some(subclass) = subclass.downcast_ref::<Self>()
898            {
899                subclass.set_abc_collection_flags_recursive(flags);
900            }
901        }
902    }
903
904    /// Check for __abc_tpflags__ and set the appropriate flags
905    /// This checks in attrs and all base classes for __abc_tpflags__
906    fn check_abc_tpflags(
907        slots: &mut PyTypeSlots,
908        attrs: &PyAttributes,
909        bases: &[PyRef<Self>],
910        ctx: &Context,
911    ) -> Result<(), String> {
912        // Always validate this class's own __abc_tpflags__ even when slot
913        // flags were already inherited, otherwise a child setting both
914        // Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING would slip through.
915        let abc_tpflags_name = ctx.intern_str("__abc_tpflags__");
916        if let Some(abc_tpflags_obj) = attrs.get(abc_tpflags_name)
917            && let Some(int_obj) = abc_tpflags_obj.downcast_ref::<crate::builtins::int::PyInt>()
918        {
919            let flags_val = int_obj.as_bigint().to_i64().unwrap_or(0);
920            let abc_flags = PyTypeFlags::from_bits_truncate(flags_val as u64);
921            let masked = abc_flags & PyTypeFlags::COLLECTION;
922            if masked == PyTypeFlags::COLLECTION {
923                return Err(
924                    "__abc_tpflags__ cannot be both Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING"
925                        .to_owned(),
926                );
927            }
928            slots.flags.remove_masked(PyTypeFlags::COLLECTION);
929            slots.flags |= masked;
930            return Ok(());
931        }
932
933        // No __abc_tpflags__ on this class. Inheritance already happened in
934        // inherit_patma_flags, using base order and including ABC markers.
935        if !slots.flags.load().is_disjoint(&PyTypeFlags::COLLECTION) {
936            return Ok(());
937        }
938
939        // Then check in base classes for legacy paths that bypassed
940        // inherit_patma_flags.
941        for base in bases {
942            if let Some(abc_tpflags_obj) = base.find_name_in_mro(abc_tpflags_name)
943                && let Some(int_obj) = abc_tpflags_obj.downcast_ref::<crate::builtins::int::PyInt>()
944            {
945                let flags_val = int_obj.as_bigint().to_i64().unwrap_or(0);
946                let abc_flags = PyTypeFlags::from_bits_truncate(flags_val as u64);
947                let masked = abc_flags & PyTypeFlags::COLLECTION;
948                if masked == PyTypeFlags::COLLECTION {
949                    return Err(
950                        "__abc_tpflags__ cannot be both Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING"
951                            .to_owned(),
952                    );
953                }
954                slots.flags.remove_masked(PyTypeFlags::COLLECTION);
955                slots.flags |= masked;
956                return Ok(());
957            }
958        }
959        Ok(())
960    }
961
962    #[allow(clippy::too_many_arguments)]
963    fn new_heap_inner(
964        base: PyRef<Self>,
965        bases: PyTypeTupleRef,
966        attrs: PyAttributes,
967        mut slots: PyTypeSlots,
968        heaptype_ext: HeapTypeExt,
969        metaclass: PyRef<Self>,
970        ctx: &Context,
971        defer_mro: bool,
972    ) -> Result<PyRef<Self>, String> {
973        let mro = if defer_mro {
974            // Leave tp_mro unset so a custom metaclass mro() sees __mro__ is None.
975            Vec::new()
976        } else {
977            Self::resolve_mro(bases.as_slice())?
978        };
979
980        // Layout flags come from each direct base's own flags. A custom
981        // mro() can omit a physical base from the stored MRO.
982        if bases
983            .as_slice()
984            .iter()
985            .any(|b| b.slots.flags.has_feature(PyTypeFlags::HAS_DICT))
986        {
987            slots.flags.insert(PyTypeFlags::HAS_DICT);
988        }
989        if bases
990            .as_slice()
991            .iter()
992            .any(|b| b.slots.flags.has_feature(PyTypeFlags::MANAGED_DICT))
993        {
994            slots.flags.insert(PyTypeFlags::MANAGED_DICT);
995        }
996
997        if bases
998            .as_slice()
999            .iter()
1000            .any(|b| b.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF))
1001        {
1002            slots.flags |= WEAKREF_FLAGS;
1003        }
1004
1005        // Inherit SEQUENCE and MAPPING flags from base classes
1006        Self::inherit_patma_flags(&mut slots, bases.as_slice());
1007
1008        // Check for __abc_tpflags__ from ABCMeta (for collections.abc.Sequence, Mapping, etc.)
1009        Self::check_abc_tpflags(&mut slots, &attrs, bases.as_slice(), ctx)?;
1010
1011        if slots.basicsize == 0 {
1012            slots.basicsize = base.slots.basicsize;
1013        }
1014
1015        // Normalize: any type with HAS_WEAKREF gets MANAGED_WEAKREF
1016        if slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF) {
1017            slots.flags.insert(PyTypeFlags::MANAGED_WEAKREF);
1018        }
1019
1020        if let Some(qualname) = attrs.get(identifier!(ctx, __qualname__))
1021            && !qualname.fast_isinstance(ctx.types.str_type)
1022        {
1023            return Err(format!(
1024                "type __qualname__ must be a str, not {}",
1025                qualname.class().name()
1026            ));
1027        }
1028
1029        let new_type = PyRef::new_ref(
1030            Self {
1031                base: Some(base).into(),
1032                bases: PyRwLock::new(bases),
1033                mro: PyRwLock::new(mro),
1034                subclasses: PyRwLock::default(),
1035                attributes: TypeNamespace::new(attrs, ctx),
1036                slots,
1037                heaptype_ext: Some(Pin::new(Box::new(heaptype_ext))),
1038                tp_version_tag: AtomicU32::new(0),
1039            },
1040            metaclass,
1041            None,
1042        );
1043        if !defer_mro {
1044            new_type.mro.write().insert(0, new_type.clone());
1045            new_type.init_slots(ctx);
1046        }
1047
1048        let weakref_type = super::PyWeak::static_type();
1049        for base in new_type.bases.read().as_slice() {
1050            base.subclasses.write().push(
1051                new_type
1052                    .as_object()
1053                    .downgrade_with_weakref_typ_opt(None, weakref_type.to_owned())
1054                    .unwrap(),
1055            );
1056        }
1057
1058        Ok(new_type)
1059    }
1060
1061    pub fn new_static(
1062        base: PyRef<Self>,
1063        attrs: PyAttributes,
1064        mut slots: PyTypeSlots,
1065        metaclass: PyRef<Self>,
1066    ) -> Result<PyRef<Self>, String> {
1067        if base.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
1068            slots.flags.insert(PyTypeFlags::HAS_DICT);
1069        }
1070        if base.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF) {
1071            slots.flags |= WEAKREF_FLAGS;
1072        }
1073
1074        // Inherit SEQUENCE and MAPPING flags from base class
1075        // For static types, we only have a single base
1076        Self::inherit_patma_flags(&mut slots, core::slice::from_ref(&base));
1077
1078        if slots.basicsize == 0 {
1079            slots.basicsize = base.slots.basicsize;
1080        }
1081
1082        // Normalize: any type with HAS_WEAKREF gets MANAGED_WEAKREF
1083        if slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF) {
1084            slots.flags.insert(PyTypeFlags::MANAGED_WEAKREF);
1085        }
1086
1087        let bases =
1088            PyTuple::new_ref_typed_with_type(vec![base.clone()], PyTuple::static_type().to_owned());
1089        let mro = base.mro_map_collect(|x| x.to_owned());
1090
1091        let new_type = PyRef::new_ref(
1092            Self {
1093                base: Some(base).into(),
1094                bases: PyRwLock::new(bases),
1095                mro: PyRwLock::new(mro),
1096                subclasses: PyRwLock::default(),
1097                attributes: attrs.into(),
1098                slots,
1099                heaptype_ext: None,
1100                tp_version_tag: AtomicU32::new(0),
1101            },
1102            metaclass,
1103            None,
1104        );
1105
1106        // Static types are not tracked by GC.
1107        // They are immortal and never participate in collectable cycles.
1108        // Heap types from PyType_FromSpec stay tracked.
1109        if !new_type.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
1110            unsafe {
1111                crate::gc_state::gc_state()
1112                    .untrack_object(core::ptr::NonNull::from(new_type.as_object()));
1113            }
1114            new_type.as_object().clear_gc_tracked();
1115        }
1116
1117        new_type.mro.write().insert(0, new_type.clone());
1118
1119        // Note: inherit_slots is called in PyClassImpl::init_class after
1120        // slots are fully initialized by make_slots()
1121
1122        Self::set_new(&new_type.slots, new_type.base.deref());
1123        Self::set_alloc(&new_type.slots, new_type.base.deref());
1124
1125        let weakref_type = super::PyWeak::static_type();
1126        for base in new_type.bases.read().as_slice() {
1127            base.subclasses.write().push(
1128                new_type
1129                    .as_object()
1130                    .downgrade_with_weakref_typ_opt(None, weakref_type.to_owned())
1131                    .unwrap(),
1132            );
1133        }
1134
1135        Ok(new_type)
1136    }
1137
1138    pub(crate) fn init_slots(&self, ctx: &Context) {
1139        // Inherit slots from MRO (mro[0] is self, so skip it)
1140        let mro_guard = self.mro.read();
1141        if mro_guard.is_empty() {
1142            return;
1143        }
1144        let mro: Vec<_> = mro_guard[1..].to_vec();
1145        drop(mro_guard);
1146        for base in &mro {
1147            self.inherit_slots(base);
1148        }
1149
1150        // Wire dunder methods to slots
1151        #[allow(clippy::mutable_key_type)]
1152        let mut slot_name_set = std::collections::HashSet::new();
1153
1154        // mro[0] is self, so skip it; self.attributes is checked separately below
1155        for cls in &self.mro.read()[1..] {
1156            for name in cls.attributes.interned_names(ctx) {
1157                if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") {
1158                    slot_name_set.insert(name);
1159                }
1160            }
1161        }
1162        for name in self.attributes.interned_names(ctx) {
1163            if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") {
1164                slot_name_set.insert(name);
1165            }
1166        }
1167        // Sort for deterministic iteration order (important for slot processing)
1168        let mut slot_names: Vec<_> = slot_name_set.into_iter().collect();
1169        slot_names.sort_by_key(|name| name.as_str());
1170        for attr_name in slot_names {
1171            self.update_slot::<true>(attr_name, ctx);
1172        }
1173
1174        Self::set_new(&self.slots, self.base.deref());
1175        Self::set_alloc(&self.slots, self.base.deref());
1176    }
1177
1178    /// Recompute every slot for this type and all its descendants. update_all_slots
1179    ///
1180    /// Unlike `init_slots`, which is additive and driven only by the dunder names
1181    /// present in the current MRO, this iterates the full `SLOT_DEFS` name table so
1182    /// a slot whose method left the MRO is reset instead of left stale. Must be
1183    /// called under the type lock after MROs have been recomputed.
1184    pub(crate) fn update_all_slots(&self, ctx: &Context) {
1185        // Invalidate version tags first; cascades to subclasses.
1186        self.modified_inner();
1187        // Distinct names only; update_slot fans out to every SLOT_DEFS entry
1188        // sharing the name and recurses into subclasses on its own.
1189        let mut seen = std::collections::HashSet::new();
1190        for def in SLOT_DEFS {
1191            if seen.insert(def.name) {
1192                let name = ctx.intern_str(def.name);
1193                self.update_slot::<true>(name, ctx);
1194            }
1195        }
1196    }
1197
1198    fn set_new(slots: &PyTypeSlots, base: Option<&Py<Self>>) {
1199        if slots.flags.has_feature(PyTypeFlags::DISALLOW_INSTANTIATION) {
1200            slots.new.store(None)
1201        } else if slots.new.load().is_none() {
1202            slots.new.store(base.and_then(|base| base.slots.new.load()))
1203        }
1204    }
1205
1206    fn set_alloc(slots: &PyTypeSlots, base: Option<&Py<Self>>) {
1207        if slots.alloc.load().is_none() {
1208            slots
1209                .alloc
1210                .store(base.and_then(|base| base.slots.alloc.load()));
1211        }
1212    }
1213
1214    pub(crate) fn finalize_bootstrap_static(typ: &Py<Self>) {
1215        Self::set_new(&typ.slots, typ.base.deref());
1216        Self::set_alloc(&typ.slots, typ.base.deref());
1217    }
1218
1219    /// Inherit slots from base type. inherit_slots
1220    pub(crate) fn inherit_slots(&self, base: &Self) {
1221        // Use SLOT_DEFS to iterate all slots
1222        for def in SLOT_DEFS {
1223            def.accessor.copyslot_if_none(self, base);
1224        }
1225    }
1226
1227    // This is used for class initialization where the vm is not yet available.
1228    pub fn set_str_attr<V: Into<PyObjectRef>>(
1229        &self,
1230        attr_name: &str,
1231        value: V,
1232        ctx: impl AsRef<Context>,
1233    ) {
1234        let ctx = ctx.as_ref();
1235        let attr_name = ctx.intern_str(attr_name);
1236        self.set_attr(attr_name, value.into())
1237    }
1238
1239    pub fn set_attr(&self, attr_name: &'static PyStrInterned, value: PyObjectRef) {
1240        // Invalidate caches BEFORE modifying attributes so that borrowed
1241        // pointers in cache entries are nullified while the source objects
1242        // are still alive.
1243        self.modified();
1244        self.attributes.set(attr_name, value);
1245    }
1246
1247    /// Internal get_attr implementation for fast lookup on a class.
1248    /// Searches the full MRO (including self) with method cache acceleration.
1249    pub fn get_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
1250        self.find_name_in_mro(attr_name)
1251    }
1252
1253    /// `_PyType_LookupRefAndVersion` equivalent for interned names.
1254    /// Returns the observed lookup result and the type version used for the lookup.
1255    ///
1256    /// Uses a lock-free SeqLock-style pattern:
1257    ///   Read:  load sequence/version/name → load value + try_to_owned →
1258    ///          validate value pointer + sequence
1259    ///   Write: sequence(begin) → version=0 → swap value/name → version=assigned → sequence(end)
1260    pub(crate) fn lookup_ref_and_version_interned(
1261        &self,
1262        name: &'static PyStrInterned,
1263        vm: &VirtualMachine,
1264    ) -> (Option<PyObjectRef>, u32) {
1265        #[cfg(all(feature = "threading", debug_assertions))]
1266        crate::vm::thread::debug_assert_current_thread_attached();
1267
1268        let version = self.tp_version_tag.load(Ordering::Acquire);
1269        if version != 0 {
1270            let idx = type_cache_hash(version, name);
1271            let entry = &TYPE_CACHE[idx];
1272            let name_ptr = name as *const _ as *mut _;
1273            loop {
1274                let seq1 = entry.begin_read();
1275                let entry_version = entry.version.load(Ordering::Acquire);
1276                let type_version = self.tp_version_tag.load(Ordering::Acquire);
1277                if entry_version != type_version
1278                    || !core::ptr::eq(entry.name.load(Ordering::Relaxed), name_ptr)
1279                {
1280                    break;
1281                }
1282                let ptr = entry.value.load(Ordering::Acquire);
1283                if ptr.is_null() {
1284                    if entry.end_read(seq1) {
1285                        return (None, entry_version);
1286                    }
1287                    continue;
1288                }
1289                if let Some(cloned) = unsafe { PyObject::try_to_owned_from_ptr(ptr) } {
1290                    let same_ptr = core::ptr::eq(entry.value.load(Ordering::Relaxed), ptr);
1291                    if same_ptr && entry.end_read(seq1) {
1292                        return (Some(cloned), entry_version);
1293                    }
1294                    drop(cloned);
1295                    continue;
1296                }
1297                break;
1298            }
1299        }
1300
1301        Self::with_type_lock(vm, || {
1302            let assigned = if self.tp_version_tag.load(Ordering::Acquire) == 0 {
1303                self.assign_version_tag_inner()
1304            } else {
1305                self.tp_version_tag.load(Ordering::Acquire)
1306            };
1307            let result = self.find_name_in_mro_uncached(name);
1308            if assigned != 0
1309                && !TYPE_CACHE_CLEARING.load(Ordering::Acquire)
1310                && self.tp_version_tag.load(Ordering::Acquire) == assigned
1311            {
1312                let idx = type_cache_hash(assigned, name);
1313                let entry = &TYPE_CACHE[idx];
1314                let name_ptr = name as *const _ as *mut _;
1315                entry.begin_write();
1316                entry.version.store(0, Ordering::Release);
1317                let new_ptr = result.as_ref().map_or(core::ptr::null_mut(), |found| {
1318                    // Defer memory reclamation of cached values via QSBR so
1319                    // racing readers never try-incref freed memory.
1320                    found.mark_cache_published();
1321                    &**found as *const PyObject as *mut _
1322                });
1323                entry.value.store(new_ptr, Ordering::Relaxed);
1324                entry.name.store(name_ptr, Ordering::Relaxed);
1325                entry.version.store(assigned, Ordering::Release);
1326                entry.end_write();
1327            }
1328            (result, assigned)
1329        })
1330    }
1331
1332    /// Cache __init__ for CALL_ALLOC_AND_ENTER_INIT specialization.
1333    /// The cache is valid only when guarded by the type version check.
1334    pub(crate) fn cache_init_for_specialization(
1335        &self,
1336        init: PyRef<PyFunction>,
1337        tp_version: u32,
1338        vm: &VirtualMachine,
1339    ) -> bool {
1340        let Some(ext) = self.heaptype_ext.as_ref() else {
1341            return false;
1342        };
1343        if tp_version == 0 {
1344            return false;
1345        }
1346        Self::with_type_lock(vm, || {
1347            if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
1348                return false;
1349            }
1350            let func_version = init.get_version_for_current_state();
1351            if func_version == 0 {
1352                return false;
1353            }
1354            ext.specialization_cache.swap_init(Some(init));
1355            ext.specialization_cache
1356                .init_version
1357                .store(func_version, Ordering::Release);
1358            true
1359        })
1360    }
1361
1362    /// Read cached __init__ for CALL_ALLOC_AND_ENTER_INIT specialization.
1363    pub(crate) fn get_cached_init_for_specialization(
1364        &self,
1365        tp_version: u32,
1366    ) -> Option<(PyRef<PyFunction>, u32)> {
1367        let ext = self.heaptype_ext.as_ref()?;
1368        if tp_version == 0 {
1369            return None;
1370        }
1371        if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
1372            return None;
1373        }
1374        // Check order: pointer (Acquire) then function version.
1375        let init = ext.specialization_cache.init.load_owned()?;
1376        let cached_version = ext
1377            .specialization_cache
1378            .init_version
1379            .load(Ordering::Acquire);
1380        if cached_version == 0 {
1381            return None;
1382        }
1383        Some((init, cached_version))
1384    }
1385
1386    /// Cache __getitem__ for BINARY_OP_SUBSCR_GETITEM specialization.
1387    /// The cache is valid only when guarded by the type version check.
1388    pub(crate) fn cache_getitem_for_specialization(
1389        &self,
1390        getitem: PyRef<PyFunction>,
1391        tp_version: u32,
1392        vm: &VirtualMachine,
1393    ) -> bool {
1394        let Some(ext) = self.heaptype_ext.as_ref() else {
1395            return false;
1396        };
1397        if tp_version == 0 {
1398            return false;
1399        }
1400        Self::with_type_lock(vm, || {
1401            if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
1402                return false;
1403            }
1404            let func_version = getitem.get_version_for_current_state();
1405            if func_version == 0 {
1406                return false;
1407            }
1408            ext.specialization_cache.swap_getitem(Some(getitem));
1409            ext.specialization_cache
1410                .getitem_version
1411                .store(func_version, Ordering::Release);
1412            true
1413        })
1414    }
1415
1416    /// Read cached __getitem__ for BINARY_OP_SUBSCR_GETITEM specialization.
1417    pub(crate) fn get_cached_getitem_for_specialization(&self) -> Option<(PyRef<PyFunction>, u32)> {
1418        let ext = self.heaptype_ext.as_ref()?;
1419        // Check order: pointer (Acquire) then function version.
1420        let getitem = ext.specialization_cache.getitem.load_owned()?;
1421        let cached_version = ext
1422            .specialization_cache
1423            .getitem_version
1424            .load(Ordering::Acquire);
1425        if cached_version == 0 {
1426            return None;
1427        }
1428        Some((getitem, cached_version))
1429    }
1430
1431    pub fn get_direct_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
1432        self.attributes.get(attr_name)
1433    }
1434
1435    /// find_name_in_mro with method cache (MCACHE).
1436    /// Looks in tp_dict of types in MRO, bypasses descriptors.
1437    fn find_name_in_mro(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> {
1438        crate::vm::thread::try_with_current_vm(|vm| {
1439            self.lookup_ref_and_version_interned(name, vm).0
1440        })
1441        // No current VM: this thread is not registered for QSBR, so the
1442        // lock-free cache read protocol is not sound here. Walk the MRO
1443        // under the attributes locks instead (the dicts hold strong refs).
1444        .unwrap_or_else(|| self.find_name_in_mro_uncached(name))
1445    }
1446
1447    /// Raw MRO walk without cache.
1448    fn find_name_in_mro_uncached(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> {
1449        // Keep a strong snapshot of the MRO because tp_mro can be replaced
1450        // during dict lookup, e.g. when comparing to non-string keys.
1451        let mro: Vec<PyTypeRef> = self.mro.read().iter().cloned().collect();
1452        for cls in &mro {
1453            if let Some(value) = cls.attributes.get(name) {
1454                return Some(value);
1455            }
1456        }
1457        None
1458    }
1459
1460    /// _PyType_LookupRef: look up a name through the MRO without setting an exception.
1461    pub fn lookup_ref(&self, name: &Py<PyStr>, vm: &VirtualMachine) -> Option<PyObjectRef> {
1462        let interned_name = vm.ctx.interned_str(name)?;
1463        self.lookup_ref_and_version_interned(interned_name, vm).0
1464    }
1465
1466    pub fn get_super_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
1467        let mro = self.mro.read();
1468        mro.get(1..)?
1469            .iter()
1470            .find_map(|class| class.attributes.get(attr_name))
1471    }
1472
1473    /// Fast lookup for attribute existence on a class.
1474    pub fn has_attr(&self, attr_name: &'static PyStrInterned) -> bool {
1475        self.has_name_in_mro(attr_name)
1476    }
1477
1478    /// Check if attribute exists in MRO, using method cache for fast check.
1479    /// Unlike find_name_in_mro, avoids cloning the value on cache hit.
1480    fn has_name_in_mro(&self, name: &'static PyStrInterned) -> bool {
1481        #[cfg(all(feature = "threading", debug_assertions))]
1482        crate::vm::thread::debug_assert_current_thread_attached();
1483
1484        let version = self.tp_version_tag.load(Ordering::Acquire);
1485        if version != 0 {
1486            let idx = type_cache_hash(version, name);
1487            let entry = &TYPE_CACHE[idx];
1488            let name_ptr = name as *const _ as *mut _;
1489            loop {
1490                let seq1 = entry.begin_read();
1491                let v1 = entry.version.load(Ordering::Acquire);
1492                let type_version = self.tp_version_tag.load(Ordering::Acquire);
1493                if v1 != type_version
1494                    || !core::ptr::eq(entry.name.load(Ordering::Relaxed), name_ptr)
1495                {
1496                    break;
1497                }
1498                let ptr = entry.value.load(Ordering::Acquire);
1499                if entry.end_read(seq1) {
1500                    if !ptr.is_null() {
1501                        return true;
1502                    }
1503                    break;
1504                }
1505                continue;
1506            }
1507        }
1508
1509        // Cache miss — use find_name_in_mro which populates cache
1510        self.find_name_in_mro(name).is_some()
1511    }
1512
1513    pub fn get_attributes(&self, ctx: &Context) -> PyAttributes {
1514        // Gather all members here:
1515        let mut attributes = PyAttributes::default();
1516
1517        // mro[0] is self, so we iterate through the entire MRO in reverse
1518        for bc in self.mro.read().iter().map(|cls| -> &Self { cls }).rev() {
1519            attributes.extend(bc.attributes.attributes(ctx));
1520        }
1521
1522        attributes
1523    }
1524
1525    // bound method for every type
1526    pub(crate) fn __new__(zelf: PyRef<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1527        let (subtype, args): (PyRef<Self>, FuncArgs) = args.bind(vm)?;
1528        if !subtype.fast_issubclass(&zelf) {
1529            return Err(vm.new_type_error(format!(
1530                "{zelf}.__new__({subtype}): {subtype} is not a subtype of {zelf}",
1531                zelf = zelf.name(),
1532                subtype = subtype.name(),
1533            )));
1534        }
1535        call_slot_new(&zelf, subtype, args, vm)
1536    }
1537
1538    fn name_inner<'a, R: 'a>(
1539        &'a self,
1540        static_f: impl FnOnce(&'static str) -> R,
1541        heap_f: impl FnOnce(&'a HeapTypeExt) -> R,
1542    ) -> R {
1543        if let Some(ref ext) = self.heaptype_ext {
1544            heap_f(ext)
1545        } else {
1546            static_f(self.slots.name)
1547        }
1548    }
1549
1550    pub fn slot_name(&self) -> BorrowedValue<'_, str> {
1551        self.name_inner(
1552            |name| name.into(),
1553            |ext| PyRwLockReadGuard::map(ext.name.read(), |name| name.as_str()).into(),
1554        )
1555    }
1556
1557    pub fn __qualname__(&self, vm: &VirtualMachine) -> PyObjectRef {
1558        if let Some(ref heap_type) = self.heaptype_ext {
1559            heap_type.qualname.read().clone().into()
1560        } else {
1561            // For static types, return the name
1562            vm.ctx.new_str(self.name().deref()).into()
1563        }
1564    }
1565    pub fn __module__(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1566        // Heap types store the module in the type dict. Static types take the
1567        // text before the last `.` of the type name, or `builtins`.
1568        if self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
1569            return self
1570                .attributes
1571                .get(identifier!(vm, __module__))
1572                .ok_or_else(|| vm.new_attribute_error("__module__"));
1573        }
1574        let slot_name = self.slot_name();
1575        let module = if let Some((module, _)) = slot_name.rsplit_once('.') {
1576            vm.ctx.intern_str(module)
1577        } else {
1578            vm.ctx.intern_str("builtins")
1579        };
1580        Ok(module.to_object())
1581    }
1582
1583    /// The type's fully qualified name, the way the `%T` format code prints it:
1584    /// `module.qualname`, with a `builtins` or `__main__` module left off.
1585    pub fn fully_qualified_name(&self, vm: &VirtualMachine) -> PyResult<String> {
1586        let qualname = self.__qualname__(vm);
1587        let qualname = qualname
1588            .downcast_ref::<PyStr>()
1589            .and_then(|qualname| qualname.to_str())
1590            .map_or_else(|| self.name().to_string(), str::to_owned);
1591        let module = self.__module__(vm)?;
1592        Ok(
1593            match module.downcast_ref::<PyStr>().and_then(|m| m.to_str()) {
1594                Some("builtins" | "__main__") | None => qualname,
1595                Some(module) => format!("{module}.{qualname}"),
1596            },
1597        )
1598    }
1599
1600    pub fn name(&self) -> BorrowedValue<'_, str> {
1601        self.name_inner(
1602            |name| name.rsplit_once('.').map_or(name, |(_, name)| name).into(),
1603            |ext| PyRwLockReadGuard::map(ext.name.read(), |name| name.as_str()).into(),
1604        )
1605    }
1606
1607    // Type Data Slot API - CPython's PyObject_GetTypeData equivalent
1608
1609    /// Initialize type data for this type. Can only be called once.
1610    /// Returns an error if the type is not a heap type or if data is already initialized.
1611    pub fn init_type_data<T: Any + Send + Sync + 'static>(&self, data: T) -> Result<(), String> {
1612        let ext = self
1613            .heaptype_ext
1614            .as_ref()
1615            .ok_or_else(|| "Cannot set type data on non-heap types".to_string())?;
1616
1617        let mut type_data = ext.type_data.write();
1618        if type_data.is_some() {
1619            return Err("Type data already initialized".to_string());
1620        }
1621        *type_data = Some(TypeDataSlot::new(data));
1622        Ok(())
1623    }
1624
1625    /// Get a read guard to the type data.
1626    /// Returns None if the type is not a heap type, has no data, or the data type doesn't match.
1627    pub fn get_type_data<T: Any + 'static>(&self) -> Option<TypeDataRef<'_, T>> {
1628        self.heaptype_ext
1629            .as_ref()
1630            .and_then(|ext| TypeDataRef::try_new(ext.type_data.read()))
1631    }
1632
1633    /// Get a write guard to the type data.
1634    /// Returns None if the type is not a heap type, has no data, or the data type doesn't match.
1635    pub fn get_type_data_mut<T: Any + 'static>(&self) -> Option<TypeDataRefMut<'_, T>> {
1636        self.heaptype_ext
1637            .as_ref()
1638            .and_then(|ext| TypeDataRefMut::try_new(ext.type_data.write()))
1639    }
1640
1641    /// Check if this type has type data of the given type.
1642    pub fn has_type_data<T: Any + 'static>(&self) -> bool {
1643        self.heaptype_ext.as_ref().is_some_and(|ext| {
1644            ext.type_data
1645                .read()
1646                .as_ref()
1647                .is_some_and(|slot| slot.get::<T>().is_some())
1648        })
1649    }
1650}
1651
1652impl Py<PyType> {
1653    #[inline]
1654    pub fn slots(&self) -> &PyTypeSlots {
1655        &self.payload().slots
1656    }
1657
1658    #[inline]
1659    pub fn attributes(&self) -> &TypeNamespace {
1660        &self.payload().attributes
1661    }
1662
1663    #[inline]
1664    pub fn tp_version_tag(&self) -> &AtomicU32 {
1665        &self.payload().tp_version_tag
1666    }
1667
1668    #[inline]
1669    pub fn heaptype_ext(&self) -> Option<&HeapTypeExt> {
1670        self.payload().heaptype_ext.as_deref()
1671    }
1672
1673    pub fn is_subtype(&self, other: &Self) -> bool {
1674        is_subtype_with_mro(&self.mro.read(), self, other)
1675    }
1676
1677    /// Equivalent to CPython's PyType_CheckExact macro
1678    /// Checks if obj is exactly a type (not a subclass)
1679    pub fn check_exact<'a>(obj: &'a PyObject, vm: &VirtualMachine) -> Option<&'a Self> {
1680        obj.downcast_ref_if_exact::<PyType>(vm)
1681    }
1682
1683    /// Determines if `subclass` is actually a subclass of `cls`, this doesn't call __subclasscheck__,
1684    /// so only use this if `cls` is known to have not overridden the base __subclasscheck__ magic
1685    /// method.
1686    pub fn fast_issubclass(&self, cls: &impl Borrow<PyObject>) -> bool {
1687        let mro = self.mro.read();
1688        if mro.is_empty() {
1689            return self.as_object().is(cls.borrow());
1690        }
1691        mro.iter().any(|c| c.is(cls.borrow()))
1692    }
1693
1694    pub fn mro_map_collect<F, R>(&self, f: F) -> Vec<R>
1695    where
1696        F: Fn(&Self) -> R,
1697    {
1698        self.mro.read().iter().map(|x| &**x).map(f).collect()
1699    }
1700
1701    pub fn mro_collect(&self) -> Vec<PyRef<PyType>> {
1702        self.mro
1703            .read()
1704            .iter()
1705            .map(|x| &**x)
1706            .map(|x| x.to_owned())
1707            .collect()
1708    }
1709
1710    pub fn iter_base_chain(&self) -> impl Iterator<Item = &Self> {
1711        core::iter::successors(Some(self), |cls| cls.base.deref())
1712    }
1713
1714    pub fn extend_methods(&'static self, method_defs: &'static [PyMethodDef], ctx: &Context) {
1715        for method_def in method_defs {
1716            let method = method_def.to_proper_method(self, ctx);
1717            self.set_attr(ctx.intern_str(method_def.name), method);
1718        }
1719    }
1720}
1721
1722impl PyType {
1723    pub fn __ror__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
1724        or_(other, zelf, vm)
1725    }
1726
1727    pub fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
1728        or_(zelf, other, vm)
1729    }
1730
1731    fn check_set_special_type_attr(
1732        &self,
1733        name: &PyStrInterned,
1734        vm: &VirtualMachine,
1735    ) -> PyResult<()> {
1736        if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
1737            return Err(vm.new_type_error(format!(
1738                "cannot set '{}' attribute of immutable type '{}'",
1739                name,
1740                self.slot_name()
1741            )));
1742        }
1743        Ok(())
1744    }
1745}
1746
1747#[pyclass(
1748    itemsize = core::mem::size_of::<crate::builtins::descriptor::PyMemberDefLayout>(),
1749    with(
1750        Py,
1751        Constructor,
1752        Initializer,
1753        GetAttr,
1754        SetAttr,
1755        Callable,
1756        AsNumber,
1757        Representable
1758    ),
1759    flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
1760)]
1761impl PyType {}
1762
1763/// Accepts and ignores any arguments.
1764struct PrepareArgs;
1765
1766impl FromArgs for PrepareArgs {
1767    const PARAMS: Option<&'static [Param]> = Some(&[
1768        Param::positional_only("name"),
1769        Param::positional_only("bases"),
1770        Param::var_keyword("kwds"),
1771    ]);
1772
1773    fn from_args(_vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
1774        core::mem::take(args);
1775        Ok(Self)
1776    }
1777}
1778
1779impl Constructor for PyType {
1780    type Args = FuncArgs;
1781
1782    fn slot_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1783        vm_trace!("type.__new__ {:?}", args);
1784
1785        let is_type_type = metatype.is(vm.ctx.types.type_type);
1786        if is_type_type && args.args.len() == 1 && args.kwargs.is_empty() {
1787            return Ok(args.args[0].class().to_owned().into());
1788        }
1789
1790        if args.args.len() != 3 {
1791            return Err(vm.new_type_error(if is_type_type {
1792                "type() takes 1 or 3 arguments".to_owned()
1793            } else {
1794                format!(
1795                    "type.__new__() takes exactly 3 arguments ({} given)",
1796                    args.args.len()
1797                )
1798            }));
1799        }
1800
1801        let (name, bases, dict, kwargs): (PyStrRef, PyTupleRef, PyDictRef, KwArgs) =
1802            args.clone().bind_for(vm, Self::NAME)?;
1803
1804        // A mapping that is not an exact dict (e.g. OrderedDict) keeps its
1805        // own iteration order; copy via the mapping protocol so that order
1806        // lands in the type dict.
1807        let dict = if args.args[2].class().is(vm.ctx.types.dict_type) {
1808            dict
1809        } else {
1810            let copied = vm.ctx.new_dict();
1811            copied.merge_object(args.args[2].clone(), vm)?;
1812            copied
1813        };
1814
1815        if name.as_bytes().contains(&0) {
1816            return Err(vm.new_value_error("type name must not contain null characters"));
1817        }
1818        let name = name.try_into_utf8(vm)?;
1819
1820        let (metatype, base, bases, base_is_type) = if bases.as_slice().is_empty() {
1821            let base = vm.ctx.types.object_type.to_owned();
1822            let bases = PyTuple::new_ref_typed(vec![base.clone()], &vm.ctx);
1823            (metatype, base, bases, false)
1824        } else {
1825            for obj in bases.as_slice() {
1826                if obj.downcast_ref::<Self>().is_none() {
1827                    if vm
1828                        .get_attribute_opt(obj, identifier!(vm, __mro_entries__))?
1829                        .is_some()
1830                    {
1831                        return Err(vm.new_type_error(
1832                            "type() doesn't support MRO entry resolution; \
1833                             use types.new_class()",
1834                        ));
1835                    }
1836                    return Err(vm.new_type_error("bases must be types"));
1837                }
1838            }
1839            let bases = bases.try_into_typed::<Self>(vm)?;
1840
1841            // Search the bases for the proper metatype to deal with this:
1842            let winner = calculate_meta_class(metatype.clone(), bases.as_slice(), vm)?;
1843            let metatype = if !winner.is(&metatype) {
1844                if let Some(ref slot_new) = winner.slots.new.load() {
1845                    // Pass it to the winner
1846                    return slot_new(winner, args, vm);
1847                }
1848                winner
1849            } else {
1850                metatype
1851            };
1852
1853            let base = best_base(bases.as_slice(), vm)?;
1854            let base_is_type = base.is(vm.ctx.types.type_type);
1855
1856            (metatype, base.to_owned(), bases, base_is_type)
1857        };
1858
1859        let qualname = dict
1860            .get_item_opt(identifier!(vm, __qualname__), vm)?
1861            .map(|obj| downcast_qualname(obj, vm))
1862            .transpose()?
1863            .unwrap_or_else(|| {
1864                // If __qualname__ is not provided, we can use the name as default
1865                name.clone().into_wtf8()
1866            });
1867
1868        let mut attributes = dict.to_attributes(vm, |vm| {
1869            crate::stdlib::_warnings::warn(
1870                vm.ctx.exceptions.runtime_warning,
1871                format!("non-string key in the __dict__ of class {name}"),
1872                1,
1873                vm,
1874            )
1875        })?;
1876        attributes.shift_remove(identifier!(vm, __qualname__));
1877
1878        // Check __doc__ for surrogates - raises UnicodeEncodeError during type creation
1879        if let Some(doc) = attributes.get(identifier!(vm, __doc__))
1880            && let Some(doc_str) = doc.downcast_ref::<PyStr>()
1881        {
1882            doc_str.ensure_valid_utf8(vm)?;
1883        }
1884
1885        if let Some(f) = attributes.get_mut(identifier!(vm, __init_subclass__))
1886            && f.class().is(vm.ctx.types.function_type)
1887        {
1888            *f = PyClassMethod::from(f.clone()).into_pyobject(vm);
1889        }
1890
1891        if let Some(f) = attributes.get_mut(identifier!(vm, __class_getitem__))
1892            && f.class().is(vm.ctx.types.function_type)
1893        {
1894            *f = PyClassMethod::from(f.clone()).into_pyobject(vm);
1895        }
1896
1897        if let Some(f) = attributes.get_mut(identifier!(vm, __new__))
1898            && f.class().is(vm.ctx.types.function_type)
1899        {
1900            *f = PyStaticMethod::from(f.clone()).into_pyobject(vm);
1901        }
1902
1903        if let Some(globals) = crate::frame::current_globals() {
1904            let entry = attributes.entry(identifier!(vm, __module__));
1905            if let Entry::Vacant(entry) = entry
1906                && let Some(module_name) = globals.get_item_opt(identifier!(vm, __name__), vm)?
1907            {
1908                entry.insert(module_name);
1909            }
1910        }
1911
1912        if attributes.get(identifier!(vm, __eq__)).is_some()
1913            && attributes.get(identifier!(vm, __hash__)).is_none()
1914        {
1915            // if __eq__ exists but __hash__ doesn't, overwrite it with None so it doesn't inherit the default hash
1916            // https://docs.python.org/3/reference/datamodel.html#object.__hash__
1917            attributes.insert(identifier!(vm, __hash__), vm.ctx.none.clone().into());
1918        }
1919
1920        let (heaptype_slots, add_dict, add_weakref): (
1921            Option<PyRef<PyTuple<PyStrRef>>>,
1922            bool,
1923            bool,
1924        ) = if let Some(x) = attributes.get(identifier!(vm, __slots__)) {
1925            // Check if __slots__ is bytes - not allowed
1926            if x.class().is(vm.ctx.types.bytes_type) {
1927                return Err(vm.new_type_error("__slots__ items must be strings, not 'bytes'"));
1928            }
1929
1930            let slots = if x.class().is(vm.ctx.types.str_type) {
1931                let x = unsafe { x.downcast_unchecked_ref::<PyStr>() };
1932                PyTuple::new_ref_typed(vec![x.to_owned()], &vm.ctx)
1933            } else {
1934                let iter = x.get_iter(vm)?;
1935                let elements = {
1936                    let mut elements = Vec::new();
1937                    while let PyIterReturn::Return(element) = iter.next(vm)? {
1938                        // Check if any slot item is bytes
1939                        if element.class().is(vm.ctx.types.bytes_type) {
1940                            return Err(
1941                                vm.new_type_error("__slots__ items must be strings, not 'bytes'")
1942                            );
1943                        }
1944                        elements.push(element);
1945                    }
1946                    elements
1947                };
1948                let tuple = elements.into_pytuple(vm);
1949                tuple.try_into_typed(vm)?
1950            };
1951
1952            // Any nonempty __slots__ is rejected when the base has a variable
1953            // item size, including a tuple of only `__dict__` or `__weakref__`.
1954            // Types like weakref.ref have itemsize 0 and do allow slots.
1955            if !slots.as_slice().is_empty() && base.slots.itemsize > 0 {
1956                return Err(vm.new_type_error(format!(
1957                    "nonempty __slots__ not supported for subtype of '{}'",
1958                    base.name()
1959                )));
1960            }
1961
1962            // Validate slot names and track duplicates
1963            let mut seen_dict = false;
1964            let mut seen_weakref = false;
1965            for slot in slots.as_slice() {
1966                // Use isidentifier for validation (handles Unicode properly)
1967                if !slot.isidentifier() {
1968                    return Err(vm.new_type_error("__slots__ must be identifiers"));
1969                }
1970
1971                let slot_name = slot.as_bytes();
1972
1973                // Check for duplicate __dict__
1974                if slot_name == b"__dict__" {
1975                    if seen_dict {
1976                        return Err(
1977                            vm.new_type_error("__dict__ slot disallowed: we already got one")
1978                        );
1979                    }
1980                    seen_dict = true;
1981                }
1982
1983                // Check for duplicate __weakref__
1984                if slot_name == b"__weakref__" {
1985                    if seen_weakref {
1986                        return Err(
1987                            vm.new_type_error("__weakref__ slot disallowed: we already got one")
1988                        );
1989                    }
1990                    seen_weakref = true;
1991                }
1992
1993                // __qualname__ and __classcell__ are written by the compiler
1994                // and may share a name with a slot.
1995                if slot_name != b"__qualname__"
1996                    && slot_name != b"__classcell__"
1997                    && slot_name != b"__classdictcell__"
1998                    && attributes.contains_key(vm.ctx.intern_str(slot.as_wtf8()))
1999                {
2000                    return Err(vm.new_value_error(format!(
2001                        "'{}' in __slots__ conflicts with class variable",
2002                        slot.as_wtf8()
2003                    )));
2004                }
2005            }
2006
2007            // Check if base class already has __dict__ - can't redefine it
2008            if seen_dict && base.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
2009                return Err(vm.new_type_error("__dict__ slot disallowed: we already got one"));
2010            }
2011
2012            // Check if base class already has __weakref__ - can't redefine it
2013            if seen_weakref && base.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF) {
2014                return Err(vm.new_type_error("__weakref__ slot disallowed: we already got one"));
2015            }
2016
2017            // Check if __dict__ or __weakref__ is in slots
2018            let dict_name = "__dict__";
2019            let weakref_name = "__weakref__";
2020            let has_dict = slots.as_slice().iter().any(|s| s.as_wtf8() == dict_name);
2021            let add_weakref = seen_weakref;
2022
2023            // Filter out __dict__ and __weakref__ from slots
2024            // (they become descriptors, not member slots), then sort so
2025            // __class__ assignment can compare layouts by slot name.
2026            let mut filtered: Vec<PyStrRef> = slots
2027                .as_slice()
2028                .iter()
2029                .filter(|s| s.as_wtf8() != dict_name && s.as_wtf8() != weakref_name)
2030                .cloned()
2031                .collect();
2032            filtered.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
2033            let filtered_slots = PyTuple::new_ref_typed(filtered, &vm.ctx);
2034
2035            (Some(filtered_slots), has_dict, add_weakref)
2036        } else {
2037            (None, false, false)
2038        };
2039
2040        // FIXME: this is a temporary fix. multi bases with multiple slots will break object
2041        let base_member_count = bases
2042            .as_slice()
2043            .iter()
2044            .map(|base| base.slots.member_count)
2045            .max()
2046            .unwrap();
2047        let heaptype_member_count = heaptype_slots.as_ref().map_or(0, |x| x.as_slice().len());
2048        let member_count: usize = base_member_count + heaptype_member_count;
2049
2050        let mut flags = PyTypeFlags::HEAP_TYPE;
2051
2052        // Check if we may add dict
2053        // We can only add a dict if the primary base class doesn't already have one
2054        // In CPython, this checks tp_dictoffset == 0
2055        let may_add_dict = !base.slots.flags.has_feature(PyTypeFlags::HAS_DICT);
2056
2057        // Add HAS_DICT and MANAGED_DICT if:
2058        // 1. __slots__ is not defined AND base doesn't have dict, OR
2059        // 2. __dict__ is in __slots__
2060        if (heaptype_slots.is_none() && may_add_dict) || add_dict {
2061            // type_ready_managed_dict: fixed-size managed-dict types
2062            // get an inline values array after the object.
2063            flags |= if base.slots.itemsize == 0 {
2064                MANAGED_DICT_INLINE_FLAGS
2065            } else {
2066                MANAGED_DICT_FLAGS
2067            };
2068        }
2069
2070        // Add HAS_WEAKREF if:
2071        // 1. __slots__ is not defined (automatic weakref support), OR
2072        // 2. __weakref__ is in __slots__
2073        // A variable-size base does not gain a weakref slot.
2074        let may_add_weakref =
2075            base.slots.itemsize == 0 && !base.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF);
2076        if (heaptype_slots.is_none() && may_add_weakref) || add_weakref {
2077            flags |= WEAKREF_FLAGS;
2078        }
2079
2080        let (slots, heaptype_ext) = {
2081            let slots = PyTypeSlots {
2082                flags: crate::types::AtomicPyTypeFlags::from_plain(flags),
2083                member_count,
2084                itemsize: base.slots.itemsize,
2085                ..PyTypeSlots::heap_default()
2086            };
2087            let heaptype_ext = HeapTypeExt {
2088                name: PyRwLock::new(name),
2089                qualname: PyRwLock::new(qualname),
2090                slots: heaptype_slots.clone(),
2091                type_data: PyRwLock::new(None),
2092                specialization_cache: TypeSpecializationCache::new(),
2093                interpreter_id: HeapTypeExt::creating_interpreter_id(),
2094            };
2095            (slots, heaptype_ext)
2096        };
2097
2098        let custom_mro = !metatype.is(vm.ctx.types.type_type);
2099        let typ = Self::new_heap_inner(
2100            base,
2101            bases,
2102            attributes,
2103            slots,
2104            heaptype_ext,
2105            metatype,
2106            &vm.ctx,
2107            custom_mro,
2108        )
2109        .map_err(|e| vm.new_type_error(e))?;
2110
2111        // tp_dict keeps non-string keys so lookup can hash-compare them.
2112        if let Some(tp_dict) = typ.attributes.as_dict() {
2113            for (key, value) in &*dict {
2114                if key.downcast_ref::<PyStr>().is_none() {
2115                    tp_dict.set_item(&*key, value, vm)?;
2116                }
2117            }
2118        }
2119
2120        // Fill __classcell__ before a custom mro() runs so methods that
2121        // close over __class__ can execute during type creation.
2122        if let Some(cell) = typ.attributes.get(identifier!(vm, __classcell__)) {
2123            let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
2124                vm.new_type_error(format!(
2125                    "__classcell__ must be a nonlocal cell, not {}",
2126                    cell.class().name()
2127                ))
2128            })?;
2129            cell.set(Some(typ.clone().into()));
2130            typ.attributes.remove(identifier!(vm, __classcell__));
2131        }
2132        if let Some(cell) = typ.attributes.get(identifier!(vm, __classdictcell__)) {
2133            let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
2134                vm.new_type_error(format!(
2135                    "__classdictcell__ must be a nonlocal cell, not {}",
2136                    cell.class().name()
2137                ))
2138            })?;
2139            let namespace = typ
2140                .attributes
2141                .as_dict()
2142                .expect("a type built by type.__new__ has a dict namespace");
2143            cell.set(Some(namespace.to_owned().into()));
2144            typ.attributes.remove(identifier!(vm, __classdictcell__));
2145        }
2146
2147        if custom_mro {
2148            mro_internal(&typ, vm)?;
2149            typ.init_slots(&vm.ctx);
2150        }
2151
2152        if let Some(ref slots) = heaptype_slots {
2153            let class_name = typ.name().to_string();
2154            for (offset, member) in (base_member_count..).zip(slots.as_slice().iter()) {
2155                // Apply name mangling for private attributes (__x -> _ClassName__x)
2156                let member_str = member
2157                    .to_str()
2158                    .ok_or_else(|| vm.new_type_error("__slots__ must be valid UTF-8 strings"))?;
2159                let mangled_name = mangle_name(&class_name, member_str);
2160                let member_def = PyMemberDef {
2161                    name: mangled_name.clone(),
2162                    kind: MemberKind::ObjectEx,
2163                    offset: crate::object::slot_member_offset(offset),
2164                    flags: PyMemberFlags::empty(),
2165                    doc: ItemDoc::NONE,
2166                };
2167                let attr_name = vm.ctx.intern_str(mangled_name.as_str());
2168                let member_descriptor: PyRef<PyMemberDescriptor> =
2169                    vm.ctx.new_pyref(PyMemberDescriptor {
2170                        common: PyDescriptorOwned {
2171                            typ: typ.clone(),
2172                            name: attr_name,
2173                            qualname: PyRwLock::new(None),
2174                        },
2175                        member: member_def,
2176                        access: MemberAccess::Offset,
2177                    });
2178                // __slots__ attributes always get a member descriptor
2179                // (this overrides any inherited attribute from MRO)
2180                typ.set_attr(attr_name, member_descriptor.into());
2181                // `init_slots` already ran in `new_heap_inner` before these member
2182                // descriptors existed, so a slot name shaped like a dunder (e.g. a
2183                // class that puts "__setitem__" in `__slots__` to store a per-instance
2184                // callable under a slot descriptor) was never wired into the type's C
2185                // slots (mp_ass_subscript and friends). Recompute the slot now that
2186                // the attribute is actually present on the type.
2187                if attr_name.as_bytes().starts_with(b"__") && attr_name.as_bytes().ends_with(b"__")
2188                {
2189                    typ.update_slot::<true>(attr_name, &vm.ctx);
2190                }
2191            }
2192        }
2193
2194        // All *classes* should have a dict. Exceptions are *instances* of
2195        // classes that define __slots__ and instances of built-in classes
2196        // (with exceptions, e.g function)
2197        // Also, type subclasses don't need their own __dict__ descriptor
2198        // since they inherit it from type
2199
2200        // Add __dict__ descriptor after type creation to ensure correct __objclass__
2201        // Only add if:
2202        // 1. base is not type (type subclasses inherit __dict__ from type)
2203        // 2. the class has HAS_DICT flag (i.e., __slots__ was not defined or __dict__ is in __slots__)
2204        // 3. no base class in MRO already provides __dict__ descriptor
2205        if !base_is_type && typ.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
2206            let __dict__ = identifier!(vm, __dict__);
2207            let has_inherited_dict = typ
2208                .mro
2209                .read()
2210                .iter()
2211                .any(|base| base.attributes.contains(__dict__));
2212            if !typ.attributes.contains(__dict__) && !has_inherited_dict {
2213                let getset = super::PyGetSet::new("__dict__", &typ, &vm.ctx)
2214                    .with_get(subtype_get_dict)
2215                    .with_set(subtype_set_dict)
2216                    .with_doc(ItemDoc::static_text("dictionary for instance variables"));
2217                let descriptor = PyRef::new_ref(getset, vm.ctx.types.getset_type.to_owned(), None);
2218                typ.attributes.set(__dict__, descriptor.into());
2219            }
2220        }
2221
2222        // Add __weakref__ descriptor for types with HAS_WEAKREF
2223        if typ.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF) {
2224            let __weakref__ = vm.ctx.intern_str("__weakref__");
2225            let has_inherited_weakref = typ
2226                .mro
2227                .read()
2228                .iter()
2229                .any(|base| base.attributes.contains(__weakref__));
2230            if !typ.attributes.contains(__weakref__) && !has_inherited_weakref {
2231                let getset = super::PyGetSet::new("__weakref__", &typ, &vm.ctx)
2232                    .with_get(subtype_get_weakref)
2233                    .with_set(subtype_set_weakref)
2234                    .with_doc(ItemDoc::static_text(
2235                        "list of weak references to the object",
2236                    ));
2237                let descriptor = PyRef::new_ref(getset, vm.ctx.types.getset_type.to_owned(), None);
2238                typ.attributes.set(__weakref__, descriptor.into());
2239            }
2240        }
2241
2242        // Set __doc__ to None if not already present in the type's dict
2243        // This matches CPython's behavior in type_dict_set_doc (typeobject.c)
2244        // which ensures every type has a __doc__ entry in its dict
2245        {
2246            let __doc__ = identifier!(vm, __doc__);
2247            if !typ.attributes.contains(__doc__) {
2248                typ.attributes.set(__doc__, vm.ctx.none());
2249            }
2250        }
2251
2252        // avoid deadlock
2253        let attributes = typ
2254            .attributes
2255            .entries()
2256            .into_iter()
2257            .filter_map(|(name, obj)| {
2258                vm.get_method(obj.clone(), identifier!(vm, __set_name__))
2259                    .map(|res| res.map(|meth| (obj, name, meth)))
2260            })
2261            .collect::<PyResult<Vec<_>>>()?;
2262        for (obj, name, set_name) in attributes {
2263            let name_repr = name
2264                .str(vm)
2265                .map_or_else(|_| "?".to_owned(), |name| name.to_string());
2266            set_name.call((typ.clone(), name), vm).inspect_err(|e| {
2267                // PEP 678: Add a note to the original exception instead of wrapping it
2268                // (Python 3.12+, gh-77757)
2269                let note = format!(
2270                    "Error calling __set_name__ on '{}' instance '{}' in '{}'",
2271                    obj.class().name(),
2272                    name_repr,
2273                    typ.name()
2274                );
2275                // Ignore result - adding a note is best-effort, the original exception is what matters
2276                drop(vm.call_method(e.as_object(), "add_note", (vm.ctx.new_str(note.as_str()),)));
2277            })?;
2278        }
2279
2280        // type_new_init_subclass: super(type, type).__init_subclass__(**kwds)
2281        let super_obj = vm
2282            .ctx
2283            .types
2284            .super_type
2285            .as_object()
2286            .call((typ.clone(), typ.clone()), vm)?;
2287        super_obj
2288            .get_attr(identifier!(vm, __init_subclass__), vm)?
2289            .call(kwargs, vm)?;
2290
2291        Ok(typ.into())
2292    }
2293
2294    fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
2295        unimplemented!("use slot_new")
2296    }
2297}
2298
2299const SIGNATURE_END_MARKER: &str = ")\n--\n\n";
2300fn get_signature(doc: &str) -> Option<&str> {
2301    doc.find(SIGNATURE_END_MARKER).map(|index| &doc[..=index])
2302}
2303
2304fn find_signature<'a>(name: &str, doc: &'a str) -> Option<&'a str> {
2305    let name = name.rsplit('.').next().unwrap();
2306    let doc = doc.strip_prefix(name)?;
2307    doc.starts_with('(').then_some(doc)
2308}
2309
2310pub(crate) fn get_text_signature_from_internal_doc<'a>(
2311    name: &str,
2312    internal_doc: &'a str,
2313) -> Option<&'a str> {
2314    find_signature(name, internal_doc).and_then(get_signature)
2315}
2316
2317// _PyType_DocWithoutSignature in CPython
2318fn doc_without_signature<'a>(name: &str, internal_doc: &'a str) -> &'a str {
2319    // If the doc starts with the type name and a '(', it's a signature
2320    if let Some(doc_without_sig) = find_signature(name, internal_doc) {
2321        // Find where the signature ends
2322        if let Some(sig_end_pos) = doc_without_sig.find(SIGNATURE_END_MARKER) {
2323            let after_sig = &doc_without_sig[sig_end_pos + SIGNATURE_END_MARKER.len()..];
2324            // Return the documentation after the signature, or empty string if none
2325            return after_sig;
2326        }
2327    }
2328    // If no signature found, return the whole doc
2329    internal_doc
2330}
2331
2332// _PyType_GetDocFromInternalDoc in CPython
2333pub(crate) fn get_doc_from_internal_doc<'a>(name: &str, internal_doc: &'a str) -> Option<&'a str> {
2334    let doc = doc_without_signature(name, internal_doc);
2335    (!doc.is_empty()).then_some(doc)
2336}
2337
2338pub(crate) fn rendered_item_doc(name: &str, doc: ItemDoc) -> Option<&'static str> {
2339    if doc.len != 0 {
2340        return db_doc(doc.offset, doc.len);
2341    }
2342    doc.text
2343        .and_then(|text| get_doc_from_internal_doc(name, text))
2344}
2345
2346impl Initializer for PyType {
2347    type Args = FuncArgs;
2348
2349    // type_init
2350    fn slot_init(_zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
2351        // type.__init__() takes 1 or 3 arguments
2352        if args.args.len() == 1 && !args.kwargs.is_empty() {
2353            return Err(vm.new_type_error("type.__init__() takes no keyword arguments"));
2354        }
2355        if args.args.len() != 1 && args.args.len() != 3 {
2356            return Err(vm.new_type_error("type.__init__() takes 1 or 3 arguments"));
2357        }
2358        Ok(())
2359    }
2360
2361    fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
2362        unreachable!("slot_init is defined")
2363    }
2364}
2365
2366impl GetAttr for PyType {
2367    fn getattro(zelf: &Py<Self>, name_str: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
2368        #[cold]
2369        fn attribute_error(
2370            zelf: &Py<PyType>,
2371            name: &Wtf8,
2372            vm: &VirtualMachine,
2373        ) -> PyBaseExceptionRef {
2374            vm.new_attribute_error(format!(
2375                "type object '{}' has no attribute '{}'",
2376                zelf.slot_name(),
2377                name,
2378            ))
2379        }
2380
2381        let Some(name) = vm.ctx.interned_str(name_str) else {
2382            return Err(attribute_error(zelf, name_str.as_wtf8(), vm));
2383        };
2384        vm_trace!("type.__getattribute__({:?}, {:?})", zelf, name);
2385        let mcl = zelf.class();
2386        let mcl_attr = mcl.get_attr(name);
2387
2388        if let Some(ref attr) = mcl_attr {
2389            let attr_class = attr.class();
2390            let has_descr_set = attr_class.slots.descr_set.load().is_some();
2391            if has_descr_set {
2392                let descr_get = attr_class.slots.descr_get.load();
2393                if let Some(descr_get) = descr_get {
2394                    return descr_get(
2395                        attr.as_object(),
2396                        Some(zelf.as_object()),
2397                        Some(mcl.as_object()),
2398                        vm,
2399                    );
2400                }
2401            }
2402        }
2403
2404        let zelf_attr = zelf.get_attr(name);
2405
2406        if let Some(attr) = zelf_attr {
2407            let descr_get = attr.class().slots().descr_get.load();
2408            if let Some(descr_get) = descr_get {
2409                descr_get(attr.as_object(), None, Some(zelf.as_object()), vm)
2410            } else {
2411                Ok(attr)
2412            }
2413        } else if let Some(attr) = mcl_attr {
2414            vm.call_if_get_descriptor(&attr, zelf.to_owned().into())
2415        } else {
2416            Err(attribute_error(zelf, name_str.as_wtf8(), vm))
2417        }
2418    }
2419}
2420
2421#[pyclass]
2422impl Py<PyType> {
2423    #[pygetset]
2424    fn __mro__(&self, vm: &VirtualMachine) -> PyObjectRef {
2425        let mro = self.mro.read();
2426        if mro.is_empty() {
2427            return vm.ctx.none();
2428        }
2429        let elements: Vec<PyObjectRef> = mro.iter().map(|x| x.as_object().to_owned()).collect();
2430        drop(mro);
2431        vm.ctx.new_tuple(elements).into()
2432    }
2433
2434    #[pygetset]
2435    fn __doc__(&self, vm: &VirtualMachine) -> PyResult {
2436        // Similar to CPython's type_get_doc
2437        // For non-heap types (static types), check if there's an internal doc
2438        let internal_doc = self.slots.doc;
2439        if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
2440            && (internal_doc.text.is_some() || internal_doc.len != 0)
2441        {
2442            let doc_str = rendered_item_doc(&self.name(), internal_doc);
2443            return Ok(doc_str.map_or_else(|| vm.ctx.none(), |doc| vm.ctx.new_str(doc).into()));
2444        }
2445
2446        // Check if there's a __doc__ in THIS type's dict only (not MRO)
2447        // CPython returns None if __doc__ is not in the type's own dict
2448        if let Some(doc_attr) = self.get_direct_attr(vm.ctx.intern_str("__doc__")) {
2449            // If it's a descriptor, call its __get__ method
2450            let descr_get = doc_attr.class().slots().descr_get.load();
2451            if let Some(descr_get) = descr_get {
2452                descr_get(doc_attr.as_object(), None, Some(self.as_object()), vm)
2453            } else {
2454                Ok(doc_attr)
2455            }
2456        } else {
2457            Ok(vm.ctx.none())
2458        }
2459    }
2460
2461    #[pygetset(setter)]
2462    fn set___doc__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
2463        // Similar to CPython's type_set_doc
2464        let value = value.ok_or_else(|| {
2465            vm.new_type_error(format!(
2466                "cannot delete '__doc__' attribute of immutable type '{}'",
2467                self.name()
2468            ))
2469        })?;
2470
2471        // Check if we can set this special type attribute
2472        self.check_set_special_type_attr(identifier!(vm, __doc__), vm)?;
2473
2474        let _prev_value = PyType::with_type_lock(vm, || {
2475            self.modified_inner();
2476            self.attributes.insert(identifier!(vm, __doc__), value)
2477        });
2478
2479        Ok(())
2480    }
2481
2482    #[pymethod]
2483    fn __dir__(&self, vm: &VirtualMachine) -> PyList {
2484        let attributes: Vec<PyObjectRef> = self
2485            .get_attributes(&vm.ctx)
2486            .into_iter()
2487            .map(|(k, _)| k.to_object())
2488            .collect();
2489        PyList::from(attributes)
2490    }
2491
2492    #[pymethod]
2493    fn __instancecheck__(&self, instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
2494        // Use real_is_instance to avoid infinite recursion
2495        instance.real_is_instance(self.as_object(), vm)
2496    }
2497
2498    #[pymethod]
2499    fn __subclasscheck__(&self, subclass: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
2500        // Use real_is_subclass to avoid going through __subclasscheck__ recursion
2501        // This matches CPython's type___subclasscheck___impl which calls _PyObject_RealIsSubclass
2502        subclass.real_is_subclass(self.as_object(), vm)
2503    }
2504
2505    #[pyclassmethod]
2506    fn __subclasshook__(_args: FuncArgs, vm: &VirtualMachine) -> PyObjectRef {
2507        vm.ctx.not_implemented()
2508    }
2509
2510    #[pymethod]
2511    fn mro(&self, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
2512        Ok(mro_implementation(self, vm)?
2513            .into_iter()
2514            .map(Into::into)
2515            .collect())
2516    }
2517
2518    #[pygetset]
2519    fn __bases__(&self, vm: &VirtualMachine) -> PyTupleRef {
2520        PyType::with_type_lock(vm, || self.bases.read().clone().into_untyped())
2521    }
2522    #[pygetset(setter, name = "__bases__")]
2523    fn set_bases(zelf: &Self, bases_tuple: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> {
2524        // TODO: Assigning to __bases__ is only used in typing.NamedTupleMeta.__new__
2525        // Rather than correctly re-initializing the class, we are skipping a few steps for now
2526        if zelf.slots().flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
2527            return Err(vm.new_type_error(format!(
2528                "cannot set '__bases__' attribute of immutable type '{}'",
2529                zelf.name()
2530            )));
2531        }
2532        if bases_tuple.as_slice().is_empty() {
2533            return Err(vm.new_type_error(format!(
2534                "can only assign non-empty tuple to {}.__bases__, not ()",
2535                zelf.name()
2536            )));
2537        }
2538        for base in bases_tuple.as_slice() {
2539            if base.downcast_ref::<PyType>().is_none() {
2540                return Err(vm.new_type_error(format!(
2541                    "{}.__bases__ must be tuple of classes, not '{}'",
2542                    zelf.name(),
2543                    base.class().name()
2544                )));
2545            }
2546        }
2547        let bases = bases_tuple.try_into_typed::<PyType>(vm)?;
2548
2549        // Compute the new solid base before committing anything. This also
2550        // validates the new bases (BASETYPE flag, no instance layout
2551        // conflict), the same checks type creation performs.
2552        let new_base = best_base(bases.as_slice(), vm)?.to_owned();
2553
2554        // Reject reparenting onto a base whose instances have an incompatible
2555        // object layout.
2556        let old_base_owned = zelf.base.load_owned();
2557        let old_base = old_base_owned
2558            .as_deref()
2559            .unwrap_or(vm.ctx.types.object_type);
2560        compatible_for_assignment(old_base, &new_base, "__bases__", vm)?;
2561
2562        // References released inside the critical section are collected here
2563        // and dropped after the lock: dropping them inside can run arbitrary
2564        // code that re-acquires the non-reentrant type mutex.
2565        let mut retired: Vec<PyObjectRef> = Vec::new();
2566
2567        // A base swapped out of `zelf.base` may still be observed by
2568        // concurrent lock-free readers; keep it alive in the frame's
2569        // temporary refs so they never see a dangling pointer.
2570        let keep_alive = |type_ref: PyTypeRef, retired: &mut Vec<PyObjectRef>| {
2571            if let Some(frame) = vm.current_frame() {
2572                frame
2573                    .iframe()
2574                    .cold()
2575                    .temporary_refs
2576                    .lock()
2577                    .push(type_ref.into());
2578            } else {
2579                retired.push(type_ref.into());
2580            }
2581        };
2582
2583        let add_as_subclass = |bases: &[PyTypeRef]| {
2584            let weakref_type = super::PyWeak::static_type();
2585            for base in bases {
2586                base.subclasses.write().push(
2587                    zelf.as_object()
2588                        .downgrade_with_weakref_typ_opt(None, weakref_type.to_owned())
2589                        .unwrap(),
2590                );
2591            }
2592        };
2593
2594        let remove_as_subclass = |bases: &[PyTypeRef], retired: &mut Vec<PyObjectRef>| {
2595            for base in bases {
2596                let mut subclasses = base.subclasses.write();
2597                let mut kept = Vec::with_capacity(subclasses.len());
2598                for weak in subclasses.drain(..) {
2599                    match weak.upgrade() {
2600                        Some(obj) if obj.is(zelf.as_object()) => {
2601                            retired.push(obj);
2602                            retired.push(weak.into());
2603                        }
2604                        Some(obj) => {
2605                            retired.push(obj);
2606                            kept.push(weak);
2607                        }
2608                        None => retired.push(weak.into()),
2609                    }
2610                }
2611                *subclasses = kept;
2612            }
2613        };
2614
2615        let result = PyType::with_type_lock(vm, || {
2616            for base in bases.as_slice() {
2617                if is_subtype_with_mro(&base.mro.read(), base, zelf)
2618                    || (!base.mro.read().is_empty() && type_is_subtype_base_chain(base, zelf, vm))
2619                {
2620                    return Err(vm.new_type_error("a __bases__ item causes an inheritance cycle"));
2621                }
2622            }
2623
2624            let old_bases = core::mem::replace(&mut *zelf.bases.write(), bases.clone());
2625            let old_base = unsafe { zelf.base.swap(Some(new_base.clone())) };
2626
2627            // Recursively update the mros of this class and all subclasses,
2628            // recording the previous mros so a failure can be rolled back.
2629            fn update_mro_recursively(
2630                cls: &Py<PyType>,
2631                undo: &mut Vec<(PyTypeRef, Vec<PyTypeRef>)>,
2632                vm: &VirtualMachine,
2633            ) -> PyResult<i32> {
2634                let old_ptr = cls.mro.read().as_ptr();
2635                let new_mro = mro_invoke(cls, vm)?;
2636                if cls.mro.read().as_ptr() != old_ptr {
2637                    // A nested custom mro() already replaced tp_mro.
2638                    return Ok(0);
2639                }
2640                let old_mro = core::mem::replace(&mut *cls.mro.write(), new_mro);
2641                undo.push((cls.to_owned(), old_mro));
2642                cls.modified_inner();
2643                let subclasses: Vec<PyTypeRef> = cls
2644                    .subclasses
2645                    .read()
2646                    .iter()
2647                    .filter_map(|subclass| subclass.upgrade())
2648                    .filter_map(|subclass| subclass.downcast::<PyType>().ok())
2649                    .collect();
2650                for subclass in subclasses {
2651                    update_mro_recursively(&subclass, undo, vm)?;
2652                }
2653                Ok(1)
2654            }
2655            let mut undo = Vec::new();
2656            if let Err(err) = update_mro_recursively(zelf, &mut undo, vm) {
2657                // Roll back to the previous state. A class reachable through
2658                // multiple bases is recorded once per visit, so restore in
2659                // reverse to end with the first-recorded (original) mro.
2660                for (cls, old_mro) in undo.into_iter().rev() {
2661                    let failed_mro = core::mem::replace(&mut *cls.mro.write(), old_mro);
2662                    retired.extend(failed_mro.into_iter().map(Into::into));
2663                    retired.push(cls.into());
2664                }
2665                // Take no action if tp_bases was replaced through reentrance.
2666                if core::ptr::eq(&**zelf.bases.read(), &*bases) {
2667                    let failed_bases = core::mem::replace(&mut *zelf.bases.write(), old_bases);
2668                    if let Some(failed_base) = unsafe { zelf.base.swap(old_base) } {
2669                        keep_alive(failed_base, &mut retired);
2670                    }
2671                    retired.push(failed_bases.into_untyped().into());
2672                    zelf.modified_inner();
2673                } else {
2674                    retired.push(old_bases.into_untyped().into());
2675                    if let Some(old_base) = old_base {
2676                        keep_alive(old_base, &mut retired);
2677                    }
2678                }
2679                return Err(err);
2680            }
2681            // Retire the replaced mros as well; dropping them here would
2682            // release them while the lock is held.
2683            for (cls, old_mro) in undo {
2684                retired.extend(old_mro.into_iter().map(Into::into));
2685                retired.push(cls.into());
2686            }
2687
2688            // Take no action if tp_bases was replaced through reentrance.
2689            if core::ptr::eq(&**zelf.bases.read(), &*bases) {
2690                remove_as_subclass(old_bases.as_slice(), &mut retired);
2691                add_as_subclass(bases.as_slice());
2692                zelf.update_all_slots(&vm.ctx);
2693            }
2694
2695            retired.push(old_bases.into_untyped().into());
2696            if let Some(old_base) = old_base {
2697                keep_alive(old_base, &mut retired);
2698            }
2699            crate::stdlib::_testinternalcapi::note_set_bases();
2700            Ok(())
2701        });
2702        drop(retired);
2703        result
2704    }
2705
2706    #[pygetset]
2707    fn __abstractmethods__(zelf: &Self, vm: &VirtualMachine) -> PyResult {
2708        if zelf.is(vm.ctx.types.type_type) {
2709            return Err(vm.new_attribute_error("__abstractmethods__"));
2710        }
2711        zelf.get_direct_attr(identifier!(vm, __abstractmethods__))
2712            .ok_or_else(|| vm.new_attribute_error("__abstractmethods__"))
2713    }
2714
2715    #[pygetset(setter)]
2716    fn set___abstractmethods__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
2717        let key = identifier!(vm, __abstractmethods__);
2718        let is_abstract = match &value {
2719            PySetterValue::Assign(val) => val.try_to_bool(vm)?,
2720            PySetterValue::Delete => false,
2721        };
2722        match value {
2723            PySetterValue::Assign(val) => {
2724                PyType::with_type_lock(vm, || {
2725                    self.modified_inner();
2726                    let _prev = self.attributes.insert(key, val);
2727                    self.set_is_abstract(is_abstract);
2728                });
2729            }
2730            PySetterValue::Delete => {
2731                let removed = PyType::with_type_lock(vm, || {
2732                    self.modified_inner();
2733                    let removed = self.attributes.remove(key);
2734                    if removed.is_some() {
2735                        self.set_is_abstract(false);
2736                    }
2737                    removed
2738                });
2739                if removed.is_none() {
2740                    return Err(vm.new_attribute_error("__abstractmethods__"));
2741                }
2742            }
2743        }
2744        Ok(())
2745    }
2746
2747    #[pygetset]
2748    pub fn __name__(&self, vm: &VirtualMachine) -> PyStrRef {
2749        self.name_inner(
2750            |name| {
2751                vm.ctx
2752                    .interned_str(name.rsplit_once('.').map_or(name, |(_, name)| name))
2753                    .unwrap_or_else(|| {
2754                        panic!(
2755                            "static type name must be already interned but {} is not",
2756                            self.slot_name()
2757                        )
2758                    })
2759                    .to_owned()
2760            },
2761            |ext| ext.name.read().clone().into_wtf8(),
2762        )
2763    }
2764
2765    #[pygetset]
2766    pub fn __qualname__(&self, vm: &VirtualMachine) -> PyObjectRef {
2767        self.payload.__qualname__(vm)
2768    }
2769
2770    #[pygetset(setter)]
2771    fn set___qualname__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
2772        self.check_set_special_type_attr(identifier!(vm, __qualname__), vm)?;
2773        let value = value.ok_or_else(|| {
2774            vm.new_type_error(format!(
2775                "cannot delete '__qualname__' attribute of immutable type '{}'",
2776                self.name()
2777            ))
2778        })?;
2779
2780        let str_value = downcast_qualname(value, vm)?;
2781
2782        let heap_type = self.heaptype_ext.as_ref().ok_or_else(|| {
2783            vm.new_type_error(format!(
2784                "cannot set '__qualname__' attribute of immutable type '{}'",
2785                self.name()
2786            ))
2787        })?;
2788
2789        // Use std::mem::replace to swap the new value in and get the old value out,
2790        // then drop the old value after releasing the lock
2791        let _old_qualname = {
2792            let mut qualname_guard = heap_type.qualname.write();
2793            core::mem::replace(&mut *qualname_guard, str_value)
2794        };
2795        // old_qualname is dropped here, outside the lock scope
2796
2797        Ok(())
2798    }
2799
2800    #[pygetset]
2801    fn __annotate__(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
2802        if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
2803            return Err(vm.new_attribute_error(format!(
2804                "type object '{}' has no attribute '__annotate__'",
2805                self.name()
2806            )));
2807        }
2808
2809        let annotate_key = identifier!(vm, __annotate__);
2810        let annotate_func_key = identifier!(vm, __annotate_func__);
2811        if let Some(annotate) = self.attributes.get(annotate_key) {
2812            return Ok(annotate);
2813        }
2814        if let Some(annotate) = self.attributes.get(annotate_func_key) {
2815            return Ok(annotate);
2816        }
2817
2818        let none = vm.ctx.none();
2819        let (result, _prev) = PyType::with_type_lock(vm, || {
2820            if let Some(annotate) = self.attributes.get(annotate_key) {
2821                return (annotate, None);
2822            }
2823            if let Some(annotate) = self.attributes.get(annotate_func_key) {
2824                return (annotate, None);
2825            }
2826            self.modified_inner();
2827            let prev = self.attributes.insert(annotate_func_key, none.clone());
2828            (none, prev)
2829        });
2830        Ok(result)
2831    }
2832
2833    #[pygetset(setter)]
2834    fn set___annotate__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
2835        let value = match value {
2836            PySetterValue::Delete => {
2837                return Err(vm.new_type_error("cannot delete __annotate__ attribute"));
2838            }
2839            PySetterValue::Assign(v) => v,
2840        };
2841
2842        if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
2843            return Err(vm.new_type_error(format!(
2844                "cannot set '__annotate__' attribute of immutable type '{}'",
2845                self.name()
2846            )));
2847        }
2848
2849        if !vm.is_none(&value) && !value.is_callable() {
2850            return Err(vm.new_type_error("__annotate__ must be callable or None"));
2851        }
2852
2853        let _prev_values = PyType::with_type_lock(vm, || {
2854            self.modified_inner();
2855            // Clear cached annotations only when setting to a new callable
2856            let removed = if !vm.is_none(&value) {
2857                self.attributes
2858                    .remove(identifier!(vm, __annotations_cache__))
2859            } else {
2860                None
2861            };
2862            let prev = self
2863                .attributes
2864                .insert(identifier!(vm, __annotate_func__), value);
2865            (removed, prev)
2866        });
2867
2868        Ok(())
2869    }
2870
2871    #[pygetset]
2872    fn __annotations__(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
2873        let annotations_key = identifier!(vm, __annotations__);
2874        let annotations_cache_key = identifier!(vm, __annotations_cache__);
2875        if let Some(annotations) = self.attributes.get(annotations_key) {
2876            // Ignore the __annotations__ descriptor stored on type itself.
2877            if !annotations.class().is(vm.ctx.types.getset_type) {
2878                if vm.is_none(&annotations)
2879                    || annotations.class().is(vm.ctx.types.dict_type)
2880                    || self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
2881                {
2882                    return Ok(annotations);
2883                }
2884                return Err(vm.new_attribute_error(format!(
2885                    "type object '{}' has no attribute '__annotations__'",
2886                    self.name()
2887                )));
2888            }
2889        }
2890        if let Some(annotations) = self.attributes.get(annotations_cache_key) {
2891            if vm.is_none(&annotations)
2892                || annotations.class().is(vm.ctx.types.dict_type)
2893                || self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
2894            {
2895                return Ok(annotations);
2896            }
2897            return Err(vm.new_attribute_error(format!(
2898                "type object '{}' has no attribute '__annotations__'",
2899                self.name()
2900            )));
2901        }
2902
2903        if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
2904            return Err(vm.new_attribute_error(format!(
2905                "type object '{}' has no attribute '__annotations__'",
2906                self.name()
2907            )));
2908        }
2909
2910        // Get __annotate__ and call it if callable
2911        let annotate = self.__annotate__(vm)?;
2912        let annotations = if annotate.is_callable() {
2913            // Call __annotate__(1) where 1 is FORMAT_VALUE
2914            let result = annotate.call((1i32,), vm)?;
2915            if !result.class().is(vm.ctx.types.dict_type) {
2916                return Err(vm.new_type_error(format!(
2917                    "__annotate__ returned non-dict of type '{}'",
2918                    result.class().name()
2919                )));
2920            }
2921            result
2922        } else {
2923            vm.ctx.new_dict().into()
2924        };
2925
2926        let (result, _prev) = PyType::with_type_lock(vm, || {
2927            if let Some(existing) = self.attributes.get(annotations_key)
2928                && !existing.class().is(vm.ctx.types.getset_type)
2929            {
2930                return (existing, None);
2931            }
2932            if let Some(existing) = self.attributes.get(annotations_cache_key) {
2933                return (existing, None);
2934            }
2935            self.modified_inner();
2936            let prev = self
2937                .attributes
2938                .insert(annotations_cache_key, annotations.clone());
2939            (annotations, prev)
2940        });
2941        Ok(result)
2942    }
2943
2944    #[pygetset(setter)]
2945    fn set___annotations__(
2946        &self,
2947        value: crate::function::PySetterValue<PyObjectRef>,
2948        vm: &VirtualMachine,
2949    ) -> PyResult<()> {
2950        if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
2951            return Err(vm.new_type_error(format!(
2952                "cannot set '__annotations__' attribute of immutable type '{}'",
2953                self.name()
2954            )));
2955        }
2956
2957        let _prev_values = PyType::with_type_lock(vm, || {
2958            self.modified_inner();
2959            let has_annotations = self.attributes.contains(identifier!(vm, __annotations__));
2960
2961            let mut prev = Vec::new();
2962            match value {
2963                crate::function::PySetterValue::Assign(value) => {
2964                    let key = if has_annotations {
2965                        identifier!(vm, __annotations__)
2966                    } else {
2967                        identifier!(vm, __annotations_cache__)
2968                    };
2969                    prev.extend(self.attributes.insert(key, value));
2970                    if has_annotations {
2971                        prev.extend(
2972                            self.attributes
2973                                .remove(identifier!(vm, __annotations_cache__)),
2974                        );
2975                    }
2976                }
2977                crate::function::PySetterValue::Delete => {
2978                    let removed = if has_annotations {
2979                        self.attributes.remove(identifier!(vm, __annotations__))
2980                    } else {
2981                        self.attributes
2982                            .remove(identifier!(vm, __annotations_cache__))
2983                    };
2984                    if removed.is_none() {
2985                        return Err(vm.new_attribute_error("__annotations__"));
2986                    }
2987                    prev.extend(removed);
2988                    if has_annotations {
2989                        prev.extend(
2990                            self.attributes
2991                                .remove(identifier!(vm, __annotations_cache__)),
2992                        );
2993                    }
2994                }
2995            }
2996            prev.extend(self.attributes.remove(identifier!(vm, __annotate_func__)));
2997            prev.extend(self.attributes.remove(identifier!(vm, __annotate__)));
2998            Ok(prev)
2999        })?;
3000
3001        Ok(())
3002    }
3003
3004    #[pygetset]
3005    pub fn __module__(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
3006        self.payload.__module__(vm)
3007    }
3008
3009    #[pygetset(setter)]
3010    fn set___module__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
3011        self.check_set_special_type_attr(identifier!(vm, __module__), vm)?;
3012        let _prev_values = PyType::with_type_lock(vm, || {
3013            self.modified_inner();
3014            let removed = self.attributes.remove(identifier!(vm, __firstlineno__));
3015            let prev = self.attributes.insert(identifier!(vm, __module__), value);
3016            (removed, prev)
3017        });
3018        Ok(())
3019    }
3020
3021    #[pyclassmethod]
3022    fn __prepare__(_cls: PyTypeRef, _args: PrepareArgs, vm: &VirtualMachine) -> PyDictRef {
3023        vm.ctx.new_dict()
3024    }
3025
3026    #[pymethod]
3027    fn __subclasses__(&self, vm: &VirtualMachine) -> PyList {
3028        let mut subclasses = self.subclasses.write();
3029        subclasses.retain(|x| x.upgrade().is_some());
3030        let interpreter_id = vm.state.interpreter_id;
3031        PyList::from(
3032            subclasses
3033                .iter()
3034                .filter_map(|x| x.upgrade())
3035                .filter(|obj| {
3036                    obj.downcast_ref::<PyType>()
3037                        .is_none_or(|typ| typ.is_visible_to_interpreter(interpreter_id))
3038                })
3039                .collect::<Vec<_>>(),
3040        )
3041    }
3042
3043    #[pygetset]
3044    fn __dict__(zelf: PyRef<PyType>) -> PyMappingProxy {
3045        PyMappingProxy::from(zelf)
3046    }
3047
3048    #[pygetset(setter)]
3049    fn set___dict__(&self, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
3050        Err(vm.new_attribute_error(format!(
3051            "attribute '__dict__' of '{}' objects is not writable",
3052            self.name()
3053        )))
3054    }
3055
3056    #[pygetset(setter)]
3057    fn set___name__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
3058        self.check_set_special_type_attr(identifier!(vm, __name__), vm)?;
3059        let name = value.downcast::<PyStr>().map_err(|value| {
3060            vm.new_type_error(format!(
3061                "can only assign string to {}.__name__, not '{}'",
3062                self.slot_name(),
3063                value.class().slot_name(),
3064            ))
3065        })?;
3066        if name.as_bytes().contains(&0) {
3067            return Err(vm.new_value_error("type name must not contain null characters"));
3068        }
3069        let name = name.try_into_utf8(vm)?;
3070
3071        let heap_type = self.heaptype_ext.as_ref().ok_or_else(|| {
3072            vm.new_type_error(format!(
3073                "cannot set '__name__' attribute of immutable type '{}'",
3074                self.slot_name()
3075            ))
3076        })?;
3077
3078        // Use std::mem::replace to swap the new value in and get the old value out,
3079        // then drop the old value after releasing the lock
3080        let _old_name = {
3081            let mut name_guard = heap_type.name.write();
3082            core::mem::replace(&mut *name_guard, name)
3083        };
3084        // old_name is dropped here, outside the lock scope
3085
3086        Ok(())
3087    }
3088
3089    #[pygetset]
3090    fn __text_signature__(&self, vm: &VirtualMachine) -> Option<String> {
3091        let name = self.name();
3092        if let Some(text) = self.slots.doc.text
3093            && let Some(signature) = get_text_signature_from_internal_doc(&name, text)
3094        {
3095            return Some(signature.to_string());
3096        }
3097        if self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
3098            && let Some(doc_attr) = self.get_direct_attr(identifier!(vm, __doc__))
3099            && let Some(doc) = doc_attr.downcast_ref::<PyStr>()
3100            && let Some(doc) = doc.to_str()
3101            && let Some(signature) = get_text_signature_from_internal_doc(&name, doc)
3102        {
3103            return Some(signature.to_string());
3104        }
3105        None
3106    }
3107
3108    #[pygetset]
3109    fn __type_params__(&self, vm: &VirtualMachine) -> PyObjectRef {
3110        let key = identifier!(vm, __type_params__);
3111        if let Some(params) = self.attributes.get(key)
3112            // Builtin type namespaces store the getset wrapper under this
3113            // name; heap types store the actual value.
3114            && !params.class().is(vm.ctx.types.getset_type)
3115        {
3116            return params;
3117        }
3118        vm.ctx.empty_tuple.clone().into()
3119    }
3120
3121    #[pygetset(setter)]
3122    fn set___type_params__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
3123        let key = identifier!(vm, __type_params__);
3124        match value {
3125            PySetterValue::Assign(val) => {
3126                self.check_set_special_type_attr(key, vm)?;
3127                let _prev_value = PyType::with_type_lock(vm, || {
3128                    self.modified_inner();
3129                    self.attributes.insert(key, val)
3130                });
3131            }
3132            PySetterValue::Delete => {
3133                return Err(vm.new_type_error("cannot delete '__type_params__'"));
3134            }
3135        }
3136        Ok(())
3137    }
3138}
3139
3140impl SetAttr for PyType {
3141    fn setattro(
3142        zelf: &Py<Self>,
3143        attr_name: &Py<PyStr>,
3144        value: PySetterValue,
3145        vm: &VirtualMachine,
3146    ) -> PyResult<()> {
3147        let attr_name = vm.ctx.intern_str(attr_name.as_wtf8());
3148        if zelf.slots().flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
3149            return Err(vm.new_type_error(format!(
3150                "cannot set '{}' attribute of immutable type '{}'",
3151                attr_name,
3152                zelf.slot_name()
3153            )));
3154        }
3155        if let Some(attr) = zelf.get_class_attr(attr_name) {
3156            let descr_set = attr.class().slots().descr_set.load();
3157            if let Some(descriptor) = descr_set {
3158                return descriptor(&attr, zelf.to_owned().into(), value, vm);
3159            }
3160        }
3161        let assign = value.is_assign();
3162
3163        // Drop old value OUTSIDE the type lock to avoid deadlock:
3164        // dropping may trigger weakref callbacks → method calls →
3165        // LOAD_ATTR specialization → version_for_specialization → type lock.
3166        let _prev_value = Self::with_type_lock(vm, || {
3167            // Invalidate inline caches before modifying attributes.
3168            // This ensures other threads see the version invalidation before
3169            // any attribute changes, preventing use-after-free of cached descriptors.
3170            zelf.modified_inner();
3171
3172            let prev_value = if let PySetterValue::Assign(value) = value {
3173                zelf.attributes().insert(attr_name, value)
3174            } else {
3175                let prev_value = zelf.attributes().remove(attr_name);
3176                if prev_value.is_none() {
3177                    return Err(vm.new_attribute_error(format!(
3178                        "type object '{}' has no attribute '{}'",
3179                        zelf.name(),
3180                        attr_name,
3181                    )));
3182                }
3183                prev_value
3184            };
3185
3186            // Keep the slot-table rewrite inside the same transaction as the
3187            // dict mutation and version invalidation.
3188            if attr_name.as_wtf8().starts_with("__") && attr_name.as_wtf8().ends_with("__") {
3189                if assign {
3190                    zelf.update_slot::<true>(attr_name, &vm.ctx);
3191                } else {
3192                    zelf.update_slot::<false>(attr_name, &vm.ctx);
3193                }
3194            }
3195            Ok(prev_value)
3196        })?;
3197        Ok(())
3198    }
3199}
3200
3201impl Callable for PyType {
3202    type Args = FuncArgs;
3203    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
3204        vm_trace!("type_call: {:?}", zelf);
3205
3206        if zelf.is(vm.ctx.types.type_type) {
3207            let num_args = args.args.len();
3208            if num_args == 1 && args.kwargs.is_empty() {
3209                return Ok(args.args[0].obj_type());
3210            }
3211            if num_args != 3 {
3212                return Err(vm.new_type_error("type() takes 1 or 3 arguments"));
3213            }
3214        }
3215
3216        let Some(slot_new) = zelf.slots().new.load() else {
3217            return Err(
3218                vm.new_type_error(format!("cannot create '{}' instances", zelf.slots().name))
3219            );
3220        };
3221
3222        // Both the new and init slots consume args, so the init call gets a
3223        // separate copy prepared before slot_new runs.
3224        let init_args = if args.is_empty() {
3225            // Even cloning empty args costs a kwargs map clone; a default
3226            // FuncArgs is indistinguishable from such a clone.
3227            FuncArgs::default()
3228        } else {
3229            // Skip the clone when no init call can follow: the class has no
3230            // init slot, is not `type` itself, and its new slot is a native
3231            // function. new_wrapper is excluded because a Python `__new__`
3232            // can install an `__init__` on the class or return an instance
3233            // of another class while it runs.
3234            // The address comparison is against the single new_wrapper fn item,
3235            // so a mismatch is conservative: if it ever compared unequal for the
3236            // wrapper it would only take the slower cloning path, never the fast
3237            // path incorrectly.
3238            if zelf.slots().init.load().is_none()
3239                && !zelf.is(vm.ctx.types.type_type)
3240                && crate::types::fn_addr(slot_new)
3241                    != crate::types::fn_addr(crate::types::new_wrapper as crate::types::NewFunc)
3242            {
3243                return slot_new(zelf.to_owned(), args, vm);
3244            }
3245            args.clone()
3246        };
3247
3248        let obj = slot_new(zelf.to_owned(), args, vm)?;
3249
3250        if !obj.class().fast_issubclass(zelf) {
3251            return Ok(obj);
3252        }
3253
3254        if let Some(init_method) = obj.class().slots().init.load() {
3255            init_method(&obj, init_args, vm)?;
3256        }
3257        Ok(obj)
3258    }
3259}
3260
3261impl AsNumber for PyType {
3262    fn as_number() -> &'static PyNumberMethods {
3263        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
3264            or: Some(|a, b, vm| or_(a.to_owned(), b.to_owned(), vm)),
3265            ..PyNumberMethods::NOT_IMPLEMENTED
3266        };
3267        &AS_NUMBER
3268    }
3269}
3270
3271impl Representable for PyType {
3272    #[inline]
3273    fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
3274        // A missing `__module__` is not an error here.
3275        let module = zelf.__module__(vm).ok();
3276        let module = module
3277            .as_ref()
3278            .and_then(|m| m.downcast_ref::<PyStr>())
3279            .map(|m| m.as_wtf8());
3280
3281        let repr = match module {
3282            Some(module) if module != "builtins" => {
3283                let qualname = zelf.__qualname__(vm);
3284                let qualname = qualname.downcast_ref::<PyStr>().map(|n| n.as_wtf8());
3285                let name = zelf.name();
3286                let qualname = qualname.unwrap_or_else(|| name.as_ref());
3287                format!("<class '{module}.{qualname}'>")
3288            }
3289            _ => format!("<class '{}'>", zelf.slot_name()),
3290        };
3291        Ok(repr)
3292    }
3293}
3294
3295// = get_builtin_base_with_dict
3296fn get_builtin_base_with_dict(typ: &Py<PyType>, vm: &VirtualMachine) -> Option<PyTypeRef> {
3297    let mut current = Some(typ.to_owned());
3298    while let Some(t) = current {
3299        // In CPython: type->tp_dictoffset != 0 && !(type->tp_flags & Py_TPFLAGS_HEAPTYPE)
3300        // Special case: type itself is a builtin with dict support
3301        if t.is(vm.ctx.types.type_type) {
3302            return Some(t);
3303        }
3304        // We check HAS_DICT flag (equivalent to tp_dictoffset != 0) and HEAPTYPE
3305        if t.slots.flags.has_feature(PyTypeFlags::HAS_DICT)
3306            && !t.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
3307        {
3308            return Some(t);
3309        }
3310        current = t.base.load_owned();
3311    }
3312    None
3313}
3314
3315// = get_dict_descriptor
3316fn get_dict_descriptor(base: &Py<PyType>, vm: &VirtualMachine) -> Option<PyObjectRef> {
3317    let dict_attr = identifier!(vm, __dict__);
3318    // Use _PyType_Lookup (which is lookup_ref in RustPython)
3319    base.lookup_ref(dict_attr, vm)
3320}
3321
3322// = raise_dict_descr_error
3323fn raise_dict_descriptor_error(obj: &PyObject, vm: &VirtualMachine) -> PyBaseExceptionRef {
3324    vm.new_type_error(format!(
3325        "this __dict__ descriptor does not support '{}' objects",
3326        obj.class().name()
3327    ))
3328}
3329
3330fn subtype_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
3331    let base = get_builtin_base_with_dict(obj.class(), vm);
3332
3333    if let Some(base_type) = base {
3334        if let Some(descr) = get_dict_descriptor(&base_type, vm) {
3335            // Call the descriptor's tp_descr_get
3336            vm.call_get_descriptor(&descr, &obj)
3337                .unwrap_or_else(|| Err(raise_dict_descriptor_error(&obj, vm)))
3338        } else {
3339            Err(raise_dict_descriptor_error(&obj, vm))
3340        }
3341    } else {
3342        // PyObject_GenericGetDict
3343        object::object_get_dict(obj, vm).map(Into::into)
3344    }
3345}
3346
3347// = subtype_setdict
3348fn subtype_set_dict(obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
3349    let base = get_builtin_base_with_dict(obj.class(), vm);
3350
3351    if let Some(base_type) = base {
3352        if let Some(descr) = get_dict_descriptor(&base_type, vm) {
3353            // Call the descriptor's tp_descr_set
3354            let descr_set = descr
3355                .class()
3356                .slots
3357                .descr_set
3358                .load()
3359                .ok_or_else(|| raise_dict_descriptor_error(&obj, vm))?;
3360            descr_set(&descr, obj, value, vm)
3361        } else {
3362            Err(raise_dict_descriptor_error(&obj, vm))
3363        }
3364    } else {
3365        // _PyObject_SetDict
3366        object::object_set_dict(obj, value, vm)?;
3367        Ok(())
3368    }
3369}
3370
3371// subtype_get_weakref
3372fn subtype_get_weakref(obj: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
3373    // Return the first weakref in the weakref list, or None
3374    let weakref = obj.get_weakrefs();
3375    weakref.unwrap_or_else(|| vm.ctx.none())
3376}
3377
3378// subtype_set_weakref: __weakref__ is read-only
3379fn subtype_set_weakref(obj: PyObjectRef, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
3380    Err(vm.new_attribute_error(format!(
3381        "attribute '__weakref__' of '{}' objects is not writable",
3382        obj.class().name()
3383    )))
3384}
3385
3386/*
3387 * The magical type type
3388 */
3389
3390/// Vectorcall for PyType (PEP 590).
3391/// Fast path: type(x) returns x.__class__ without constructing FuncArgs.
3392///
3393/// # Implementation note: `slots.vectorcall` dual use
3394///
3395/// CPython has three distinct fields on PyTypeObject:
3396///   - `tp_vectorcall`: constructor fast path (e.g. `list_vectorcall`)
3397///   - `tp_vectorcall_offset`: per-instance vectorcall for callables (e.g. functions)
3398///   - `tp_call`: standard call slot
3399///
3400/// RustPython collapses the first two into a single `slots.vectorcall`. The
3401/// `call.is_none()` guard below distinguishes the two uses: callable types have
3402/// `slots.call` set, so their `slots.vectorcall` is for calling instances,
3403/// not for construction.
3404///
3405/// This heuristic is correct for all current builtins but is not a general
3406/// solution — `type` itself is both callable and has a constructor vectorcall,
3407/// handled by the explicit `zelf.is(type_type)` check above the guard.
3408/// If more such types arise, consider splitting into a dedicated
3409/// `constructor_vectorcall` slot.
3410fn vectorcall_type(
3411    zelf_obj: &PyObject,
3412    args: Vec<PyObjectRef>,
3413    nargs: usize,
3414    kwnames: Option<&[PyObjectRef]>,
3415    vm: &VirtualMachine,
3416) -> PyResult {
3417    let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
3418
3419    // type(x) fast path: single positional arg, no kwargs
3420    if zelf.is(vm.ctx.types.type_type) {
3421        let no_kwargs = kwnames.is_none_or(|kw| kw.is_empty());
3422        if nargs == 1 && no_kwargs {
3423            return Ok(args[0].obj_type());
3424        }
3425    } else if zelf.slots().call.load().is_none() && zelf.slots().new.load().is_some() {
3426        // Per-type constructor vectorcall for non-callable types (dict, list, int, etc.)
3427        // Also guard on slots.new to avoid dispatching for DISALLOW_INSTANTIATION types.
3428        if let Some(type_vc) = zelf.slots().vectorcall.load() {
3429            return type_vc(zelf_obj, args, nargs, kwnames, vm);
3430        }
3431    }
3432
3433    // Fallback: construct FuncArgs and use standard call
3434    let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
3435    PyType::call(zelf, func_args, vm)
3436}
3437
3438pub(crate) fn init(ctx: &'static Context) {
3439    PyType::extend_class(ctx, ctx.types.type_type);
3440    ctx.types
3441        .type_type
3442        .slots
3443        .vectorcall
3444        .store(Some(vectorcall_type));
3445}
3446
3447pub(crate) fn call_slot_new(
3448    typ: &Py<PyType>,
3449    subtype: PyTypeRef,
3450    args: FuncArgs,
3451    vm: &VirtualMachine,
3452) -> PyResult {
3453    // Check DISALLOW_INSTANTIATION flag on subtype (the type being instantiated)
3454    if subtype
3455        .slots
3456        .flags
3457        .has_feature(PyTypeFlags::DISALLOW_INSTANTIATION)
3458    {
3459        return Err(vm.new_type_error(format!("cannot create '{}' instances", subtype.slot_name())));
3460    }
3461
3462    // "is not safe" check (tp_new_wrapper). Walk past bases whose tp_new is
3463    // new_wrapper so a Python subclass of a native heap type still reaches
3464    // that type's tp_new; reject object.__new__(dict) and similar.
3465    let mut staticbase = subtype.clone();
3466    while staticbase
3467        .slots
3468        .new
3469        .load()
3470        .is_some_and(|f| fn_addr(f) == fn_addr(new_wrapper as NewFunc))
3471    {
3472        match staticbase.base.load_owned() {
3473            Some(base) => staticbase = base,
3474            None => break,
3475        }
3476    }
3477
3478    let typ_new = typ.slots.new.load();
3479    let staticbase_new = staticbase.slots.new.load();
3480    if typ_new.map(fn_addr) != staticbase_new.map(fn_addr) {
3481        return Err(vm.new_type_error(format!(
3482            "{}.__new__({}) is not safe, use {}.__new__()",
3483            typ.slot_name(),
3484            subtype.slot_name(),
3485            staticbase.slot_name()
3486        )));
3487    }
3488
3489    let slot_new = typ
3490        .slots
3491        .new
3492        .load()
3493        .expect("Should be able to find a new slot somewhere in the mro");
3494    slot_new(subtype, args, vm)
3495}
3496
3497pub(crate) fn or_(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
3498    union_::or_op(zelf, other, vm)
3499}
3500
3501fn take_next_base(bases: &mut [Vec<PyTypeRef>]) -> Option<PyTypeRef> {
3502    for base in bases.iter() {
3503        let head = base[0].clone();
3504        if !bases.iter().any(|x| x[1..].iter().any(|x| x.is(&head))) {
3505            // Remove from other heads.
3506            for item in bases.iter_mut() {
3507                if item[0].is(&head) {
3508                    item.remove(0);
3509                }
3510            }
3511
3512            return Some(head);
3513        }
3514    }
3515
3516    None
3517}
3518
3519fn linearise_mro(mut bases: Vec<Vec<PyTypeRef>>) -> Result<Vec<PyTypeRef>, String> {
3520    vm_trace!("Linearise MRO: {:?}", bases);
3521    // Python requires that the class direct bases are kept in the same order.
3522    // This is called local precedence ordering.
3523    // This means we must verify that for classes A(), B(A) we must reject C(A, B) even though this
3524    // algorithm will allow the mro ordering of [C, B, A, object].
3525    // To verify this, we make sure non of the direct bases are in the mro of bases after them.
3526    for (i, base_mro) in bases.iter().enumerate() {
3527        let base = &base_mro[0]; // MROs cannot be empty.
3528        for later_mro in &bases[i + 1..] {
3529            // We start at index 1 to skip direct bases.
3530            // This will not catch duplicate bases, but such a thing is already tested for.
3531            if later_mro[1..].iter().any(|cls| cls.is(base)) {
3532                return Err(format!(
3533                    "Cannot create a consistent method resolution order (MRO) for bases {}",
3534                    bases.iter().map(|x| x.first().unwrap()).format(", ")
3535                ));
3536            }
3537        }
3538    }
3539
3540    let mut result = vec![];
3541    while !bases.is_empty() {
3542        let head = take_next_base(&mut bases).ok_or_else(|| {
3543            // Take the head class of each class here. Now that we have reached the problematic bases.
3544            // Because this failed, we assume the lists cannot be empty.
3545            format!(
3546                "Cannot create a consistent method resolution order (MRO) for bases {}",
3547                bases.iter().map(|x| x.first().unwrap()).format(", ")
3548            )
3549        })?;
3550
3551        result.push(head);
3552
3553        bases.retain(|x| !x.is_empty());
3554    }
3555    Ok(result)
3556}
3557
3558fn calculate_meta_class(
3559    metatype: PyTypeRef,
3560    bases: &[PyTypeRef],
3561    vm: &VirtualMachine,
3562) -> PyResult<PyTypeRef> {
3563    // = _PyType_CalculateMetaclass
3564    let mut winner = metatype;
3565    for base in bases {
3566        let base_type = base.class();
3567
3568        // First try fast_issubclass for PyType instances
3569        if winner.fast_issubclass(base_type) {
3570            continue;
3571        } else if base_type.fast_issubclass(&winner) {
3572            winner = base_type.to_owned();
3573            continue;
3574        }
3575
3576        // If fast_issubclass didn't work, fall back to general is_subclass
3577        // This handles cases where metaclasses are not PyType subclasses
3578        let winner_is_subclass = winner.as_object().is_subclass(base_type.as_object(), vm)?;
3579        if winner_is_subclass {
3580            continue;
3581        }
3582
3583        let base_type_is_subclass = base_type.as_object().is_subclass(winner.as_object(), vm)?;
3584        if base_type_is_subclass {
3585            winner = base_type.to_owned();
3586            continue;
3587        }
3588
3589        return Err(vm.new_type_error(
3590            "metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass \
3591             of the metaclasses of all its bases",
3592        ));
3593    }
3594    Ok(winner)
3595}
3596
3597fn type_is_subtype_base_chain(a: &Py<PyType>, b: &Py<PyType>, vm: &VirtualMachine) -> bool {
3598    let mut current = Some(a);
3599    while let Some(typ) = current {
3600        if typ.is(b) {
3601            return true;
3602        }
3603        current = typ.base.deref();
3604    }
3605    b.is(vm.ctx.types.object_type)
3606}
3607
3608fn mro_implementation(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<Vec<PyTypeRef>> {
3609    for base in typ.bases.read().as_slice() {
3610        if base.mro.read().is_empty() {
3611            return Err(vm.new_type_error(format!(
3612                "Cannot extend an incomplete type '{}'",
3613                base.name()
3614            )));
3615        }
3616    }
3617    let mut mro =
3618        PyType::resolve_mro(typ.bases.read().as_slice()).map_err(|msg| vm.new_type_error(msg))?;
3619    mro.insert(0, typ.to_owned());
3620    Ok(mro)
3621}
3622
3623fn mro_check(typ: &Py<PyType>, mro: &[PyObjectRef], vm: &VirtualMachine) -> PyResult<()> {
3624    let solid = solid_base(typ, vm).to_owned();
3625    for obj in mro {
3626        let Some(base) = obj.downcast_ref::<PyType>() else {
3627            return Err(vm.new_type_error(format!(
3628                "mro() returned a non-class ('{}')",
3629                obj.class().name()
3630            )));
3631        };
3632        if !is_subtype_with_mro(&solid.mro.read(), &solid, solid_base(base, vm)) {
3633            return Err(vm.new_type_error(format!(
3634                "mro() returned base with unsuitable layout ('{}')",
3635                base.slot_name()
3636            )));
3637        }
3638    }
3639    Ok(())
3640}
3641
3642fn mro_invoke(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<Vec<PyTypeRef>> {
3643    let custom = !typ.class().is(vm.ctx.types.type_type);
3644    if !custom {
3645        return mro_implementation(typ, vm);
3646    }
3647
3648    let mro_result = vm.call_special_method(typ.as_object(), identifier!(vm, mro), ())?;
3649    let items: Vec<PyObjectRef> = mro_result.try_to_value(vm)?;
3650    if items.is_empty() {
3651        return Err(vm.new_type_error("type MRO must not be empty"));
3652    }
3653    mro_check(typ, &items, vm)?;
3654    items
3655        .into_iter()
3656        .map(|obj| {
3657            obj.downcast::<PyType>().map_err(|obj| {
3658                vm.new_type_error(format!(
3659                    "mro() returned a non-class ('{}')",
3660                    obj.class().name()
3661                ))
3662            })
3663        })
3664        .collect()
3665}
3666
3667fn mro_internal(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<i32> {
3668    let old_ptr = typ.mro.read().as_ptr();
3669    let new_mro = mro_invoke(typ, vm)?;
3670    if typ.mro.read().as_ptr() != old_ptr {
3671        return Ok(0);
3672    }
3673    *typ.mro.write() = new_mro;
3674    typ.modified();
3675    Ok(1)
3676}
3677
3678/// Returns true if the two types have different instance layouts.
3679fn shape_differs(t1: &Py<PyType>, t2: &Py<PyType>) -> bool {
3680    t1.slots.basicsize != t2.slots.basicsize || t1.slots.itemsize != t2.slots.itemsize
3681}
3682
3683fn solid_base<'a>(typ: &'a Py<PyType>, vm: &VirtualMachine) -> &'a Py<PyType> {
3684    let base = if let Some(base) = typ.base.deref() {
3685        solid_base(base, vm)
3686    } else {
3687        vm.ctx.types.object_type
3688    };
3689
3690    if shape_differs(typ, base) { typ } else { base }
3691}
3692
3693fn best_base<'a>(bases: &'a [PyTypeRef], vm: &VirtualMachine) -> PyResult<&'a Py<PyType>> {
3694    let mut base: Option<&Py<PyType>> = None;
3695    let mut winner: Option<&Py<PyType>> = None;
3696
3697    for base_i in bases {
3698        // if !base_i.fast_issubclass(vm.ctx.types.type_type) {
3699        //     println!("base_i type : {}", base_i.name());
3700        //     return Err(vm.new_type_error("best must be types".into()));
3701        // }
3702
3703        if !base_i.slots.flags.has_feature(PyTypeFlags::BASETYPE) {
3704            return Err(vm.new_type_error(format!(
3705                "type '{}' is not an acceptable base type",
3706                base_i.slot_name()
3707            )));
3708        }
3709
3710        let candidate = solid_base(base_i, vm);
3711        if winner.is_none() {
3712            winner = Some(candidate);
3713            base = Some(&**base_i);
3714        } else if winner.unwrap().fast_issubclass(candidate) {
3715            // Do nothing
3716        } else if candidate.fast_issubclass(winner.unwrap()) {
3717            winner = Some(candidate);
3718            base = Some(&**base_i);
3719        } else {
3720            return Err(vm.new_type_error("multiple bases have instance layout conflict"));
3721        }
3722    }
3723
3724    debug_assert!(base.is_some());
3725    Ok(base.unwrap())
3726}
3727
3728fn type_has_dict(typ: &Py<PyType>) -> bool {
3729    typ.slots.flags.has_feature(PyTypeFlags::HAS_DICT)
3730}
3731
3732fn type_has_weakref(typ: &Py<PyType>) -> bool {
3733    typ.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF)
3734}
3735
3736/// Returns true if `child` adds no instance layout of its own beyond its base,
3737/// so the base can stand in for it when comparing object layouts.
3738fn compatible_with_base(child: &Py<PyType>) -> bool {
3739    let Some(parent) = child.base.deref() else {
3740        return false;
3741    };
3742    child.slots.basicsize == parent.slots.basicsize
3743        && child.slots.itemsize == parent.slots.itemsize
3744        && child.slots.member_count == parent.slots.member_count
3745        && type_has_dict(child) == type_has_dict(parent)
3746        && type_has_weakref(child) == type_has_weakref(parent)
3747}
3748
3749/// Walk up to the most derived base that actually fixes the instance layout.
3750fn layout_solid_base(mut typ: &Py<PyType>) -> &Py<PyType> {
3751    while compatible_with_base(typ) {
3752        typ = typ.base.deref().unwrap();
3753    }
3754    typ
3755}
3756
3757/// Returns true if `a` and `b`, which share the same base, added the same
3758/// instance layout (`__dict__`, `__weakref__`, and `__slots__`).
3759fn same_slots_added(a: &Py<PyType>, b: &Py<PyType>) -> bool {
3760    if a.slots.basicsize != b.slots.basicsize
3761        || a.slots.itemsize != b.slots.itemsize
3762        || a.slots.member_count != b.slots.member_count
3763        || type_has_dict(a) != type_has_dict(b)
3764        || type_has_weakref(a) != type_has_weakref(b)
3765    {
3766        return false;
3767    }
3768    match (
3769        a.heaptype_ext.as_ref().and_then(|e| e.slots.as_ref()),
3770        b.heaptype_ext.as_ref().and_then(|e| e.slots.as_ref()),
3771    ) {
3772        (Some(x), Some(y)) => {
3773            x.as_slice().len() == y.as_slice().len()
3774                && x.as_slice()
3775                    .iter()
3776                    .zip(y.as_slice().iter())
3777                    .all(|(p, q)| p.as_wtf8() == q.as_wtf8())
3778        }
3779        _ => true,
3780    }
3781}
3782
3783/// Validates that instances of `old_to` and `new_to` share an interchangeable
3784/// object layout, the check `__class__` and `__bases__` assignment perform.
3785///
3786/// `attr` names the attribute being assigned for the error message; the
3787/// message reports `new_to` first and `old_to` second.
3788pub(crate) fn compatible_for_assignment(
3789    old_to: &Py<PyType>,
3790    new_to: &Py<PyType>,
3791    attr: &str,
3792    vm: &VirtualMachine,
3793) -> PyResult<()> {
3794    let newbase = layout_solid_base(new_to);
3795    let oldbase = layout_solid_base(old_to);
3796    let bases_equal = match (newbase.base.deref(), oldbase.base.deref()) {
3797        (Some(x), Some(y)) => x.is(y),
3798        (None, None) => true,
3799        _ => false,
3800    };
3801    let compatible = newbase.is(oldbase) || (bases_equal && same_slots_added(newbase, oldbase));
3802    if compatible {
3803        return Ok(());
3804    }
3805    Err(vm.new_type_error(format!(
3806        "{attr} assignment: '{}' object layout differs from '{}'",
3807        new_to.name(),
3808        old_to.name()
3809    )))
3810}
3811
3812/// Apply Python name mangling for private attributes.
3813/// `__x` becomes `_ClassName__x` if inside a class.
3814fn mangle_name(class_name: &str, name: &str) -> String {
3815    // Only mangle names starting with __ and not ending with __
3816    if !name.starts_with("__") || name.ends_with("__") || name.contains('.') {
3817        return name.to_string();
3818    }
3819    // Strip leading underscores from class name
3820    let class_name = class_name.trim_start_matches('_');
3821    format!("_{class_name}{name}")
3822}
3823
3824#[cfg(test)]
3825mod tests {
3826    use super::*;
3827
3828    fn map_ids(obj: Result<Vec<PyTypeRef>, String>) -> Result<Vec<usize>, String> {
3829        Ok(obj?.into_iter().map(|x| x.get_id()).collect())
3830    }
3831
3832    #[test]
3833    fn linearise() {
3834        let context = Context::genesis();
3835        let object = context.types.object_type.to_owned();
3836        let type_type = context.types.type_type.to_owned();
3837
3838        let a = PyType::new_heap(
3839            "A",
3840            vec![object.clone()],
3841            PyAttributes::default(),
3842            Default::default(),
3843            type_type.clone(),
3844            context,
3845        )
3846        .unwrap();
3847        let b = PyType::new_heap(
3848            "B",
3849            vec![object.clone()],
3850            PyAttributes::default(),
3851            Default::default(),
3852            type_type,
3853            context,
3854        )
3855        .unwrap();
3856
3857        assert_eq!(
3858            map_ids(linearise_mro(vec![
3859                vec![object.clone()],
3860                vec![object.clone()]
3861            ])),
3862            map_ids(Ok(vec![object.clone()]))
3863        );
3864        assert_eq!(
3865            map_ids(linearise_mro(vec![
3866                vec![a.clone(), object.clone()],
3867                vec![b.clone(), object.clone()],
3868            ])),
3869            map_ids(Ok(vec![a, b, object]))
3870        );
3871    }
3872}