diff --git a/mypy/checker.py b/mypy/checker.py index dbc997ba33c6..367fa2c01b71 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2148,7 +2148,7 @@ def check_method_override_for_base_with_name( pass elif isinstance(original_type, FunctionLike) and isinstance(typ, FunctionLike): # Check that the types are compatible. - self.check_override( + ok = self.check_override( typ, original_type, defn.name, @@ -2158,6 +2158,21 @@ def check_method_override_for_base_with_name( override_class_or_static, context, ) + if ( + ok + and original_node + and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes + and self.is_writable_attribute(original_node) + and not is_subtype(original_type, typ, ignore_pos_arg_names=True) + ): + base_str, override_str = format_type_distinctly( + original_type, typ, options=self.options + ) + msg = message_registry.COVARIANT_OVERRIDE_OF_MUTABLE_ATTRIBUTE.with_additional_msg( + f' (base class "{base.name}" defined the type as {base_str},' + f" override has type {override_str})" + ) + self.fail(msg, context) 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 @@ -2247,7 +2262,7 @@ def check_override( original_class_or_static: bool, override_class_or_static: bool, node: Context, - ) -> None: + ) -> bool: """Check a method override with given signatures. Arguments: @@ -2397,6 +2412,7 @@ def erase_override(t: Type) -> Type: node, code=codes.OVERRIDE, ) + return not fail def check__exit__return_type(self, defn: FuncItem) -> None: """Generate error if the return type of __exit__ is problematic. diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 82208d27df41..a6ae963d6c23 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -710,6 +710,72 @@ class B(A): @dec def f(self) -> Y: pass +[case testOverrideCallableAttributeWithMethod] +from typing import Callable + +class A: + f1: Callable[[str], None] + f2: Callable[[str], None] + f3: Callable[[str], None] + +class B(A): + def f1(self, x: object) -> None: + pass + + @classmethod + def f2(cls, x: object) -> None: + pass + + @staticmethod + def f3(x: object) -> None: + pass +[builtins fixtures/classmethod.pyi] + +[case testOverrideCallableAttributeWithSettableProperty] +from typing import Callable + +class A: + f: Callable[[str], None] + +class B(A): + @property + def f(self) -> Callable[[object], None]: pass + @func.setter + def f(self, x: object) -> None: pass +[builtins fixtures/property.pyi] + +[case testOverrideCallableAttributeWithMethodMutableOverride] +# flags: --enable-error-code=mutable-override +from typing import Callable + +class A: + f1: Callable[[str], None] + f2: Callable[[str], None] + f3: Callable[[str], None] + +class B(A): + def f1(self, x: object) -> None: pass # E: Covariant override of a mutable attribute (base class "A" defined the type as "Callable[[str], None]", override has type "Callable[[object], None]") + + @classmethod + def f2(cls, x: object) -> None: pass # E: Covariant override of a mutable attribute (base class "A" defined the type as "Callable[[str], None]", override has type "Callable[[object], None]") + + @staticmethod + def f3(x: object) -> None: pass # E: Covariant override of a mutable attribute (base class "A" defined the type as "Callable[[str], None]", override has type "Callable[[object], None]") +[builtins fixtures/classmethod.pyi] + +[case testOverrideCallableAttributeWithSettablePropertyMutableOverride] +# flags: --enable-error-code=mutable-override +from typing import Callable + +class A: + f: Callable[[str], None] + +class B(A): + @property # E: Covariant override of a mutable attribute (base class "A" defined the type as "Callable[[str], None]", override has type "Callable[[object], None]") + def f(self) -> Callable[[object], None]: pass + @func.setter + def f(self, x: object) -> None: pass +[builtins fixtures/property.pyi] -- Constructors -- ------------