Skip to content
Draft
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
12 changes: 12 additions & 0 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2274,6 +2274,18 @@ def infer_variance(info: TypeInfo, i: int) -> bool:
if member in ("__init__", "__new__", "__mypy-replace"):
continue

# Members synthesized by plugins must not influence variance
# inference. attrs, for example, generates ordering methods whose
# "other" parameter is typed as the class's own Self[T], plus an
# __attrs_attrs__ tuple of (invariant) Attribute[T]; dataclasses
# generate __replace__. These mention the type variable only because
# they are derived from the user's own declarations -- and those
# declarations are not plugin-generated, so they still drive the
# inferred variance.
sym = info.get(member)
if sym is not None and sym.plugin_generated:
continue

if isinstance(self_type, TupleType):
self_type = mypy.typeops.tuple_fallback(self_type)
flags = get_member_flags(member, self_type)
Expand Down
36 changes: 36 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,42 @@ inv2: Invariant[int] = Invariant[float]([1]) # E: Incompatible types in assignm
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-full.pyi]

[case testPEP695InferVarianceWithAttrsFrozen]
import attrs

# attrs synthesizes ordering dunders (__lt__/__le__/__gt__/__ge__) whose
# ``other`` parameter is typed as the class's own ``Self[T]``. Those methods
# are plugin-generated and must not drag T into a contravariant position
# during PEP 695 variance inference. A frozen class using T only covariantly
# should be inferred covariant.
@attrs.frozen
class Covariant[T]:
x: T
def get(self) -> T:
return self.x

cov1: Covariant[object] = Covariant[int](1)
cov2: Covariant[int] = Covariant[object](1) # E: Incompatible types in assignment (expression has type "Covariant[object]", variable has type "Covariant[int]")

# A mutable attribute still makes the class invariant.
@attrs.define
class Invariant[T]:
x: T

inv1: Invariant[object] = Invariant[int](1) # E: Incompatible types in assignment (expression has type "Invariant[int]", variable has type "Invariant[object]")
inv2: Invariant[int] = Invariant[object](1) # E: Incompatible types in assignment (expression has type "Invariant[object]", variable has type "Invariant[int]")

# A user-written method with T in a parameter must still be honored: only
# plugin-generated methods are skipped, so this class stays contravariant.
@attrs.frozen
class Contravariant[T]:
def feed(self, x: T) -> None: ...

con1: Contravariant[int] = Contravariant[object]()
con2: Contravariant[object] = Contravariant[int]() # E: Incompatible types in assignment (expression has type "Contravariant[int]", variable has type "Contravariant[object]")
[builtins fixtures/plugin_attrs.pyi]
[typing fixtures/typing-full.pyi]

[case testPEP695InferVarianceCalculateOnDemand]
class Covariant[T]:
def __init__(self) -> None:
Expand Down
16 changes: 16 additions & 0 deletions test-data/unit/check-python313.test
Original file line number Diff line number Diff line change
Expand Up @@ -480,3 +480,19 @@ reveal_type(x) # N: Revealed type is "builtins.list[tuple[()]]"
reveal_type(y) # N: Revealed type is "builtins.list[tuple[()]]"
reveal_type(z) # N: Revealed type is "builtins.list[tuple[()]]"
[builtins fixtures/tuple.pyi]

[case testPEP695InferVarianceInFrozenDataclass]
# On Python 3.13+ the dataclass plugin synthesizes a __replace__ method whose
# keyword parameters reuse the field types. Being plugin-generated, it must not
# drag the type variable into a contravariant position and make an otherwise
# covariant frozen dataclass invariant.
from dataclasses import dataclass

@dataclass(frozen=True)
class Covariant[T]:
x: T

cov1: Covariant[float] = Covariant[int](1)
cov2: Covariant[int] = Covariant[float](1) # E: Incompatible types in assignment (expression has type "Covariant[float]", variable has type "Covariant[int]")
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-full.pyi]
Loading