# PEP 695 type aliases

PEP 695 type aliases are only available in Python 3.12 and later:

```toml
[environment]
python-version = "3.12"
```

## Basic

```py
type IntOrStr = int | str

reveal_type(IntOrStr)  # revealed: TypeAliasType
reveal_type(IntOrStr.__name__)  # revealed: Literal["IntOrStr"]

x: IntOrStr = 1

reveal_type(x)  # revealed: Literal[1]

def f() -> None:
    reveal_type(x)  # revealed: int | str
```

## Type aliases in `type[...]`

```py
from typing import Type, TypeAliasType, assert_never

class A: ...
class B: ...

type IntAlias = int
type ChainedIntAlias = IntAlias
type UnionAlias = A | B
type NestedUnionAlias = IntAlias | str
type GenericAlias[T] = T
ManualIntAlias = TypeAliasType("ManualIntAlias", int)

def _(
    simple: type[IntAlias],
    chained: type[ChainedIntAlias],
    union: type[UnionAlias],
    nested_union: type[NestedUnionAlias],
    direct_union: type[IntAlias | str],
    generic: type[GenericAlias[int]],
    manual: type[ManualIntAlias],
    typing_type: Type[IntAlias],
):
    reveal_type(simple)  # revealed: type[int]
    reveal_type(chained)  # revealed: type[int]
    reveal_type(union)  # revealed: type[A | B]
    reveal_type(nested_union)  # revealed: type[int | str]
    reveal_type(direct_union)  # revealed: type[int | str]
    reveal_type(generic)  # revealed: type[int]
    reveal_type(manual)  # revealed: type[int]
    reveal_type(typing_type)  # revealed: type[int]

# error: [invalid-assignment]
bad: type[IntAlias] = str

def exhaust_union(value: type[UnionAlias]) -> str:
    if issubclass(value, A):
        return "A"
    if issubclass(value, B):
        return "B"
    assert_never(value)
```

## `__value__` attribute

```py
type IntOrStr = int | str

reveal_type(IntOrStr.__value__)  # revealed: Any
```

## Invalid assignment

```py
type OptionalInt = int | None

# error: [invalid-assignment]
x: OptionalInt = "1"
```

## No type qualifiers

The right-hand side of a type alias definition is a type expression, not an annotation expression.
Type qualifiers like `ClassVar` and `Final` are only valid in annotation expressions, so they cannot
appear at the top level of a PEP 695 alias definition:

```py
from typing_extensions import ClassVar, Final, Required, NotRequired, ReadOnly
from dataclasses import InitVar

# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type alias values"
type Bad1 = ClassVar[str]
# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type alias values"
type Bad2 = ClassVar
# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type alias values"
type Bad3 = Final[int]
# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type alias values"
type Bad4 = Final
# error: [invalid-type-form] "Type qualifier `typing.Required` is not allowed in type alias values"
type Bad5 = Required[int]
# error: [invalid-type-form] "Type qualifier `typing.NotRequired` is not allowed in type alias values"
type Bad6 = NotRequired[int]
# error: [invalid-type-form] "Type qualifier `typing.ReadOnly` is not allowed in type alias values"
type Bad7 = ReadOnly[int]
# error: [invalid-type-form] "Type qualifier `dataclasses.InitVar` is not allowed in type alias values"
type Bad8 = InitVar[int]
# error: [invalid-type-form] "Type qualifier `dataclasses.InitVar` is not allowed in type alias values"
type Bad9 = InitVar
```

## Type aliases in type aliases

```py
type IntOrStr = int | str
type IntOrStrOrBytes = IntOrStr | bytes

x: IntOrStrOrBytes = 1

def f() -> None:
    reveal_type(x)  # revealed: int | str | bytes
```

## `Self`

`Self` is not allowed in an explicit type alias value, even when the alias is defined in a class
body. Runtime-expression positions, such as `Annotated` metadata, are not part of the alias value's
type expression.

TODO: Reject `Self` in alias type-parameter bounds and defaults.

TODO: Reject `Self` introduced indirectly through runtime-expression forms such as `TypeOf[value]`.

```py
from typing import Annotated, Self, cast

class C:
    # error: [invalid-type-form] "`Self` cannot be used in a type alias"
    type Alias = tuple[Self]

    # error: [invalid-type-form] "`Self` cannot be used in a type alias"
    type Simplified = object | Self

    type Metadata = Annotated[int, cast(Self, object())]
```

## Aliased type aliases

```py
type IntOrStr = int | str
MyIntOrStr = IntOrStr

x: MyIntOrStr = 1

# error: [invalid-assignment]
y: MyIntOrStr = None
```

## Unpacking from a type alias

```py
type T = tuple[int, str]

def f(x: T):
    a, b = x
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: str
```

