mirror of
https://github.com/luau-lang/rfcs.git
synced 2025-04-10 21:40:56 +01:00
Update codeblocks' language to use luau. (#40)
This commit is contained in:
parent
6002a16fc3
commit
364425c518
44 changed files with 267 additions and 269 deletions
|
@ -43,7 +43,7 @@ We could instead equalize (ha!) the behavior between Luau and Lua. In fact, this
|
||||||
|
|
||||||
We could work with developers to change their games to stop relying on this. However, this is more complicated to deploy and - upon reflection - makes `==` less intuitive than the main proposal when comparing objects with NaN, since e.g. it means that these two functions have a different behavior:
|
We could work with developers to change their games to stop relying on this. However, this is more complicated to deploy and - upon reflection - makes `==` less intuitive than the main proposal when comparing objects with NaN, since e.g. it means that these two functions have a different behavior:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
function compare1(a: Vector3, b: Vector3)
|
function compare1(a: Vector3, b: Vector3)
|
||||||
return a == b
|
return a == b
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,21 +16,21 @@ We have had cases where we talked about using syntax like `setmetatable(T, MT)`
|
||||||
|
|
||||||
An example that _will_ cause a change in semantics:
|
An example that _will_ cause a change in semantics:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
local t: F
|
local t: F
|
||||||
(u):m()
|
(u):m()
|
||||||
```
|
```
|
||||||
|
|
||||||
where today, `local t: F` is one statement, and `(u):m()` is another. If we had the syntax for `F(T)` here, it becomes invalid input because it gets parsed as
|
where today, `local t: F` is one statement, and `(u):m()` is another. If we had the syntax for `F(T)` here, it becomes invalid input because it gets parsed as
|
||||||
|
|
||||||
```
|
```luau
|
||||||
local t: F(u)
|
local t: F(u)
|
||||||
:m()
|
:m()
|
||||||
```
|
```
|
||||||
|
|
||||||
This is important because of the `setmetatable(T, MT)` case:
|
This is important because of the `setmetatable(T, MT)` case:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
type Foo = setmetatable({ x: number }, { ... })
|
type Foo = setmetatable({ x: number }, { ... })
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ For `setmetatable`, the parser isn't sure whether `{}` is actually a type or an
|
||||||
|
|
||||||
An example that _will_ cause a change in semantics:
|
An example that _will_ cause a change in semantics:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
local function f(t): F T
|
local function f(t): F T
|
||||||
(t or u):m()
|
(t or u):m()
|
||||||
end
|
end
|
||||||
|
@ -50,7 +50,7 @@ where today, the return type annotation `F T` is simply parsed as just `F`, foll
|
||||||
|
|
||||||
For `keyof`, here's a practical example of the above issue:
|
For `keyof`, here's a practical example of the above issue:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
type Vec2 = {x: number, y: number}
|
type Vec2 = {x: number, y: number}
|
||||||
|
|
||||||
local function f(t, u): keyof Vec2
|
local function f(t, u): keyof Vec2
|
||||||
|
@ -65,13 +65,13 @@ There's three possible outcomes:
|
||||||
|
|
||||||
This particular case is even worse when we keep going:
|
This particular case is even worse when we keep going:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
local function f(t): F
|
local function f(t): F
|
||||||
T(t or u):m()
|
T(t or u):m()
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```luau
|
||||||
local function f(t): F T
|
local function f(t): F T
|
||||||
{1, 2, 3}
|
{1, 2, 3}
|
||||||
end
|
end
|
||||||
|
@ -101,13 +101,13 @@ If #1 is what ends up happening, there's not much to worry about because the typ
|
||||||
|
|
||||||
If #2 is what ends up happening, there could be a problem if we didn't future-proof against `<` and `(` to follow `function`:
|
If #2 is what ends up happening, there could be a problem if we didn't future-proof against `<` and `(` to follow `function`:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
return f :: function(T) -> U
|
return f :: function(T) -> U
|
||||||
```
|
```
|
||||||
|
|
||||||
which would be a parse error because at the point of `(` we expect one of `until`, `end`, or `EOF`, and
|
which would be a parse error because at the point of `(` we expect one of `until`, `end`, or `EOF`, and
|
||||||
|
|
||||||
```
|
```luau
|
||||||
return f :: function<a>(a) -> a
|
return f :: function<a>(a) -> a
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ The endianness of an integer is generally invisible to Luau users. Numbers are t
|
||||||
|
|
||||||
While the endianness of numbers can be swapped through a few methods, it is cumbersome. Modern CPUs have instructions dedicated to this (`bswap` on x86-64, `rev` on aarch64) but in Luau, the current best method is to manually shift bytes around and OR them together. For 32-bit integers, this becomes a total of 7 calls:
|
While the endianness of numbers can be swapped through a few methods, it is cumbersome. Modern CPUs have instructions dedicated to this (`bswap` on x86-64, `rev` on aarch64) but in Luau, the current best method is to manually shift bytes around and OR them together. For 32-bit integers, this becomes a total of 7 calls:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
bit32.bor(
|
bit32.bor(
|
||||||
bit32.lshift(n, 24),
|
bit32.lshift(n, 24),
|
||||||
bit32.band(bit32.lshift(n, 8), 0xFF0000),
|
bit32.band(bit32.lshift(n, 8), 0xFF0000),
|
||||||
|
@ -27,7 +27,7 @@ Along with being inefficient, it is also difficult read this code and remember i
|
||||||
|
|
||||||
The `bit32` library will gain a new function: `bit32.byteswap`:
|
The `bit32` library will gain a new function: `bit32.byteswap`:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
bit32.byteswap(n: number): number
|
bit32.byteswap(n: number): number
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ Today it's possible to approximate `countlz` using `floor` and `log` but this ap
|
||||||
|
|
||||||
`bit32` library will gain two new functions, `countlz` and `countrz`:
|
`bit32` library will gain two new functions, `countlz` and `countrz`:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
function bit32.countlz(n: number): number
|
function bit32.countlz(n: number): number
|
||||||
function bit32.countrz(n: number): number
|
function bit32.countrz(n: number): number
|
||||||
```
|
```
|
||||||
|
|
|
@ -39,7 +39,7 @@ Unlike Lua version, which would use the options given to fill a resulting table
|
||||||
|
|
||||||
For example, here's how you implement a stack trace function:
|
For example, here's how you implement a stack trace function:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
for i=1,100 do -- limit at 100 entries for very deep stacks
|
for i=1,100 do -- limit at 100 entries for very deep stacks
|
||||||
local source, name, line = debug.info(i, "snl")
|
local source, name, line = debug.info(i, "snl")
|
||||||
if not source then break end
|
if not source then break end
|
||||||
|
|
|
@ -46,7 +46,7 @@ Of course, the functions are memory-safe; if the input string is too short to pr
|
||||||
|
|
||||||
This may seem slightly unconventional but it's very powerful and expressive, in much the same way format strings and regular expressions are :) Here's a basic example of how you might transmit a 3-component vector with this:
|
This may seem slightly unconventional but it's very powerful and expressive, in much the same way format strings and regular expressions are :) Here's a basic example of how you might transmit a 3-component vector with this:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
-- returns a 24-byte string with 64-bit double encoded three times, similar to how we'd replicate 3 raw numbers
|
-- returns a 24-byte string with 64-bit double encoded three times, similar to how we'd replicate 3 raw numbers
|
||||||
string.pack("ddd", x, y, z)
|
string.pack("ddd", x, y, z)
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ The table can be modified after cloning; as such, functions that compute a sligh
|
||||||
|
|
||||||
`table.clone(t)` is functionally equivalent to the following code, but it's more ergonomic (on the account of being built-in) and significantly faster:
|
`table.clone(t)` is functionally equivalent to the following code, but it's more ergonomic (on the account of being built-in) and significantly faster:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
assert(type(t) == "table")
|
assert(type(t) == "table")
|
||||||
local nt = {}
|
local nt = {}
|
||||||
for k,v in pairs(t) do
|
for k,v in pairs(t) do
|
||||||
|
|
|
@ -16,7 +16,7 @@ This proposal suggests adding two new builtin table functions:
|
||||||
|
|
||||||
`table.find` is roughly equivalent to the following code modulo semantical oddities with #t and performance:
|
`table.find` is roughly equivalent to the following code modulo semantical oddities with #t and performance:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
function find(table, value, init)
|
function find(table, value, init)
|
||||||
for i=init or 1, #table do
|
for i=init or 1, #table do
|
||||||
if rawget(table, i) == value then
|
if rawget(table, i) == value then
|
||||||
|
|
|
@ -24,7 +24,7 @@ This proposal solves all of these by providing a way to implement uniform iterat
|
||||||
|
|
||||||
In Lua, `for vars in iter do` has the following semantics (otherwise known as the iteration protocol): `iter` is expanded into three variables, `gen`, `state` and `index` (using `nil` if `iter` evaluates to fewer than 3 results); after this the loop is converted to the following pseudocode:
|
In Lua, `for vars in iter do` has the following semantics (otherwise known as the iteration protocol): `iter` is expanded into three variables, `gen`, `state` and `index` (using `nil` if `iter` evaluates to fewer than 3 results); after this the loop is converted to the following pseudocode:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
while true do
|
while true do
|
||||||
vars... = gen(state, index)
|
vars... = gen(state, index)
|
||||||
index = vars... -- copy the first variable into the index
|
index = vars... -- copy the first variable into the index
|
||||||
|
@ -40,7 +40,7 @@ Thus, today the loop `for k, v in tab do` effectively executes `k, v = tab()` on
|
||||||
|
|
||||||
This proposal comes in two parts: general support for `__iter` metamethod and default implementation for tables without one. With both of these in place, there's going to be a single, idiomatic, general and performant way to iterate through the object of any type:
|
This proposal comes in two parts: general support for `__iter` metamethod and default implementation for tables without one. With both of these in place, there's going to be a single, idiomatic, general and performant way to iterate through the object of any type:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
for k, v in obj do
|
for k, v in obj do
|
||||||
...
|
...
|
||||||
end
|
end
|
||||||
|
@ -50,7 +50,7 @@ end
|
||||||
|
|
||||||
To support self-iterating objects, we modify the iteration protocol as follows: instead of simply expanding the result of expression `iter` into three variables (`gen`, `state` and `index`), we check if the first result has an `__iter` metamethod (which can be the case if it's a table, userdata or another composite object (e.g. a record in the future). If it does, the metamethod is called with `gen` as the first argument, and the returned three values replace `gen`/`state`/`index`. This happens *before* the loop:
|
To support self-iterating objects, we modify the iteration protocol as follows: instead of simply expanding the result of expression `iter` into three variables (`gen`, `state` and `index`), we check if the first result has an `__iter` metamethod (which can be the case if it's a table, userdata or another composite object (e.g. a record in the future). If it does, the metamethod is called with `gen` as the first argument, and the returned three values replace `gen`/`state`/`index`. This happens *before* the loop:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local genmt = rawgetmetatable(gen) -- pseudo code for getmetatable that bypasses __metatable
|
local genmt = rawgetmetatable(gen) -- pseudo code for getmetatable that bypasses __metatable
|
||||||
local iterf = genmt and rawget(genmt, "__iter")
|
local iterf = genmt and rawget(genmt, "__iter")
|
||||||
if iterf then
|
if iterf then
|
||||||
|
@ -62,7 +62,7 @@ This check is comparatively trivial: usually `gen` is a function, and functions
|
||||||
|
|
||||||
This allows objects to provide a custom function that guides the iteration. Since the function is called once, it is easy to reuse other functions in the implementation, for example here's a node object that exposes iteration through its children:
|
This allows objects to provide a custom function that guides the iteration. Since the function is called once, it is easy to reuse other functions in the implementation, for example here's a node object that exposes iteration through its children:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local Node = {}
|
local Node = {}
|
||||||
Node.__index = Node
|
Node.__index = Node
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ of places where the typechecker will automatically perform instantiation with
|
||||||
the goal of permitting more programs. These instances of instantiation are
|
the goal of permitting more programs. These instances of instantiation are
|
||||||
ad-hoc and strategic, but useful in practice for permitting programs such as:
|
ad-hoc and strategic, but useful in practice for permitting programs such as:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function id<T>(x: T): T
|
function id<T>(x: T): T
|
||||||
return x
|
return x
|
||||||
end
|
end
|
||||||
|
@ -50,7 +50,7 @@ subtype having some generic parameters and the supertype having none. So, if we
|
||||||
look once again at our simple example from motivation, we can walk through how
|
look once again at our simple example from motivation, we can walk through how
|
||||||
we expect it to type check:
|
we expect it to type check:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function id<T>(x: T): T
|
function id<T>(x: T): T
|
||||||
return x
|
return x
|
||||||
end
|
end
|
||||||
|
@ -74,7 +74,7 @@ Adding instantiation to subtyping does pose some additional questions still
|
||||||
about when exactly to instantiate. Namely, we need to consider cases like
|
about when exactly to instantiate. Namely, we need to consider cases like
|
||||||
function application. We can see why by looking at some examples:
|
function application. We can see why by looking at some examples:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function rank2(f: <a>(a) -> a): (number) -> number
|
function rank2(f: <a>(a) -> a): (number) -> number
|
||||||
return f
|
return f
|
||||||
end
|
end
|
||||||
|
@ -126,7 +126,7 @@ It may also be helpful to consider an example of rank-1 polymorphism to
|
||||||
understand the full scope of the behavior. So, we can look at what happens if we
|
understand the full scope of the behavior. So, we can look at what happens if we
|
||||||
simply move the type parameter out in our working example:
|
simply move the type parameter out in our working example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function rank1<a>(f: (a) -> a): (number) -> number
|
function rank1<a>(f: (a) -> a): (number) -> number
|
||||||
return f
|
return f
|
||||||
end
|
end
|
||||||
|
@ -152,7 +152,7 @@ however, programmers may be surprised by the added restriction when it comes to
|
||||||
properties in tables. In particular, we can consider a small variation of our
|
properties in tables. In particular, we can consider a small variation of our
|
||||||
original example with identity functions:
|
original example with identity functions:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function id<T>(x: T): T
|
function id<T>(x: T): T
|
||||||
return x
|
return x
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ Extend the syntax and semantics of functions to support explicit generic functio
|
||||||
|
|
||||||
Currently Luau allows generic functions to be inferred but not given explicit type annotations. For example
|
Currently Luau allows generic functions to be inferred but not given explicit type annotations. For example
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function id(x) return x end
|
function id(x) return x end
|
||||||
local x: string = id("hi")
|
local x: string = id("hi")
|
||||||
local y: number = id(37)
|
local y: number = id(37)
|
||||||
|
@ -22,25 +22,25 @@ is fine, but there is no way for a user to write the type of `id`.
|
||||||
|
|
||||||
Allow functions to take type parameters as well as function parameters, similar to Java/Typescript/...
|
Allow functions to take type parameters as well as function parameters, similar to Java/Typescript/...
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function id<a>(x : a) : a return x end
|
function id<a>(x : a) : a return x end
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions may also take generic type pack arguments for varargs, for instance:
|
Functions may also take generic type pack arguments for varargs, for instance:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function compose<a...>(... : a...) -> (a...) return ... end
|
function compose<a...>(... : a...) -> (a...) return ... end
|
||||||
```
|
```
|
||||||
|
|
||||||
Generic type and type pack parameters can also be used in function types, for instance:
|
Generic type and type pack parameters can also be used in function types, for instance:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local id: <a>(a)->a = function(x) return x end
|
local id: <a>(a)->a = function(x) return x end
|
||||||
```
|
```
|
||||||
|
|
||||||
This change is *not* only syntax, as explicit type parameters need to be part of the semantics of types. For example, we can define a generic identity function
|
This change is *not* only syntax, as explicit type parameters need to be part of the semantics of types. For example, we can define a generic identity function
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local function id(x) return x end
|
local function id(x) return x end
|
||||||
local x: string = id("hi")
|
local x: string = id("hi")
|
||||||
local y: number = id(37)
|
local y: number = id(37)
|
||||||
|
@ -49,7 +49,7 @@ type Id = typeof(id)
|
||||||
|
|
||||||
and two functions
|
and two functions
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function f()
|
function f()
|
||||||
return id
|
return id
|
||||||
end
|
end
|
||||||
|
@ -65,14 +65,14 @@ end
|
||||||
|
|
||||||
The types of these functions are
|
The types of these functions are
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
f : () -> <a>(a) -> a
|
f : () -> <a>(a) -> a
|
||||||
g : <a>() -> (a) -> a
|
g : <a>() -> (a) -> a
|
||||||
```
|
```
|
||||||
|
|
||||||
so this is okay:
|
so this is okay:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local i: Id = f()
|
local i: Id = f()
|
||||||
local x: string = i("hi")
|
local x: string = i("hi")
|
||||||
local y: number = i(37)
|
local y: number = i(37)
|
||||||
|
@ -80,7 +80,7 @@ so this is okay:
|
||||||
|
|
||||||
but this is not:
|
but this is not:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- This assignment shouldn't typecheck!
|
-- This assignment shouldn't typecheck!
|
||||||
local i: Id = g()
|
local i: Id = g()
|
||||||
local x: string = i("hi")
|
local x: string = i("hi")
|
||||||
|
@ -96,7 +96,7 @@ We propose supporting type parameters which can be instantiated with any type (j
|
||||||
|
|
||||||
Note that this RFC proposes a syntax for adding generic parameters to functions, but it does *not* propose syntax for adding generic arguments to function call site. For example, for `id` function you *can* write:
|
Note that this RFC proposes a syntax for adding generic parameters to functions, but it does *not* propose syntax for adding generic arguments to function call site. For example, for `id` function you *can* write:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- generic type gets inferred as a number in all these cases
|
-- generic type gets inferred as a number in all these cases
|
||||||
local x = id(4)
|
local x = id(4)
|
||||||
local x = id(y) :: number
|
local x = id(y) :: number
|
||||||
|
@ -113,7 +113,7 @@ If we ever want to implement this though, we can use a solution inspired by Rust
|
||||||
|
|
||||||
The following two variants are grammatically unambiguous in expression context in Luau, and are a better parallel for Rust's turbofish (in Rust, `::` is more similar to Luau's `:` or `.` than `::`, which in Rust is called `as`):
|
The following two variants are grammatically unambiguous in expression context in Luau, and are a better parallel for Rust's turbofish (in Rust, `::` is more similar to Luau's `:` or `.` than `::`, which in Rust is called `as`):
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
foo:<number, string>() -- require : before <; this is only valid in Luau in variable declaration context, so it's safe to use in expression context
|
foo:<number, string>() -- require : before <; this is only valid in Luau in variable declaration context, so it's safe to use in expression context
|
||||||
foo.<number, string>() -- require . before <; this is currently never valid in Luau
|
foo.<number, string>() -- require . before <; this is currently never valid in Luau
|
||||||
```
|
```
|
||||||
|
@ -128,7 +128,7 @@ Types become more complex, so harder for programmers to reason about, and adding
|
||||||
|
|
||||||
Not having higher-kinded types stops some examples which are parameterized on container types, for example:
|
Not having higher-kinded types stops some examples which are parameterized on container types, for example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function g<c>(f : <a>(a) -> c<a>) : <b>(b) -> c<c<b>>
|
function g<c>(f : <a>(a) -> c<a>) : <b>(b) -> c<c<b>>
|
||||||
return function(x) return f(f(x)) end
|
return function(x) return f(f(x)) end
|
||||||
end
|
end
|
||||||
|
@ -136,7 +136,7 @@ Not having higher-kinded types stops some examples which are parameterized on co
|
||||||
|
|
||||||
Not having bounded types stops some examples like giving a type to the function that sums an non-empty array:
|
Not having bounded types stops some examples like giving a type to the function that sums an non-empty array:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function sum(xs)
|
function sum(xs)
|
||||||
local result = x[0]
|
local result = x[0]
|
||||||
for i=1,#xs
|
for i=1,#xs
|
||||||
|
|
|
@ -13,7 +13,7 @@ as objects and/or enumerations, and to reduce the amount of duplicate work users
|
||||||
must undergo in order to type code that does this. For instance, consider the
|
must undergo in order to type code that does this. For instance, consider the
|
||||||
following example code:
|
following example code:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type AnimalType = "cat" | "dog" | "monkey" | "fox"
|
type AnimalType = "cat" | "dog" | "monkey" | "fox"
|
||||||
|
|
||||||
local animals = {
|
local animals = {
|
||||||
|
@ -43,7 +43,7 @@ The solution to this problem is a type operator, `keyof`, that can compute the
|
||||||
type based on the type of `animals`. This would allow us to instead write this
|
type based on the type of `animals`. This would allow us to instead write this
|
||||||
code as follows:
|
code as follows:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local animals = {
|
local animals = {
|
||||||
cat = { speak = function() print "meow" end },
|
cat = { speak = function() print "meow" end },
|
||||||
dog = { speak = function() print "woof woof" end },
|
dog = { speak = function() print "woof woof" end },
|
||||||
|
@ -77,7 +77,7 @@ legal keys for indexing and only the keys legal for `rawget` respectively.
|
||||||
|
|
||||||
So, if we consider some very simple strawman code here:
|
So, if we consider some very simple strawman code here:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local MyClass = { Foo = "Bar" }
|
local MyClass = { Foo = "Bar" }
|
||||||
local OtherClass = setmetatable({ Hello = "World" }, { __index = MyClass })
|
local OtherClass = setmetatable({ Hello = "World" }, { __index = MyClass })
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ We originally implemented nonstrict mode by making some tactical adjustments to
|
||||||
|
|
||||||
Separately, we would also like more accurate type inference in general. Our current type solver jumps to conclusions a little bit too quickly. For example, it cannot infer an accurate type for an ordinary search function:
|
Separately, we would also like more accurate type inference in general. Our current type solver jumps to conclusions a little bit too quickly. For example, it cannot infer an accurate type for an ordinary search function:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function index_of(tbl, el)
|
function index_of(tbl, el)
|
||||||
for i = 0, #tbl do
|
for i = 0, #tbl do
|
||||||
if tbl[i] == el then
|
if tbl[i] == el then
|
||||||
|
@ -49,7 +49,7 @@ When dispatching a constraint `T <: 't`, we replace the lower bounds of `'t` by
|
||||||
|
|
||||||
A return statement expands the lower bounds of the enclosing function's return type.
|
A return statement expands the lower bounds of the enclosing function's return type.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function f(): R
|
function f(): R
|
||||||
local x: X
|
local x: X
|
||||||
return x
|
return x
|
||||||
|
@ -59,7 +59,7 @@ end
|
||||||
|
|
||||||
An assignment adds to the lower bounds of the assignee.
|
An assignment adds to the lower bounds of the assignee.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local a: A
|
local a: A
|
||||||
local b: B
|
local b: B
|
||||||
a = b
|
a = b
|
||||||
|
@ -70,7 +70,7 @@ A function call adds to the upper bounds of the function being called.
|
||||||
|
|
||||||
Equivalently, passing a value to a function adds to the upper bounds of that value and to the lower bounds of its return value.
|
Equivalently, passing a value to a function adds to the upper bounds of that value and to the lower bounds of its return value.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local g
|
local g
|
||||||
local h: H
|
local h: H
|
||||||
local j = g(h)
|
local j = g(h)
|
||||||
|
@ -79,7 +79,7 @@ local j = g(h)
|
||||||
```
|
```
|
||||||
|
|
||||||
Property access is a constraint on a value's upper bounds.
|
Property access is a constraint on a value's upper bounds.
|
||||||
```lua
|
```luau
|
||||||
local a: A
|
local a: A
|
||||||
a.b = 2
|
a.b = 2
|
||||||
-- A <: {b: number}
|
-- A <: {b: number}
|
||||||
|
@ -100,7 +100,7 @@ If a free type has neither upper nor lower bounds, we replace it with a generic.
|
||||||
|
|
||||||
Some simple examples:
|
Some simple examples:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function print_number(n: number) print(n) end
|
function print_number(n: number) print(n) end
|
||||||
|
|
||||||
function f(n)
|
function f(n)
|
||||||
|
@ -112,7 +112,7 @@ We arrive at the solution `never <: 'n <: number`. When we generalize, we can r
|
||||||
|
|
||||||
Next example:
|
Next example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function index_of(tbl, el) -- index_of : ('a, 'b) -> 'r
|
function index_of(tbl, el) -- index_of : ('a, 'b) -> 'r
|
||||||
for i = 0, #tbl do -- i : number
|
for i = 0, #tbl do -- i : number
|
||||||
if tbl[i] == el then -- 'a <: {'c}
|
if tbl[i] == el then -- 'a <: {'c}
|
||||||
|
@ -146,7 +146,7 @@ This algorithm requires that we create a lot of union and intersection types. W
|
||||||
|
|
||||||
Local type inference is also more permissive than what we have been doing up until now. For instance, the following is perfectly fine:
|
Local type inference is also more permissive than what we have been doing up until now. For instance, the following is perfectly fine:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local x = nil
|
local x = nil
|
||||||
if something then
|
if something then
|
||||||
x = 41
|
x = 41
|
||||||
|
|
|
@ -18,7 +18,7 @@ Many of these revolve around type variables that occur in contravariant position
|
||||||
|
|
||||||
A very common thing to write in Luau is a function to try to find something in some data structure. These functions habitually return the relevant datum when it is successfully found, or `nil` in the case that it cannot. For instance:
|
A very common thing to write in Luau is a function to try to find something in some data structure. These functions habitually return the relevant datum when it is successfully found, or `nil` in the case that it cannot. For instance:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- A.lua
|
-- A.lua
|
||||||
function find_first_if(vec, f)
|
function find_first_if(vec, f)
|
||||||
for i, e in ipairs(vec) do
|
for i, e in ipairs(vec) do
|
||||||
|
@ -37,7 +37,7 @@ We would like to automatically infer `find_first_if : <T>({T}, (T) -> boolean) -
|
||||||
|
|
||||||
Higher order functions also present a similar problem.
|
Higher order functions also present a similar problem.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- B.lua
|
-- B.lua
|
||||||
function foo(f)
|
function foo(f)
|
||||||
f(5)
|
f(5)
|
||||||
|
@ -81,7 +81,7 @@ If we are going to infer intersections of functions, then we need to be very car
|
||||||
|
|
||||||
A very important use case for us is the case where the user is providing a callback to some higher-order function, and that function will be invoked with extra arguments that the original customer doesn't actually care about. For example:
|
A very important use case for us is the case where the user is providing a callback to some higher-order function, and that function will be invoked with extra arguments that the original customer doesn't actually care about. For example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- C.lua
|
-- C.lua
|
||||||
function map_array(arr, f)
|
function map_array(arr, f)
|
||||||
local result = {}
|
local result = {}
|
||||||
|
@ -101,7 +101,7 @@ This use case is very important for Roblox, as we have many APIs that accept cal
|
||||||
|
|
||||||
Here is an example straight out of the Roblox developer documentation. ([full example here](https://developer.roblox.com/en-us/api-reference/event/BasePart/Touched))
|
Here is an example straight out of the Roblox developer documentation. ([full example here](https://developer.roblox.com/en-us/api-reference/event/BasePart/Touched))
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- D.lua
|
-- D.lua
|
||||||
local part = script.Parent
|
local part = script.Parent
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ The `Touched` event actually passes a single argument: the part that touched the
|
||||||
|
|
||||||
We therefore want _oversaturation_ of a function to be allowed, but this combines with optional function arguments to create a problem with soundness. Consider the following:
|
We therefore want _oversaturation_ of a function to be allowed, but this combines with optional function arguments to create a problem with soundness. Consider the following:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- E.lua
|
-- E.lua
|
||||||
type Callback = (Instance) -> ()
|
type Callback = (Instance) -> ()
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ The problem we run into is, if we allow the subtyping rule `(T?) -> () <: () ->
|
||||||
|
|
||||||
Next, consider the following type alias
|
Next, consider the following type alias
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- F.lua
|
-- F.lua
|
||||||
type OldFunctionType = (any, any) -> any
|
type OldFunctionType = (any, any) -> any
|
||||||
type NewFunctionType = (any) -> any
|
type NewFunctionType = (any) -> any
|
||||||
|
|
|
@ -16,13 +16,13 @@ special "switch off the type system" superpowers.
|
||||||
Any use of `unknown` must be narrowed by type refinements unless another `unknown` or `any` is expected. For
|
Any use of `unknown` must be narrowed by type refinements unless another `unknown` or `any` is expected. For
|
||||||
example a function which can return any value is:
|
example a function which can return any value is:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function anything() : unknown ... end
|
function anything() : unknown ... end
|
||||||
```
|
```
|
||||||
|
|
||||||
and can be used as:
|
and can be used as:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local x = anything()
|
local x = anything()
|
||||||
if type(x) == "number" then
|
if type(x) == "number" then
|
||||||
print(x + 1)
|
print(x + 1)
|
||||||
|
@ -33,7 +33,7 @@ The type of this function cannot be given concisely in current
|
||||||
Luau. The nearest equivalent is `any`, but this switches off the type system, for example
|
Luau. The nearest equivalent is `any`, but this switches off the type system, for example
|
||||||
if the type of `anything` is `() -> any` then the following code typechecks:
|
if the type of `anything` is `() -> any` then the following code typechecks:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local x = anything()
|
local x = anything()
|
||||||
print(x + 1)
|
print(x + 1)
|
||||||
```
|
```
|
||||||
|
@ -42,7 +42,7 @@ This is fine in nonstrict mode, but strict mode should flag this as an error.
|
||||||
|
|
||||||
The `never` type comes up whenever type inference infers incompatible types for a variable, for example
|
The `never` type comes up whenever type inference infers incompatible types for a variable, for example
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function oops(x)
|
function oops(x)
|
||||||
print("hi " .. x) -- constrains x must be a string
|
print("hi " .. x) -- constrains x must be a string
|
||||||
print(math.abs(x)) -- constrains x must be a number
|
print(math.abs(x)) -- constrains x must be a number
|
||||||
|
@ -55,7 +55,7 @@ a type error, but we still need to provide a type for `oops`. With a
|
||||||
|
|
||||||
or when exhaustive type casing is achieved:
|
or when exhaustive type casing is achieved:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function f(x: string | number)
|
function f(x: string | number)
|
||||||
if type(x) == "string" then
|
if type(x) == "string" then
|
||||||
-- x : string
|
-- x : string
|
||||||
|
@ -69,7 +69,7 @@ or when exhaustive type casing is achieved:
|
||||||
|
|
||||||
or even when the type casing is simply nonsensical:
|
or even when the type casing is simply nonsensical:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function f(x: string | number)
|
function f(x: string | number)
|
||||||
if type(x) == "string" and type(x) == "number" then
|
if type(x) == "string" and type(x) == "number" then
|
||||||
-- x : string & number which is never
|
-- x : string & number which is never
|
||||||
|
@ -80,7 +80,7 @@ or even when the type casing is simply nonsensical:
|
||||||
The `never` type is also useful in cases such as tagged unions where
|
The `never` type is also useful in cases such as tagged unions where
|
||||||
some of the cases are impossible. For example:
|
some of the cases are impossible. For example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Result<T, E> = { err: false, val: T } | { err: true, err: E }
|
type Result<T, E> = { err: false, val: T } | { err: true, err: E }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ has type `Result<never, E>`.
|
||||||
|
|
||||||
These types can _almost_ be defined in current Luau, but only quite verbosely:
|
These types can _almost_ be defined in current Luau, but only quite verbosely:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type never = number & string
|
type never = number & string
|
||||||
type unknown = nil | number | boolean | string | {} | (...never) -> (...unknown)
|
type unknown = nil | number | boolean | string | {} | (...never) -> (...unknown)
|
||||||
```
|
```
|
||||||
|
@ -119,7 +119,7 @@ the type `() -> never` is not completely accurate, it should be `() -> (never, .
|
||||||
cascading type errors. Ditto for when an expression list `f(), g()` where the resulting type pack is
|
cascading type errors. Ditto for when an expression list `f(), g()` where the resulting type pack is
|
||||||
`(never, string, number)` is still the same as `(never, ...never)`.
|
`(never, string, number)` is still the same as `(never, ...never)`.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function f(): never error() end
|
function f(): never error() end
|
||||||
function g(): string return "" end
|
function g(): string return "" end
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ that there is a code defect. Example defects are:
|
||||||
|
|
||||||
Detecting run-time errors is undecidable, for example
|
Detecting run-time errors is undecidable, for example
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
if cond() then
|
if cond() then
|
||||||
math.abs(“hi”)
|
math.abs(“hi”)
|
||||||
end
|
end
|
||||||
|
@ -55,7 +55,7 @@ run-time error, and so report a type error in this case.
|
||||||
missing property is accessed (though embeddings may). So something
|
missing property is accessed (though embeddings may). So something
|
||||||
like
|
like
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local t = { Foo = 5 }
|
local t = { Foo = 5 }
|
||||||
local x = t.Fop
|
local x = t.Fop
|
||||||
```
|
```
|
||||||
|
@ -64,7 +64,7 @@ won’t produce a run-time error, but is more likely than not a
|
||||||
programmer error. In this case, if the programmer intent was to
|
programmer error. In this case, if the programmer intent was to
|
||||||
initialize `x` as `nil`, they could have written
|
initialize `x` as `nil`, they could have written
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local t = { Foo = 5 }
|
local t = { Foo = 5 }
|
||||||
local x = nil
|
local x = nil
|
||||||
```
|
```
|
||||||
|
@ -75,7 +75,7 @@ type system guarantees is of type `nil`.
|
||||||
*Writing properties that are never read*: There is a matching problem
|
*Writing properties that are never read*: There is a matching problem
|
||||||
with misspelling properties when writing. For example
|
with misspelling properties when writing. For example
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function f()
|
function f()
|
||||||
local t = {}
|
local t = {}
|
||||||
t.Foo = 5
|
t.Foo = 5
|
||||||
|
@ -108,7 +108,7 @@ produce a type error.
|
||||||
|
|
||||||
For example in the program
|
For example in the program
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function h(x, y)
|
function h(x, y)
|
||||||
math.abs(x)
|
math.abs(x)
|
||||||
string.lower(y)
|
string.lower(y)
|
||||||
|
@ -124,7 +124,7 @@ y : ~string
|
||||||
|
|
||||||
In the function:
|
In the function:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function f(x)
|
function f(x)
|
||||||
math.abs(x)
|
math.abs(x)
|
||||||
string.lower(x)
|
string.lower(x)
|
||||||
|
@ -141,7 +141,7 @@ Since `~number | ~string` is equivalent to `unknown`, non-strict mode
|
||||||
can report a warning, since calling the function is guaranteed to
|
can report a warning, since calling the function is guaranteed to
|
||||||
throw a run-time error. In contrast:
|
throw a run-time error. In contrast:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function g(x)
|
function g(x)
|
||||||
if cond() then
|
if cond() then
|
||||||
math.abs(x)
|
math.abs(x)
|
||||||
|
@ -219,7 +219,7 @@ issues warnings at the point that data flows into a place
|
||||||
guaranteed to later produce a run-time error, which may not be perfect
|
guaranteed to later produce a run-time error, which may not be perfect
|
||||||
ergonomics. For example, in the program:
|
ergonomics. For example, in the program:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local x
|
local x
|
||||||
if cond() then
|
if cond() then
|
||||||
x = 5
|
x = 5
|
||||||
|
@ -297,7 +297,7 @@ We could use this design to infer checked functions. In function
|
||||||
`(unknown^(i-1),Ti,unknown^(N-i))->error` to the inferred type of `f`. For
|
`(unknown^(i-1),Ti,unknown^(N-i))->error` to the inferred type of `f`. For
|
||||||
example, for the function
|
example, for the function
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function h(x, y)
|
function h(x, y)
|
||||||
math.abs(x)
|
math.abs(x)
|
||||||
string.lower(y)
|
string.lower(y)
|
||||||
|
|
|
@ -16,14 +16,14 @@ Currently, relative paths are always evaluated relative to the current working d
|
||||||
|
|
||||||
Suppose the module `math.luau` is located in `/Users/JohnDoe/LuauModules/Math` and contains the following:
|
Suppose the module `math.luau` is located in `/Users/JohnDoe/LuauModules/Math` and contains the following:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- Beginning of /Users/JohnDoe/LuauModules/Math/math.luau
|
-- Beginning of /Users/JohnDoe/LuauModules/Math/math.luau
|
||||||
local sqrt = require("../MathHelperFunctions/sqrt")
|
local sqrt = require("../MathHelperFunctions/sqrt")
|
||||||
```
|
```
|
||||||
|
|
||||||
If we then launched the Luau CLI from the directory `/Users/JohnDoe/Projects/MyCalculator` and required `math.luau` as follows:
|
If we then launched the Luau CLI from the directory `/Users/JohnDoe/Projects/MyCalculator` and required `math.luau` as follows:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local math = require("/Users/JohnDoe/LuauModules/Math/math")
|
local math = require("/Users/JohnDoe/LuauModules/Math/math")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ To require a Luau module under the current implementation, we must require it ei
|
||||||
Modules can be required relative to the requiring file's location in the filesystem (note, this is different from the current implementation, which evaluates all relative paths in relation to the current working directory).
|
Modules can be required relative to the requiring file's location in the filesystem (note, this is different from the current implementation, which evaluates all relative paths in relation to the current working directory).
|
||||||
|
|
||||||
If we are trying to require a module called `MyModule.luau` in `C:/MyLibrary`:
|
If we are trying to require a module called `MyModule.luau` in `C:/MyLibrary`:
|
||||||
```lua
|
```luau
|
||||||
local MyModule = require("MyModule")
|
local MyModule = require("MyModule")
|
||||||
|
|
||||||
-- From C:/MyLibrary/SubDirectory/SubModule.luau
|
-- From C:/MyLibrary/SubDirectory/SubModule.luau
|
||||||
|
@ -126,13 +126,13 @@ For consistency, we propose storing the file extension in `lua_require` and alwa
|
||||||
|
|
||||||
By interpreting relative paths relative to the requiring file's location, Luau projects can now have internal dependencies. For example, in [Roact's current implementation](https://github.com/Roblox/roact), `Component.lua` requires `assign.lua` [like this](https://github.com/Roblox/roact/blob/beb0bc2706b307b04204abdcf129385fd3cb3e6f/src/Component.lua#L1C1-L1C45):
|
By interpreting relative paths relative to the requiring file's location, Luau projects can now have internal dependencies. For example, in [Roact's current implementation](https://github.com/Roblox/roact), `Component.lua` requires `assign.lua` [like this](https://github.com/Roblox/roact/blob/beb0bc2706b307b04204abdcf129385fd3cb3e6f/src/Component.lua#L1C1-L1C45):
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local assign = require(script.Parent.assign)
|
local assign = require(script.Parent.assign)
|
||||||
```
|
```
|
||||||
|
|
||||||
By using "Roblox-style" syntax (referring to Roblox Instances in the require statement), `Component.lua` is able to perform a relative-to-requiring-script require. However, with the proposed changes in this RFC, we could instead do this with clean syntax that works outside of the context of Roblox:
|
By using "Roblox-style" syntax (referring to Roblox Instances in the require statement), `Component.lua` is able to perform a relative-to-requiring-script require. However, with the proposed changes in this RFC, we could instead do this with clean syntax that works outside of the context of Roblox:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local assign = require("./assign")
|
local assign = require("./assign")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ Luau libraries are already not compatible with existing Lua libraries. This is b
|
||||||
|
|
||||||
In considering alternatives to enhancing relative imports in Luau, one can draw inspiration from other language systems. An elegant solution is the package import system similar to Dart's approach. Instead of relying on file-specific paths, this proposed system would utilize an absolute `package:` syntax:
|
In considering alternatives to enhancing relative imports in Luau, one can draw inspiration from other language systems. An elegant solution is the package import system similar to Dart's approach. Instead of relying on file-specific paths, this proposed system would utilize an absolute `package:` syntax:
|
||||||
|
|
||||||
```lua
|
```
|
||||||
import 'package:my_package/my_file.lua';
|
import 'package:my_package/my_file.lua';
|
||||||
```
|
```
|
||||||
Undesirable because this would be redundant with the [alias RFC](https://github.com/Roblox/luau/pull/1061).
|
Undesirable because this would be redundant with the [alias RFC](https://github.com/Roblox/luau/pull/1061).
|
||||||
|
|
|
@ -30,7 +30,7 @@ This proposal is not about syntax, but it will be useful for examples to have so
|
||||||
* `get p: T` for a read-only property of type `T`.
|
* `get p: T` for a read-only property of type `T`.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
```lua
|
```luau
|
||||||
function f(t)
|
function f(t)
|
||||||
t.p = 1 + t.p + t.q
|
t.p = 1 + t.p + t.q
|
||||||
end
|
end
|
||||||
|
@ -57,7 +57,7 @@ Indexers can be marked read-only just like properties. In
|
||||||
particular, this means there are read-only arrays `{get T}`, that are
|
particular, this means there are read-only arrays `{get T}`, that are
|
||||||
covariant, so we have a solution to the "covariant array problem":
|
covariant, so we have a solution to the "covariant array problem":
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local dogs: {Dog}
|
local dogs: {Dog}
|
||||||
function f(a: {get Animal}) ... end
|
function f(a: {get Animal}) ... end
|
||||||
f(dogs)
|
f(dogs)
|
||||||
|
@ -71,7 +71,7 @@ for example `function f(a: {Animal}) a[1] = Cat.new() end`.
|
||||||
### Functions
|
### Functions
|
||||||
|
|
||||||
Functions are not normally mutated after they are initialized, so
|
Functions are not normally mutated after they are initialized, so
|
||||||
```lua
|
```luau
|
||||||
local t = {}
|
local t = {}
|
||||||
function t.f() ... end
|
function t.f() ... end
|
||||||
function t:m() ... end
|
function t:m() ... end
|
||||||
|
@ -87,18 +87,18 @@ t : {
|
||||||
|
|
||||||
If developers want a mutable function,
|
If developers want a mutable function,
|
||||||
they can use the anonymous function version
|
they can use the anonymous function version
|
||||||
```lua
|
```luau
|
||||||
t.g = function() ... end
|
t.g = function() ... end
|
||||||
```
|
```
|
||||||
|
|
||||||
For example, if we define:
|
For example, if we define:
|
||||||
```lua
|
```luau
|
||||||
type RWFactory<A> = { build : () -> A }
|
type RWFactory<A> = { build : () -> A }
|
||||||
```
|
```
|
||||||
|
|
||||||
then we do *not* have that `RWFactory<Dog>` is a subtype of `RWFactory<Animal>`
|
then we do *not* have that `RWFactory<Dog>` is a subtype of `RWFactory<Animal>`
|
||||||
since the build method is read-write, so users can update it:
|
since the build method is read-write, so users can update it:
|
||||||
```lua
|
```luau
|
||||||
local mkdog : RWFactory<Dog> = { build = Dog.new }
|
local mkdog : RWFactory<Dog> = { build = Dog.new }
|
||||||
local mkanimal : RWFactory<Animal> = mkdog -- Does not typecheck
|
local mkanimal : RWFactory<Animal> = mkdog -- Does not typecheck
|
||||||
mkanimal.build = Cat.new -- Assigning to methods is OK for RWFactory
|
mkanimal.build = Cat.new -- Assigning to methods is OK for RWFactory
|
||||||
|
@ -106,13 +106,13 @@ since the build method is read-write, so users can update it:
|
||||||
```
|
```
|
||||||
|
|
||||||
but if we define:
|
but if we define:
|
||||||
```lua
|
```luau
|
||||||
type ROFactory<A> = { get build : () -> A }
|
type ROFactory<A> = { get build : () -> A }
|
||||||
```
|
```
|
||||||
|
|
||||||
then we do have that `ROFactory<Dog>` is a subtype of `ROFactory<Animal>`
|
then we do have that `ROFactory<Dog>` is a subtype of `ROFactory<Animal>`
|
||||||
since the build method is read-write, so users can update it:
|
since the build method is read-write, so users can update it:
|
||||||
```lua
|
```luau
|
||||||
local mkdog : ROFactory<Dog> = { build = Dog.new }
|
local mkdog : ROFactory<Dog> = { build = Dog.new }
|
||||||
local mkanimal : ROFactory<Animal> = mkdog -- Typechecks now!
|
local mkanimal : ROFactory<Animal> = mkdog -- Typechecks now!
|
||||||
mkanimal.build = Cat.new -- Fails to typecheck, since build is read-only
|
mkanimal.build = Cat.new -- Fails to typecheck, since build is read-only
|
||||||
|
|
|
@ -16,12 +16,12 @@ that we can infer a most specific type for functions, which we can't do if
|
||||||
we only have read-write and read-only properties.
|
we only have read-write and read-only properties.
|
||||||
|
|
||||||
For example, consider the function
|
For example, consider the function
|
||||||
```lua
|
```luau
|
||||||
function f(t) t.p = Dog.new() end
|
function f(t) t.p = Dog.new() end
|
||||||
```
|
```
|
||||||
|
|
||||||
The obvious type for this is
|
The obvious type for this is
|
||||||
```lua
|
```luau
|
||||||
f : ({ p: Dog }) -> ()
|
f : ({ p: Dog }) -> ()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -31,13 +31,13 @@ These types are incomparable (neither is a subtype of the other)
|
||||||
and there are uses of `f` that fail to typecheck depending which one choose.
|
and there are uses of `f` that fail to typecheck depending which one choose.
|
||||||
|
|
||||||
If `f : ({ p: Dog }) -> ()` then
|
If `f : ({ p: Dog }) -> ()` then
|
||||||
```lua
|
```luau
|
||||||
local x : { p : Animal } = { p = Cat.new() }
|
local x : { p : Animal } = { p = Cat.new() }
|
||||||
f(x) -- Fails to typecheck
|
f(x) -- Fails to typecheck
|
||||||
```
|
```
|
||||||
|
|
||||||
If `f : ({ p: Animal }) -> ()` then
|
If `f : ({ p: Animal }) -> ()` then
|
||||||
```lua
|
```luau
|
||||||
local x : { p : Dog } = { p = Dog.new() }
|
local x : { p : Dog } = { p = Dog.new() }
|
||||||
f(x) -- Fails to typecheck
|
f(x) -- Fails to typecheck
|
||||||
```
|
```
|
||||||
|
@ -45,7 +45,7 @@ If `f : ({ p: Animal }) -> ()` then
|
||||||
The reason for these failures is that neither of these is the most
|
The reason for these failures is that neither of these is the most
|
||||||
specific type. It is one which includes that `t.p` is written to, and
|
specific type. It is one which includes that `t.p` is written to, and
|
||||||
not read from.
|
not read from.
|
||||||
```lua
|
```luau
|
||||||
f : ({ set p: Dog }) -> ()
|
f : ({ set p: Dog }) -> ()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ This proposal is not about syntax, but it will be useful for examples to have so
|
||||||
* `set p: T` for a write-only property of type `T`.
|
* `set p: T` for a write-only property of type `T`.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
```lua
|
```luau
|
||||||
function f(t)
|
function f(t)
|
||||||
t.p = 1 + t.q
|
t.p = 1 + t.q
|
||||||
end
|
end
|
||||||
|
@ -124,7 +124,7 @@ Indexers can be marked write-only just like properties. In
|
||||||
particular, this means there are write-only arrays `{set T}`, that are
|
particular, this means there are write-only arrays `{set T}`, that are
|
||||||
contravariant. These are sometimes useful, for example:
|
contravariant. These are sometimes useful, for example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function move(src, tgt)
|
function move(src, tgt)
|
||||||
for i,v in ipairs(src) do
|
for i,v in ipairs(src) do
|
||||||
tgt[i] = src[i]
|
tgt[i] = src[i]
|
||||||
|
@ -140,7 +140,7 @@ we can give this function the type
|
||||||
|
|
||||||
and since write-only arrays are contravariant, we can call this with differently-typed
|
and since write-only arrays are contravariant, we can call this with differently-typed
|
||||||
arrays:
|
arrays:
|
||||||
```lua
|
```luau
|
||||||
local dogs : {Dog} = {fido,rover}
|
local dogs : {Dog} = {fido,rover}
|
||||||
local animals : {Animal} = {tweety,sylvester}
|
local animals : {Animal} = {tweety,sylvester}
|
||||||
move (dogs,animals)
|
move (dogs,animals)
|
||||||
|
@ -159,7 +159,7 @@ Some Roblox APIs which manipulate callbacks are write-only for security reasons.
|
||||||
Once we have read-only properties and write-only properties, type intersection
|
Once we have read-only properties and write-only properties, type intersection
|
||||||
gives read-write properties with different types.
|
gives read-write properties with different types.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
{ get p: T } & { set p : U }
|
{ get p: T } & { set p : U }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,15 @@ Restrict generic type aliases to only be able to refer to the exact same instant
|
||||||
|
|
||||||
Luau supports recursive type aliases, but with an important restriction:
|
Luau supports recursive type aliases, but with an important restriction:
|
||||||
users can declare functions of recursive types, such as:
|
users can declare functions of recursive types, such as:
|
||||||
```lua
|
```luau
|
||||||
type Tree<a> = { data: a, children: {Tree<a>} }
|
type Tree<a> = { data: a, children: {Tree<a>} }
|
||||||
```
|
```
|
||||||
but *not* recursive type functions, such as:
|
but *not* recursive type functions, such as:
|
||||||
```lua
|
```luau
|
||||||
type Weird<a> = { data: a, children: Weird<{a}> }
|
type Weird<a> = { data: a, children: Weird<{a}> }
|
||||||
```
|
```
|
||||||
If types such as `Weird` were allowed, they would have infinite unfoldings for example:
|
If types such as `Weird` were allowed, they would have infinite unfoldings for example:
|
||||||
```lua
|
```luau
|
||||||
Weird<number> = { data: number, children: Weird<{number}> }`
|
Weird<number> = { data: number, children: Weird<{number}> }`
|
||||||
Weird<{number}> = { data: {number}, children: Weird<{{number}}> }
|
Weird<{number}> = { data: {number}, children: Weird<{{number}}> }
|
||||||
Weird<{{number}}> = { data: {{number}}, children: Weird<{{{number}}}> }
|
Weird<{{number}}> = { data: {{number}}, children: Weird<{{{number}}}> }
|
||||||
|
@ -36,11 +36,11 @@ recursive types, we require that in any recursive type alias defining `T<gs>`,
|
||||||
in any recursive use of `T<Us>`, we have that `gs` and `Us` are equal.
|
in any recursive use of `T<Us>`, we have that `gs` and `Us` are equal.
|
||||||
|
|
||||||
This allows types such as:
|
This allows types such as:
|
||||||
```lua
|
```luau
|
||||||
type Tree<a> = { data: a, children: {Tree<a>} }
|
type Tree<a> = { data: a, children: {Tree<a>} }
|
||||||
```
|
```
|
||||||
but *not*:
|
but *not*:
|
||||||
```lua
|
```luau
|
||||||
type Weird<a> = { data: a, children: Weird<{a}> }
|
type Weird<a> = { data: a, children: Weird<{a}> }
|
||||||
```
|
```
|
||||||
since in the recursive use `a` is not equal to `{a}`.
|
since in the recursive use `a` is not equal to `{a}`.
|
||||||
|
@ -51,7 +51,7 @@ This restriction applies to mutually recursive types too.
|
||||||
|
|
||||||
This restriction bans some type declarations which do not produce infinite unfoldings,
|
This restriction bans some type declarations which do not produce infinite unfoldings,
|
||||||
such as:
|
such as:
|
||||||
```lua
|
```luau
|
||||||
type WeirdButFinite<a> = { data: a, children: WeirdButFinite<number> }
|
type WeirdButFinite<a> = { data: a, children: WeirdButFinite<number> }
|
||||||
```
|
```
|
||||||
This restriction is stricter than TypeScript, which allows programs such as:
|
This restriction is stricter than TypeScript, which allows programs such as:
|
||||||
|
|
|
@ -24,13 +24,13 @@ Aliases can be used to bind an absolute or relative path to a convenient, case-i
|
||||||
|
|
||||||
Based on the alias map above, you would be able to require Roact directly with an `@` prefix:
|
Based on the alias map above, you would be able to require Roact directly with an `@` prefix:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local Roact = require("@Roact")
|
local Roact = require("@Roact")
|
||||||
```
|
```
|
||||||
|
|
||||||
Or even a sub-module:
|
Or even a sub-module:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local createElement = require("@Roact/createElement")
|
local createElement = require("@Roact/createElement")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ While package management itself is outside of the scope of this RFC, we want to
|
||||||
|
|
||||||
To require a Luau module under the current implementation, we must require it either by relative or absolute path:
|
To require a Luau module under the current implementation, we must require it either by relative or absolute path:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- Requiring Roact by absolute path
|
-- Requiring Roact by absolute path
|
||||||
local Roact = require("C:/LuauModules/Roact-v1.4.2")
|
local Roact = require("C:/LuauModules/Roact-v1.4.2")
|
||||||
```
|
```
|
||||||
|
@ -53,7 +53,7 @@ To solve this, we introduce path and alias configuration in this RFC, which woul
|
||||||
|
|
||||||
This would also create simple and readable require statements for developers.
|
This would also create simple and readable require statements for developers.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- Suppose "@src" is an alias for the same directory as "../../../../../"
|
-- Suppose "@src" is an alias for the same directory as "../../../../../"
|
||||||
|
|
||||||
-- Instead of this:
|
-- Instead of this:
|
||||||
|
@ -100,7 +100,7 @@ With the given `paths` definition (`.luaurc` file located in `/Users/johndoe/Pro
|
||||||
```
|
```
|
||||||
|
|
||||||
If `/Users/johndoe/Projects/MyProject/src/init.luau` contained the following code:
|
If `/Users/johndoe/Projects/MyProject/src/init.luau` contained the following code:
|
||||||
```lua
|
```luau
|
||||||
local graphing = require("graphing")
|
local graphing = require("graphing")
|
||||||
```
|
```
|
||||||
We would search the following directories, in order:
|
We would search the following directories, in order:
|
||||||
|
@ -130,7 +130,7 @@ If `.luaurc` contained the following `paths` array:
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, `module.luau` could simply require `dependency.luau` like this:
|
Then, `module.luau` could simply require `dependency.luau` like this:
|
||||||
```lua
|
```luau
|
||||||
local dependency = require("dependency")
|
local dependency = require("dependency")
|
||||||
|
|
||||||
-- Instead of: require("../dependencies/dependency")
|
-- Instead of: require("../dependencies/dependency")
|
||||||
|
@ -197,7 +197,7 @@ For example, if we wanted to require `Roact` in `module.luau`, we could add the
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, we could simply write the following in `module.luau`, and everything would work as intended:
|
Then, we could simply write the following in `module.luau`, and everything would work as intended:
|
||||||
```lua
|
```luau
|
||||||
local Roact = require("@Roact")
|
local Roact = require("@Roact")
|
||||||
local Component = require("@Roact/Component")
|
local Component = require("@Roact/Component")
|
||||||
```
|
```
|
||||||
|
@ -229,7 +229,7 @@ We can provide the following alias in `large-luau-project/.luaurc`:
|
||||||
This way, each subproject directory can contain its own source code, dependencies, and `.luaurc` configuration files, while also inheriting the `com.roblox.luau` alias from `large-luau-project/.luaurc`.
|
This way, each subproject directory can contain its own source code, dependencies, and `.luaurc` configuration files, while also inheriting the `com.roblox.luau` alias from `large-luau-project/.luaurc`.
|
||||||
|
|
||||||
This allows us to refer to other subprojects like this, regardless of the exact location of the requiring file in `large-luau-project`:
|
This allows us to refer to other subprojects like this, regardless of the exact location of the requiring file in `large-luau-project`:
|
||||||
```lua
|
```luau
|
||||||
local subproject1 = require("@com.roblox.luau/subproject-1")
|
local subproject1 = require("@com.roblox.luau/subproject-1")
|
||||||
```
|
```
|
||||||
### Roblox Specifics
|
### Roblox Specifics
|
||||||
|
@ -246,7 +246,7 @@ This alias system introduces a new layer to require that wasn't previously there
|
||||||
#### Defining paths/aliases directly in the requiring file
|
#### Defining paths/aliases directly in the requiring file
|
||||||
|
|
||||||
Rather than defining paths/alias maps in an external configuration file, we could alternatively define paths/aliases directly in the files that require them. For example, this could manifest itself through an extension of the `--!` comment syntax or introduce new syntax like `--@<ALIAS> = @<PATH>`.
|
Rather than defining paths/alias maps in an external configuration file, we could alternatively define paths/aliases directly in the files that require them. For example, this could manifest itself through an extension of the `--!` comment syntax or introduce new syntax like `--@<ALIAS> = @<PATH>`.
|
||||||
```lua
|
```luau
|
||||||
--@"Roact" = @"C:/LuauModules/Roact-v1.4.2"
|
--@"Roact" = @"C:/LuauModules/Roact-v1.4.2"
|
||||||
local Roact = require("@Roact")
|
local Roact = require("@Roact")
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ In Luau, tables have a state, which can, among others, be "sealed". A sealed tab
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
We would like this code to type check:
|
We would like this code to type check:
|
||||||
```lua
|
```luau
|
||||||
type Interface = {
|
type Interface = {
|
||||||
name: string,
|
name: string,
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ This change affects existing code, but it should be a strictly more permissive c
|
||||||
|
|
||||||
This change will mean that sealed tables that don't exactly match may be permitted. In the past, this was an error; users may be relying on the type checker to perform these checks. We think the risk of this is minimal, as the presence of extra properties is unlikely to break user code. This is an example of code that would have raised a type error before:
|
This change will mean that sealed tables that don't exactly match may be permitted. In the past, this was an error; users may be relying on the type checker to perform these checks. We think the risk of this is minimal, as the presence of extra properties is unlikely to break user code. This is an example of code that would have raised a type error before:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type A = {
|
type A = {
|
||||||
name: string,
|
name: string,
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ local a: A = {
|
||||||
|
|
||||||
In order to avoid any chance of breaking backwards-compatibility, we could introduce a new state for tables, "interface" or something similar, that can only be produced via new syntax. This state would act like a sealed table, except with the addition of the subtyping rule described in this RFC. An example syntax for this:
|
In order to avoid any chance of breaking backwards-compatibility, we could introduce a new state for tables, "interface" or something similar, that can only be produced via new syntax. This state would act like a sealed table, except with the addition of the subtyping rule described in this RFC. An example syntax for this:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- `interface` context-sensitive keyword denotes an interface table
|
-- `interface` context-sensitive keyword denotes an interface table
|
||||||
type A = interface {
|
type A = interface {
|
||||||
name: string,
|
name: string,
|
||||||
|
|
|
@ -15,7 +15,7 @@ exponential blowup in the size of the type graph.
|
||||||
|
|
||||||
For example, `Point` class can be simulated using metatables:
|
For example, `Point` class can be simulated using metatables:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
--!strict
|
--!strict
|
||||||
|
|
||||||
local Point = {}
|
local Point = {}
|
||||||
|
@ -50,7 +50,7 @@ Even worse, the method `Point.abs` does not type-check, since the type
|
||||||
of `self.x * self.x` is unknown. If Luau had subtyping constraints and type families
|
of `self.x * self.x` is unknown. If Luau had subtyping constraints and type families
|
||||||
for overloaded operators, the inferred type would be something like:
|
for overloaded operators, the inferred type would be something like:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type PointMT = {
|
type PointMT = {
|
||||||
new : () -> Point,
|
new : () -> Point,
|
||||||
getX : <a>({ x : a }) -> a,
|
getX : <a>({ x : a }) -> a,
|
||||||
|
@ -70,7 +70,7 @@ but this type is not great ergonomically, since this type may be presented to
|
||||||
users in type hover or type error messages, and will surprise users
|
users in type hover or type error messages, and will surprise users
|
||||||
expecting a simpler type such as:
|
expecting a simpler type such as:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type PointMT = {
|
type PointMT = {
|
||||||
new : () -> Point
|
new : () -> Point
|
||||||
getX : (Point) -> number,
|
getX : (Point) -> number,
|
||||||
|
@ -92,7 +92,7 @@ Unfortunately, while this change is fairly straightforward for
|
||||||
monomorphic types like `Point`, it is problematic for generic classes
|
monomorphic types like `Point`, it is problematic for generic classes
|
||||||
such as containers. For example:
|
such as containers. For example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local Set = {}
|
local Set = {}
|
||||||
Set.__index = Set
|
Set.__index = Set
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ end
|
||||||
In this case, the expected type would be something like:
|
In this case, the expected type would be something like:
|
||||||
|
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type SetMT = {
|
type SetMT = {
|
||||||
new : <E>() -> Set<E>,
|
new : <E>() -> Set<E>,
|
||||||
add : <E>(Set<E>, E) -> (),
|
add : <E>(Set<E>, E) -> (),
|
||||||
|
@ -130,7 +130,7 @@ Inferring this type is beyond the scope of this RFC, though. Initially, we propo
|
||||||
in this case:
|
in this case:
|
||||||
|
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type SetMT = {
|
type SetMT = {
|
||||||
new : () -> Set,
|
new : () -> Set,
|
||||||
add : (Set, unknown) -> (),
|
add : (Set, unknown) -> (),
|
||||||
|
@ -145,7 +145,7 @@ in this case:
|
||||||
and propose allowing explicit declaration of the shared self type,
|
and propose allowing explicit declaration of the shared self type,
|
||||||
following the common practice of naming the self type after the metatable:
|
following the common practice of naming the self type after the metatable:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Set<E> = { elements : { [E] : boolean } }
|
type Set<E> = { elements : { [E] : boolean } }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ This type (and its generic type parameters) are used to derive the
|
||||||
type of `self` in methods declared using `function Set:m()`
|
type of `self` in methods declared using `function Set:m()`
|
||||||
declarations:
|
declarations:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type SetSelf<E> = {
|
type SetSelf<E> = {
|
||||||
elements : { [E] : boolean },
|
elements : { [E] : boolean },
|
||||||
@metatable SetMT
|
@metatable SetMT
|
||||||
|
@ -164,13 +164,13 @@ In cases where shared self types are just getting in the way, there
|
||||||
are two work-arounds. Firstly, the shared self type can be declared to
|
are two work-arounds. Firstly, the shared self type can be declared to
|
||||||
be `any`, which will silence type errors:
|
be `any`, which will silence type errors:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Foo = any
|
type Foo = any
|
||||||
```
|
```
|
||||||
|
|
||||||
Secondly, the self type can be declared explicitly:
|
Secondly, the self type can be declared explicitly:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function Foo.m(self : Bar) ... end
|
function Foo.m(self : Bar) ... end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ For each table `t`, introduce:
|
||||||
|
|
||||||
These can be declared explicitly:
|
These can be declared explicitly:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type t<As> = U
|
type t<As> = U
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ which defines, when `t` has type `T`:
|
||||||
|
|
||||||
For example,
|
For example,
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Set<E> = { [E] : boolean }
|
type Set<E> = { [E] : boolean }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ all of their fields initialized *before* any methods are called. In
|
||||||
cases where methods are called before all the fields are initialized,
|
cases where methods are called before all the fields are initialized,
|
||||||
this will result in optional types being inferred. For example:
|
this will result in optional types being inferred. For example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function Point.new()
|
function Point.new()
|
||||||
local result = setmetatable({}, Point)
|
local result = setmetatable({}, Point)
|
||||||
result.x = 0
|
result.x = 0
|
||||||
|
@ -292,7 +292,7 @@ this will result in optional types being inferred. For example:
|
||||||
the call to `result:getY()` uses the *current* type state of `result`, which is `{ x : number, @metatable PointMT }`.
|
the call to `result:getY()` uses the *current* type state of `result`, which is `{ x : number, @metatable PointMT }`.
|
||||||
Unification will then cause `Point` to consider `y` to be optional:
|
Unification will then cause `Point` to consider `y` to be optional:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type PointMT = {
|
type PointMT = {
|
||||||
new : () -> Point
|
new : () -> Point
|
||||||
getX : (Point) -> number,
|
getX : (Point) -> number,
|
||||||
|
@ -310,7 +310,7 @@ Since `y` has type `number?` rather than `number`, the `abs` method will fail to
|
||||||
|
|
||||||
As a workaround, developers can declare different self types for different methods:
|
As a workaround, developers can declare different self types for different methods:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function Point.getX(self : { x : number }) : number
|
function Point.getX(self : { x : number }) : number
|
||||||
return self.x
|
return self.x
|
||||||
end
|
end
|
||||||
|
@ -321,7 +321,7 @@ As a workaround, developers can declare different self types for different metho
|
||||||
|
|
||||||
resulting in:
|
resulting in:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type PointMT = {
|
type PointMT = {
|
||||||
new : () -> Point
|
new : () -> Point
|
||||||
getX : ({ x : number }) -> number,
|
getX : ({ x : number }) -> number,
|
||||||
|
@ -347,7 +347,7 @@ result in inferring that both `x` and `y` are optional,
|
||||||
|
|
||||||
With the current greedy unifier, classes with constructors of different types will fail:
|
With the current greedy unifier, classes with constructors of different types will fail:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local Foo = {}
|
local Foo = {}
|
||||||
Foo.__index = Foo
|
Foo.__index = Foo
|
||||||
function Foo.from(x) return setmetatable({ msg = tostring(x) }, Foo) end
|
function Foo.from(x) return setmetatable({ msg = tostring(x) }, Foo) end
|
||||||
|
|
|
@ -35,7 +35,7 @@ attribute = '@' NAME
|
||||||
Attributes would attach to the function as a form of metadata to adjust the behavior of Luau, whether it be in the compiler, analyzer, or otherwise.
|
Attributes would attach to the function as a form of metadata to adjust the behavior of Luau, whether it be in the compiler, analyzer, or otherwise.
|
||||||
|
|
||||||
Attributes would be valid before function declarations, both anonymous and named:
|
Attributes would be valid before function declarations, both anonymous and named:
|
||||||
```lua
|
```luau
|
||||||
@example
|
@example
|
||||||
function foo()
|
function foo()
|
||||||
end
|
end
|
||||||
|
@ -46,7 +46,7 @@ foo = @example function() end
|
||||||
Whether the name of a function is a local variable would have no impact on the use of attributes.
|
Whether the name of a function is a local variable would have no impact on the use of attributes.
|
||||||
|
|
||||||
When declaring a function as a member of a table, any attributes should be for the function:
|
When declaring a function as a member of a table, any attributes should be for the function:
|
||||||
```lua
|
```luau
|
||||||
@example -- @example applies to `bar`, not `foo`
|
@example -- @example applies to `bar`, not `foo`
|
||||||
function foo:bar()
|
function foo:bar()
|
||||||
end
|
end
|
||||||
|
@ -55,7 +55,7 @@ end
|
||||||
This is consistent with other uses, as it applies the attribute to what is being declared.
|
This is consistent with other uses, as it applies the attribute to what is being declared.
|
||||||
|
|
||||||
Multiple attributes are supported inline, so that a function with multiple attributes does not have several lines of boilerplate:
|
Multiple attributes are supported inline, so that a function with multiple attributes does not have several lines of boilerplate:
|
||||||
```lua
|
```luau
|
||||||
@attribute1 @attribute2 @attribute3 @attribute4 @attribute5
|
@attribute1 @attribute2 @attribute3 @attribute4 @attribute5
|
||||||
local function example()
|
local function example()
|
||||||
end
|
end
|
||||||
|
@ -88,7 +88,7 @@ Other potential syntaxes that were considered include a Rust-style syntax (`#[at
|
||||||
- Comments being used to control language features and flow means that tooling and users must care about them, which is antithetical to how comments are traditionally used
|
- Comments being used to control language features and flow means that tooling and users must care about them, which is antithetical to how comments are traditionally used
|
||||||
|
|
||||||
- Attribute-as-comments would naturally conflict with the language in a lot of weird ways and would just cause a lot of problems:
|
- Attribute-as-comments would naturally conflict with the language in a lot of weird ways and would just cause a lot of problems:
|
||||||
```lua
|
```luau
|
||||||
local f = --!native function() end
|
local f = --!native function() end
|
||||||
print(f)
|
print(f)
|
||||||
```
|
```
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
|
|
||||||
A feature present in many many programming languages is assignment operators that perform operations on the left hand side, for example
|
A feature present in many many programming languages is assignment operators that perform operations on the left hand side, for example
|
||||||
|
|
||||||
```
|
```luau
|
||||||
a += b
|
a += b
|
||||||
```
|
```
|
||||||
|
|
||||||
Lua doesn't provide this right now, so it requires code that's more verbose, for example
|
Lua doesn't provide this right now, so it requires code that's more verbose, for example
|
||||||
|
|
||||||
```
|
```luau
|
||||||
data[index].cost = data[index].cost + 1
|
data[index].cost = data[index].cost + 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -30,13 +30,13 @@ The semantics of the operators is going to be as follows:
|
||||||
|
|
||||||
Crucially, this proposal does *not* introduce new metamethods, and instead uses the existing metamethods and table access semantics, for example
|
Crucially, this proposal does *not* introduce new metamethods, and instead uses the existing metamethods and table access semantics, for example
|
||||||
|
|
||||||
```
|
```luau
|
||||||
data[index].cost += 1
|
data[index].cost += 1
|
||||||
```
|
```
|
||||||
|
|
||||||
translates to
|
translates to
|
||||||
|
|
||||||
```
|
```luau
|
||||||
local table = data[index]
|
local table = data[index]
|
||||||
local key = "cost"
|
local key = "cost"
|
||||||
table[key] = table[key] + 1
|
table[key] = table[key] + 1
|
||||||
|
|
|
@ -24,7 +24,7 @@ These rules effectively say that continue statement is the statement that *does
|
||||||
|
|
||||||
This is a continue statement:
|
This is a continue statement:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
do
|
do
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
@ -32,7 +32,7 @@ end
|
||||||
|
|
||||||
This is not a continue statement:
|
This is not a continue statement:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
do
|
do
|
||||||
continue = 5
|
continue = 5
|
||||||
end
|
end
|
||||||
|
@ -40,7 +40,7 @@ end
|
||||||
|
|
||||||
This is not a continue statement:
|
This is not a continue statement:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
do
|
do
|
||||||
continue(5)
|
continue(5)
|
||||||
end
|
end
|
||||||
|
@ -48,7 +48,7 @@ end
|
||||||
|
|
||||||
This is not a continue statement either, why do you ask?
|
This is not a continue statement either, why do you ask?
|
||||||
|
|
||||||
```
|
```luau
|
||||||
do
|
do
|
||||||
continue, foo = table.unpack(...)
|
continue, foo = table.unpack(...)
|
||||||
end
|
end
|
||||||
|
@ -62,7 +62,7 @@ Alternatively in this specific case we could parse "continue", parse the next to
|
||||||
|
|
||||||
The rules make it so that the only time we interpret `continue` as a continuation statement is when in the old Lua the program would not have compiled correctly - because this is not valid Lua 5.x:
|
The rules make it so that the only time we interpret `continue` as a continuation statement is when in the old Lua the program would not have compiled correctly - because this is not valid Lua 5.x:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
do
|
do
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
@ -70,7 +70,7 @@ end
|
||||||
|
|
||||||
There is one case where this can create new confusion in the newly written code - code like this:
|
There is one case where this can create new confusion in the newly written code - code like this:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
do
|
do
|
||||||
continue
|
continue
|
||||||
(foo())(5)
|
(foo())(5)
|
||||||
|
|
|
@ -12,7 +12,7 @@ Luau has support for type parameters for type aliases and functions.
|
||||||
In languages with similar features like C++, Rust, Flow and TypeScript, it is possible to specify default values for looser coupling and easier composability, and users with experience in those languages would like to have these design capabilities in Luau.
|
In languages with similar features like C++, Rust, Flow and TypeScript, it is possible to specify default values for looser coupling and easier composability, and users with experience in those languages would like to have these design capabilities in Luau.
|
||||||
|
|
||||||
Here is an example that is coming up frequently during development of GraphQL Luau library:
|
Here is an example that is coming up frequently during development of GraphQL Luau library:
|
||||||
```lua
|
```luau
|
||||||
export type GraphQLFieldResolver<
|
export type GraphQLFieldResolver<
|
||||||
TSource,
|
TSource,
|
||||||
TContext,
|
TContext,
|
||||||
|
@ -25,7 +25,7 @@ Some engineers already skip these extra arguments and use `'any'` to save time,
|
||||||
Without default parameter values it's also harder to refactor the code as each type alias reference that uses 'common' type arguments has to be updated.
|
Without default parameter values it's also harder to refactor the code as each type alias reference that uses 'common' type arguments has to be updated.
|
||||||
|
|
||||||
While previous example uses a concrete type for default type value, it should also be possible to reference generic types from the same list:
|
While previous example uses a concrete type for default type value, it should also be possible to reference generic types from the same list:
|
||||||
```lua
|
```luau
|
||||||
type Eq<T, U = T> = (l: T, r: U) -> boolean
|
type Eq<T, U = T> = (l: T, r: U) -> boolean
|
||||||
|
|
||||||
local a: Eq<number> = ...
|
local a: Eq<number> = ...
|
||||||
|
@ -37,19 +37,19 @@ Generic functions in Luau also have a type parameter list, but it's not possible
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||
If a default type parameter value is assigned, following type parameters (on the right) must also have default type parameter values.
|
If a default type parameter value is assigned, following type parameters (on the right) must also have default type parameter values.
|
||||||
```lua
|
```luau
|
||||||
type A<T, U = string, V> = ... -- not allowed
|
type A<T, U = string, V> = ... -- not allowed
|
||||||
```
|
```
|
||||||
|
|
||||||
Default type parameter values can reference type parameters which were defined earlier (to the left):
|
Default type parameter values can reference type parameters which were defined earlier (to the left):
|
||||||
```lua
|
```luau
|
||||||
type A<T, U = T> = ...-- ok
|
type A<T, U = T> = ...-- ok
|
||||||
|
|
||||||
type A<T, U = V, V = T> = ... -- not allowed
|
type A<T, U = V, V = T> = ... -- not allowed
|
||||||
```
|
```
|
||||||
|
|
||||||
Default type parameter values are also allowed for type packs:
|
Default type parameter values are also allowed for type packs:
|
||||||
```lua
|
```luau
|
||||||
type A<T, U... = ...string> -- ok, variadic type pack
|
type A<T, U... = ...string> -- ok, variadic type pack
|
||||||
type B<T, U... = ()> -- ok, type pack with no elements
|
type B<T, U... = ()> -- ok, type pack with no elements
|
||||||
type C<T, U... = (string)> -- ok, type pack with one element
|
type C<T, U... = (string)> -- ok, type pack with one element
|
||||||
|
@ -69,7 +69,7 @@ Instead of storing a simple array of names in AstStatTypeAlias, we will store an
|
||||||
When type alias is referenced, missing type parameters are replaced with default type values, if they are available.
|
When type alias is referenced, missing type parameters are replaced with default type values, if they are available.
|
||||||
|
|
||||||
If all type parameters have a default type value, it is now possible to reference that without providing a type parameter list:
|
If all type parameters have a default type value, it is now possible to reference that without providing a type parameter list:
|
||||||
```lua
|
```luau
|
||||||
type All<T = string, U = number> = ...
|
type All<T = string, U = number> = ...
|
||||||
|
|
||||||
local a: All -- ok
|
local a: All -- ok
|
||||||
|
@ -88,7 +88,7 @@ Type annotation with `':'` could be used in the future for bounded quantificatio
|
||||||
Other languages might allow references to the type alias without arguments inside the scope of that type alias to resolve into a recursive reference to the type alias with the same arguments.
|
Other languages might allow references to the type alias without arguments inside the scope of that type alias to resolve into a recursive reference to the type alias with the same arguments.
|
||||||
|
|
||||||
While that is not allowed in Luau right now, if we decide to change that in the future, we will have an ambiguity when all type alias parameters have default values:
|
While that is not allowed in Luau right now, if we decide to change that in the future, we will have an ambiguity when all type alias parameters have default values:
|
||||||
```lua
|
```luau
|
||||||
-- ok if we allow Type to mean Type<A, B>
|
-- ok if we allow Type to mean Type<A, B>
|
||||||
type Type<A, B> = { x: number, b: Type? }
|
type Type<A, B> = { x: number, b: Type? }
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ The typechecker does not need special handling for the new operators. It can sim
|
||||||
|
|
||||||
Examples of usage:
|
Examples of usage:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
-- Convert offset into 2d indices
|
-- Convert offset into 2d indices
|
||||||
local i, j = offset % 5, offset // 5
|
local i, j = offset % 5, offset // 5
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ The result of the expression is the then-expression when condition is truthy (no
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local x = if FFlagFoo then A else B
|
local x = if FFlagFoo then A else B
|
||||||
|
|
||||||
MyComponent.validateProps = t.strictInterface({
|
MyComponent.validateProps = t.strictInterface({
|
||||||
|
@ -39,7 +39,7 @@ Note that `else` is mandatory because it's always better to be explicit. If it w
|
||||||
|
|
||||||
This example will not do what it looks like it's supposed to do! The if expression will _successfully_ parse and be interpreted as to return `h()` if `g()` evaluates to some falsy value, when in actual fact the clear intention is to evaluate `h()` only if `f()` is falsy.
|
This example will not do what it looks like it's supposed to do! The if expression will _successfully_ parse and be interpreted as to return `h()` if `g()` evaluates to some falsy value, when in actual fact the clear intention is to evaluate `h()` only if `f()` is falsy.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
if f() then
|
if f() then
|
||||||
...
|
...
|
||||||
local foo = if g() then x
|
local foo = if g() then x
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
Introduce a new syntax for unpacking key values into their own variables, such that:
|
Introduce a new syntax for unpacking key values into their own variables, such that:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local { .a, .b } = t
|
local { .a, .b } = t
|
||||||
-- a == t.a
|
-- a == t.a
|
||||||
-- b == t.b
|
-- b == t.b
|
||||||
|
@ -21,7 +21,7 @@ const { useState, useEffect } = require("react");
|
||||||
|
|
||||||
...which allows you to quickly use `useState` and `useEffect` without fully qualifying it in the form of `React.useState` and `React.useEffect`. In Luau, if you do not want to fully qualify common React functions, the top of your file will often look like:
|
...which allows you to quickly use `useState` and `useEffect` without fully qualifying it in the form of `React.useState` and `React.useEffect`. In Luau, if you do not want to fully qualify common React functions, the top of your file will often look like:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local useEffect = React.useEffect
|
local useEffect = React.useEffect
|
||||||
local useMemo = React.useMemo
|
local useMemo = React.useMemo
|
||||||
local useState = React.useState
|
local useState = React.useState
|
||||||
|
@ -32,7 +32,7 @@ local useState = React.useState
|
||||||
|
|
||||||
It is also common to want to have short identifiers to React properties, which basically always map onto a variable of the same name. As an anecdote, a regex search of `^\s+local (\w+) = \w+\.\1$` comes up 103 times in the My Movie codebase, many in the form of indexing React properties:
|
It is also common to want to have short identifiers to React properties, which basically always map onto a variable of the same name. As an anecdote, a regex search of `^\s+local (\w+) = \w+\.\1$` comes up 103 times in the My Movie codebase, many in the form of indexing React properties:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local position = props.position
|
local position = props.position
|
||||||
local style = props.style
|
local style = props.style
|
||||||
-- etc...
|
-- etc...
|
||||||
|
@ -71,7 +71,7 @@ binding = NAME [':' Type]
|
||||||
|
|
||||||
This would allow for the following:
|
This would allow for the following:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local { .a, .b }, c = t
|
local { .a, .b }, c = t
|
||||||
|
|
||||||
for _, { .a, .b } in ts do
|
for _, { .a, .b } in ts do
|
||||||
|
@ -80,7 +80,7 @@ end
|
||||||
|
|
||||||
In all of these cases, `.x` is an index of the table it corresponds to, assigned to a local variable of that name. For example, `local { .a, .b } = t` is syntax sugar for:
|
In all of these cases, `.x` is an index of the table it corresponds to, assigned to a local variable of that name. For example, `local { .a, .b } = t` is syntax sugar for:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local a = t.a
|
local a = t.a
|
||||||
local b = t.b
|
local b = t.b
|
||||||
```
|
```
|
||||||
|
@ -88,7 +88,7 @@ local b = t.b
|
||||||
This will have the same semantics with regards to `__index`, in the order of the variables being assigned. Furthermore, if `t` for whatever reason cannot be indexed (not a table, nil), you will get the same errors as you would if you were writing out `t.a` by hand.
|
This will have the same semantics with regards to `__index`, in the order of the variables being assigned. Furthermore, if `t` for whatever reason cannot be indexed (not a table, nil), you will get the same errors as you would if you were writing out `t.a` by hand.
|
||||||
|
|
||||||
Trying to use object destructuring in a local assignment without a corresponding assignment, such as...
|
Trying to use object destructuring in a local assignment without a corresponding assignment, such as...
|
||||||
```lua
|
```luau
|
||||||
local { .x, .y }
|
local { .x, .y }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -97,14 +97,14 @@ local { .x, .y }
|
||||||
#### Function arguments
|
#### Function arguments
|
||||||
Functions use `parlist`, which eventually uses `binding`. However, attempting to use key destructuring in a function body is not supported.
|
Functions use `parlist`, which eventually uses `binding`. However, attempting to use key destructuring in a function body is not supported.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- NOT supported
|
-- NOT supported
|
||||||
local function f({ .x, .y })
|
local function f({ .x, .y })
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Types
|
#### Types
|
||||||
An optional type can be supplied, such as:
|
An optional type can be supplied, such as:
|
||||||
```lua
|
```luau
|
||||||
local { .a: string, .b: number } = t
|
local { .a: string, .b: number } = t
|
||||||
|
|
||||||
-- Equivalent to...
|
-- Equivalent to...
|
||||||
|
@ -113,20 +113,20 @@ local b: number = t.b
|
||||||
```
|
```
|
||||||
|
|
||||||
Without explicit types, local assignments and for loops will assume the type of `<rhs>.<field>`. For example...
|
Without explicit types, local assignments and for loops will assume the type of `<rhs>.<field>`. For example...
|
||||||
```lua
|
```luau
|
||||||
-- x and y will both be typed `number` here
|
-- x and y will both be typed `number` here
|
||||||
local { .x, .y } = position :: { x: number, y: number }
|
local { .x, .y } = position :: { x: number, y: number }
|
||||||
```
|
```
|
||||||
|
|
||||||
Additionally, you can specify a type on the "table" as a whole.
|
Additionally, you can specify a type on the "table" as a whole.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local { .x, .y }: Position = p
|
local { .x, .y }: Position = p
|
||||||
```
|
```
|
||||||
|
|
||||||
Combining both is acceptable, in which case the type on the variable takes priority:
|
Combining both is acceptable, in which case the type on the variable takes priority:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- `x` will be `number`, `y` will be the type of `T.y`
|
-- `x` will be `number`, `y` will be the type of `T.y`
|
||||||
local { .x: number, .y }: T = p
|
local { .x: number, .y }: T = p
|
||||||
```
|
```
|
||||||
|
@ -135,14 +135,14 @@ local { .x: number, .y }: T = p
|
||||||
|
|
||||||
This proposal allows for renaming the assignments using `as`.
|
This proposal allows for renaming the assignments using `as`.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local { .real as aliased } = t
|
local { .real as aliased } = t
|
||||||
-- is equivalent to...
|
-- is equivalent to...
|
||||||
local aliased = t.real
|
local aliased = t.real
|
||||||
```
|
```
|
||||||
|
|
||||||
This helps support multiple assignments on the same name:
|
This helps support multiple assignments on the same name:
|
||||||
```lua
|
```luau
|
||||||
local { .name as nameA } = getObject(a)
|
local { .name as nameA } = getObject(a)
|
||||||
local { .name as nameB } = getObject(b)
|
local { .name as nameB } = getObject(b)
|
||||||
```
|
```
|
||||||
|
@ -153,13 +153,13 @@ This RFC does not concern itself with array destructuring. This is in part becau
|
||||||
#### Reassignments
|
#### Reassignments
|
||||||
We do not support key destructuring in reassignments, for example...
|
We do not support key destructuring in reassignments, for example...
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
{ .x, .y } = position
|
{ .x, .y } = position
|
||||||
```
|
```
|
||||||
|
|
||||||
This is to avoid ambiguity with potential table calls:
|
This is to avoid ambiguity with potential table calls:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local a = b
|
local a = b
|
||||||
{ .x, .y } = c
|
{ .x, .y } = c
|
||||||
```
|
```
|
||||||
|
@ -178,7 +178,7 @@ This also blocks nested destructuring, such as JavaScript's `const { a: { b } }
|
||||||
### Roblox - Property casing
|
### Roblox - Property casing
|
||||||
Today in Roblox, every index doubly works with camel case, such as `part.position` being equivalent to `part.Position`. This use is considered deprecated and frowned upon. However, even with variable renaming, this becomes significantly more appealing. For example, it is common you will only want a few pieces of information from a `RaycastResult`, so you might be tempted to write:
|
Today in Roblox, every index doubly works with camel case, such as `part.position` being equivalent to `part.Position`. This use is considered deprecated and frowned upon. However, even with variable renaming, this becomes significantly more appealing. For example, it is common you will only want a few pieces of information from a `RaycastResult`, so you might be tempted to write:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local { .position } = Workspace:Raycast(etc)
|
local { .position } = Workspace:Raycast(etc)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ An intuitive suggestion is `local { a, b } = t`, but this syntax fails this test
|
||||||
|
|
||||||
If we simply replace `as` with `=`, though it would parse, it does not read like any other assignment in Luau.
|
If we simply replace `as` with `=`, though it would parse, it does not read like any other assignment in Luau.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local { .x = y } = t
|
local { .x = y } = t
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ In Luau, variable assignments follow "name = assignment", whereas this flips it
|
||||||
|
|
||||||
If we flip it on the left...
|
If we flip it on the left...
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local { y = .x } = t
|
local { y = .x } = t
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,13 @@ Allow the use of `|` and `&` without any types preceding them.
|
||||||
|
|
||||||
Occasionally, you will have many different types in a union or intersection type that exceeds a reasonable line limit and end up formatting it to avoid horizontal scrolling. Using the English alphabet as an example:
|
Occasionally, you will have many different types in a union or intersection type that exceeds a reasonable line limit and end up formatting it to avoid horizontal scrolling. Using the English alphabet as an example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type EnglishAlphabet = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
|
type EnglishAlphabet = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
|
||||||
```
|
```
|
||||||
|
|
||||||
Or you might just format it for readability:
|
Or you might just format it for readability:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type EnglishAlphabet = never
|
type EnglishAlphabet = never
|
||||||
| "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
|
| "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
|
||||||
| "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
|
| "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
|
||||||
|
@ -27,7 +27,7 @@ Currently, there are two solutions to effect it:
|
||||||
1. Moving `=` to the next line
|
1. Moving `=` to the next line
|
||||||
2. Keep `=` on the line and add `never` if using `|`, or `unknown` if using `&`
|
2. Keep `=` on the line and add `never` if using `|`, or `unknown` if using `&`
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
-- 1) union:
|
-- 1) union:
|
||||||
type Result<T, E>
|
type Result<T, E>
|
||||||
= { tag: "ok", value: T }
|
= { tag: "ok", value: T }
|
||||||
|
@ -67,7 +67,7 @@ type Tree<T> =
|
||||||
|
|
||||||
This type becomes valid Luau syntax.
|
This type becomes valid Luau syntax.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Tree<T> =
|
type Tree<T> =
|
||||||
| { type: "leaf" }
|
| { type: "leaf" }
|
||||||
| { type: "node", left: Tree<T>, value: T, right: Tree<T> }
|
| { type: "node", left: Tree<T>, value: T, right: Tree<T> }
|
||||||
|
|
|
@ -17,12 +17,12 @@ This proposal uses the same syntax that functions use to name the arguments: `(a
|
||||||
Names can be provided in any place where function type is used, for example:
|
Names can be provided in any place where function type is used, for example:
|
||||||
|
|
||||||
* in type aliases:
|
* in type aliases:
|
||||||
```
|
```luau
|
||||||
type MyFunc = (cost: number, name: string) -> string
|
type MyFunc = (cost: number, name: string) -> string
|
||||||
```
|
```
|
||||||
|
|
||||||
* in definition files for table types:
|
* in definition files for table types:
|
||||||
```
|
```luau
|
||||||
declare string: {
|
declare string: {
|
||||||
rep: (pattern: string, repeats: number) -> string,
|
rep: (pattern: string, repeats: number) -> string,
|
||||||
sub: (string, start: number, end: number?) -> string -- names are optional, here the first argument doesn't use a name
|
sub: (string, start: number, end: number?) -> string -- names are optional, here the first argument doesn't use a name
|
||||||
|
@ -30,7 +30,7 @@ declare string: {
|
||||||
```
|
```
|
||||||
|
|
||||||
* for variables:
|
* for variables:
|
||||||
```
|
```luau
|
||||||
local cb: (amount: number) -> number
|
local cb: (amount: number) -> number
|
||||||
local function foo(cb: (name: string) -> ())
|
local function foo(cb: (name: string) -> ())
|
||||||
```
|
```
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the semantic RFCs for motivation:
|
||||||
|
|
||||||
We will use the following syntax for describing a read or a write type of a property:
|
We will use the following syntax for describing a read or a write type of a property:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type ReadOnly = { read x: number }
|
type ReadOnly = { read x: number }
|
||||||
type WriteOnly = { write x: number }
|
type WriteOnly = { write x: number }
|
||||||
```
|
```
|
||||||
|
@ -23,7 +23,7 @@ type WriteOnly = { write x: number }
|
||||||
A property will occasionally be both readable and writable, but using different
|
A property will occasionally be both readable and writable, but using different
|
||||||
types. The author will have to duplicate the property name in this case:
|
types. The author will have to duplicate the property name in this case:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Foo = {
|
type Foo = {
|
||||||
read p: Animal,
|
read p: Animal,
|
||||||
write p: Dog
|
write p: Dog
|
||||||
|
@ -32,14 +32,14 @@ type Foo = {
|
||||||
|
|
||||||
The tokens `read` and `write` are contextual. They are still valid property names.
|
The tokens `read` and `write` are contextual. They are still valid property names.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Reader = { read: () -> number }
|
type Reader = { read: () -> number }
|
||||||
type Writer = { write: (number) -> () }
|
type Writer = { write: (number) -> () }
|
||||||
```
|
```
|
||||||
|
|
||||||
Indexers can also be read-only or write-only.
|
Indexers can also be read-only or write-only.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type ReadOnlyMap<K, V> = { read [K]: V }
|
type ReadOnlyMap<K, V> = { read [K]: V }
|
||||||
type WriteOnlyMap<K, V> = { write [K]: V }
|
type WriteOnlyMap<K, V> = { write [K]: V }
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ type WriteDogs = { write Dog }
|
||||||
|
|
||||||
Mixed indexers are allowed but heavily discouraged:
|
Mixed indexers are allowed but heavily discouraged:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type MixedMap = { read [string]: Animal, write [string]: Dog }
|
type MixedMap = { read [string]: Animal, write [string]: Dog }
|
||||||
type MixedArray = { read Animal, write Dog }
|
type MixedArray = { read Animal, write Dog }
|
||||||
```
|
```
|
||||||
|
@ -57,7 +57,7 @@ type MixedArray = { read Animal, write Dog }
|
||||||
Redundant record fields are still disallowed: Each field may have at most one
|
Redundant record fields are still disallowed: Each field may have at most one
|
||||||
read type and one write type:
|
read type and one write type:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type A = { read x: string, write x: "Hello" } -- OK
|
type A = { read x: string, write x: "Hello" } -- OK
|
||||||
type C = { read x: string, read x: "hello" } -- ERROR
|
type C = { read x: string, read x: "hello" } -- ERROR
|
||||||
type B = { x: string, read x: "hello" } -- ERROR
|
type B = { x: string, read x: "hello" } -- ERROR
|
||||||
|
@ -66,7 +66,7 @@ type B = { x: string, read x: "hello" } -- ERROR
|
||||||
We place no restriction on the relationship between the read and write type.
|
We place no restriction on the relationship between the read and write type.
|
||||||
The following is certainly a bad idea, but it is legal:
|
The following is certainly a bad idea, but it is legal:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type T = { read n: number, write n: string }
|
type T = { read n: number, write n: string }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ use case.
|
||||||
`read` and `write` are also very useful method names. It's a little bit
|
`read` and `write` are also very useful method names. It's a little bit
|
||||||
awkward to talk about a table that has a `read` or a `write` method:
|
awkward to talk about a table that has a `read` or a `write` method:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Reader = { read read: () -> number }
|
type Reader = { read read: () -> number }
|
||||||
type Writer = { read write: (number) -> () }
|
type Writer = { read write: (number) -> () }
|
||||||
```
|
```
|
||||||
|
@ -115,7 +115,7 @@ backward compatbility, they cannot be made keywords. This presents
|
||||||
issues with code that uses the chosen names as type or property names,
|
issues with code that uses the chosen names as type or property names,
|
||||||
for instance:
|
for instance:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type set = { [any] : bool }
|
type set = { [any] : bool }
|
||||||
type ugh = { get set : set }
|
type ugh = { get set : set }
|
||||||
```
|
```
|
||||||
|
@ -141,7 +141,7 @@ Luau syntax.
|
||||||
|
|
||||||
For attributes, the position is given by the syntax of attributes, for example:
|
For attributes, the position is given by the syntax of attributes, for example:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Vector2 = { @read x: number, @read y : Number }
|
type Vector2 = { @read x: number, @read y : Number }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ For the other proposals, there are four possibilities, depending on whether the
|
||||||
modifier is west-coast or east-coast, and whether it modifies the propertry name
|
modifier is west-coast or east-coast, and whether it modifies the propertry name
|
||||||
or the type:
|
or the type:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Vector2 = { read x : number, read y : number }
|
type Vector2 = { read x : number, read y : number }
|
||||||
type Vector2 = { x read : number, y read : number }
|
type Vector2 = { x read : number, y read : number }
|
||||||
type Vector2 = { x : read number, y : read number }
|
type Vector2 = { x : read number, y : read number }
|
||||||
|
@ -160,7 +160,7 @@ The east-coast options are not easy-to-read with names, but are
|
||||||
easier with symbols, especially since `T?` is already postfix, for
|
easier with symbols, especially since `T?` is already postfix, for
|
||||||
example
|
example
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type Foo = { p: number?+ }
|
type Foo = { p: number?+ }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ One corner case is that type inference may deduce different read- and
|
||||||
write-types, which need to be presented to the user. For example the
|
write-types, which need to be presented to the user. For example the
|
||||||
read-type of `x` is `Animal` but its write-type is `Dog` in the principal type of:
|
read-type of `x` is `Animal` but its write-type is `Dog` in the principal type of:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function f(x)
|
function f(x)
|
||||||
let a: Animal = x.pet
|
let a: Animal = x.pet
|
||||||
x.pet = Dog.new()
|
x.pet = Dog.new()
|
||||||
|
@ -180,16 +180,16 @@ read-type of `x` is `Animal` but its write-type is `Dog` in the principal type o
|
||||||
|
|
||||||
If we are adding the modifier to the property name, we can repeat the name, for example
|
If we are adding the modifier to the property name, we can repeat the name, for example
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
x : { read pet : Animal, write pet : Dog }
|
x : { read pet : Animal, write pet : Dog }
|
||||||
```
|
```
|
||||||
|
|
||||||
If we are adding the modifier to the property type, we can give both types, for example:
|
If we are adding the modifier to the property type, we can give both types, for example:
|
||||||
```lua
|
```luau
|
||||||
x : { pet : read Animal + write Dog }
|
x : { pet : read Animal + write Dog }
|
||||||
```
|
```
|
||||||
|
|
||||||
This syntax plays well with symbols for modifiers, for example
|
This syntax plays well with symbols for modifiers, for example
|
||||||
```lua
|
```luau
|
||||||
x : { pet : +Animal -Dog }
|
x : { pet : +Animal -Dog }
|
||||||
```
|
```
|
||||||
|
|
|
@ -53,7 +53,7 @@ type TrueOrNil = true?
|
||||||
Adding constant strings as type means that it is now legal to write
|
Adding constant strings as type means that it is now legal to write
|
||||||
`{["foo"]:T}` as a table type. This should be parsed as a property,
|
`{["foo"]:T}` as a table type. This should be parsed as a property,
|
||||||
not an indexer. For example:
|
not an indexer. For example:
|
||||||
```lua
|
```luau
|
||||||
type T = {
|
type T = {
|
||||||
["foo"]: number,
|
["foo"]: number,
|
||||||
["$$bar"]: string,
|
["$$bar"]: string,
|
||||||
|
@ -66,7 +66,7 @@ The table type `T` is a table with three properties and no indexer.
|
||||||
|
|
||||||
You are allowed to provide a constant value to the generic primitive type.
|
You are allowed to provide a constant value to the generic primitive type.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local foo: "Hello world" = "Hello world"
|
local foo: "Hello world" = "Hello world"
|
||||||
local bar: string = foo -- allowed
|
local bar: string = foo -- allowed
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ local bar: boolean = foo -- also allowed
|
||||||
|
|
||||||
The inverse is not true, because you're trying to narrow any values to a specific value.
|
The inverse is not true, because you're trying to narrow any values to a specific value.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local foo: string = "Hello world"
|
local foo: string = "Hello world"
|
||||||
local bar: "Hello world" = foo -- not allowed
|
local bar: "Hello world" = foo -- not allowed
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ print`Hello {name}`
|
||||||
{% raw %}
|
{% raw %}
|
||||||
The restriction on `{{` exists solely for the people coming from languages e.g. C#, Rust, or Python which uses `{{` to escape and get the character `{` at runtime. We're also rejecting this at parse time too, since the proper way to escape it is `\{`, so:
|
The restriction on `{{` exists solely for the people coming from languages e.g. C#, Rust, or Python which uses `{{` to escape and get the character `{` at runtime. We're also rejecting this at parse time too, since the proper way to escape it is `\{`, so:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
print(`{{1, 2, 3}} = {myCoolSet}`) -- parse error
|
print(`{{1, 2, 3}} = {myCoolSet}`) -- parse error
|
||||||
```
|
```
|
||||||
{% endraw %}
|
{% endraw %}
|
||||||
|
@ -116,14 +116,14 @@ If we did not apply this as a parse error, then the above would wind up printing
|
||||||
|
|
||||||
Since the string interpolation expression is going to be lowered into a `string.format` call, we'll also need to extend `string.format`. The bare minimum to support the lowering is to add a new token whose definition is to perform a `tostring` call. `%*` is currently an invalid token, so this is a backward compatible extension. This RFC shall define `%*` to have the same behavior as if `tostring` was called.
|
Since the string interpolation expression is going to be lowered into a `string.format` call, we'll also need to extend `string.format`. The bare minimum to support the lowering is to add a new token whose definition is to perform a `tostring` call. `%*` is currently an invalid token, so this is a backward compatible extension. This RFC shall define `%*` to have the same behavior as if `tostring` was called.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
print(string.format("%* %*", 1, 2))
|
print(string.format("%* %*", 1, 2))
|
||||||
--> 1 2
|
--> 1 2
|
||||||
```
|
```
|
||||||
|
|
||||||
The offset must always be within bound of the numbers of values passed to `string.format`.
|
The offset must always be within bound of the numbers of values passed to `string.format`.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local function return_one_thing() return "hi" end
|
local function return_one_thing() return "hi" end
|
||||||
local function return_two_nils() return nil, nil end
|
local function return_two_nils() return nil, nil end
|
||||||
|
|
||||||
|
@ -142,14 +142,14 @@ print(string.format("%* %* %*", return_two_nils()))
|
||||||
|
|
||||||
It must be said that we are not allowing this style of string literals in type annotations at this time, regardless of zero or many interpolating expressions, so the following two type annotations below are illegal syntax:
|
It must be said that we are not allowing this style of string literals in type annotations at this time, regardless of zero or many interpolating expressions, so the following two type annotations below are illegal syntax:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local foo: `foo`
|
local foo: `foo`
|
||||||
local bar: `bar{baz}`
|
local bar: `bar{baz}`
|
||||||
```
|
```
|
||||||
|
|
||||||
String interpolation syntax will also support escape sequences. Except `\u{...}`, there is no ambiguity with other escape sequences. If `\u{...}` occurs within a string interpolation literal, it takes priority.
|
String interpolation syntax will also support escape sequences. Except `\u{...}`, there is no ambiguity with other escape sequences. If `\u{...}` occurs within a string interpolation literal, it takes priority.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local foo = `foo\tbar` -- "foo bar"
|
local foo = `foo\tbar` -- "foo bar"
|
||||||
local bar = `\u{0041} \u{42}` -- "A B"
|
local bar = `\u{0041} \u{42}` -- "A B"
|
||||||
```
|
```
|
||||||
|
|
|
@ -9,7 +9,7 @@ Provide semantics for referencing type packs inside the body of a type alias dec
|
||||||
## Motivation
|
## 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:
|
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
|
```luau
|
||||||
type X<A...> = () -> A... -- cannot reference A... as the return value pack
|
type X<A...> = () -> A... -- cannot reference A... as the return value pack
|
||||||
|
|
||||||
type Y = X<number, string> -- invalid number of arguments
|
type Y = X<number, string> -- invalid number of arguments
|
||||||
|
@ -22,7 +22,7 @@ Declaration syntax also supports multiple type packs, but we don't have defined
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||
We currently support type packs at these locations:
|
We currently support type packs at these locations:
|
||||||
```lua
|
```luau
|
||||||
-- for variadic function parameter when type pack is generic
|
-- for variadic function parameter when type pack is generic
|
||||||
local function f<a...>(...: a...)
|
local function f<a...>(...: a...)
|
||||||
|
|
||||||
|
@ -34,14 +34,14 @@ local function f<a...>(): (number, a...)
|
||||||
```
|
```
|
||||||
|
|
||||||
We want to be able to use type packs for type alias instantiation:
|
We want to be able to use type packs for type alias instantiation:
|
||||||
```lua
|
```luau
|
||||||
type X<T...> = --
|
type X<T...> = --
|
||||||
|
|
||||||
type A<S...> = X<S...> -- T... = (S...)
|
type A<S...> = X<S...> -- T... = (S...)
|
||||||
```
|
```
|
||||||
|
|
||||||
Similar to function calls, we want to be able to assign zero or more regular types to a single type pack:
|
Similar to function calls, we want to be able to assign zero or more regular types to a single type pack:
|
||||||
```lua
|
```luau
|
||||||
type A = X<> -- T... = ()
|
type A = X<> -- T... = ()
|
||||||
type B = X<number> -- T... = (number)
|
type B = X<number> -- T... = (number)
|
||||||
type C = X<number, string> -- T... = (number, string)
|
type C = X<number, string> -- T... = (number, string)
|
||||||
|
@ -50,7 +50,7 @@ type C = X<number, string> -- T... = (number, string)
|
||||||
Definition of `A` doesn't parse right now, we would like to make it legal going forward.
|
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:
|
Variadic types can also be assigned to type alias type pack:
|
||||||
```lua
|
```luau
|
||||||
type D = X<...number> -- T... = (...number)
|
type D = X<...number> -- T... = (...number)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ We have to keep in mind that it is also possible to declare a type alias that ta
|
||||||
Again, type parameters that haven't been matched with type arguments are combined together into the first type pack.
|
Again, type parameters that haven't been matched with type arguments are combined together into the first type pack.
|
||||||
After the first type pack parameter was assigned, following type parameters are not allowed.
|
After the first type pack parameter was assigned, following type parameters are not allowed.
|
||||||
Type pack parameters after the first one have to be type packs:
|
Type pack parameters after the first one have to be type packs:
|
||||||
```lua
|
```luau
|
||||||
type Y<T..., U...> = --
|
type Y<T..., U...> = --
|
||||||
|
|
||||||
type A<S...> = Y<S..., S...> -- T... = S..., U... = S...
|
type A<S...> = Y<S..., S...> -- T... = S..., U... = S...
|
||||||
|
@ -86,7 +86,7 @@ type I<S...> = W<number, string, S...> -- U... = (string), V... = S...
|
||||||
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.
|
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:
|
Similar to variadic types `...a` and generic type packs `T...`, explicit type packs can only be used at type pack positions:
|
||||||
```lua
|
```luau
|
||||||
type Y<T..., U...> = (T...) -> (U...)
|
type Y<T..., U...> = (T...) -> (U...)
|
||||||
|
|
||||||
type F1 = Y<(number, string), (boolean)> -- T... = (number, string), U... = (boolean)
|
type F1 = Y<(number, string), (boolean)> -- T... = (number, string), U... = (boolean)
|
||||||
|
@ -98,7 +98,7 @@ 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 in contrast to function return type pack annotation, where `() -> number` is the same as `() -> (number)`.
|
||||||
|
|
||||||
However, to preserve backwards-compatibility with optional parenthesis around regular types, type alias instantiation is allowed to assign a non-variadic type pack parameter with a single element to a type argument:
|
However, to preserve backwards-compatibility with optional parenthesis around regular types, type alias instantiation is allowed to assign a non-variadic type pack parameter with a single element to a type argument:
|
||||||
```lua
|
```luau
|
||||||
type X<T, U> = (T) -> U?
|
type X<T, U> = (T) -> U?
|
||||||
type A = X<(number), (string)> -- T = number, U = string
|
type A = X<(number), (string)> -- T = number, U = string
|
||||||
type A = X<(number), string> -- same
|
type A = X<(number), string> -- same
|
||||||
|
@ -114,7 +114,7 @@ Explicit type pack syntax is not available in other type pack annotation context
|
||||||
### Type pack element extraction
|
### 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:
|
Because our type alias instantiations are not lazy, it's impossible to split of a single type from a type pack:
|
||||||
```lua
|
```luau
|
||||||
type Car<T, U...> = T
|
type Car<T, U...> = T
|
||||||
|
|
||||||
type X = Car<number, string, boolean> -- number
|
type X = Car<number, string, boolean> -- number
|
||||||
|
@ -129,7 +129,7 @@ Splitting off a single type is is a common pattern with variadic templates in C+
|
||||||
### Type alias can't result in a type pack
|
### Type alias can't result in a type pack
|
||||||
|
|
||||||
We don't propose type aliases to generate type packs, which could have looked as:
|
We don't propose type aliases to generate type packs, which could have looked as:
|
||||||
```lua
|
```luau
|
||||||
type Car<T, U...> = T
|
type Car<T, U...> = T
|
||||||
type Cdr<T, U...> = U...
|
type Cdr<T, U...> = U...
|
||||||
type Cons<T, U...> = (T, U...)
|
type Cons<T, U...> = (T, U...)
|
||||||
|
@ -150,7 +150,7 @@ Support for variadic types in the middle of a type pack can be found in TypeScri
|
||||||
Another option that was considered is to parse `(T)` as `T`, like we do for return type annotation.
|
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.
|
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
|
```luau
|
||||||
type X<T...>
|
type X<T...>
|
||||||
type C = X<number, number> -- T... = (number, number)
|
type C = X<number, number> -- T... = (number, number)
|
||||||
type D = X<(number), (number)> -- T... = (number, number)
|
type D = X<(number), (number)> -- T... = (number, number)
|
||||||
|
@ -179,7 +179,7 @@ Since our current ruleset no longer has a problem with single element type tuple
|
||||||
|
|
||||||
One option that we have is to remove implicit pack assignment from a set of types and always require new explicit type pack syntax:
|
One option that we have is to remove implicit pack assignment from a set of types and always require new explicit type pack syntax:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type X<T...> = --
|
type X<T...> = --
|
||||||
|
|
||||||
type B = X<> -- invalid
|
type B = X<> -- invalid
|
||||||
|
@ -193,7 +193,7 @@ type D = X<(number, string)> -- T... = (number, string)
|
||||||
|
|
||||||
But this doesn't allow users to define type aliases where they only care about a few types and use the rest as a 'tail':
|
But this doesn't allow users to define type aliases where they only care about a few types and use the rest as a 'tail':
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
type X<T, U, Rest...> = (T, U, Rest...) -> Rest...
|
type X<T, U, Rest...> = (T, U, Rest...) -> Rest...
|
||||||
|
|
||||||
type A = X<number, string, ()> -- forced to use a type pack when there are no tail elements
|
type A = X<number, string, ()> -- forced to use a type pack when there are no tail elements
|
||||||
|
@ -204,14 +204,14 @@ It also makes it harder to change the type parameter count without fixing up the
|
||||||
### Combining types together with the following type pack into a single argument
|
### Combining types together with the following type pack into a single argument
|
||||||
|
|
||||||
Earlier version of the proposal allowed types to be combined together with a type pack as a tail:
|
Earlier version of the proposal allowed types to be combined together with a type pack as a tail:
|
||||||
```lua
|
```luau
|
||||||
type X<T...> = --
|
type X<T...> = --
|
||||||
|
|
||||||
type A<S...> = X<number, S...> --- T... = (number, S...)
|
type A<S...> = X<number, S...> --- T... = (number, S...)
|
||||||
```
|
```
|
||||||
|
|
||||||
But this syntax resulted in some confusing behavior when multiple type pack arguments are expected:
|
But this syntax resulted in some confusing behavior when multiple type pack arguments are expected:
|
||||||
```lua
|
```luau
|
||||||
type Y<T..., U...> = --
|
type Y<T..., U...> = --
|
||||||
|
|
||||||
type B = Y<number, (string, number)> -- not enough type pack parameters
|
type B = Y<number, (string, number)> -- not enough type pack parameters
|
||||||
|
|
|
@ -12,7 +12,7 @@ Due to an accident of the implementation, the Luau `::` operator can only be use
|
||||||
|
|
||||||
Because of this property, `::` works as users expect in a great many cases, but doesn't actually make a whole lot of sense when scrutinized.
|
Because of this property, `::` works as users expect in a great many cases, but doesn't actually make a whole lot of sense when scrutinized.
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local t = {x=0, y=0}
|
local t = {x=0, y=0}
|
||||||
|
|
||||||
local a = t :: {x: number, y: number, z: number} -- OK
|
local a = t :: {x: number, y: number, z: number} -- OK
|
||||||
|
|
|
@ -12,7 +12,7 @@ Implement syntax for type ascriptions using `::`
|
||||||
|
|
||||||
Luau would like to provide a mechanism for requiring a value to be of a specific type:
|
Luau would like to provide a mechanism for requiring a value to be of a specific type:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
-- Asserts that the result of a + b is a number.
|
-- Asserts that the result of a + b is a number.
|
||||||
-- Emits a type error if it isn't.
|
-- Emits a type error if it isn't.
|
||||||
local foo = (a + b) as number
|
local foo = (a + b) as number
|
||||||
|
@ -20,7 +20,7 @@ local foo = (a + b) as number
|
||||||
|
|
||||||
This syntax was proposed in the original Luau syntax proposal. Unfortunately, we discovered that there is a syntactical ambiguity with `as`:
|
This syntax was proposed in the original Luau syntax proposal. Unfortunately, we discovered that there is a syntactical ambiguity with `as`:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
-- Two function calls or a type assertion?
|
-- Two function calls or a type assertion?
|
||||||
foo() as (bar)
|
foo() as (bar)
|
||||||
```
|
```
|
||||||
|
@ -29,7 +29,7 @@ foo() as (bar)
|
||||||
|
|
||||||
To provide this functionality without introducing syntactical confusion, we want to change this syntax to use the `::` symbol instead of `as`:
|
To provide this functionality without introducing syntactical confusion, we want to change this syntax to use the `::` symbol instead of `as`:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
local foo = (a + b) :: number
|
local foo = (a + b) :: number
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -37,14 +37,14 @@ This syntax is borrowed from Haskell, where it performs the same function.
|
||||||
|
|
||||||
The `::` operator will bind very tightly, like `as`:
|
The `::` operator will bind very tightly, like `as`:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
-- type assertion applies to c, not (b + c).
|
-- type assertion applies to c, not (b + c).
|
||||||
local a = b + c :: number
|
local a = b + c :: number
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that `::` can only cast a *single* value to a type - not a type pack (multiple values). This means that in the following context, `::` changes runtime behavior:
|
Note that `::` can only cast a *single* value to a type - not a type pack (multiple values). This means that in the following context, `::` changes runtime behavior:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
foo(1, bar()) -- passes all values returned by bar() to foo()
|
foo(1, bar()) -- passes all values returned by bar() to foo()
|
||||||
foo(1, bar() :: any) -- passes just the first value returned by bar() to foo()
|
foo(1, bar() :: any) -- passes just the first value returned by bar() to foo()
|
||||||
```
|
```
|
||||||
|
@ -59,7 +59,7 @@ It's somewhat unusual for Lua to use symbols as operators, with the exception of
|
||||||
|
|
||||||
We considered requiring `as` to be wrapped in parentheses, and then relaxing this restriction where there's no chance of syntactical ambiguity:
|
We considered requiring `as` to be wrapped in parentheses, and then relaxing this restriction where there's no chance of syntactical ambiguity:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
local foo: SomeType = (fn() as SomeType)
|
local foo: SomeType = (fn() as SomeType)
|
||||||
-- Parentheses not needed: unambiguous!
|
-- Parentheses not needed: unambiguous!
|
||||||
bar(foo as number)
|
bar(foo as number)
|
||||||
|
|
|
@ -16,7 +16,7 @@ Luau's type checker internally can represent a typed variadic: any number of val
|
||||||
|
|
||||||
We think that the postfix `...: T` syntax is the best balance of readability and simplicity. In function type annotations, we will use `...T`:
|
We think that the postfix `...: T` syntax is the best balance of readability and simplicity. In function type annotations, we will use `...T`:
|
||||||
|
|
||||||
```
|
```luau
|
||||||
function math.max(...: number): number
|
function math.max(...: number): number
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ We propose that cast operator should instead test for whether there exists a com
|
||||||
|
|
||||||
For example, `e :: T` will report an error if and only if `typeof(e) & T` is uninhabited, unless `typeof(e)` is already uninhabited. More concretely:
|
For example, `e :: T` will report an error if and only if `typeof(e) & T` is uninhabited, unless `typeof(e)` is already uninhabited. More concretely:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local function noop(x) end
|
local function noop(x) end
|
||||||
|
|
||||||
local function f(e: number | string)
|
local function f(e: number | string)
|
||||||
|
@ -42,7 +42,7 @@ end
|
||||||
|
|
||||||
The reason why the special case oughtn't report an error is to support ad hoc typed holes pattern instead of having to hand-craft an expression that matches that type:
|
The reason why the special case oughtn't report an error is to support ad hoc typed holes pattern instead of having to hand-craft an expression that matches that type:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local x = error("") :: string | number
|
local x = error("") :: string | number
|
||||||
-- versus
|
-- versus
|
||||||
local x = if math.random() > 0.5 then "hello" else 5
|
local x = if math.random() > 0.5 then "hello" else 5
|
||||||
|
@ -50,7 +50,7 @@ local x = if math.random() > 0.5 then "hello" else 5
|
||||||
|
|
||||||
We don't apply the same special case for `T`, otherwise we won't report an error when `e : string` and `T` is `never`. This would mean we get to support the exhaustive analysis use case:
|
We don't apply the same special case for `T`, otherwise we won't report an error when `e : string` and `T` is `never`. This would mean we get to support the exhaustive analysis use case:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local function f(e: number | string)
|
local function f(e: number | string)
|
||||||
if typeof(e) == "number" then
|
if typeof(e) == "number" then
|
||||||
-- ...
|
-- ...
|
||||||
|
|
|
@ -13,7 +13,7 @@ bottom" behavior of the `any` type.
|
||||||
### Error suppression
|
### Error suppression
|
||||||
|
|
||||||
Currently, we have ad hoc error suppression, where we try to avoid cascading errors, for example in
|
Currently, we have ad hoc error suppression, where we try to avoid cascading errors, for example in
|
||||||
```lua
|
```luau
|
||||||
local x = t.p.q.r
|
local x = t.p.q.r
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ when the old algorithm generates no errors. But it can result in different unifi
|
||||||
For example, if `Y` is a free type variable, then currently checking `(any & Y) <: number`
|
For example, if `Y` is a free type variable, then currently checking `(any & Y) <: number`
|
||||||
will not perform any unification, which makes a difference to the program:
|
will not perform any unification, which makes a difference to the program:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
function f(x : any, y) -- introduces a new free type Y for y
|
function f(x : any, y) -- introduces a new free type Y for y
|
||||||
if x == y then -- type refinement makes y have type (any & Y)
|
if x == y then -- type refinement makes y have type (any & Y)
|
||||||
return math.abs(y) -- checks (any & Y) <: number
|
return math.abs(y) -- checks (any & Y) <: number
|
||||||
|
@ -128,4 +128,3 @@ We could implement Siek and Taha's algorithm, but that only helps with
|
||||||
`any`, not with more general error supression.
|
`any`, not with more general error supression.
|
||||||
|
|
||||||
We could leave everything alone, and live with the weirdness of non-transitive subtyping.
|
We could leave everything alone, and live with the weirdness of non-transitive subtyping.
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ literal to an unsealed table creates an optional property.
|
||||||
|
|
||||||
In lua-apps, there is testing code which (simplified) looks like:
|
In lua-apps, there is testing code which (simplified) looks like:
|
||||||
|
|
||||||
```lua
|
```luau
|
||||||
local t = { u = {} }
|
local t = { u = {} }
|
||||||
t = { u = { p = 37 } }
|
t = { u = { p = 37 } }
|
||||||
t = { u = { q = "hi" } }
|
t = { u = { q = "hi" } }
|
||||||
|
@ -43,7 +43,7 @@ tables with indexers, this allows table literals to be used as dictionaries,
|
||||||
for example the type of `t` is a subtype of `{ u: { [string]: number } }`.
|
for example the type of `t` is a subtype of `{ u: { [string]: number } }`.
|
||||||
|
|
||||||
Note that we need to add an optional property, otherwise the example above will not typecheck.
|
Note that we need to add an optional property, otherwise the example above will not typecheck.
|
||||||
```lua
|
```luau
|
||||||
local t = { u = {} }
|
local t = { u = {} }
|
||||||
t = { u = { p = 37 } }
|
t = { u = { p = 37 } }
|
||||||
t = { u = { q = "hi" } } -- fails because there's no u.p
|
t = { u = { q = "hi" } } -- fails because there's no u.p
|
||||||
|
|
|
@ -25,13 +25,13 @@ Table types can be *sealed* or *unsealed*. These are different in that:
|
||||||
* Unsealed tables are subtypes of sealed tables.
|
* Unsealed tables are subtypes of sealed tables.
|
||||||
|
|
||||||
Currently the only way to create an unsealed table is using an empty table literal, so
|
Currently the only way to create an unsealed table is using an empty table literal, so
|
||||||
```lua
|
```luau
|
||||||
local t = {}
|
local t = {}
|
||||||
t.p = 5
|
t.p = 5
|
||||||
t.q = "hi"
|
t.q = "hi"
|
||||||
```
|
```
|
||||||
typechecks, but
|
typechecks, but
|
||||||
```lua
|
```luau
|
||||||
local t = { p = 5 }
|
local t = { p = 5 }
|
||||||
t.q = "hi"
|
t.q = "hi"
|
||||||
```
|
```
|
||||||
|
@ -39,7 +39,7 @@ does not.
|
||||||
|
|
||||||
This causes problems in examples, in particular developers
|
This causes problems in examples, in particular developers
|
||||||
may initialize properties but not methods:
|
may initialize properties but not methods:
|
||||||
```lua
|
```luau
|
||||||
local t = { p = 5 }
|
local t = { p = 5 }
|
||||||
function t.f() return t.p end
|
function t.f() return t.p end
|
||||||
```
|
```
|
||||||
|
@ -56,7 +56,7 @@ It does encourage developers to add new properties to tables during initializati
|
||||||
may be considered poor style.
|
may be considered poor style.
|
||||||
|
|
||||||
It does mean that some spelling mistakes will not be caught, for example
|
It does mean that some spelling mistakes will not be caught, for example
|
||||||
```lua
|
```luau
|
||||||
local t = {x = 1, y = 2}
|
local t = {x = 1, y = 2}
|
||||||
if foo then
|
if foo then
|
||||||
t.z = 3 -- is z a typo or intentional 2-vs-3 choice?
|
t.z = 3 -- is z a typo or intentional 2-vs-3 choice?
|
||||||
|
@ -64,7 +64,7 @@ end
|
||||||
```
|
```
|
||||||
|
|
||||||
In particular, we no longer warn about adding properties to array-like tables.
|
In particular, we no longer warn about adding properties to array-like tables.
|
||||||
```lua
|
```luau
|
||||||
local a = {1,2,3}
|
local a = {1,2,3}
|
||||||
a.p = 5
|
a.p = 5
|
||||||
```
|
```
|
||||||
|
|
|
@ -27,14 +27,14 @@ Table types can be *sealed* or *unsealed*. These are different in that:
|
||||||
Currently we allow subtyping to strip away optional fields
|
Currently we allow subtyping to strip away optional fields
|
||||||
as long as the supertype is sealed.
|
as long as the supertype is sealed.
|
||||||
This is necessary for examples, for instance:
|
This is necessary for examples, for instance:
|
||||||
```lua
|
```luau
|
||||||
local t : { p: number, q: string? } = { p = 5, q = "hi" }
|
local t : { p: number, q: string? } = { p = 5, q = "hi" }
|
||||||
t = { p = 7 }
|
t = { p = 7 }
|
||||||
```
|
```
|
||||||
typechecks because `{ p : number }` is a subtype of
|
typechecks because `{ p : number }` is a subtype of
|
||||||
`{ p : number, q : string? }`. Unfortunately this is not sound,
|
`{ p : number, q : string? }`. Unfortunately this is not sound,
|
||||||
since sealed tables support width subtyping:
|
since sealed tables support width subtyping:
|
||||||
```lua
|
```luau
|
||||||
local t : { p: number, q: string? } = { p = 5, q = "hi" }
|
local t : { p: number, q: string? } = { p = 5, q = "hi" }
|
||||||
local u : { p: number } = { p = 5, q = false }
|
local u : { p: number } = { p = 5, q = false }
|
||||||
t = u
|
t = u
|
||||||
|
@ -54,7 +54,7 @@ This RFC is for (2). There is a [separate RFC](unsealed-table-literals.md) for (
|
||||||
|
|
||||||
This introduces new type errors (it has to, since it is fixing a source of
|
This introduces new type errors (it has to, since it is fixing a source of
|
||||||
unsoundness). This means that there are now false positives such as:
|
unsoundness). This means that there are now false positives such as:
|
||||||
```lua
|
```luau
|
||||||
local t : { p: number, q: string? } = { p = 5, q = "hi" }
|
local t : { p: number, q: string? } = { p = 5, q = "hi" }
|
||||||
local u : { p: number } = { p = 5, q = "lo" }
|
local u : { p: number } = { p = 5, q = "lo" }
|
||||||
t = u
|
t = u
|
||||||
|
@ -65,4 +65,3 @@ that it is difficult to see how to allow them soundly.
|
||||||
## Alternatives
|
## Alternatives
|
||||||
|
|
||||||
We could just live with unsoundness.
|
We could just live with unsoundness.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue