@@ -2349,6 +2349,15 @@ impl<'db> Class<'db> {
23492349 self . explicit_bases_query ( db)
23502350 }
23512351
2352+ /// Iterate over this class's explicit bases, filtering out any bases that are not class objects.
2353+ fn fully_static_explicit_bases ( self , db : & ' db dyn Db ) -> impl Iterator < Item = Class < ' db > > {
2354+ self . explicit_bases ( db)
2355+ . iter ( )
2356+ . copied ( )
2357+ . filter_map ( Type :: into_class_literal)
2358+ . map ( |ClassLiteralType { class } | class)
2359+ }
2360+
23522361 #[ salsa:: tracked( return_ref) ]
23532362 fn explicit_bases_query ( self , db : & ' db dyn Db ) -> Box < [ Type < ' db > ] > {
23542363 let class_stmt = self . node ( db) ;
@@ -2448,102 +2457,80 @@ impl<'db> Class<'db> {
24482457 /// Return the metaclass of this class, or `Unknown` if the metaclass cannot be inferred.
24492458 pub ( crate ) fn metaclass ( self , db : & ' db dyn Db ) -> Type < ' db > {
24502459 // TODO: `type[Unknown]` would be a more precise fallback
2451- // (needs support for <https://docs.python.org/3/library/typing.html#the-type-of-class-objects>)
24522460 self . try_metaclass ( db) . unwrap_or ( Type :: Unknown )
24532461 }
24542462
24552463 /// Return the metaclass of this class, or an error if the metaclass cannot be inferred.
24562464 #[ salsa:: tracked]
24572465 pub ( crate ) fn try_metaclass ( self , db : & ' db dyn Db ) -> Result < Type < ' db > , MetaclassError < ' db > > {
2458- /// Infer the metaclass of a class, tracking the classes that have been visited to detect
2459- /// cyclic definitions.
2460- fn infer < ' db > (
2461- db : & ' db dyn Db ,
2462- class : Class < ' db > ,
2463- seen : & mut SeenSet < Class < ' db > > ,
2464- ) -> Result < Type < ' db > , MetaclassError < ' db > > {
2465- // Recursively infer the metaclass of a class, ensuring that cyclic definitions are
2466- // detected.
2467- let mut safe_recurse = |class : Class < ' db > | -> Result < Type < ' db > , MetaclassError < ' db > > {
2468- // Each base must be considered in isolation.
2469- let num_seen = seen. len ( ) ;
2470- if !seen. insert ( class) {
2471- return Err ( MetaclassError {
2472- kind : MetaclassErrorKind :: CyclicDefinition ,
2473- } ) ;
2474- }
2475- let metaclass = infer ( db, class, seen) ?;
2476- seen. truncate ( num_seen) ;
2477- Ok ( metaclass)
2478- } ;
2466+ // Identify the class's own metaclass (or take the first base class's metaclass).
2467+ let mut base_classes = self . fully_static_explicit_bases ( db) . peekable ( ) ;
24792468
2480- let mut base_classes = class
2481- . explicit_bases ( db)
2482- . iter ( )
2483- . copied ( )
2484- . filter_map ( Type :: into_class_literal) ;
2469+ if base_classes. peek ( ) . is_some ( ) && self . is_cyclically_defined ( db) {
2470+ // We emit diagnostics for cyclic class definitions elsewhere.
2471+ // Avoid attempting to infer the metaclass if the class is cyclically defined:
2472+ // it would be easy to enter an infinite loop.
2473+ //
2474+ // TODO: `type[Unknown]` might be better here?
2475+ return Ok ( Type :: Unknown ) ;
2476+ }
24852477
2486- // Identify the class's own metaclass (or take the first base class's metaclass).
2487- let explicit_metaclass = class. explicit_metaclass ( db) ;
2488- let ( metaclass, class_metaclass_was_from) = if let Some ( metaclass) = explicit_metaclass
2489- {
2490- ( metaclass, class)
2491- } else if let Some ( base_class) = base_classes. next ( ) {
2492- ( safe_recurse ( base_class. class ) ?, base_class. class )
2493- } else {
2494- ( KnownClass :: Type . to_class ( db) , class)
2495- } ;
2478+ let explicit_metaclass = self . explicit_metaclass ( db) ;
2479+ let ( metaclass, class_metaclass_was_from) = if let Some ( metaclass) = explicit_metaclass {
2480+ ( metaclass, self )
2481+ } else if let Some ( base_class) = base_classes. next ( ) {
2482+ ( base_class. metaclass ( db) , base_class)
2483+ } else {
2484+ ( KnownClass :: Type . to_class ( db) , self )
2485+ } ;
24962486
2497- let mut candidate = if let Type :: ClassLiteral ( metaclass_ty) = metaclass {
2498- MetaclassCandidate {
2499- metaclass : metaclass_ty. class ,
2500- explicit_metaclass_of : class_metaclass_was_from,
2501- }
2502- } else {
2503- // TODO: If the metaclass is not a class, we should verify that it's a callable
2504- // which accepts the same arguments as `type.__new__` (otherwise error), and return
2505- // the meta-type of its return type. (And validate that is a class type?)
2506- return Ok ( Type :: Todo ) ;
2507- } ;
2487+ let mut candidate = if let Type :: ClassLiteral ( metaclass_ty) = metaclass {
2488+ MetaclassCandidate {
2489+ metaclass : metaclass_ty. class ,
2490+ explicit_metaclass_of : class_metaclass_was_from,
2491+ }
2492+ } else {
2493+ // TODO: If the metaclass is not a class, we should verify that it's a callable
2494+ // which accepts the same arguments as `type.__new__` (otherwise error), and return
2495+ // the meta-type of its return type. (And validate that is a class type?)
2496+ return Ok ( Type :: Todo ) ;
2497+ } ;
25082498
2509- // Reconcile all base classes' metaclasses with the candidate metaclass.
2510- //
2511- // See:
2512- // - https://docs.python.org/3/reference/datamodel.html#determining-the-appropriate-metaclass
2513- // - https://github.com/python/cpython/blob/83ba8c2bba834c0b92de669cac16fcda17485e0e/Objects/typeobject.c#L3629-L3663
2514- for base_class in base_classes {
2515- let metaclass = safe_recurse ( base_class. class ) ?;
2516- let Type :: ClassLiteral ( metaclass) = metaclass else {
2517- continue ;
2499+ // Reconcile all base classes' metaclasses with the candidate metaclass.
2500+ //
2501+ // See:
2502+ // - https://docs.python.org/3/reference/datamodel.html#determining-the-appropriate-metaclass
2503+ // - https://github.com/python/cpython/blob/83ba8c2bba834c0b92de669cac16fcda17485e0e/Objects/typeobject.c#L3629-L3663
2504+ for base_class in base_classes {
2505+ let metaclass = base_class. metaclass ( db) ;
2506+ let Type :: ClassLiteral ( metaclass) = metaclass else {
2507+ continue ;
2508+ } ;
2509+ if metaclass. class . is_subclass_of ( db, candidate. metaclass ) {
2510+ candidate = MetaclassCandidate {
2511+ metaclass : metaclass. class ,
2512+ explicit_metaclass_of : base_class,
25182513 } ;
2519- if metaclass. class . is_subclass_of ( db, candidate. metaclass ) {
2520- candidate = MetaclassCandidate {
2514+ continue ;
2515+ }
2516+ if candidate. metaclass . is_subclass_of ( db, metaclass. class ) {
2517+ continue ;
2518+ }
2519+ return Err ( MetaclassError {
2520+ kind : MetaclassErrorKind :: Conflict {
2521+ candidate1 : candidate,
2522+ candidate2 : MetaclassCandidate {
25212523 metaclass : metaclass. class ,
2522- explicit_metaclass_of : base_class. class ,
2523- } ;
2524- continue ;
2525- }
2526- if candidate. metaclass . is_subclass_of ( db, metaclass. class ) {
2527- continue ;
2528- }
2529- return Err ( MetaclassError {
2530- kind : MetaclassErrorKind :: Conflict {
2531- candidate1 : candidate,
2532- candidate2 : MetaclassCandidate {
2533- metaclass : metaclass. class ,
2534- explicit_metaclass_of : base_class. class ,
2535- } ,
2536- candidate1_is_base_class : explicit_metaclass. is_none ( ) ,
2524+ explicit_metaclass_of : base_class,
25372525 } ,
2538- } ) ;
2539- }
2540-
2541- Ok ( Type :: ClassLiteral ( ClassLiteralType {
2542- class : candidate. metaclass ,
2543- } ) )
2526+ candidate1_is_base_class : explicit_metaclass. is_none ( ) ,
2527+ } ,
2528+ } ) ;
25442529 }
25452530
2546- infer ( db, self , & mut SeenSet :: new ( self ) )
2531+ Ok ( Type :: ClassLiteral ( ClassLiteralType {
2532+ class : candidate. metaclass ,
2533+ } ) )
25472534 }
25482535
25492536 /// Returns the class member of this class named `name`.
@@ -2590,6 +2577,39 @@ impl<'db> Class<'db> {
25902577 let scope = self . body_scope ( db) ;
25912578 symbol ( db, scope, name)
25922579 }
2580+
2581+ /// Return `true` if this class appears to be a cyclic definition,
2582+ /// i.e., it inherits either directly or indirectly from itself.
2583+ ///
2584+ /// A class definition like this will fail at runtime,
2585+ /// but we must be resilient to it or we could panic.
2586+ #[ salsa:: tracked]
2587+ fn is_cyclically_defined ( self , db : & ' db dyn Db ) -> bool {
2588+ fn is_cyclically_defined_recursive < ' db > (
2589+ db : & ' db dyn Db ,
2590+ class : Class < ' db > ,
2591+ classes_to_watch : & mut IndexSet < Class < ' db > > ,
2592+ ) -> bool {
2593+ if !classes_to_watch. insert ( class) {
2594+ return true ;
2595+ }
2596+ for explicit_base_class in class. fully_static_explicit_bases ( db) {
2597+ // Each base must be considered in isolation.
2598+ // This is due to the fact that if a class uses multiple inheritance,
2599+ // there could easily be a situation where two bases have the same class in their MROs;
2600+ // that isn't enough to constitute the class being cyclically defined.
2601+ let classes_to_watch_len = classes_to_watch. len ( ) ;
2602+ if is_cyclically_defined_recursive ( db, explicit_base_class, classes_to_watch) {
2603+ return true ;
2604+ }
2605+ classes_to_watch. truncate ( classes_to_watch_len) ;
2606+ }
2607+ false
2608+ }
2609+
2610+ self . fully_static_explicit_bases ( db)
2611+ . any ( |base_class| is_cyclically_defined_recursive ( db, base_class, & mut IndexSet :: new ( ) ) )
2612+ }
25932613}
25942614
25952615/// Either the explicit `metaclass=` keyword of the class, or the inferred metaclass of one of its base classes.
@@ -2599,38 +2619,6 @@ pub(super) struct MetaclassCandidate<'db> {
25992619 explicit_metaclass_of : Class < ' db > ,
26002620}
26012621
2602- /// A utility struct for detecting duplicates in class hierarchies while storing the initial
2603- /// entry on the stack.
2604- #[ derive( Debug , Clone , PartialEq , Eq ) ]
2605- struct SeenSet < T : Hash + Eq > {
2606- initial : T ,
2607- visited : IndexSet < T > ,
2608- }
2609-
2610- impl < T : Hash + Eq > SeenSet < T > {
2611- fn new ( initial : T ) -> SeenSet < T > {
2612- Self {
2613- initial,
2614- visited : IndexSet :: new ( ) ,
2615- }
2616- }
2617-
2618- fn len ( & self ) -> usize {
2619- self . visited . len ( )
2620- }
2621-
2622- fn truncate ( & mut self , len : usize ) {
2623- self . visited . truncate ( len) ;
2624- }
2625-
2626- fn insert ( & mut self , value : T ) -> bool {
2627- if value == self . initial {
2628- return false ;
2629- }
2630- self . visited . insert ( value)
2631- }
2632- }
2633-
26342622/// A singleton type representing a single class object at runtime.
26352623#[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash , salsa:: Update ) ]
26362624pub struct ClassLiteralType < ' db > {
@@ -2714,13 +2702,6 @@ pub(super) enum MetaclassErrorKind<'db> {
27142702 /// inferred metaclass of a base class. This helps us give better error messages in diagnostics.
27152703 candidate1_is_base_class : bool ,
27162704 } ,
2717-
2718- /// The class inherits from itself!
2719- ///
2720- /// This is very unlikely to happen in working real-world code,
2721- /// but it's important to explicitly account for it.
2722- /// If we don't, there's a possibility of an infinite loop and a panic.
2723- CyclicDefinition ,
27242705}
27252706
27262707#[ salsa:: interned]
0 commit comments