## Scoping

PEP 695 type aliases delay runtime evaluation of their right-hand side, so they are a lazy (not
eager) nested scope.

```py
type Alias = Foo | str

def f(x: Alias):
    reveal_type(x)  # revealed: Foo | str

class Foo:
    pass
```

But narrowing of names used in the type alias is still respected:

```py
def _(flag: bool):
    t = int if flag else None
    if t is not None:
        type Alias = t | str
        def f(x: Alias):
            reveal_type(x)  # revealed: int | str
```

## Generic type aliases

```py
type ListOrSet[T] = list[T] | set[T]
reveal_type(ListOrSet.__type_params__)  # revealed: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
type Tuple1[T] = tuple[T]

def _(cond: bool):
    Generic = ListOrSet if cond else Tuple1

    def _(x: Generic[int]):
        reveal_type(x)  # revealed: list[int] | set[int] | tuple[int]

try:
    class Foo[T]:
        x: T
        def foo(self) -> T:
            return self.x

    ...
except Exception:
    class Foo[T]:
        x: T
        def foo(self) -> T:
            return self.x

def f(x: Foo[int]):
    reveal_type(x.foo())  # revealed: int
```

## Stringified values

Stringifying the right-hand side of a type alias is redundant, but allowed:

```py
type X = "int | str"

def f(obj: X):
    reveal_type(obj)  # revealed: int | str
```

The right-hand side of a PEP-695 type alias will not usually be executed, but can be if the user
accesses the `.__value__` attribute. Normal runtime rules still therefore apply regarding partially
stringified alias values:

```py
# snapshot: unsupported-operator
type Y = "int" | str

def g(obj: Y):
    reveal_type(obj)  # revealed: int | str
```

```snapshot
error[unsupported-operator]: Unsupported `|` operation
 --> src/mdtest_snippet.py:6:10
  |
6 | type Y = "int" | str
  |          -----^^^---
  |          |       |
  |          |       Has type `<class 'str'>`
  |          Has type `Literal["int"]`
info: A type alias scope is lazy but will be executed at runtime if the `__value__` property is accessed
```

## 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
```

## Loop-carried augmented unions

PEP 604 unions created by augmented assignment should converge when the previous loop iteration
already contains the same type alias:

```py
def f(condition: bool):
    type Left = int
    alias = Left
    reveal_type(alias)  # revealed: TypeAliasType

    while condition:
        type Right = str
        alias |= Right
        reveal_type(alias)  # revealed: <types.UnionType special-form 'int | str'>

    reveal_type(alias)  # revealed: TypeAliasType | <types.UnionType special-form 'int | str'>

    # it would be okay to emit an `invalid-type-form` error here
    def inner(x: alias):
        reveal_type(x)  # revealed: int | str
```

## Multiple layers of union aliases

```py
class A: ...
class B: ...
class C: ...
class D: ...

type W = A | B
type X = C | D
type Y = W | X

from ty_extensions import static_assert
from ty_extensions._internal import is_equivalent_to

static_assert(is_equivalent_to(Y, A | B | C | D))
```

## Unions of enum literal aliases

A union of aliases covering every member of an enum is equivalent to the enum itself, including when
the aliases occur inside a larger type.

```py
from enum import Enum
from typing import Literal

class Choice(Enum):
    A = "A"
    B = "B"

type A = Literal[Choice.A]
type B = Literal[Choice.B]
type Either = A | B
type Selector = A | B | tuple[A, B]

def accept_either(value: Either) -> None: ...
def accept_optional_either(value: Either | None) -> None: ...
def accept_selector(value: Selector) -> None: ...

class Config:
    either: Either
    selector: Selector

def _(choice: Choice, config: Config) -> None:
    direct: Either = choice
    accept_either(choice)
    accept_optional_either(config.either)
    values: list[Selector] = []
    accept_selector(config.selector)

class ExtendedChoice(Enum):
    A = "A"
    B = "B"
    C = "C"

type ExtendedA = Literal[ExtendedChoice.A]
type ExtendedB = Literal[ExtendedChoice.B]

def _(choice: ExtendedChoice) -> None:
    partial: ExtendedA | ExtendedB = choice  # error: [invalid-assignment]
```

## In binary ops

```py
from typing import Literal

type X = tuple[Literal[1], Literal[2]]

def _(x: X, y: tuple[Literal[1], Literal[3]]):
    reveal_type(x == y)  # revealed: Literal[False]
    reveal_type(x < y)  # revealed: Literal[True]
```

## `TypeAliasType` properties

Two `TypeAliasType`s are distinct and disjoint, even if they refer to the same type

```py
from ty_extensions import static_assert
from ty_extensions._internal import TypeOf, is_equivalent_to, is_disjoint_from

type Alias1 = int
type Alias2 = int

type TypeAliasType1 = TypeOf[Alias1]
type TypeAliasType2 = TypeOf[Alias2]

static_assert(not is_equivalent_to(TypeAliasType1, TypeAliasType2))
static_assert(is_disjoint_from(TypeAliasType1, TypeAliasType2))
```

## Direct use of `TypeAliasType`

`TypeAliasType` can also be used directly. This is useful for versions of Python prior to 3.12.

```toml
[environment]
python-version = "3.9"
```

### Basic example

```py
from typing_extensions import TypeAliasType, Union

IntOrStr = TypeAliasType("IntOrStr", Union[int, str])

reveal_type(IntOrStr)  # revealed: TypeAliasType

reveal_type(IntOrStr.__name__)  # revealed: Literal["IntOrStr"]

def f(x: IntOrStr) -> None:
    reveal_type(x)  # revealed: int | str
```

### Generic example

Manual aliases can be specialized in annotations and value positions, including when they are used
in `type[...]` or nested inside another alias.

```py
from typing import Callable, Concatenate, Generic
from typing_extensions import ParamSpec, TypeAliasType, TypeVar, TypeVarTuple, Union, Unpack

T = TypeVar("T")

IntAndT = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))

def f(x: IntAndT[str]) -> None:
    reveal_type(x)  # revealed: tuple[int, str]

reveal_type(IntAndT[str])  # revealed: <type alias 'IntAndT[str]'>

def generic_meta(value: type[IntAndT[str]]) -> None:
    reveal_type(value)  # revealed: type[tuple[int, str]]

Nested = TypeAliasType("Nested", list[IntAndT[T]], type_params=(T,))

def nested(value: Nested[str]) -> None:
    reveal_type(value)  # revealed: list[IntAndT[str]]
```

Defaults apply to unspecialized aliases, and the order of `type_params` determines how type
arguments are mapped even if the parameters appear in a different order in the alias value.

```py
U = TypeVar("U", default=str)

ListOrSet = TypeAliasType("ListOrSet", Union[list[U], set[U]], type_params=(U,))
MyDict = TypeAliasType("MyDict", dict[T, U], type_params=(T, U))
Reordered = TypeAliasType("Reordered", tuple[U, T], type_params=(T, U))

def g(
    list_or_set_of_int: ListOrSet[int],
    list_or_set_of_str: ListOrSet,
    dict_int_str: MyDict[int, str],
    dict_unknown_str: MyDict,
    reordered: Reordered[int, str],
) -> None:
    reveal_type(list_or_set_of_int)  # revealed: list[int] | set[int]
    reveal_type(list_or_set_of_str)  # revealed: list[str] | set[str]
    reveal_type(dict_int_str)  # revealed: dict[int, str]
    reveal_type(dict_unknown_str)  # revealed: dict[Unknown, str]
    reveal_type(reordered)  # revealed: tuple[str, int]
```

Constructor inference sees through the specialized `ModelAlias[T]`: passing `Model` infers `T` as
`Model` in `ViaAlias[T]`.

```py
ModelAlias = TypeAliasType("ModelAlias", type[T], type_params=(T,))

class Model: ...

class ViaAlias(Generic[T]):
    def __init__(self, value: ModelAlias[T]) -> None: ...

reveal_type(ViaAlias(Model))  # revealed: ViaAlias[Model]
```

`ParamSpec` parameters can be specialized alongside regular type variables and are preserved when a
callable alias is used as a decorator return type.

```py
P = ParamSpec("P")
R = TypeVar("R")
WrappedMethod = TypeAliasType("WrappedMethod", Callable[Concatenate[T, P], R], type_params=(T, P, R))

def wrapped_method(value: WrappedMethod[int, P, str]) -> None:
    reveal_type(value)  # revealed: (int, /, *args: P@wrapped_method.args, **kwargs: P@wrapped_method.kwargs) -> str

WrapsMethod = TypeAliasType("WrapsMethod", Callable[Concatenate[T, ...], R], type_params=(T, R))

def decorate(value: WrapsMethod[T, R], /) -> WrappedMethod[T, P, R]:
    return value

@decorate
def decorated(value: int) -> int:
    return value

reveal_type(decorated)  # revealed: [**P'return](int, /, *args: P'return.args, **kwargs: P'return.kwargs) -> int
```

`TypeVarTuple` parameters accept multiple type arguments when specializing a variadic alias.

```py
Ts = TypeVarTuple("Ts")
Variadic = TypeAliasType("Variadic", tuple[Unpack[Ts]], type_params=(Ts,))

def variadic(value: Variadic[int, str]) -> None:
    reveal_type(value)  # revealed: tuple[int, str]
