From 58d3ef259fdf308d67c08b06fa0f027d94e86b7f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 21 May 2023 17:30:28 -0700 Subject: [PATCH 1/2] add infer_variance for ParamSpec For compatibility with Python 3.12, which adds this undocumented parameter along with the existing covariant/contravariant ones. Intentionally leaving this undocumented: it's here in case we want to make variance on ParamSpec mean something later, but it currently has no supported use. --- src/test_typing_extensions.py | 20 +++++++++++++++++++- src/typing_extensions.py | 21 ++++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 8d0ed9df..58c4861e 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -3314,6 +3314,7 @@ def test_repr(self): P = ParamSpec('P') P_co = ParamSpec('P_co', covariant=True) P_contra = ParamSpec('P_contra', contravariant=True) + P_infer = ParamSpec('P_infer', infer_variance=True) P_2 = ParamSpec('P_2') self.assertEqual(repr(P), '~P') self.assertEqual(repr(P_2), '~P_2') @@ -3322,6 +3323,24 @@ def test_repr(self): # just follow CPython. self.assertEqual(repr(P_co), '+P_co') self.assertEqual(repr(P_contra), '-P_contra') + self.assertEqual(repr(P_infer), 'P_infer') + + def test_variance(self): + P_co = ParamSpec('P_co', covariant=True) + P_contra = ParamSpec('P_contra', contravariant=True) + P_infer = ParamSpec('P_infer', infer_variance=True) + + self.assertIs(P_co.__covariant__, True) + self.assertIs(P_co.__contravariant__, False) + self.assertIs(P_co.__infer_variance__, False) + + self.assertIs(P_contra.__covariant__, False) + self.assertIs(P_contra.__contravariant__, True) + self.assertIs(P_contra.__infer_variance__, False) + + self.assertIs(P_infer.__covariant__, False) + self.assertIs(P_infer.__contravariant__, False) + self.assertIs(P_infer.__infer_variance__, True) def test_valid_uses(self): P = ParamSpec('P') @@ -3333,7 +3352,6 @@ def test_valid_uses(self): self.assertEqual(C2.__args__, (P, T)) self.assertEqual(C2.__parameters__, (P, T)) - # Test collections.abc.Callable too. if sys.version_info[:2] >= (3, 9): # Note: no tests for Callable.__parameters__ here diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 93a0dca6..d774afc4 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1475,9 +1475,17 @@ class _ParamSpecMeta(_TypeVarLikeMeta): def __call__(self, name, *, bound=None, covariant=False, contravariant=False, - default=_marker): - paramspec = typing.ParamSpec(name, bound=bound, - covariant=covariant, contravariant=contravariant) + infer_variance=False, default=_marker): + if hasattr(typing, "TypeAliasType"): + # PEP 695 implemented, can pass infer_variance to typing.TypeVar + paramspec = typing.ParamSpec(name, bound=bound, + covariant=covariant, contravariant=contravariant, + infer_variance=infer_variance) + else: + paramspec = typing.ParamSpec(name, bound=bound, + covariant=covariant, contravariant=contravariant) + paramspec.__infer_variance__ = infer_variance + _set_default(paramspec, default) _set_module(paramspec) return paramspec @@ -1551,11 +1559,12 @@ def kwargs(self): return ParamSpecKwargs(self) def __init__(self, name, *, bound=None, covariant=False, contravariant=False, - default=_marker): + infer_variance=False, default=_marker): super().__init__([self]) self.__name__ = name self.__covariant__ = bool(covariant) self.__contravariant__ = bool(contravariant) + self.__infer_variance__ = bool(infer_variance) if bound: self.__bound__ = typing._type_check(bound, 'Bound must be a type.') else: @@ -1568,7 +1577,9 @@ def __init__(self, name, *, bound=None, covariant=False, contravariant=False, self.__module__ = def_mod def __repr__(self): - if self.__covariant__: + if self.__infer_variance__: + prefix = '' + elif self.__covariant__: prefix = '+' elif self.__contravariant__: prefix = '-' From 31826875fabcd67d4b795efd24035d32452a4c1c Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 21 May 2023 17:39:58 -0700 Subject: [PATCH 2/2] fixes --- src/test_typing_extensions.py | 8 +++++++- src/typing_extensions.py | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 58c4861e..882b4500 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -3323,7 +3323,13 @@ def test_repr(self): # just follow CPython. self.assertEqual(repr(P_co), '+P_co') self.assertEqual(repr(P_contra), '-P_contra') - self.assertEqual(repr(P_infer), 'P_infer') + # On other versions we use typing.ParamSpec, but it is not aware of + # infer_variance=. Not worth creating our own version of ParamSpec + # for this. + if hasattr(typing, 'TypeAliasType') or not hasattr(typing, 'ParamSpec'): + self.assertEqual(repr(P_infer), 'P_infer') + else: + self.assertEqual(repr(P_infer), '~P_infer') def test_variance(self): P_co = ParamSpec('P_co', covariant=True) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index d774afc4..30255a00 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1479,11 +1479,13 @@ def __call__(self, name, *, bound=None, if hasattr(typing, "TypeAliasType"): # PEP 695 implemented, can pass infer_variance to typing.TypeVar paramspec = typing.ParamSpec(name, bound=bound, - covariant=covariant, contravariant=contravariant, + covariant=covariant, + contravariant=contravariant, infer_variance=infer_variance) else: paramspec = typing.ParamSpec(name, bound=bound, - covariant=covariant, contravariant=contravariant) + covariant=covariant, + contravariant=contravariant) paramspec.__infer_variance__ = infer_variance _set_default(paramspec, default)