@@ -2952,39 +2952,49 @@ def check_compatibility_all_supers(
2952
2952
# Show only one error per variable
2953
2953
break
2954
2954
2955
- direct_bases = lvalue_node .info . direct_base_classes ()
2956
- last_immediate_base = direct_bases [ - 1 ] if direct_bases else None
2955
+ if is_private ( lvalue_node .name ):
2956
+ return False
2957
2957
2958
- for base in lvalue_node .info .mro [1 :]:
2959
- # The type of "__slots__" and some other attributes usually doesn't need to
2960
- # be compatible with a base class. We'll still check the type of "__slots__"
2961
- # against "object" as an exception.
2962
- if lvalue_node .allow_incompatible_override and not (
2963
- lvalue_node .name == "__slots__" and base .fullname == "builtins.object"
2958
+ for base , base_type , base_node in self .classvar_base_types (lvalue_node ):
2959
+ if not self .check_compatibility_super (
2960
+ lvalue , lvalue_type , rvalue , base , base_type , base_node
2964
2961
):
2965
- continue
2966
-
2967
- if is_private (lvalue_node .name ):
2968
- continue
2969
-
2970
- base_type , base_node = self .lvalue_type_from_base (lvalue_node , base )
2971
- if isinstance (base_type , PartialType ):
2972
- base_type = None
2973
-
2974
- if base_type :
2975
- assert base_node is not None
2976
- if not self .check_compatibility_super (
2977
- lvalue , lvalue_type , rvalue , base , base_type , base_node
2978
- ):
2979
- # Only show one error per variable; even if other
2980
- # base classes are also incompatible
2981
- return True
2982
- if base is last_immediate_base :
2983
- # At this point, the attribute was found to be compatible with all
2984
- # immediate parents.
2985
- break
2962
+ # Only show one error per variable; even if other
2963
+ # base classes are also incompatible
2964
+ return True
2986
2965
return False
2987
2966
2967
+ def classvar_base_types (self , node : Var ) -> list [tuple [TypeInfo , Type , Node ]]:
2968
+ """Determine base classes that a class variable should be checked against."""
2969
+ base_types = []
2970
+ direct_bases = node .info .direct_base_classes ()
2971
+ last_immediate_base = direct_bases [- 1 ] if direct_bases else None
2972
+ for base in node .info .mro [1 :]:
2973
+ # The type of "__slots__" and some other attributes usually doesn't need to
2974
+ # be compatible with a base class. We'll still check the type of "__slots__"
2975
+ # against "object" as an exception.
2976
+ if node .allow_incompatible_override and not (
2977
+ node .name == "__slots__" and base .fullname == "builtins.object"
2978
+ ):
2979
+ continue
2980
+ base_type , base_node = self .lvalue_type_from_base (node , base )
2981
+ if not base_type or isinstance (base_type , PartialType ):
2982
+ continue
2983
+ assert base_node is not None
2984
+ if isinstance (base_node , Var ) and base_node .is_inferred :
2985
+ # Skip the type inferred from the value if there is a superclass
2986
+ # with an annotation or a (possibly more general) inferred type.
2987
+ base_node_base_types = self .classvar_base_types (base_node )
2988
+ if base_node_base_types :
2989
+ base_types .extend (base_node_base_types )
2990
+ else :
2991
+ base_types .append ((base , base_type , base_node ))
2992
+ else :
2993
+ base_types .append ((base , base_type , base_node ))
2994
+ if base is last_immediate_base :
2995
+ break
2996
+ return base_types
2997
+
2988
2998
def check_compatibility_super (
2989
2999
self ,
2990
3000
lvalue : RefExpr ,
@@ -3053,8 +3063,7 @@ def check_compatibility_super(
3053
3063
def lvalue_type_from_base (
3054
3064
self , expr_node : Var , base : TypeInfo
3055
3065
) -> tuple [Type | None , Node | None ]:
3056
- """For a NameExpr that is part of a class, walk all base classes and try
3057
- to find the first class that defines a Type for the same name."""
3066
+ """Get a NameExpr type from a given base class."""
3058
3067
expr_name = expr_node .name
3059
3068
base_var = base .names .get (expr_name )
3060
3069
0 commit comments