From 117964ecdc928794eb729e00db79222653f79402 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] 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 | 47 +++++++++++++++++++ docs/spec/generics.rst | 11 ++--- 8 files changed, 197 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 00000000..78a4e288 --- /dev/null +++ b/conformance/results/mypy/generics_paramspec_variance.toml @@ -0,0 +1,22 @@ +conformant = "Unsupported" +conformance_automated = "Fail" +errors_diff = """ +Line 24: Expected 1 errors +Line 31: Expected 1 errors +Line 47: Expected 1 errors +Line 23: Unexpected errors ['generics_paramspec_variance.py:23: error: Incompatible types in assignment (expression has type "CovariantParamSpec[[int]]", variable has type "CovariantParamSpec[[object]]") [assignment]'] +Line 26: Unexpected errors ['generics_paramspec_variance.py:26: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]'] +Line 30: Unexpected errors ['generics_paramspec_variance.py:30: error: Missing return statement [empty-body]'] +Line 37: Unexpected errors ['generics_paramspec_variance.py:37: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]'] +Line 46: Unexpected errors ['generics_paramspec_variance.py:46: error: Incompatible types in assignment (expression has type "CovariantParamSpecOld[[int]]", variable has type "CovariantParamSpecOld[[object]]") [assignment]'] +""" +output = """ +generics_paramspec_variance.py:15: error: Incompatible types in assignment (expression has type "ContravariantParamSpec[[int]]", variable has type "ContravariantParamSpec[[object]]") [assignment] +generics_paramspec_variance.py:23: error: Incompatible types in assignment (expression has type "CovariantParamSpec[[int]]", variable has type "CovariantParamSpec[[object]]") [assignment] +generics_paramspec_variance.py:26: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc] +generics_paramspec_variance.py:30: error: Missing return statement [empty-body] +generics_paramspec_variance.py:35: error: Incompatible types in assignment (expression has type "ContravariantParamSpecOld[[int]]", variable has type "ContravariantParamSpecOld[[object]]") [assignment] +generics_paramspec_variance.py:37: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc] +generics_paramspec_variance.py:41: error: Missing return statement [empty-body] +generics_paramspec_variance.py:46: 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 00000000..8fe747ea --- /dev/null +++ b/conformance/results/pyrefly/generics_paramspec_variance.toml @@ -0,0 +1,24 @@ +conformant = "Unsupported" +conformance_automated = "Fail" +errors_diff = """ +Line 15: Expected 1 errors +Line 24: Expected 1 errors +Line 31: Expected 1 errors +Line 41: Expected 1 errors +Line 14: Unexpected errors ['`ContravariantParamSpec[[object]]` is not assignable to `ContravariantParamSpec[[int]]` [bad-assignment]'] +Line 23: Unexpected errors ['`CovariantParamSpec[[int]]` is not assignable to `CovariantParamSpec[[object]]` [bad-assignment]'] +Line 26: Unexpected errors ['Unexpected keyword argument `contravariant` to ParamSpec [invalid-param-spec]'] +Line 34: Unexpected errors ['`ContravariantParamSpecOld[[object]]` is not assignable to `ContravariantParamSpecOld[[int]]` [bad-assignment]'] +Line 37: Unexpected errors ['Unexpected keyword argument `covariant` to ParamSpec [invalid-param-spec]'] +Line 46: Unexpected errors ['`CovariantParamSpecOld[[int]]` is not assignable to `CovariantParamSpecOld[[object]]` [bad-assignment]'] +""" +output = """ +ERROR generics_paramspec_variance.py:14:39-45: `ContravariantParamSpec[[object]]` is not assignable to `ContravariantParamSpec[[int]]` [bad-assignment] +ERROR generics_paramspec_variance.py:23:39-46: `CovariantParamSpec[[int]]` is not assignable to `CovariantParamSpec[[object]]` [bad-assignment] +ERROR generics_paramspec_variance.py:26:24-42: Unexpected keyword argument `contravariant` to ParamSpec [invalid-param-spec] +ERROR generics_paramspec_variance.py:34:46-56: `ContravariantParamSpecOld[[object]]` is not assignable to `ContravariantParamSpecOld[[int]]` [bad-assignment] +ERROR generics_paramspec_variance.py:35:14-24: `ContravariantParamSpecOld[[int]]` is not assignable to variable `in_obj_old` with type `ContravariantParamSpecOld[[object]]` [bad-assignment] +ERROR generics_paramspec_variance.py:37:26-40: Unexpected keyword argument `covariant` to ParamSpec [invalid-param-spec] +ERROR generics_paramspec_variance.py:46:46-57: `CovariantParamSpecOld[[int]]` is not assignable to `CovariantParamSpecOld[[object]]` [bad-assignment] +ERROR generics_paramspec_variance.py:47:15-26: `CovariantParamSpecOld[[object]]` is not assignable to variable `out_int_old` with type `CovariantParamSpecOld[[int]]` [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 00000000..b2abd1ce --- /dev/null +++ b/conformance/results/pyright/generics_paramspec_variance.toml @@ -0,0 +1,40 @@ +conformant = "Unsupported" +conformance_automated = "Fail" +errors_diff = """ +Line 31: Expected 1 errors +Line 41: 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 23: Unexpected errors ['generics_paramspec_variance.py:23:39 - error: Type "CovariantParamSpec[(int)]" is not assignable to declared type "CovariantParamSpec[(object)]"'] +Line 26: Unexpected errors ['generics_paramspec_variance.py:26:24 - error: "contravariant" is unknown parameter to ParamSpec (reportGeneralTypeIssues)'] +Line 34: Unexpected errors ['generics_paramspec_variance.py:34:46 - error: Type "ContravariantParamSpecOld[(object)]" is not assignable to declared type "ContravariantParamSpecOld[(int)]"'] +Line 37: Unexpected errors ['generics_paramspec_variance.py:37:26 - error: "covariant" is unknown parameter to ParamSpec (reportGeneralTypeIssues)'] +Line 46: Unexpected errors ['generics_paramspec_variance.py:46:46 - error: Type "CovariantParamSpecOld[(int)]" is not assignable to declared type "CovariantParamSpecOld[(object)]"'] +""" +output = """ +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:15:10 - 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:23: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:11 - 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:26:24 - error: "contravariant" is unknown parameter to ParamSpec (reportGeneralTypeIssues) +generics_paramspec_variance.py:34: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:35:14 - 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:37:26 - error: "covariant" is unknown parameter to ParamSpec (reportGeneralTypeIssues) +generics_paramspec_variance.py:46: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) +generics_paramspec_variance.py:47:15 - 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) +""" diff --git a/conformance/results/results.html b/conformance/results/results.html index 3ddb751c..9dad0c6a 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 00000000..c2c23d85 --- /dev/null +++ b/conformance/results/ty/generics_paramspec_variance.toml @@ -0,0 +1,30 @@ +conformant = "Unsupported" +conformance_automated = "Fail" +errors_diff = """ +Line 15: Expected 1 errors +Line 24: Expected 1 errors +Line 35: Expected 1 errors +Line 47: Expected 1 errors +Line 26: Unexpected errors ['generics_paramspec_variance.py:26:7: error[invalid-paramspec] The variance and bound arguments for `ParamSpec` do not have defined semantics yet'] +Line 29: Unexpected errors ['generics_paramspec_variance.py:29:33: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`'] +Line 30: Unexpected errors ['generics_paramspec_variance.py:30:23: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `(...) -> Unknown`', 'generics_paramspec_variance.py:30:32: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a return type annotation', 'generics_paramspec_variance.py:30:32: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`'] +Line 37: Unexpected errors ['generics_paramspec_variance.py:37:8: error[invalid-paramspec] The variance and bound arguments for `ParamSpec` do not have defined semantics yet'] +Line 40: Unexpected errors ['generics_paramspec_variance.py:40:29: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`'] +Line 42: Unexpected errors ['generics_paramspec_variance.py:42:34: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a parameter annotation', 'generics_paramspec_variance.py:42: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:26:7: error[invalid-paramspec] The variance and bound arguments for `ParamSpec` do not have defined semantics yet +generics_paramspec_variance.py:29:33: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic` +generics_paramspec_variance.py:30:23: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `(...) -> Unknown` +generics_paramspec_variance.py:30:32: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a return type annotation +generics_paramspec_variance.py:30:32: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...` +generics_paramspec_variance.py:31:34: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a parameter annotation +generics_paramspec_variance.py:31:34: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...` +generics_paramspec_variance.py:37:8: error[invalid-paramspec] The variance and bound arguments for `ParamSpec` do not have defined semantics yet +generics_paramspec_variance.py:40:29: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic` +generics_paramspec_variance.py:41:23: error[empty-body] Function always implicitly returns `None`, which is not assignable to return type `(...) -> Unknown` +generics_paramspec_variance.py:41:32: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a return type annotation +generics_paramspec_variance.py:41:32: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...` +generics_paramspec_variance.py:42:34: error[invalid-type-form] Variable of type `ParamSpec` is not allowed in a parameter annotation +generics_paramspec_variance.py:42: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 00000000..78a4e288 --- /dev/null +++ b/conformance/results/zuban/generics_paramspec_variance.toml @@ -0,0 +1,22 @@ +conformant = "Unsupported" +conformance_automated = "Fail" +errors_diff = """ +Line 24: Expected 1 errors +Line 31: Expected 1 errors +Line 47: Expected 1 errors +Line 23: Unexpected errors ['generics_paramspec_variance.py:23: error: Incompatible types in assignment (expression has type "CovariantParamSpec[[int]]", variable has type "CovariantParamSpec[[object]]") [assignment]'] +Line 26: Unexpected errors ['generics_paramspec_variance.py:26: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]'] +Line 30: Unexpected errors ['generics_paramspec_variance.py:30: error: Missing return statement [empty-body]'] +Line 37: Unexpected errors ['generics_paramspec_variance.py:37: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc]'] +Line 46: Unexpected errors ['generics_paramspec_variance.py:46: error: Incompatible types in assignment (expression has type "CovariantParamSpecOld[[int]]", variable has type "CovariantParamSpecOld[[object]]") [assignment]'] +""" +output = """ +generics_paramspec_variance.py:15: error: Incompatible types in assignment (expression has type "ContravariantParamSpec[[int]]", variable has type "ContravariantParamSpec[[object]]") [assignment] +generics_paramspec_variance.py:23: error: Incompatible types in assignment (expression has type "CovariantParamSpec[[int]]", variable has type "CovariantParamSpec[[object]]") [assignment] +generics_paramspec_variance.py:26: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc] +generics_paramspec_variance.py:30: error: Missing return statement [empty-body] +generics_paramspec_variance.py:35: error: Incompatible types in assignment (expression has type "ContravariantParamSpecOld[[int]]", variable has type "ContravariantParamSpecOld[[object]]") [assignment] +generics_paramspec_variance.py:37: error: The variance and bound arguments to ParamSpec do not have defined semantics yet [misc] +generics_paramspec_variance.py:41: error: Missing return statement [empty-body] +generics_paramspec_variance.py:46: 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 00000000..1c0e780c --- /dev/null +++ b/conformance/tests/generics_paramspec_variance.py @@ -0,0 +1,47 @@ +""" +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() +in_int: ContravariantParamSpec[int] = in_obj # OK +in_obj = in_int # E + + +class CovariantParamSpec[**OutP]: + def f(self, fn: Callable[OutP, None]) -> None: ... + + +out_int: CovariantParamSpec[int] = CovariantParamSpec() +out_obj: CovariantParamSpec[object] = out_int # OK +out_int = out_obj # E + +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() +in_int_old: ContravariantParamSpecOld[int] = in_obj_old # OK +in_obj_old = in_int_old # E + +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() +out_obj_old: CovariantParamSpecOld[object] = out_int_old # OK +out_int_old = out_obj_old # E diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst index c1fa0532..bf6ba9e6 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,