diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 305cfa9de5f2..2d048ae2ee54 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -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) diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index c4a232079746..2beefa82cc0d 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -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: diff --git a/test-data/unit/check-python313.test b/test-data/unit/check-python313.test index d52cb575bc91..9b08b1eed6eb 100644 --- a/test-data/unit/check-python313.test +++ b/test-data/unit/check-python313.test @@ -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]