From 834e125895c5ca297d7a62d11b34e7566d96762e Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sat, 29 Mar 2025 18:08:22 +0800 Subject: [PATCH 1/3] feat: check if `@property` overrides parent class variable --- mypy/checker.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 12afa4d3edf5..67af0f816fb6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2280,6 +2280,17 @@ def check_method_override_for_base_with_name( if inner is not None: typ = inner typ = get_property_type(typ) + + if ( + isinstance(original_node, Var) + and original_node.is_classvar + and defn.name == original_node.name + and isinstance(defn, Decorator) + ): + self.fail( + message_registry.CANNOT_OVERRIDE_CLASS_VAR.format(base.name), defn.func + ) + if ( isinstance(original_node, Var) and not original_node.is_final From 00603342581dfac42762f8767d427a7a94a5803e Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sat, 29 Mar 2025 18:09:27 +0800 Subject: [PATCH 2/3] test: add `testOverrideWithPropertyDecorator` unit test --- test-data/unit/check-classvar.test | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test-data/unit/check-classvar.test b/test-data/unit/check-classvar.test index 918926627bfd..276e4aada47d 100644 --- a/test-data/unit/check-classvar.test +++ b/test-data/unit/check-classvar.test @@ -269,6 +269,19 @@ class A(metaclass=ABCMeta): class B(A): x = 0 # type: ClassVar[int] +[case testOverrideWithPropertyDecorator] +from typing import ClassVar + +class A: + x: ClassVar[int] +class B(A): + @property + def x(self) -> int: ... +[builtins fixtures/property.pyi] +[out] +main:7: error: Cannot override class variable (previously declared on base class "A") with instance variable +main:7: error: Cannot override writeable attribute with read-only property + [case testAcrossModules] import m reveal_type(m.A().x) From f2f8a98e10e5b6d794d338aaf8edce01e3206a4f Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 30 Mar 2025 00:43:52 +0800 Subject: [PATCH 3/3] fix: prevent override when there exists a `setter` --- mypy/checker.py | 16 ++++++++++++---- test-data/unit/check-classvar.test | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 67af0f816fb6..0183a78ed05d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2285,11 +2285,19 @@ def check_method_override_for_base_with_name( isinstance(original_node, Var) and original_node.is_classvar and defn.name == original_node.name - and isinstance(defn, Decorator) + and (isinstance(defn, (Decorator, OverloadedFuncDef))) ): - self.fail( - message_registry.CANNOT_OVERRIDE_CLASS_VAR.format(base.name), defn.func - ) + decorator_func = None + if isinstance(defn, Decorator): + decorator_func = defn.func + elif isinstance(defn.items[0], Decorator): + decorator_func = defn.items[0].func + + if decorator_func: + self.fail( + message_registry.CANNOT_OVERRIDE_CLASS_VAR.format(base.name), + decorator_func, + ) if ( isinstance(original_node, Var) diff --git a/test-data/unit/check-classvar.test b/test-data/unit/check-classvar.test index 276e4aada47d..1b7998786baf 100644 --- a/test-data/unit/check-classvar.test +++ b/test-data/unit/check-classvar.test @@ -277,10 +277,12 @@ class A: class B(A): @property def x(self) -> int: ... + + @x.setter + def x(self, value: int) -> None: ... [builtins fixtures/property.pyi] [out] main:7: error: Cannot override class variable (previously declared on base class "A") with instance variable -main:7: error: Cannot override writeable attribute with read-only property [case testAcrossModules] import m