Skip to content

Commit a646f33

Browse files
authored
[PEP 695] Inherit variance if base class has explicit variance (#17787)
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.
1 parent 2a8c91e commit a646f33

File tree

3 files changed

+50
-1
lines changed

3 files changed

+50
-1
lines changed

mypy/subtypes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,15 @@ def infer_variance(info: TypeInfo, i: int) -> bool:
20312031
contra = False
20322032
if settable:
20332033
co = False
2034+
2035+
# Infer variance from base classes, in case they have explicit variances
2036+
for base in info.bases:
2037+
base2 = expand_type(base, {tvar.id: object_type})
2038+
if not is_subtype(base, base2):
2039+
co = False
2040+
if not is_subtype(base2, base):
2041+
contra = False
2042+
20342043
if co:
20352044
v = COVARIANT
20362045
elif contra:

test-data/unit/check-python312.test

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,3 +1891,43 @@ class A:
18911891

18921892
a = A()
18931893
v = a.f
1894+
1895+
[case testPEP695VarianceInheritedFromBaseWithExplicitVariance]
1896+
# flags: --enable-incomplete-feature=NewGenericSyntax
1897+
from typing import TypeVar, Generic
1898+
1899+
T = TypeVar("T")
1900+
1901+
class ParentInvariant(Generic[T]):
1902+
pass
1903+
1904+
class Invariant1[T](ParentInvariant[T]):
1905+
pass
1906+
1907+
a1: Invariant1[int] = Invariant1[float]() # E: Incompatible types in assignment (expression has type "Invariant1[float]", variable has type "Invariant1[int]")
1908+
a2: Invariant1[float] = Invariant1[int]() # E: Incompatible types in assignment (expression has type "Invariant1[int]", variable has type "Invariant1[float]")
1909+
1910+
T_contra = TypeVar("T_contra", contravariant=True)
1911+
1912+
class ParentContravariant(Generic[T_contra]):
1913+
pass
1914+
1915+
class Contravariant[T](ParentContravariant[T]):
1916+
pass
1917+
1918+
b1: Contravariant[int] = Contravariant[float]()
1919+
b2: Contravariant[float] = Contravariant[int]() # E: Incompatible types in assignment (expression has type "Contravariant[int]", variable has type "Contravariant[float]")
1920+
1921+
class Invariant2[T](ParentContravariant[T]):
1922+
def f(self) -> T: ...
1923+
1924+
c1: Invariant2[int] = Invariant2[float]() # E: Incompatible types in assignment (expression has type "Invariant2[float]", variable has type "Invariant2[int]")
1925+
c2: Invariant2[float] = Invariant2[int]() # E: Incompatible types in assignment (expression has type "Invariant2[int]", variable has type "Invariant2[float]")
1926+
1927+
class Multi[T, S](ParentInvariant[T], ParentContravariant[S]):
1928+
pass
1929+
1930+
d1: Multi[int, str] = Multi[float, str]() # E: Incompatible types in assignment (expression has type "Multi[float, str]", variable has type "Multi[int, str]")
1931+
d2: Multi[float, str] = Multi[int, str]() # E: Incompatible types in assignment (expression has type "Multi[int, str]", variable has type "Multi[float, str]")
1932+
d3: Multi[str, int] = Multi[str, float]()
1933+
d4: Multi[str, float] = Multi[str, int]() # E: Incompatible types in assignment (expression has type "Multi[str, int]", variable has type "Multi[str, float]")

test-data/unit/fixtures/tuple-simple.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from typing import Iterable, TypeVar, Generic
77

8-
T = TypeVar('T')
8+
T = TypeVar('T', covariant=True)
99

1010
class object:
1111
def __init__(self): pass

0 commit comments

Comments
 (0)