From fddd43d2699176e87bb336a4ca267fe7f4c55f95 Mon Sep 17 00:00:00 2001 From: dot Date: Sat, 10 Oct 2026 17:07:57 +0900 Subject: [PATCH] Reject conflicting member layouts in multiple inheritance Extract the Python 3.14-compatible solid-base check from #8954. Prefix member cells contribute to instance layout even though they do not change basicsize. Include member_count in shape comparison while preserving all native layout safeguards, and cover conflicting and compatible bases in the existing type snippet. Assisted-by: OpenAI Codex:model identifier unavailable --- crates/vm/src/builtins/type.rs | 6 +++- extra_tests/snippets/builtin_type.py | 50 ++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 3eb9c94df52..a2449f136ef 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -3677,7 +3677,11 @@ fn mro_internal(typ: &Py, vm: &VirtualMachine) -> PyResult { /// Returns true if the two types have different instance layouts. fn shape_differs(t1: &Py, t2: &Py) -> bool { - t1.slots.basicsize != t2.slots.basicsize || t1.slots.itemsize != t2.slots.itemsize + // Prefix member cells do not change basicsize, but still make a base + // solid: unrelated slotted bases cannot share the same member indexes. + t1.slots.basicsize != t2.slots.basicsize + || t1.slots.itemsize != t2.slots.itemsize + || t1.slots.member_count != t2.slots.member_count } fn solid_base<'a>(typ: &'a Py, vm: &VirtualMachine) -> &'a Py { diff --git a/extra_tests/snippets/builtin_type.py b/extra_tests/snippets/builtin_type.py index f00baaa57c7..36e0d6330dd 100644 --- a/extra_tests/snippets/builtin_type.py +++ b/extra_tests/snippets/builtin_type.py @@ -748,3 +748,53 @@ class StrKey(str): warnings.simplefilter("error") with assert_raises(RuntimeWarning): type("NonStringKeyRaises", (), {6: 7}) + + +class Left: + __slots__ = ("left",) + + +class Right: + __slots__ = ("right",) + + +with assert_raises(TypeError): + + class Conflict(Left, Right): + pass + + +with assert_raises(TypeError): + + class ReverseConflict(Right, Left): + pass + + +class Empty: + __slots__ = () + + +class Compatible(Left, Empty): + pass + + +value = Compatible() +value.left = 42 +assert value.left == 42 + + +class FirstChild(Left): + __slots__ = () + + +class SecondChild(Left): + __slots__ = () + + +class Diamond(FirstChild, SecondChild): + pass + + +value = Diamond() +value.left = 43 +assert value.left == 43