@@ -1439,7 +1439,7 @@ def check_method_or_accessor_override_for_base(self, defn: Union[FuncDef,
1439
1439
self .msg .cant_override_final (name , base .name , defn )
1440
1440
# Second, final can't override anything writeable independently of types.
1441
1441
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 )
1443
1443
1444
1444
# Check the type of override.
1445
1445
if name not in ('__init__' , '__new__' , '__init_subclass__' ):
@@ -1534,7 +1534,10 @@ def check_method_override_for_base_with_name(
1534
1534
# that this doesn't affect read-only properties which can have
1535
1535
# covariant overrides.
1536
1536
#
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
1538
1541
pass
1539
1542
else :
1540
1543
self .msg .signature_incompatible_with_supertype (
@@ -1920,7 +1923,7 @@ class C(B, A[int]): ... # this is unsafe because...
1920
1923
if is_final_node (second .node ):
1921
1924
self .msg .cant_override_final (name , base2 .name , ctx )
1922
1925
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 )
1924
1927
# __slots__ is special and the type can vary across class hierarchy.
1925
1928
if name == '__slots__' :
1926
1929
ok = True
@@ -2385,10 +2388,14 @@ def check_compatibility_final_super(self, node: Var,
2385
2388
self .msg .cant_override_final (node .name , base .name , node )
2386
2389
return False
2387
2390
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 )
2389
2392
return True
2390
2393
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 :
2392
2399
"""Check that a final variable doesn't override writeable attribute.
2393
2400
2394
2401
This is done to prevent situations like this:
@@ -2400,14 +2407,10 @@ class D(C):
2400
2407
x: C = D()
2401
2408
x.attr = 3 # Oops!
2402
2409
"""
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 :
2411
2414
self .msg .final_cant_override_writable (name , ctx )
2412
2415
2413
2416
def get_final_context (self ) -> bool :
@@ -4868,6 +4871,16 @@ def conditional_type_map_with_intersection(self,
4868
4871
new_yes_type = make_simplified_union (out )
4869
4872
return {expr : new_yes_type }, {}
4870
4873
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
+
4871
4884
4872
4885
def conditional_type_map (expr : Expression ,
4873
4886
current_type : Optional [Type ],
0 commit comments