-
Notifications
You must be signed in to change notification settings - Fork 393
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
zeux
merged 4 commits into
luau-lang:master
from
vegorov-rbx:rfc-syntax-type-alias-type-packs
Oct 27, 2021
Merged
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
130d22c
RFC: Type alias type packs
vegorov-rbx 7237cd6
Add support for explicit type pack syntax in type parameter list
vegorov-rbx ed70fd0
Apply the alternative that allows single element type pack parameter …
vegorov-rbx 8025038
Do not combine a type pack together with regular types into the first…
vegorov-rbx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
||
### 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. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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>
.There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.