# 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 typing import Callable
from ty_extensions._internal import generic_context

type SingleTypevar[T] = list[T]
type MultipleTypevars[T, S] = tuple[T, S]
type SingleParamSpec[**P] = Callable[P, int]
type TypeVarAndParamSpec[T, **P] = Callable[P, T]
type SingleTypeVarTuple[*Ts] = tuple[*Ts]
type TypeVarAndTypeVarTuple[T, *Ts] = tuple[T, *Ts]

# revealed: ty_extensions._internal.GenericContext[T@SingleTypevar]
reveal_type(generic_context(SingleTypevar))
# revealed: ty_extensions._internal.GenericContext[T@MultipleTypevars, S@MultipleTypevars]
reveal_type(generic_context(MultipleTypevars))

# TODO: support `TypeVarTuple` properly
# (these should include the `TypeVarTuple`s in their generic contexts)
# revealed: ty_extensions._internal.GenericContext[P@SingleParamSpec]
reveal_type(generic_context(SingleParamSpec))
# revealed: ty_extensions._internal.GenericContext[T@TypeVarAndParamSpec, P@TypeVarAndParamSpec]
reveal_type(generic_context(TypeVarAndParamSpec))
# revealed: ty_extensions._internal.GenericContext[]
reveal_type(generic_context(SingleTypeVarTuple))
# revealed: ty_extensions._internal.GenericContext[T@TypeVarAndTypeVarTuple]
reveal_type(generic_context(TypeVarAndTypeVarTuple))
```

You cannot use the same typevar more than once.

```py
# error: [invalid-syntax] "duplicate type parameter"
type RepeatedTypevar[T, T] = tuple[T, T]
```

Legacy type variables cannot be used:

```py
from typing import TypeVar

V = TypeVar("V")

# error: [unbound-type-variable]
type TA1[K] = dict[K, V]
```

## Specializing type aliases explicitly

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: [invalid-type-arguments] "Too many type arguments: expected 1, got 2"
reveal_type(C[int, int])  # revealed: <type alias 'C[Unknown]'>
```

And non-generic types cannot be specialized:

```py
from typing import TypeVar, Protocol, TypedDict

type B = int

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

# error: [not-subscriptable] "Cannot specialize non-generic type alias `B`"
def _(b: B[int]):
    reveal_type(b)  # revealed: Unknown

type IntOrStr = int | str

# error: [not-subscriptable] "Cannot specialize non-generic type alias `IntOrStr`"
def _(c: IntOrStr[int]):
    reveal_type(c)  # revealed: Unknown

type ListOfInts = list[int]

# error: [not-subscriptable] "Cannot specialize non-generic type alias `ListOfInts`"
def _(l: ListOfInts[int]):
    reveal_type(l)  # revealed: Unknown

type List[T] = list[T]

# error: [invalid-type-form] "Only simple names and dotted names can be subscripted in parameter annotations"
def _(l: List[int][int]):
    reveal_type(l)  # revealed: Unknown

# error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type alias values"
type DoubleSpecialization[T] = list[T][T]

def _(d: DoubleSpecialization[int]):
    reveal_type(d)  # revealed: Unknown

type Tuple = tuple[int, str]

# error: [not-subscriptable] "Cannot specialize non-generic type alias `Tuple`"
def _(doubly_specialized: Tuple[int]):
    reveal_type(doubly_specialized)  # revealed: Unknown

T = TypeVar("T")

class LegacyProto(Protocol[T]):
    pass

type LegacyProtoInt = LegacyProto[int]

# error: [not-subscriptable] "Cannot specialize non-generic type alias `LegacyProtoInt`"
def _(x: LegacyProtoInt[int]):
    reveal_type(x)  # revealed: Unknown

class Proto[T](Protocol):
    pass

type ProtoInt = Proto[int]

# error: [not-subscriptable] "Cannot specialize non-generic type alias `ProtoInt`"
def _(x: ProtoInt[int]):
    reveal_type(x)  # revealed: Unknown

# TODO: TypedDict is just a function object at runtime, we should emit an error
class LegacyDict(TypedDict[T]):
    # error: [unbound-type-variable]
    x: T

type LegacyDictInt = LegacyDict[int]

# error: [not-subscriptable] "Cannot specialize non-generic type alias `LegacyDictInt`"
def _(x: LegacyDictInt[int]):
    reveal_type(x)  # revealed: Unknown

class Dict[T](TypedDict):
    x: T

type DictInt = Dict[int]

# error: [not-subscriptable] "Cannot specialize non-generic type alias `DictInt`"
def _(x: DictInt[int]):
    reveal_type(x)  # revealed: Unknown

type Union = list[str] | list[int]

# error: [not-subscriptable] "Cannot specialize non-generic type alias `Union`"
def _(x: Union[int]):
    reveal_type(x)  # revealed: Unknown
```

