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 12 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 @@ -16,6 +16,9 @@
subscripted objects) had wrong parameters if they were directly
subscripted with an `Unpack` object.
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
56 changes: 56 additions & 0 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7312,6 +7312,62 @@ def test_no_instance_subclassing(self):
class MyAlias(TypeAliasType):
pass

def test_type_params_compatibility(self):
# Regression test to assure compatibility with typing variants
with self.subTest(type_params="typing.TypeVar"):
TypeAliasType("TypingTypeParams", ..., type_params=(typing.TypeVar('T'),))
with self.subTest(type_params="typing.TypeAliasType"):
if not hasattr(typing, "TypeAliasType"):
self.skipTest("typing.TypeAliasType is not available before 3.12")
Daraan marked this conversation as resolved.
Show resolved Hide resolved
TypeAliasType("TypingTypeParams", ..., type_params=(typing.TypeVarTuple("Ts"),))
with self.subTest(type_params="typing.TypeAliasType"):
Daraan marked this conversation as resolved.
Show resolved Hide resolved
if not hasattr(typing, "ParamSpec"):
self.skipTest("typing.ParamSpec is not available before 3.10")
TypeAliasType("TypingTypeParams", ..., type_params=(typing.ParamSpec("P"),))

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: "TypeVars with defaults cannot immediately follow TypeVarTuples"
# from PEP 696 is currently not enfored for the type statement and are 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"),

# Potentially add invalid inputs, e.g. literals or classes
# depends on upstream
((1,), "Expected a type param, got 1"),
((str,), f"Expected a type param, got {str!r}"),
]

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):
if TYPING_3_12_0 and sys.version_info < (3, 14):
self.skipTest("No backport for 3.12 and 3.13 requires cpython PR #124795")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We decided not to backport in CPython. This test should be changed so that typing_extensions does provide the backport.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As TypeAliasType cannot be subclassed, I tried to do a minimal invasive workaround with __new__ to return the typing variant but this fails the pickling check.

Do you know a workaround or is this __new__ method not something to pursue further?


At the moment the <3.12 backport would pass the test and would work as a full backport for 3.12 & 3.13.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just use the pre-3.12 variant on 3.12 and 3.13.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess with annotationlib (likely?) comming it needs a backport anyway.

with self.assertRaisesRegex(TypeError, msg):
TypeAliasType("InvalidCase", List[T], type_params=case)

class DocTests(BaseTestCase):
def test_annotation(self):
Expand Down
13 changes: 13 additions & 0 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3531,11 +3531,24 @@ 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)):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to accept both the typing and typing_extensions versions of these classes, which are different on some versions. Test this.

Copy link
Contributor Author

@Daraan Daraan Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I saw this too, but realized that _TypeVarLikeMeta handles isinstance check. test_type_params_compatibility test_type_var_compatibility and the other methods test this.

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