-
-
Notifications
You must be signed in to change notification settings - Fork 117
Add TypeAliasType #160
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
Add TypeAliasType #160
Changes from 4 commits
5b556b8
d11a723
cbdfbfa
fdd32d4
5c5267a
b7551a6
6f0f4f4
facb227
a7d36fa
f4fe7f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -79,6 +79,7 @@ | |
'runtime_checkable', | ||
'Text', | ||
'TypeAlias', | ||
'TypeAliasType', | ||
'TypeGuard', | ||
'TYPE_CHECKING', | ||
'Never', | ||
|
@@ -2610,3 +2611,86 @@ def __or__(self, other): | |
|
||
def __ror__(self, other): | ||
return typing.Union[other, self] | ||
|
||
|
||
if hasattr(typing, "TypeAliasType"): | ||
TypeAliasType = typing.TypeAliasType | ||
else: | ||
class TypeAliasType: | ||
"""Create named, parameterized type aliases. | ||
|
||
This provides a backport of the new `type` statement in Python 3.12: | ||
|
||
type ListOrSet[T] = list[T] | set[T] | ||
|
||
is equivalent to: | ||
|
||
T = TypeVar("T") | ||
ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,)) | ||
|
||
The name ListOrSet can then be used as an alias for the type it refers to. | ||
|
||
The type_params argument should contain all the type parameters used | ||
in the value of the type alias. If the alias is not generic, this | ||
argument is omitted. | ||
|
||
Static type checkers should only support type aliases declared using | ||
TypeAliasType that follow these rules: | ||
|
||
- The first argument (the name) must be a string literal. | ||
- The TypeAliasType instance must be immediately assigned to a variable | ||
of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid, | ||
as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)'). | ||
|
||
""" | ||
JelleZijlstra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def __init__(self, name: str, value, *, type_params=()): | ||
if not isinstance(name, str): | ||
raise TypeError("TypeAliasType name must be a string") | ||
self.__name__ = name | ||
self.__value__ = value | ||
self.__type_params__ = type_params | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The PEP specifies that these attributes should be read-only, so shouldn't they be read-only properties here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose we could do that, but it feels unnecessarily complicated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't feel strongly, but it doesn't feel that complicated to me, and I think it would be nice to match CPython on 3.12+ here 🤷♂️ |
||
|
||
parameters = [] | ||
for type_param in type_params: | ||
if isinstance(type_param, TypeVarTuple): | ||
parameters.extend(type_param) | ||
else: | ||
parameters.append(type_param) | ||
self.__parameters__ = tuple(parameters) | ||
def_mod = _caller() | ||
if def_mod != 'typing_extensions': | ||
self.__module__ = def_mod | ||
Comment on lines
+2696
to
+2698
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In python/cpython#103764, it looks like >>> type T = int | str
>>> T.__module__
'typing' I like the behavior you have in this PR more, but it's more complicated to implement, and doesn't match the CPython PR currently :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Resolved by python/cpython#104550, for future reference) |
||
|
||
def __repr__(self) -> str: | ||
return self.__name__ | ||
|
||
def __getitem__(self, parameters): | ||
if not isinstance(parameters, tuple): | ||
parameters = (parameters,) | ||
parameters = [ | ||
typing._type_check( | ||
item, f'Subscripting {self.__name__} requires a type.' | ||
) | ||
for item in parameters | ||
] | ||
return typing._GenericAlias(self, tuple(parameters)) | ||
|
||
def __reduce__(self): | ||
return self.__name__ | ||
|
||
def __init_subclass__(cls, *args, **kwargs): | ||
raise TypeError( | ||
"type 'typing_extensions.TypeAliasType' is not an acceptable base type" | ||
) | ||
|
||
# The presence of this method convinces typing._type_check | ||
# that TypeAliasTypes are types. | ||
def __call__(self): | ||
raise TypeError("Type alias is not callable") | ||
|
||
if sys.version_info >= (3, 10): | ||
def __or__(self, right): | ||
return typing.Union[self, right] | ||
|
||
def __ror__(self, left): | ||
return typing.Union[left, self] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Might be worth adding a test like this to python/cpython#103764 as well; I couldn't spot one)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, python/cpython@d4e72a5