If the type variable has an upper bound, the specialized type must satisfy that bound:

```py
type Bounded[T: int] = list[T]
type BoundedByUnion[T: int | str] = list[T]

class IntSubclass(int): ...

reveal_type(Bounded[int])  # revealed: <type alias 'Bounded[int]'>
reveal_type(Bounded[IntSubclass])  # revealed: <type alias 'Bounded[IntSubclass]'>

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

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

reveal_type(BoundedByUnion[int])  # revealed: <type alias 'BoundedByUnion[int]'>
reveal_type(BoundedByUnion[IntSubclass])  # revealed: <type alias 'BoundedByUnion[IntSubclass]'>
reveal_type(BoundedByUnion[str])  # revealed: <type alias 'BoundedByUnion[str]'>
reveal_type(BoundedByUnion[int | str])  # revealed: <type alias 'BoundedByUnion[int | str]'>

type TupleOfIntAndStr[T: int, U: str] = tuple[T, U]

def _(x: TupleOfIntAndStr[int, str]):
    reveal_type(x)  # revealed: tuple[int, str]

# error: [invalid-type-arguments] "Type `int` is not assignable to upper bound `str` of type variable `U@TupleOfIntAndStr`"
def _(x: TupleOfIntAndStr[int, int]):
    reveal_type(x)  # revealed: tuple[int, Unknown]
```

If the type variable is constrained, the specialized type must satisfy those constraints:

```py
type Constrained[T: (int, str)] = list[T]

reveal_type(Constrained[int])  # revealed: <type alias 'Constrained[int]'>

# TODO: error: [invalid-argument-type]
# TODO: revealed: Constrained[Unknown]
reveal_type(Constrained[IntSubclass])  # revealed: <type alias 'Constrained[IntSubclass]'>

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

# TODO: error: [invalid-argument-type]
# TODO: revealed: Unknown
reveal_type(Constrained[int | str])  # revealed: <type alias 'Constrained[int | str]'>

# error: [invalid-type-arguments] "Type `object` does not satisfy constraints `int`, `str` of type variable `T@Constrained`"
reveal_type(Constrained[object])  # revealed: <type alias 'Constrained[Unknown]'>

type TupleOfIntOrStr[T: (int, str), U: (int, str)] = tuple[T, U]

def _(x: TupleOfIntOrStr[int, str]):
    reveal_type(x)  # revealed: tuple[int, str]

# error: [invalid-type-arguments] "Type `object` does not satisfy constraints `int`, `str` of type variable `U@TupleOfIntOrStr`"
def _(x: TupleOfIntOrStr[int, object]):
    reveal_type(x)  # revealed: tuple[int, Unknown]
```

If the type variable has a default, it can be omitted:

```py
type WithDefault[T, U = int] = dict[T, U]

reveal_type(WithDefault[str, str])  # revealed: <type alias 'WithDefault[str, str]'>
reveal_type(WithDefault[str])  # revealed: <type alias '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]
```

Bare generic aliases used inside another specialized alias are also specialized with their defaults:

```py
type Defaulted[T = int] = T
type Outer[U] = Defaulted

def _(x: Outer[str]):
    reveal_type(x)  # revealed: int
    y: int = x
```

Self-referential defaults should not crash type inference:

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

An explicit alias specialization overrides the alias type parameter's default:

```py
type ExplicitAlias[
    FirstT = ExplicitAlias[int, str],
    SecondT = FirstT,
] = list[SecondT]

def explicit_alias_default(x: ExplicitAlias):
    reveal_type(x)  # revealed: list[ExplicitAlias[int, str]]
```

A self-referential default that does not reference itself in the alias body should also not crash,
even when the default is evaluated (e.g., by omitting the type argument):

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

