Skip to content

Commit

Permalink
Ignore dataclass.__replace__ LSP violations (#18464)
Browse files Browse the repository at this point in the history
Refining dataclass attributes with a narrower type has historically been
accepted. Mypy shouldn't emit an LSP warning for the synthesized
`__replace__` method added in Python 3.13 either.

Users are instead encouraged to enable `--enable-error-code
mutable-override` to highlight potential issues.

Fixes #18216
  • Loading branch information
cdce8p authored Jan 14, 2025
1 parent b68c545 commit 5e119d0
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 12 deletions.
15 changes: 9 additions & 6 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1980,12 +1980,15 @@ def check_method_override(
Return a list of base classes which contain an attribute with the method name.
"""
# Check against definitions in base classes.
check_override_compatibility = defn.name not in (
"__init__",
"__new__",
"__init_subclass__",
"__post_init__",
) and (self.options.check_untyped_defs or not defn.is_dynamic())
check_override_compatibility = (
defn.name not in ("__init__", "__new__", "__init_subclass__", "__post_init__")
and (self.options.check_untyped_defs or not defn.is_dynamic())
and (
# don't check override for synthesized __replace__ methods from dataclasses
defn.name != "__replace__"
or defn.info.metadata.get("dataclass_tag") is None
)
)
found_method_base_classes: list[TypeInfo] = []
for base in defn.info.mro[1:]:
result = self.check_method_or_accessor_override_for_base(
Expand Down
14 changes: 14 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,20 @@ class B(A):
def h(cls) -> int: pass
[builtins fixtures/classmethod.pyi]

[case testOverrideReplaceMethod]
# flags: --show-error-codes
from typing import Optional
from typing_extensions import Self
class A:
def __replace__(self, x: Optional[str]) -> Self: pass

class B(A):
def __replace__(self, x: str) -> Self: pass # E: \
# E: Argument 1 of "__replace__" is incompatible with supertype "A"; supertype defines the argument type as "Optional[str]" [override] \
# N: This violates the Liskov substitution principle \
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
[builtins fixtures/tuple.pyi]

[case testAllowCovarianceInReadOnlyAttributes]
from typing import Callable, TypeVar

Expand Down
25 changes: 19 additions & 6 deletions test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -2527,16 +2527,29 @@ Gen(2).__replace__(x="not an int") # E: Argument "x" to "__replace__" of "Gen"
[builtins fixtures/tuple.pyi]

[case testDunderReplaceCovariantOverride]
# flags: --python-version 3.13
# flags: --python-version 3.13 --enable-error-code mutable-override
from dataclasses import dataclass
from typing import Optional
from typing_extensions import dataclass_transform

@dataclass
class Base:
a: object
a: Optional[int]

@dataclass
class Child(Base): # E: Argument 1 of "__replace__" is incompatible with supertype "Base"; supertype defines the argument type as "object" \
# N: This violates the Liskov substitution principle \
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
a: int
class Child(Base):
a: int # E: Covariant override of a mutable attribute (base class "Base" defined the type as "Optional[int]", expression has type "int")

@dataclass
class Other(Base):
a: str # E: Incompatible types in assignment (expression has type "str", base class "Base" defined the type as "Optional[int]")

@dataclass_transform(kw_only_default=True)
class DCMeta(type): ...

class X(metaclass=DCMeta):
a: Optional[int]

class Y(X):
a: int # E: Covariant override of a mutable attribute (base class "X" defined the type as "Optional[int]", expression has type "int")
[builtins fixtures/tuple.pyi]

0 comments on commit 5e119d0

Please sign in to comment.