Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1152,16 +1152,16 @@ impl PyType {
if !defer_mro {
new_type.mro.write().insert(0, new_type.clone());
new_type.init_slots(ctx);
}

let weakref_type = super::PyWeak::static_type();
for base in new_type.bases.read().as_slice() {
base.subclasses.write().push(
new_type
.as_object()
.downgrade_with_weakref_typ_opt(None, weakref_type.to_owned())
.unwrap(),
);
let weakref_type = super::PyWeak::static_type();
for base in new_type.bases.read().as_slice() {
base.subclasses.write().push(
new_type
.as_object()
.downgrade_with_weakref_typ_opt(None, weakref_type.to_owned())
.unwrap(),
);
}
}

Ok(new_type)
Expand Down Expand Up @@ -2423,6 +2423,26 @@ impl Constructor for PyType {
}
}

if custom_mro {
// Like CPython's type_ready_publish, expose the type only after
// its MRO and slots are initialized. A failed custom mro() may
// leave the incomplete type alive through a user reference.
Self::with_type_lock(vm, || {
let weak = typ
.as_object()
.downgrade_with_weakref_typ_opt(None, super::PyWeak::static_type().to_owned())
.unwrap();
for base in typ.bases.read().as_slice() {
let mut subclasses = base.subclasses.write();
Comment on lines +2435 to +2436

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh collection flags after custom MRO callbacks

If a custom mro() calls collections.abc.Sequence.register(Base) (or Mapping.register(Base)) while constructing Child(Base), deferring this link means _abc_register()'s set_abc_collection_flags_recursive() no longer reaches Child. new_heap_inner() copied the collection flags before the callback, and neither init_slots() nor update_subclass_flags_recursive() refreshes PyTypeFlags::COLLECTION, so the completed child keeps stale flags and sequence/mapping patterns silently fail to match its instances. The old early link propagated the registration correctly. Refresh the inherited collection flags after the callback and before publishing the type.

Useful? React with 👍 / 👎.

// A reentrant __bases__ assignment may have registered
// this type already. Callback-free weakrefs are cached.
if !subclasses.iter().any(|existing| existing.is(&weak)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid scanning the full subclass history on every new type

When many dynamic classes are created with a custom metaclass—for example repeated typing.TypedDict(...) calls, which use dict as a physical base—this check scans the base's entire subclass history for each new class. GC leaves dead weakrefs in that vector until a pruning operation such as __subclasses__(), so collecting the classes does not prevent N creations from doing O(N²) comparisons where the previous publication path did O(N) appends. These scans also hold the interpreter-wide type lock. Preserve duplicate prevention for reentrant registration without scanning all prior subclasses on the normal construction path.

Useful? React with 👍 / 👎.

subclasses.push(weak.clone());
}
}
});
}

// avoid deadlock
let attributes = typ
.attributes
Expand Down
Loading