From 9ae3b3059cb7ad19744e0cbed7308d179755c462 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 19 Sep 2024 14:54:31 +0100 Subject: [PATCH] [PEP 695] Inherit variance if base class has explicit variance 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. --- mypy/subtypes.py | 9 ++++++ test-data/unit/check-python312.test | 40 ++++++++++++++++++++++++ test-data/unit/fixtures/tuple-simple.pyi | 2 +- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 5c4471cc5b62..df040dcb1311 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -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: diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index d0a39f7e56a6..47e5350e0e11 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -1883,3 +1883,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]") diff --git a/test-data/unit/fixtures/tuple-simple.pyi b/test-data/unit/fixtures/tuple-simple.pyi index 6c816c1c5b7a..07f9edf63cdd 100644 --- a/test-data/unit/fixtures/tuple-simple.pyi +++ b/test-data/unit/fixtures/tuple-simple.pyi @@ -5,7 +5,7 @@ from typing import Iterable, TypeVar, Generic -T = TypeVar('T') +T = TypeVar('T', covariant=True) class object: def __init__(self): pass