33//!
44//! [Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
55
6+ use std:: cell:: OnceCell ;
7+
68use bitflags:: bitflags;
79use ruff_db:: diagnostic:: Annotation ;
810use ruff_python_ast:: name:: Name ;
@@ -325,7 +327,7 @@ fn check_class_declaration<'db>(
325327 let mut overridden_final_method = None ;
326328 let mut overridden_final_variable: Option < ( ClassType < ' db > , Option < Definition < ' db > > ) > = None ;
327329 let is_private_member = is_mangled_private ( member. name . as_str ( ) ) ;
328- let mut subclass_variable_kind = VariableKindCache :: default ( ) ;
330+ let subclass_variable_kind: OnceCell < Option < VariableKind > > = OnceCell :: new ( ) ;
329331
330332 // Track the first superclass that defines this method (the "immediate parent" for this method).
331333 // We need this to check if parent itself already has an LSP violation with an ancestor.
@@ -470,8 +472,6 @@ fn check_class_declaration<'db>(
470472 continue ;
471473 }
472474
473- let superclass_definition =
474- superclass_symbol_id. and_then ( |id| symbol_definition ( db, superclass_scope, id) ) ;
475475 let superclass_variable_kind = superclass_variable_kind (
476476 db,
477477 superclass. own_class_member ( db, None , & member. name ) . inner ,
@@ -485,22 +485,21 @@ fn check_class_declaration<'db>(
485485 }
486486
487487 if let Some ( superclass_variable_kind) = superclass_variable_kind {
488- let subclass_variable_kind = subclass_variable_kind. get_or_compute ( || {
488+ let subclass_kind = * subclass_variable_kind. get_or_init ( || {
489489 variable_kind (
490490 db,
491491 class. own_class_member ( db, None , & member. name ) . inner ,
492492 subclass_instance_member,
493493 )
494494 } ) ;
495495
496- if let Some ( invalid_override) = invalid_classvar_instance_override (
497- subclass_variable_kind,
498- Some ( superclass_variable_kind) ,
499- ) {
496+ if let Some ( subclass_kind) = subclass_kind
497+ && subclass_kind != superclass_variable_kind
498+ {
500499 // An unannotated class-body assignment can inherit an overridden `ClassVar`
501500 // declaration instead of introducing a conflicting instance variable.
502- if invalid_override . subclass_kind == VariableKind :: Instance
503- && invalid_override . superclass_kind == VariableKind :: Class
501+ if subclass_kind == VariableKind :: Instance
502+ && superclass_variable_kind == VariableKind :: Class
504503 && first_reachable_definition
505504 . kind ( db)
506505 . is_unannotated_assignment ( )
@@ -512,18 +511,21 @@ fn check_class_declaration<'db>(
512511 immediate_parent_variable_kind
513512 && immediate_parent != superclass
514513 && immediate_parent. is_subclass_of ( db, superclass)
515- && immediate_parent_kind != invalid_override . superclass_kind
514+ && immediate_parent_kind != superclass_variable_kind
516515 {
517516 continue ;
518517 }
519518
519+ let superclass_definition = superclass_symbol_id
520+ . and_then ( |id| symbol_definition ( db, superclass_scope, id) ) ;
520521 report_invalid_attribute_override (
521522 context,
522523 & member. name ,
523524 * first_reachable_definition,
524525 superclass,
525526 superclass_definition,
526- invalid_override,
527+ subclass_kind,
528+ superclass_variable_kind,
527529 ) ;
528530 liskov_diagnostic_emitted = true ;
529531 continue ;
@@ -652,42 +654,17 @@ fn check_class_declaration<'db>(
652654 }
653655}
654656
655- #[ derive( Debug , Clone , Copy , PartialEq , Eq , Default ) ]
656- enum VariableKindCache {
657- #[ default]
658- Uncomputed ,
659- NotVariable ,
660- Variable ( VariableKind ) ,
661- }
662-
663- impl VariableKindCache {
664- fn get_or_compute (
665- & mut self ,
666- compute : impl FnOnce ( ) -> Option < VariableKind > ,
667- ) -> Option < VariableKind > {
668- match * self {
669- Self :: NotVariable => None ,
670- Self :: Variable ( variable_kind) => Some ( variable_kind) ,
671- Self :: Uncomputed => {
672- if let Some ( variable_kind) = compute ( ) {
673- * self = Self :: Variable ( variable_kind) ;
674- Some ( variable_kind)
675- } else {
676- * self = Self :: NotVariable ;
677- None
678- }
679- }
680- }
681- }
682- }
683-
657+ /// Whether an attribute declaration is a class variable or an instance variable.
684658#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
685659enum VariableKind {
660+ /// A variable annotated with `ClassVar`.
686661 Class ,
662+ /// An instance variable, including an unannotated class-body assignment.
687663 Instance ,
688664}
689665
690666impl VariableKind {
667+ /// Returns the wording used for this variable kind in diagnostics.
691668 const fn description ( self ) -> & ' static str {
692669 match self {
693670 VariableKind :: Class => "class variable" ,
@@ -696,24 +673,7 @@ impl VariableKind {
696673 }
697674}
698675
699- #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
700- struct InvalidAttributeOverride {
701- subclass_kind : VariableKind ,
702- superclass_kind : VariableKind ,
703- }
704-
705- fn invalid_classvar_instance_override (
706- subclass_kind : Option < VariableKind > ,
707- superclass_kind : Option < VariableKind > ,
708- ) -> Option < InvalidAttributeOverride > {
709- let subclass_kind = subclass_kind?;
710- let superclass_kind = superclass_kind?;
711- ( subclass_kind != superclass_kind) . then_some ( InvalidAttributeOverride {
712- subclass_kind,
713- superclass_kind,
714- } )
715- }
716-
676+ /// Returns the variable kind for a superclass member, excluding final attributes.
717677fn superclass_variable_kind < ' db > (
718678 db : & ' db dyn Db ,
719679 class_member : PlaceAndQualifiers < ' db > ,
@@ -726,6 +686,7 @@ fn superclass_variable_kind<'db>(
726686 variable_kind ( db, class_member, instance_member)
727687}
728688
689+ /// Returns the variable kind for an attribute if it should participate in `ClassVar` override checks.
729690fn variable_kind < ' db > (
730691 db : & ' db dyn Db ,
731692 class_member : PlaceAndQualifiers < ' db > ,
@@ -739,11 +700,7 @@ fn variable_kind<'db>(
739700 return None ;
740701 }
741702
742- if class_member. qualifiers . contains ( TypeQualifiers :: CLASS_VAR )
743- || instance_member
744- . qualifiers
745- . contains ( TypeQualifiers :: CLASS_VAR )
746- {
703+ if class_member. is_class_var ( ) || instance_member. is_class_var ( ) {
747704 return Some ( VariableKind :: Class ) ;
748705 }
749706
@@ -760,6 +717,7 @@ fn variable_kind<'db>(
760717 Some ( VariableKind :: Instance )
761718}
762719
720+ /// Returns the definition to use as the secondary annotation for an overridden symbol.
763721fn symbol_definition < ' db > (
764722 db : & ' db dyn Db ,
765723 scope : ScopeId < ' db > ,
@@ -775,6 +733,7 @@ fn symbol_definition<'db>(
775733 } )
776734}
777735
736+ /// Returns `true` if a type represents a variable-like attribute.
778737fn is_variable_like_type < ' db > ( db : & ' db dyn Db , ty : Type < ' db > ) -> bool {
779738 match ty {
780739 Type :: FunctionLiteral ( _)
@@ -794,13 +753,15 @@ fn is_variable_like_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
794753 }
795754}
796755
756+ /// Reports an invalid override between a class variable and an instance variable.
797757fn report_invalid_attribute_override < ' db > (
798758 context : & InferContext < ' db , ' _ > ,
799759 member : & Name ,
800760 subclass_definition : Definition < ' db > ,
801761 superclass : ClassType < ' db > ,
802762 superclass_definition : Option < Definition < ' db > > ,
803- invalid_override : InvalidAttributeOverride ,
763+ subclass_kind : VariableKind ,
764+ superclass_kind : VariableKind ,
804765) {
805766 let db = context. db ( ) ;
806767
@@ -813,8 +774,8 @@ fn report_invalid_attribute_override<'db>(
813774
814775 let superclass_name = superclass. name ( db) ;
815776 let superclass_member = format ! ( "{superclass_name}.{member}" ) ;
816- let subclass_kind = invalid_override . subclass_kind . description ( ) ;
817- let superclass_kind = invalid_override . superclass_kind . description ( ) ;
777+ let subclass_kind = subclass_kind. description ( ) ;
778+ let superclass_kind = superclass_kind. description ( ) ;
818779
819780 let mut diagnostic =
820781 builder. into_diagnostic ( format_args ! ( "Invalid override of attribute `{member}`" ) ) ;
0 commit comments