Skip to content

Commit

Permalink
Emit [mutable-override] for covariant override of attribute with me…
Browse files Browse the repository at this point in the history
…thod (#18058)

Fixes #18052

Given:
```python
# flags: --enable-error-code=mutable-override
from typing import Callable

class Parent:
    func: Callable[[str], None]

class Child(Parent):
    def func(self, x: object) -> None: pass 
```
Before:
```
Success: no issues found in 1 source file
```
After:
```
main.py:7 error: Covariant override of a mutable attribute (base class "Parent" defined the type as "Callable[[str], None]", override has type "Callable[[object], None]")
Found 1 error in 1 file (checked 1 source file)
```
  • Loading branch information
brianschubert authored Oct 27, 2024
1 parent 80843fe commit 7f09f0c
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 2 deletions.
20 changes: 18 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2136,7 +2136,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,
Expand All @@ -2146,6 +2146,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
Expand Down Expand Up @@ -2235,7 +2250,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:
Expand Down Expand Up @@ -2385,6 +2400,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.
Expand Down
66 changes: 66 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
-- ------------
Expand Down

0 comments on commit 7f09f0c

Please sign in to comment.