-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Add support for generic PEP695 type aliases #20219
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
40b7c95
add support for generic PEP695 type aliases
ibraheemdev 6599945
avoid infinite recursion in `try_bool` for type aliases
ibraheemdev f7bae33
remove outdated comment
ibraheemdev 58e6103
fix default specialization for generic type aliases
ibraheemdev 6476c6a
avoid eagerly unpacking specialized type aliases
ibraheemdev 3151714
move specialization API from `PEP695TypeAliasType` to `TypeAliasType`
ibraheemdev 0d2a272
add cycle recover to `TypeVarInstance::lazy_bound`
ibraheemdev f5c6ca5
remove incorrect test with shadowed type variables
ibraheemdev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| # Generic type aliases: PEP 695 syntax | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.13" | ||
| ``` | ||
|
|
||
| ## Defining a generic alias | ||
|
|
||
| At its simplest, to define a type alias using PEP 695 syntax, you add a list of `TypeVar`s, | ||
| `ParamSpec`s or `TypeVarTuple`s after the alias name. | ||
|
|
||
| ```py | ||
| from ty_extensions import generic_context | ||
|
|
||
| type SingleTypevar[T] = ... | ||
| type MultipleTypevars[T, S] = ... | ||
| type SingleParamSpec[**P] = ... | ||
| type TypeVarAndParamSpec[T, **P] = ... | ||
| type SingleTypeVarTuple[*Ts] = ... | ||
| type TypeVarAndTypeVarTuple[T, *Ts] = ... | ||
|
|
||
| # revealed: tuple[T@SingleTypevar] | ||
| reveal_type(generic_context(SingleTypevar)) | ||
| # revealed: tuple[T@MultipleTypevars, S@MultipleTypevars] | ||
| reveal_type(generic_context(MultipleTypevars)) | ||
|
|
||
| # TODO: support `ParamSpec`/`TypeVarTuple` properly | ||
| # (these should include the `ParamSpec`s and `TypeVarTuple`s in their generic contexts) | ||
| reveal_type(generic_context(SingleParamSpec)) # revealed: tuple[()] | ||
| reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: tuple[T@TypeVarAndParamSpec] | ||
| reveal_type(generic_context(SingleTypeVarTuple)) # revealed: tuple[()] | ||
| reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: tuple[T@TypeVarAndTypeVarTuple] | ||
| ``` | ||
|
|
||
| You cannot use the same typevar more than once. | ||
|
|
||
| ```py | ||
| # error: [invalid-syntax] "duplicate type parameter" | ||
| type RepeatedTypevar[T, T] = ... | ||
| ``` | ||
|
|
||
| ## Specializing type aliases explicitly | ||
ibraheemdev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| The type parameter can be specified explicitly: | ||
|
|
||
| ```py | ||
| from typing import Literal | ||
|
|
||
| type C[T] = T | ||
|
|
||
| def _(a: C[int], b: C[Literal[5]]): | ||
| reveal_type(a) # revealed: int | ||
| reveal_type(b) # revealed: Literal[5] | ||
| ``` | ||
|
|
||
| The specialization must match the generic types: | ||
|
|
||
| ```py | ||
| # error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2" | ||
| reveal_type(C[int, int]) # revealed: Unknown | ||
| ``` | ||
|
|
||
| And non-generic types cannot be specialized: | ||
|
|
||
| ```py | ||
| type B = ... | ||
|
|
||
| # error: [non-subscriptable] "Cannot subscript non-generic type alias" | ||
| reveal_type(B[int]) # revealed: Unknown | ||
|
|
||
| # error: [non-subscriptable] "Cannot subscript non-generic type alias" | ||
| def _(b: B[int]): ... | ||
| ``` | ||
|
|
||
| If the type variable has an upper bound, the specialized type must satisfy that bound: | ||
|
|
||
| ```py | ||
| type Bounded[T: int] = ... | ||
| type BoundedByUnion[T: int | str] = ... | ||
|
|
||
| class IntSubclass(int): ... | ||
|
|
||
| reveal_type(Bounded[int]) # revealed: Bounded[int] | ||
| reveal_type(Bounded[IntSubclass]) # revealed: Bounded[IntSubclass] | ||
|
|
||
| # TODO: update this diagnostic to talk about type parameters and specializations | ||
| # error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `str`" | ||
| reveal_type(Bounded[str]) # revealed: Unknown | ||
|
|
||
| # TODO: update this diagnostic to talk about type parameters and specializations | ||
| # error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `int | str`" | ||
| reveal_type(Bounded[int | str]) # revealed: Unknown | ||
|
|
||
| reveal_type(BoundedByUnion[int]) # revealed: BoundedByUnion[int] | ||
| reveal_type(BoundedByUnion[IntSubclass]) # revealed: BoundedByUnion[IntSubclass] | ||
| reveal_type(BoundedByUnion[str]) # revealed: BoundedByUnion[str] | ||
| reveal_type(BoundedByUnion[int | str]) # revealed: BoundedByUnion[int | str] | ||
| ``` | ||
|
|
||
| If the type variable is constrained, the specialized type must satisfy those constraints: | ||
|
|
||
| ```py | ||
| type Constrained[T: (int, str)] = ... | ||
|
|
||
| reveal_type(Constrained[int]) # revealed: Constrained[int] | ||
|
|
||
| # TODO: error: [invalid-argument-type] | ||
| # TODO: revealed: Constrained[Unknown] | ||
| reveal_type(Constrained[IntSubclass]) # revealed: Constrained[IntSubclass] | ||
|
|
||
| reveal_type(Constrained[str]) # revealed: Constrained[str] | ||
|
|
||
| # TODO: error: [invalid-argument-type] | ||
| # TODO: revealed: Unknown | ||
| reveal_type(Constrained[int | str]) # revealed: Constrained[int | str] | ||
|
|
||
| # TODO: update this diagnostic to talk about type parameters and specializations | ||
| # error: [invalid-argument-type] "Argument is incorrect: Expected `int | str`, found `object`" | ||
| reveal_type(Constrained[object]) # revealed: Unknown | ||
| ``` | ||
|
|
||
| If the type variable has a default, it can be omitted: | ||
|
|
||
| ```py | ||
| type WithDefault[T, U = int] = ... | ||
|
|
||
| reveal_type(WithDefault[str, str]) # revealed: WithDefault[str, str] | ||
| reveal_type(WithDefault[str]) # revealed: WithDefault[str, int] | ||
| ``` | ||
|
|
||
| If the type alias is not specialized explicitly, it is implicitly specialized to `Unknown`: | ||
|
|
||
| ```py | ||
| type G[T] = list[T] | ||
|
|
||
| def _(g: G): | ||
| reveal_type(g) # revealed: list[Unknown] | ||
| ``` | ||
|
|
||
| Unless a type default was provided: | ||
|
|
||
| ```py | ||
| type G[T = int] = list[T] | ||
|
|
||
| def _(g: G): | ||
| reveal_type(g) # revealed: list[int] | ||
| ``` | ||
|
|
||
| ## Aliases are not callable | ||
|
|
||
| ```py | ||
| type A = int | ||
| type B[T] = T | ||
|
|
||
| # error: [call-non-callable] "Object of type `TypeAliasType` is not callable" | ||
| reveal_type(A()) # revealed: Unknown | ||
|
|
||
| # error: [call-non-callable] "Object of type `GenericAlias` is not callable" | ||
| reveal_type(B[int]()) # revealed: Unknown | ||
| ``` | ||
|
|
||
| ## Recursive Truthiness | ||
|
|
||
| Make sure we handle cycles correctly when computing the truthiness of a generic type alias: | ||
|
|
||
| ```py | ||
| type X[T: X] = T | ||
|
|
||
| def _(x: X): | ||
| assert x | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
We also have
resources/mdtest/pep695_type_aliases.md. Should these two files be merged?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.
Possibly, although this file is specific to generic PEP 695 aliases.
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.
Yes. And I don't care too much about the structure/layout of the tests. Just wanted do let you know that there are some generics-related tests in that other file as well.
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. I think we can look at this as a follow-up.