Skip to main content

rustpython_vm/builtins/
type.rs

1use super::{
2    PyClassMethod, PyDictRef, PyList, PyStaticMethod, PyStr, PyStrInterned, PyStrRef, PyTupleRef,
3    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            MemberGetter, MemberKind, MemberSetter, PyDescriptorOwned, PyMemberDef,
12            PyMemberDescriptor,
13        },
14        function::{PyCellRef, PyFunction},
15        tuple::{IntoPyTuple, PyTuple},
16    },
17    class::{PyClassImpl, StaticType},
18    common::{
19        ascii,
20        borrow::BorrowedValue,
21        lock::{PyMutex, PyRwLock, PyRwLockReadGuard},
22    },
23    function::{FuncArgs, KwArgs, OptionalArg, PyMethodDef, PySetterValue},
24    object::{Traverse, TraverseFn},
25    protocol::{PyIterReturn, PyNumberMethods},
26    types::{
27        AsNumber, Callable, Constructor, GetAttr, Initializer, PyTypeFlags, PyTypeSlots,
28        Representable, SLOT_DEFS, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot,
29    },
30};
31use core::{
32    any::Any,
33    borrow::Borrow,
34    ops::Deref,
35    pin::Pin,
36    ptr::NonNull,
37    sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering},
38};
39use indexmap::{IndexMap, map::Entry};
40use itertools::Itertools;
41use num_traits::ToPrimitive;
42use rustpython_common::wtf8::Wtf8;
43use std::collections::HashSet;
44
45#[pyclass(module = false, name = "type", traverse = "manual")]
46pub struct PyType {
47    pub base: Option<PyTypeRef>,
48    pub bases: PyRwLock<Vec<PyTypeRef>>,
49    pub mro: PyRwLock<Vec<PyTypeRef>>,
50    pub subclasses: PyRwLock<Vec<PyRef<PyWeak>>>,
51    pub attributes: PyRwLock<PyAttributes>,
52    pub slots: PyTypeSlots,
53    pub heaptype_ext: Option<Pin<Box<HeapTypeExt>>>,
54    /// Type version tag for inline caching. 0 means unassigned/invalidated.
55    pub tp_version_tag: AtomicU32,
56}
57
58/// Monotonic counter for type version tags. Once it reaches `u32::MAX`,
59/// `assign_version_tag()` returns 0 permanently, disabling new inline-cache
60/// entries but not invalidating correctness (cache misses fall back to the
61/// generic path).
62static NEXT_TYPE_VERSION: AtomicU32 = AtomicU32::new(1);
63
64// Method cache (type_cache / MCACHE): direct-mapped cache keyed by
65// (tp_version_tag, interned_name_ptr).
66//
67// Uses a lock-free SeqLock pattern for the read/write protocol:
68//  - Readers validate sequence/version/name before and after the value read.
69//  - Writers bracket updates with sequence odd/even transitions.
70// No mutex needed on the hot path (cache hit).
71
72const TYPE_CACHE_SIZE_EXP: u32 = 12;
73const TYPE_CACHE_SIZE: usize = 1 << TYPE_CACHE_SIZE_EXP;
74const TYPE_CACHE_MASK: usize = TYPE_CACHE_SIZE - 1;
75
76struct TypeCacheEntry {
77    /// Sequence lock (odd = write in progress, even = quiescent).
78    sequence: AtomicU32,
79    /// tp_version_tag at cache time. 0 = empty/invalid.
80    version: AtomicU32,
81    /// Interned attribute name pointer (pointer equality check).
82    name: AtomicPtr<PyStrInterned>,
83    /// Cached lookup result as raw pointer. null = empty.
84    /// The cache holds a **borrowed** pointer (no refcount increment).
85    /// Safety: `type_cache_clear()` nullifies all entries during GC,
86    /// and `type_cache_clear_version()` nullifies entries when a type
87    /// is modified — both before the source dict entry is removed.
88    /// Types are always part of reference cycles (via `mro` self-reference)
89    /// so they are always collected by the cyclic GC (never refcount-freed).
90    value: AtomicPtr<PyObject>,
91}
92
93// SAFETY: TypeCacheEntry is thread-safe:
94// - All fields use atomic operations
95// - Value pointer is valid as long as version matches (SeqLock pattern)
96// - PyObjectRef uses atomic reference counting
97unsafe impl Send for TypeCacheEntry {}
98unsafe impl Sync for TypeCacheEntry {}
99
100impl TypeCacheEntry {
101    fn new() -> Self {
102        Self {
103            sequence: AtomicU32::new(0),
104            version: AtomicU32::new(0),
105            name: AtomicPtr::new(core::ptr::null_mut()),
106            value: AtomicPtr::new(core::ptr::null_mut()),
107        }
108    }
109
110    #[inline]
111    fn begin_write(&self) {
112        let mut seq = self.sequence.load(Ordering::Acquire);
113        loop {
114            while (seq & 1) != 0 {
115                core::hint::spin_loop();
116                seq = self.sequence.load(Ordering::Acquire);
117            }
118            match self.sequence.compare_exchange_weak(
119                seq,
120                seq.wrapping_add(1),
121                Ordering::AcqRel,
122                Ordering::Acquire,
123            ) {
124                Ok(_) => {
125                    core::sync::atomic::fence(Ordering::Release);
126                    break;
127                }
128                Err(observed) => {
129                    core::hint::spin_loop();
130                    seq = observed;
131                }
132            }
133        }
134    }
135
136    #[inline]
137    fn end_write(&self) {
138        self.sequence.fetch_add(1, Ordering::Release);
139    }
140
141    #[inline]
142    fn begin_read(&self) -> u32 {
143        let mut sequence = self.sequence.load(Ordering::Acquire);
144        while (sequence & 1) != 0 {
145            core::hint::spin_loop();
146            sequence = self.sequence.load(Ordering::Acquire);
147        }
148        sequence
149    }
150
151    #[inline]
152    fn end_read(&self, previous: u32) -> bool {
153        core::sync::atomic::fence(Ordering::Acquire);
154        self.sequence.load(Ordering::Relaxed) == previous
155    }
156
157    /// Null out the cached value pointer.
158    /// Caller must ensure no concurrent reads can observe this entry
159    /// (version should be set to 0 first).
160    fn clear_value(&self) {
161        self.value.store(core::ptr::null_mut(), Ordering::Relaxed);
162    }
163}
164
165// std::sync::LazyLock is used here (not crate::common::lock::LazyLock)
166// because TYPE_CACHE is a global shared across test threads. The common
167// LazyLock delegates to LazyCell in non-threading mode, which is !Sync.
168static TYPE_CACHE: std::sync::LazyLock<Box<[TypeCacheEntry]>> = std::sync::LazyLock::new(|| {
169    (0..TYPE_CACHE_SIZE)
170        .map(|_| TypeCacheEntry::new())
171        .collect::<Vec<_>>()
172        .into_boxed_slice()
173});
174
175/// When true, find_name_in_mro skips populating the cache.
176/// Set during GC's type_cache_clear to prevent re-population from drops.
177static TYPE_CACHE_CLEARING: AtomicBool = AtomicBool::new(false);
178
179/// MCACHE_HASH: XOR of version and name pointer hash, masked to cache size.
180#[inline]
181fn type_cache_hash(version: u32, name: &'static PyStrInterned) -> usize {
182    let name_hash = (name as *const PyStrInterned as usize >> 3) as u32;
183    ((version ^ name_hash) as usize) & TYPE_CACHE_MASK
184}
185
186/// Invalidate cache entries for a specific version tag.
187/// Called from modified() when a type is changed.
188fn type_cache_clear_version(version: u32) {
189    for entry in TYPE_CACHE.iter() {
190        if entry.version.load(Ordering::Relaxed) == version {
191            entry.begin_write();
192            if entry.version.load(Ordering::Relaxed) == version {
193                entry.version.store(0, Ordering::Release);
194                entry.clear_value();
195            }
196            entry.end_write();
197        }
198    }
199}
200
201/// Clear all method cache entries (_PyType_ClearCache).
202/// Called during GC collection to nullify borrowed pointers before
203/// the collector breaks cycles.
204///
205/// Sets TYPE_CACHE_CLEARING to suppress cache re-population during the
206/// entire operation, preventing concurrent lookups from repopulating
207/// entries while we're clearing them.
208pub fn type_cache_clear() {
209    TYPE_CACHE_CLEARING.store(true, Ordering::Release);
210    for entry in TYPE_CACHE.iter() {
211        entry.begin_write();
212        entry.version.store(0, Ordering::Release);
213        entry.clear_value();
214        entry.end_write();
215    }
216    TYPE_CACHE_CLEARING.store(false, Ordering::Release);
217}
218
219unsafe impl crate::object::Traverse for PyType {
220    fn traverse(&self, tracer_fn: &mut crate::object::TraverseFn<'_>) {
221        self.base.traverse(tracer_fn);
222        self.bases.traverse(tracer_fn);
223        self.mro.traverse(tracer_fn);
224        self.subclasses.traverse(tracer_fn);
225        self.attributes
226            .read_recursive()
227            .iter()
228            .map(|(_, v)| v.traverse(tracer_fn))
229            .count();
230        if let Some(ext) = self.heaptype_ext.as_ref() {
231            ext.specialization_cache.traverse(tracer_fn);
232        }
233    }
234
235    /// type_clear: break reference cycles in type objects
236    fn clear(&mut self, out: &mut Vec<crate::PyObjectRef>) {
237        if let Some(base) = self.base.take() {
238            out.push(base.into());
239        }
240        if let Some(mut guard) = self.bases.try_write() {
241            for base in guard.drain(..) {
242                out.push(base.into());
243            }
244        }
245        if let Some(mut guard) = self.mro.try_write() {
246            for typ in guard.drain(..) {
247                out.push(typ.into());
248            }
249        }
250        if let Some(mut guard) = self.subclasses.try_write() {
251            for weak in guard.drain(..) {
252                out.push(weak.into());
253            }
254        }
255        if let Some(mut guard) = self.attributes.try_write() {
256            for (_, val) in guard.drain(..) {
257                out.push(val);
258            }
259        }
260        if let Some(ext) = self.heaptype_ext.as_ref() {
261            ext.specialization_cache.clear_into(out);
262        }
263    }
264}
265
266// PyHeapTypeObject in CPython
267pub struct HeapTypeExt {
268    pub name: PyRwLock<PyUtf8StrRef>,
269    pub qualname: PyRwLock<PyStrRef>,
270    pub slots: Option<PyRef<PyTuple<PyStrRef>>>,
271    pub type_data: PyRwLock<Option<TypeDataSlot>>,
272    pub specialization_cache: TypeSpecializationCache,
273}
274
275pub struct TypeSpecializationCache {
276    pub init: PyAtomicRef<Option<PyFunction>>,
277    pub getitem: PyAtomicRef<Option<PyFunction>>,
278    pub getitem_version: AtomicU32,
279    // Serialize cache writes/invalidation similar to CPython's BEGIN_TYPE_LOCK.
280    write_lock: PyMutex<()>,
281    retired: PyRwLock<Vec<PyObjectRef>>,
282}
283
284impl TypeSpecializationCache {
285    fn new() -> Self {
286        Self {
287            init: PyAtomicRef::from(None::<PyRef<PyFunction>>),
288            getitem: PyAtomicRef::from(None::<PyRef<PyFunction>>),
289            getitem_version: AtomicU32::new(0),
290            write_lock: PyMutex::new(()),
291            retired: PyRwLock::new(Vec::new()),
292        }
293    }
294
295    #[inline]
296    fn retire_old_function(&self, old: Option<PyRef<PyFunction>>) {
297        if let Some(old) = old {
298            self.retired.write().push(old.into());
299        }
300    }
301
302    #[inline]
303    fn swap_init(&self, new_init: Option<PyRef<PyFunction>>, vm: Option<&VirtualMachine>) {
304        if let Some(vm) = vm {
305            // Keep replaced refs alive for the currently executing frame, matching
306            // CPython-style "old pointer remains valid during ongoing execution"
307            // without accumulating global retired refs.
308            self.init.swap_to_temporary_refs(new_init, vm);
309            return;
310        }
311        // SAFETY: old value is moved to `retired`, so it stays alive while
312        // concurrent readers may still hold borrowed references.
313        let old = unsafe { self.init.swap(new_init) };
314        self.retire_old_function(old);
315    }
316
317    #[inline]
318    fn swap_getitem(&self, new_getitem: Option<PyRef<PyFunction>>, vm: Option<&VirtualMachine>) {
319        if let Some(vm) = vm {
320            self.getitem.swap_to_temporary_refs(new_getitem, vm);
321            return;
322        }
323        // SAFETY: old value is moved to `retired`, so it stays alive while
324        // concurrent readers may still hold borrowed references.
325        let old = unsafe { self.getitem.swap(new_getitem) };
326        self.retire_old_function(old);
327    }
328
329    #[inline]
330    fn invalidate_for_type_modified(&self) {
331        let _guard = self.write_lock.lock();
332        // _spec_cache contract: type modification invalidates all cached
333        // specialization functions.
334        self.swap_init(None, None);
335        self.swap_getitem(None, None);
336    }
337
338    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
339        if let Some(init) = self.init.deref() {
340            tracer_fn(init.as_object());
341        }
342        if let Some(getitem) = self.getitem.deref() {
343            tracer_fn(getitem.as_object());
344        }
345        self.retired
346            .read()
347            .iter()
348            .map(|obj| obj.traverse(tracer_fn))
349            .count();
350    }
351
352    fn clear_into(&self, out: &mut Vec<PyObjectRef>) {
353        let _guard = self.write_lock.lock();
354        let old_init = unsafe { self.init.swap(None) };
355        if let Some(old_init) = old_init {
356            out.push(old_init.into());
357        }
358        let old_getitem = unsafe { self.getitem.swap(None) };
359        if let Some(old_getitem) = old_getitem {
360            out.push(old_getitem.into());
361        }
362        self.getitem_version.store(0, Ordering::Release);
363        out.extend(self.retired.write().drain(..));
364    }
365}
366
367pub struct PointerSlot<T>(NonNull<T>);
368
369unsafe impl<T> Sync for PointerSlot<T> {}
370unsafe impl<T> Send for PointerSlot<T> {}
371
372impl<T> PointerSlot<T> {
373    pub const unsafe fn borrow_static(&self) -> &'static T {
374        unsafe { self.0.as_ref() }
375    }
376}
377
378impl<T> Clone for PointerSlot<T> {
379    fn clone(&self) -> Self {
380        *self
381    }
382}
383
384impl<T> Copy for PointerSlot<T> {}
385
386impl<T> From<&'static T> for PointerSlot<T> {
387    fn from(x: &'static T) -> Self {
388        Self(NonNull::from(x))
389    }
390}
391
392impl<T> AsRef<T> for PointerSlot<T> {
393    fn as_ref(&self) -> &T {
394        unsafe { self.0.as_ref() }
395    }
396}
397
398pub type PyTypeRef = PyRef<PyType>;
399
400cfg_if::cfg_if! {
401    if #[cfg(feature = "threading")] {
402        unsafe impl Send for PyType {}
403        unsafe impl Sync for PyType {}
404    }
405}
406
407/// For attributes we do not use a dict, but an IndexMap, which is an Hash Table
408/// that maintains order and is compatible with the standard HashMap  This is probably
409/// faster and only supports strings as keys.
410pub type PyAttributes = IndexMap<&'static PyStrInterned, PyObjectRef, ahash::RandomState>;
411
412unsafe impl Traverse for PyAttributes {
413    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
414        self.values().for_each(|v| v.traverse(tracer_fn));
415    }
416}
417
418impl core::fmt::Display for PyType {
419    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
420        core::fmt::Display::fmt(&self.name(), f)
421    }
422}
423
424impl core::fmt::Debug for PyType {
425    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
426        write!(f, "[PyType {}]", &self.name())
427    }
428}
429
430impl PyPayload for PyType {
431    #[inline]
432    fn class(ctx: &Context) -> &'static Py<PyType> {
433        ctx.types.type_type
434    }
435}
436
437fn downcast_qualname(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
438    match value.downcast::<PyStr>() {
439        Ok(value) => Ok(value),
440        Err(value) => Err(vm.new_type_error(format!(
441            "can only assign string to __qualname__, not '{}'",
442            value.class().name()
443        ))),
444    }
445}
446
447fn is_subtype_with_mro(a_mro: &[PyTypeRef], a: &Py<PyType>, b: &Py<PyType>) -> bool {
448    if a.is(b) {
449        return true;
450    }
451    for item in a_mro {
452        if item.is(b) {
453            return true;
454        }
455    }
456    false
457}
458
459impl PyType {
460    /// Assign a fresh version tag. Returns 0 if the version counter has been
461    /// exhausted, in which case no new cache entries can be created.
462    pub fn assign_version_tag(&self) -> u32 {
463        let v = self.tp_version_tag.load(Ordering::Acquire);
464        if v != 0 {
465            return v;
466        }
467
468        // Assign versions to all direct bases first (MRO invariant).
469        for base in self.bases.read().iter() {
470            if base.assign_version_tag() == 0 {
471                return 0;
472            }
473        }
474
475        loop {
476            let current = NEXT_TYPE_VERSION.load(Ordering::Relaxed);
477            let Some(next) = current.checked_add(1) else {
478                return 0; // Overflow: version space exhausted
479            };
480            if NEXT_TYPE_VERSION
481                .compare_exchange_weak(current, next, Ordering::Relaxed, Ordering::Relaxed)
482                .is_ok()
483            {
484                self.tp_version_tag.store(current, Ordering::Release);
485                return current;
486            }
487        }
488    }
489
490    /// Invalidate this type's version tag and cascade to all subclasses.
491    pub fn modified(&self) {
492        if let Some(ext) = self.heaptype_ext.as_ref() {
493            ext.specialization_cache.invalidate_for_type_modified();
494        }
495        // If already invalidated, all subclasses must also be invalidated
496        // (guaranteed by the MRO invariant in assign_version_tag).
497        let old_version = self.tp_version_tag.load(Ordering::Acquire);
498        if old_version == 0 {
499            return;
500        }
501        self.tp_version_tag.store(0, Ordering::SeqCst);
502        // Nullify borrowed pointers in cache entries for this version
503        // so they don't dangle after the dict is modified.
504        type_cache_clear_version(old_version);
505        let subclasses = self.subclasses.read();
506        for weak_ref in subclasses.iter() {
507            if let Some(sub) = weak_ref.upgrade() {
508                sub.downcast_ref::<PyType>().unwrap().modified();
509            }
510        }
511    }
512
513    pub fn new_simple_heap(
514        name: &str,
515        base: &Py<PyType>,
516        ctx: &Context,
517    ) -> Result<PyRef<Self>, String> {
518        Self::new_heap(
519            name,
520            vec![base.to_owned()],
521            Default::default(),
522            Default::default(),
523            Self::static_type().to_owned(),
524            ctx,
525        )
526    }
527    pub fn new_heap(
528        name: &str,
529        bases: Vec<PyRef<Self>>,
530        attrs: PyAttributes,
531        mut slots: PyTypeSlots,
532        metaclass: PyRef<Self>,
533        ctx: &Context,
534    ) -> Result<PyRef<Self>, String> {
535        // TODO: ensure clean slot name
536        // assert_eq!(slots.name.borrow(), "");
537
538        // Set HEAPTYPE flag for heap-allocated types
539        slots.flags |= PyTypeFlags::HEAPTYPE;
540
541        let name_utf8 = ctx.new_utf8_str(name);
542        let name = name_utf8.clone().into_wtf8();
543        let heaptype_ext = HeapTypeExt {
544            name: PyRwLock::new(name_utf8),
545            qualname: PyRwLock::new(name),
546            slots: None,
547            type_data: PyRwLock::new(None),
548            specialization_cache: TypeSpecializationCache::new(),
549        };
550        let base = bases[0].clone();
551
552        Self::new_heap_inner(base, bases, attrs, slots, heaptype_ext, metaclass, ctx)
553    }
554
555    /// Equivalent to CPython's PyType_Check macro
556    /// Checks if obj is an instance of type (or its subclass)
557    pub(crate) fn check(obj: &PyObject) -> Option<&Py<Self>> {
558        obj.downcast_ref::<Self>()
559    }
560
561    fn resolve_mro(bases: &[PyRef<Self>]) -> Result<Vec<PyTypeRef>, String> {
562        // Check for duplicates in bases.
563        let mut unique_bases = HashSet::new();
564        for base in bases {
565            if !unique_bases.insert(base.get_id()) {
566                return Err(format!("duplicate base class {}", base.name()));
567            }
568        }
569
570        let mros = bases
571            .iter()
572            .map(|base| base.mro_map_collect(|t| t.to_owned()))
573            .collect();
574        linearise_mro(mros)
575    }
576
577    /// Inherit SEQUENCE and MAPPING flags from base classes
578    /// Check all bases in order and inherit the first SEQUENCE or MAPPING flag found
579    fn inherit_patma_flags(slots: &mut PyTypeSlots, bases: &[PyRef<Self>]) {
580        const COLLECTION_FLAGS: PyTypeFlags = PyTypeFlags::from_bits_truncate(
581            PyTypeFlags::SEQUENCE.bits() | PyTypeFlags::MAPPING.bits(),
582        );
583
584        // If flags are already set, don't override
585        if slots.flags.intersects(COLLECTION_FLAGS) {
586            return;
587        }
588
589        // Check each base in order and inherit the first collection flag found
590        for base in bases {
591            let base_flags = base.slots.flags & COLLECTION_FLAGS;
592            if !base_flags.is_empty() {
593                slots.flags |= base_flags;
594                return;
595            }
596        }
597    }
598
599    /// Check for __abc_tpflags__ and set the appropriate flags
600    /// This checks in attrs and all base classes for __abc_tpflags__
601    fn check_abc_tpflags(
602        slots: &mut PyTypeSlots,
603        attrs: &PyAttributes,
604        bases: &[PyRef<Self>],
605        ctx: &Context,
606    ) {
607        const COLLECTION_FLAGS: PyTypeFlags = PyTypeFlags::from_bits_truncate(
608            PyTypeFlags::SEQUENCE.bits() | PyTypeFlags::MAPPING.bits(),
609        );
610
611        // Don't override if flags are already set
612        if slots.flags.intersects(COLLECTION_FLAGS) {
613            return;
614        }
615
616        // First check in our own attributes
617        let abc_tpflags_name = ctx.intern_str("__abc_tpflags__");
618        if let Some(abc_tpflags_obj) = attrs.get(abc_tpflags_name)
619            && let Some(int_obj) = abc_tpflags_obj.downcast_ref::<crate::builtins::int::PyInt>()
620        {
621            let flags_val = int_obj.as_bigint().to_i64().unwrap_or(0);
622            let abc_flags = PyTypeFlags::from_bits_truncate(flags_val as u64);
623            slots.flags |= abc_flags & COLLECTION_FLAGS;
624            return;
625        }
626
627        // Then check in base classes
628        for base in bases {
629            if let Some(abc_tpflags_obj) = base.find_name_in_mro(abc_tpflags_name)
630                && let Some(int_obj) = abc_tpflags_obj.downcast_ref::<crate::builtins::int::PyInt>()
631            {
632                let flags_val = int_obj.as_bigint().to_i64().unwrap_or(0);
633                let abc_flags = PyTypeFlags::from_bits_truncate(flags_val as u64);
634                slots.flags |= abc_flags & COLLECTION_FLAGS;
635                return;
636            }
637        }
638    }
639
640    #[allow(clippy::too_many_arguments)]
641    fn new_heap_inner(
642        base: PyRef<Self>,
643        bases: Vec<PyRef<Self>>,
644        attrs: PyAttributes,
645        mut slots: PyTypeSlots,
646        heaptype_ext: HeapTypeExt,
647        metaclass: PyRef<Self>,
648        ctx: &Context,
649    ) -> Result<PyRef<Self>, String> {
650        let mro = Self::resolve_mro(&bases)?;
651
652        // Inherit HAS_DICT from any base in MRO that has it
653        // (not just the first base, as any base with __dict__ means subclass needs it too)
654        if mro
655            .iter()
656            .any(|b| b.slots.flags.has_feature(PyTypeFlags::HAS_DICT))
657        {
658            slots.flags |= PyTypeFlags::HAS_DICT
659        }
660
661        // Inherit HAS_WEAKREF/MANAGED_WEAKREF from any base in MRO that has it
662        if mro
663            .iter()
664            .any(|b| b.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF))
665        {
666            slots.flags |= PyTypeFlags::HAS_WEAKREF | PyTypeFlags::MANAGED_WEAKREF
667        }
668
669        // Inherit SEQUENCE and MAPPING flags from base classes
670        Self::inherit_patma_flags(&mut slots, &bases);
671
672        // Check for __abc_tpflags__ from ABCMeta (for collections.abc.Sequence, Mapping, etc.)
673        Self::check_abc_tpflags(&mut slots, &attrs, &bases, ctx);
674
675        if slots.basicsize == 0 {
676            slots.basicsize = base.slots.basicsize;
677        }
678
679        Self::inherit_readonly_slots(&mut slots, &base);
680
681        // Normalize: any type with HAS_WEAKREF gets MANAGED_WEAKREF
682        if slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF) {
683            slots.flags |= PyTypeFlags::MANAGED_WEAKREF;
684        }
685
686        if let Some(qualname) = attrs.get(identifier!(ctx, __qualname__))
687            && !qualname.fast_isinstance(ctx.types.str_type)
688        {
689            return Err(format!(
690                "type __qualname__ must be a str, not {}",
691                qualname.class().name()
692            ));
693        }
694
695        let new_type = PyRef::new_ref(
696            Self {
697                base: Some(base),
698                bases: PyRwLock::new(bases),
699                mro: PyRwLock::new(mro),
700                subclasses: PyRwLock::default(),
701                attributes: PyRwLock::new(attrs),
702                slots,
703                heaptype_ext: Some(Pin::new(Box::new(heaptype_ext))),
704                tp_version_tag: AtomicU32::new(0),
705            },
706            metaclass,
707            None,
708        );
709        new_type.mro.write().insert(0, new_type.clone());
710
711        new_type.init_slots(ctx);
712
713        let weakref_type = super::PyWeak::static_type();
714        for base in new_type.bases.read().iter() {
715            base.subclasses.write().push(
716                new_type
717                    .as_object()
718                    .downgrade_with_weakref_typ_opt(None, weakref_type.to_owned())
719                    .unwrap(),
720            );
721        }
722
723        Ok(new_type)
724    }
725
726    pub fn new_static(
727        base: PyRef<Self>,
728        attrs: PyAttributes,
729        mut slots: PyTypeSlots,
730        metaclass: PyRef<Self>,
731    ) -> Result<PyRef<Self>, String> {
732        if base.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
733            slots.flags |= PyTypeFlags::HAS_DICT
734        }
735        if base.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF) {
736            slots.flags |= PyTypeFlags::HAS_WEAKREF | PyTypeFlags::MANAGED_WEAKREF
737        }
738
739        // Inherit SEQUENCE and MAPPING flags from base class
740        // For static types, we only have a single base
741        Self::inherit_patma_flags(&mut slots, core::slice::from_ref(&base));
742
743        if slots.basicsize == 0 {
744            slots.basicsize = base.slots.basicsize;
745        }
746
747        Self::inherit_readonly_slots(&mut slots, &base);
748
749        // Normalize: any type with HAS_WEAKREF gets MANAGED_WEAKREF
750        if slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF) {
751            slots.flags |= PyTypeFlags::MANAGED_WEAKREF;
752        }
753
754        let bases = PyRwLock::new(vec![base.clone()]);
755        let mro = base.mro_map_collect(|x| x.to_owned());
756
757        let new_type = PyRef::new_ref(
758            Self {
759                base: Some(base),
760                bases,
761                mro: PyRwLock::new(mro),
762                subclasses: PyRwLock::default(),
763                attributes: PyRwLock::new(attrs),
764                slots,
765                heaptype_ext: None,
766                tp_version_tag: AtomicU32::new(0),
767            },
768            metaclass,
769            None,
770        );
771
772        // Static types are not tracked by GC.
773        // They are immortal and never participate in collectable cycles.
774        unsafe {
775            crate::gc_state::gc_state()
776                .untrack_object(core::ptr::NonNull::from(new_type.as_object()));
777        }
778        new_type.as_object().clear_gc_tracked();
779
780        new_type.mro.write().insert(0, new_type.clone());
781
782        // Note: inherit_slots is called in PyClassImpl::init_class after
783        // slots are fully initialized by make_slots()
784
785        Self::set_new(&new_type.slots, &new_type.base);
786        Self::set_alloc(&new_type.slots, &new_type.base);
787
788        let weakref_type = super::PyWeak::static_type();
789        for base in new_type.bases.read().iter() {
790            base.subclasses.write().push(
791                new_type
792                    .as_object()
793                    .downgrade_with_weakref_typ_opt(None, weakref_type.to_owned())
794                    .unwrap(),
795            );
796        }
797
798        Ok(new_type)
799    }
800
801    pub(crate) fn init_slots(&self, ctx: &Context) {
802        // Inherit slots from MRO (mro[0] is self, so skip it)
803        let mro: Vec<_> = self.mro.read()[1..].to_vec();
804        for base in mro.iter() {
805            self.inherit_slots(base);
806        }
807
808        // Wire dunder methods to slots
809        #[allow(clippy::mutable_key_type)]
810        let mut slot_name_set = std::collections::HashSet::new();
811
812        // mro[0] is self, so skip it; self.attributes is checked separately below
813        for cls in self.mro.read()[1..].iter() {
814            for &name in cls.attributes.read().keys() {
815                if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") {
816                    slot_name_set.insert(name);
817                }
818            }
819        }
820        for &name in self.attributes.read().keys() {
821            if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") {
822                slot_name_set.insert(name);
823            }
824        }
825        // Sort for deterministic iteration order (important for slot processing)
826        let mut slot_names: Vec<_> = slot_name_set.into_iter().collect();
827        slot_names.sort_by_key(|name| name.as_str());
828        for attr_name in slot_names {
829            self.update_slot::<true>(attr_name, ctx);
830        }
831
832        Self::set_new(&self.slots, &self.base);
833        Self::set_alloc(&self.slots, &self.base);
834    }
835
836    fn set_new(slots: &PyTypeSlots, base: &Option<PyTypeRef>) {
837        if slots.flags.contains(PyTypeFlags::DISALLOW_INSTANTIATION) {
838            slots.new.store(None)
839        } else if slots.new.load().is_none() {
840            slots.new.store(
841                base.as_ref()
842                    .map(|base| base.slots.new.load())
843                    .unwrap_or(None),
844            )
845        }
846    }
847
848    fn set_alloc(slots: &PyTypeSlots, base: &Option<PyTypeRef>) {
849        if slots.alloc.load().is_none() {
850            slots.alloc.store(
851                base.as_ref()
852                    .map(|base| base.slots.alloc.load())
853                    .unwrap_or(None),
854            );
855        }
856    }
857
858    /// Inherit readonly slots from base type at creation time.
859    /// These slots are not AtomicCell and must be set before the type is used.
860    fn inherit_readonly_slots(slots: &mut PyTypeSlots, base: &Self) {
861        if slots.as_buffer.is_none() {
862            slots.as_buffer = base.slots.as_buffer;
863        }
864    }
865
866    /// Inherit slots from base type. inherit_slots
867    pub(crate) fn inherit_slots(&self, base: &Self) {
868        // Use SLOT_DEFS to iterate all slots
869        // Note: as_buffer is handled in inherit_readonly_slots (not AtomicCell)
870        for def in SLOT_DEFS {
871            def.accessor.copyslot_if_none(self, base);
872        }
873    }
874
875    // This is used for class initialization where the vm is not yet available.
876    pub fn set_str_attr<V: Into<PyObjectRef>>(
877        &self,
878        attr_name: &str,
879        value: V,
880        ctx: impl AsRef<Context>,
881    ) {
882        let ctx = ctx.as_ref();
883        let attr_name = ctx.intern_str(attr_name);
884        self.set_attr(attr_name, value.into())
885    }
886
887    pub fn set_attr(&self, attr_name: &'static PyStrInterned, value: PyObjectRef) {
888        // Invalidate caches BEFORE modifying attributes so that borrowed
889        // pointers in cache entries are nullified while the source objects
890        // are still alive.
891        self.modified();
892        self.attributes.write().insert(attr_name, value);
893    }
894
895    /// Internal get_attr implementation for fast lookup on a class.
896    /// Searches the full MRO (including self) with method cache acceleration.
897    pub fn get_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
898        self.find_name_in_mro(attr_name)
899    }
900
901    /// Cache __init__ for CALL_ALLOC_AND_ENTER_INIT specialization.
902    /// The cache is valid only when guarded by the type version check.
903    pub(crate) fn cache_init_for_specialization(
904        &self,
905        init: PyRef<PyFunction>,
906        tp_version: u32,
907        vm: &VirtualMachine,
908    ) -> bool {
909        let Some(ext) = self.heaptype_ext.as_ref() else {
910            return false;
911        };
912        if tp_version == 0 {
913            return false;
914        }
915        if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
916            return false;
917        }
918        let _guard = ext.specialization_cache.write_lock.lock();
919        if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
920            return false;
921        }
922        ext.specialization_cache.swap_init(Some(init), Some(vm));
923        true
924    }
925
926    /// Read cached __init__ for CALL_ALLOC_AND_ENTER_INIT specialization.
927    pub(crate) fn get_cached_init_for_specialization(
928        &self,
929        tp_version: u32,
930    ) -> Option<PyRef<PyFunction>> {
931        let ext = self.heaptype_ext.as_ref()?;
932        if tp_version == 0 {
933            return None;
934        }
935        if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
936            return None;
937        }
938        ext.specialization_cache
939            .init
940            .to_owned_ordering(Ordering::Acquire)
941    }
942
943    /// Cache __getitem__ for BINARY_OP_SUBSCR_GETITEM specialization.
944    /// The cache is valid only when guarded by the type version check.
945    pub(crate) fn cache_getitem_for_specialization(
946        &self,
947        getitem: PyRef<PyFunction>,
948        tp_version: u32,
949        vm: &VirtualMachine,
950    ) -> bool {
951        let Some(ext) = self.heaptype_ext.as_ref() else {
952            return false;
953        };
954        if tp_version == 0 {
955            return false;
956        }
957        let _guard = ext.specialization_cache.write_lock.lock();
958        if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
959            return false;
960        }
961        let func_version = getitem.get_version_for_current_state();
962        if func_version == 0 {
963            return false;
964        }
965        ext.specialization_cache
966            .swap_getitem(Some(getitem), Some(vm));
967        ext.specialization_cache
968            .getitem_version
969            .store(func_version, Ordering::Relaxed);
970        true
971    }
972
973    /// Read cached __getitem__ for BINARY_OP_SUBSCR_GETITEM specialization.
974    pub(crate) fn get_cached_getitem_for_specialization(&self) -> Option<(PyRef<PyFunction>, u32)> {
975        let ext = self.heaptype_ext.as_ref()?;
976        // Match CPython check order: pointer (Acquire) then function version.
977        let getitem = ext
978            .specialization_cache
979            .getitem
980            .to_owned_ordering(Ordering::Acquire)?;
981        let cached_version = ext
982            .specialization_cache
983            .getitem_version
984            .load(Ordering::Relaxed);
985        if cached_version == 0 {
986            return None;
987        }
988        Some((getitem, cached_version))
989    }
990
991    pub fn get_direct_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
992        self.attributes.read().get(attr_name).cloned()
993    }
994
995    /// find_name_in_mro with method cache (MCACHE).
996    /// Looks in tp_dict of types in MRO, bypasses descriptors.
997    ///
998    /// Uses a lock-free SeqLock-style pattern:
999    ///   Read:  load sequence/version/name → load value + try_to_owned →
1000    ///          validate value pointer + sequence
1001    ///   Write: sequence(begin) → version=0 → swap value/name → version=assigned → sequence(end)
1002    fn find_name_in_mro(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> {
1003        let version = self.tp_version_tag.load(Ordering::Acquire);
1004        if version != 0 {
1005            let idx = type_cache_hash(version, name);
1006            let entry = &TYPE_CACHE[idx];
1007            let name_ptr = name as *const _ as *mut _;
1008            loop {
1009                let seq1 = entry.begin_read();
1010                let v1 = entry.version.load(Ordering::Acquire);
1011                let type_version = self.tp_version_tag.load(Ordering::Acquire);
1012                if v1 != type_version
1013                    || !core::ptr::eq(entry.name.load(Ordering::Relaxed), name_ptr)
1014                {
1015                    break;
1016                }
1017                let ptr = entry.value.load(Ordering::Acquire);
1018                if ptr.is_null() {
1019                    if entry.end_read(seq1) {
1020                        break;
1021                    }
1022                    continue;
1023                }
1024                // _Py_TryIncrefCompare-style validation:
1025                // safe_inc via raw pointer, then ensure source is unchanged.
1026                if let Some(cloned) = unsafe { PyObject::try_to_owned_from_ptr(ptr) } {
1027                    let same_ptr = core::ptr::eq(entry.value.load(Ordering::Relaxed), ptr);
1028                    if same_ptr && entry.end_read(seq1) {
1029                        return Some(cloned);
1030                    }
1031                    drop(cloned);
1032                    continue;
1033                }
1034                break;
1035            }
1036        }
1037
1038        // Assign version BEFORE the MRO walk so that any concurrent
1039        // modified() call during the walk invalidates this version.
1040        let assigned = if version == 0 {
1041            self.assign_version_tag()
1042        } else {
1043            version
1044        };
1045
1046        // MRO walk
1047        let result = self.find_name_in_mro_uncached(name);
1048
1049        // Only cache positive results. Negative results are not cached to
1050        // avoid stale entries from transient MRO walk failures during
1051        // concurrent type modifications.
1052        if let Some(ref found) = result
1053            && assigned != 0
1054            && !TYPE_CACHE_CLEARING.load(Ordering::Acquire)
1055            && self.tp_version_tag.load(Ordering::Acquire) == assigned
1056        {
1057            let idx = type_cache_hash(assigned, name);
1058            let entry = &TYPE_CACHE[idx];
1059            let name_ptr = name as *const _ as *mut _;
1060            entry.begin_write();
1061            // Invalidate first to prevent readers from seeing partial state
1062            entry.version.store(0, Ordering::Release);
1063            // Store borrowed pointer (no refcount increment).
1064            let new_ptr = &**found as *const PyObject as *mut PyObject;
1065            entry.value.store(new_ptr, Ordering::Relaxed);
1066            entry.name.store(name_ptr, Ordering::Relaxed);
1067            // Activate entry — Release ensures value/name writes are visible
1068            entry.version.store(assigned, Ordering::Release);
1069            entry.end_write();
1070        }
1071
1072        result
1073    }
1074
1075    /// Raw MRO walk without cache.
1076    fn find_name_in_mro_uncached(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> {
1077        for cls in self.mro.read().iter() {
1078            if let Some(value) = cls.attributes.read().get(name) {
1079                return Some(value.clone());
1080            }
1081        }
1082        None
1083    }
1084
1085    /// _PyType_LookupRef: look up a name through the MRO without setting an exception.
1086    pub fn lookup_ref(&self, name: &Py<PyStr>, vm: &VirtualMachine) -> Option<PyObjectRef> {
1087        let interned_name = vm.ctx.interned_str(name)?;
1088        self.find_name_in_mro(interned_name)
1089    }
1090
1091    pub fn get_super_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
1092        self.mro.read()[1..]
1093            .iter()
1094            .find_map(|class| class.attributes.read().get(attr_name).cloned())
1095    }
1096
1097    /// Fast lookup for attribute existence on a class.
1098    pub fn has_attr(&self, attr_name: &'static PyStrInterned) -> bool {
1099        self.has_name_in_mro(attr_name)
1100    }
1101
1102    /// Check if attribute exists in MRO, using method cache for fast check.
1103    /// Unlike find_name_in_mro, avoids cloning the value on cache hit.
1104    fn has_name_in_mro(&self, name: &'static PyStrInterned) -> bool {
1105        let version = self.tp_version_tag.load(Ordering::Acquire);
1106        if version != 0 {
1107            let idx = type_cache_hash(version, name);
1108            let entry = &TYPE_CACHE[idx];
1109            let name_ptr = name as *const _ as *mut _;
1110            loop {
1111                let seq1 = entry.begin_read();
1112                let v1 = entry.version.load(Ordering::Acquire);
1113                let type_version = self.tp_version_tag.load(Ordering::Acquire);
1114                if v1 != type_version
1115                    || !core::ptr::eq(entry.name.load(Ordering::Relaxed), name_ptr)
1116                {
1117                    break;
1118                }
1119                let ptr = entry.value.load(Ordering::Acquire);
1120                if entry.end_read(seq1) {
1121                    if !ptr.is_null() {
1122                        return true;
1123                    }
1124                    break;
1125                }
1126                continue;
1127            }
1128        }
1129
1130        // Cache miss — use find_name_in_mro which populates cache
1131        self.find_name_in_mro(name).is_some()
1132    }
1133
1134    pub fn get_attributes(&self) -> PyAttributes {
1135        // Gather all members here:
1136        let mut attributes = PyAttributes::default();
1137
1138        // mro[0] is self, so we iterate through the entire MRO in reverse
1139        for bc in self.mro.read().iter().map(|cls| -> &Self { cls }).rev() {
1140            for (name, value) in bc.attributes.read().iter() {
1141                attributes.insert(name.to_owned(), value.clone());
1142            }
1143        }
1144
1145        attributes
1146    }
1147
1148    // bound method for every type
1149    pub(crate) fn __new__(zelf: PyRef<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1150        let (subtype, args): (PyRef<Self>, FuncArgs) = args.bind(vm)?;
1151        if !subtype.fast_issubclass(&zelf) {
1152            return Err(vm.new_type_error(format!(
1153                "{zelf}.__new__({subtype}): {subtype} is not a subtype of {zelf}",
1154                zelf = zelf.name(),
1155                subtype = subtype.name(),
1156            )));
1157        }
1158        call_slot_new(zelf, subtype, args, vm)
1159    }
1160
1161    fn name_inner<'a, R: 'a>(
1162        &'a self,
1163        static_f: impl FnOnce(&'static str) -> R,
1164        heap_f: impl FnOnce(&'a HeapTypeExt) -> R,
1165    ) -> R {
1166        if let Some(ref ext) = self.heaptype_ext {
1167            heap_f(ext)
1168        } else {
1169            static_f(self.slots.name)
1170        }
1171    }
1172
1173    pub fn slot_name(&self) -> BorrowedValue<'_, str> {
1174        self.name_inner(
1175            |name| name.into(),
1176            |ext| {
1177                PyRwLockReadGuard::map(ext.name.read(), |name: &PyUtf8StrRef| -> &str {
1178                    name.as_str()
1179                })
1180                .into()
1181            },
1182        )
1183    }
1184
1185    pub fn name(&self) -> BorrowedValue<'_, str> {
1186        self.name_inner(
1187            |name| name.rsplit_once('.').map_or(name, |(_, name)| name).into(),
1188            |ext| {
1189                PyRwLockReadGuard::map(ext.name.read(), |name: &PyUtf8StrRef| -> &str {
1190                    name.as_str()
1191                })
1192                .into()
1193            },
1194        )
1195    }
1196
1197    // Type Data Slot API - CPython's PyObject_GetTypeData equivalent
1198
1199    /// Initialize type data for this type. Can only be called once.
1200    /// Returns an error if the type is not a heap type or if data is already initialized.
1201    pub fn init_type_data<T: Any + Send + Sync + 'static>(&self, data: T) -> Result<(), String> {
1202        let ext = self
1203            .heaptype_ext
1204            .as_ref()
1205            .ok_or_else(|| "Cannot set type data on non-heap types".to_string())?;
1206
1207        let mut type_data = ext.type_data.write();
1208        if type_data.is_some() {
1209            return Err("Type data already initialized".to_string());
1210        }
1211        *type_data = Some(TypeDataSlot::new(data));
1212        Ok(())
1213    }
1214
1215    /// Get a read guard to the type data.
1216    /// Returns None if the type is not a heap type, has no data, or the data type doesn't match.
1217    pub fn get_type_data<T: Any + 'static>(&self) -> Option<TypeDataRef<'_, T>> {
1218        self.heaptype_ext
1219            .as_ref()
1220            .and_then(|ext| TypeDataRef::try_new(ext.type_data.read()))
1221    }
1222
1223    /// Get a write guard to the type data.
1224    /// Returns None if the type is not a heap type, has no data, or the data type doesn't match.
1225    pub fn get_type_data_mut<T: Any + 'static>(&self) -> Option<TypeDataRefMut<'_, T>> {
1226        self.heaptype_ext
1227            .as_ref()
1228            .and_then(|ext| TypeDataRefMut::try_new(ext.type_data.write()))
1229    }
1230
1231    /// Check if this type has type data of the given type.
1232    pub fn has_type_data<T: Any + 'static>(&self) -> bool {
1233        self.heaptype_ext.as_ref().is_some_and(|ext| {
1234            ext.type_data
1235                .read()
1236                .as_ref()
1237                .is_some_and(|slot| slot.get::<T>().is_some())
1238        })
1239    }
1240}
1241
1242impl Py<PyType> {
1243    pub(crate) fn is_subtype(&self, other: &Self) -> bool {
1244        is_subtype_with_mro(&self.mro.read(), self, other)
1245    }
1246
1247    /// Equivalent to CPython's PyType_CheckExact macro
1248    /// Checks if obj is exactly a type (not a subclass)
1249    pub fn check_exact<'a>(obj: &'a PyObject, vm: &VirtualMachine) -> Option<&'a Self> {
1250        obj.downcast_ref_if_exact::<PyType>(vm)
1251    }
1252
1253    /// Determines if `subclass` is actually a subclass of `cls`, this doesn't call __subclasscheck__,
1254    /// so only use this if `cls` is known to have not overridden the base __subclasscheck__ magic
1255    /// method.
1256    pub fn fast_issubclass(&self, cls: &impl Borrow<PyObject>) -> bool {
1257        self.as_object().is(cls.borrow()) || self.mro.read()[1..].iter().any(|c| c.is(cls.borrow()))
1258    }
1259
1260    pub fn mro_map_collect<F, R>(&self, f: F) -> Vec<R>
1261    where
1262        F: Fn(&Self) -> R,
1263    {
1264        self.mro.read().iter().map(|x| x.deref()).map(f).collect()
1265    }
1266
1267    pub fn mro_collect(&self) -> Vec<PyRef<PyType>> {
1268        self.mro
1269            .read()
1270            .iter()
1271            .map(|x| x.deref())
1272            .map(|x| x.to_owned())
1273            .collect()
1274    }
1275
1276    pub fn iter_base_chain(&self) -> impl Iterator<Item = &Self> {
1277        core::iter::successors(Some(self), |cls| cls.base.as_deref())
1278    }
1279
1280    pub fn extend_methods(&'static self, method_defs: &'static [PyMethodDef], ctx: &Context) {
1281        for method_def in method_defs {
1282            let method = method_def.to_proper_method(self, ctx);
1283            self.set_attr(ctx.intern_str(method_def.name), method);
1284        }
1285    }
1286}
1287
1288#[pyclass(
1289    with(
1290        Py,
1291        Constructor,
1292        Initializer,
1293        GetAttr,
1294        SetAttr,
1295        Callable,
1296        AsNumber,
1297        Representable
1298    ),
1299    flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
1300)]
1301impl PyType {
1302    #[pygetset]
1303    fn __bases__(&self, vm: &VirtualMachine) -> PyTupleRef {
1304        vm.ctx.new_tuple(
1305            self.bases
1306                .read()
1307                .iter()
1308                .map(|x| x.as_object().to_owned())
1309                .collect(),
1310        )
1311    }
1312    #[pygetset(setter, name = "__bases__")]
1313    fn set_bases(zelf: &Py<Self>, bases: Vec<PyTypeRef>, vm: &VirtualMachine) -> PyResult<()> {
1314        // TODO: Assigning to __bases__ is only used in typing.NamedTupleMeta.__new__
1315        // Rather than correctly re-initializing the class, we are skipping a few steps for now
1316        if zelf.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
1317            return Err(vm.new_type_error(format!(
1318                "cannot set '__bases__' attribute of immutable type '{}'",
1319                zelf.name()
1320            )));
1321        }
1322        if bases.is_empty() {
1323            return Err(vm.new_type_error(format!(
1324                "can only assign non-empty tuple to %s.__bases__, not {}",
1325                zelf.name()
1326            )));
1327        }
1328
1329        // TODO: check for mro cycles
1330
1331        // TODO: Remove this class from all subclass lists
1332        // for base in self.bases.read().iter() {
1333        //     let subclasses = base.subclasses.write();
1334        //     // TODO: how to uniquely identify the subclasses to remove?
1335        // }
1336
1337        *zelf.bases.write() = bases;
1338        // Recursively update the mros of this class and all subclasses
1339        fn update_mro_recursively(cls: &PyType, vm: &VirtualMachine) -> PyResult<()> {
1340            let mut mro =
1341                PyType::resolve_mro(&cls.bases.read()).map_err(|msg| vm.new_type_error(msg))?;
1342            // Preserve self (mro[0]) when updating MRO
1343            mro.insert(0, cls.mro.read()[0].to_owned());
1344            *cls.mro.write() = mro;
1345            for subclass in cls.subclasses.write().iter() {
1346                let subclass = subclass.upgrade().unwrap();
1347                let subclass: &Py<PyType> = subclass.downcast_ref().unwrap();
1348                update_mro_recursively(subclass, vm)?;
1349            }
1350            Ok(())
1351        }
1352        update_mro_recursively(zelf, vm)?;
1353
1354        // Invalidate inline caches
1355        zelf.modified();
1356
1357        // TODO: do any old slots need to be cleaned up first?
1358        zelf.init_slots(&vm.ctx);
1359
1360        // Register this type as a subclass of its new bases
1361        let weakref_type = super::PyWeak::static_type();
1362        for base in zelf.bases.read().iter() {
1363            base.subclasses.write().push(
1364                zelf.as_object()
1365                    .downgrade_with_weakref_typ_opt(None, weakref_type.to_owned())
1366                    .unwrap(),
1367            );
1368        }
1369
1370        Ok(())
1371    }
1372
1373    #[pygetset]
1374    fn __base__(&self) -> Option<PyTypeRef> {
1375        self.base.clone()
1376    }
1377
1378    #[pygetset]
1379    const fn __flags__(&self) -> u64 {
1380        self.slots.flags.bits()
1381    }
1382
1383    #[pygetset]
1384    fn __basicsize__(&self) -> usize {
1385        crate::object::SIZEOF_PYOBJECT_HEAD + self.slots.basicsize
1386    }
1387
1388    #[pygetset]
1389    fn __itemsize__(&self) -> usize {
1390        self.slots.itemsize
1391    }
1392
1393    #[pygetset]
1394    pub fn __name__(&self, vm: &VirtualMachine) -> PyStrRef {
1395        self.name_inner(
1396            |name| {
1397                vm.ctx
1398                    .interned_str(name.rsplit_once('.').map_or(name, |(_, name)| name))
1399                    .unwrap_or_else(|| {
1400                        panic!(
1401                            "static type name must be already interned but {} is not",
1402                            self.slot_name()
1403                        )
1404                    })
1405                    .to_owned()
1406            },
1407            |ext| ext.name.read().clone().into_wtf8(),
1408        )
1409    }
1410
1411    #[pygetset]
1412    pub fn __qualname__(&self, vm: &VirtualMachine) -> PyObjectRef {
1413        if let Some(ref heap_type) = self.heaptype_ext {
1414            heap_type.qualname.read().clone().into()
1415        } else {
1416            // For static types, return the name
1417            vm.ctx.new_str(self.name().deref()).into()
1418        }
1419    }
1420
1421    #[pygetset(setter)]
1422    fn set___qualname__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
1423        self.check_set_special_type_attr(identifier!(vm, __qualname__), vm)?;
1424        let value = value.ok_or_else(|| {
1425            vm.new_type_error(format!(
1426                "cannot delete '__qualname__' attribute of immutable type '{}'",
1427                self.name()
1428            ))
1429        })?;
1430
1431        let str_value = downcast_qualname(value, vm)?;
1432
1433        let heap_type = self.heaptype_ext.as_ref().ok_or_else(|| {
1434            vm.new_type_error(format!(
1435                "cannot set '__qualname__' attribute of immutable type '{}'",
1436                self.name()
1437            ))
1438        })?;
1439
1440        // Use std::mem::replace to swap the new value in and get the old value out,
1441        // then drop the old value after releasing the lock
1442        let _old_qualname = {
1443            let mut qualname_guard = heap_type.qualname.write();
1444            core::mem::replace(&mut *qualname_guard, str_value)
1445        };
1446        // old_qualname is dropped here, outside the lock scope
1447
1448        Ok(())
1449    }
1450
1451    #[pygetset]
1452    fn __annotate__(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1453        if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
1454            return Err(vm.new_attribute_error(format!(
1455                "type object '{}' has no attribute '__annotate__'",
1456                self.name()
1457            )));
1458        }
1459
1460        let mut attrs = self.attributes.write();
1461        // First try __annotate__, in case that's been set explicitly
1462        if let Some(annotate) = attrs.get(identifier!(vm, __annotate__)).cloned() {
1463            return Ok(annotate);
1464        }
1465        // Then try __annotate_func__
1466        if let Some(annotate) = attrs.get(identifier!(vm, __annotate_func__)).cloned() {
1467            // TODO: Apply descriptor tp_descr_get if needed
1468            return Ok(annotate);
1469        }
1470        // Set __annotate_func__ = None and return None
1471        let none = vm.ctx.none();
1472        attrs.insert(identifier!(vm, __annotate_func__), none.clone());
1473        Ok(none)
1474    }
1475
1476    #[pygetset(setter)]
1477    fn set___annotate__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
1478        let value = match value {
1479            PySetterValue::Delete => {
1480                return Err(vm.new_type_error("cannot delete __annotate__ attribute"));
1481            }
1482            PySetterValue::Assign(v) => v,
1483        };
1484
1485        if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
1486            return Err(vm.new_type_error(format!(
1487                "cannot set '__annotate__' attribute of immutable type '{}'",
1488                self.name()
1489            )));
1490        }
1491
1492        if !vm.is_none(&value) && !value.is_callable() {
1493            return Err(vm.new_type_error("__annotate__ must be callable or None"));
1494        }
1495
1496        let mut attrs = self.attributes.write();
1497        // Clear cached annotations only when setting to a new callable
1498        if !vm.is_none(&value) {
1499            attrs.swap_remove(identifier!(vm, __annotations_cache__));
1500        }
1501        attrs.insert(identifier!(vm, __annotate_func__), value.clone());
1502
1503        Ok(())
1504    }
1505
1506    #[pygetset]
1507    fn __annotations__(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1508        let attrs = self.attributes.read();
1509        if let Some(annotations) = attrs.get(identifier!(vm, __annotations__)).cloned() {
1510            // Ignore the __annotations__ descriptor stored on type itself.
1511            if !annotations.class().is(vm.ctx.types.getset_type) {
1512                if vm.is_none(&annotations)
1513                    || annotations.class().is(vm.ctx.types.dict_type)
1514                    || self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
1515                {
1516                    return Ok(annotations);
1517                }
1518                return Err(vm.new_attribute_error(format!(
1519                    "type object '{}' has no attribute '__annotations__'",
1520                    self.name()
1521                )));
1522            }
1523        }
1524        // Then try __annotations_cache__
1525        if let Some(annotations) = attrs.get(identifier!(vm, __annotations_cache__)).cloned() {
1526            if vm.is_none(&annotations)
1527                || annotations.class().is(vm.ctx.types.dict_type)
1528                || self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
1529            {
1530                return Ok(annotations);
1531            }
1532            return Err(vm.new_attribute_error(format!(
1533                "type object '{}' has no attribute '__annotations__'",
1534                self.name()
1535            )));
1536        }
1537        drop(attrs);
1538
1539        if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
1540            return Err(vm.new_attribute_error(format!(
1541                "type object '{}' has no attribute '__annotations__'",
1542                self.name()
1543            )));
1544        }
1545
1546        // Get __annotate__ and call it if callable
1547        let annotate = self.__annotate__(vm)?;
1548        let annotations = if annotate.is_callable() {
1549            // Call __annotate__(1) where 1 is FORMAT_VALUE
1550            let result = annotate.call((1i32,), vm)?;
1551            if !result.class().is(vm.ctx.types.dict_type) {
1552                return Err(vm.new_type_error(format!(
1553                    "__annotate__ returned non-dict of type '{}'",
1554                    result.class().name()
1555                )));
1556            }
1557            result
1558        } else {
1559            vm.ctx.new_dict().into()
1560        };
1561
1562        // Cache the result in __annotations_cache__
1563        self.attributes
1564            .write()
1565            .insert(identifier!(vm, __annotations_cache__), annotations.clone());
1566        Ok(annotations)
1567    }
1568
1569    #[pygetset(setter)]
1570    fn set___annotations__(
1571        &self,
1572        value: crate::function::PySetterValue<PyObjectRef>,
1573        vm: &VirtualMachine,
1574    ) -> PyResult<()> {
1575        if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
1576            return Err(vm.new_type_error(format!(
1577                "cannot set '__annotations__' attribute of immutable type '{}'",
1578                self.name()
1579            )));
1580        }
1581
1582        let mut attrs = self.attributes.write();
1583        let has_annotations = attrs.contains_key(identifier!(vm, __annotations__));
1584
1585        match value {
1586            crate::function::PySetterValue::Assign(value) => {
1587                // SET path: store the value (including None)
1588                let key = if has_annotations {
1589                    identifier!(vm, __annotations__)
1590                } else {
1591                    identifier!(vm, __annotations_cache__)
1592                };
1593                attrs.insert(key, value);
1594                if has_annotations {
1595                    attrs.swap_remove(identifier!(vm, __annotations_cache__));
1596                }
1597            }
1598            crate::function::PySetterValue::Delete => {
1599                // DELETE path: remove the key
1600                let removed = if has_annotations {
1601                    attrs
1602                        .swap_remove(identifier!(vm, __annotations__))
1603                        .is_some()
1604                } else {
1605                    attrs
1606                        .swap_remove(identifier!(vm, __annotations_cache__))
1607                        .is_some()
1608                };
1609                if !removed {
1610                    return Err(vm.new_attribute_error("__annotations__"));
1611                }
1612                if has_annotations {
1613                    attrs.swap_remove(identifier!(vm, __annotations_cache__));
1614                }
1615            }
1616        }
1617        attrs.swap_remove(identifier!(vm, __annotate_func__));
1618        attrs.swap_remove(identifier!(vm, __annotate__));
1619
1620        Ok(())
1621    }
1622
1623    #[pygetset]
1624    pub fn __module__(&self, vm: &VirtualMachine) -> PyObjectRef {
1625        self.attributes
1626            .read()
1627            .get(identifier!(vm, __module__))
1628            .cloned()
1629            // We need to exclude this method from going into recursion:
1630            .and_then(|found| {
1631                if found.fast_isinstance(vm.ctx.types.getset_type) {
1632                    None
1633                } else {
1634                    Some(found)
1635                }
1636            })
1637            .unwrap_or_else(|| {
1638                // For non-heap types, extract module from tp_name (e.g. "typing.TypeAliasType" -> "typing")
1639                let slot_name = self.slot_name();
1640                if let Some((module, _)) = slot_name.rsplit_once('.') {
1641                    vm.ctx.intern_str(module).to_object()
1642                } else {
1643                    vm.ctx.new_str(ascii!("builtins")).into()
1644                }
1645            })
1646    }
1647
1648    #[pygetset(setter)]
1649    fn set___module__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
1650        self.check_set_special_type_attr(identifier!(vm, __module__), vm)?;
1651        let mut attributes = self.attributes.write();
1652        attributes.swap_remove(identifier!(vm, __firstlineno__));
1653        attributes.insert(identifier!(vm, __module__), value);
1654        Ok(())
1655    }
1656
1657    #[pyclassmethod]
1658    fn __prepare__(
1659        _cls: PyTypeRef,
1660        _name: OptionalArg<PyObjectRef>,
1661        _bases: OptionalArg<PyObjectRef>,
1662        _kwargs: KwArgs,
1663        vm: &VirtualMachine,
1664    ) -> PyDictRef {
1665        vm.ctx.new_dict()
1666    }
1667
1668    #[pymethod]
1669    fn __subclasses__(&self) -> PyList {
1670        let mut subclasses = self.subclasses.write();
1671        subclasses.retain(|x| x.upgrade().is_some());
1672        PyList::from(
1673            subclasses
1674                .iter()
1675                .map(|x| x.upgrade().unwrap())
1676                .collect::<Vec<_>>(),
1677        )
1678    }
1679
1680    pub fn __ror__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
1681        or_(other, zelf, vm)
1682    }
1683
1684    pub fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
1685        or_(zelf, other, vm)
1686    }
1687
1688    #[pygetset]
1689    fn __dict__(zelf: PyRef<Self>) -> PyMappingProxy {
1690        PyMappingProxy::from(zelf)
1691    }
1692
1693    #[pygetset(setter)]
1694    fn set___dict__(&self, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
1695        Err(vm.new_not_implemented_error(
1696            "Setting __dict__ attribute on a type isn't yet implemented",
1697        ))
1698    }
1699
1700    fn check_set_special_type_attr(
1701        &self,
1702        name: &PyStrInterned,
1703        vm: &VirtualMachine,
1704    ) -> PyResult<()> {
1705        if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
1706            return Err(vm.new_type_error(format!(
1707                "cannot set '{}' attribute of immutable type '{}'",
1708                name,
1709                self.slot_name()
1710            )));
1711        }
1712        Ok(())
1713    }
1714
1715    #[pygetset(setter)]
1716    fn set___name__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
1717        self.check_set_special_type_attr(identifier!(vm, __name__), vm)?;
1718        let name = value.downcast::<PyStr>().map_err(|value| {
1719            vm.new_type_error(format!(
1720                "can only assign string to {}.__name__, not '{}'",
1721                self.slot_name(),
1722                value.class().slot_name(),
1723            ))
1724        })?;
1725        if name.as_bytes().contains(&0) {
1726            return Err(vm.new_value_error("type name must not contain null characters"));
1727        }
1728        let name = name.try_into_utf8(vm)?;
1729
1730        let heap_type = self.heaptype_ext.as_ref().ok_or_else(|| {
1731            vm.new_type_error(format!(
1732                "cannot set '__name__' attribute of immutable type '{}'",
1733                self.slot_name()
1734            ))
1735        })?;
1736
1737        // Use std::mem::replace to swap the new value in and get the old value out,
1738        // then drop the old value after releasing the lock
1739        let _old_name = {
1740            let mut name_guard = heap_type.name.write();
1741            core::mem::replace(&mut *name_guard, name)
1742        };
1743        // old_name is dropped here, outside the lock scope
1744
1745        Ok(())
1746    }
1747
1748    #[pygetset]
1749    fn __text_signature__(&self) -> Option<String> {
1750        self.slots
1751            .doc
1752            .and_then(|doc| get_text_signature_from_internal_doc(&self.name(), doc))
1753            .map(|signature| signature.to_string())
1754    }
1755
1756    #[pygetset]
1757    fn __type_params__(&self, vm: &VirtualMachine) -> PyTupleRef {
1758        let attrs = self.attributes.read();
1759        let key = identifier!(vm, __type_params__);
1760        if let Some(params) = attrs.get(&key)
1761            && let Ok(tuple) = params.clone().downcast::<PyTuple>()
1762        {
1763            return tuple;
1764        }
1765        // Return empty tuple if not found or not a tuple
1766        vm.ctx.empty_tuple.clone()
1767    }
1768
1769    #[pygetset(setter)]
1770    fn set___type_params__(
1771        &self,
1772        value: PySetterValue<PyTupleRef>,
1773        vm: &VirtualMachine,
1774    ) -> PyResult<()> {
1775        match value {
1776            PySetterValue::Assign(ref val) => {
1777                let key = identifier!(vm, __type_params__);
1778                self.check_set_special_type_attr(key, vm)?;
1779                self.modified();
1780                self.attributes.write().insert(key, val.clone().into());
1781            }
1782            PySetterValue::Delete => {
1783                // For delete, we still need to check if the type is immutable
1784                if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
1785                    return Err(vm.new_type_error(format!(
1786                        "cannot delete '__type_params__' attribute of immutable type '{}'",
1787                        self.slot_name()
1788                    )));
1789                }
1790                let key = identifier!(vm, __type_params__);
1791                self.modified();
1792                self.attributes.write().shift_remove(&key);
1793            }
1794        }
1795        Ok(())
1796    }
1797}
1798
1799impl Constructor for PyType {
1800    type Args = FuncArgs;
1801
1802    fn slot_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1803        vm_trace!("type.__new__ {:?}", args);
1804
1805        let is_type_type = metatype.is(vm.ctx.types.type_type);
1806        if is_type_type && args.args.len() == 1 && args.kwargs.is_empty() {
1807            return Ok(args.args[0].class().to_owned().into());
1808        }
1809
1810        if args.args.len() != 3 {
1811            return Err(vm.new_type_error(if is_type_type {
1812                "type() takes 1 or 3 arguments".to_owned()
1813            } else {
1814                format!(
1815                    "type.__new__() takes exactly 3 arguments ({} given)",
1816                    args.args.len()
1817                )
1818            }));
1819        }
1820
1821        let (name, bases, dict, kwargs): (PyStrRef, PyTupleRef, PyDictRef, KwArgs) =
1822            args.clone().bind(vm)?;
1823
1824        if name.as_bytes().contains(&0) {
1825            return Err(vm.new_value_error("type name must not contain null characters"));
1826        }
1827        let name = name.try_into_utf8(vm)?;
1828
1829        let (metatype, base, bases, base_is_type) = if bases.is_empty() {
1830            let base = vm.ctx.types.object_type.to_owned();
1831            (metatype, base.clone(), vec![base], false)
1832        } else {
1833            let bases = bases
1834                .iter()
1835                .map(|obj| {
1836                    obj.clone().downcast::<Self>().or_else(|obj| {
1837                        if vm
1838                            .get_attribute_opt(obj, identifier!(vm, __mro_entries__))?
1839                            .is_some()
1840                        {
1841                            Err(vm.new_type_error(
1842                                "type() doesn't support MRO entry resolution; \
1843                                 use types.new_class()",
1844                            ))
1845                        } else {
1846                            Err(vm.new_type_error("bases must be types"))
1847                        }
1848                    })
1849                })
1850                .collect::<PyResult<Vec<_>>>()?;
1851
1852            // Search the bases for the proper metatype to deal with this:
1853            let winner = calculate_meta_class(metatype.clone(), &bases, vm)?;
1854            let metatype = if !winner.is(&metatype) {
1855                if let Some(ref slot_new) = winner.slots.new.load() {
1856                    // Pass it to the winner
1857                    return slot_new(winner, args, vm);
1858                }
1859                winner
1860            } else {
1861                metatype
1862            };
1863
1864            let base = best_base(&bases, vm)?;
1865            let base_is_type = base.is(vm.ctx.types.type_type);
1866
1867            (metatype, base.to_owned(), bases, base_is_type)
1868        };
1869
1870        let qualname = dict
1871            .get_item_opt(identifier!(vm, __qualname__), vm)?
1872            .map(|obj| downcast_qualname(obj, vm))
1873            .transpose()?
1874            .unwrap_or_else(|| {
1875                // If __qualname__ is not provided, we can use the name as default
1876                name.clone().into_wtf8()
1877            });
1878
1879        let mut attributes = dict.to_attributes(vm);
1880        attributes.shift_remove(identifier!(vm, __qualname__));
1881
1882        // Check __doc__ for surrogates - raises UnicodeEncodeError during type creation
1883        if let Some(doc) = attributes.get(identifier!(vm, __doc__))
1884            && let Some(doc_str) = doc.downcast_ref::<PyStr>()
1885        {
1886            doc_str.ensure_valid_utf8(vm)?;
1887        }
1888
1889        if let Some(f) = attributes.get_mut(identifier!(vm, __init_subclass__))
1890            && f.class().is(vm.ctx.types.function_type)
1891        {
1892            *f = PyClassMethod::from(f.clone()).into_pyobject(vm);
1893        }
1894
1895        if let Some(f) = attributes.get_mut(identifier!(vm, __class_getitem__))
1896            && f.class().is(vm.ctx.types.function_type)
1897        {
1898            *f = PyClassMethod::from(f.clone()).into_pyobject(vm);
1899        }
1900
1901        if let Some(f) = attributes.get_mut(identifier!(vm, __new__))
1902            && f.class().is(vm.ctx.types.function_type)
1903        {
1904            *f = PyStaticMethod::from(f.clone()).into_pyobject(vm);
1905        }
1906
1907        if let Some(current_frame) = vm.current_frame() {
1908            let entry = attributes.entry(identifier!(vm, __module__));
1909            if matches!(entry, Entry::Vacant(_)) {
1910                let module_name = vm.unwrap_or_none(
1911                    current_frame
1912                        .globals
1913                        .get_item_opt(identifier!(vm, __name__), vm)?,
1914                );
1915                entry.or_insert(module_name);
1916            }
1917        }
1918
1919        if attributes.get(identifier!(vm, __eq__)).is_some()
1920            && attributes.get(identifier!(vm, __hash__)).is_none()
1921        {
1922            // if __eq__ exists but __hash__ doesn't, overwrite it with None so it doesn't inherit the default hash
1923            // https://docs.python.org/3/reference/datamodel.html#object.__hash__
1924            attributes.insert(identifier!(vm, __hash__), vm.ctx.none.clone().into());
1925        }
1926
1927        let (heaptype_slots, add_dict, add_weakref): (
1928            Option<PyRef<PyTuple<PyStrRef>>>,
1929            bool,
1930            bool,
1931        ) = if let Some(x) = attributes.get(identifier!(vm, __slots__)) {
1932            // Check if __slots__ is bytes - not allowed
1933            if x.class().is(vm.ctx.types.bytes_type) {
1934                return Err(vm.new_type_error("__slots__ items must be strings, not 'bytes'"));
1935            }
1936
1937            let slots = if x.class().is(vm.ctx.types.str_type) {
1938                let x = unsafe { x.downcast_unchecked_ref::<PyStr>() };
1939                PyTuple::new_ref_typed(vec![x.to_owned()], &vm.ctx)
1940            } else {
1941                let iter = x.get_iter(vm)?;
1942                let elements = {
1943                    let mut elements = Vec::new();
1944                    while let PyIterReturn::Return(element) = iter.next(vm)? {
1945                        // Check if any slot item is bytes
1946                        if element.class().is(vm.ctx.types.bytes_type) {
1947                            return Err(
1948                                vm.new_type_error("__slots__ items must be strings, not 'bytes'")
1949                            );
1950                        }
1951                        elements.push(element);
1952                    }
1953                    elements
1954                };
1955                let tuple = elements.into_pytuple(vm);
1956                tuple.try_into_typed(vm)?
1957            };
1958
1959            // Check if base has itemsize > 0 - can't add arbitrary slots to variable-size types
1960            // Types like int, bytes, tuple have itemsize > 0 and don't allow custom slots
1961            // But types like weakref.ref have itemsize = 0 and DO allow slots
1962            let has_custom_slots = slots
1963                .iter()
1964                .any(|s| !matches!(s.as_bytes(), b"__dict__" | b"__weakref__"));
1965            if has_custom_slots && base.slots.itemsize > 0 {
1966                return Err(vm.new_type_error(format!(
1967                    "nonempty __slots__ not supported for subtype of '{}'",
1968                    base.name()
1969                )));
1970            }
1971
1972            // Validate slot names and track duplicates
1973            let mut seen_dict = false;
1974            let mut seen_weakref = false;
1975            for slot in slots.iter() {
1976                // Use isidentifier for validation (handles Unicode properly)
1977                if !slot.isidentifier() {
1978                    return Err(vm.new_type_error("__slots__ must be identifiers"));
1979                }
1980
1981                let slot_name = slot.as_bytes();
1982
1983                // Check for duplicate __dict__
1984                if slot_name == b"__dict__" {
1985                    if seen_dict {
1986                        return Err(
1987                            vm.new_type_error("__dict__ slot disallowed: we already got one")
1988                        );
1989                    }
1990                    seen_dict = true;
1991                }
1992
1993                // Check for duplicate __weakref__
1994                if slot_name == b"__weakref__" {
1995                    if seen_weakref {
1996                        return Err(
1997                            vm.new_type_error("__weakref__ slot disallowed: we already got one")
1998                        );
1999                    }
2000                    seen_weakref = true;
2001                }
2002
2003                // Check if slot name conflicts with class attributes
2004                if attributes.contains_key(vm.ctx.intern_str(slot.as_wtf8())) {
2005                    return Err(vm.new_value_error(format!(
2006                        "'{}' in __slots__ conflicts with a class variable",
2007                        slot.as_wtf8()
2008                    )));
2009                }
2010            }
2011
2012            // Check if base class already has __dict__ - can't redefine it
2013            if seen_dict && base.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
2014                return Err(vm.new_type_error("__dict__ slot disallowed: we already got one"));
2015            }
2016
2017            // Check if base class already has __weakref__ - can't redefine it
2018            if seen_weakref && base.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF) {
2019                return Err(vm.new_type_error("__weakref__ slot disallowed: we already got one"));
2020            }
2021
2022            // Check if __dict__ or __weakref__ is in slots
2023            let dict_name = "__dict__";
2024            let weakref_name = "__weakref__";
2025            let has_dict = slots.iter().any(|s| s.as_wtf8() == dict_name);
2026            let add_weakref = seen_weakref;
2027
2028            // Filter out __dict__ and __weakref__ from slots
2029            // (they become descriptors, not member slots)
2030            let filtered_slots = if has_dict || add_weakref {
2031                let filtered: Vec<PyStrRef> = slots
2032                    .iter()
2033                    .filter(|s| s.as_wtf8() != dict_name && s.as_wtf8() != weakref_name)
2034                    .cloned()
2035                    .collect();
2036                PyTuple::new_ref_typed(filtered, &vm.ctx)
2037            } else {
2038                slots
2039            };
2040
2041            (Some(filtered_slots), has_dict, add_weakref)
2042        } else {
2043            (None, false, false)
2044        };
2045
2046        // FIXME: this is a temporary fix. multi bases with multiple slots will break object
2047        let base_member_count = bases
2048            .iter()
2049            .map(|base| base.slots.member_count)
2050            .max()
2051            .unwrap();
2052        let heaptype_member_count = heaptype_slots.as_ref().map(|x| x.len()).unwrap_or(0);
2053        let member_count: usize = base_member_count + heaptype_member_count;
2054
2055        let mut flags = PyTypeFlags::heap_type_flags();
2056
2057        // Check if we may add dict
2058        // We can only add a dict if the primary base class doesn't already have one
2059        // In CPython, this checks tp_dictoffset == 0
2060        let may_add_dict = !base.slots.flags.has_feature(PyTypeFlags::HAS_DICT);
2061
2062        // Add HAS_DICT and MANAGED_DICT if:
2063        // 1. __slots__ is not defined AND base doesn't have dict, OR
2064        // 2. __dict__ is in __slots__
2065        if (heaptype_slots.is_none() && may_add_dict) || add_dict {
2066            flags |= PyTypeFlags::HAS_DICT | PyTypeFlags::MANAGED_DICT;
2067        }
2068
2069        // Add HAS_WEAKREF if:
2070        // 1. __slots__ is not defined (automatic weakref support), OR
2071        // 2. __weakref__ is in __slots__
2072        let may_add_weakref = !base.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF);
2073        if (heaptype_slots.is_none() && may_add_weakref) || add_weakref {
2074            flags |= PyTypeFlags::HAS_WEAKREF | PyTypeFlags::MANAGED_WEAKREF;
2075        }
2076
2077        let (slots, heaptype_ext) = {
2078            let slots = PyTypeSlots {
2079                flags,
2080                member_count,
2081                itemsize: base.slots.itemsize,
2082                ..PyTypeSlots::heap_default()
2083            };
2084            let heaptype_ext = HeapTypeExt {
2085                name: PyRwLock::new(name),
2086                qualname: PyRwLock::new(qualname),
2087                slots: heaptype_slots.clone(),
2088                type_data: PyRwLock::new(None),
2089                specialization_cache: TypeSpecializationCache::new(),
2090            };
2091            (slots, heaptype_ext)
2092        };
2093
2094        let typ = Self::new_heap_inner(
2095            base,
2096            bases,
2097            attributes,
2098            slots,
2099            heaptype_ext,
2100            metatype,
2101            &vm.ctx,
2102        )
2103        .map_err(|e| vm.new_type_error(e))?;
2104
2105        if let Some(ref slots) = heaptype_slots {
2106            let mut offset = base_member_count;
2107            let class_name = typ.name().to_string();
2108            for member in slots.as_slice() {
2109                // Apply name mangling for private attributes (__x -> _ClassName__x)
2110                let member_str = member
2111                    .to_str()
2112                    .ok_or_else(|| vm.new_type_error("__slots__ must be valid UTF-8 strings"))?;
2113                let mangled_name = mangle_name(&class_name, member_str);
2114                let member_def = PyMemberDef {
2115                    name: mangled_name.clone(),
2116                    kind: MemberKind::ObjectEx,
2117                    getter: MemberGetter::Offset(offset),
2118                    setter: MemberSetter::Offset(offset),
2119                    doc: None,
2120                };
2121                let attr_name = vm.ctx.intern_str(mangled_name.as_str());
2122                let member_descriptor: PyRef<PyMemberDescriptor> =
2123                    vm.ctx.new_pyref(PyMemberDescriptor {
2124                        common: PyDescriptorOwned {
2125                            typ: typ.clone(),
2126                            name: attr_name,
2127                            qualname: PyRwLock::new(None),
2128                        },
2129                        member: member_def,
2130                    });
2131                // __slots__ attributes always get a member descriptor
2132                // (this overrides any inherited attribute from MRO)
2133                typ.set_attr(attr_name, member_descriptor.into());
2134                offset += 1;
2135            }
2136        }
2137
2138        {
2139            let mut attrs = typ.attributes.write();
2140            if let Some(cell) = attrs.get(identifier!(vm, __classcell__)) {
2141                let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
2142                    vm.new_type_error(format!(
2143                        "__classcell__ must be a nonlocal cell, not {}",
2144                        cell.class().name()
2145                    ))
2146                })?;
2147                cell.set(Some(typ.clone().into()));
2148                attrs.shift_remove(identifier!(vm, __classcell__));
2149            }
2150            if let Some(cell) = attrs.get(identifier!(vm, __classdictcell__)) {
2151                let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
2152                    vm.new_type_error(format!(
2153                        "__classdictcell__ must be a nonlocal cell, not {}",
2154                        cell.class().name()
2155                    ))
2156                })?;
2157                cell.set(Some(dict.clone().into()));
2158                attrs.shift_remove(identifier!(vm, __classdictcell__));
2159            }
2160        }
2161
2162        // All *classes* should have a dict. Exceptions are *instances* of
2163        // classes that define __slots__ and instances of built-in classes
2164        // (with exceptions, e.g function)
2165        // Also, type subclasses don't need their own __dict__ descriptor
2166        // since they inherit it from type
2167
2168        // Add __dict__ descriptor after type creation to ensure correct __objclass__
2169        // Only add if:
2170        // 1. base is not type (type subclasses inherit __dict__ from type)
2171        // 2. the class has HAS_DICT flag (i.e., __slots__ was not defined or __dict__ is in __slots__)
2172        // 3. no base class in MRO already provides __dict__ descriptor
2173        if !base_is_type && typ.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
2174            let __dict__ = identifier!(vm, __dict__);
2175            let has_inherited_dict = typ
2176                .mro
2177                .read()
2178                .iter()
2179                .any(|base| base.attributes.read().contains_key(&__dict__));
2180            if !typ.attributes.read().contains_key(&__dict__) && !has_inherited_dict {
2181                unsafe {
2182                    let descriptor =
2183                        vm.ctx
2184                            .new_getset("__dict__", &typ, subtype_get_dict, subtype_set_dict);
2185                    typ.attributes.write().insert(__dict__, descriptor.into());
2186                }
2187            }
2188        }
2189
2190        // Add __weakref__ descriptor for types with HAS_WEAKREF
2191        if typ.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF) {
2192            let __weakref__ = vm.ctx.intern_str("__weakref__");
2193            let has_inherited_weakref = typ
2194                .mro
2195                .read()
2196                .iter()
2197                .any(|base| base.attributes.read().contains_key(&__weakref__));
2198            if !typ.attributes.read().contains_key(&__weakref__) && !has_inherited_weakref {
2199                unsafe {
2200                    let descriptor = vm.ctx.new_getset(
2201                        "__weakref__",
2202                        &typ,
2203                        subtype_get_weakref,
2204                        subtype_set_weakref,
2205                    );
2206                    typ.attributes
2207                        .write()
2208                        .insert(__weakref__, descriptor.into());
2209                }
2210            }
2211        }
2212
2213        // Set __doc__ to None if not already present in the type's dict
2214        // This matches CPython's behavior in type_dict_set_doc (typeobject.c)
2215        // which ensures every type has a __doc__ entry in its dict
2216        {
2217            let __doc__ = identifier!(vm, __doc__);
2218            if !typ.attributes.read().contains_key(&__doc__) {
2219                typ.attributes.write().insert(__doc__, vm.ctx.none());
2220            }
2221        }
2222
2223        // avoid deadlock
2224        let attributes = typ
2225            .attributes
2226            .read()
2227            .iter()
2228            .filter_map(|(name, obj)| {
2229                vm.get_method(obj.clone(), identifier!(vm, __set_name__))
2230                    .map(|res| res.map(|meth| (obj.clone(), name.to_owned(), meth)))
2231            })
2232            .collect::<PyResult<Vec<_>>>()?;
2233        for (obj, name, set_name) in attributes {
2234            set_name.call((typ.clone(), name), vm).inspect_err(|e| {
2235                // PEP 678: Add a note to the original exception instead of wrapping it
2236                // (Python 3.12+, gh-77757)
2237                let note = format!(
2238                    "Error calling __set_name__ on '{}' instance '{}' in '{}'",
2239                    obj.class().name(),
2240                    name,
2241                    typ.name()
2242                );
2243                // Ignore result - adding a note is best-effort, the original exception is what matters
2244                drop(vm.call_method(e.as_object(), "add_note", (vm.ctx.new_str(note.as_str()),)));
2245            })?;
2246        }
2247
2248        if let Some(init_subclass) = typ.get_super_attr(identifier!(vm, __init_subclass__)) {
2249            let init_subclass = vm
2250                .call_get_descriptor_specific(&init_subclass, None, Some(typ.clone().into()))
2251                .unwrap_or(Ok(init_subclass))?;
2252            init_subclass.call(kwargs, vm)?;
2253        };
2254
2255        Ok(typ.into())
2256    }
2257
2258    fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
2259        unimplemented!("use slot_new")
2260    }
2261}
2262
2263const SIGNATURE_END_MARKER: &str = ")\n--\n\n";
2264fn get_signature(doc: &str) -> Option<&str> {
2265    doc.find(SIGNATURE_END_MARKER).map(|index| &doc[..=index])
2266}
2267
2268fn find_signature<'a>(name: &str, doc: &'a str) -> Option<&'a str> {
2269    let name = name.rsplit('.').next().unwrap();
2270    let doc = doc.strip_prefix(name)?;
2271    doc.starts_with('(').then_some(doc)
2272}
2273
2274pub(crate) fn get_text_signature_from_internal_doc<'a>(
2275    name: &str,
2276    internal_doc: &'a str,
2277) -> Option<&'a str> {
2278    find_signature(name, internal_doc).and_then(get_signature)
2279}
2280
2281// _PyType_GetDocFromInternalDoc in CPython
2282fn get_doc_from_internal_doc<'a>(name: &str, internal_doc: &'a str) -> &'a str {
2283    // Similar to CPython's _PyType_DocWithoutSignature
2284    // If the doc starts with the type name and a '(', it's a signature
2285    if let Some(doc_without_sig) = find_signature(name, internal_doc) {
2286        // Find where the signature ends
2287        if let Some(sig_end_pos) = doc_without_sig.find(SIGNATURE_END_MARKER) {
2288            let after_sig = &doc_without_sig[sig_end_pos + SIGNATURE_END_MARKER.len()..];
2289            // Return the documentation after the signature, or empty string if none
2290            return after_sig;
2291        }
2292    }
2293    // If no signature found, return the whole doc
2294    internal_doc
2295}
2296
2297impl Initializer for PyType {
2298    type Args = FuncArgs;
2299
2300    // type_init
2301    fn slot_init(_zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
2302        // type.__init__() takes 1 or 3 arguments
2303        if args.args.len() == 1 && !args.kwargs.is_empty() {
2304            return Err(vm.new_type_error("type.__init__() takes no keyword arguments"));
2305        }
2306        if args.args.len() != 1 && args.args.len() != 3 {
2307            return Err(vm.new_type_error("type.__init__() takes 1 or 3 arguments"));
2308        }
2309        Ok(())
2310    }
2311
2312    fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
2313        unreachable!("slot_init is defined")
2314    }
2315}
2316
2317impl GetAttr for PyType {
2318    fn getattro(zelf: &Py<Self>, name_str: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
2319        #[cold]
2320        fn attribute_error(
2321            zelf: &Py<PyType>,
2322            name: &Wtf8,
2323            vm: &VirtualMachine,
2324        ) -> PyBaseExceptionRef {
2325            vm.new_attribute_error(format!(
2326                "type object '{}' has no attribute '{}'",
2327                zelf.slot_name(),
2328                name,
2329            ))
2330        }
2331
2332        let Some(name) = vm.ctx.interned_str(name_str) else {
2333            return Err(attribute_error(zelf, name_str.as_wtf8(), vm));
2334        };
2335        vm_trace!("type.__getattribute__({:?}, {:?})", zelf, name);
2336        let mcl = zelf.class();
2337        let mcl_attr = mcl.get_attr(name);
2338
2339        if let Some(ref attr) = mcl_attr {
2340            let attr_class = attr.class();
2341            let has_descr_set = attr_class.slots.descr_set.load().is_some();
2342            if has_descr_set {
2343                let descr_get = attr_class.slots.descr_get.load();
2344                if let Some(descr_get) = descr_get {
2345                    let mcl = mcl.to_owned().into();
2346                    return descr_get(attr.clone(), Some(zelf.to_owned().into()), Some(mcl), vm);
2347                }
2348            }
2349        }
2350
2351        let zelf_attr = zelf.get_attr(name);
2352
2353        if let Some(attr) = zelf_attr {
2354            let descr_get = attr.class().slots.descr_get.load();
2355            if let Some(descr_get) = descr_get {
2356                descr_get(attr, None, Some(zelf.to_owned().into()), vm)
2357            } else {
2358                Ok(attr)
2359            }
2360        } else if let Some(attr) = mcl_attr {
2361            vm.call_if_get_descriptor(&attr, zelf.to_owned().into())
2362        } else {
2363            Err(attribute_error(zelf, name_str.as_wtf8(), vm))
2364        }
2365    }
2366}
2367
2368#[pyclass]
2369impl Py<PyType> {
2370    #[pygetset]
2371    fn __mro__(&self) -> PyTuple {
2372        let elements: Vec<PyObjectRef> = self.mro_map_collect(|x| x.as_object().to_owned());
2373        PyTuple::new_unchecked(elements.into_boxed_slice())
2374    }
2375
2376    #[pygetset]
2377    fn __doc__(&self, vm: &VirtualMachine) -> PyResult {
2378        // Similar to CPython's type_get_doc
2379        // For non-heap types (static types), check if there's an internal doc
2380        if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
2381            && let Some(internal_doc) = self.slots.doc
2382        {
2383            // Process internal doc, removing signature if present
2384            let doc_str = get_doc_from_internal_doc(&self.name(), internal_doc);
2385            return Ok(vm.ctx.new_str(doc_str).into());
2386        }
2387
2388        // Check if there's a __doc__ in THIS type's dict only (not MRO)
2389        // CPython returns None if __doc__ is not in the type's own dict
2390        if let Some(doc_attr) = self.get_direct_attr(vm.ctx.intern_str("__doc__")) {
2391            // If it's a descriptor, call its __get__ method
2392            let descr_get = doc_attr.class().slots.descr_get.load();
2393            if let Some(descr_get) = descr_get {
2394                descr_get(doc_attr, None, Some(self.to_owned().into()), vm)
2395            } else {
2396                Ok(doc_attr)
2397            }
2398        } else {
2399            Ok(vm.ctx.none())
2400        }
2401    }
2402
2403    #[pygetset(setter)]
2404    fn set___doc__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
2405        // Similar to CPython's type_set_doc
2406        let value = value.ok_or_else(|| {
2407            vm.new_type_error(format!(
2408                "cannot delete '__doc__' attribute of type '{}'",
2409                self.name()
2410            ))
2411        })?;
2412
2413        // Check if we can set this special type attribute
2414        self.check_set_special_type_attr(identifier!(vm, __doc__), vm)?;
2415
2416        // Set the __doc__ in the type's dict
2417        self.attributes
2418            .write()
2419            .insert(identifier!(vm, __doc__), value);
2420
2421        Ok(())
2422    }
2423
2424    #[pymethod]
2425    fn __dir__(&self) -> PyList {
2426        let attributes: Vec<PyObjectRef> = self
2427            .get_attributes()
2428            .into_iter()
2429            .map(|(k, _)| k.to_object())
2430            .collect();
2431        PyList::from(attributes)
2432    }
2433
2434    #[pymethod]
2435    fn __instancecheck__(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
2436        // Use real_is_instance to avoid infinite recursion
2437        obj.real_is_instance(self.as_object(), vm)
2438    }
2439
2440    #[pymethod]
2441    fn __subclasscheck__(&self, subclass: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
2442        // Use real_is_subclass to avoid going through __subclasscheck__ recursion
2443        // This matches CPython's type___subclasscheck___impl which calls _PyObject_RealIsSubclass
2444        subclass.real_is_subclass(self.as_object(), vm)
2445    }
2446
2447    #[pyclassmethod]
2448    fn __subclasshook__(_args: FuncArgs, vm: &VirtualMachine) -> PyObjectRef {
2449        vm.ctx.not_implemented()
2450    }
2451
2452    #[pymethod]
2453    fn mro(&self) -> Vec<PyObjectRef> {
2454        self.mro_map_collect(|cls| cls.to_owned().into())
2455    }
2456}
2457
2458impl SetAttr for PyType {
2459    fn setattro(
2460        zelf: &Py<Self>,
2461        attr_name: &Py<PyStr>,
2462        value: PySetterValue,
2463        vm: &VirtualMachine,
2464    ) -> PyResult<()> {
2465        let attr_name = vm.ctx.intern_str(attr_name.as_wtf8());
2466        if zelf.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
2467            return Err(vm.new_type_error(format!(
2468                "cannot set '{}' attribute of immutable type '{}'",
2469                attr_name,
2470                zelf.slot_name()
2471            )));
2472        }
2473        if let Some(attr) = zelf.get_class_attr(attr_name) {
2474            let descr_set = attr.class().slots.descr_set.load();
2475            if let Some(descriptor) = descr_set {
2476                return descriptor(&attr, zelf.to_owned().into(), value, vm);
2477            }
2478        }
2479        let assign = value.is_assign();
2480
2481        // Invalidate inline caches before modifying attributes.
2482        // This ensures other threads see the version invalidation before
2483        // any attribute changes, preventing use-after-free of cached descriptors.
2484        zelf.modified();
2485
2486        if let PySetterValue::Assign(value) = value {
2487            zelf.attributes.write().insert(attr_name, value);
2488        } else {
2489            let prev_value = zelf.attributes.write().shift_remove(attr_name); // TODO: swap_remove applicable?
2490            if prev_value.is_none() {
2491                return Err(vm.new_attribute_error(format!(
2492                    "type object '{}' has no attribute '{}'",
2493                    zelf.name(),
2494                    attr_name,
2495                )));
2496            }
2497        }
2498
2499        if attr_name.as_wtf8().starts_with("__") && attr_name.as_wtf8().ends_with("__") {
2500            if assign {
2501                zelf.update_slot::<true>(attr_name, &vm.ctx);
2502            } else {
2503                zelf.update_slot::<false>(attr_name, &vm.ctx);
2504            }
2505        }
2506        Ok(())
2507    }
2508}
2509
2510impl Callable for PyType {
2511    type Args = FuncArgs;
2512    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
2513        vm_trace!("type_call: {:?}", zelf);
2514
2515        if zelf.is(vm.ctx.types.type_type) {
2516            let num_args = args.args.len();
2517            if num_args == 1 && args.kwargs.is_empty() {
2518                return Ok(args.args[0].obj_type());
2519            }
2520            if num_args != 3 {
2521                return Err(vm.new_type_error("type() takes 1 or 3 arguments"));
2522            }
2523        }
2524
2525        let obj = if let Some(slot_new) = zelf.slots.new.load() {
2526            slot_new(zelf.to_owned(), args.clone(), vm)?
2527        } else {
2528            return Err(vm.new_type_error(format!("cannot create '{}' instances", zelf.slots.name)));
2529        };
2530
2531        if !obj.class().fast_issubclass(zelf) {
2532            return Ok(obj);
2533        }
2534
2535        if let Some(init_method) = obj.class().slots.init.load() {
2536            init_method(obj.clone(), args, vm)?;
2537        }
2538        Ok(obj)
2539    }
2540}
2541
2542impl AsNumber for PyType {
2543    fn as_number() -> &'static PyNumberMethods {
2544        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
2545            or: Some(|a, b, vm| or_(a.to_owned(), b.to_owned(), vm)),
2546            ..PyNumberMethods::NOT_IMPLEMENTED
2547        };
2548        &AS_NUMBER
2549    }
2550}
2551
2552impl Representable for PyType {
2553    #[inline]
2554    fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
2555        let module = zelf.__module__(vm);
2556        let module = module.downcast_ref::<PyStr>().map(|m| m.as_wtf8());
2557
2558        let repr = match module {
2559            Some(module) if module != "builtins" => {
2560                let qualname = zelf.__qualname__(vm);
2561                let qualname = qualname.downcast_ref::<PyStr>().map(|n| n.as_wtf8());
2562                let name = zelf.name();
2563                let qualname = qualname.unwrap_or_else(|| name.as_ref());
2564                format!("<class '{module}.{qualname}'>")
2565            }
2566            _ => format!("<class '{}'>", zelf.slot_name()),
2567        };
2568        Ok(repr)
2569    }
2570}
2571
2572// = get_builtin_base_with_dict
2573fn get_builtin_base_with_dict(typ: &Py<PyType>, vm: &VirtualMachine) -> Option<PyTypeRef> {
2574    let mut current = Some(typ.to_owned());
2575    while let Some(t) = current {
2576        // In CPython: type->tp_dictoffset != 0 && !(type->tp_flags & Py_TPFLAGS_HEAPTYPE)
2577        // Special case: type itself is a builtin with dict support
2578        if t.is(vm.ctx.types.type_type) {
2579            return Some(t);
2580        }
2581        // We check HAS_DICT flag (equivalent to tp_dictoffset != 0) and HEAPTYPE
2582        if t.slots.flags.contains(PyTypeFlags::HAS_DICT)
2583            && !t.slots.flags.contains(PyTypeFlags::HEAPTYPE)
2584        {
2585            return Some(t);
2586        }
2587        current = t.__base__();
2588    }
2589    None
2590}
2591
2592// = get_dict_descriptor
2593fn get_dict_descriptor(base: &Py<PyType>, vm: &VirtualMachine) -> Option<PyObjectRef> {
2594    let dict_attr = identifier!(vm, __dict__);
2595    // Use _PyType_Lookup (which is lookup_ref in RustPython)
2596    base.lookup_ref(dict_attr, vm)
2597}
2598
2599// = raise_dict_descr_error
2600fn raise_dict_descriptor_error(obj: &PyObject, vm: &VirtualMachine) -> PyBaseExceptionRef {
2601    vm.new_type_error(format!(
2602        "this __dict__ descriptor does not support '{}' objects",
2603        obj.class().name()
2604    ))
2605}
2606
2607fn subtype_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
2608    let base = get_builtin_base_with_dict(obj.class(), vm);
2609
2610    if let Some(base_type) = base {
2611        if let Some(descr) = get_dict_descriptor(&base_type, vm) {
2612            // Call the descriptor's tp_descr_get
2613            vm.call_get_descriptor(&descr, obj.clone())
2614                .unwrap_or_else(|| Err(raise_dict_descriptor_error(&obj, vm)))
2615        } else {
2616            Err(raise_dict_descriptor_error(&obj, vm))
2617        }
2618    } else {
2619        // PyObject_GenericGetDict
2620        object::object_get_dict(obj, vm).map(Into::into)
2621    }
2622}
2623
2624// = subtype_setdict
2625fn subtype_set_dict(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
2626    let base = get_builtin_base_with_dict(obj.class(), vm);
2627
2628    if let Some(base_type) = base {
2629        if let Some(descr) = get_dict_descriptor(&base_type, vm) {
2630            // Call the descriptor's tp_descr_set
2631            let descr_set = descr
2632                .class()
2633                .slots
2634                .descr_set
2635                .load()
2636                .ok_or_else(|| raise_dict_descriptor_error(&obj, vm))?;
2637            descr_set(&descr, obj, PySetterValue::Assign(value), vm)
2638        } else {
2639            Err(raise_dict_descriptor_error(&obj, vm))
2640        }
2641    } else {
2642        // PyObject_GenericSetDict
2643        object::object_set_dict(obj, value.try_into_value(vm)?, vm)?;
2644        Ok(())
2645    }
2646}
2647
2648// subtype_get_weakref
2649fn subtype_get_weakref(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
2650    // Return the first weakref in the weakref list, or None
2651    let weakref = obj.get_weakrefs();
2652    Ok(weakref.unwrap_or_else(|| vm.ctx.none()))
2653}
2654
2655// subtype_set_weakref: __weakref__ is read-only
2656fn subtype_set_weakref(obj: PyObjectRef, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
2657    Err(vm.new_attribute_error(format!(
2658        "attribute '__weakref__' of '{}' objects is not writable",
2659        obj.class().name()
2660    )))
2661}
2662
2663/*
2664 * The magical type type
2665 */
2666
2667/// Vectorcall for PyType (PEP 590).
2668/// Fast path: type(x) returns x.__class__ without constructing FuncArgs.
2669///
2670/// # Implementation note: `slots.vectorcall` dual use
2671///
2672/// CPython has three distinct fields on PyTypeObject:
2673///   - `tp_vectorcall`: constructor fast path (e.g. `list_vectorcall`)
2674///   - `tp_vectorcall_offset`: per-instance vectorcall for callables (e.g. functions)
2675///   - `tp_call`: standard call slot
2676///
2677/// RustPython collapses the first two into a single `slots.vectorcall`. The
2678/// `call.is_none()` guard below distinguishes the two uses: callable types have
2679/// `slots.call` set, so their `slots.vectorcall` is for calling instances,
2680/// not for construction.
2681///
2682/// This heuristic is correct for all current builtins but is not a general
2683/// solution — `type` itself is both callable and has a constructor vectorcall,
2684/// handled by the explicit `zelf.is(type_type)` check above the guard.
2685/// If more such types arise, consider splitting into a dedicated
2686/// `constructor_vectorcall` slot.
2687fn vectorcall_type(
2688    zelf_obj: &PyObject,
2689    args: Vec<PyObjectRef>,
2690    nargs: usize,
2691    kwnames: Option<&[PyObjectRef]>,
2692    vm: &VirtualMachine,
2693) -> PyResult {
2694    let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
2695
2696    // type(x) fast path: single positional arg, no kwargs
2697    if zelf.is(vm.ctx.types.type_type) {
2698        let no_kwargs = kwnames.is_none_or(|kw| kw.is_empty());
2699        if nargs == 1 && no_kwargs {
2700            return Ok(args[0].obj_type());
2701        }
2702    } else if zelf.slots.call.load().is_none() && zelf.slots.new.load().is_some() {
2703        // Per-type constructor vectorcall for non-callable types (dict, list, int, etc.)
2704        // Also guard on slots.new to avoid dispatching for DISALLOW_INSTANTIATION types.
2705        if let Some(type_vc) = zelf.slots.vectorcall.load() {
2706            return type_vc(zelf_obj, args, nargs, kwnames, vm);
2707        }
2708    }
2709
2710    // Fallback: construct FuncArgs and use standard call
2711    let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
2712    PyType::call(zelf, func_args, vm)
2713}
2714
2715pub(crate) fn init(ctx: &'static Context) {
2716    PyType::extend_class(ctx, ctx.types.type_type);
2717    ctx.types
2718        .type_type
2719        .slots
2720        .vectorcall
2721        .store(Some(vectorcall_type));
2722}
2723
2724pub(crate) fn call_slot_new(
2725    typ: PyTypeRef,
2726    subtype: PyTypeRef,
2727    args: FuncArgs,
2728    vm: &VirtualMachine,
2729) -> PyResult {
2730    // Check DISALLOW_INSTANTIATION flag on subtype (the type being instantiated)
2731    if subtype
2732        .slots
2733        .flags
2734        .has_feature(PyTypeFlags::DISALLOW_INSTANTIATION)
2735    {
2736        return Err(vm.new_type_error(format!("cannot create '{}' instances", subtype.slot_name())));
2737    }
2738
2739    // "is not safe" check (tp_new_wrapper logic)
2740    // Check that the user doesn't do something silly and unsafe like
2741    // object.__new__(dict). To do this, we check that the most derived base
2742    // that's not a heap type is this type.
2743    let mut staticbase = subtype.clone();
2744    while staticbase.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
2745        if let Some(base) = staticbase.base.as_ref() {
2746            staticbase = base.clone();
2747        } else {
2748            break;
2749        }
2750    }
2751
2752    // Check if staticbase's tp_new differs from typ's tp_new
2753    let typ_new = typ.slots.new.load();
2754    let staticbase_new = staticbase.slots.new.load();
2755    if typ_new.map(|f| f as usize) != staticbase_new.map(|f| f as usize) {
2756        return Err(vm.new_type_error(format!(
2757            "{}.__new__({}) is not safe, use {}.__new__()",
2758            typ.slot_name(),
2759            subtype.slot_name(),
2760            staticbase.slot_name()
2761        )));
2762    }
2763
2764    let slot_new = typ
2765        .slots
2766        .new
2767        .load()
2768        .expect("Should be able to find a new slot somewhere in the mro");
2769    slot_new(subtype, args, vm)
2770}
2771
2772pub(crate) fn or_(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
2773    union_::or_op(zelf, other, vm)
2774}
2775
2776fn take_next_base(bases: &mut [Vec<PyTypeRef>]) -> Option<PyTypeRef> {
2777    for base in bases.iter() {
2778        let head = base[0].clone();
2779        if !bases.iter().any(|x| x[1..].iter().any(|x| x.is(&head))) {
2780            // Remove from other heads.
2781            for item in bases.iter_mut() {
2782                if item[0].is(&head) {
2783                    item.remove(0);
2784                }
2785            }
2786
2787            return Some(head);
2788        }
2789    }
2790
2791    None
2792}
2793
2794fn linearise_mro(mut bases: Vec<Vec<PyTypeRef>>) -> Result<Vec<PyTypeRef>, String> {
2795    vm_trace!("Linearise MRO: {:?}", bases);
2796    // Python requires that the class direct bases are kept in the same order.
2797    // This is called local precedence ordering.
2798    // This means we must verify that for classes A(), B(A) we must reject C(A, B) even though this
2799    // algorithm will allow the mro ordering of [C, B, A, object].
2800    // To verify this, we make sure non of the direct bases are in the mro of bases after them.
2801    for (i, base_mro) in bases.iter().enumerate() {
2802        let base = &base_mro[0]; // MROs cannot be empty.
2803        for later_mro in &bases[i + 1..] {
2804            // We start at index 1 to skip direct bases.
2805            // This will not catch duplicate bases, but such a thing is already tested for.
2806            if later_mro[1..].iter().any(|cls| cls.is(base)) {
2807                return Err(format!(
2808                    "Cannot create a consistent method resolution order (MRO) for bases {}",
2809                    bases.iter().map(|x| x.first().unwrap()).format(", ")
2810                ));
2811            }
2812        }
2813    }
2814
2815    let mut result = vec![];
2816    while !bases.is_empty() {
2817        let head = take_next_base(&mut bases).ok_or_else(|| {
2818            // Take the head class of each class here. Now that we have reached the problematic bases.
2819            // Because this failed, we assume the lists cannot be empty.
2820            format!(
2821                "Cannot create a consistent method resolution order (MRO) for bases {}",
2822                bases.iter().map(|x| x.first().unwrap()).format(", ")
2823            )
2824        })?;
2825
2826        result.push(head);
2827
2828        bases.retain(|x| !x.is_empty());
2829    }
2830    Ok(result)
2831}
2832
2833fn calculate_meta_class(
2834    metatype: PyTypeRef,
2835    bases: &[PyTypeRef],
2836    vm: &VirtualMachine,
2837) -> PyResult<PyTypeRef> {
2838    // = _PyType_CalculateMetaclass
2839    let mut winner = metatype;
2840    for base in bases {
2841        let base_type = base.class();
2842
2843        // First try fast_issubclass for PyType instances
2844        if winner.fast_issubclass(base_type) {
2845            continue;
2846        } else if base_type.fast_issubclass(&winner) {
2847            winner = base_type.to_owned();
2848            continue;
2849        }
2850
2851        // If fast_issubclass didn't work, fall back to general is_subclass
2852        // This handles cases where metaclasses are not PyType subclasses
2853        let winner_is_subclass = winner.as_object().is_subclass(base_type.as_object(), vm)?;
2854        if winner_is_subclass {
2855            continue;
2856        }
2857
2858        let base_type_is_subclass = base_type.as_object().is_subclass(winner.as_object(), vm)?;
2859        if base_type_is_subclass {
2860            winner = base_type.to_owned();
2861            continue;
2862        }
2863
2864        return Err(vm.new_type_error(
2865            "metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass \
2866             of the metaclasses of all its bases",
2867        ));
2868    }
2869    Ok(winner)
2870}
2871
2872/// Returns true if the two types have different instance layouts.
2873fn shape_differs(t1: &Py<PyType>, t2: &Py<PyType>) -> bool {
2874    t1.__basicsize__() != t2.__basicsize__() || t1.slots.itemsize != t2.slots.itemsize
2875}
2876
2877fn solid_base<'a>(typ: &'a Py<PyType>, vm: &VirtualMachine) -> &'a Py<PyType> {
2878    let base = if let Some(base) = &typ.base {
2879        solid_base(base, vm)
2880    } else {
2881        vm.ctx.types.object_type
2882    };
2883
2884    if shape_differs(typ, base) { typ } else { base }
2885}
2886
2887fn best_base<'a>(bases: &'a [PyTypeRef], vm: &VirtualMachine) -> PyResult<&'a Py<PyType>> {
2888    let mut base: Option<&Py<PyType>> = None;
2889    let mut winner: Option<&Py<PyType>> = None;
2890
2891    for base_i in bases {
2892        // if !base_i.fast_issubclass(vm.ctx.types.type_type) {
2893        //     println!("base_i type : {}", base_i.name());
2894        //     return Err(vm.new_type_error("best must be types".into()));
2895        // }
2896
2897        if !base_i.slots.flags.has_feature(PyTypeFlags::BASETYPE) {
2898            return Err(vm.new_type_error(format!(
2899                "type '{}' is not an acceptable base type",
2900                base_i.slot_name()
2901            )));
2902        }
2903
2904        let candidate = solid_base(base_i, vm);
2905        if winner.is_none() {
2906            winner = Some(candidate);
2907            base = Some(base_i.deref());
2908        } else if winner.unwrap().fast_issubclass(candidate) {
2909            // Do nothing
2910        } else if candidate.fast_issubclass(winner.unwrap()) {
2911            winner = Some(candidate);
2912            base = Some(base_i.deref());
2913        } else {
2914            return Err(vm.new_type_error("multiple bases have instance layout conflict"));
2915        }
2916    }
2917
2918    debug_assert!(base.is_some());
2919    Ok(base.unwrap())
2920}
2921
2922/// Apply Python name mangling for private attributes.
2923/// `__x` becomes `_ClassName__x` if inside a class.
2924fn mangle_name(class_name: &str, name: &str) -> String {
2925    // Only mangle names starting with __ and not ending with __
2926    if !name.starts_with("__") || name.ends_with("__") || name.contains('.') {
2927        return name.to_string();
2928    }
2929    // Strip leading underscores from class name
2930    let class_name = class_name.trim_start_matches('_');
2931    format!("_{}{}", class_name, name)
2932}
2933
2934#[cfg(test)]
2935mod tests {
2936    use super::*;
2937
2938    fn map_ids(obj: Result<Vec<PyTypeRef>, String>) -> Result<Vec<usize>, String> {
2939        Ok(obj?.into_iter().map(|x| x.get_id()).collect())
2940    }
2941
2942    #[test]
2943    fn test_linearise() {
2944        let context = Context::genesis();
2945        let object = context.types.object_type.to_owned();
2946        let type_type = context.types.type_type.to_owned();
2947
2948        let a = PyType::new_heap(
2949            "A",
2950            vec![object.clone()],
2951            PyAttributes::default(),
2952            Default::default(),
2953            type_type.clone(),
2954            context,
2955        )
2956        .unwrap();
2957        let b = PyType::new_heap(
2958            "B",
2959            vec![object.clone()],
2960            PyAttributes::default(),
2961            Default::default(),
2962            type_type,
2963            context,
2964        )
2965        .unwrap();
2966
2967        assert_eq!(
2968            map_ids(linearise_mro(vec![
2969                vec![object.clone()],
2970                vec![object.clone()]
2971            ])),
2972            map_ids(Ok(vec![object.clone()]))
2973        );
2974        assert_eq!(
2975            map_ids(linearise_mro(vec![
2976                vec![a.clone(), object.clone()],
2977                vec![b.clone(), object.clone()],
2978            ])),
2979            map_ids(Ok(vec![a, b, object]))
2980        );
2981    }
2982}