From 825f14480343aba96918828d9178f6700a0e3890 Mon Sep 17 00:00:00 2001
From: kotlinisland <65446343+kotlinisland@users.noreply.github.com>
Date: Mon, 9 Mar 2026 12:58:09 +1000
Subject: [PATCH 1/2] specify that parameter specification should have variance
---
.../mypy/generics_paramspec_variance.toml | 22 ++++++++++
.../pyrefly/generics_paramspec_variance.toml | 24 +++++++++++
.../pyright/generics_paramspec_variance.toml | 40 +++++++++++++++++
conformance/results/results.html | 7 +++
.../ty/generics_paramspec_variance.toml | 30 +++++++++++++
.../zuban/generics_paramspec_variance.toml | 22 ++++++++++
.../tests/generics_paramspec_variance.py | 43 +++++++++++++++++++
docs/spec/generics.rst | 11 +++--
8 files changed, 193 insertions(+), 6 deletions(-)
create mode 100644 conformance/results/mypy/generics_paramspec_variance.toml
create mode 100644 conformance/results/pyrefly/generics_paramspec_variance.toml
create mode 100644 conformance/results/pyright/generics_paramspec_variance.toml
create mode 100644 conformance/results/ty/generics_paramspec_variance.toml
create mode 100644 conformance/results/zuban/generics_paramspec_variance.toml
create mode 100644 conformance/tests/generics_paramspec_variance.py
diff --git a/conformance/results/mypy/generics_paramspec_variance.toml b/conformance/results/mypy/generics_paramspec_variance.toml
new file mode 100644
index 000000000..188439ca3
--- /dev/null
+++ b/conformance/results/mypy/generics_paramspec_variance.toml
@@ -0,0 +1,22 @@
+conformant = "Unsupported"
+conformance_automated = "Fail"
+errors_diff = """
+Line 21: Expected 1 errors
+Line 29: Expected 1 errors
+Line 42: Expected 1 errors
+Line 22: Unexpected errors ['generics_paramspec_variance.py:22: error: Incompatible types in assignment (expression has type "CovariantParamSpec[[int]]", variable has type "CovariantParamSpec[[object]]") [assignment]']
+Line 24: Unexpected errors ['generics_paramspec_variance.py:24: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]']
+Line 28: Unexpected errors ['generics_paramspec_variance.py:28: error: Missing return statement [empty-body]']
+Line 34: Unexpected errors ['generics_paramspec_variance.py:34: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]']
+Line 43: Unexpected errors ['generics_paramspec_variance.py:43: error: Incompatible types in assignment (expression has type "CovariantParamSpecOld[[int]]", variable has type "CovariantParamSpecOld[[object]]") [assignment]']
+"""
+output = """
+generics_paramspec_variance.py:13: error: Incompatible types in assignment (expression has type "ContravariantParamSpec[[int]]", variable has type "ContravariantParamSpec[[object]]") [assignment]
+generics_paramspec_variance.py:22: error: Incompatible types in assignment (expression has type "CovariantParamSpec[[int]]", variable has type "CovariantParamSpec[[object]]") [assignment]
+generics_paramspec_variance.py:24: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]
+generics_paramspec_variance.py:28: error: Missing return statement [empty-body]
+generics_paramspec_variance.py:31: error: Incompatible types in assignment (expression has type "ContravariantParamSpecOld[[int]]", variable has type "ContravariantParamSpecOld[[object]]") [assignment]
+generics_paramspec_variance.py:34: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]
+generics_paramspec_variance.py:38: error: Missing return statement [empty-body]
+generics_paramspec_variance.py:43: error: Incompatible types in assignment (expression has type "CovariantParamSpecOld[[int]]", variable has type "CovariantParamSpecOld[[object]]") [assignment]
+"""
diff --git a/conformance/results/pyrefly/generics_paramspec_variance.toml b/conformance/results/pyrefly/generics_paramspec_variance.toml
new file mode 100644
index 000000000..890b4d182
--- /dev/null
+++ b/conformance/results/pyrefly/generics_paramspec_variance.toml
@@ -0,0 +1,24 @@
+conformant = "Unsupported"
+conformance_automated = "Fail"
+errors_diff = """
+Line 13: Expected 1 errors
+Line 21: Expected 1 errors
+Line 29: Expected 1 errors
+Line 38: Expected 1 errors
+Line 14: Unexpected errors ['`ContravariantParamSpec[[object]]` is not assignable to `ContravariantParamSpec[[int]]` [bad-assignment]']
+Line 22: Unexpected errors ['`CovariantParamSpec[[int]]` is not assignable to `CovariantParamSpec[[object]]` [bad-assignment]']
+Line 24: Unexpected errors ['Unexpected keyword argument `contravariant` to ParamSpec [invalid-param-spec]']
+Line 32: Unexpected errors ['`ContravariantParamSpecOld[[object]]` is not assignable to `ContravariantParamSpecOld[[int]]` [bad-assignment]']
+Line 34: Unexpected errors ['Unexpected keyword argument `covariant` to ParamSpec [invalid-param-spec]']
+Line 43: Unexpected errors ['`CovariantParamSpecOld[[int]]` is not assignable to `CovariantParamSpecOld[[object]]` [bad-assignment]']
+"""
+output = """
+ERROR generics_paramspec_variance.py:14:39-71: `ContravariantParamSpec[[object]]` is not assignable to `ContravariantParamSpec[[int]]` [bad-assignment]
+ERROR generics_paramspec_variance.py:22:39-64: `CovariantParamSpec[[int]]` is not assignable to `CovariantParamSpec[[object]]` [bad-assignment]
+ERROR generics_paramspec_variance.py:24:24-42: Unexpected keyword argument `contravariant` to ParamSpec [invalid-param-spec]
+ERROR generics_paramspec_variance.py:31:49-81: `ContravariantParamSpecOld[[int]]` is not assignable to `ContravariantParamSpecOld[[object]]` [bad-assignment]
+ERROR generics_paramspec_variance.py:32:46-81: `ContravariantParamSpecOld[[object]]` is not assignable to `ContravariantParamSpecOld[[int]]` [bad-assignment]
+ERROR generics_paramspec_variance.py:34:26-40: Unexpected keyword argument `covariant` to ParamSpec [invalid-param-spec]
+ERROR generics_paramspec_variance.py:42:43-74: `CovariantParamSpecOld[[object]]` is not assignable to `CovariantParamSpecOld[[int]]` [bad-assignment]
+ERROR generics_paramspec_variance.py:43:46-74: `CovariantParamSpecOld[[int]]` is not assignable to `CovariantParamSpecOld[[object]]` [bad-assignment]
+"""
diff --git a/conformance/results/pyright/generics_paramspec_variance.toml b/conformance/results/pyright/generics_paramspec_variance.toml
new file mode 100644
index 000000000..df031a431
--- /dev/null
+++ b/conformance/results/pyright/generics_paramspec_variance.toml
@@ -0,0 +1,40 @@
+conformant = "Unsupported"
+conformance_automated = "Fail"
+errors_diff = """
+Line 29: Expected 1 errors
+Line 38: Expected 1 errors
+Line 14: Unexpected errors ['generics_paramspec_variance.py:14:39 - error: Type "ContravariantParamSpec[(object)]" is not assignable to declared type "ContravariantParamSpec[(int)]"']
+Line 22: Unexpected errors ['generics_paramspec_variance.py:22:39 - error: Type "CovariantParamSpec[(int)]" is not assignable to declared type "CovariantParamSpec[(object)]"']
+Line 24: Unexpected errors ['generics_paramspec_variance.py:24:24 - error: "contravariant" is unknown parameter to ParamSpec (reportGeneralTypeIssues)']
+Line 32: Unexpected errors ['generics_paramspec_variance.py:32:46 - error: Type "ContravariantParamSpecOld[(object)]" is not assignable to declared type "ContravariantParamSpecOld[(int)]"']
+Line 34: Unexpected errors ['generics_paramspec_variance.py:34:26 - error: "covariant" is unknown parameter to ParamSpec (reportGeneralTypeIssues)']
+Line 43: Unexpected errors ['generics_paramspec_variance.py:43:46 - error: Type "CovariantParamSpecOld[(int)]" is not assignable to declared type "CovariantParamSpecOld[(object)]"']
+"""
+output = """
+generics_paramspec_variance.py:13:42 - error: Type "ContravariantParamSpec[(int)]" is not assignable to declared type "ContravariantParamSpec[(object)]"
+ "ContravariantParamSpec[(int)]" is not assignable to "ContravariantParamSpec[(object)]"
+ Type parameter "InP@ContravariantParamSpec" is invariant, but "(int)" is not the same as "(object)" (reportAssignmentType)
+generics_paramspec_variance.py:14:39 - error: Type "ContravariantParamSpec[(object)]" is not assignable to declared type "ContravariantParamSpec[(int)]"
+ "ContravariantParamSpec[(object)]" is not assignable to "ContravariantParamSpec[(int)]"
+ Type parameter "InP@ContravariantParamSpec" is invariant, but "(object)" is not the same as "(int)" (reportAssignmentType)
+generics_paramspec_variance.py:21:36 - error: Type "CovariantParamSpec[(object)]" is not assignable to declared type "CovariantParamSpec[(int)]"
+ "CovariantParamSpec[(object)]" is not assignable to "CovariantParamSpec[(int)]"
+ Type parameter "OutP@CovariantParamSpec" is invariant, but "(object)" is not the same as "(int)" (reportAssignmentType)
+generics_paramspec_variance.py:22:39 - error: Type "CovariantParamSpec[(int)]" is not assignable to declared type "CovariantParamSpec[(object)]"
+ "CovariantParamSpec[(int)]" is not assignable to "CovariantParamSpec[(object)]"
+ Type parameter "OutP@CovariantParamSpec" is invariant, but "(int)" is not the same as "(object)" (reportAssignmentType)
+generics_paramspec_variance.py:24:24 - error: "contravariant" is unknown parameter to ParamSpec (reportGeneralTypeIssues)
+generics_paramspec_variance.py:31:49 - error: Type "ContravariantParamSpecOld[(int)]" is not assignable to declared type "ContravariantParamSpecOld[(object)]"
+ "ContravariantParamSpecOld[(int)]" is not assignable to "ContravariantParamSpecOld[(object)]"
+ Type parameter "InP@ContravariantParamSpecOld" is invariant, but "(int)" is not the same as "(object)" (reportAssignmentType)
+generics_paramspec_variance.py:32:46 - error: Type "ContravariantParamSpecOld[(object)]" is not assignable to declared type "ContravariantParamSpecOld[(int)]"
+ "ContravariantParamSpecOld[(object)]" is not assignable to "ContravariantParamSpecOld[(int)]"
+ Type parameter "InP@ContravariantParamSpecOld" is invariant, but "(object)" is not the same as "(int)" (reportAssignmentType)
+generics_paramspec_variance.py:34:26 - error: "covariant" is unknown parameter to ParamSpec (reportGeneralTypeIssues)
+generics_paramspec_variance.py:42:43 - error: Type "CovariantParamSpecOld[(object)]" is not assignable to declared type "CovariantParamSpecOld[(int)]"
+ "CovariantParamSpecOld[(object)]" is not assignable to "CovariantParamSpecOld[(int)]"
+ Type parameter "OutP@CovariantParamSpecOld" is invariant, but "(object)" is not the same as "(int)" (reportAssignmentType)
+generics_paramspec_variance.py:43:46 - error: Type "CovariantParamSpecOld[(int)]" is not assignable to declared type "CovariantParamSpecOld[(object)]"
+ "CovariantParamSpecOld[(int)]" is not assignable to "CovariantParamSpecOld[(object)]"
+ Type parameter "OutP@CovariantParamSpecOld" is invariant, but "(int)" is not the same as "(object)" (reportAssignmentType)
+"""
diff --git a/conformance/results/results.html b/conformance/results/results.html
index 3ddb751cd..9dad0c6aa 100644
--- a/conformance/results/results.html
+++ b/conformance/results/results.html
@@ -335,6 +335,13 @@
Python Type System Conformance Test Results
Pass |
Pass |
+| generics_paramspec_variance |
+Unsupported |
+Unsupported |
+Unsupported |
+Unsupported |
+Unsupported |
+
| generics_scoping |
Pass |
Pass |
diff --git a/conformance/results/ty/generics_paramspec_variance.toml b/conformance/results/ty/generics_paramspec_variance.toml
new file mode 100644
index 000000000..8e48da703
--- /dev/null
+++ b/conformance/results/ty/generics_paramspec_variance.toml
@@ -0,0 +1,30 @@
+conformant = "Unsupported"
+conformance_automated = "Fail"
+errors_diff = """
+Line 13: Expected 1 errors
+Line 21: Expected 1 errors
+Line 31: Expected 1 errors
+Line 42: Expected 1 errors
+Line 24: Unexpected errors ['generics_paramspec_variance.py:24:7: error[invalid-paramspec] The variance and bound arguments for `ParamSpec` do not have defined semantics yet']
+Line 27: Unexpected errors ['generics_paramspec_variance.py:27:33: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`']
+Line 28: Unexpected errors ['generics_paramspec_variance.py:28:23: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `(...) -> Unknown`', 'generics_paramspec_variance.py:28:32: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a return type annotation', 'generics_paramspec_variance.py:28:32: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`']
+Line 34: Unexpected errors ['generics_paramspec_variance.py:34:8: error[invalid-paramspec] The variance and bound arguments for `ParamSpec` do not have defined semantics yet']
+Line 37: Unexpected errors ['generics_paramspec_variance.py:37:29: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`']
+Line 39: Unexpected errors ['generics_paramspec_variance.py:39:34: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a parameter annotation', 'generics_paramspec_variance.py:39:34: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`']
+"""
+output = """
+generics_paramspec_variance.py:24:7: error[invalid-paramspec] The variance and bound arguments for `ParamSpec` do not have defined semantics yet
+generics_paramspec_variance.py:27:33: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`
+generics_paramspec_variance.py:28:23: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `(...) -> Unknown`
+generics_paramspec_variance.py:28:32: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a return type annotation
+generics_paramspec_variance.py:28:32: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`
+generics_paramspec_variance.py:29:34: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a parameter annotation
+generics_paramspec_variance.py:29:34: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`
+generics_paramspec_variance.py:34:8: error[invalid-paramspec] The variance and bound arguments for `ParamSpec` do not have defined semantics yet
+generics_paramspec_variance.py:37:29: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`
+generics_paramspec_variance.py:38:23: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `(...) -> Unknown`
+generics_paramspec_variance.py:38:32: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a return type annotation
+generics_paramspec_variance.py:38:32: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`
+generics_paramspec_variance.py:39:34: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a parameter annotation
+generics_paramspec_variance.py:39:34: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`
+"""
diff --git a/conformance/results/zuban/generics_paramspec_variance.toml b/conformance/results/zuban/generics_paramspec_variance.toml
new file mode 100644
index 000000000..188439ca3
--- /dev/null
+++ b/conformance/results/zuban/generics_paramspec_variance.toml
@@ -0,0 +1,22 @@
+conformant = "Unsupported"
+conformance_automated = "Fail"
+errors_diff = """
+Line 21: Expected 1 errors
+Line 29: Expected 1 errors
+Line 42: Expected 1 errors
+Line 22: Unexpected errors ['generics_paramspec_variance.py:22: error: Incompatible types in assignment (expression has type "CovariantParamSpec[[int]]", variable has type "CovariantParamSpec[[object]]") [assignment]']
+Line 24: Unexpected errors ['generics_paramspec_variance.py:24: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]']
+Line 28: Unexpected errors ['generics_paramspec_variance.py:28: error: Missing return statement [empty-body]']
+Line 34: Unexpected errors ['generics_paramspec_variance.py:34: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]']
+Line 43: Unexpected errors ['generics_paramspec_variance.py:43: error: Incompatible types in assignment (expression has type "CovariantParamSpecOld[[int]]", variable has type "CovariantParamSpecOld[[object]]") [assignment]']
+"""
+output = """
+generics_paramspec_variance.py:13: error: Incompatible types in assignment (expression has type "ContravariantParamSpec[[int]]", variable has type "ContravariantParamSpec[[object]]") [assignment]
+generics_paramspec_variance.py:22: error: Incompatible types in assignment (expression has type "CovariantParamSpec[[int]]", variable has type "CovariantParamSpec[[object]]") [assignment]
+generics_paramspec_variance.py:24: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]
+generics_paramspec_variance.py:28: error: Missing return statement [empty-body]
+generics_paramspec_variance.py:31: error: Incompatible types in assignment (expression has type "ContravariantParamSpecOld[[int]]", variable has type "ContravariantParamSpecOld[[object]]") [assignment]
+generics_paramspec_variance.py:34: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]
+generics_paramspec_variance.py:38: error: Missing return statement [empty-body]
+generics_paramspec_variance.py:43: error: Incompatible types in assignment (expression has type "CovariantParamSpecOld[[int]]", variable has type "CovariantParamSpecOld[[object]]") [assignment]
+"""
diff --git a/conformance/tests/generics_paramspec_variance.py b/conformance/tests/generics_paramspec_variance.py
new file mode 100644
index 000000000..59c90b758
--- /dev/null
+++ b/conformance/tests/generics_paramspec_variance.py
@@ -0,0 +1,43 @@
+"""
+Tests variance of ParamSpec.
+"""
+
+# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#semantics
+
+
+from typing import Callable, Generic, ParamSpec
+
+class ContravariantParamSpec[**InP]:
+ def f(self, *args: InP.args, **kwargs: InP.kwargs): ...
+
+in_obj: ContravariantParamSpec[object] = ContravariantParamSpec[int]() # E
+in_int: ContravariantParamSpec[int] = ContravariantParamSpec[object]() # OK
+
+
+class CovariantParamSpec[**OutP]:
+ def f(self, fn: Callable[OutP, None]) -> None: ...
+
+
+out_int: CovariantParamSpec[int] = CovariantParamSpec[object]() # E
+out_obj: CovariantParamSpec[object] = CovariantParamSpec[int]() # OK
+
+InP = ParamSpec("InP", contravariant=True)
+
+
+class ContravariantParamSpecOld(Generic[InP]):
+ def in_f(self) -> Callable[InP, None]: ... # OK
+ def out_f(self, fn: Callable[InP, None]) -> None: ... # E
+
+in_obj_old: ContravariantParamSpecOld[object] = ContravariantParamSpecOld[int]() # E
+in_int_old: ContravariantParamSpecOld[int] = ContravariantParamSpecOld[object]() # OK
+
+OutP = ParamSpec("OutP", covariant=True)
+
+
+class CovariantParamSpecOld(Generic[OutP]):
+ def in_f(self) -> Callable[OutP, None]: ... # E
+ def out_f(self, fn: Callable[OutP, None]) -> None: ... # OK
+
+
+out_int_old: CovariantParamSpecOld[int] = CovariantParamSpecOld[object]() # E
+out_obj_old: CovariantParamSpecOld[object] = CovariantParamSpecOld[int]() # OK
diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst
index c1fa05325..bf6ba9e66 100644
--- a/docs/spec/generics.rst
+++ b/docs/spec/generics.rst
@@ -2712,13 +2712,12 @@ The algorithm for computing the variance of a type parameter is as follows.
For each type parameter in a generic class:
-1. If the type parameter is variadic (``TypeVarTuple``) or a parameter
-specification (``ParamSpec``), it is always considered invariant. No further
-inference is needed.
+1. If the type parameter is variadic (``TypeVarTuple``) it is always
+considered invariant. No further inference is needed.
-2. If the type parameter comes from a traditional ``TypeVar`` declaration and
-is not specified as ``infer_variance`` (see below), its variance is specified
-by the ``TypeVar`` constructor call. No further inference is needed.
+2. If the type parameter comes from a traditional ``TypeVar``/``ParamSpec``
+declaration and is not specified as ``infer_variance`` (see below), its
+variance is specified by the constructor call. No further inference is needed.
3. Create two specialized versions of the class. We'll refer to these as
``upper`` and ``lower`` specializations. In both of these specializations,
From 5788125c68b70d6379d07358f911eabcbba8d0ce Mon Sep 17 00:00:00 2001
From: kotlinisland <65446343+kotlinisland@users.noreply.github.com>
Date: Tue, 7 Apr 2026 14:58:52 +1000
Subject: [PATCH 2/2] specify that type variable tuple should have variance
---
.../mypy/generics_typevartuple_variance.toml | 24 ++++++++++
.../generics_typevartuple_variance.toml | 24 ++++++++++
.../generics_typevartuple_variance.toml | 40 +++++++++++++++++
conformance/results/results.html | 7 +++
.../ty/generics_typevartuple_variance.toml | 28 ++++++++++++
.../zuban/generics_typevartuple_variance.toml | 24 ++++++++++
.../tests/generics_typevartuple_variance.py | 44 +++++++++++++++++++
docs/spec/generics.rst | 17 +++----
8 files changed, 198 insertions(+), 10 deletions(-)
create mode 100644 conformance/results/mypy/generics_typevartuple_variance.toml
create mode 100644 conformance/results/pyrefly/generics_typevartuple_variance.toml
create mode 100644 conformance/results/pyright/generics_typevartuple_variance.toml
create mode 100644 conformance/results/ty/generics_typevartuple_variance.toml
create mode 100644 conformance/results/zuban/generics_typevartuple_variance.toml
create mode 100644 conformance/tests/generics_typevartuple_variance.py
diff --git a/conformance/results/mypy/generics_typevartuple_variance.toml b/conformance/results/mypy/generics_typevartuple_variance.toml
new file mode 100644
index 000000000..b9869a7d4
--- /dev/null
+++ b/conformance/results/mypy/generics_typevartuple_variance.toml
@@ -0,0 +1,24 @@
+conformant = "Unsupported"
+conformance_automated = "Fail"
+errors_diff = """
+Line 13: Expected 1 errors
+Line 32: Expected 1 errors
+Line 39: Expected 1 errors
+Line 14: Unexpected errors ['generics_typevartuple_variance.py:14: error: Incompatible types in assignment (expression has type "ContravariantTypeVarTuple[object]", variable has type "ContravariantTypeVarTuple[int]") [assignment]']
+Line 18: Unexpected errors ['generics_typevartuple_variance.py:18: error: Missing return statement [empty-body]']
+Line 25: Unexpected errors ['generics_typevartuple_variance.py:25: error: Unexpected keyword argument "contravariant" for "TypeVarTuple" [misc]']
+Line 33: Unexpected errors ['generics_typevartuple_variance.py:33: error: Incompatible types in assignment (expression has type "ContravariantTypeVarTupleOld[object]", variable has type "ContravariantTypeVarTupleOld[int]") [assignment]']
+Line 35: Unexpected errors ['generics_typevartuple_variance.py:35: error: Unexpected keyword argument "covariant" for "TypeVarTuple" [misc]']
+Line 40: Unexpected errors ['generics_typevartuple_variance.py:40: error: Missing return statement [empty-body]']
+"""
+output = """
+generics_typevartuple_variance.py:14: error: Incompatible types in assignment (expression has type "ContravariantTypeVarTuple[object]", variable has type "ContravariantTypeVarTuple[int]") [assignment]
+generics_typevartuple_variance.py:18: error: Missing return statement [empty-body]
+generics_typevartuple_variance.py:21: error: Incompatible types in assignment (expression has type "CovariantTypeVarTuple[object]", variable has type "CovariantTypeVarTuple[int]") [assignment]
+generics_typevartuple_variance.py:25: error: Unexpected keyword argument "contravariant" for "TypeVarTuple" [misc]
+generics_typevartuple_variance.py:30: error: Missing return statement [empty-body]
+generics_typevartuple_variance.py:33: error: Incompatible types in assignment (expression has type "ContravariantTypeVarTupleOld[object]", variable has type "ContravariantTypeVarTupleOld[int]") [assignment]
+generics_typevartuple_variance.py:35: error: Unexpected keyword argument "covariant" for "TypeVarTuple" [misc]
+generics_typevartuple_variance.py:40: error: Missing return statement [empty-body]
+generics_typevartuple_variance.py:43: error: Incompatible types in assignment (expression has type "CovariantTypeVarTupleOld[object]", variable has type "CovariantTypeVarTupleOld[int]") [assignment]
+"""
diff --git a/conformance/results/pyrefly/generics_typevartuple_variance.toml b/conformance/results/pyrefly/generics_typevartuple_variance.toml
new file mode 100644
index 000000000..d82f805d7
--- /dev/null
+++ b/conformance/results/pyrefly/generics_typevartuple_variance.toml
@@ -0,0 +1,24 @@
+conformant = "Unsupported"
+conformance_automated = "Fail"
+errors_diff = """
+Line 30: Expected 1 errors
+Line 39: Expected 1 errors
+Line 14: Unexpected errors ['`ContravariantTypeVarTuple[object]` is not assignable to `ContravariantTypeVarTuple[int]` [bad-assignment]']
+Line 22: Unexpected errors ['`CovariantTypeVarTuple[int]` is not assignable to `CovariantTypeVarTuple[object]` [bad-assignment]']
+Line 25: Unexpected errors ['Unexpected keyword argument `contravariant` to TypeVarTuple [invalid-type-var-tuple]']
+Line 33: Unexpected errors ['`ContravariantTypeVarTupleOld[object]` is not assignable to `ContravariantTypeVarTupleOld[int]` [bad-assignment]']
+Line 35: Unexpected errors ['Unexpected keyword argument `covariant` to TypeVarTuple [invalid-type-var-tuple]']
+Line 44: Unexpected errors ['`CovariantTypeVarTupleOld[int]` is not assignable to `CovariantTypeVarTupleOld[object]` [bad-assignment]']
+"""
+output = """
+ERROR generics_typevartuple_variance.py:13:45-77: `ContravariantTypeVarTuple[int]` is not assignable to `ContravariantTypeVarTuple[object]` [bad-assignment]
+ERROR generics_typevartuple_variance.py:14:42-77: `ContravariantTypeVarTuple[object]` is not assignable to `ContravariantTypeVarTuple[int]` [bad-assignment]
+ERROR generics_typevartuple_variance.py:21:39-70: `CovariantTypeVarTuple[object]` is not assignable to `CovariantTypeVarTuple[int]` [bad-assignment]
+ERROR generics_typevartuple_variance.py:22:42-70: `CovariantTypeVarTuple[int]` is not assignable to `CovariantTypeVarTuple[object]` [bad-assignment]
+ERROR generics_typevartuple_variance.py:25:29-47: Unexpected keyword argument `contravariant` to TypeVarTuple [invalid-type-var-tuple]
+ERROR generics_typevartuple_variance.py:32:52-87: `ContravariantTypeVarTupleOld[int]` is not assignable to `ContravariantTypeVarTupleOld[object]` [bad-assignment]
+ERROR generics_typevartuple_variance.py:33:49-87: `ContravariantTypeVarTupleOld[object]` is not assignable to `ContravariantTypeVarTupleOld[int]` [bad-assignment]
+ERROR generics_typevartuple_variance.py:35:31-45: Unexpected keyword argument `covariant` to TypeVarTuple [invalid-type-var-tuple]
+ERROR generics_typevartuple_variance.py:43:46-80: `CovariantTypeVarTupleOld[object]` is not assignable to `CovariantTypeVarTupleOld[int]` [bad-assignment]
+ERROR generics_typevartuple_variance.py:44:49-80: `CovariantTypeVarTupleOld[int]` is not assignable to `CovariantTypeVarTupleOld[object]` [bad-assignment]
+"""
diff --git a/conformance/results/pyright/generics_typevartuple_variance.toml b/conformance/results/pyright/generics_typevartuple_variance.toml
new file mode 100644
index 000000000..d604ca859
--- /dev/null
+++ b/conformance/results/pyright/generics_typevartuple_variance.toml
@@ -0,0 +1,40 @@
+conformant = "Unsupported"
+conformance_automated = "Fail"
+errors_diff = """
+Line 30: Expected 1 errors
+Line 39: Expected 1 errors
+Line 14: Unexpected errors ['generics_typevartuple_variance.py:14:42 - error: Type "ContravariantTypeVarTuple[object]" is not assignable to declared type "ContravariantTypeVarTuple[int]"']
+Line 22: Unexpected errors ['generics_typevartuple_variance.py:22:42 - error: Type "CovariantTypeVarTuple[int]" is not assignable to declared type "CovariantTypeVarTuple[object]"']
+Line 25: Unexpected errors ['generics_typevartuple_variance.py:25:29 - error: "contravariant" is unknown parameter to TypeVarTuple (reportGeneralTypeIssues)']
+Line 33: Unexpected errors ['generics_typevartuple_variance.py:33:49 - error: Type "ContravariantTypeVarTupleOld[object]" is not assignable to declared type "ContravariantTypeVarTupleOld[int]"']
+Line 35: Unexpected errors ['generics_typevartuple_variance.py:35:31 - error: "covariant" is unknown parameter to TypeVarTuple (reportGeneralTypeIssues)']
+Line 44: Unexpected errors ['generics_typevartuple_variance.py:44:49 - error: Type "CovariantTypeVarTupleOld[int]" is not assignable to declared type "CovariantTypeVarTupleOld[object]"']
+"""
+output = """
+generics_typevartuple_variance.py:13:45 - error: Type "ContravariantTypeVarTuple[int]" is not assignable to declared type "ContravariantTypeVarTuple[object]"
+ "ContravariantTypeVarTuple[int]" is not assignable to "ContravariantTypeVarTuple[object]"
+ Type parameter "InTs@ContravariantTypeVarTuple" is invariant, but "*tuple[int]" is not the same as "*tuple[object]" (reportAssignmentType)
+generics_typevartuple_variance.py:14:42 - error: Type "ContravariantTypeVarTuple[object]" is not assignable to declared type "ContravariantTypeVarTuple[int]"
+ "ContravariantTypeVarTuple[object]" is not assignable to "ContravariantTypeVarTuple[int]"
+ Type parameter "InTs@ContravariantTypeVarTuple" is invariant, but "*tuple[object]" is not the same as "*tuple[int]" (reportAssignmentType)
+generics_typevartuple_variance.py:21:39 - error: Type "CovariantTypeVarTuple[object]" is not assignable to declared type "CovariantTypeVarTuple[int]"
+ "CovariantTypeVarTuple[object]" is not assignable to "CovariantTypeVarTuple[int]"
+ Type parameter "OutTs@CovariantTypeVarTuple" is invariant, but "*tuple[object]" is not the same as "*tuple[int]" (reportAssignmentType)
+generics_typevartuple_variance.py:22:42 - error: Type "CovariantTypeVarTuple[int]" is not assignable to declared type "CovariantTypeVarTuple[object]"
+ "CovariantTypeVarTuple[int]" is not assignable to "CovariantTypeVarTuple[object]"
+ Type parameter "OutTs@CovariantTypeVarTuple" is invariant, but "*tuple[int]" is not the same as "*tuple[object]" (reportAssignmentType)
+generics_typevartuple_variance.py:25:29 - error: "contravariant" is unknown parameter to TypeVarTuple (reportGeneralTypeIssues)
+generics_typevartuple_variance.py:32:52 - error: Type "ContravariantTypeVarTupleOld[int]" is not assignable to declared type "ContravariantTypeVarTupleOld[object]"
+ "ContravariantTypeVarTupleOld[int]" is not assignable to "ContravariantTypeVarTupleOld[object]"
+ Type parameter "InTs@ContravariantTypeVarTupleOld" is invariant, but "*tuple[int]" is not the same as "*tuple[object]" (reportAssignmentType)
+generics_typevartuple_variance.py:33:49 - error: Type "ContravariantTypeVarTupleOld[object]" is not assignable to declared type "ContravariantTypeVarTupleOld[int]"
+ "ContravariantTypeVarTupleOld[object]" is not assignable to "ContravariantTypeVarTupleOld[int]"
+ Type parameter "InTs@ContravariantTypeVarTupleOld" is invariant, but "*tuple[object]" is not the same as "*tuple[int]" (reportAssignmentType)
+generics_typevartuple_variance.py:35:31 - error: "covariant" is unknown parameter to TypeVarTuple (reportGeneralTypeIssues)
+generics_typevartuple_variance.py:43:46 - error: Type "CovariantTypeVarTupleOld[object]" is not assignable to declared type "CovariantTypeVarTupleOld[int]"
+ "CovariantTypeVarTupleOld[object]" is not assignable to "CovariantTypeVarTupleOld[int]"
+ Type parameter "OutTs@CovariantTypeVarTupleOld" is invariant, but "*tuple[object]" is not the same as "*tuple[int]" (reportAssignmentType)
+generics_typevartuple_variance.py:44:49 - error: Type "CovariantTypeVarTupleOld[int]" is not assignable to declared type "CovariantTypeVarTupleOld[object]"
+ "CovariantTypeVarTupleOld[int]" is not assignable to "CovariantTypeVarTupleOld[object]"
+ Type parameter "OutTs@CovariantTypeVarTupleOld" is invariant, but "*tuple[int]" is not the same as "*tuple[object]" (reportAssignmentType)
+"""
diff --git a/conformance/results/results.html b/conformance/results/results.html
index 9dad0c6aa..0f8c1c35d 100644
--- a/conformance/results/results.html
+++ b/conformance/results/results.html
@@ -468,6 +468,13 @@ Python Type System Conformance Test Results
Pass |
Unsupported |
+| generics_typevartuple_variance |
+Unsupported |
+Unsupported |
+Unsupported |
+Unsupported |
+Unsupported |
+
| generics_upper_bound |
Partial Does not reject use of type variable within an upper bound. |
Pass |
diff --git a/conformance/results/ty/generics_typevartuple_variance.toml b/conformance/results/ty/generics_typevartuple_variance.toml
new file mode 100644
index 000000000..8c726b6df
--- /dev/null
+++ b/conformance/results/ty/generics_typevartuple_variance.toml
@@ -0,0 +1,28 @@
+conformant = "Unsupported"
+conformance_automated = "Fail"
+errors_diff = """
+Line 32: Expected 1 errors
+Line 39: Expected 1 errors
+Line 43: Expected 1 errors
+Line 14: Unexpected errors ["generics_typevartuple_variance.py:14:9: error[not-subscriptable] Cannot subscript non-generic type ``", "generics_typevartuple_variance.py:14:42: error[not-subscriptable] Cannot subscript non-generic type ``"]
+Line 18: Unexpected errors ['generics_typevartuple_variance.py:18:20: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo(TypeVarTuple), ...]`']
+Line 22: Unexpected errors ["generics_typevartuple_variance.py:22:10: error[not-subscriptable] Cannot subscript non-generic type ``", "generics_typevartuple_variance.py:22:42: error[not-subscriptable] Cannot subscript non-generic type ``"]
+Line 25: Unexpected errors ['generics_typevartuple_variance.py:25:29: error[unknown-argument] Argument `contravariant` does not match any known parameter of function `__new__`']
+Line 35: Unexpected errors ['generics_typevartuple_variance.py:35:31: error[unknown-argument] Argument `covariant` does not match any known parameter of function `__new__`']
+Line 40: Unexpected errors ['generics_typevartuple_variance.py:40:24: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo(TypeVarTuple), ...]`']
+"""
+output = """
+generics_typevartuple_variance.py:13:9: error[not-subscriptable] Cannot subscript non-generic type ``
+generics_typevartuple_variance.py:13:45: error[not-subscriptable] Cannot subscript non-generic type ``
+generics_typevartuple_variance.py:14:9: error[not-subscriptable] Cannot subscript non-generic type ``
+generics_typevartuple_variance.py:14:42: error[not-subscriptable] Cannot subscript non-generic type ``
+generics_typevartuple_variance.py:18:20: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo(TypeVarTuple), ...]`
+generics_typevartuple_variance.py:21:10: error[not-subscriptable] Cannot subscript non-generic type ``
+generics_typevartuple_variance.py:21:39: error[not-subscriptable] Cannot subscript non-generic type ``
+generics_typevartuple_variance.py:22:10: error[not-subscriptable] Cannot subscript non-generic type ``
+generics_typevartuple_variance.py:22:42: error[not-subscriptable] Cannot subscript non-generic type ``
+generics_typevartuple_variance.py:25:29: error[unknown-argument] Argument `contravariant` does not match any known parameter of function `__new__`
+generics_typevartuple_variance.py:30:24: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo(TypeVarTuple), ...]`
+generics_typevartuple_variance.py:35:31: error[unknown-argument] Argument `covariant` does not match any known parameter of function `__new__`
+generics_typevartuple_variance.py:40:24: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo(TypeVarTuple), ...]`
+"""
diff --git a/conformance/results/zuban/generics_typevartuple_variance.toml b/conformance/results/zuban/generics_typevartuple_variance.toml
new file mode 100644
index 000000000..c1482b688
--- /dev/null
+++ b/conformance/results/zuban/generics_typevartuple_variance.toml
@@ -0,0 +1,24 @@
+conformant = "Unsupported"
+conformance_automated = "Fail"
+errors_diff = """
+Line 13: Expected 1 errors
+Line 32: Expected 1 errors
+Line 39: Expected 1 errors
+Line 14: Unexpected errors ['generics_typevartuple_variance.py:14: error: Incompatible types in assignment (expression has type "ContravariantTypeVarTuple[object]", variable has type "ContravariantTypeVarTuple[int]") [assignment]']
+Line 18: Unexpected errors ['generics_typevartuple_variance.py:18: error: Missing return statement [empty-body]']
+Line 25: Unexpected errors ['generics_typevartuple_variance.py:25: error: Unexpected keyword argument "contravariant" for "TypeVarTuple" [call-arg]']
+Line 33: Unexpected errors ['generics_typevartuple_variance.py:33: error: Incompatible types in assignment (expression has type "ContravariantTypeVarTupleOld[object]", variable has type "ContravariantTypeVarTupleOld[int]") [assignment]']
+Line 35: Unexpected errors ['generics_typevartuple_variance.py:35: error: Unexpected keyword argument "covariant" for "TypeVarTuple" [call-arg]']
+Line 40: Unexpected errors ['generics_typevartuple_variance.py:40: error: Missing return statement [empty-body]']
+"""
+output = """
+generics_typevartuple_variance.py:14: error: Incompatible types in assignment (expression has type "ContravariantTypeVarTuple[object]", variable has type "ContravariantTypeVarTuple[int]") [assignment]
+generics_typevartuple_variance.py:18: error: Missing return statement [empty-body]
+generics_typevartuple_variance.py:21: error: Incompatible types in assignment (expression has type "CovariantTypeVarTuple[object]", variable has type "CovariantTypeVarTuple[int]") [assignment]
+generics_typevartuple_variance.py:25: error: Unexpected keyword argument "contravariant" for "TypeVarTuple" [call-arg]
+generics_typevartuple_variance.py:30: error: Missing return statement [empty-body]
+generics_typevartuple_variance.py:33: error: Incompatible types in assignment (expression has type "ContravariantTypeVarTupleOld[object]", variable has type "ContravariantTypeVarTupleOld[int]") [assignment]
+generics_typevartuple_variance.py:35: error: Unexpected keyword argument "covariant" for "TypeVarTuple" [call-arg]
+generics_typevartuple_variance.py:40: error: Missing return statement [empty-body]
+generics_typevartuple_variance.py:43: error: Incompatible types in assignment (expression has type "CovariantTypeVarTupleOld[object]", variable has type "CovariantTypeVarTupleOld[int]") [assignment]
+"""
diff --git a/conformance/tests/generics_typevartuple_variance.py b/conformance/tests/generics_typevartuple_variance.py
new file mode 100644
index 000000000..e6d92d6b4
--- /dev/null
+++ b/conformance/tests/generics_typevartuple_variance.py
@@ -0,0 +1,44 @@
+"""
+Tests variance of TypeVarTuple.
+"""
+
+# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#semantics
+
+
+from typing import Generic, TypeVarTuple
+
+class ContravariantTypeVarTuple[*InTs]:
+ def f(self, t: tuple[*InTs]): ...
+
+in_obj: ContravariantTypeVarTuple[object] = ContravariantTypeVarTuple[int]() # E
+in_int: ContravariantTypeVarTuple[int] = ContravariantTypeVarTuple[object]() # OK
+
+
+class CovariantTypeVarTuple[*OutTs]:
+ def f(self) -> tuple[*OutTs]: ...
+
+
+out_int: CovariantTypeVarTuple[int] = CovariantTypeVarTuple[object]() # E
+out_obj: CovariantTypeVarTuple[object] = CovariantTypeVarTuple[int]() # OK
+
+
+InTs = TypeVarTuple("InTs", contravariant=True)
+
+
+class ContravariantTypeVarTupleOld(Generic[*InTs]):
+ def in_f(self, *args: *InTs) -> None: ... # OK
+ def out_f(self) -> tuple[*InTs]: ... # E
+
+in_obj_old: ContravariantTypeVarTupleOld[object] = ContravariantTypeVarTupleOld[int]() # E
+in_int_old: ContravariantTypeVarTupleOld[int] = ContravariantTypeVarTupleOld[object]() # OK
+
+OutTs = TypeVarTuple("OutTs", covariant=True)
+
+
+class CovariantTypeVarTupleOld(Generic[*OutTs]):
+ def in_f(self, *args: *OutTs) -> None: ... # E
+ def out_f(self) -> tuple[*OutTs]: ... # OK
+
+
+out_int_old: CovariantTypeVarTupleOld[int] = CovariantTypeVarTupleOld[object]() # E
+out_obj_old: CovariantTypeVarTupleOld[object] = CovariantTypeVarTupleOld[int]() # OK
diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst
index bf6ba9e66..887e87bf1 100644
--- a/docs/spec/generics.rst
+++ b/docs/spec/generics.rst
@@ -1189,12 +1189,11 @@ for two reasons:
* To improve readability: the star also functions as an explicit visual
indicator that the type variable tuple is not a normal type variable.
-Variance, Type Constraints and Type Bounds: Not Supported
+Type Constraints and Type Bounds: Not Supported
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""
``TypeVarTuple`` does not currently support specification of:
-* Variance (e.g. ``TypeVar('T', covariant=True)``)
* Type constraints (``TypeVar('T', int, float)``)
* Type bounds (``TypeVar('T', bound=ParentClass)``)
@@ -2712,14 +2711,12 @@ The algorithm for computing the variance of a type parameter is as follows.
For each type parameter in a generic class:
-1. If the type parameter is variadic (``TypeVarTuple``) it is always
-considered invariant. No further inference is needed.
+1. If the type parameter comes from a traditional
+``TypeVar``/``TypeVarTuple``/``ParamSpec`` declaration and is not specified
+as ``infer_variance`` (see below), its variance is specified by the
+constructor call. No further inference is needed.
-2. If the type parameter comes from a traditional ``TypeVar``/``ParamSpec``
-declaration and is not specified as ``infer_variance`` (see below), its
-variance is specified by the constructor call. No further inference is needed.
-
-3. Create two specialized versions of the class. We'll refer to these as
+2. Create two specialized versions of the class. We'll refer to these as
``upper`` and ``lower`` specializations. In both of these specializations,
replace all type parameters other than the one being inferred by a dummy type
instance (a concrete anonymous class that is assumed to meet the bounds or
@@ -2729,7 +2726,7 @@ specialization ignores the type parameter's upper bound or constraints. In the
``lower`` specialized class, specialize the target type parameter with itself
(i.e. the corresponding type argument is the type parameter itself).
-4. Determine whether ``lower`` can be assigned to ``upper`` using normal
+3. Determine whether ``lower`` can be assigned to ``upper`` using normal
assignability rules. If so, the target type parameter is covariant. If not,
determine whether ``upper`` can be assigned to ``lower``. If so, the target
type parameter is contravariant. If neither of these combinations are
|---|