From 5471cdcb08e5e1906bc66a3f7f7d27c9c64a1cf2 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 1 Feb 2024 21:27:38 -0800 Subject: [PATCH 1/3] Fix override checking for decorated property Fixes #16855 --- mypy/checker.py | 35 ++++++++++++----------- test-data/unit/check-functions.test | 43 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index cd23e74a8dacc..4241769c56058 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2022,22 +2022,25 @@ def check_method_override_for_base_with_name( if original_node and is_property(original_node): original_type = get_property_type(original_type) - if isinstance(typ, FunctionLike) and is_property(defn): - typ = get_property_type(typ) - if ( - isinstance(original_node, Var) - and not original_node.is_final - and (not original_node.is_property or original_node.is_settable_property) - and isinstance(defn, Decorator) - ): - # We only give an error where no other similar errors will be given. - if not isinstance(original_type, AnyType): - self.msg.fail( - "Cannot override writeable attribute with read-only property", - # Give an error on function line to match old behaviour. - defn.func, - code=codes.OVERRIDE, - ) + if is_property(defn): + inner = self.extract_callable_type(typ, context) + if inner is not None: + typ = inner + typ = get_property_type(typ) + if ( + isinstance(original_node, Var) + and not original_node.is_final + and (not original_node.is_property or original_node.is_settable_property) + and isinstance(defn, Decorator) + ): + # We only give an error where no other similar errors will be given. + if not isinstance(original_type, AnyType): + self.msg.fail( + "Cannot override writeable attribute with read-only property", + # Give an error on function line to match old behaviour. + defn.func, + code=codes.OVERRIDE, + ) if isinstance(original_type, AnyType) or isinstance(typ, AnyType): pass diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index b3df5fddafbac..3aecbe065c275 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2730,6 +2730,49 @@ f: Callable[[Sequence[TI]], None] g: Callable[[Union[Sequence[TI], Sequence[TS]]], None] f = g +[case testOverrideDecoratedProperty] +class Base: + @property + def foo(self) -> int: ... + +class decorator: + def __init__(self, fn): + self.fn = fn + def __call__(self, decorated_self) -> int: + return self.fn(decorated_self) + +class Child(Base): + @property + @decorator + def foo(self) -> int: + return 42 + +reveal_type(Child().foo) # N: Revealed type is "builtins.int" + +class BadChild1(Base): + @decorator + def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: decorator + return 42 + +class not_a_decorator: + def __init__(self, fn): ... + +class BadChild2(Base): + @property + @not_a_decorator + def foo(self) -> int: # E: "not_a_decorator" not callable \ + # E: Signature of "foo" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: not_a_decorator + return 42 +[builtins fixtures/property.pyi] + [case explicitOverride] # flags: --python-version 3.12 from typing import override From 6fe9d0e8e253336ffdf32447ed968bb926015ff8 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 1 Feb 2024 21:51:28 -0800 Subject: [PATCH 2/3] hack for overloads --- mypy/checker.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 4241769c56058..41c64cf63b57d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2023,7 +2023,10 @@ def check_method_override_for_base_with_name( original_type = get_property_type(original_type) if is_property(defn): - inner = self.extract_callable_type(typ, context) + if isinstance(typ, FunctionLike): + inner = typ + else: + inner = self.extract_callable_type(typ, context) if inner is not None: typ = inner typ = get_property_type(typ) From 0c6cc60f0dfb1d98921552919f484c59de40b4db Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 1 Feb 2024 22:01:58 -0800 Subject: [PATCH 3/3] . --- mypy/checker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/checker.py b/mypy/checker.py index 41c64cf63b57d..8d7766a948c22 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2023,6 +2023,7 @@ def check_method_override_for_base_with_name( original_type = get_property_type(original_type) if is_property(defn): + inner: FunctionLike | None if isinstance(typ, FunctionLike): inner = typ else: