From 01e2c195e62755bbd00425facd91f2498065485c Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 23 Sep 2022 01:00:16 -0700 Subject: [PATCH 1/6] Fix unsound variance --- mypy/checker.py | 15 +++++++++++++++ test-data/unit/check-generic-subtyping.test | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index de98fa0fa179..cb6320b09ed1 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2072,6 +2072,21 @@ def visit_class_def(self, defn: ClassDef) -> None: self.allow_abstract_call = old_allow_abstract_call # TODO: Apply the sig to the actual TypeInfo so we can handle decorators # that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]]) + if typ.defn.type_vars: + for base in typ.bases: + for base_tvar, base_declared_tvar in zip(base.args, base.type.defn.type_vars): + if ( + isinstance(base_tvar, TypeVarType) + and base_tvar.variance != INVARIANT + and isinstance(base_declared_tvar, TypeVarType) + and base_declared_tvar.variance != base_tvar.variance + ): + self.fail( + f'Variance of TypeVar "{base_tvar.name}" incompatible ' + "with variance in parent type", + context=defn, + ) + if typ.is_protocol and typ.defn.type_vars: self.check_protocol_variance(defn) if not defn.has_incompatible_baseclass and defn.info.is_enum: diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test index bd1f487bc895..1f06bc7c540a 100644 --- a/test-data/unit/check-generic-subtyping.test +++ b/test-data/unit/check-generic-subtyping.test @@ -1033,3 +1033,21 @@ x2: X2[str, int] reveal_type(iter(x2)) # N: Revealed type is "typing.Iterator[builtins.int]" reveal_type([*x2]) # N: Revealed type is "builtins.list[builtins.int]" [builtins fixtures/dict.pyi] + +[case testIncompatibleVariance] +from typing import TypeVar, Generic +T = TypeVar('T') +T_co = TypeVar('T_co', covariant=True) +T_contra = TypeVar('T_contra', contravariant=True) + +class A(Generic[T_co]): ... +class B(A[T_contra], Generic[T_contra]): ... # E: Variance of TypeVar "T_contra" incompatible with variance in parent type + +class C(Generic[T_contra]): ... +class D(C[T_co], Generic[T_co]): ... # E: Variance of TypeVar "T_co" incompatible with variance in parent type + +class E(Generic[T]): ... +class F(E[T_co], Generic[T_co]): ... # E: Variance of TypeVar "T_co" incompatible with variance in parent type + +class G(Generic[T]): ... +class H(G[T_contra], Generic[T_contra]): ... # E: Variance of TypeVar "T_contra" incompatible with variance in parent type From 11b09e14967e64097ff1d9a283a905c322524d0b Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 23 Sep 2022 01:12:12 -0700 Subject: [PATCH 2/6] type-ignore in typeshed --- mypy/typeshed/stdlib/_typeshed/__init__.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/typeshed/stdlib/_typeshed/__init__.pyi b/mypy/typeshed/stdlib/_typeshed/__init__.pyi index 89ca9d81619a..740727c69e9d 100644 --- a/mypy/typeshed/stdlib/_typeshed/__init__.pyi +++ b/mypy/typeshed/stdlib/_typeshed/__init__.pyi @@ -118,7 +118,7 @@ class SupportsKeysAndGetItem(Protocol[_KT, _VT_co]): def __getitem__(self, __k: _KT) -> _VT_co: ... # stable -class SupportsGetItem(Container[_KT_contra], Protocol[_KT_contra, _VT_co]): +class SupportsGetItem(Container[_KT_contra], Protocol[_KT_contra, _VT_co]): # type: ignore def __getitem__(self, __k: _KT_contra) -> _VT_co: ... # stable From 04f5d6bd2a97be41db995f2c2faa75401e10a4ab Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 23 Sep 2022 01:18:04 -0700 Subject: [PATCH 3/6] alias --- mypy/checker.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index cb6320b09ed1..50b1f9f6cfa8 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2073,13 +2073,13 @@ def visit_class_def(self, defn: ClassDef) -> None: # TODO: Apply the sig to the actual TypeInfo so we can handle decorators # that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]]) if typ.defn.type_vars: - for base in typ.bases: - for base_tvar, base_declared_tvar in zip(base.args, base.type.defn.type_vars): + for base_inst in typ.bases: + for base_tvar, base_decl_tvar in zip(base_inst.args, base_inst.type.defn.type_vars): if ( isinstance(base_tvar, TypeVarType) and base_tvar.variance != INVARIANT - and isinstance(base_declared_tvar, TypeVarType) - and base_declared_tvar.variance != base_tvar.variance + and isinstance(base_decl_tvar, TypeVarType) + and base_decl_tvar.variance != base_tvar.variance ): self.fail( f'Variance of TypeVar "{base_tvar.name}" incompatible ' From d4b85f180b4e5674ca8055dc6cc931896b898904 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 23 Sep 2022 11:08:50 -0700 Subject: [PATCH 4/6] black --- mypy/checker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 50b1f9f6cfa8..dd313804b0ec 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2074,7 +2074,9 @@ def visit_class_def(self, defn: ClassDef) -> None: # that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]]) if typ.defn.type_vars: for base_inst in typ.bases: - for base_tvar, base_decl_tvar in zip(base_inst.args, base_inst.type.defn.type_vars): + for base_tvar, base_decl_tvar in zip( + base_inst.args, base_inst.type.defn.type_vars + ): if ( isinstance(base_tvar, TypeVarType) and base_tvar.variance != INVARIANT From 18b7aa99cc13850ed258694692c88a5b23155198 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 23 Sep 2022 11:16:43 -0700 Subject: [PATCH 5/6] fix fixture --- test-data/unit/fixtures/typing-full.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/fixtures/typing-full.pyi b/test-data/unit/fixtures/typing-full.pyi index dad30dd7bcee..c406da986818 100644 --- a/test-data/unit/fixtures/typing-full.pyi +++ b/test-data/unit/fixtures/typing-full.pyi @@ -160,8 +160,8 @@ class SupportsAbs(Protocol[T_co]): def runtime_checkable(cls: T) -> T: return cls -class ContextManager(Generic[T]): - def __enter__(self) -> T: pass +class ContextManager(Generic[T_co]): + def __enter__(self) -> T_co: pass # Use Any because not all the precise types are in the fixtures. def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any: pass From b2396007bcc7b7618dd6c3a879e5ef2ad4e6ee66 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 26 Sep 2022 09:49:29 -0700 Subject: [PATCH 6/6] error code --- mypy/checker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/checker.py b/mypy/checker.py index dd313804b0ec..2f2f6036bfb5 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2087,6 +2087,7 @@ def visit_class_def(self, defn: ClassDef) -> None: f'Variance of TypeVar "{base_tvar.name}" incompatible ' "with variance in parent type", context=defn, + code=codes.TYPE_VAR, ) if typ.is_protocol and typ.defn.type_vars: