|
| 1 | +# Generic type aliases: PEP 695 syntax |
| 2 | + |
| 3 | +```toml |
| 4 | +[environment] |
| 5 | +python-version = "3.13" |
| 6 | +``` |
| 7 | + |
| 8 | +## Defining a generic alias |
| 9 | + |
| 10 | +At its simplest, to define a type alias using PEP 695 syntax, you add a list of `TypeVar`s, |
| 11 | +`ParamSpec`s or `TypeVarTuple`s after the alias name. |
| 12 | + |
| 13 | +```py |
| 14 | +from ty_extensions import generic_context |
| 15 | + |
| 16 | +type SingleTypevar[T] = ... |
| 17 | +type MultipleTypevars[T, S] = ... |
| 18 | +type SingleParamSpec[**P] = ... |
| 19 | +type TypeVarAndParamSpec[T, **P] = ... |
| 20 | +type SingleTypeVarTuple[*Ts] = ... |
| 21 | +type TypeVarAndTypeVarTuple[T, *Ts] = ... |
| 22 | + |
| 23 | +# revealed: tuple[T@SingleTypevar] |
| 24 | +reveal_type(generic_context(SingleTypevar)) |
| 25 | +# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars] |
| 26 | +reveal_type(generic_context(MultipleTypevars)) |
| 27 | + |
| 28 | +# TODO: support `ParamSpec`/`TypeVarTuple` properly |
| 29 | +# (these should include the `ParamSpec`s and `TypeVarTuple`s in their generic contexts) |
| 30 | +reveal_type(generic_context(SingleParamSpec)) # revealed: tuple[()] |
| 31 | +reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: tuple[T@TypeVarAndParamSpec] |
| 32 | +reveal_type(generic_context(SingleTypeVarTuple)) # revealed: tuple[()] |
| 33 | +reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: tuple[T@TypeVarAndTypeVarTuple] |
| 34 | +``` |
| 35 | + |
| 36 | +You cannot use the same typevar more than once. |
| 37 | + |
| 38 | +```py |
| 39 | +# error: [invalid-syntax] "duplicate type parameter" |
| 40 | +type RepeatedTypevar[T, T] = ... |
| 41 | +``` |
| 42 | + |
| 43 | +You can only use typevars (TODO: or param specs or typevar tuples) in the alias's generic context. |
| 44 | + |
| 45 | +```py |
| 46 | +# TODO: error |
| 47 | +type GenericOfType[int] = ... |
| 48 | +``` |
| 49 | + |
| 50 | +## Specializing type aliases explicitly |
| 51 | + |
| 52 | +The type parameter can be specified explicitly: |
| 53 | + |
| 54 | +```py |
| 55 | +from typing import Literal |
| 56 | + |
| 57 | +type C[T] = T |
| 58 | + |
| 59 | +def reveal(a: C[int], b: C[Literal[5]]): |
| 60 | + reveal_type(a) # revealed: int |
| 61 | + reveal_type(b) # revealed: Literal[5] |
| 62 | +``` |
| 63 | + |
| 64 | +The specialization must match the generic types: |
| 65 | + |
| 66 | +```py |
| 67 | +# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2" |
| 68 | +reveal_type(C[int, int]) # revealed: Unknown |
| 69 | +``` |
| 70 | + |
| 71 | +And non-generic types cannot be specialized: |
| 72 | + |
| 73 | +```py |
| 74 | +type B = ... |
| 75 | + |
| 76 | +# error: [non-subscriptable] "Cannot subscript non-generic type alias" |
| 77 | +reveal_type(B[int]) # revealed: Unknown |
| 78 | + |
| 79 | +# error: [non-subscriptable] "Cannot subscript non-generic type alias" |
| 80 | +def x(b: B[int]): ... |
| 81 | +``` |
| 82 | + |
| 83 | +If the type variable has an upper bound, the specialized type must satisfy that bound: |
| 84 | + |
| 85 | +```py |
| 86 | +type Bounded[T: int] = ... |
| 87 | +type BoundedByUnion[T: int | str] = ... |
| 88 | + |
| 89 | +class IntSubclass(int): ... |
| 90 | + |
| 91 | +reveal_type(Bounded[int]) # revealed: Bounded[int] |
| 92 | +reveal_type(Bounded[IntSubclass]) # revealed: Bounded[IntSubclass] |
| 93 | + |
| 94 | +# TODO: update this diagnostic to talk about type parameters and specializations |
| 95 | +# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `str`" |
| 96 | +reveal_type(Bounded[str]) # revealed: Unknown |
| 97 | + |
| 98 | +# TODO: update this diagnostic to talk about type parameters and specializations |
| 99 | +# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `int | str`" |
| 100 | +reveal_type(Bounded[int | str]) # revealed: Unknown |
| 101 | + |
| 102 | +reveal_type(BoundedByUnion[int]) # revealed: BoundedByUnion[int] |
| 103 | +reveal_type(BoundedByUnion[IntSubclass]) # revealed: BoundedByUnion[IntSubclass] |
| 104 | +reveal_type(BoundedByUnion[str]) # revealed: BoundedByUnion[str] |
| 105 | +reveal_type(BoundedByUnion[int | str]) # revealed: BoundedByUnion[int | str] |
| 106 | +``` |
| 107 | + |
| 108 | +If the type variable is constrained, the specialized type must satisfy those constraints: |
| 109 | + |
| 110 | +```py |
| 111 | +type Constrained[T: (int, str)] = ... |
| 112 | + |
| 113 | +reveal_type(Constrained[int]) # revealed: Constrained[int] |
| 114 | + |
| 115 | +# TODO: error: [invalid-argument-type] |
| 116 | +# TODO: revealed: Constrained[Unknown] |
| 117 | +reveal_type(Constrained[IntSubclass]) # revealed: Constrained[IntSubclass] |
| 118 | + |
| 119 | +reveal_type(Constrained[str]) # revealed: Constrained[str] |
| 120 | + |
| 121 | +# TODO: error: [invalid-argument-type] |
| 122 | +# TODO: revealed: Unknown |
| 123 | +reveal_type(Constrained[int | str]) # revealed: Constrained[int | str] |
| 124 | + |
| 125 | +# TODO: update this diagnostic to talk about type parameters and specializations |
| 126 | +# error: [invalid-argument-type] "Argument is incorrect: Expected `int | str`, found `object`" |
| 127 | +reveal_type(Constrained[object]) # revealed: Unknown |
| 128 | +``` |
| 129 | + |
| 130 | +If the type variable has a default, it can be omitted: |
| 131 | + |
| 132 | +```py |
| 133 | +type WithDefault[T, U = int] = ... |
| 134 | + |
| 135 | +reveal_type(WithDefault[str, str]) # revealed: WithDefault[str, str] |
| 136 | +reveal_type(WithDefault[str]) # revealed: WithDefault[str, int] |
| 137 | +``` |
| 138 | + |
| 139 | +## Aliases are not callable |
| 140 | + |
| 141 | +```py |
| 142 | +type A = int |
| 143 | +type B[T] = T |
| 144 | + |
| 145 | +# error: [call-non-callable] "Object of type `TypeAliasType` is not callable" |
| 146 | +reveal_type(A()) # revealed: Unknown |
| 147 | + |
| 148 | +# error: [call-non-callable] "Object of type `GenericAlias` is not callable" |
| 149 | +reveal_type(B[int]()) # revealed: Unknown |
| 150 | +``` |
0 commit comments