Add live syncback functionality and optimize service encoding

- Introduced a new endpoint `/api/syncback` for one-click full sync from Roblox Studio to the filesystem, eliminating the need for an active WebSocket connection.
- Implemented `SyncbackRequest` and `ServiceChunk` structures to handle service-level properties and child instances during syncback.
- Enhanced `encodeService` to include ObjectValue carriers for accurate reference handling and prevent data loss.
- Updated the syncback process to utilize Luau-style relative paths for reference attributes, improving path management and consistency.
- Integrated session caching in the matching algorithm to optimize performance during syncback operations.

These changes significantly improve the live syncback experience, ensuring reliable data synchronization and enhanced performance during development.
This commit is contained in:
Joe 2026-02-18 22:34:52 -06:00
parent 7f4b5c4941
commit b47eef1173
165 changed files with 5205 additions and 206 deletions

View file

@ -0,0 +1,133 @@
---
slug: compatibility
title: Compatibility with Lua
toc: true
---
Luau is based on Lua 5.1, and as such incorporates all features of 5.1, except for ones that had to be taken out due to sandboxing limitations. Because of backwards compatibility constraints, we don't remove features deprecated by later versions (e.g. we still support `getfenv`/`setfenv`). Later Lua versions introduce new features into the language and new libraries/functions.
Our overall goal is to incorporate features from the later versions of Lua when it makes sense for us to do so - the motivations behind some newer features are unclear or don't apply to the domain Luau is used in, and many features carry costs that don't always make sense to pay. The rest of this document describes the status of all features of Lua 5.2 and beyond, with the following classification:
- ✔️ - the feature is available in Luau
- ❌ - the feature is not available in Luau because we don't believe it makes sense to include it
- 😞 - the feature is not available in Luau because of compatibility/sandboxing concerns
- 🔜 - the feature is not available in Luau yet but we'd like to include it and are possibly working on it
- 🤷‍♀️ - the feature is not available in Luau yet; we don't have strong opinions on it so it might make it at some point
Please note that all of these decisions are not final, they are just our current stance. In some cases evolution of our VM may make a feature that was previously impractical to support due to performance complications feasible. In some cases a feature that didn't have a strong use case gains one, so we can implement it.
## Implementation limits
Luau has certain limitations around the number of local variables, registers, upvalues, constants and instructions. These limits are often different from the limits imposed by various versions of Lua, and are documented here without promising that future versions will adhere to these. Note that writing code that is close to any of these limits is dangerous because this code may become invalid as our codegen evolves.
- Local variables: 200 per function (same as all versions of Lua, this includes function arguments)
- Upvalues: 200 per function (up from 60 in Lua 5.1)
- Registers: 255 per function (same as all versions of Lua, this includes local variables and function arguments)
- Constants: 2^23 per function (up from 2^18 in Lua 5.1)
- Instructions: 2^23 per function (up from 2^17 in Lua 5.1, although in both cases the limit only applies to control flow)
- Nested functions: 2^15 per function (down from 2^18 in Lua 5.1)
- Stack depth: 20000 Lua calls per Lua thread, 200 C calls per C thread (e.g. `coroutine.resume`/`pcall` nesting is limited to 200)
Note that Lua 5.3 has a larger upvalue limit (255) and a larger constant limit (2^26); existing Luau limits are likely sufficient for reasonable use cases.
## Lua 5.1
Since several features were removed from Lua 5.1 for sandboxing reasons, this table lists them for completeness.
| feature | notes |
|---------|------|
| `io`, `os`, `package` and `debug` library | note that some functions in `os`/`debug` are still present |
| `loadfile`, `dofile` | removed for sandboxing, no direct file access |
| `loadstring` bytecode and `string.dump` | exposing bytecode is dangerous for sandboxing reasons |
| `newproxy` can only be called with nil or boolean | extra flexibility removed for sandboxing |
Sandboxing challenges are [covered in the dedicated section](../sandbox).
## Lua 5.2
| feature | status | notes |
|---------|--------|------|
| yieldable pcall/xpcall | ✔️ | |
| yieldable xpcall error handler | ❌ | no strong use cases, VM complexity and performance implications |
| yieldable metamethods | ❌ | significant performance implications |
| ephemeron tables | ❌ | this complicates and slows down the garbage collector esp. for large weak tables |
| emergency garbage collector | 🤷‍ | Luau runs in environments where handling memory exhaustion in emergency situations is not tenable |
| goto statement | ❌ | this complicates the compiler, makes control flow unstructured and doesn't address a significant need |
| finalizers for tables | ❌ | no `__gc` support due to sandboxing and performance/complexity |
| no more fenv for threads or functions | 😞 | we love this, but it breaks compatibility |
| tables honor the `__len` metamethod | ✔️ | |
| hex and `\z` escapes in strings | ✔️ | |
| support for hexadecimal floats | 🤷‍♀️ | no strong use cases |
| order metamethods (`__lt`/`__le`) are called for unrelated metatables | ❌ | no strong use cases and more complicated semantics, compatibility and performance implications |
| empty statement | 🤷‍♀️ | less useful in Lua than in JS/C#/C/C++ |
| `break` statement may appear in the middle of a block | 🤷‍♀️ | we'd like to do it consistently for `break`/`return`/`continue` but there be dragons |
| arguments for function called through `xpcall` | ✔️ | |
| optional base in `math.log` | ✔️ | |
| optional separator in `string.rep` | 🤷‍♀️ | no strong use cases |
| new metamethods `__pairs` and `__ipairs` | ❌ | superseded by `__iter` |
| frontier patterns | ✔️ | |
| `%g` in patterns | ✔️ | |
| `\0` in patterns | ✔️ | |
| `bit32` library | ✔️ | |
| `string.gsub` is stricter about using `%` on special characters only | ✔️ | |
| light C functions | 😞 | this changes semantics of fenv on C functions and has complex implications wrt runtime performance |
| NaN keys are supported for tables with `__newindex` | ✔️ | |
Two things that are important to call out here are various new metamethods for tables and yielding in metamethods. In both cases, there are performance implications to supporting this - our implementation is *very* highly tuned for performance, so any changes that affect the core fundamentals of how Lua works have a price. To support yielding in metamethods we'd need to make the core of the VM more involved, since almost every single "interesting" opcode would need to learn how to be resumable - which also complicates future JIT/AOT story. Metamethods in general are important for extensibility, but very challenging to deal with in implementation, so we err on the side of not supporting any new metamethods unless a strong need arises.
For `__pairs`/`__ipairs`, we felt that extending library functions to enable custom containers wasn't the right choice. Instead we revisited iteration design to allow for self-iterating objects via `__iter` metamethod, which results in a cleaner iteration design that also makes it easier to iterate over tables. As such, we have no plans to support `__pairs`/`__ipairs` as all use cases for it can now be solved by `__iter`.
Ephemeron tables may be implemented at some point since they do have valid uses and they make weak tables semantically cleaner, however the cleanup mechanism for these is expensive and complicated, and as such this can only be considered after the pending GC rework is complete.
## Lua 5.3
| feature | status | notes |
|---------|--------|------|
| `\u` escapes in strings | ✔️ | |
| integers (64-bit by default) | ❌ | backwards compatibility and performance implications |
| bitwise operators | ❌ | `bit32` library covers this in absence of 64-bit integers |
| basic utf-8 support | ✔️ | we include `utf8` library and other UTF8 features |
| functions for packing and unpacking values (string.pack/unpack/packsize) | ✔️ | |
| floor division | ✔️ | |
| `ipairs` and the `table` library respect metamethods | ❌ | no strong use cases, performance implications |
| new function `table.move` | ✔️ | |
| `collectgarbage("count")` now returns only one result | ✔️ | |
| `coroutine.isyieldable` | ✔️ | |
| stricter error checking for `table.insert`/`table.remove` | 😞 | we love this, but it breaks compatibility |
| `__eq` metamethod is called for unrelated metatables | ❌ | backwards compatibility and typechecking implications |
It's important to highlight integer support and bitwise operators. For Luau, it's rare that a full 64-bit integer type is necessary - double-precision types support integers up to 2^53 (in Lua which is used in embedded space, integers may be more appealing in environments without a native 64-bit FPU). However, there's a *lot* of value in having a single number type, both from performance perspective and for consistency. Notably, Lua doesn't handle integer overflow properly, so using integers also carries compatibility implications.
If integers are taken out of the equation, bitwise operators make less sense, as integers aren't a first class feature; additionally, `bit32` library is more fully featured (includes commonly used operations such as rotates and arithmetic shift; bit extraction/replacement is also more readable). Adding operators along with metamethods for all of them increases complexity, which means this feature isn't worth it on the balance. Common arguments for this include a more familiar syntax, which, while true, gets more nuanced as `^` isn't available as a xor operator, and arithmetic right shift isn't expressible without yet another operator, and performance, which in Luau is substantially better than in Lua because `bit32` library uses VM builtins instead of expensive function calls.
## Lua 5.4
| feature | status | notes |
|--|--|--|
| new generational mode for garbage collection | 🔜 | we're working on gc optimizations and generational mode is on our radar
| to-be-closed variables | ❌ | the syntax is inconsistent with how we'd like to do attributes long-term; no strong use cases in our domain |
| const variables | ❌ | while there's some demand for const variables, we'd never adopt this syntax |
| new implementation for math.random | ✔️ | our RNG is based on PCG, unlike Lua 5.4 which uses Xoroshiro |
| optional `init` argument to `string.gmatch` | 🤷‍♀️ | no strong use cases |
| new functions `lua_resetthread` and `coroutine.close` | ✔️ ||
| coercions string-to-number moved to the string library | 😞 | we love this, but it breaks compatibility |
| new format `%p` in `string.format` | 🤷‍♀️ | no strong use cases |
| `utf8` library accepts codepoints up to 2^31 | 🤷‍♀️ | no strong use cases |
| The use of the `__lt` metamethod to emulate `__le` has been removed | ❌ | breaks compatibility and complicates comparison overloading story |
| When finalizing objects, Lua will call `__gc` metamethods that are not functions | ❌ | no `__gc` support due to sandboxing and performance/complexity |
| function `print` calls `__tostring` instead of `tostring` to format its arguments | ✔️ | |
| decoding functions in the utf8 library do not accept surrogates | ✔️ | |
Taking syntax aside (which doesn't feel idiomatic or beautiful), `<close>` isn't very useful in Luau - its dominant use case is for code that works with external resources like files or sockets, but we don't provide such APIs - and has a very large complexity cost, evidences by a lot of bug fixes since the initial implementation in 5.4 work versions. `<const>` in Luau doesn't matter for performance - our multi-pass compiler is already able to analyze the usage of the variable to know if it's modified or not and extract all performance gains from it - so the only use here is for code readability, where the `<const>` syntax is... suboptimal.
If we do end up introducing const variables, it would be through a `const var = value` syntax, which is backwards compatible through a context-sensitive keyword similar to `type`. That said, there's ambiguity wrt whether `const` should simply behave like a read-only variable, ala JavaScript, or if it should represent a stronger contract, for example by limiting the expressions on the right hand side to ones compiler can evaluate ahead of time, or by freezing table values and thus guaranteeing immutability.
## Differences from Lua
We have a few behavior deviations from Lua 5.x that come from either a different implementation, or our desire to clean up small inconsistencies in the language/libraries:
* Tail calls are not supported to simplify implementation, make debugging/stack traces more predictable and allow deep validation of caller identity for security
* Order of table assignment in table literals follows program order in mixed tables (Lua 5.x assigns array elements first in some cases)
* Equality comparisons call `__eq` metamethod even when objects are rawequal (which matches other metamethods like `<=` and facilitates NaN checking)
* `function()` expressions may reuse a previously created closure in certain scenarios (when all upvalues captured are the same) for efficiency, which changes object identity but doesn't change call semantics -- this is different from Lua 5.1 but similar to Lua 5.2/5.3
* `os.time` returns UTC timestamp when called with a table for consistency

View file

@ -0,0 +1,147 @@
---
slug: getting-started
title: An introduction to Luau
sidebar:
order: 1
---
Luau is a fast, small, safe, gradually typed embeddable scripting language derived from Lua 5.1. Luau ships as a command line tool for running, analyzing, and linting your Luau scripts, and is also integrated with Roblox Studio. Roblox developers should also visit our [Creator Docs Luau Section](https://create.roblox.com/docs/luau).
To get started with Luau, you can use the `luau` command line binary to run your code and `luau-analyze` to run static analysis (including type checking and linting). You can download these from [a recent release](https://github.com/luau-lang/luau/releases).
## Creating a script
To create your own testing script, create a new file with `.luau` as the extension:
```luau
--!hidden mode=nonstrict
function ispositive(x)
return x > 0
end
print(ispositive(1))
print(ispositive("2"))
function isfoo(a)
return a == "foo"
end
print(isfoo("bar"))
print(isfoo(1))
```
You can now run the file using `luau test.luau` and analyze it using `luau-analyze test.luau`.
Note that there are no warnings about calling ``ispositive()`` with a string, or calling ``isfoo()`` with a number. This is because Luau's type checking uses non-strict mode by default, which only reports errors if it's certain a program will error at runtime.
## Type checking
Now modify the script to include ``--!strict`` at the top:
```luau
--!strict
function ispositive(x)
return x > 0
end
print(ispositive(1))
print(ispositive("2"))
```
We just mentioned ``nonstrict`` mode, where we only report errors if we can prove that a program will error at runtime. We also have ``strict`` mode, which will report errors if a program might error at runtime.
In this case, Luau will use the ``return x > 0`` statement to infer that ``ispositive()`` is a function taking a number and returning a boolean. Note that in this case, we were able to determine the type of `ispositive` without the presence of any explicit type annotations.
Based on Luau's type inference, the analysis tool will now flag the incorrect call to ``ispositive()``:
```
$ luau-analyze test.luau
test.luau(7,18): TypeError: Type 'string' could not be converted into 'number'
```
## Annotations
You can add annotations to locals, arguments, and function return types. Among other things, annotations can help enforce that you don't accidentally do something silly. Here's how we would add annotations to ``ispositive()``:
```luau
--!strict
function ispositive(x : number) : boolean
return x > 0
end
local result : boolean
result = ispositive(1)
```
Now we've explicitly told Luau that ``ispositive()`` accepts a number and returns a boolean. This wasn't strictly (pun intended) necessary in this case, because Luau's inference was able to deduce this already. But even in this case, there are advantages to explicit annotations. Imagine that later we decide to change ``ispositive()`` to return a string value:
```luau
--!strict
function ispositive(x : number) : boolean
if x > 0 then
return "yes"
else
return "no"
end
end
local result : boolean
result = ispositive(1)
```
Oops -- we're returning string values, but we forgot to update the function return type. Since we've told Luau that ``ispositive()`` returns a boolean (and that's how we're using it), the call site isn't flagged as an error. But because the annotation doesn't match our code, we get a warning in the function body itself:
```
$ luau-analyze test.luau
test.luau(5,9): TypeError: Type 'string' could not be converted into 'boolean'
test.luau(7,9): TypeError: Type 'string' could not be converted into 'boolean'
```
The fix is simple; just change the annotation to declare the return type as a string:
```luau
--!strict
function ispositive(x : number) : string
if x > 0 then
return "yes"
else
return "no"
end
end
local result : boolean
result = ispositive(1)
```
Well, almost - since we declared ``result`` as a boolean, the call site is now flagged:
```
$ luau-analyze test.luau
test.luau(12,10): TypeError: Type 'string' could not be converted into 'boolean'
```
If we update the type of the local variable, everything is good. Note that we could also just let Luau infer the type of ``result`` by changing it to the single line version ``local result = ispositive(1)``.
```luau
--!strict
function ispositive(x : number) : string
if x > 0 then
return "yes"
else
return "no"
end
end
local result : string
result = ispositive(1)
```
## Conclusions
This has been a brief tour of the basic functionality of Luau, but there's lots more to explore. If you're interested in reading more, check out our main reference pages for [syntax](/syntax) and [typechecking](/types).

View file

@ -0,0 +1,387 @@
---
slug: lint
title: Luau's linter
sidebar:
order: 4
---
Luau comes with a set of linting passes, that help make sure that the code is correct and consistent. Unlike the type checker, that models the behavior of the code thoroughly and points toward type mismatches that are likely to result in runtime errors, the linter is more opinionated and produces warnings that can often be safely ignored, although it's recommended to keep the code clean of the warnings.
Linter produces many different types of warnings; many of these are enabled by default, and can be suppressed by declaring `--!nolint NAME` at the top of the file. In dire situations `--!nolint` at the top of the file can be used to completely disable all warnings (note that the type checker is still active, and requires a separate `--!nocheck` declaration).
The rest of this page documents all warnings produced by the linter; each warning has a name and a numeric code, the latter is used when displaying warnings. Note that the in-browser version of Luau on this website does not currently use the linter.
## UnknownGlobal (1)
By default, variables in Luau are global (this is inherited from Lua 5.x and can't be changed because of backwards compatibility). This means that typos in identifiers are invisible to the parser, and often break at runtime. For this reason, the linter considers all globals that aren't part of the builtin global table and aren't explicitly defined in the script "unknown":
```luau
--!hidden mode=nocheck
local displayName = "Roblox"
-- Unknown global 'displaName'
print(displaName)
```
Note that injecting globals via `setfenv` can produce this warning in correct code; global injection is incompatible with type checking and has performance implications so we recommend against it and in favor of using `require` with correctly scoped identifiers.
## DeprecatedGlobal (2)
Some global names exist for compatibility but their use is discouraged. This mostly affects globals introduced by Roblox, and since they can have problematic behavior or can break in the future, this warning highlights their uses:
```luau
--!hidden mode=nocheck
-- Global 'ypcall' is deprecated, use 'pcall' instead
ypcall(function()
print("hello")
end)
```
## GlobalUsedAsLocal (3)
The UnknownGlobal lint can catch typos in globals that are read, but can't catch them in globals that are assigned to. Because of this, and to discourage the use of globals in general, linter detects cases when a global is only used in one function and can be safely converted to a local variable. Note that in some cases this requires declaring the local variable in the beginning of the function instead of where it's being assigned to.
```luau
--!hidden mode=nocheck
local function testFunc(a)
if a < 5 then
-- Global 'b' is only used in the enclosing function; consider changing it to local
b = 1
else
b = 2
end
print(b)
end
```
## LocalShadow (4)
In Luau, it is valid to shadow locals and globals with a local variable, including doing it in the same function. This can result in subtle bugs, since the shadowing may not be obvious to the reader. This warning detects cases where local variables shadow other local variables in the same function, or global variables used in the script.
```luau
--!hidden mode=nocheck
local function foo()
for i=1,10 do
-- Variable 'i' shadows previous declaration at line 2
for i=1,10 do
print(i)
end
end
end
```
## SameLineStatement (5)
Luau doesn't require the use of semicolons and doesn't automatically insert them at line breaks. When used wisely this results in code that is easy to read and understand, however it can cause subtle issues and hard to understand code when abused by using many different statements on the same line. This warning highlights cases where code should either be broken into multiple lines, or use `;` as a visual guide.
```luau
--!hidden mode=nocheck
-- A new statement is on the same line; add semi-colon on previous statement to silence
if b < 0 then local a = b + 1 print(a, b) end
```
## MultiLineStatement (6)
An opposite problem is having statements that span multiple lines. This is good for readability when the code is indented properly, but when it's not it results in code that's hard to understand, as its easy to confuse the next line for a separate statement.
```luau
--!hidden mode=nocheck
-- Statement spans multiple lines; use indentation to silence
print(math.max(1,
math.min(2, 3)))
```
## LocalUnused (7)
This warning is one of the few warnings that highlight unused variables. Local variable declarations that aren't used may indicate a bug in the code (for example, there could be a typo in the code that uses the wrong variable) or redundant code that is no longer necessary (for example, calling a function to get its result and never using this result). This warning warns about locals that aren't used; if the locals are not used intentionally they can be prefixed with `_` to silence the warning:
```luau
--!hidden mode=nocheck
local x = 1
-- Variable 'y' is never used; prefix with '_' to silence
local y = 2
print(x, x)
```
## FunctionUnused (8)
While unused local variables could be useful for debugging, unused functions usually contain dead code that was meant to be removed. Unused functions clutter code, can be a result of typos similar to local variables, and can mislead the reader into thinking that some functionality is supported.
```luau
--!hidden mode=nocheck
-- Function 'bar' is never used; prefix with '_' to silence
local function bar()
end
local function foo()
end
return foo()
```
## ImportUnused (9)
In Luau, there's no first-class module system that's part of the syntax, but `require` function acts as an import statement. When a local is initialized with a `require` result, and the local is unused, this is classified as "unused import". Removing unused imports improves code quality because it makes it obvious what the dependencies of the code are:
```luau
--!hidden mode=nocheck
-- Import 'Roact' is never used; prefix with '_' to silence
local Roact = require("@game/Packages/Roact")
```
## BuiltinGlobalWrite (10)
While the sandboxing model of Luau prevents overwriting built-in globals such as `table` for the entire program, it's still valid to reassign these globals - this results in "global shadowing", where the script's global table contains a custom version of `table` after writing to it. This is problematic because it disables some optimizations, and can result in misleading code. When shadowing built-in globals, use locals instead.
```luau
-- Built-in global 'math' is overwritten here; consider using a local or changing the name
math = {}
```
## PlaceholderRead (11)
`_` variable name is commonly used as a placeholder to discard function results. The linter follows this convention and doesn't warn about the use of `_` in various cases where a different name would cause a warning otherwise. To make sure the placeholder is only used to write values to it, this warning detects the cases where it's read instead:
```luau
local _ = 5
-- Placeholder value '_' is read here; consider using a named variable
return _
```
## UnreachableCode (12)
In some cases the linter can detect code that is never executed, because all execution paths through the function exit the function or the loop before reaching it. Such code is misleading because it's not always obvious to the reader that it never runs, and as such it should be removed.
```luau
function cbrt(v)
if v >= 0 then
return v ^ (1/3)
else
error('cbrt expects a non-negative argument')
end
-- Unreachable code (previous statement always returns)
return 0
end
```
## UnknownType (13)
Luau provides several functions to get the value type as a string (`type`, `typeof`), and some Roblox APIs expose class names through string arguments (`Instance.new`). This warning detects incorrect use of the type names by checking the string literals used in type comparisons and function calls.
```luau
--!hidden mode=nocheck
-- Unknown type 'String' (expected primitive type)
if type(v) == "String" then
print("v is a string")
end
```
## ForRange (14)
When using a numeric for, it's possible to make various mistakes when specifying the for bounds. For example, to iterate through the table backwards, it's important to specify the negative step size. This warning detects several cases where the numeric for only runs for 0 or 1 iterations, or when the step doesn't divide the size of the range evenly.
```luau
--!hidden mode=nocheck
-- For loop should iterate backwards; did you forget to specify -1 as step?
for i=#t,1 do
end
```
## UnbalancedAssignment (15)
Assignment statements and local variable declarations in Luau support multiple variables on the left side and multiple values on the right side. The number of values doesn't need to match; when the right side has more values, the extra values are discarded, and then the left side has more variables the extra variables are set to `nil`. However, this can result in subtle bugs where a value is omitted mistakenly. This warning warns about cases like this; if the last expression on the right hand side returns multiple values, the warning is not emitted.
```luau
-- Assigning 2 values to 3 variables initializes extra variables with nil; add 'nil' to value list to silence
local x, y, z = 1, 2
```
## ImplicitReturn (16)
In Luau, there's a subtle difference between returning no values from a function and returning `nil`. In many contexts these are equivalent, but when the results are passed to a variadic function (perhaps implicitly), the difference can be observed - for example, `print(foo())` prints nothing if `foo` returns no values, and `nil` if it returns `nil`.
To help write code that has consistent behavior, linter warns about cases when a function implicitly returns no values, if there are cases when it explicitly returns a result. For code like this it's recommended to use explicit `return` or `return nil` at the end of the function (these have different semantics, so the correct version to use depends on the function):
```luau
--!hidden mode=nocheck
local function find(t, expected)
for k,v in pairs(t) do
if k == expected then
return v
end
end
-- Function 'find' can implicitly return no values even though there's an explicit return at line 4; add explicit return to silence
end
```
## DuplicateLocal (17)
Luau syntax allows to use the same name for different parameters of a function as well as different local variables declared in the same statement. This is error prone, even if it's occasionally useful, so a warning is emitted in cases like this, unless the duplicate name is the placeholder `_`:
```luau
--!hidden mode=nocheck
function foo(a, b, a) -- Function parameter 'a' already defined on column 14
end
function obj:method(self) -- Function parameter 'self' already defined implicitly
end
local x, y, x = v:GetComponents() -- Variable 'x' already defined on column 7
```
## FormatString (18)
Luau has several library functions that expect a format string that specifies the behavior for the function. These format strings follow a specific syntax that depends on the question; mistakes in these strings can lead to runtime errors or unexpected behavior of the code.
To help make sure that the strings used for these functions are correct, linter checks calls to `string.format`, `string.pack`, `string.packsize`, `string.unpack`, `string.match`, `string.gmatch`, `string.find`, `string.gsub` and `os.date` and issues warnings when the call uses a literal string with an incorrect format:
```luau
--!hidden mode=nocheck
-- Invalid match pattern: invalid capture reference, must refer to a closed capture
local cap = string.match(s, "(%d)%2")
-- Invalid format string: unfinished format specifier
local str = ("%d %"):format(1, 2)
```
Note that with the exception of `string.format` this only works when the function is called via the library, not via the method call (so prefer `string.match(s, "pattern")` to `s:match("pattern")`).
## TableLiteral (19)
Table literals are often used in Luau to create objects or specify data. The syntax for table literals is rich but invites mistakes, for example it allows duplicate keys or redundant index specification for values already present in the list form. This warning is emitted for cases where some entries in the table literal are ignored at runtime as they're duplicate:
```luau
print({
first = 1,
second = 2,
first = 3, -- Table field 'first' is a duplicate; previously defined at line 2
})
```
## UninitializedLocal (20)
It's easy to forget to initialize a local variable and then proceed to use it regardless. This can happen due to a typo, or sometimes it can happen because the original variable definition is shadowed. When a local variable doesn't have an initial value and is used without ever being assigned to, this warning is emitted:
```luau
local foo
if foo then -- Variable 'foo' defined at line 1 is never initialized or assigned; initialize with 'nil' to silence
print(foo)
end
```
## DuplicateFunction (21)
This warning is emitted when a script defines two functions with the same name in the same scope.
The warning is not produced when the functions are defined in different scopes because this is much more likely to be intentional.
```luau
--!hidden mode=nocheck
function foo() end
function foo() end -- Duplicate function definition: 'foo' also defined on line 1
-- OK: the functions are not defined in the same scope.
if x then
function bar() end
else
function bar() end
end
```
## DeprecatedApi (22)
This warning is emitted when a script accesses a method or field that is marked as deprecated. Use of deprecated APIs is discouraged since they may have performance or correctness issues, may result in unexpected behavior, and could be removed in the future.
```luau
--!hidden mode=nocheck
function getSize(i: Instance)
return i.DataCost -- Member 'Instance.DataCost' is deprecated
end
```
## TableOperations (23)
To manipulate array-like tables, Luau provides a set of functions as part of the standard `table` library. To help use these functions correctly, this warning is emitted when one of these functions is used incorrectly or can be adjusted to be more performant.
```luau
local t = {}
table.insert(t, 0, 42) -- table.insert uses index 0 but arrays are 1-based; did you mean 1 instead?
table.insert(t, #t+1, 42) -- table.insert will append the value to the table; consider removing the second argument for efficiency
```
In addition, when type information is present, this warning will be emitted when `#` or `ipairs` is used on a table that has no numeric keys or indexers. This helps avoid common bugs like using `#t == 0` to check if a dictionary is empty.
```luau
local message = { data = { 1, 2, 3 } }
if #message == 0 then -- Using '#' on a table without an array part is likely a bug
end
```
## DuplicateCondition (24)
When checking multiple conditions via `and/or` or `if/elseif`, a copy & paste error may result in checking the same condition redundantly. This almost always indicates a bug, so a warning is emitted when use of a duplicate condition is detected.
```luau
--!hidden mode=nocheck
assert(self._adorns[normID1] and self._adorns[normID1]) -- Condition has already been checked on column 8
```
## MisleadingAndOr (25)
In Lua, there is no first-class ternary operator but it can be emulated via `a and b or c` pattern. However, due to how boolean evaluation works, if `b` is `false` or `nil`, the resulting expression evaluates to `c` regardless of the value of `a`. Luau solves this problem with the `if a then b else c` expression; a warning is emitted for and-or expressions where the first alternative is `false` or `nil` because it's almost always a bug.
```luau
--!hidden mode=nocheck
-- The and-or expression always evaluates to the second alternative because the first alternative is false; consider using if-then-else expression instead
local x = flag and false or true
```
The code above can be rewritten as follows to avoid the warning and the associated bug:
```luau
--!hidden mode=nocheck
local x = if flag then false else true
```
## CommentDirective (26)
Luau uses comments that start from `!` to control certain aspects of analysis, for example setting type checking mode via `--!strict` or disabling individual lints with `--!nolint`. Unknown directives are ignored, for example `--!nostrict` doesn't have any effect on the type checking process as the correct spelling is `--!nonstrict`. This warning flags comment directives that are ignored during processing:
```luau
--!nostrict
-- Unknown comment directive 'nostrict'; did you mean 'nonstrict'?"
```
## IntegerParsing (27)
Luau parses hexadecimal and binary literals as 64-bit integers before converting them to Luau numbers. As a result, numbers that exceed 2^64 are silently truncated to 2^64, which can result in unexpected program behavior. This warning flags literals that are truncated:
```luau
-- Hexadecimal number literal exceeded available precision and was truncated to 2^64
local x = 0x1111111111111111111111111111111111111
```
Luau numbers are represented as double precision IEEE754 floating point numbers; they can represent integers up to 2^53 exactly, but larger integers may lose precision. This warning also flags literals that are parsed with a precision loss:
```luau
-- Number literal exceeded available precision and was truncated to closest representable number
local x = 9007199254740993
```
## ComparisonPrecedence (28)
Because of operator precedence rules, not X == Y parses as (not X) == Y; however, often the intent was to invert the result of the comparison. This warning flags erroneous conditions like that, as well as flagging cases where two comparisons happen in a row without any parentheses:
```luau
--!hidden mode=nocheck
-- not X == Y is equivalent to (not X) == Y; consider using X ~= Y, or wrap one of the expressions in parentheses to silence
if not x == 5 then
end
-- X <= Y <= Z is equivalent to (X <= Y) <= Z; wrap one of the expressions in parentheses to silence
if 1 <= x <= 3 then
end
```

View file

@ -0,0 +1,318 @@
---
slug: syntax
title: Luau syntax by example
description: An informal reference for Luau's syntax by example.
sidebar:
order: 3
---
Luau uses the baseline [syntax of Lua 5.1](https://www.lua.org/manual/5.1/manual.html#2). For detailed documentation, please refer to the Lua manual, this is an example:
```luau
local function tree_insert(tree, x)
local lower, equal, greater = split(tree.root, x)
if not equal then
equal = {
x = x,
y = math.random(0, 2^31-1),
left = nil,
right = nil
}
end
tree.root = merge3(lower, equal, greater)
end
```
Note that future versions of Lua extend the Lua 5.1 syntax with more features; Luau does support string literal extensions but does not support other 5.x additions; for details please refer to [compatibility section](../compatibility).
The rest of this document documents additional syntax used in Luau.
## String literals
Luau implements support for hexadecimal (`\x`), Unicode (`\u`) and `\z` escapes for string literals. This syntax follows [Lua 5.3 syntax](https://www.lua.org/manual/5.3/manual.html#3.1):
- `\xAB` inserts a character with the code 0xAB into the string
- `\u{ABC}` inserts a UTF8 byte sequence that encodes U+0ABC character into the string (note that braces are mandatory)
- `\z` at the end of the line inside a string literal ignores all following whitespace including newlines, which can be helpful for breaking long literals into multiple lines.
## Number literals
In addition to basic integer and floating-point decimal numbers, Luau supports:
- Hexadecimal integer literals, `0xABC` or `0XABC`
- Binary integer literals, `0b01010101` or `0B01010101`
- Decimal separators in all integer literals, using `_` for readability: `1_048_576`, `0xFFFF_FFFF`, `0b_0101_0101`
Note that Luau only has a single number type, a 64-bit IEEE754 double precision number (which can represent integers up to 2^53 exactly), and larger integer literals are stored with precision loss.
## Continue statement
In addition to `break` in all loops, Luau supports `continue` statement. Similar to `break`, `continue` must be the last statement in the block.
Note that unlike `break`, `continue` is not a keyword. This is required to preserve backwards compatibility with existing code; so this is a `continue` statement:
```luau
--!hidden mode=nocheck
if x < 0 then
continue
end
```
Whereas this is a function call:
```luau
--!hidden mode=nocheck
if x < 0 then
continue()
end
```
When used in `repeat..until` loops, `continue` can not skip the declaration of a local variable if that local variable is used in the loop condition; code like this is invalid and won't compile:
```luau
repeat
do continue end
local a = 5
until a > 0
```
## Compound assignments
Luau supports compound assignments with the following operators: `+=`, `-=`, `*=`, `/=`, `//=`, `%=`, `^=`, `..=`. Just like regular assignments, compound assignments are statements, not expressions:
```luau
local a = 5
-- this works
a += 1
-- this doesn't work
print(a += 1)
```
Compound assignments only support a single value on the left and right hand side; additionally, the function calls on the left hand side are only evaluated once:
```luau
function foo() return 2 end
local a = { 1, 2, 3 }
-- calls foo() twice
a[foo()] = a[foo()] + 1
-- calls foo() once
a[foo()] += 1
```
Compound assignments call the arithmetic metamethods (`__add` et al) and table indexing metamethods (`__index` and `__newindex`) as needed - for custom types no extra effort is necessary to support them.
## Type annotations
To support gradual typing, Luau supports optional type annotations for variables and functions, as well as declaring type aliases.
Types can be declared for local variables, function arguments and function return types using `:` as a separator:
```luau
function foo(x: number, y: string): boolean
local k: string = y:rep(x)
return k == "a"
end
```
In addition, the type of any expression can be overridden using a type cast `::`:
```luau
function foo(x: number, y: unknown): boolean
local k = (y :: string):rep(x)
return k == "a"
end
```
There are several simple builtin types: `any` (represents inability of the type checker to reason about the type), `nil`, `boolean`, `number`, `string` and `thread`.
Function types are specified using the arguments and return types, separated with `->`:
```luau
local foo: (number, string) -> boolean
```
To return no values or more than one, you need to wrap the return type position with parentheses, and then list your types there.
```luau
local no_returns: (number, string) -> ()
local returns_boolean_and_string: (number, string) -> (boolean, string)
function foo(x: number, y: number): (number, string)
return x + y, tostring(x) .. tostring(y)
end
```
Note that function types are specified without the argument names in the examples above, but it's also possible to specify the names (that are not semantically significant but can show up in documentation and autocomplete):
```luau
local callback: (errorCode: number, errorText: string) -> ()
```
Table types are specified using the table literal syntax, using `:` to separate keys from values:
```luau
local array: { [number] : string }
local object: { x: number, y: string }
```
When the table consists of values keyed by numbers, it's called an array-like table and has a special short-hand syntax, `{T}` (e.g. `{string}`).
Additionally, the type syntax supports type intersections (`((number) -> string) & ((boolean) -> string)`) and unions (`(number | boolean) -> string`). An intersection represents a type with values that conform to both sides at the same time, which is useful for overloaded functions; a union represents a type that can store values of either type - `any` is technically a union of all possible types.
It's common in Lua for function arguments or other values to store either a value of a given type or `nil`; this is represented as a union (`number | nil`), but can be specified using `?` as a shorthand syntax (`number?`).
In addition to declaring types for a given value, Luau supports declaring type aliases via `type` syntax:
```luau
type Point = { x: number, y: number }
type Array<T> = { [number]: T }
type Something = typeof(string.gmatch("", "%d"))
```
The right hand side of the type alias can be a type definition or a `typeof` expression; `typeof` expression doesn't evaluate its argument at runtime.
By default type aliases are local to the file they are declared in. To be able to use type aliases in other modules using `require`, they need to be exported:
```luau
export type Point = { x: number, y: number }
```
An exported type can be used in another module by prefixing its name with the require alias that you used to import the module.
```luau
--!file main.luau
local M = require("./other")
local a: M.Point = {x=5, y=6}
--!file other.luau
export type Point = { x: number, y: number }
return {}
```
For more information please refer to [typechecking documentation](../types).
## If-then-else expressions
In addition to supporting standard if *statements*, Luau adds support for if *expressions*. Syntactically, `if-then-else` expressions look very similar to if statements. However instead of conditionally executing blocks of code, if expressions conditionally evaluate expressions and return the value produced as a result. Also, unlike if statements, if expressions do not terminate with the `end` keyword.
Here is a simple example of an `if-then-else` expression:
```luau
local a, b = 6, 7
local maxValue = if a > b then a else b
```
`if-then-else` expressions may occur in any place a regular expression is used. The `if-then-else` expression must match `if <expr> then <expr> else <expr>`; it can also contain an arbitrary number of `elseif` clauses, like `if <expr> then <expr> elseif <expr> then <expr> else <expr>`. Note that in either case, `else` is mandatory.
Here's is an example demonstrating `elseif`:
```luau
local x = -21
local sign = if x < 0 then -1 elseif x > 0 then 1 else 0
```
**Note:** In Luau, the `if-then-else` expression is preferred vs the standard Lua idiom of writing `a and b or c` (which roughly simulates a ternary operator). However, the Lua idiom may return an unexpected result if `b` evaluates to false. The `if-then-else` expression will behave as expected in all situations.
## Generalized iteration
Luau uses the standard Lua syntax for iterating through containers, `for vars in values`, but extends the semantics with support for generalized iteration. In Lua, to iterate over a table you need to use an iterator like `next` or a function that returns one like `pairs` or `ipairs`. In Luau, you can simply iterate over a table:
```luau
for k, v in {1, 4, 9} do
assert(k * k == v)
end
```
Further, iteration can be extended for tables or userdata by implementing the `__iter` metamethod which is called before the iteration begins, and should return an iterator function like `next` (or a custom one):
```luau
local obj = { items = {1, 4, 9} }
setmetatable(obj, { __iter = function(o) return next, o.items end })
for k, v in obj do
assert(k * k == v)
end
```
The default iteration order for tables is specified to be consecutive for elements `1..#t` and unordered after that, visiting every element; similarly to iteration using `pairs`, modifying the table entries for keys other than the current one results in unspecified behavior.
## String interpolation
Luau adds an additional way to define string values that allows you to place runtime expressions directly inside specific spots of the literal.
This is a more ergonomic alternative over using `string.format` or `("literal"):format`.
To use string interpolation, use a backtick string literal:
```luau
local count = 3
print(`Bob has {count} apple(s)!`)
--> Bob has 3 apple(s)!
```
Any expression can be used inside `{}`:
```luau
local combos = {2, 7, 1, 8, 5}
print(`The lock combination is {table.concat(combos)}.`)
--> The lock combination is 27185.
```
Inside backtick string literal, `\` is used to escape `` ` ``, `{`, `\` itself and a newline:
```luau
print(`Some example escaping the braces \{like so}`)
--> Some example escaping the braces {like so}
print(`Backslash \ that escapes the space is not a part of the string...`)
--> Backslash that escapes the space is not a part of the string...
print(`Backslash \\ will escape the second backslash...`)
--> Backslash \ will escape the second backslash...
print(`Some text that also includes \`...`)
--> Some text that also includes `...
local name = "Luau"
print(`Welcome to {
name
}!`)
--> Welcome to Luau!
```
### Restrictions and limitations
The sequence of two opening braces {{"`{{`"}} is rejected with a parse error.
This restriction is made to prevent developers using other programming languages with a similar feature from trying to attempt that as a way to escape a single `{` and getting unexpected results in Luau.
Luau currently does not support backtick string literals in type annotations, therefore `` type Foo = `Foo` `` is invalid syntax.
Unlike single and double-quoted string literals, backtick string literals must always be wrapped in parentheses for function calls:
```luau
print "hello" -- valid
print`hello` -- invalid syntax
print(`hello`) -- valid
```
## Floor division (`//`)
Luau supports floor division, including its operator (`//`), its compound assignment operator (`//=`), and overloading metamethod (`__idiv`), as an ergonomic alternative to `math.floor`.
For numbers, `a // b` is equal to `math.floor(a / b)`:
```luau
local n = 6.72
print(n // 2) --> 3
n //= 3
print(n) --> 2
```
Note that it's possible to get `inf`, `-inf`, or `NaN` with floor division; when `b` is `0`, `a // b` results in positive or negative infinity, and when both `a` and `b` are `0`, `a // b` results in NaN.
For native vectors, `c // d` applies `math.floor` to each component of the vector `c`. Therefore `c // d` is equivalent to `vector.create(math.floor(c.x / d), math.floor(c.y / b), math.floor(c.z / b))`.
Floor division syntax and semantics follow from [Lua 5.3](https://www.lua.org/manual/5.3/manual.html#3.4.1) where applicable.

View file

@ -0,0 +1,28 @@
---
slug: why
title: Why Luau?
sidebar:
order: 2
---
Around 2006, [Roblox](https://www.roblox.com) started using Lua 5.1 as a scripting language for games. Over the years the runtime had to be tweaked to provide a safe, secure sandboxed environment; we gradually started accumulating small library changes and tweaks.
Over the course of the last few years, instead of using Web-based stack for our player-facing application, Lua-based in-game UI and Qt-based editor UI, we've started consolidating a lot of the efforts and developing all of these using Roblox engine and Lua as a scripting language.
Having grown a substantial internal codebase that needed to be correct and performant, and with the focus shifting a bit from novice game developers to professional studios building games on Roblox and our own teams of engineers building applications, there was a need to improve performance and quality of the code we were writing.
Unlike mainline Lua, we also could not afford to do major breaking changes to the language (hence the 5.1 language baseline that remained unchanged for more than a decade). While faster implementations of Lua 5.1 like LuaJIT were available, they didn't meet our needs in terms of portability, ease of change and they didn't address the problem of developing robust code at scale.
All of these motivated us to start reshaping Lua 5.1 that we started from into a new, derivative language that we call Luau. Our focus is on making the language more performant and feature-rich, and make it easier to write robust code through a combination of linting and type checking using a gradual type system.
## Complete rewrite?
A very large part of Luau codebase is written from scratch. We needed a set of tools to be able to write language analysis tools; Lua has a parser that is integrated with the bytecode compiler, which makes it unsuitable for complex semantic analysis. For bytecode compilation, while a single pass compiler can deliver better compilation throughput and be simpler than a full frontend/backend, it significantly limits the optimizations that can be done at the bytecode level.
Luau compiler and analysis tools are thus written from scratch, closely following the syntax and semantics of Lua. Our compiler is not single-pass, and instead relies on a set of analysis passes that run over the AST to produce efficient bytecode, followed by some post-process optimizations.
As for the runtime, we had to rewrite the interpreter from scratch to get substantially faster performance; using a combination of techniques pioneered by LuaJIT and custom optimizations that are able to improve performance by taking control over the entire stack (language, compiler, interpreter, virtual machine), we're able to get close to LuaJIT interpreter performance while using C as an implementation language.
The garbage collector and the core libraries represent more of an incremental change, where we used Lua 5.1 as a baseline but we're continuing to rewrite these as well with performance in mind.
While Luau initially didn't include a JIT/AOT, native code generation has since been implemented for x64/arm64 platform as an optional component.