Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.14] Address invalid inputs of TypeAliasType #477

Merged
merged 22 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
507088c
type_params must be a tuple
Daraan Sep 30, 2024
c00494a
no non default value after default value
Daraan Sep 30, 2024
4e2f041
Consider Type Error from python/cpython/pull/124795
Daraan Sep 30, 2024
b2412b0
Adjusted error messages to match cpython
Daraan Oct 1, 2024
54a67ab
updated changelog
Daraan Oct 1, 2024
64e4007
Removed invalid tests and left comment
Daraan Oct 1, 2024
dccb363
Slight modification of tests
Daraan Oct 1, 2024
4edee59
fix indent
Daraan Oct 1, 2024
c928b20
Corrected 3.12.8 requirement
Daraan Oct 1, 2024
9ad28aa
Merge 'main' into TypeAliasType/invalid_param_spec
Daraan Oct 11, 2024
4b9a04c
Updated barrier to skip 3.12-3.13
Daraan Oct 11, 2024
6ba3f5e
Merge branch 'main' into TypeAliasType/invalid_param_spec
JelleZijlstra Oct 21, 2024
e729c1f
Split compatibility checks into more methods
Daraan Oct 21, 2024
83bbb98
Merge remote-tracking branch 'upstream/main' into TypeAliasType/inval…
Daraan Oct 21, 2024
ae88d98
Merge remote-tracking branch 'origin/TypeAliasType/invalid_param_spec…
Daraan Oct 21, 2024
6bc5c46
Merge remote-tracking branch 'upstream/main' into TypeAliasType/inval…
Daraan Oct 22, 2024
94b8c86
Draft for 3.12, 3.13 backport
Daraan Oct 22, 2024
46ab3ac
Use TypeAliasType backport for <3.14
Daraan Oct 22, 2024
b38852e
fix typo and style
Daraan Oct 22, 2024
1ef3e00
Unpack should not pass as a TypeVar
Daraan Oct 22, 2024
cdf7fec
Clarified statement about Unpack for < 3.12
Daraan Oct 23, 2024
c14567d
Updated unpack comment in main code
Daraan Oct 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ aliases that have a `Concatenate` special form as their argument.
`Ellipsis` as an argument. Patch by [Daraan](https://github.com/Daraan).
- Fix error in subscription of `Unpack` aliases causing nested Unpacks
to not be resolved correctly. Patch by [Daraan](https://github.com/Daraan).
- Backport of CPython PR [#124795](https://github.com/python/cpython/pull/124795)
and fix that `TypeAliasType` not raising an error on non-tupple inputs for `type_params`.
Patch by [Daraan](https://github.com/Daraan).

# Release 4.12.2 (June 7, 2024)

Expand Down
78 changes: 78 additions & 0 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6192,6 +6192,10 @@ def test_typing_extensions_defers_when_possible(self):
'AsyncGenerator', 'ContextManager', 'AsyncContextManager',
'ParamSpec', 'TypeVar', 'TypeVarTuple', 'get_type_hints',
}
if sys.version_info < (3, 14):
exclude |= {
'TypeAliasType'
}
if not typing_extensions._PEP_728_IMPLEMENTED:
exclude |= {'TypedDict', 'is_typeddict'}
for item in typing_extensions.__all__:
Expand Down Expand Up @@ -7402,6 +7406,80 @@ def test_no_instance_subclassing(self):
class MyAlias(TypeAliasType):
pass

def test_type_var_compatibility(self):
# Regression test to assure compatibility with typing variants
typingT = typing.TypeVar('typingT')
T1 = TypeAliasType("TypingTypeVar", ..., type_params=(typingT,))
self.assertEqual(T1.__type_params__, (typingT,))

# Test typing_extensions backports
textT = TypeVar('textT')
T2 = TypeAliasType("TypingExtTypeVar", ..., type_params=(textT,))
self.assertEqual(T2.__type_params__, (textT,))

textP = ParamSpec("textP")
T3 = TypeAliasType("TypingExtParamSpec", ..., type_params=(textP,))
self.assertEqual(T3.__type_params__, (textP,))

textTs = TypeVarTuple("textTs")
T4 = TypeAliasType("TypingExtTypeVarTuple", ..., type_params=(textTs,))
self.assertEqual(T4.__type_params__, (textTs,))

@skipUnless(TYPING_3_10_0, "typing.ParamSpec is not available before 3.10")
def test_param_spec_compatibility(self):
# Regression test to assure compatibility with typing variant
typingP = typing.ParamSpec("typingP")
T5 = TypeAliasType("TypingParamSpec", ..., type_params=(typingP,))
self.assertEqual(T5.__type_params__, (typingP,))

@skipUnless(TYPING_3_12_0, "typing.TypeVarTuple is not available before 3.12")
def test_type_var_tuple_compatibility(self):
# Regression test to assure compatibility with typing variant
typingTs = typing.TypeVarTuple("typingTs")
T6 = TypeAliasType("TypingTypeVarTuple", ..., type_params=(typingTs,))
self.assertEqual(T6.__type_params__, (typingTs,))

def test_type_params_possibilities(self):
T = TypeVar('T')
# Test not a tuple
with self.assertRaisesRegex(TypeError, "type_params must be a tuple"):
TypeAliasType("InvalidTypeParams", List[T], type_params=[T])

# Test default order and other invalid inputs
T_default = TypeVar('T_default', default=int)
Ts = TypeVarTuple('Ts')
Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[str, int]])
P = ParamSpec('P')
P_default = ParamSpec('P_default', default=[str, int])

# NOTE: PEP 696 states: "TypeVars with defaults cannot immediately follow TypeVarTuples"
# this is currently not enforced for the type statement and is not tested.
# PEP 695: Double usage of the same name is also not enforced and not tested.
valid_cases = [
(T, P, Ts),
(T, Ts_default),
(P_default, T_default),
(P, T_default, Ts_default),
(T_default, P_default, Ts_default),
]
invalid_cases = [
((T_default, T), f"non-default type parameter {T!r} follows default"),
((P_default, P), f"non-default type parameter {P!r} follows default"),
((Ts_default, T), f"non-default type parameter {T!r} follows default"),
# Only type params are accepted
((1,), "Expected a type param, got 1"),
((str,), f"Expected a type param, got {str!r}"),
# Unpack is not a TypeVar but isinstance(Unpack[Ts], TypeVar) is True in Python < 3.12
((Unpack[Ts],), f"Expected a type param, got {re.escape(repr(Unpack[Ts]))}"),
]

for case in valid_cases:
with self.subTest(type_params=case):
TypeAliasType("OkCase", List[T], type_params=case)
for case, msg in invalid_cases:
with self.subTest(type_params=case):
with self.assertRaisesRegex(TypeError, msg):
TypeAliasType("InvalidCase", List[T], type_params=case)

class DocTests(BaseTestCase):
def test_annotation(self):
Expand Down
21 changes: 20 additions & 1 deletion src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3528,8 +3528,9 @@ def __ror__(self, other):
return typing.Union[other, self]


if hasattr(typing, "TypeAliasType"):
if sys.version_info >= (3, 14):
TypeAliasType = typing.TypeAliasType
# 3.8-3.13
else:
def _is_unionable(obj):
"""Corresponds to is_unionable() in unionobject.c in CPython."""
Expand Down Expand Up @@ -3602,11 +3603,29 @@ class TypeAliasType:
def __init__(self, name: str, value, *, type_params=()):
if not isinstance(name, str):
raise TypeError("TypeAliasType name must be a string")
if not isinstance(type_params, tuple):
raise TypeError("type_params must be a tuple")
self.__value__ = value
self.__type_params__ = type_params

default_value_encountered = False
parameters = []
for type_param in type_params:
if (
not isinstance(type_param, (TypeVar, TypeVarTuple, ParamSpec))
# 3.8-3.11
# Unpack Backport passes isinstance(type_param, TypeVar)
or _is_unpack(type_param)
):
raise TypeError(f"Expected a type param, got {type_param!r}")
has_default = (
getattr(type_param, '__default__', NoDefault) is not NoDefault
)
if default_value_encountered and not has_default:
raise TypeError(f'non-default type parameter {type_param!r}'
' follows default type parameter')
if has_default:
default_value_encountered = True
if isinstance(type_param, TypeVarTuple):
parameters.extend(type_param)
else:
Expand Down
Loading