```

### Recursive generic example

```py
from typing import Callable
from typing_extensions import TypeAliasType, TypeVar, Union

T = TypeVar("T")
Recursive = TypeAliasType("Recursive", Union[T, list["Recursive[T]"]], type_params=(T,))
RecursiveCallable = Callable[[Recursive[T]], None]

def recursive(value: Recursive[int]) -> None:
    reveal_type(value)  # revealed: int | list[Recursive[int]]

def recursive_callable(value: RecursiveCallable[int]) -> None:
    reveal_type(value)  # revealed: (Recursive[int], /) -> None
```

### Generic specialization errors

```py
from typing_extensions import TypeAliasType, TypeVar

T = TypeVar("T")
BoundedT = TypeVar("BoundedT", bound=int)

GenericAlias = TypeAliasType("GenericAlias", list[T], type_params=(T,))
BoundedAlias = TypeAliasType("BoundedAlias", list[BoundedT], type_params=(BoundedT,))
NonGenericAlias = TypeAliasType("NonGenericAlias", list[int])
DefaultedT = TypeVar("DefaultedT", default=str)

# error: [invalid-type-variable-default] "Type parameter `T` without a default cannot follow earlier parameter `DefaultedT` with a default"
InvalidOrder = TypeAliasType("InvalidOrder", tuple[DefaultedT, T], type_params=(DefaultedT, T))

# error: [invalid-type-arguments] "Too many type arguments: expected 1, got 2"
reveal_type(GenericAlias[int, str])  # revealed: <type alias 'GenericAlias[Unknown]'>

# error: [invalid-type-arguments] "Type `str` is not assignable to upper bound `int` of type variable `BoundedT@BoundedAlias`"
reveal_type(BoundedAlias[str])  # revealed: <type alias 'BoundedAlias[Unknown]'>

# error: [not-subscriptable] "Cannot subscript non-generic type alias `NonGenericAlias`"
reveal_type(NonGenericAlias[int])  # revealed: Unknown

# error: [not-subscriptable] "Cannot specialize non-generic type alias `NonGenericAlias`"
def non_generic(value: NonGenericAlias[int]) -> None:
    reveal_type(value)  # revealed: Unknown
```

### Invalid type parameters

```py
from typing_extensions import TypeAliasType, TypeVar, TypeVarTuple, Union, Unpack

T = TypeVar("T")
U = TypeVar("U")
Ts = TypeVarTuple("Ts")
Us = TypeVarTuple("Us")

# error: [invalid-type-alias-type] "The `type_params` argument to `TypeAliasType` must be a tuple literal"
InvalidList = TypeAliasType("InvalidList", list[T], type_params=[T])

# error: [invalid-type-alias-type] "The `type_params` argument to `TypeAliasType` must be a tuple literal"
InvalidBare = TypeAliasType("InvalidBare", list[T], type_params=T)

params = (T,)
# error: [invalid-type-alias-type] "The `type_params` argument to `TypeAliasType` must be a tuple literal"
InvalidTupleVariable = TypeAliasType("InvalidTupleVariable", list[T], type_params=params)

# error: [invalid-type-alias-type] "Each `type_params` entry for `TypeAliasType` must be a type variable"
InvalidUnpack = TypeAliasType("InvalidUnpack", list[T], type_params=(*params,))

# error: [invalid-type-alias-type] "Each `type_params` entry for `TypeAliasType` must be a type variable"
InvalidNested = TypeAliasType("InvalidNested", list[T], type_params=(list[T],))

# error: [invalid-type-alias-type] "Each `type_params` entry for `TypeAliasType` must be a type variable"
InvalidMixed = TypeAliasType("InvalidMixed", list[T], type_params=(int, T))

# error: [invalid-type-alias-type] "Type parameter `U` used in the alias value must be included in `type_params`"
Missing = TypeAliasType("Missing", dict[T, U], type_params=(T,))

# error: [invalid-type-alias-type] "Type parameter `T` used in the alias value must be included in `type_params`"
MissingAll = TypeAliasType("MissingAll", list[T])

# error: [invalid-type-alias-type] "Type parameter `T` is duplicated in `type_params`"
Duplicate = TypeAliasType("Duplicate", tuple[T, U], type_params=(T, U, T))

MultipleTypeVarTuples = TypeAliasType(
    "MultipleTypeVarTuples",
    Union[tuple[Unpack[Ts]], tuple[Unpack[Us]]],
    # error: [invalid-type-alias-type] "Only one `TypeVarTuple` parameter is allowed in `type_params`"
    type_params=(Ts, Us),
)

DefaultedT = TypeVar("DefaultedT", default=int)

