From d90aa0781f8bd7321defab9e9522c0164a556ece Mon Sep 17 00:00:00 2001 From: 1ndahous3 <9205230+1ndahous3@users.noreply.github.com> Date: Fri, 16 Oct 2026 04:08:59 +0300 Subject: [PATCH] Preserve direct-base precedence in C3 method resolution (#9154) Assisted-by: Codex:gpt-6 --- crates/vm/src/builtins/type.rs | 30 ++++++++++------------------ extra_tests/snippets/builtin_type.py | 19 ++++++++++++++++++ 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 9feafdaae..7a2e4010a 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -853,10 +853,14 @@ impl PyType { } } - let mros = bases + let mut mros: Vec<_> = bases .iter() .map(|base| base.mro_map_collect(|t| t.to_owned())) .collect(); + if bases.len() > 1 { + // C3 also merges the direct bases to preserve their local precedence. + mros.push(bases.to_vec()); + } linearise_mro(mros) } @@ -3533,24 +3537,6 @@ fn take_next_base(bases: &mut [Vec]) -> Option { fn linearise_mro(mut bases: Vec>) -> Result, String> { vm_trace!("Linearise MRO: {:?}", bases); - // Python requires that the class direct bases are kept in the same order. - // This is called local precedence ordering. - // This means we must verify that for classes A(), B(A) we must reject C(A, B) even though this - // algorithm will allow the mro ordering of [C, B, A, object]. - // To verify this, we make sure non of the direct bases are in the mro of bases after them. - for (i, base_mro) in bases.iter().enumerate() { - let base = &base_mro[0]; // MROs cannot be empty. - for later_mro in &bases[i + 1..] { - // We start at index 1 to skip direct bases. - // This will not catch duplicate bases, but such a thing is already tested for. - if later_mro[1..].iter().any(|cls| cls.is(base)) { - return Err(format!( - "Cannot create a consistent method resolution order (MRO) for bases {}", - bases.iter().map(|x| x.first().unwrap()).format(", ") - )); - } - } - } let mut result = vec![]; while !bases.is_empty() { @@ -3559,7 +3545,11 @@ fn linearise_mro(mut bases: Vec>) -> Result, Strin // Because this failed, we assume the lists cannot be empty. format!( "Cannot create a consistent method resolution order (MRO) for bases {}", - bases.iter().map(|x| x.first().unwrap()).format(", ") + bases + .iter() + .map(|x| x.first().unwrap()) + .unique_by(|x| x.get_id()) + .format(", ") ) })?; diff --git a/extra_tests/snippets/builtin_type.py b/extra_tests/snippets/builtin_type.py index 36e0d6330..df81b844b 100644 --- a/extra_tests/snippets/builtin_type.py +++ b/extra_tests/snippets/builtin_type.py @@ -241,6 +241,25 @@ class C(B, BB): assert C.mro() == [C, B, A, BB, AA, object] +# Direct base order must also constrain ancestors shared with an earlier base. +class D(B, AA, A): + pass + + +assert D.mro() == [D, B, AA, A, object] + + +class E(AA, A): + pass + + +class F(D, E): + pass + + +assert F.mro() == [F, D, B, E, AA, A, object] + + class TypeA: def __init__(self): self.a = 1