Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Type alias type packs #83

Merged
merged 4 commits into from
Oct 27, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions rfcs/syntax-type-alias-type-packs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# 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...> = () -> A... -- cannot reference A... as the return value pack

type Y = X<number, string> -- 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 alias.

## Design

We currently support type packs at these locations:
```lua
-- for variadic function parameter when type pack is generic
local function f<a...>(...: a...)

-- for multiple return values
local function f<a...>(): a...

-- as the tail item of function return value pack
local function f<a...>(): (number, a...)
```

We want to be able to use type packs for type alias instantiation:
```lua
type X<T...> = --

type A<S...> = X<S...> -- T... = (S...)
```

Similar to function calls, we want to be able to assign multiple regular types to a single type pack:
```lua
type A = X -- T... = (). Note: check 'Alternatives'
type B = X<number> -- T... = (number)
type C = X<number, string> -- T... = (number, string)
```

Variadic types can also be assigned to type alias type pack:
```lua
type D = X<...number> -- T... = (...number)
type E = X<number, ...string> -- T... = (number, ...string)
```

Multiple regular types can be assigned together with a type pack argument in a tail position:
```lua
type F<S...> = X<number, S...> -- T... = (number, S...)
type G<S...> = X<S..., number> -- error, type arguments can't follow type pack arguments
```

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

And since we only allow type pack arguments after regular type arguments, trailing regular types with a type pack that follows are combined into a single type pack argument:
```lua
type Y<T..., U...> = --

type A<S...> = Y<S..., S...> -- T... = (S...), U... = (S...)
type B<S...> = Y<number, ...string, S...> -- T... = (number, ...string), U... = S...
type C<S...> = Y<number, string, S...> -- 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<T, U...> = --

type E<S...> = Z<number, S...> -- T = number, U... = (S...)
type F<S...> = Z<number, string, S...> -- T = number, U... = (string, S...)
type G<S...> = Z<S...> -- error, not enough regular type arguments, can't split the front of S... into T

type W<T, U..., V...> = --

type H<S..., R...> = W<number, S..., R...> -- U... = S..., V... = R...
type I<S..., R...> = W<number, string, S..., R...> -- U... = (string, S...), V... = R...
```

## 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, U...> = T

type X = Car<number, string, boolean> -- number
type Y<S...> = Car<S...> -- error, not enough regular type arguments
type Z = Y<number, string, boolean> -- 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find explicitly representing packs this way to be a surprising direction, but that might just be my familiarity with C++ templates talking. It'd be nice if the rationale for doing it this way was explained a bit more, instead of only how it works mechanically.

It'd still be possible to implement splitting in the future through a magic utility type, right? Something like Select<T..., number>.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's correct to use 'direction' when talking about something new.
This RFC doesn't select a direction in how type packs works here, this is an existing limitation of the current type system (eager type alias definitions).
If this element of the type system changes (in a different RFC), splitting off type pack elements might become available.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would also be great to see useful examples of type pack element extraction in context of Luau programs.


### 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, U...> = T
type Cdr<T, U...> = U...
type Cons<T, U...> = (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.

### Explicit type pack syntax

To enable additional control for the content of a type pack, we would have liked to introduce an explicit type pack syntax.

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...> = (T...) -> (U...)

type F1 = Y<(number, string), (boolean)> -- T... = (number, string), U... = (boolean)
type F2 = Y<(), ()> -- T... = (), U... = ()
type F3<S...> = Y<string, S..., (number, S...)> -- T... = (string, S...), U... = (number, S...)
```

This explicit type pack syntax could've been used in other syntax positions where type packs are allowed:
```lua
local function f(...: (number, ...string)) end
local function g(...: (number, (string, number)) end
local function h(): (number, (string, ...boolean)) return 1, 's', true, false end
```

Unfortunately, our syntax supports placing parenthesis around the type:
```lua
local a: number
local b: (number)
```

This means that the syntax around type packs is ambiguous:
```lua
type Y<T..., U...> = (T...) -> (U...)

type A = Y<(number), (number)> -- will resolve as Y<number, number>

local function f(...: (number)) end -- '...' is a '...number', not a type pack with a single element
vegorov-rbx marked this conversation as resolved.
Show resolved Hide resolved
```

## Alternatives

The syntax we use right now to instantiate type alias with a single type pack parameter to an empty type pack is to not specify the argument list at all:
```lua
type X<T...> = --

type A = X -- ok
type B = X<> -- error
```

This is the current parser behavior and while we could introduce `X<>` as an alternative, without a warning/deprecation/epoch system we might not be able to disallow the `X` syntax.