# Type alias type packs ## Summary Provide semantics for referencing type packs inside the body of a type alias declaration ## Motivation We now have an ability to declare a placeholder for a type pack in type alias declaration, but there is no support to reference this pack inside the body of the alias: ```lua type X = () -> A... -- cannot reference A... as the return value pack type Y = X -- invalid number of arguments ``` Additionally, while a simple introduction of these generic type packs into the scope will provide an ability to reference them in function declarations, we want to be able to use them to instantiate other type aliases as well. Declaration syntax also supports multiple type packs, but we don't have defined semantics on instantiation of such type alias. ## Design We currently support type packs at these locations: ```lua -- for variadic function parameter when type pack is generic local function f(...: a...) -- for multiple return values local function f(): a... -- as the tail item of function return value pack local function f(): (number, a...) ``` We want to be able to use type packs for type alias instantiation: ```lua type X = -- type A = X -- T... = (S...) ``` Similar to function calls, we want to be able to assign zero or more regular types to a single type pack: ```lua type A = X<> -- T... = () type B = X -- T... = (number) type C = X -- T... = (number, string) ``` Definition of `A` doesn't parse right now, we would like to make it legal going forward. Variadic types can also be assigned to type alias type pack: ```lua type D = X<...number> -- T... = (...number) type E = X -- T... = (number, ...string) ``` Multiple regular types can be assigned together with a type pack argument in a tail position: ```lua type F = X -- T... = (number, S...) type G = X -- T... = (number, string, S...) ``` Regular type parameters cannot follow type pack parameters: ```lua type H = X -- error, type parameters can't follow type pack parameters ``` ### Multiple type pack parameters We have to keep in mind that it is also possible to declare a type alias that takes multiple type pack parameters. Similar to the previous examples, type parameters that haven't been matched with type arguments are combined together with the next type pack (if present) into the first type pack. Type pack parameters after the first one have to be type packs: ```lua type Y = -- type A = Y -- T... = (S...), U... = (S...) type B = Y -- T... = (number, ...string), U... = S... type C = Y -- error, T... = (number, string, S...), but U... = undefined type D = Y<...number> -- error, T = (...number), but U... = undefined, not (...number) even though one infinite set is enough to fill two, we may have '...number' inside a type pack argument and we'll be unable to see its content type Z = -- type E = Z -- T = number, U... = (S...) type F = Z -- T = number, U... = (string, S...) type G = Z -- error, not enough regular type arguments, can't split the front of S... into T type W = -- type H = W -- U... = S..., V... = R... type I = W -- U... = (string, S...), V... = R... ``` ### Explicit type pack syntax To enable additional control for the content of a type pack, especially in cases where multiple type pack parameters are expected, we introduce an explicit type pack syntax for use in type alias instantiation. Similar to variadic types `...a` and generic type packs `T...`, explicit type packs can only be used at type pack positions: ```lua type Y = (T...) -> (U...) type F1 = Y<(number, string), (boolean)> -- T... = (number, string), U... = (boolean) type F2 = Y<(), ()> -- T... = (), U... = () type F3 = Y -- T... = (string, S...), U... = (number, S...) ``` In type parameter list, types inside the parentheses always produce a type pack. This is in contrast to function return type pack annotation, where `() -> number` is the same as `() -> (number)`. This is a breaking change. Users can already have type alias instantiations like these: ```lua type X = T? type A = X<(number)> -- valid right now, typechecking error after this RFC ``` Explicit type pack syntax is not available in other type pack annotation contexts. ## Drawbacks ### Type pack element extraction Because our type alias instantiations are not lazy, it's impossible to split of a single type from a type pack: ```lua type Car = T type X = Car -- number type Y = Car -- error, not enough regular type arguments type Z = Y -- error, Y doesn't have a valid definition ``` Splitting off a single type is is a common pattern with variadic templates in C++, but we don't allow type alias overloads, so use cases are more limited. ### Type alias can't result in a type pack We don't propose type aliases to generate type packs, which could have looked as: ```lua type Car = T type Cdr = U... type Cons = (T, U...) --[[ using type functions to operate on type packs as a list of types ]] ``` We wouldn't be able to differentiate if an instantiation results in a type or a type pack and our type system only allows variadic types as the type pack tail element. Support for variadic types in the middle of a type pack can be found in TypeScript's tuples. ## Alternatives ### Backwards compatibility for single type in parentheses It is possible to allow single element type pack parameter assignment to a type argument: ```lua type X = T? type A = X<(number)> ``` This is not proposed to keep separation between type and type packs more clear. If we supported warning generation, we could create a deprecation period, but since our typechecking errors don't block compilation, it is not that critical. ### Function return type syntax for explicit type packs Another option that was considered is to parse `(T)` as `T`, like we do for return type annotation. This option complicates the match ruleset since the typechecker will never know if the user has written `T` or `(T)` so each regular type could be a single element type pack and vice versa. ```lua type X type C = X -- T... = (number, number) type D = X<(number), (number)> -- T... = (number, number) type Y --- two items that were enough to satisfy only a single T... in X are enough to satisfy two T..., U... in Y type E = Y -- T... = (number), U... = (number) ```