def _(x: B) -> None:
    pass
```

Mutually-referential defaults (where two type aliases reference each other via their typevar
defaults) should also not crash:

```py
type X[T = Y] = list[T]
type Y[U = X] = list[U]

def _(x: X, y: Y) -> None:
    pass
```

Indirect self-references through a chain of type aliases should also not crash:

```py
type P[T = R] = list[T]
type Q[T = P] = list[T]
type R[T = Q] = list[T]

def _(p: P) -> None:
    pass
```

## Snapshots of verbose diagnostics

```py
class A: ...
class B[T]: ...

type AliasA = A
type AliasB = B[int]

# snapshot: not-subscriptable
def _(a: AliasA[int]): ...
```

```snapshot
error[not-subscriptable]: Cannot specialize non-generic type alias `AliasA`
 --> src/mdtest_snippet.py:8:10
  |
8 | def _(a: AliasA[int]): ...
  |          ------^^^^^
  |          |
  |          Alias to `A`, which is not generic
  |
```

```py
# snapshot: not-subscriptable
def _(b: AliasB[int]): ...
```

```snapshot
error[not-subscriptable]: Cannot specialize non-generic type alias `AliasB`
  --> src/mdtest_snippet.py:10:10
   |
10 | def _(b: AliasB[int]): ...
   |          ------^^^^^
   |          |
   |          Alias to `B[int]`, which is already specialized
   |
```

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

## Recursive generic type aliases

```py
type RecursiveList[T] = T | list[RecursiveList[T]]

r1: RecursiveList[int] = 1
r2: RecursiveList[int] = [1, [1, 2, 3]]
# error: [invalid-assignment] "Object of type `Literal["a"]` is not assignable to `RecursiveList[int]`"
r3: RecursiveList[int] = "a"
# error: [invalid-assignment]
r4: RecursiveList[int] = ["a"]
# error: [invalid-assignment] "Object of type `list[int | list[RecursiveList[int]] | list[int | list[RecursiveList[int]] | str]]` is not assignable to `RecursiveList[int]`"
r5: RecursiveList[int] = [1, ["a"]]

def _(x: RecursiveList[int]):
    if isinstance(x, list):
        reveal_type(x[0])  # revealed: int | list[RecursiveList[int]]
    if isinstance(x, list) and isinstance(x[0], list):
        reveal_type(x[0])  # revealed: list[RecursiveList[int]]
```

Assignment checks respect structural subtyping, i.e. type aliases with the same structure are
assignable to each other.

```py
# This is structurally equivalent to RecursiveList[T].
type RecursiveList2[T] = T | list[T | list[RecursiveList[T]]]
# This is not structurally equivalent to RecursiveList[T].
type RecursiveList3[T] = T | list[list[RecursiveList[T]]]

def _(x: RecursiveList[int], y: RecursiveList2[int]):
    r1: RecursiveList2[int] = x
    # error: [invalid-assignment]
    r2: RecursiveList3[int] = x

    r3: RecursiveList[int] = y
    # error: [invalid-assignment]
    r4: RecursiveList3[int] = y
```

It is also possible to handle divergent type aliases that are not actually have instances.

```py
# The type variable `T` has no meaning here, it's just to make sure it works correctly.
type DivergentList[T] = list[DivergentList[T]]

d1: DivergentList[int] = []
# error: [invalid-assignment]
d2: DivergentList[int] = [1]
# error: [invalid-assignment]
d3: DivergentList[int] = ["a"]
# error: [invalid-assignment] "Object of type `list[list[DivergentList[int]] | list[list[DivergentList[int]] | int]]` is not assignable to `DivergentList[int]`"
d4: DivergentList[int] = [[1]]

def _(x: DivergentList[int]):
    d1: DivergentList[int] = [x]
    d2: DivergentList[int] = x[0]
```

## Solving generics with type alias parameters

A generic function parameter annotated with a PEP 695 type alias that contains a type variable
should properly infer the specialization from the argument:

```py
type MyList[T] = list[T]
type MyDict[K, V] = dict[K, V]

def head[T](my_list: MyList[T]) -> T:
    return my_list[0]

def get_value[K, V](my_dict: MyDict[K, V], key: K) -> V:
    return my_dict[key]

reveal_type(head([1, 2]))  # revealed: int
reveal_type(head(["a", "b"]))  # revealed: str

d: dict[str, int] = {"a": 1}
reveal_type(get_value(d, "a"))  # revealed: int
```

It also works in the reverse direction, where the type alias is used as the argument type:

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

def head[T](l: list[T]) -> T:
    return l[0]

def _(x: MyList[int]):
    reveal_type(head(x))  # revealed: int
```

## Bidirectional type inference with union type aliases

When a PEP 695 type alias expands to a union, bidirectional type inference should still work
correctly. The type alias should be expanded to its value type when determining the expected type
for specialization inference.

```py
type MaybeList[T] = list[T] | T

def test[X: int](items: list[X]) -> list[X]:
    # The annotation MaybeList[str | int] expands to `list[str | int] | str | int`.
    # Bidirectional inference should infer list[str | int] from the list() call.
    a: MaybeList[str | int] = list(items)
    # The revealed type is list[str | int] because that's what list() returns.
    reveal_type(a)  # revealed: list[str | int]
    return items
```

This also works for more complex cases with multiple generic functions:

```py
type OptionalList[T] = list[T] | None

def copy_list[T](items: list[T]) -> list[T]:
    return list(items)

def _(values: list[int]) -> OptionalList[int]:
    result: OptionalList[int] = copy_list(values)
    # The revealed type is list[int] because that's what copy_list returns.
    reveal_type(result)  # revealed: list[int]
    return result
```

Union type aliases also work correctly with TypedDict dict literal inference:

```py
from typing import TypedDict

class Person(TypedDict):
    name: str
    age: int

type MaybePerson = Person | None

def _(p: MaybePerson):
    # Dict literal should be inferred as Person, not dict[str, str | int]
    x: MaybePerson = {"name": "Alice", "age": 30}
    reveal_type(x)  # revealed: Person
```

And with `dict()` calls in TypedDict context:

```py
from typing import TypedDict

class Dog(TypedDict):
    name: str
    breed: str

type MaybeDog = Dog | None

def _():
    # dict() call with keyword args should be inferred as Dog
    animal: MaybeDog = dict(name="Buddy", breed="Labrador")
    reveal_type(animal)  # revealed: Dog
```

And with set literal inference:

```py
type MaybeSet[T] = set[T] | T

def _():
    # Set literal should be inferred as set[int]
    x: MaybeSet[int] = {1, 2, 3}
    reveal_type(x)  # revealed: set[int]
```

## Fully qualified type alias names in error messages

When two type aliases have the same name but are in different scopes, they should be fully qualified
in error messages to distinguish them:

```py
class A:
    class B[T]:
        pass

    type D = list[int]

class C:
    class B[T]:
        pass

    type D = list[str]

def f(b: C.B[C.D]) -> None:
    # error: [invalid-assignment] "Object of type `mdtest_snippet.C.B[mdtest_snippet.C.D]` is not assignable to `mdtest_snippet.A.B[mdtest_snippet.A.D]`"
    a: A.B[A.D] = b
```

## Fully qualified type alias names in nested classes

Type aliases in nested classes should include the full class path:

```py
class Outer1:
    class Inner:
        type Alias = int

class Outer2:
    class Inner:
        type Alias = str

def g(x: Outer1.Inner.Alias, y: Outer2.Inner.Alias) -> None:
    # error: [invalid-assignment] "Object of type `mdtest_snippet.Outer2.Inner.Alias` is not assignable to `mdtest_snippet.Outer1.Inner.Alias`"
    a: Outer1.Inner.Alias = y
```

## Fully qualified generic type aliases

Ambiguous generic type aliases should also be fully qualified:

```py
class X:
    type GenAlias[T] = list[T]

class Y:
    type GenAlias[T] = dict[str, T]

def h(x: X.GenAlias[int], y: Y.GenAlias[int]) -> None:
    # error: [invalid-assignment] "Object of type `mdtest_snippet.Y.GenAlias[int]` is not assignable to `mdtest_snippet.X.GenAlias[int]`"
    a: X.GenAlias[int] = y
```

## Different specializations of the same generic type alias

Different specializations of the same generic type alias refer to the same definition, so they
should not be qualified:

