Skip to content

Commit de52272

Browse files
committed
Emit [mutable-override] for covariant override of attribute with method
1 parent 2b033cb commit de52272

File tree

2 files changed

+84
-2
lines changed

2 files changed

+84
-2
lines changed

mypy/checker.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,7 +2148,7 @@ def check_method_override_for_base_with_name(
21482148
pass
21492149
elif isinstance(original_type, FunctionLike) and isinstance(typ, FunctionLike):
21502150
# Check that the types are compatible.
2151-
self.check_override(
2151+
ok = self.check_override(
21522152
typ,
21532153
original_type,
21542154
defn.name,
@@ -2158,6 +2158,21 @@ def check_method_override_for_base_with_name(
21582158
override_class_or_static,
21592159
context,
21602160
)
2161+
if (
2162+
ok
2163+
and original_node
2164+
and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes
2165+
and self.is_writable_attribute(original_node)
2166+
and not is_subtype(original_type, typ, ignore_pos_arg_names=True)
2167+
):
2168+
base_str, override_str = format_type_distinctly(
2169+
original_type, typ, options=self.options
2170+
)
2171+
msg = message_registry.COVARIANT_OVERRIDE_OF_MUTABLE_ATTRIBUTE.with_additional_msg(
2172+
f' (base class "{base.name}" defined the type as {base_str},'
2173+
f" override has type {override_str})"
2174+
)
2175+
self.fail(msg, context)
21612176
elif is_equivalent(original_type, typ):
21622177
# Assume invariance for a non-callable attribute here. Note
21632178
# that this doesn't affect read-only properties which can have
@@ -2247,7 +2262,7 @@ def check_override(
22472262
original_class_or_static: bool,
22482263
override_class_or_static: bool,
22492264
node: Context,
2250-
) -> None:
2265+
) -> bool:
22512266
"""Check a method override with given signatures.
22522267
22532268
Arguments:
@@ -2397,6 +2412,7 @@ def erase_override(t: Type) -> Type:
23972412
node,
23982413
code=codes.OVERRIDE,
23992414
)
2415+
return not fail
24002416

24012417
def check__exit__return_type(self, defn: FuncItem) -> None:
24022418
"""Generate error if the return type of __exit__ is problematic.

test-data/unit/check-classes.test

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,72 @@ class B(A):
710710
@dec
711711
def f(self) -> Y: pass
712712

713+
[case testOverrideCallableAttributeWithMethod]
714+
from typing import Callable
715+
716+
class A:
717+
f1: Callable[[str], None]
718+
f2: Callable[[str], None]
719+
f3: Callable[[str], None]
720+
721+
class B(A):
722+
def f1(self, x: object) -> None:
723+
pass
724+
725+
@classmethod
726+
def f2(cls, x: object) -> None:
727+
pass
728+
729+
@staticmethod
730+
def f3(x: object) -> None:
731+
pass
732+
[builtins fixtures/classmethod.pyi]
733+
734+
[case testOverrideCallableAttributeWithSettableProperty]
735+
from typing import Callable
736+
737+
class A:
738+
f: Callable[[str], None]
739+
740+
class B(A):
741+
@property
742+
def f(self) -> Callable[[object], None]: pass
743+
@func.setter
744+
def f(self, x: object) -> None: pass
745+
[builtins fixtures/property.pyi]
746+
747+
[case testOverrideCallableAttributeWithMethodMutableOverride]
748+
# flags: --enable-error-code=mutable-override
749+
from typing import Callable
750+
751+
class A:
752+
f1: Callable[[str], None]
753+
f2: Callable[[str], None]
754+
f3: Callable[[str], None]
755+
756+
class B(A):
757+
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]")
758+
759+
@classmethod
760+
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]")
761+
762+
@staticmethod
763+
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]")
764+
[builtins fixtures/classmethod.pyi]
765+
766+
[case testOverrideCallableAttributeWithSettablePropertyMutableOverride]
767+
# flags: --enable-error-code=mutable-override
768+
from typing import Callable
769+
770+
class A:
771+
f: Callable[[str], None]
772+
773+
class B(A):
774+
@property # E: Covariant override of a mutable attribute (base class "A" defined the type as "Callable[[str], None]", override has type "Callable[[object], None]")
775+
def f(self) -> Callable[[object], None]: pass
776+
@func.setter
777+
def f(self, x: object) -> None: pass
778+
[builtins fixtures/property.pyi]
713779

714780
-- Constructors
715781
-- ------------

0 commit comments

Comments
 (0)