@@ -1439,7 +1439,7 @@ def check_method_or_accessor_override_for_base(self, defn: Union[FuncDef,
14391439 self .msg .cant_override_final (name , base .name , defn )
14401440 # Second, final can't override anything writeable independently of types.
14411441 if defn .is_final :
1442- self .check_no_writable (name , base_attr .node , defn )
1442+ self .check_if_final_var_override_writable (name , base_attr .node , defn )
14431443
14441444 # Check the type of override.
14451445 if name not in ('__init__' , '__new__' , '__init_subclass__' ):
@@ -1534,7 +1534,10 @@ def check_method_override_for_base_with_name(
15341534 # that this doesn't affect read-only properties which can have
15351535 # covariant overrides.
15361536 #
1537- # TODO: Allow covariance for read-only attributes?
1537+ pass
1538+ elif (base_attr .node and not self .is_writable_attribute (base_attr .node )
1539+ and is_subtype (typ , original_type )):
1540+ # If the attribute is read-only, allow covariance
15381541 pass
15391542 else :
15401543 self .msg .signature_incompatible_with_supertype (
@@ -1920,7 +1923,7 @@ class C(B, A[int]): ... # this is unsafe because...
19201923 if is_final_node (second .node ):
19211924 self .msg .cant_override_final (name , base2 .name , ctx )
19221925 if is_final_node (first .node ):
1923- self .check_no_writable (name , second .node , ctx )
1926+ self .check_if_final_var_override_writable (name , second .node , ctx )
19241927 # __slots__ is special and the type can vary across class hierarchy.
19251928 if name == '__slots__' :
19261929 ok = True
@@ -2385,10 +2388,14 @@ def check_compatibility_final_super(self, node: Var,
23852388 self .msg .cant_override_final (node .name , base .name , node )
23862389 return False
23872390 if node .is_final :
2388- self .check_no_writable (node .name , base_node , node )
2391+ self .check_if_final_var_override_writable (node .name , base_node , node )
23892392 return True
23902393
2391- def check_no_writable (self , name : str , base_node : Optional [Node ], ctx : Context ) -> None :
2394+ def check_if_final_var_override_writable (self ,
2395+ name : str ,
2396+ base_node :
2397+ Optional [Node ],
2398+ ctx : Context ) -> None :
23922399 """Check that a final variable doesn't override writeable attribute.
23932400
23942401 This is done to prevent situations like this:
@@ -2400,14 +2407,10 @@ class D(C):
24002407 x: C = D()
24012408 x.attr = 3 # Oops!
24022409 """
2403- if isinstance (base_node , Var ):
2404- ok = False
2405- elif isinstance (base_node , OverloadedFuncDef ) and base_node .is_property :
2406- first_item = cast (Decorator , base_node .items [0 ])
2407- ok = not first_item .var .is_settable_property
2408- else :
2409- ok = True
2410- if not ok :
2410+ writable = True
2411+ if base_node :
2412+ writable = self .is_writable_attribute (base_node )
2413+ if writable :
24112414 self .msg .final_cant_override_writable (name , ctx )
24122415
24132416 def get_final_context (self ) -> bool :
@@ -4868,6 +4871,16 @@ def conditional_type_map_with_intersection(self,
48684871 new_yes_type = make_simplified_union (out )
48694872 return {expr : new_yes_type }, {}
48704873
4874+ def is_writable_attribute (self , node : Node ) -> bool :
4875+ """Check if an attribute is writable"""
4876+ if isinstance (node , Var ):
4877+ return True
4878+ elif isinstance (node , OverloadedFuncDef ) and node .is_property :
4879+ first_item = cast (Decorator , node .items [0 ])
4880+ return first_item .var .is_settable_property
4881+ else :
4882+ return False
4883+
48714884
48724885def conditional_type_map (expr : Expression ,
48734886 current_type : Optional [Type ],
0 commit comments