DefaultAfterTypeVarTuple = TypeAliasType(
    "DefaultAfterTypeVarTuple",
    tuple[Unpack[Ts], DefaultedT],
    # error: [invalid-type-variable-default] "Type parameter `DefaultedT` with a default follows TypeVarTuple `Ts`"
    type_params=(Ts, DefaultedT),
)

InvalidOrderAndEntries = TypeAliasType(
    "InvalidOrderAndEntries",
    tuple[DefaultedT, T],
    # error: [invalid-type-variable-default] "Type parameter `T` without a default cannot follow earlier parameter `DefaultedT` with a default"
    # error: [invalid-type-alias-type] "Type parameter `T` is duplicated in `type_params`"
    # error: [invalid-type-alias-type] "Each `type_params` entry for `TypeAliasType` must be a type variable"
    type_params=(DefaultedT, T, T, "V"),
)
```

### Scoped type parameters

```toml
[environment]
python-version = "3.12"
```

```py
from typing import Callable
from typing_extensions import TypeAliasType, TypeVar

LegacyT = TypeVar("LegacyT")

def pep695_outer[T]() -> None:
    # error: [invalid-type-alias-type] "Type parameter `T` is bound in an outer scope and cannot be used in `type_params`"
    Pep695Alias = TypeAliasType("Pep695Alias", list[T], type_params=(T,))
    # error: [not-subscriptable] "Cannot specialize non-generic type alias `Pep695Alias`"
    def check(value: Pep695Alias[int]) -> None: ...

def legacy_outer(value: LegacyT) -> None:
    # error: [invalid-type-alias-type] "Type parameter `LegacyT` is bound in an outer scope and cannot be used in `type_params`"
    LegacyAlias = TypeAliasType("LegacyAlias", list[LegacyT], type_params=(LegacyT,))
    # error: [not-subscriptable] "Cannot specialize non-generic type alias `LegacyAlias`"
    def check(value: LegacyAlias[int]) -> None: ...

class Pep695Outer[T]:
    # error: [invalid-type-alias-type] "Type parameter `T` is bound in an outer scope and cannot be used in `type_params`"
    ClassAlias = TypeAliasType("ClassAlias", list[T], type_params=(T,))

def paramspec_outer[**P]() -> None:
    # error: [invalid-type-alias-type] "Type parameter `P` is bound in an outer scope and cannot be used in `type_params`"
    ParamSpecAlias = TypeAliasType("ParamSpecAlias", Callable[P, int], type_params=(P,))

def variadic_outer[*Ts]() -> None:
    # error: [invalid-type-alias-type] "Type parameter `Ts` is bound in an outer scope and cannot be used in `type_params`"
    VariadicAlias = TypeAliasType("VariadicAlias", tuple[*Ts], type_params=(Ts,))
```

### Generic alias from `typing`

```toml
[environment]
python-version = "3.12"
```

```py
from typing import TypeAliasType, TypeVar

K = TypeVar("K")
V = TypeVar("V")
MyDict = TypeAliasType("MyDict", dict[K, V], type_params=(K, V))

def generic_from_typing(value: MyDict[str, int]) -> None:
    reveal_type(value)  # revealed: dict[str, int]
```

### PEP 695 aliases in `Callable`

```toml
[environment]
python-version = "3.12"
```

```py
from typing import Callable, TypeVar

T = TypeVar("T")

type Pep695List[A] = list[A]
Pep695ConcreteCallable = Callable[[Pep695List[int]], None]
Pep695GenericCallable = Callable[[Pep695List[T]], None]

type Recursive[A] = A | list[Recursive[A]]
RecursiveCallable = Callable[[Recursive[int]], None]

def _(
    concrete: Pep695ConcreteCallable,
    generic: Pep695GenericCallable[str],
    recursive: RecursiveCallable,
) -> None:
    reveal_type(concrete)  # revealed: (Pep695List[int], /) -> None
    reveal_type(generic)  # revealed: (Pep695List[str], /) -> None
    reveal_type(recursive)  # revealed: (Recursive[int], /) -> None
```

### Generic value binds type variables to alias definition

```py
from typing import Generic
from typing_extensions import TypeAliasType, TypeVar

T = TypeVar("T", bound=int)
A = TypeAliasType("A", tuple[T], type_params=(T,))

S = TypeVar("S", bound=tuple[int])

class C(Generic[S]):
    pass

x: C[A]
```

### Error cases

#### Name is not a string literal

```py
from typing_extensions import TypeAliasType

def get_name() -> str:
    return "IntOrStr"

# error: [invalid-type-alias-type] "The first argument to `TypeAliasType` must be a string literal"
IntOrStr = TypeAliasType(get_name(), int | str)
```

#### Name does not match variable

```py
from typing import Union
from typing_extensions import TypeAliasType

