@@ -5,7 +5,7 @@ use indexmap::IndexMap;
55use rustc_hash:: { FxBuildHasher , FxHashSet } ;
66
77use crate :: Db ;
8- use crate :: types:: class:: DynamicClassLiteral ;
8+ use crate :: types:: class:: { DynamicClassLiteral , DynamicEnumLiteral } ;
99use crate :: types:: class_base:: ClassBase ;
1010use crate :: types:: generics:: Specialization ;
1111use crate :: types:: {
@@ -421,6 +421,84 @@ impl<'db> Mro<'db> {
421421 }
422422 }
423423
424+ /// Compute the MRO of a dynamic enum (created via the functional `Enum()`/`StrEnum()` API).
425+ ///
426+ /// Uses C3 linearization to correctly handle the optional `type=` mixin parameter.
427+ /// For example, `Enum("Http", {"OK": 200}, type=int)` is equivalent to
428+ /// `class Http(int, Enum)` at runtime, so the MRO must be C3-linearized from
429+ /// both bases to produce `[Http, int, Enum, object]`
430+ pub ( super ) fn of_dynamic_enum (
431+ db : & ' db dyn Db ,
432+ dynamic_enum : DynamicEnumLiteral < ' db > ,
433+ ) -> Result < Self , DynamicMroError < ' db > > {
434+ let self_base = ClassBase :: Class ( ClassType :: NonGeneric ( dynamic_enum. into ( ) ) ) ;
435+
436+ // Build the bases list: [mixin, base_class] when a `type=` mixin is present,
437+ // or just [base_class] otherwise. For example:
438+ // Enum("Color", ...) -> resolved_bases = [Enum]
439+ // Enum("Http", ..., type=int) -> resolved_bases = [int, Enum]
440+ // StrEnum("Method", ...) -> resolved_bases = [StrEnum]
441+ let mut resolved_bases: Vec < ClassBase < ' db > > = Vec :: with_capacity ( 2 ) ;
442+ if let Some ( mixin) = dynamic_enum. mixin_type ( db) {
443+ if let Some ( base) = ClassBase :: try_from_type ( db, mixin, None ) {
444+ resolved_bases. push ( base) ;
445+ }
446+ }
447+ let enum_base = dynamic_enum
448+ . base_class ( db)
449+ . to_class_literal ( db)
450+ . as_class_literal ( )
451+ . expect ( "enum base should be a class literal" )
452+ . default_specialization ( db) ;
453+ resolved_bases. push ( ClassBase :: Class ( enum_base) ) ;
454+
455+ // When C3 linearization fails (e.g. a bad `type=` mixin), we still need a
456+ // usable MRO for downstream type inference. Rather than falling back to the
457+ // generic `[self, Unknown, object]`, we chain the bases' MROs with
458+ // deduplication. This preserves type information from the known bases so that
459+ // member lookups can still find attributes from the mixin and enum base class.
460+ //
461+ // For example, if `Enum("Foo", ..., type=BadMixin)` fails C3, the fallback
462+ // produces `[Foo, BadMixin, ..., Enum, object]` (deduped), so lookups for
463+ // `Foo.some_method` can still resolve methods from `BadMixin` or `Enum`.
464+ // With `[Foo, Unknown, object]`, those lookups would silently return Unknown.
465+ //
466+ // This matches the `dynamic_fallback` approach used by `of_dynamic_class`.
467+ let fallback_mro = || {
468+ let mut result = vec ! [ self_base] ;
469+ let mut seen = FxHashSet :: default ( ) ;
470+ seen. insert ( self_base) ;
471+ for base in & resolved_bases {
472+ for item in base. mro ( db, None ) {
473+ if seen. insert ( item) {
474+ result. push ( item) ;
475+ }
476+ }
477+ }
478+ Self :: from ( result)
479+ } ;
480+
481+ // Standard C3 linearization: build sequences from each base's MRO, plus the
482+ // bases list itself, then merge. See `of_static_class` and `of_dynamic_class`
483+ // for the same pattern.
484+ let mut seqs = vec ! [ VecDeque :: from( [ self_base] ) ] ;
485+ for base in & resolved_bases {
486+ if base. has_cyclic_mro ( db) {
487+ return Err ( DynamicMroError {
488+ kind : DynamicMroErrorKind :: InheritanceCycle ,
489+ fallback_mro : fallback_mro ( ) ,
490+ } ) ;
491+ }
492+ seqs. push ( base. mro ( db, None ) . collect ( ) ) ;
493+ }
494+ seqs. push ( resolved_bases. iter ( ) . copied ( ) . collect ( ) ) ;
495+
496+ c3_merge ( seqs) . ok_or_else ( || DynamicMroError {
497+ kind : DynamicMroErrorKind :: UnresolvableMro ,
498+ fallback_mro : fallback_mro ( ) ,
499+ } )
500+ }
501+
424502 /// Compute a fallback MRO for a dynamic class when `of_dynamic_class` fails.
425503 ///
426504 /// Iterates over base MROs sequentially with deduplication.
@@ -577,7 +655,10 @@ impl<'db> MroIterator<'db> {
577655 full_mro_iter
578656 }
579657 ClassLiteral :: DynamicEnum ( literal) => {
580- let mut full_mro_iter = literal. mro ( self . db ) . iter ( ) ;
658+ let mut full_mro_iter = match literal. try_mro ( self . db ) {
659+ Ok ( mro) => mro. iter ( ) ,
660+ Err ( error) => error. fallback_mro ( ) . iter ( ) ,
661+ } ;
581662 full_mro_iter. next ( ) ;
582663 full_mro_iter
583664 }
0 commit comments