From 4b02624428008e1020c3b8120b49d0e97b5406a4 Mon Sep 17 00:00:00 2001 From: TH3CHARLie Date: Wed, 25 Dec 2019 14:54:46 +0800 Subject: [PATCH 1/5] todo --- mypy/checker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/checker.py b/mypy/checker.py index 8528bf35248d..3916e30fb1b9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1528,6 +1528,7 @@ def check_method_override_for_base_with_name( original_class_or_static, override_class_or_static, context) + # TODO: elif is_equivalent(original_type, typ): # Assume invariance for a non-callable attribute here. Note # that this doesn't affect read-only properties which can have From 1c4afe0da9a7945b65f7b323d35f7992cbeb6770 Mon Sep 17 00:00:00 2001 From: TH3CHARLie Date: Wed, 25 Dec 2019 14:54:46 +0800 Subject: [PATCH 2/5] todo, add test.py --- mypy/checker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/checker.py b/mypy/checker.py index 8528bf35248d..3916e30fb1b9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1528,6 +1528,7 @@ def check_method_override_for_base_with_name( original_class_or_static, override_class_or_static, context) + # TODO: elif is_equivalent(original_type, typ): # Assume invariance for a non-callable attribute here. Note # that this doesn't affect read-only properties which can have From 001f5fb7b78760115ae7246a3c8a1f2c66514e37 Mon Sep 17 00:00:00 2001 From: Xuanda Yang Date: Fri, 31 Jan 2020 23:08:04 +0800 Subject: [PATCH 3/5] Allow covariance for read-only attributes --- mypy/checker.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 3916e30fb1b9..2ce88935055a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1528,13 +1528,14 @@ def check_method_override_for_base_with_name( original_class_or_static, override_class_or_static, context) - # TODO: elif is_equivalent(original_type, typ): # Assume invariance for a non-callable attribute here. Note # that this doesn't affect read-only properties which can have # covariant overrides. # - # TODO: Allow covariance for read-only attributes? + pass + elif not self.is_writable_attribute(base_attr) and is_subtype(typ, original_type): + # If the attribute is read-only, allow covariance pass else: self.msg.signature_incompatible_with_supertype( @@ -4488,6 +4489,16 @@ def infer_issubclass_maps(self, node: CallExpr, yes_map, no_map = map(convert_to_typetype, (yes_map, no_map)) return yes_map, no_map + def is_writable_attribute(self, node: Node) -> bool: + """Check if an attribute is writable""" + if isinstance(node, Var): + return True + elif isinstance(node, OverloadedFuncDef) and node.is_property: + first_item = cast(Decorator, node.items[0]) + return first_item.var.is_settable_property + else: + return False + def conditional_type_map(expr: Expression, current_type: Optional[Type], From 3a396de6d864a21b0ba8ecc19052e9901a188731 Mon Sep 17 00:00:00 2001 From: Xuanda Yang Date: Fri, 31 Jan 2020 23:25:42 +0800 Subject: [PATCH 4/5] fix --- mypy/checker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 45eb1df86bbf..5896610935d2 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1535,7 +1535,8 @@ def check_method_override_for_base_with_name( # covariant overrides. # pass - elif not self.is_writable_attribute(base_attr) and is_subtype(typ, original_type): + elif (base_attr.node and not self.is_writable_attribute(base_attr.node) + and is_subtype(typ, original_type)): # If the attribute is read-only, allow covariance pass else: From 0613c77ff36cb6557088f42a77118eb4a69fae6a Mon Sep 17 00:00:00 2001 From: Xuanda Yang Date: Tue, 4 Feb 2020 03:02:20 +0800 Subject: [PATCH 5/5] add testcase and refactor function --- mypy/checker.py | 24 ++++++++++++------------ test-data/unit/check-classes.test | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 5896610935d2..bb97963076da 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1439,7 +1439,7 @@ def check_method_or_accessor_override_for_base(self, defn: Union[FuncDef, self.msg.cant_override_final(name, base.name, defn) # Second, final can't override anything writeable independently of types. if defn.is_final: - self.check_no_writable(name, base_attr.node, defn) + self.check_if_final_var_override_writable(name, base_attr.node, defn) # Check the type of override. if name not in ('__init__', '__new__', '__init_subclass__'): @@ -1923,7 +1923,7 @@ class C(B, A[int]): ... # this is unsafe because... if is_final_node(second.node): self.msg.cant_override_final(name, base2.name, ctx) if is_final_node(first.node): - self.check_no_writable(name, second.node, ctx) + self.check_if_final_var_override_writable(name, second.node, ctx) # __slots__ is special and the type can vary across class hierarchy. if name == '__slots__': ok = True @@ -2388,10 +2388,14 @@ def check_compatibility_final_super(self, node: Var, self.msg.cant_override_final(node.name, base.name, node) return False if node.is_final: - self.check_no_writable(node.name, base_node, node) + self.check_if_final_var_override_writable(node.name, base_node, node) return True - def check_no_writable(self, name: str, base_node: Optional[Node], ctx: Context) -> None: + def check_if_final_var_override_writable(self, + name: str, + base_node: + Optional[Node], + ctx: Context) -> None: """Check that a final variable doesn't override writeable attribute. This is done to prevent situations like this: @@ -2403,14 +2407,10 @@ class D(C): x: C = D() x.attr = 3 # Oops! """ - if isinstance(base_node, Var): - ok = False - elif isinstance(base_node, OverloadedFuncDef) and base_node.is_property: - first_item = cast(Decorator, base_node.items[0]) - ok = not first_item.var.is_settable_property - else: - ok = True - if not ok: + writable = True + if base_node: + writable = self.is_writable_attribute(base_node) + if writable: self.msg.final_cant_override_writable(name, ctx) def get_final_context(self) -> bool: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 56b591c041d4..ed547510b46c 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -512,6 +512,28 @@ class B(A): def h(cls) -> int: pass [builtins fixtures/classmethod.pyi] +[case testAllowCovarianceInReadOnlyAttributes] +from typing import Callable, TypeVar + +T = TypeVar('T') + +class X: + pass + + +class Y(X): + pass + +def dec(f: Callable[..., T]) -> T: pass + +class A: + @dec + def f(self) -> X: pass + +class B(A): + @dec + def f(self) -> Y: pass + -- Constructors -- ------------