# error: [mismatched-type-name] "The name passed to `TypeAliasType` must match the variable it is assigned to: Expected "IntOrStr", got "WrongName""
IntOrStr = TypeAliasType("WrongName", Union[int, str])
reveal_type(IntOrStr)  # revealed: TypeAliasType
```

#### Not a simple variable assignment

`TypeAliasType` must be used in a simple variable assignment. Using it as a standalone expression or
in a tuple unpacking is not supported.

```py
from typing_extensions import TypeAliasType

# error: [invalid-type-alias-type] "A `TypeAliasType` definition must be a simple variable assignment"
TypeAliasType("IntOrStr", "int | str")
```

### Mutually recursive `TypeAliasType` definitions

Mutually recursive type aliases created via the `TypeAliasType` constructor should not cause the
type checker to hang. The value type is computed lazily to break cycles.

```py
from typing_extensions import TypeAliasType, Union

A = TypeAliasType("A", Union[str, "B"])
B = TypeAliasType("B", list[A])

def f(x: A) -> None:
    reveal_type(x)  # revealed: str | list[A]

def g(x: B) -> None:
    reveal_type(x)  # revealed: list[A]
```

## 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, ...]

type RecursiveList = list[RecursiveList]

def g(x: RecursiveList):
    reveal_type(x[0])  # revealed: list[RecursiveList]
```

### Invalid self-referential

```py
# TODO emit a diagnostic on these two lines
type IntOr = int | IntOr
type OrInt = OrInt | int

def f(x: IntOr, y: OrInt):
    reveal_type(x)  # revealed: int
    reveal_type(y)  # revealed: int
    if not isinstance(x, int):
        reveal_type(x)  # revealed: Never
    if not isinstance(y, int):
        reveal_type(y)  # revealed: Never

# error: [cyclic-type-alias-definition] "Cyclic definition of `Itself`"
type Itself = Itself

def foo(
    # this is a very strange thing to do, but this is a regression test to ensure it doesn't panic
    Itself: Itself,
):
    x: Itself
    reveal_type(Itself)  # revealed: Divergent

# A type alias defined with invalid recursion behaves as a dynamic type.
foo(42)
foo("hello")

# error: [cyclic-type-alias-definition] "Cyclic definition of `A`"
type A = B
# error: [cyclic-type-alias-definition] "Cyclic definition of `B`"
type B = A

def bar(B: B):
    x: B
    reveal_type(B)  # revealed: Divergent

# error: [cyclic-type-alias-definition] "Cyclic definition of `G`"
type G[T] = G[T]
# error: [cyclic-type-alias-definition] "Cyclic definition of `H`"
type H[T] = I[T]
# error: [cyclic-type-alias-definition] "Cyclic definition of `I`"
type I[T] = H[T]

# It's not possible to create an element of this type, but it's not an error for now
type DirectRecursiveList[T] = list[DirectRecursiveList[T]]

# TODO: this should probably be a cyclic-type-alias-definition error
type Foo[T] = list[T] | Bar[T]
type Bar[T] = int | Foo[T]

def _(x: Bar[int]):
    reveal_type(x)  # revealed: int | list[int]
```

### With legacy generic

```py
from typing import Generic, TypeVar

T = TypeVar("T")

type Alias = list["Alias"] | int

class A(Generic[T]):
    attr: T

class B(A[Alias]):
    pass

def f(b: B):
    reveal_type(b)  # revealed: B
    reveal_type(b.attr)  # revealed: list[Alias] | int
```

### 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
```

### Self-recursive callable type

```py
from typing import Callable

type C = Callable[[], C | None]

def _(x: C):
    reveal_type(x)  # revealed: () -> C | None
```

### Growing recursive alias relations without unions

```py
from ty_extensions import static_assert
from ty_extensions._internal import is_subtype_of

type Left[T] = tuple[Left[list[T]]]
type Right[T] = tuple[Right[list[T]]]

# TODO: Left[int] should be equivalent to (subtype of) Right[int]
static_assert(not is_subtype_of(Left[int], Right[int]))

type Box[T] = list[T]
type WrappedLeft[T] = tuple[Box[Box[WrappedLeft[list[T]]]]]
type WrappedRight[T] = tuple[Box[Box[WrappedRight[list[T]]]]]

# A repeated non-recursive alias must not hide the recursive reference in its type arguments.
# TODO: WrappedLeft[int] should be equivalent to (subtype of) WrappedRight[int]
static_assert(not is_subtype_of(WrappedLeft[int], WrappedRight[int]))
```

### Non-recursive nested generic aliases

A repeated use of the same generic alias can be a finite alias application instead of recursion.

```py
from typing import Literal

