Repository navigation
Description
Summary
RustPython panics (exit code 101) on a valid program. A metaclass mro() tries to subclass the class being created, which correctly fails with TypeError: Cannot extend an incomplete type. If the half-created subclass is kept alive, creating the outer class then panics in crates/vm/src/types/slot.rs.
Reproducer
keep = []
class M(type):
def mro(cls):
if cls.__name__ == 'X':
keep.append(cls)
if cls.__mro__ is None and cls.__name__ != 'X':
try:
class X(cls):
pass
except TypeError as e:
print('TypeError:', e)
return type.mro(cls)
class A(metaclass=M):
pass
print(A.__mro__)
print([(k, k.__mro__) for k in keep])Expected
CPython 3.12.13:
TypeError: Cannot extend an incomplete type 'A'
(<class '__main__.A'>, <class 'object'>)
[(<class '__main__.M.mro.<locals>.X'>, None)]
Actual
RustPython main (ef023bc):
thread 'main' panicked at crates/vm/src/types/slot.rs:2031:28:
range start index 1 out of range for slice of length 0
Without the keep.append(cls) line the program works (this is CPython's test_descr.MroTest.test_incomplete_extend, which passes).
Cause
With a custom mro(), PyType::new_heap_inner creates the type with an empty MRO and adds it to its bases' subclasses before mro() runs (type.rs#L1152-L1165). When mro() then fails for X, X stays in A's subclass list with an empty MRO. Creating A later calls init_slots -> update_slot -> update_subclasses (slot.rs#L938-L959), which reaches X through the weak reference, and lookup_slot_in_mro slices mro[1..] (slot.rs#L2030-L2031). Backtrace:
<rustpython_vm::builtins::type_::PyType>::update_one_slot::<true>
<rustpython_vm::builtins::type_::PyType>::update_subclasses::<true>
<rustpython_vm::builtins::type_::PyType>::update_slot::<true>
<rustpython_vm::builtins::type_::PyType>::init_slots
<rustpython_vm::builtins::type_::PyType as rustpython_vm::types::slot::Constructor>::slot_new
CPython computes the MRO before it registers the new type as a subclass of its bases, so a type whose mro() failed is never in a subclass list. Either registering the subclass only after the MRO is set, or skipping subclasses with an empty MRO in update_subclasses, would avoid the panic.
Python Documentation
type.mro(): "This method can be overridden by a metaclass to customize the method resolution order for its instances. It is called at class instantiation, and its result is stored in __mro__."
Activity