Skip to content

Commit

Permalink
[PEP 695] Inherit variance if base class has explicit variance (#17787)
Browse files Browse the repository at this point in the history
Previously we only inferred variance based on member types, but if a
base class has explicit variance for some type variables, we need to
consider it as well.
  • Loading branch information
JukkaL authored Sep 19, 2024
1 parent 2a8c91e commit a646f33
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 1 deletion.
9 changes: 9 additions & 0 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2031,6 +2031,15 @@ def infer_variance(info: TypeInfo, i: int) -> bool:
contra = False
if settable:
co = False

# Infer variance from base classes, in case they have explicit variances
for base in info.bases:
base2 = expand_type(base, {tvar.id: object_type})
if not is_subtype(base, base2):
co = False
if not is_subtype(base2, base):
contra = False

if co:
v = COVARIANT
elif contra:
Expand Down
40 changes: 40 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -1891,3 +1891,43 @@ class A:

a = A()
v = a.f

[case testPEP695VarianceInheritedFromBaseWithExplicitVariance]
# flags: --enable-incomplete-feature=NewGenericSyntax
from typing import TypeVar, Generic

T = TypeVar("T")

class ParentInvariant(Generic[T]):
pass

class Invariant1[T](ParentInvariant[T]):
pass

a1: Invariant1[int] = Invariant1[float]() # E: Incompatible types in assignment (expression has type "Invariant1[float]", variable has type "Invariant1[int]")
a2: Invariant1[float] = Invariant1[int]() # E: Incompatible types in assignment (expression has type "Invariant1[int]", variable has type "Invariant1[float]")

T_contra = TypeVar("T_contra", contravariant=True)

class ParentContravariant(Generic[T_contra]):
pass

class Contravariant[T](ParentContravariant[T]):
pass

b1: Contravariant[int] = Contravariant[float]()
b2: Contravariant[float] = Contravariant[int]() # E: Incompatible types in assignment (expression has type "Contravariant[int]", variable has type "Contravariant[float]")

class Invariant2[T](ParentContravariant[T]):
def f(self) -> T: ...

c1: Invariant2[int] = Invariant2[float]() # E: Incompatible types in assignment (expression has type "Invariant2[float]", variable has type "Invariant2[int]")
c2: Invariant2[float] = Invariant2[int]() # E: Incompatible types in assignment (expression has type "Invariant2[int]", variable has type "Invariant2[float]")

class Multi[T, S](ParentInvariant[T], ParentContravariant[S]):
pass

d1: Multi[int, str] = Multi[float, str]() # E: Incompatible types in assignment (expression has type "Multi[float, str]", variable has type "Multi[int, str]")
d2: Multi[float, str] = Multi[int, str]() # E: Incompatible types in assignment (expression has type "Multi[int, str]", variable has type "Multi[float, str]")
d3: Multi[str, int] = Multi[str, float]()
d4: Multi[str, float] = Multi[str, int]() # E: Incompatible types in assignment (expression has type "Multi[str, int]", variable has type "Multi[str, float]")
2 changes: 1 addition & 1 deletion test-data/unit/fixtures/tuple-simple.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from typing import Iterable, TypeVar, Generic

T = TypeVar('T')
T = TypeVar('T', covariant=True)

class object:
def __init__(self): pass
Expand Down

0 comments on commit a646f33

Please sign in to comment.