from ty_extensions import static_assert
from ty_extensions._internal import is_subtype_of

type NonRecursiveId[T] = T

static_assert(is_subtype_of(NonRecursiveId[NonRecursiveId[int]], int))
static_assert(not is_subtype_of(NonRecursiveId[NonRecursiveId[int]], str))

truth: NonRecursiveId[NonRecursiveId[Literal[True]]] = True
static_assert(truth)

one: NonRecursiveId[NonRecursiveId[Literal[1]]] = 1
reveal_type(one + 1)  # revealed: Literal[2]
reveal_type(one == 1)  # revealed: Literal[True]

def nested_union(
    value: list[NonRecursiveId[NonRecursiveId[int]]] | list[int],
):
    reveal_type(value)  # revealed: list[NonRecursiveId[NonRecursiveId[int]]]

# A finite nested application can also be hidden behind another named alias.
type Intermediate[T] = T
type LeftIntAlias = NonRecursiveId[int]
type RightIntAlias = NonRecursiveId[int]

def finite_alias_chain(x: NonRecursiveId[Intermediate[NonRecursiveId[int]]]):
    reveal_type(x + 1)  # revealed: int
    reveal_type(x == 1)  # revealed: bool
    # error: [invalid-assignment]
    invalid: str = x

def equivalent_finite_aliases(x: NonRecursiveId[LeftIntAlias]):
    valid: NonRecursiveId[RightIntAlias] = x

type IntAlias = int

def unchanged_alias_pair(x: NonRecursiveId[NonRecursiveId[bool]]):
    # `bool` is a subtype of `int`. The unchanged target alias must not cause the finite source
    # expansion to be treated as recursive.
    valid: IntAlias = x

type NoneAlias = NonRecursiveId[None]
type NestedNoneAlias = NonRecursiveId[NoneAlias]

def finite_alias_union(x: NonRecursiveId[NestedNoneAlias], condition: bool):
    reveal_type(x if condition else 1)  # revealed: None | Literal[1]
```

### Generic self-recursive aliases with deeper specializations

Regression test for <https://github.com/astral-sh/ty/issues/3452>.

A recursive alias may refer to itself with a more deeply nested specialization. This should still
terminate and preserve the alias at the recursive position.

```py
from typing import Callable, Concatenate

type Recursive[T] = int | Recursive[list[T]]

def _(value: Recursive[int]):
    reveal_type(value + 1)  # revealed: int
    reveal_type(1 + value)  # revealed: int

type RecursiveParamspec[**P] = Callable[[], RecursiveParamspec[Concatenate[int, P]]]

def paramspec_alias(func: RecursiveParamspec):
    reveal_type(func)  # revealed: () -> RecursiveParamspec[(int, /, *args: Unknown, **kwargs: Unknown)]

type RecursiveCallable[T] = Callable[[RecursiveCallable[T]], RecursiveCallable[T | RecursiveCallable[T]]]

def callable_alias(x: RecursiveCallable[int]):
    reveal_type(x)  # revealed: (RecursiveCallable[int], /) -> RecursiveCallable[int | RecursiveCallable[int]]

type GrowingList[T] = list[GrowingList[T | GrowingList[T]]]

def growing_list(x: GrowingList[int]):
    reveal_type(x)  # revealed: list[GrowingList[int | GrowingList[int]]]

type GrowingCallable[T] = Callable[[], GrowingCallable[T | GrowingCallable[T]] | None]

def growing_callable(x: GrowingCallable[int]):
    # revealed: (() -> GrowingCallable[int | GrowingCallable[int] | GrowingCallable[int | GrowingCallable[int]]] | None) | None
    reveal_type(x())
```

Non-growing recursive aliases should continue to preserve distinct specializations.

```py
type StableWrapped[T] = list[StableWrapped[T]]

def stable_wrapped(x: StableWrapped[int], y: StableWrapped[str]):
    reveal_type(x)  # revealed: list[StableWrapped[int]]
    reveal_type(y)  # revealed: list[StableWrapped[str]]
    # error: [invalid-assignment] "Object of type `StableWrapped[str]` is not assignable to `StableWrapped[int]`"
    x = y
    # error: [invalid-assignment] "Object of type `StableWrapped[int]` is not assignable to `StableWrapped[str]`"
    y = x
```

### Subtyping of materializations of cyclic aliases

```py
from ty_extensions import static_assert, Bottom, Top
from ty_extensions._internal import is_subtype_of

type JsonValue = None | JsonDict
type JsonDict = dict[str, JsonValue]

static_assert(is_subtype_of(Top[JsonDict], Top[JsonDict]))
static_assert(is_subtype_of(Top[JsonDict], Bottom[JsonDict]))
static_assert(is_subtype_of(Bottom[JsonDict], Bottom[JsonDict]))
static_assert(is_subtype_of(Bottom[JsonDict], Top[JsonDict]))
```

### Equivalence of top materializations of mutually recursive invariant aliases

```py
from typing import Callable
from ty_extensions import static_assert, Top
from ty_extensions._internal import is_equivalent_to, is_subtype_of

class Box[T]:
    pass

type A = Callable[[B], None]
type B = Callable[[A], None]

static_assert(is_equivalent_to(Top[Box[A]], Top[Box[B]]))
static_assert(is_subtype_of(Top[Box[A]], Top[Box[B]]))
static_assert(is_subtype_of(Top[Box[B]], Top[Box[A]]))
```

### Assignment through recursive aliases

```py
from __future__ import annotations

type JSON = str | int | float | bool | list[JSON] | list[JSON_OBJECT] | dict[str, JSON] | None
type JSON_OBJECT = dict[str, JSON]

x: JSON_OBJECT = {"hello": 23}

def f() -> JSON_OBJECT:
    return {"hello": 23}
```

### Aliased union in a self-recursive type alias

Regression test for <https://github.com/astral-sh/ty/issues/3835>.

```py
from collections.abc import Sequence

type JSONScalar = str | int
type JSONValue = JSONScalar | Sequence[JSONValue] | dict[str, JSONValue]
```

### Recursive dict alias in method return

```py
from __future__ import annotations
from dataclasses import dataclass

type NodeDict = dict[str, str | list[NodeDict]]

@dataclass
class Node:
    label: str
    children: list[Node]

    def to_dict(self) -> NodeDict:
        return {"label": self.label, "children": [child.to_dict() for child in self.children]}
```

### Cyclic defaults

```py
from typing_extensions import Protocol, TypeVar

T = TypeVar("T", default="C", covariant=True)

class P(Protocol[T]):
    pass

class C(P[T]):
    pass

reveal_type(C[int]())  # revealed: C[int]
reveal_type(C())  # revealed: C[C[Divergent]]
```

### Union inside generic

#### With old-style union

```py
from typing import Union

type A = list[Union["A", str]]

def f(x: A):
    reveal_type(x)  # revealed: list[A | str]
    for item in x:
        reveal_type(item)  # revealed: list[A | str] | str
```

#### With new-style union

```py
type A = list[A | str]

def f(x: A):
    reveal_type(x)  # revealed: list[A | str]
    for item in x:
        reveal_type(item)  # revealed: list[A | str] | str
```

#### With Optional

```py
from typing import Optional, Union

type A = list[Optional[Union["A", str]]]

def f(x: A):
    reveal_type(x)  # revealed: list[A | str | None]
    for item in x:
        reveal_type(item)  # revealed: list[A | str | None] | str | None
```

### Tuple comparison

```py
type X = tuple[X, int]

def _(x: X):
    reveal_type(x is x)  # revealed: bool
```

### Recursive invariant

```py
type X = dict[str, X]
type Y = X | str | dict[str, Y]

def _(y: Y):
    if isinstance(y, dict):
        reveal_type(y)  # revealed: dict[str, X] | dict[str, Y]
```

### Recursive alias with tuple - stack overflow test (issue 2470)

This test case used to cause a stack overflow. The returned type `list[int]` is not assignable to
`RecursiveT = int | tuple[RecursiveT, ...]`, so we get an error.

```py
type RecursiveT = int | tuple[RecursiveT, ...]

def foo(a: int, b: int) -> RecursiveT:
    some_intermediate_var = (a, b)
    # error: [invalid-return-type] "Return type does not match returned value: expected `RecursiveT`, found `list[int]`"
    return list(some_intermediate_var)
```

### Recursive `TypeIs` and `TypeGuard` aliases don't stack overflow

```py
from typing_extensions import TypeGuard, TypeIs
from collections.abc import Callable

type RecursiveIs = TypeIs[RecursiveIs]  # error: [cyclic-type-alias-definition]
type RecursiveGuard = TypeGuard[RecursiveGuard]

type AliasIs = RecursiveIs  # error: [cyclic-type-alias-definition]
type AliasGuard = RecursiveGuard

type CallableIs = TypeIs[Callable[[], CallableIs]]
type CallableGuard = TypeGuard[Callable[[], CallableGuard]]

reveal_type(CallableIs)  # revealed: TypeAliasType
reveal_type(CallableGuard)  # revealed: TypeAliasType
```

### Recursive alias in binary operators doesn't stack overflow

```py
from typing import reveal_type

type A = int | A

def foo(x: A):
    reveal_type(x + 1)  # revealed: int
    reveal_type(1 + x)  # revealed: int
```