```py
class Box[T]:
    item: T

type RefBox[T] = Box[T]

def transmute_inner[T, V](x: RefBox[T]) -> RefBox[V]:
    # error: [invalid-return-type] "Return type does not match returned value: expected `RefBox[V@transmute_inner]`, found `RefBox[T@transmute_inner]`"
    return x
```

## Non-ambiguous type aliases should not be qualified

Type aliases with unique names should NOT be qualified:

```py
class P:
    type UniqueAlias1 = int

class Q:
    type UniqueAlias2 = str

def i(x: P.UniqueAlias1, y: Q.UniqueAlias2) -> None:
    # error: [invalid-assignment] "Object of type `UniqueAlias2` is not assignable to `UniqueAlias1`"
    a: P.UniqueAlias1 = y
```

## Class and type alias with same name

When a class and a type alias have the same name in different scopes, both should be fully qualified
to distinguish them in error messages:

```py
class Container1:
    class Item:
        pass

class Container2:
    type Item = str

def j(x: Container1.Item, y: Container2.Item) -> None:
    # error: [invalid-assignment] "Object of type `mdtest_snippet.Container2.Item` is not assignable to `mdtest_snippet.Container1.Item`"
    a: Container1.Item = y
```

## Default type parameter after `TypeVarTuple`

A type parameter with a default cannot follow a `TypeVarTuple` in a type parameter list. This is
prohibited by the typing spec because a `TypeVarTuple` consumes all remaining positional type
arguments, making any subsequent defaults meaningless.

```py
# snapshot: invalid-type-variable-default
type Alias1[*Ts, T = int] = tuple[*Ts, T]
```

```snapshot
error[invalid-type-variable-default]: Type parameters with defaults cannot follow a TypeVarTuple parameter
 --> src/mdtest_snippet.py:2:13
  |
2 | type Alias1[*Ts, T = int] = tuple[*Ts, T]
  |             ---  ^^^^^^^ `T` has a default
  |             |
  |             `Ts` is a TypeVarTuple
  |
info: See https://typing.python.org/en/latest/spec/generics.html#defaults-following-typevartuple
```

```py
# snapshot: invalid-type-variable-default
type Alias2[T1, *Ts, T2 = int] = tuple[T1, *Ts, T2]
```

```snapshot
error[invalid-type-variable-default]: Type parameters with defaults cannot follow a TypeVarTuple parameter
 --> src/mdtest_snippet.py:4:17
  |
4 | type Alias2[T1, *Ts, T2 = int] = tuple[T1, *Ts, T2]
  |                 ---  ^^^^^^^^ `T2` has a default
  |                 |
  |                 `Ts` is a TypeVarTuple
  |
info: See https://typing.python.org/en/latest/spec/generics.html#defaults-following-typevartuple
```

```py
# snapshot: invalid-type-variable-default
type Alias3[*Ts, T1 = int, T2 = str] = tuple[*Ts, T1, T2]
```

```snapshot
error[invalid-type-variable-default]: Type parameters with defaults cannot follow a TypeVarTuple parameter
 --> src/mdtest_snippet.py:6:13
  |
6 | type Alias3[*Ts, T1 = int, T2 = str] = tuple[*Ts, T1, T2]
  |             ---  ^^^^^^^^  -------- `T2` also has a default
  |             |    |
  |             |    `T1` has a default
  |             `Ts` is a TypeVarTuple
  |
info: See https://typing.python.org/en/latest/spec/generics.html#defaults-following-typevartuple
```

```py
# snapshot: invalid-type-variable-default
type Alias4[*Us, *Ts = *tuple[int, str]] = tuple[*Us, *Ts]
```

```snapshot
error[invalid-type-variable-default]: Type parameters with defaults cannot follow a TypeVarTuple parameter
 --> src/mdtest_snippet.py:8:13
  |
8 | type Alias4[*Us, *Ts = *tuple[int, str]] = tuple[*Us, *Ts]
  |             ---  ^^^^^^^^^^^^^^^^^^^^^^ `Ts` has a default
  |             |
  |             `Us` is a TypeVarTuple
  |
info: See https://typing.python.org/en/latest/spec/generics.html#defaults-following-typevartuple
```

```py
# These are fine:
type Ok1[T, *Ts] = tuple[T, *Ts]
```
