mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] support recursive type aliases (#19805)
## Summary Support recursive type aliases by adding a `Type::TypeAlias` type variant, which allows referring to a type alias directly as a type without eagerly unpacking it to its value. We still unpack type aliases when they are added to intersections and unions, so that we can simplify the intersection/union appropriately based on the unpacked value of the type alias. This introduces new possible recursive types, and so also requires expanding our usage of recursion-detecting visitors in Type methods. The use of these visitors is still not fully comprehensive in this PR, and will require further expansion to support recursion in more kinds of types (I already have further work on this locally), but I think it may be better to do this incrementally in multiple PRs. ## Test Plan Added some recursive type-alias tests and made them pass.
This commit is contained in:
parent
d76fd103ae
commit
13bdba5d28
19 changed files with 542 additions and 150 deletions
|
@ -71,6 +71,18 @@ type ListOrSet[T] = list[T] | set[T]
|
|||
reveal_type(ListOrSet.__type_params__) # revealed: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||
```
|
||||
|
||||
## In unions and intersections
|
||||
|
||||
We can "break apart" a type alias by e.g. adding it to a union:
|
||||
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
|
||||
def f(x: IntOrStr, y: str | bytes):
|
||||
z = x or y
|
||||
reveal_type(z) # revealed: (int & ~AlwaysFalsy) | str | bytes
|
||||
```
|
||||
|
||||
## `TypeAliasType` properties
|
||||
|
||||
Two `TypeAliasType`s are distinct and disjoint, even if they refer to the same type
|
||||
|
@ -138,3 +150,50 @@ def get_name() -> str:
|
|||
# error: [invalid-type-alias-type] "The name of a `typing.TypeAlias` must be a string literal"
|
||||
IntOrStr = TypeAliasType(get_name(), int | str)
|
||||
```
|
||||
|
||||
## Cyclic aliases
|
||||
|
||||
### Self-referential
|
||||
|
||||
```py
|
||||
type OptNestedInt = int | tuple[OptNestedInt, ...] | None
|
||||
|
||||
def f(x: OptNestedInt) -> None:
|
||||
reveal_type(x) # revealed: int | tuple[OptNestedInt, ...] | None
|
||||
if x is not None:
|
||||
reveal_type(x) # revealed: int | tuple[OptNestedInt, ...]
|
||||
```
|
||||
|
||||
### Invalid self-referential
|
||||
|
||||
```py
|
||||
# TODO emit a diagnostic here
|
||||
type IntOr = int | IntOr
|
||||
|
||||
def f(x: IntOr):
|
||||
reveal_type(x) # revealed: int
|
||||
if not isinstance(x, int):
|
||||
reveal_type(x) # revealed: Never
|
||||
```
|
||||
|
||||
### Mutually recursive
|
||||
|
||||
```py
|
||||
type A = tuple[B] | None
|
||||
type B = tuple[A] | None
|
||||
|
||||
def f(x: A):
|
||||
if x is not None:
|
||||
reveal_type(x) # revealed: tuple[B]
|
||||
y = x[0]
|
||||
if y is not None:
|
||||
reveal_type(y) # revealed: tuple[A]
|
||||
|
||||
def g(x: A | B):
|
||||
reveal_type(x) # revealed: tuple[B] | None
|
||||
|
||||
from ty_extensions import Intersection
|
||||
|
||||
def h(x: Intersection[A, B]):
|
||||
reveal_type(x) # revealed: tuple[B] | None
|
||||
```
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue