Skip to content

Allow union-with-callable attributes to be overridden by methods #18018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2146,6 +2146,7 @@ def check_method_override_for_base_with_name(
override_class_or_static,
context,
)
# Check if this override is covariant.
if (
ok
and original_node
Expand All @@ -2161,6 +2162,25 @@ def check_method_override_for_base_with_name(
f" override has type {override_str})"
)
self.fail(msg, context)
elif isinstance(original_type, UnionType) and any(
is_subtype(typ, orig_typ, ignore_pos_arg_names=True)
for orig_typ in original_type.items
):
# This method is a subtype of at least one union variant.
if (
original_node
and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes
and self.is_writable_attribute(original_node)
):
# Covariant override of mutable attribute.
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
78 changes: 64 additions & 14 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ class Base:
class Derived(Base):
__hash__ = 1 # E: Incompatible types in assignment (expression has type "int", base class "Base" defined the type as "Callable[[Base], int]")


[case testOverridePartialAttributeWithMethod]
# This was crashing: https://github.com/python/mypy/issues/11686.
class Base:
Expand Down Expand Up @@ -731,19 +730,6 @@ class B(A):
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
Expand All @@ -763,6 +749,19 @@ class B(A):
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 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 testOverrideCallableAttributeWithSettablePropertyMutableOverride]
# flags: --enable-error-code=mutable-override
from typing import Callable
Expand All @@ -777,6 +776,57 @@ class B(A):
def f(self, x: object) -> None: pass
[builtins fixtures/property.pyi]

[case testOverrideCallableUnionAttributeWithMethod]
from typing import Callable, Union

class A:
f1: Union[Callable[[str], str], str]
f2: Union[Callable[[str], str], str]
f3: Union[Callable[[str], str], str]
f4: Union[Callable[[str], str], str]

class B(A):
def f1(self, x: str) -> str:
pass

def f2(self, x: object) -> str:
pass

@classmethod
def f3(cls, x: str) -> str:
pass

@staticmethod
def f4(x: str) -> str:
pass
[builtins fixtures/classmethod.pyi]

[case testOverrideCallableUnionAttributeWithMethodMutableOverride]
# flags: --enable-error-code=mutable-override
from typing import Callable, Union

class A:
f1: Union[Callable[[str], str], str]
f2: Union[Callable[[str], str], str]
f3: Union[Callable[[str], str], str]
f4: Union[Callable[[str], str], str]

class B(A):
def f1(self, x: str) -> str: # E: Covariant override of a mutable attribute (base class "A" defined the type as "Union[Callable[[str], str], str]", override has type "Callable[[str], str]")
pass

def f2(self, x: object) -> str: # E: Covariant override of a mutable attribute (base class "A" defined the type as "Union[Callable[[str], str], str]", override has type "Callable[[object], str]")
pass

@classmethod
def f3(cls, x: str) -> str: # E: Covariant override of a mutable attribute (base class "A" defined the type as "Union[Callable[[str], str], str]", override has type "Callable[[str], str]")
pass

@staticmethod
def f4(x: str) -> str: # E: Covariant override of a mutable attribute (base class "A" defined the type as "Union[Callable[[str], str], str]", override has type "Callable[[str], str]")
pass
[builtins fixtures/classmethod.pyi]

-- Constructors
-- ------------

Expand Down
Loading