# How we make Luau fast

One of main goals of Luau is to enable high performance code, with gameplay code being the main use case. This can be viewed as two separate goals:

  * Make idiomatic code that wasn’t tuned faster
  * Enable even higher performance through careful tuning

Both of these goals are important - it’s insufficient to just focus on the highly tuned code, and all things being equal we prefer to raise all boats by implementing general optimizations. However, in some cases it’s important to be aware of optimizations that Luau does and doesn’t do.

Worth noting is that Luau is focused on, first and foremost, stable high performance code in interpreted context. This is because JIT compilation is not available on many platforms Luau runs on, and AOT compilation would only work for code that Roblox ships (and even that does not always work). This is in stark contrast with LuaJIT that, while providing an excellent interpreter as well, focuses a lot of the attention on JIT (with many optimizations unavailable in the interpreter).

Having said that, Luau has been updated to include an optional JIT component for x64 and arm64 platforms. This component can compile a selected set of functions, including limiting compilation to functions or modules marked explicitly by the user. While functions can be compiled at any time, automated JIT compilation decisions based on statistics/tracing are not performed. Luau JIT takes into account the type annotations present in the source code to specialize code paths and at this time, doesn’t include runtime analysis of the types/values flowing through the program.

The rest of this document goes into some optimizations that Luau employs and how to best leverage them when writing code. The document is not complete - a lot of optimizations are transparent to the user and involve detailed low-level tuning of various parts that is not described here - and all of this is subject to change without notice, as it doesn’t affect the semantics of valid code.

## Fast bytecode interpreter

Section titled “Fast bytecode interpreter”

Luau features a very highly tuned portable bytecode interpreter. It’s similar to Lua interpreter in that it’s written in C, but it’s highly tuned to yield efficient assembly when compiled with Clang and latest versions of MSVC. On some workloads it can match the performance of LuaJIT interpreter which is written in highly specialized assembly. We are continuing to tune the interpreter and the bytecode format over time; while extra performance can be extracted by rewriting the interpreter in assembly, we’re unlikely to ever do that as the extra gains at this point are marginal, and we gain a lot from C in terms of portability and being able to quickly implement new optimizations.

Of course the interpreter isn’t typical C code - it uses many tricks to achieve extreme levels of performance and to coerce the compiler to produce efficient assembly. Due to a better bytecode design and more efficient dispatch loop it’s noticeably faster than Lua 5.x (including Lua 5.4 which made some of the changes similar to Luau, but doesn’t come close). The bytecode design was partially inspired by excellent LuaJIT interpreter. Most computationally intensive scripts only use the interpreter core loop and builtins, which on x64 compiles into ~16 KB, thus leaving half of the instruction cache for other infrequently called code.

## Optimizing compiler

Section titled “Optimizing compiler”

Unlike Lua and LuaJIT, Luau uses a multi-pass compiler with a frontend that parses source into an AST and a backend that generates bytecode from it. This carries a small penalty in terms of compilation time, but results in more flexible code and, crucially, makes it easier to optimize the generated bytecode.

> Note: Compilation throughput isn’t the main focus in Luau, but our compiler is reasonably fast; with all currently implemented optimizations enabled, it compiles 950K lines of Luau code in 1 second on a single core of a desktop Ryzen 5900X CPU, producing bytecode and debug information.

While bytecode optimizations are limited due to the flexibility of Luau code (e.g. `a * 1` may not be equivalent to `a` if `*` is overloaded through metatables), even in absence of type information Luau compiler can perform some optimizations such as “deep” constant folding across functions and local variables, perform upvalue optimizations for upvalues that aren’t mutated, do analysis of builtin function usage, optimize the instruction sequences for multiple variable assignments, and some peephole optimizations on the resulting bytecode. The compiler can also be instructed to use more aggressive optimizations by enabling optimization level 2 (`-O2` in CLI tools), some of which are documented further on this page.

Most bytecode optimizations are performed on individual statements or functions, however the compiler also does a limited amount of interprocedural optimizations; notably, calls to local functions can be optimized with the knowledge of the argument count or number of return values involved. Interprocedural optimizations are limited to a single module due to the compilation model.

Luau compiler is also able to use type information to do further optimizations. Because we control the entire stack (unlike e.g. TypeScript where the type information is discarded completely before reaching the VM), we have more flexibility there and can make some tradeoffs during codegen even if the type system isn’t completely sound. For example, it might be reasonable to assume that in presence of known types, we can infer absence of side effects for arithmetic operations and builtins - if the runtime types mismatch due to intentional violation of the type safety through global injection, the code will still be safely sandboxed. Type information is currently limited to small peephole optimizations, but it has a potential to unlock optimizations such as common subexpression elimination and allocation hoisting in the future, without having to rely on a JIT. These future optimizations opportunities are speculative pending further research.

## Epsilon-overhead debugger

Section titled “Epsilon-overhead debugger”

It’s important for Luau to have stable and predictable performance. Something that comes up in Lua-based environments often is the use of line hooks to implement debugging (both for breakpoints and for stepping). This is problematic because the support for hooks is typically not free in general, but importantly once the hook is enabled, calling the hook has a considerable overhead, and the hook itself may be very costly to evaluate since it will need to associate the script:line pair with the breakpoint information.

Luau does not support hooks at all, and relies on first-class support for breakpoints (using bytecode patching) and single-stepping (using a custom interpreter loop) to implement debugging. As a result, the presence of breakpoints doesn’t slow the script execution down - the only noticeable discrepancy between running code under a debugger and without a debugger should be in cases where breakpoints are evaluated and skipped based on breakpoint conditions, or when stepping over long-running fragments of code.

## Inline caching for table and global access

Section titled “Inline caching for table and global access”

Table access for field lookup is optimized in Luau using a mechanism that blends inline caching (classically used in Java/JavaScript VMs) and HREFs (implemented in LuaJIT). Compiler can predict the hash slot used by field lookup, and the VM can correct this prediction dynamically.

As a result, field access can be very fast in Luau, provided that:

  * The field name is known at compile time. To make sure this is the case, `table.field` notation is recommended, although the compiler will also optimize `table["field"]` when the expression is known to be a constant string.
  * The field access doesn’t use metatables. The fastest way to work with tables in Luau is to store fields directly inside the table, and store methods in the metatable (see below); access to “static” fields in classic OOP designs is best done through `Class.StaticField` instead of `object.StaticField`.
  * The object structure is usually uniform. While it’s possible to use the same function to access tables of different shape - e.g. `function getX(obj) return obj.x end` can be used on any table that has a field `"x"` \- it’s best to not vary the keys used in the tables too much, as it defeats this optimization.

The same optimization is applied to the custom globals declared in the script, although it’s best to avoid these altogether by using locals instead. Still, this means that the difference between `function` and `local function` is less pronounced in Luau.

## Importing global access chains

Section titled “Importing global access chains”

While global access for library functions can be optimized in a similar way, this optimization breaks down when the global table is using sandboxing through metatables, and even when globals aren’t sandboxed, `math.max` still requires two table accesses.

It’s always possible to “localize” the global accesses by using `local max = math.max`, but this is cumbersome - in practice it’s easy to forget to apply this optimization. To avoid relying on programmers remembering to do this, Luau implements a special optimization called “imports”, where most global chains such as `math.max` are resolved when the script is loaded instead of when the script is executed.

This optimization relies on being able to predict the shape of the environment table for a given function; this is possible due to global sandboxing, however this optimization is invalid in some cases:

  * `loadstring` can load additional code that runs in context of the caller’s environment
  * `getfenv`/`setfenv` can directly modify the environment of any function

The use of any of these functions performs a dynamic deoptimization, marking the affected environment as “impure”. The optimizations are only in effect on functions with “pure” environments - because of this, the use of `loadstring`/`getfenv`/`setfenv` is not recommended. Note that `getfenv` deoptimizes the environment even if it’s only used to read values from the environment.

> Note: Luau still supports these functions as part of our backwards compatibility promise, although we’d love to switch to Lua 5.2’s `_ENV` as that mechanism is cleaner and doesn’t require costly dynamic deoptimization.

## Fast method calls

Section titled “Fast method calls”

Luau specializes method calls to improve their performance through a combination of compiler, VM and binding optimizations. Compiler emits a specialized instruction sequence when methods are called through `obj:Method` syntax (while this isn’t idiomatic anyway, you should avoid `obj.Method(obj)`). When the object in question is a Lua table, VM performs some voodoo magic based on inline caching to try to quickly discover the implementation of this method through the metatable.

For this to be effective, it’s crucial that `__index` in a metatable points to a table directly. For performance reasons it’s strongly recommended to avoid `__index` functions as well as deep `__index` chains; an ideal object in Luau is a table with a metatable that points to itself through `__index`.

When the object in question is a reflected userdata, a special mechanism called “namecall” is used to minimize the interop cost. In classical Lua binding model, `obj:Method` is called in two steps, retrieving the function object (`obj.Method`) and calling it; both steps are often implemented in C++, and the method retrieval needs to use a method object cache - all of this makes method calls slow.

Luau can directly call the method by name using the “namecall” extension, and an optimized reflection layer can retrieve the correct method quickly through more voodoo magic based on string interning and custom Luau features that aren’t exposed through Luau scripts.

As a result of both optimizations, common Lua tricks of caching the method in a local variable aren’t very productive in Luau and aren’t recommended either.

## Specialized builtin function calls

Section titled “Specialized builtin function calls”

Due to global sandboxing and the ability to dynamically deoptimize code running in impure environments, in pure environments we go beyond optimizing the interpreter and optimize many built-in functions through a “fastcall” mechanism.

For this mechanism to work, function call must be “obvious” to the compiler - it needs to call a builtin function directly, e.g. `math.max(x, 1)`, although it also works if the function is “localized” (`local max = math.max`); this mechanism doesn’t work for indirect function calls unless they were inlined during compilation, and doesn’t work for method calls (so calling `string.byte` is more efficient than `s:byte`).

The mechanism works by directly invoking a highly specialized and optimized implementation of a builtin function from the interpreter core loop without setting up a stack frame and omitting other work; additionally, some fastcall specializations are partial in that they don’t support all types of arguments, for example all `math` library builtins are only specialized for numeric arguments, so calling `math.abs` with a string argument will fall back to the slower implementation that will do string->number coercion.

As a result, builtin calls are very fast in Luau - they are still slightly slower than core instructions such as arithmetic operations, but only slightly so. The set of fastcall builtins is slowly expanding over time and as of this writing contains `assert`, `type`, `typeof`, `rawget`/`rawset`/`rawequal`, `getmetatable`/`setmetatable`, `tonumber`/`tostring`, all functions from `math` (except `noise` and `random`/`randomseed`) and `bit32`, and some functions from `string` and `table` library.

Some builtin functions have partial specializations that reduce the cost of the common case further. Notably:

  * `assert` is specialized for cases when the assertion return value is not used and the condition is truthy; this helps reduce the runtime cost of assertions to the extent possible
  * `bit32.extract` is optimized further when field and width selectors are constant
  * `select` is optimized when the second argument is `...`; in particular, `select(x, ...)` is O(1) when using the builtin dispatch mechanism even though it’s normally O(N) in variadic argument count.

Some functions from `math` library like `math.floor` can additionally take advantage of advanced SIMD instruction sets like SSE4.1 when available.

In addition to runtime optimizations for builtin calls, many builtin calls, as well as constants like `math.pi`/`math.huge`, can also be constant-folded by the bytecode compiler when using aggressive optimizations (level 2); this currently applies to most builtin calls with constant arguments and a single return value. For builtin calls that can not be constant folded, compiler assumes knowledge of argument/return count (level 2) to produce more efficient bytecode instructions.

## Optimized table iteration

Section titled “Optimized table iteration”

Luau implements a fully generic iteration protocol; however, for iteration through tables in addition to generalized iteration (`for .. in t`) it recognizes three common idioms (`for .. in ipairs(t)`, `for .. in pairs(t)` and `for .. in next, t`) and emits specialized bytecode that is carefully optimized using custom internal iterators.

As a result, iteration through tables typically doesn’t result in function calls for every iteration; the performance of iteration using generalized iteration, `pairs` and `ipairs` is comparable, so generalized iteration (without the use of `pairs`/`ipairs`) is recommended unless the code needs to be compatible with vanilla Lua or the specific semantics of `ipairs` (which stops at the first `nil` element) is required. Additionally, using generalized iteration avoids calling `pairs` when the loop starts which can be noticeable when the table is very short.

Iterating through array-like tables using `for i=1,#t` tends to be slightly slower because of extra cost incurred when reading elements from the table.

## Optimized table length

Section titled “Optimized table length”

Luau tables use a hybrid array/hash storage, like in Lua; in some sense “arrays” don’t truly exist and are an internal optimization, but some operations, notably `#t` and functions that depend on it, like `table.insert`, are defined by the Luau/Lua language to allow internal optimizations. Luau takes advantage of that fact.

Unlike Lua, Luau guarantees that the element at index `#t` is stored in the array part of the table. This can accelerate various table operations that use indices limited by `#t`, and this makes `#t` worst-case complexity O(logN), unlike Lua where the worst case complexity is O(N). This also accelerates computation of this value for small tables like `{ [1] = 1 }` since we never need to look at the hash part.

The “default” implementation of `#t` in both Lua and Luau is a binary search. Luau uses a special branch-free (depending on the compiler…) implementation of the binary search which results in 50+% faster computation of table length when it needs to be computed from scratch.

Additionally, Luau can cache the length of the table and adjust it following operations like `table.insert`/`table.remove`; this means that in practice, `#t` is almost always a constant time operation.

## Creating and modifying tables

Section titled “Creating and modifying tables”

Luau implements several optimizations for table creation. When creating object-like tables, it’s recommended to use table literals (`{ ... }`) and to specify all table fields in the literal in one go instead of assigning fields later; this triggers an optimization inspired by LuaJIT’s “table templates” and results in higher performance when creating objects. When creating array-like tables, if the maximum size of the table is known up front, it’s recommended to use `table.create` function which can create an empty table with preallocated storage, and optionally fill it with a given value.

When the exact table shape isn’t known, Luau compiler can still predict the table capacity required in case the table is initialized with an empty literal (`{}`) and filled with fields subsequently. For example, the following code creates a correctly sized table implicitly:

When appending elements to tables, it’s recommended to use `table.insert` (which is the fastest method to append an element to a table if the table size is not known). In cases when a table is filled sequentially, however, it can be more efficient to use a known index for insertion - together with preallocating tables using `table.create` this can result in much faster code, for example this is the fastest way to build a table of squares:

## Native vector math

Section titled “Native vector math”

Luau uses tagged value storage - each value contains a type tag and the data that represents the value of a given type. Because of the need to store 64-bit double precision numbers _and_ 64-bit pointers, we don’t use NaN tagging and have to pay the cost of 16 bytes per value.

We take advantage of this to provide a native value type that can store a 32-bit floating point vector with 3 components. This type is fundamental to game computations and as such it’s important to optimize the storage and the operations with that type - our VM implements first class support for all math operations and component manipulation, which essentially means we have native 3-wide SIMD support. For code that uses many vector values this results in significantly smaller GC pressure and significantly faster execution, and gives programmers a mechanism to hand-vectorize numeric code if need be.

## Optimized upvalue storage

Section titled “Optimized upvalue storage”

Lua implements upvalues as garbage collected objects that can point directly at the thread’s stack or, when the value leaves the stack frame (and is “closed”), store the value inside the object. This representation is necessary when upvalues are mutated, but inefficient when they aren’t - and 90% or more of upvalues aren’t mutated in typical Lua code. Luau takes advantage of this by reworking upvalue storage to prioritize immutable upvalues - capturing upvalues that don’t change doesn’t require extra allocations or upvalue closing, resulting in faster closure allocation, faster execution, faster garbage collection and faster upvalue access due to better memory locality.

Note that “immutable” in this case only refers to the variable itself - if the variable isn’t assigned to it can be captured by value, even if it’s a table that has its contents change.

When upvalues are mutable, they do require an extra allocated object; we carefully optimize the memory consumption and access cost for mutable upvalues to reduce the associated overhead.

## Closure caching

Section titled “Closure caching”

With optimized upvalue storage, creating new closures (function objects) is more efficient but still requires allocating a new object every time. This can be problematic for cases when functions are passed to algorithms like `table.sort` or functions like `pcall`, as it results in excessive allocation traffic which then leads to more work for garbage collector.

To make closure creation cheaper, Luau compiler implements closure caching - when multiple executions of the same function expression are guaranteed to result in the function object that is semantically identical, the compiler may cache the closure and always return the same object. This changes the function identity which may affect code that uses function objects as table keys, but preserves the calling semantics - compiler will only do this if calling the original (cached) function behaves the same way as a newly created function would. The heuristics used for this optimization are subject to change; currently, the compiler will cache closures that have no upvalues, or all upvalues are immutable (see previous section) and are declared at the module scope, as the module scope is (almost always) evaluated only once.

## Fast memory allocator

Section titled “Fast memory allocator”

Similarly to LuaJIT, but unlike vanilla Lua, Luau implements a custom allocator that is highly specialized and tuned to the common allocation workloads we see. The allocator design is inspired by classic pool allocators as well as the excellent `mimalloc`, but through careful domain-specific tuning it beats all general purpose allocators we’ve tested, including `rpmalloc`, `mimalloc`, `jemalloc`, `ptmalloc` and `tcmalloc`.

This doesn’t mean that memory allocation in Luau is free - it’s carefully optimized, but it still carries a cost, and a high rate of allocations requires more work from the garbage collector. The garbage collector is incremental, so short of some edge cases this rarely results in visible GC pauses, but can impact the throughput since scripts will interrupt to perform “GC assists” (helping clean up the garbage). Thus for high performance Luau code it’s recommended to avoid allocating memory in tight loops, by avoiding temporary table and userdata creation.

In addition to a fast allocator, all frequently used structures in Luau have been optimized for memory consumption, especially on 64-bit platforms, compared to Lua 5.1 baseline. This helps to reduce heap memory footprint and improve performance in some cases by reducing the memory bandwidth impact of garbage collection.

## Optimized libraries

Section titled “Optimized libraries”

While the best performing code in Luau spends most of the time in the interpreter, performance of the standard library functions is critical to some applications. In addition to specializing many small and simple functions using the builtin call mechanism, we spend extra care on optimizing all library functions and providing additional functions beyond the Lua standard library that help achieve good performance with idiomatic code.

Functions from the `table` library like `insert`, `remove` and `move` have been tuned for performance on array-like tables, achieving 3x and more performance compared to un-tuned versions, and Luau provides additional functions like `table.create` and `table.find` to achieve further speedup when applicable. Our implementation of `table.sort` is using `introsort` algorithm which results in guaranteed worst case `NlogN` complexity regardless of the input, and, together with the array-like specializations, helps achieve ~4x speedup on average.

For `string` library, we use a carefully tuned dynamic string buffer implementation; it is optimized for smaller strings to reduce garbage created during string manipulation, and for larger strings it allows to produce a large string without extra copies, especially in cases where the resulting size is known ahead of time. Additionally, functions like `format` have been tuned to avoid the overhead of `sprintf` where possible, resulting in further speedups.

## Improved garbage collector pacing

Section titled “Improved garbage collector pacing”

Luau uses an incremental garbage collector which does a little bit of work every so often, and at no point does it stop the world to traverse the entire heap. The runtime will make sure that the collector runs interspersed with the program execution as the program allocates additional memory, which is known as “garbage collection assists”, and can also run in response to explicit garbage collection invocation via `lua_gc`. In interactive environments such as video game engines it’s possible, and even desirable, to request garbage collection every frame to make sure assists are minimized, since that allows scheduling the garbage collection to run concurrently with other engine processing that doesn’t involve script execution.

Inspired by excellent work by Austin Clements on Go’s garbage collector pacer, we’ve implemented a pacing algorithm that uses a proportional–integral–derivative controller to estimate internal garbage collector tunables to reach a target heap size, defined as a percentage of the live heap data (which is more intuitive and actionable than Lua 5.x “GC pause” setting). Luau runtime also estimates the allocation rate making it easy (given uniform allocation rates) to adjust the per-frame garbage collection requests to do most of the required GC work outside of script execution.

## Reduced garbage collector pauses

Section titled “Reduced garbage collector pauses”

While Luau uses an incremental garbage collector, once per each collector cycle it runs a so-called “atomic” step. While all other GC steps can do very little work by only looking at a few objects at a given time, which means that the collector can have arbitrarily short pauses, the “atomic” step needs to traverse some amount of data that, in some cases, may scale with the application heap. Since atomic step is indivisible, it can result in occasional pauses on the order of tens of milliseconds, which is problematic for interactive applications. We’ve implemented a series of optimizations to help reduce the atomic step.

Normally objects that have been modified after the GC marked them in an incremental mark phase need to be rescanned during atomic phase, so frequent modifications of existing tables may result in a slow atomic step. To address this, we run a “remark” step where we traverse objects that have been modified after being marked once more (incrementally); additionally, the write barrier that triggers for object modifications changes the transition logic during remark phase to reduce the probability that the object will need to be rescanned.

Another source of scalability challenges is coroutines. Writes to coroutine stacks don’t use a write barrier, since that’s prohibitively expensive as they are too frequent. This means that coroutine stacks need to be traversed during atomic step, so applications with many coroutines suffer large atomic pauses. To address this, we implement incremental marking of coroutines: marking a coroutine makes it “inactive” and resuming a coroutine (or pushing extra objects on the coroutine stack via C API) makes it “active”. Atomic step only needs to traverse active coroutines again, which reduces the cost of atomic step by effectively making coroutine collection incremental as well.

While large tables can be a problem for incremental GC in general since currently marking a single object is indivisible, large weak tables are a unique challenge because they also need to be processed during atomic phase, and the main use case for weak tables - object caches - may result in tables with large capacity but few live objects in long-running applications that exhibit bursts of activity. To address this, weak tables in Luau can be marked as “shrinkable” by including `s` as part of `__mode` string, which results in weak tables being resized to the optimal capacity during GC. This option may result in missing keys during table iteration if the table is resized while iteration is in progress and as such is only recommended for use in specific circumstances.

## Optimized garbage collector sweeping

Section titled “Optimized garbage collector sweeping”

The incremental garbage collector in Luau runs three phases for each cycle: mark, atomic and sweep. Mark incrementally traverses all live objects, atomic finishes various operations that need to happen without mutator intervention (see previous section), and sweep traverses all objects in the heap, reclaiming memory used by dead objects and performing minor fixup for live objects. While objects allocated during the mark phase are traversed in the same cycle and thus may get reclaimed, objects allocated during the sweep phase are considered live. Because of this, the faster the sweep phase completes, the less garbage will accumulate; and, of course, the less time sweeping takes the less overhead there is from this phase of garbage collection on the process.

Since sweeping traverses the whole heap, we maximize the efficiency of this traversal by allocating garbage-collected objects of the same size in 16 KB pages, and traversing each page at a time, which is otherwise known as a paged sweeper. This ensures good locality of reference as consecutively swept objects are contiguous in memory, and allows us to spend no memory for each object on sweep-related data or allocation metadata, since paged sweeper doesn’t need to be able to free objects without knowing which page they are in. Compared to linked list based sweeping that Lua/LuaJIT implement, paged sweeper is 2-3x faster, and saves 16 bytes per object on 64-bit platforms.

## Function inlining and loop unrolling

Section titled “Function inlining and loop unrolling”

By default, the bytecode compiler performs a series of optimizations that result in faster execution of the code, but they preserve both execution semantics and debuggability. For example, a function call is compiled as a function call, which may be observable via `debug.traceback`; a loop is compiled as a loop, which may be observable via `lua_getlocal`. To help improve performance in cases where these restrictions can be relaxed, the bytecode compiler implements additional optimizations when optimization level 2 is enabled (which requires using `-O2` switch when using Luau CLI), namely function inlining and loop unrolling.

Only loops with loop bounds known at compile time, such as `for i=1,4 do`, can be unrolled. The loop body must be simple enough for the optimization to be profitable; compiler uses heuristics to estimate the performance benefit and automatically decide if unrolling should be performed.

Only local functions (defined either as `local function foo` or `local foo = function`) can be inlined. The function body must be simple enough for the optimization to be profitable; compiler uses heuristics to estimate the performance benefit and automatically decide if each call to the function should be inlined instead. Additionally recursive invocations of a function can’t be inlined at this time, and inlining is completely disabled for modules that use `getfenv`/`setfenv` functions.

In both cases, in addition to removing the overhead associated with function calls or loop iteration, these optimizations can additionally benefit by enabling additional optimizations, such as constant folding of expressions dependent on loop iteration variable or constant function arguments, or using more efficient instructions for certain expressions when the inputs to these instructions are constants.


# News

The latest news from the Luau team! Stay up to date with new features, performance improvements, and language updates.

[Luau Recap for 2025: Runtime Posted December 19, 2025 ](https://luau.org/news/2025-12-19-luau-recap-runtime-2025)[Recap: July 2024 Posted July 23, 2024 ](https://luau.org/news/2024-07-23-luau-recap-july-2024)[Recap: October 2023 Posted November 1, 2023 ](https://luau.org/news/2023-11-01-luau-recap-october-2023)[Recap: July 2023 Posted July 28, 2023 ](https://luau.org/news/2023-07-28-luau-recap-july-2023)[Recap: March 2023 Posted March 31, 2023 ](https://luau.org/news/2023-03-31-luau-recap-march-2023)[String Interpolation Posted February 2, 2023 ](https://luau.org/news/2023-02-02-luau-string-interpolation)[Recap: November 2022 Posted November 30, 2022 ](https://luau.org/news/2022-11-30-luau-recap-november-2022)[Luau origins and evolution Posted November 4, 2022 ](https://luau.org/news/2022-11-04-luau-origins-and-evolution)[Recap: September & October 2022 Posted November 1, 2022 ](https://luau.org/news/2022-11-01-luau-recap-september-october-2022)[Semantic Subtyping in Luau Posted October 31, 2022 ](https://luau.org/news/2022-10-31-luau-semantic-subtyping)[Recap: July & August 2022 Posted August 29, 2022 ](https://luau.org/news/2022-08-29-luau-recap-august-2022)[Recap: June 2022 Posted July 7, 2022 ](https://luau.org/news/2022-07-07-luau-recap-june-2022)[Recap: May 2022 Posted June 1, 2022 ](https://luau.org/news/2022-06-01-luau-recap-may-2022)[Recap: April 2022 Posted May 2, 2022 ](https://luau.org/news/2022-05-02-luau-recap-april-2022)[Recap: March 2022 Posted March 31, 2022 ](https://luau.org/news/2022-03-31-luau-recap-march-2022)[Recap: February 2022 Posted February 28, 2022 ](https://luau.org/news/2022-02-28-luau-recap-february-2022)[Recap: January 2022 Posted January 27, 2022 ](https://luau.org/news/2022-01-27-luau-recap-january-2022)[Recap: November 2021 Posted November 29, 2021 ](https://luau.org/news/2021-11-29-luau-recap-november-2021)[Luau Goes Open-Source Posted November 3, 2021 ](https://luau.org/news/2021-11-03-luau-goes-open-source)[Recap: October 2021 Posted October 31, 2021 ](https://luau.org/news/2021-10-31-luau-recap-october-2021)[Recap: September 2021 Posted September 30, 2021 ](https://luau.org/news/2021-09-30-luau-recap-september-2021)[Recap: August 2021 Posted August 31, 2021 ](https://luau.org/news/2021-08-31-luau-recap-august-2021)[Recap: July 2021 Posted July 30, 2021 ](https://luau.org/news/2021-07-30-luau-recap-july-2021)[Recap: June 2021 Posted June 30, 2021 ](https://luau.org/news/2021-06-30-luau-recap-june-2021)[Recap: May 2021 Posted May 31, 2021 ](https://luau.org/news/2021-05-31-luau-recap-may-2021)[Recap: April 2021 Posted April 30, 2021 ](https://luau.org/news/2021-04-30-luau-recap-april-2021)[Recap: March 2021 Posted March 29, 2021 ](https://luau.org/news/2021-03-29-luau-recap-march-2021)[Recap: February 2021 Posted March 1, 2021 ](https://luau.org/news/2021-03-01-luau-recap-february-2021)[Luau Type Checking Release Posted November 19, 2020 ](https://luau.org/news/2020-11-19-luau-type-checking-release)[Recap: October 2020 Posted October 30, 2020 ](https://luau.org/news/2020-10-30-luau-recap-october-2020)[Recap: August 2020 Posted August 11, 2020 ](https://luau.org/news/2020-08-11-luau-recap-august-2020)[Recap: June 2020 Posted June 20, 2020 ](https://luau.org/news/2020-06-20-luau-recap-june-2020)[Recap: May 2020 Posted May 18, 2020 ](https://luau.org/news/2020-05-18-luau-recap-may-2020)[Recap: February 2020 Posted February 25, 2020 ](https://luau.org/news/2020-02-25-luau-recap-february-2020)[Luau Type Checking Beta Posted January 16, 2020 ](https://luau.org/news/2020-01-16-luau-type-checking-beta)[Recap: November 2019 Posted November 11, 2019 ](https://luau.org/news/2019-11-11-luau-recap-november-2019)


# Additional Considerations

## Module interactions

Section titled “Module interactions”

Let’s say that we have two modules, `Foo` and `Bar`. Luau will try to resolve the paths if it can find any `require` in any scripts. In this case, when you say `./bar`, Luau will resolve it as: relative to this script, go to my sibling script named Bar.

There are some caveats here though. For instance, the require path must be resolvable statically, otherwise Luau cannot accurately type check it.

### Cyclic module dependencies

Section titled “Cyclic module dependencies”

Cyclic module dependencies can cause problems for the type checker. In order to break a module dependency cycle a typecast of the module to `any` may be used:


# Compatibility with Lua

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

Section titled “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

Section titled “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](https://luau.org/sandbox).

## Lua 5.2

Section titled “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

Section titled “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

Section titled “Lua 5.4”

feature| status| notes
---|---|---
new generational mode for garbage collection| 🤷♀| we’ve implemented other optimizations to garbage collection
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| ❌| available through `const var = value` 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.

Const variables are available in Luau through a `const var = value` syntax, which is backwards compatible through a context-sensitive keyword (similar to `type`).

## Lua 5.5

Section titled “Lua 5.5”

feature| status| notes
---|---|---
declarations for global variables| ❌| no strong use cases, mistakes are caught by typechecker
named vararg tables| 🤷♀|
for-loop variables are read only| ❌| breaks compatibility
floats are printed in decimal with enough digits to be read back correctly| ✔|
`table.create(arraysize, recordsize)`| ❌| breaks compatibility
`utf8.offset` returns also final position of character| 🤷♀|
external strings| ❌| not compatible with internal object structure;
new functions `luaL_openselectedlibs` and `luaL_makeseed`| 🤷♀|

## Differences from Lua

Section titled “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


# Primitives and Simple Types

## Builtin types

Section titled “Builtin types”

The Luau VM supports 10 primitive types:

  1. `nil`
  2. `string`
  3. `number`
  4. `boolean`
  5. `table`
  6. `function`
  7. `thread`
  8. `userdata`
  9. `vector`
  10. `buffer`

Most of these can be specified by their name and written directly in type annotations:

Some types have special syntax:

  * `table` and `function` are not represented by name, but have their dedicated syntax as covered in this [syntax document](https://luau.org/syntax)
  * `userdata` is represented by [concrete types](https://luau.org/types/roblox-types), and
  * `vector` is not representable by name at all

The type checker also provides the builtin types `unknown`, `never`, and `any`.

#### Special behavior with `nil`

Section titled “Special behavior with nil”

There’s a special case where we intentionally avoid inferring `nil` for local variables. This allows you to declare variables first and assign values to them later — if we inferred `nil`, you wouldn’t be able to assign other types to these variables.

### `unknown` type

Section titled “unknown type”

`unknown` is also said to be the _top_ type, that is it’s a union of all types.

Unlike `any`, `unknown` will not allow itself to be used as a different type!

In order to turn a variable of type `unknown` into a different type, you must apply [type refinements](https://luau.org/types/types/refinements.md) on that variable.

### `never` type

Section titled “never type”

`never` is also said to be the _bottom_ type, meaning there doesn’t exist a value that inhabits the type `never`. In fact, it is the _dual_ of `unknown`. `never` is useful in many scenarios, and one such use case is when type refinements proves it impossible:

### `any` type

Section titled “any type”

`any` is just like `unknown`, except that it allows itself to be used as an arbitrary type without further checks or annotations. Essentially, it’s an opt-out from the type system entirely.

## Function types

Section titled “Function types”

Let’s start with something simple.

In strict mode, the inferred type of this function `f` is `<A>(A) -> A` (take a look at [generics](https://luau.org/types/generics)), whereas in nonstrict we infer `(any) -> any`. We know this is true because `f` can take anything and then return that. If we used `x` with another concrete type, then we would end up inferring that.

Similarly, we can infer the types of the parameters with ease. By passing a parameter into _anything_ that also has a type, we are saying “this and that has the same type.”

## Variadic types

Section titled “Variadic types”

Luau permits assigning a type to the `...` variadic symbol like any other parameter:

`f` accepts any number of `number` values.

In type annotations, this is written as `...T`:

## Type packs

Section titled “Type packs”

Multiple function return values as well as the function variadic parameter use a type pack to represent a list of types.

When a type alias is defined, generic type pack parameters can be used after the type parameters:

> Keep in mind that `...T` is a variadic type pack (many elements of the same type `T`), while `U...` is a generic type pack that can contain zero or more types and they don’t have to be the same.

It is also possible for a generic function to reference a generic type pack from the generics list:

Generic types with type packs can be instantiated by providing a type pack:

There are also other ways to instantiate types with generic type pack parameters:

Trailing type pack argument can also be provided without parentheses by specifying variadic type arguments:

Type pack parameters are not limited to a single one, as many as required can be specified:

## Singleton types (aka literal types)

Section titled “Singleton types (aka literal types)”

Luau’s type system also supports singleton types, which means it’s a type that represents one single value at runtime. At this time, both string and booleans are representable in types.

> We do not currently support numbers as types. For now, this is intentional.

This happens all the time, especially through [type refinements](https://luau.org/types/type-refinements) and is also incredibly useful when you want to enforce program invariants in the type system! See [tagged unions](https://luau.org/types/unions-and-intersections/#tagged-unions) for more information.


# Type Function Library

The `types` library is used to create and transform types, and can only be used within [type functions](https://luau.org/types/type-functions).

### `types` library properties

Section titled “types library properties”

    types.any

The [any](https://luau.org/types/basic-types#any-type) `type`.

    types.unknown

The [unknown](https://luau.org/types/basic-types#unknown-type) `type`.

    types.never

The [never](https://luau.org/types/basic-types#never-type) `type`.

    types.boolean

The boolean `type`.

    types.buffer

The [buffer](https://luau.org/library#buffer-library) `type`.

    types.number

The number `type`.

    types.string

The string `type`.

    types.thread

The thread `type`.

## `types` library functions

Section titled “types library functions”

    types.singleton(arg: string | boolean | nil): type

Returns the [singleton](https://luau.org/types/basic-types#singleton-types-aka-literal-types) type of the argument.

    types.negationof(arg: type): type

Returns an immutable negation of the argument type.

    types.optional(arg: type): type

Returns a version of the given type that is now optional.

  * If the given type is a [union type](https://luau.org/types/unions-and-intersections#union-types), `nil` will be added unconditionally as a component.
  * Otherwise, the result will be a union of the given type and the `nil` type.

    types.unionof(first: type, second: type, ...: type): type

Returns an immutable [union](https://luau.org/types/unions-and-intersections#union-types) of two or more arguments.

    types.intersectionof(first: type, second: type, ...: type): type

Returns an immutable [intersection](https://luau.org/types/unions-and-intersections#intersection-types) of two or more arguments.

    types.newtable(props: { [type]: type | { read: type?, write: type? } }?, indexer: { index: type, readresult: type, writeresult: type? }?, metatable: type?): type

Returns a fresh, mutable table `type`. Property keys must be string singleton `type`s. The table’s metatable is set if one is provided.

    types.newfunction(parameters: { head: {type}?, tail: type? }, returns: { head: {type}?, tail: type? }?, generics: {type}?): type

Returns a fresh, mutable function `type`, using the ordered parameters of `head` and the variadic tail of `tail`.

    types.copy(arg: type): type

Returns a deep copy of the argument type.

    types.generic(name: string?, ispack: boolean?): type

Creates a [generic](https://luau.org/types/generics#generic-functions) named `name`. If `ispack` is `true`, the result is a [generic pack](https://luau.org/types/basic-types#type-packs).

### `type` instance

Section titled “type instance”

`type` instances can have extra properties and methods described in subsections depending on its tag.

    type.tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "thread" | "buffer"

An immutable property holding the type’s tag.

    __eq(arg: type): boolean

Overrides the `==` operator to return `true` if `self` is syntactically equal to `arg`. This excludes semantically equivalent types, `true | false` is unequal to `boolean`.

    type:is(arg: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "thread" | "buffer")

Returns `true` if `self` has the argument as its tag.

### Singleton `type` instance

Section titled “Singleton type instance”

    singletontype:value(): boolean | nil | "string"

Returns the singleton’s actual value, like `true` for `types.singleton(true)`.

### Generic `type` instance

Section titled “Generic type instance”

    generictype:name(): string?

Returns the name of the [generic](https://luau.org/types/generics#generic-functions) or `nil` if it has no name.

    generictype:ispack(): boolean

Returns `true` if the [generic](https://luau.org/types/generics#generic-functions) is a [pack](https://luau.org/types/basic-types#type-packs), or `false` otherwise.

### Table `type` instance

Section titled “Table type instance”

    tabletype:setproperty(key: type, value: type?)

Sets the type of the property for the given `key`, using the same type for both reading from and writing to the table.

  * `key` is expected to be a string singleton type, naming the property.
  * `value` will be set as both the `read type` and `write type` of the property.
  * If `value` is `nil`, the property is removed.

    tabletype:setreadproperty(key: type, value: type?)

Sets the type for reading from the property named by `key`, leaving the type for writing this property as-is.

  * `key` is expected to be a string singleton type, naming the property.
  * `value` will be set as the `read type`, the `write type` will be unchanged.
  * If `key` is not already present, only a `read type` will be set, making the property read-only.
  * If `value` is `nil`, the property is removed.

    tabletype:setwriteproperty(key: type, value: type?)

Sets the type for writing to the property named by `key`, leaving the type for reading this property as-is.

  * `key` is expected to be a string singleton type, naming the property.
  * `value` will be set as the `write type`, the `read type` will be unchanged.
  * If `key` is not already present, only a `write type` will be set, making the property write-only.
  * If `value` is `nil`, the property is removed.

    tabletype:readproperty(key: type): type?

Returns the type used for reading values from this property, or `nil` if the property doesn’t exist.

    tabletype:writeproperty(key: type): type?

Returns the type used for writing values to this property, or `nil` if the property doesn’t exist.

    tabletype:properties(): { [type]: { read: type?, write: type? } }

Returns a table mapping property keys to their read and write types.

    tabletype:setindexer(index: type, result: type)

Sets the table’s indexer, using the same type for reads and writes.

    tabletype:setreadindexer(index: type, result: type)

Sets the type resulting from reading from this table via indexing.

    tabletype:setwriteindexer(index: type, result: type)

Sets the type for writing to this table via indexing.

    tabletype:indexer(): { index: type, readresult: type, writeresult: type }

Returns the table’s indexer as a table, or `nil` if it doesn’t exist.

    tabletype:readindexer(): { index: type, result: type }?

Returns the table’s indexer using the result’s read type, or `nil` if it doesn’t exist.

    tabletype:writeindexer()

Returns the table’s indexer using the result’s write type, or `nil` if it doesn’t exist.

    tabletype:setmetatable(arg: type)

Sets the table’s metatable.

    tabletype:metatable(): type?

Gets the table’s metatable, or `nil` if it doesn’t exist.

### Function `type` instance

Section titled “Function type instance”

    functiontype:setparameters(head: {type}?, tail: type?)

Sets the function’s parameters, with the ordered parameters in `head` and the variadic tail in `tail`.

    functiontype:parameters(): { head: {type}?, tail: type? }

Returns the function’s parameters, with the ordered parameters in `head` and the variadic tail in `tail`.

    functiontype:setreturns(head: {type}?, tail: type?)

Sets the function’s return types, with the ordered parameters in `head` and the variadic tail in `tail`.

    functiontype:returns(): { head: {type}?, tail: type? }

Returns the function’s return types, with the ordered parameters in `head` and the variadic tail in `tail`.

    functiontype:generics(): {type}

Returns an array of the function’s [generic](https://luau.org/types/generics#generic-functions) `type`s.

    functiontype:setgenerics(generics: {type}?)

Sets the function’s [generic](https://luau.org/types/generics#generic-functions) `type`s.

### Negation `type` instance

Section titled “Negation type instance”

    type:inner(): type

Returns the `type` being negated.

### Union `type` instance

Section titled “Union type instance”

    uniontype:components(): {type}

Returns an array of the [unioned](https://luau.org/types/unions-and-intersections#union-types) types.

### Intersection `type` instance

Section titled “Intersection type instance”

    intersectiontype:components()

Returns an array of the [intersected](https://luau.org/types/unions-and-intersections#intersection-types) types.

### Class `type` instance

Section titled “Class type instance”

    classtype:properties(): { [type]: { read: type?, write: type? } }

Returns the properties of the class with their respective `read` and `write` types.

    classtype:readparent(): type?

Returns the type of reading this class’ parent, or returns `nil` if the parent class doesn’t exist.

    classtype:writeparent(): type?

Returns the type for writing to this class’ parent, or returns `nil` if the parent class doesn’t exist.

    classtype:metatable(): type?

Returns the class’ metatable, or `nil` if it doesn’t exist.

    classtype:indexer(): { index: type, readresult: type, writeresult: type }?

Returns the class’ indexer, or `nil` if it doesn’t exist.

    classtype:readindexer(): { index: type, result: type }?

Returns result type of reading from the class via indexing, or `nil` if it doesn’t exist.

    classtype:writeindexer(): { index: type, result: type }?

Returns the type for writing to the class via indexing, or `nil` if it doesn’t exist.


# Type Functions

Type functions are functions that run during analysis time and operate on types, instead of runtime values. They can use the [types](https://luau.org/types-library) library to transform existing types or create new ones.

Here’s a simplified implementation of the builtin type function `keyof`. It takes a table type and returns its property names as a [union](https://luau.org/types/unions-and-intersections#union-types) of [singletons](https://luau.org/types/basic-types#singleton-types-aka-literal-types).

### Type function environment

Section titled “Type function environment”

In addition to the [types](https://luau.org/types-library) library, type functions have access to:

  * `assert`, `error`, `print`
  * `next`, `ipairs`, `pairs`
  * `select`, `unpack`
  * `getmetatable`, `setmetatable`
  * `rawget`, `rawset`, `rawlen`, `raweq`
  * `tonumber`, `tostring`
  * `type`, `typeof`
  * `math` library
  * `table` library
  * `string` library
  * `bit32` library
  * `utf8` library
  * `buffer` library


# Object-Oriented Programming

## Adding types for faux object oriented programs

Section titled “Adding types for faux object oriented programs”

One common pattern we see with existing Lua/Luau code is the following object-oriented code. While Luau is capable of inferring a decent chunk of this code, it cannot pin down on the types of `self` when it spans multiple methods.

For example, the type of `Account.new` is `<a, b>(name: a, balance: b) -> { ..., name: a, balance: b, ... }` (snipping out the metatable). For better or worse, this means you are allowed to call `Account.new(5, "hello")` as well as `Account.new({}, {})`. In this case, this is quite unfortunate, so your first attempt may be to add type annotations to the parameters `name` and `balance`.

There’s the next problem: the type of `self` is not shared across methods of `Account`, this is because you are allowed to explicitly opt for a different value to pass as `self` by writing `account.deposit(another_account, 50)`. As a result, the type of `Account:deposit` is `<a, b>(self: { balance: a }, credit: b) -> ()`. Consequently, Luau cannot infer the result of the `+` operation from `a` and `b`, so a type error is reported.

We can see there’s a lot of problems happening here. This is a case where you’ll have to provide some guidance to Luau in the form of annotations today, but the process is straightforward and without repetition. You first specify the type of _data_ you want your class to have, and then you define the class type separately with `setmetatable` (either via `typeof`, or in the New Type Solver, the `setmetatable` type function). From then on, you can explicitly annotate the `self` type of each method with your class type! Note that while the definition is written e.g. `Account.deposit`, you can still call it as `account:deposit(...)`.

Based on feedback, we plan to restrict the types of all functions defined with `:` syntax to [share their self types](https://rfcs.luau.org/shared-self-types.html). This will enable future versions of this code to work without any explicit `self` annotations because it amounts to having type inference make precisely the assumptions we are encoding with annotations here --- namely, that the type of the constructors and the method definitions is intended by the developer to be the same.


# Profiling your Luau code

One of main goals of Luau is to enable high performance code. To help with that goal, we are relentlessly optimizing the compiler and runtime - but ultimately, performance of their code is in developers’ hands, and is a combination of good algorithm design and implementation that adheres to the strengths of the language. To help write efficient code, Luau provides a built-in profiler that samples the execution of the program and outputs a profiler dump that can be converted to an interactive flamegraph.

To run the profiler, make sure you have an optimized build of the interpreter (otherwise profiling results are going to be very skewed) and run it with `--profile` argument:

    $ luau --profile tests/chess.lua

    OK      8902    rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1

    OK      2039    r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 0

    OK      2812    8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 0

    OK      9467    r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1

    OK      1486    rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8

    OK      2079    r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10

    Profiler dump written to profile.out (total runtime 2.034 seconds, 20344 samples, 374 stacks)

    GC: 0.378 seconds (18.58%), mark 46.80%, remark 3.33%, atomic 1.93%, sweepstring 6.77%, sweep 41.16%

The resulting `profile.out` file can be converted to an SVG file by running `perfgraph.py` script that is part of Luau repository:

    $ python tools/perfgraph.py profile.out >profile.svg

This produces an SVG file that can be opened in a browser (the image below is clickable):

[](https://luau.org/images/chess-profile.svg)

In a flame graph visualization, the individual bars represent function calls, the width represents how much of the total program runtime they execute, and the nesting matches the call stack encountered during program execution. This is a fantastic visualization technique that allows you to hone in on the specific bottlenecks affecting your program performance, optimize those exact bottlenecks, and then re-generate the profile data and visualizer, and look for the next set of true bottlenecks (if any).

Hovering your mouse cursor over individual sections will display detailed function information in the status bar and in a tooltip. If you want to Search for a specific named function, use the Search field in the upper right, or press Ctrl+F.

Notice that some of the bars in the screenshot don’t have any text. In some cases, there isn’t enough room in the size of the bar to display the name. You can hover your mouse over those bars to see the name and source location of the function in the tool tip, or double-click to zoom in on that part of the flame graph.

Some tooltips will have a source location for the function you’re hovering over, but no name. Those are anonymous functions, or functions that were not declared in a way that allows Luau compiler to track the name. To fill in more names, you may want to make these changes to your code:

`local myFunc = function() --[[ work ]] end` -> `local function myFunc() --[[ work ]] end`

Even without these changes, you can hover over a given bar with no visible name and see it’s source location.

As any sampling profiler, this profiler relies on gathering enough information for the resulting output to be statistically meaningful. It may miss short functions if they aren’t called often enough. By default the profiler runs at 10 kHz, this can be customized by passing a different parameter to `--profile=`. Note that higher frequencies result in higher profiling overhead and longer program execution, potentially skewing the results.

This profiler doesn’t track leaf C functions and instead attributes the time spent there to calling Luau functions. As a result, when thinking about why a given function is slow, consider not just the work it does immediately but also the library functions it calls.

This profiler tracks time consumed by Luau thread stacks; when a thread calls another thread via `coroutine.resume`, the time spent is not attributed to the parent thread that’s waiting for resume results. This limitation will be removed in the future.


# Union and Intersection Types

## Union types

Section titled “Union types”

A union type represents _one of_ the types in this set. If you try to pass a union onto another thing that expects a _more specific_ type, it will fail.

For example, what if this `string | number` was passed into something that expects `number`, but the passed in value was actually a `string`?

Note: it’s impossible to be able to call a function if there are two or more function types in this union.

### Tagged unions

Section titled “Tagged unions”

Tagged unions are just union types! In particular, they’re union types of tables where they have at least _some_ common properties but the structure of the tables are different enough. Here’s one example:

This `Result<T, E>` type can be discriminated by using type refinements on the property `type`, like so:

Which works out because `value: T` exists only when `type` is in actual fact `"ok"`, and `error: E` exists only when `type` is in actual fact `"err"`.

## Intersection types

Section titled “Intersection types”

An intersection type represents _all of_ the types in this set. It’s useful for two main things: to join multiple tables together, or to specify overloadable functions.

Note: it’s impossible to create an intersection type of some primitive types, e.g. `string & number`, or `string & boolean`, or other variations thereof.

Note: Luau still does not support user-defined overloaded functions. Some of Roblox and Lua 5.1 functions have different function signature, so inherently requires overloaded functions.


# Embedding a sandboxed Luau virtual machine

Luau is safe to embed. Broadly speaking, this means that even in the face of untrusted (and in Roblox case, actively malicious) code, the language and the standard library don’t allow unsafe access to the underlying system, and don’t have known bugs that allow escaping out of the sandbox (e.g. to gain native code execution through ROP gadgets et al). Additionally, the VM provides extra features to implement isolation of privileged code from unprivileged code and protect one from the other; this is important if the embedding environment decides to expose some APIs that may not be safe to call from untrusted code, for example because they do provide controlled access to the underlying system or risk PII exposure through fingerprinting etc.

This safety is achieved through a combination of removing features from the standard library that are unsafe, adding features to the VM that make it possible to implement sandboxing and isolation, and making sure the implementation is safe from memory safety issues using fuzzing.

Of course, since the entire stack is implemented in C++, the sandboxing isn’t formally proven - in theory, compiler or the standard library can have exploitable vulnerabilities. In practice these are very rare and usually found and fixed quickly. While implementing the stack in a safer language such as Rust would make it easier to provide these guarantees, to our knowledge (based on prior art) this would make it difficult to reach the level of performance required.

## Library

Section titled “Library”

Parts of the Lua 5.x standard library are unsafe. Some of the functions provide access to the host operating system, including process execution and file reads. Some functions lack sufficient memory safety checks. Some functions are safe if all code is untrusted, but can break the isolation barrier between trusted and untrusted code.

The following libraries and global functions have been removed as a result:

  * `io.` library has been removed entirely, as it gives access to files and allows running processes
  * `package.` library has been removed entirely, as it gives access to files and allows loading native modules
  * `os.` library has been cleaned up from file and environment access functions (`execute`, `exit`, etc.). The only supported functions in the library are `clock`, `date`, `difftime` and `time`.
  * `debug.` library has been removed to a large extent, as it has functions that aren’t memory safe and other functions break isolation; the only supported functions are `traceback` and `info` (which is similar to `debug.getinfo` but has a slightly different interface).
  * `dofile` and `loadfile` allowed access to file system and have been removed.

To achieve memory safety, access to function bytecode has been removed. Bytecode is hard to validate and using untrusted bytecode may lead to exploits. Thus, `loadstring` doesn’t work with bytecode inputs, and `string.dump`/`load` have been removed as they aren’t necessary anymore. When embedding Luau, bytecode should be encrypted/signed to prevent MITM attacks as well, as the VM assumes that the bytecode was generated by the Luau compiler (which never produces invalid/unsafe bytecode).

Finally, to make isolation possible within the same VM, the following global functions have reduced functionality:

  * `collectgarbage` only works with `"count"` argument, as modifying the state of GC can interfere with the expectations of other code running in the process. As such, `collectgarbage()` became an inferior version of `gcinfo()` and is deprecated.
  * `newproxy` only works with `true`/`false`/`nil` arguments.
  * `module` allowed overriding global packages and was removed as a result.

> Note: `getfenv`/`setfenv` result in additional isolation challenges, as they allow injecting globals into scripts on the call stack. Ideally, these should be disabled as well, but unfortunately Roblox community relies on these for various reasons. This can be mitigated by limiting interaction between trusted and untrusted code, and/or using separate VMs.

## Environment

Section titled “Environment”

The modification to the library functions are sufficient to make embedding safe, but aren’t sufficient to provide isolation within the same VM. It should be noted that to achieve guaranteed isolation, it’s advisable to load trusted and untrusted code into separate VMs; however, even within the same VM Luau provides additional safety features to make isolation cheaper.

When initializing the default globals table, the tables are protected from modification:

  * All libraries (`string`, `math`, etc.) are marked as readonly
  * The string metatable is marked as readonly
  * The global table itself is marked as readonly

This is using the VM feature that is not accessible from scripts, that prevents all writes to the table, including assignments, `rawset` and `setmetatable`. This makes sure that globals can’t be monkey-patched in place, and can only be substituted through `setfenv`.

By itself this would mean that code that runs in Luau can’t use globals at all, since assigning globals would fail. While this is feasible, in Roblox we solve this by creating a new global table for each script, that uses `__index` to point to the builtin global table. This safely sandboxes the builtin globals while still allowing writing globals from each script. This also means that short of exposing special shared globals from the host, all scripts are isolated from each other.

## `__gc`

Section titled “__gc”

Lua 5.1 exposes a `__gc` metamethod for userdata, which can be used on proxies (`newproxy`) to hook into garbage collector. Later versions of Lua extend this mechanism to work on tables.

This mechanism is bad for performance, memory safety and isolation:

  * In Lua 5.1, `__gc` support requires traversing userdata lists redundantly during garbage collection to filter out finalizable objects
  * In later versions of Lua, userdata that implement `__gc` are split into separate lists; however, finalization prolongs the lifetime of the finalized objects which results in less prompt memory reclamation, and two-step destruction results in extra cache misses for userdata
  * `__gc` runs during garbage collection in context of an arbitrary thread which makes the thread identity mechanism used in Roblox to support trusted Luau code invalid
  * Objects can be removed from weak tables _after_ being finalized, which means that accessing these objects can result in memory safety bugs, unless all exposed userdata methods guard against use-after-gc.
  * If `__gc` method ever leaks to scripts, they can call it directly on an object and use any method exposed by that object after that. This means that `__gc` and all other exposed methods must support memory safety when called on a destroyed object.

Because of these issues, Luau does not support `__gc`. Instead it uses tag-based destructors that can perform additional memory cleanup during userdata destruction; crucially, these are only available to the host (so they can never be invoked manually), and they run right before freeing the userdata memory block which is both optimal for performance, and guaranteed to be memory safe.

For monitoring garbage collector behavior the recommendation is to use weak tables instead.

## Interrupts

Section titled “Interrupts”

In addition to preventing API access, it can be important for isolation to limit the memory and CPU usage of code that runs inside the VM.

By default, no memory limits are imposed on the running code, so it’s possible to exhaust the address space of the host; this is easy to configure from the host for Luau allocations, but of course with a rich API surface exposed by the host it’s hard to eliminate this as a possibility. Memory exhaustion doesn’t result in memory safety issues or any particular risk to the system that’s running the host process, other than the host process getting terminated by the OS.

Limiting CPU usage can be equally challenging with a rich API. However, Luau does provide a VM-level feature to try to contain runaway scripts which makes it possible to terminate any script externally. This works through a global interrupt mechanism, where the host can setup an interrupt handler at any point, and any Luau code is guaranteed to call this handler “eventually” (in practice this can happen at any function call or at any loop iteration). This still leaves the possibility of a very long running script open if the script manages to find a way to call a single C function that takes a lot of time, but short of that the interruption is very prompt.

Roblox sets up the interrupt handler using a watchdog that:

  * Limits the runtime of any script in Studio to 10 seconds (configurable through Studio settings)
  * Upon client shutdown, interrupts execution of every running script 1 second after shutdown


# String Interpolation

February 2, 2023

String interpolation is the new syntax introduced to Luau that allows you to create a string literal with expressions inside of that string literal.

In short, it’s a safer and more ergonomic alternative over `string.format`.

Here’s a quick example of a string interpolation:

String interpolation also composes well with the `__tostring` metamethod.

To find out more details about this feature, check out [Luau Syntax page](https://luau.org/syntax#string-interpolation).

This is also the first major language feature implemented in a [contribution](https://github.com/Roblox/luau/pull/614) from the open-source community. Thanks [Kampfkarren](https://github.com/Kampfkarren)!


# Recap: April 2022

May 2, 2022

It’s been a bit of a quiet month. We mostly have small optimizations and bugfixes for you.

It is now allowed to define functions on sealed tables that have string indexers. These functions will be typechecked against the indexer type. For example, the following is now valid:

Autocomplete will now provide string literal suggestions for singleton types. eg

Improve error recovery in the case where we encounter a type pack variable in a place where one is not allowed. eg `type Foo<A...> = { value: A... }`

When code does not pass enough arguments to a variadic function, the error feedback is now better.

For example, the following script now produces a much nicer error message:

If the following code were to error because `Hello` was undefined, we would erroneously include the comment in the span of the error. This is now fixed.

Fix a crash that could occur when strict scripts have cyclic require() dependencies.

Add an option to autocomplete to cause it to abort processing after a certain amount of time has elapsed.


# Recap: April 2021

April 30, 2021

Another busy month in Luau with many performance improvements.

## Editor features

Luau implementation now provides an internal API for type-aware autocomplete suggestions.

Roblox Studio will be the first user of this API and we plan for a new beta feature to come soon in addition to existing Luau-powered beta features like Go To Declaration, Type Hovers and Script Function Filter (you should check those out!)

## Performance improvements

Performance is a very important part of Luau implementation and we continue bringing in new performance optimizations:

  * We’ve finished the work on internal `vector` value type that will be used by `Vector3` type in Roblox. Improvements of up to 10x can be seen for primitive operations and some of our heavy `Vector3` benchmarks have seen 2-3x improvement. You can read more about this feature [on Roblox Developer forums](https://devforum.roblox.com/t/native-luau-vector3-beta/)
  * By optimizing the way string buffers are handled internally, we bring improvements to string operations including `string.lower`, `string.upper`, `string.reverse`, `string.rep`, `table.concat` and string concatenation operator `..`. Biggest improvements can be seen on large strings
  * Improved performance of `table.insert` and `table.remove`. Operations in the middle of large arrays can be multiple times faster with this change
  * Improved performance of internal table resize which brings additional 30% speedup for `table.insert`
  * Improved performance of checks for missing table fields

## Generic functions

We had to temporarily disable generic function definitions last month after finding critical issues in the implementation.

While they are still not available, we are making steady progress on fixing those issues and making additional typechecking improvements to bring them back in.

## Debugger improvements

Debugging is now supported for parallel Luau Actors in Roblox Studio.

Read more about the feature [on Roblox Developer forums](https://devforum.roblox.com/t/parallel-lua-beta/) and try it out yourself.

## Behavior changes

Backwards compatibility is important for Luau, but sometimes a change is required to fix corner cases in the language / libraries or to improve performance. Even still, we try to keep impact of these changes to a minimum:

  * __eq tag method will always get called for table comparisons even when a table is compared to itself

## Coming soon…

  * Better type refinements for statements under a condition using a new constraint resolver. Luau will now understand complex conditions combining `and`/`not` and type guards with more improvements to come


# Recap: October 2023

November 1, 2023

We’re still quite busy working on some big type checking updates that we hope to talk about soon, but we have a few equally exciting updates to share in the meantime!

Let’s dive in!

## Floor Division

Luau now has a floor division operator. It is spelled `//`:

For numbers, `a // b` is equivalent to `math.floor(a / b)`, and you can also overload this operator by implementing the `__idiv` metamethod. The syntax and semantics are borrowed from Lua 5.3 (although Lua 5.3 has an integer type while we don’t, we tried to match the behavior to be as close as possible).

## Native Codegen Preview

We are actively working on our new native code generation module that can significantly improve the performance of compute-dense scripts by compiling them to X64 (Intel/AMD) or A64 (ARM) machine code and executing that natively. We aim to support all AArch64 hardware with the current focus being Apple Silicon (M1-M3) chips, and all Intel/AMD hardware that supports AVX1 (with no planned support for earlier systems). When the hardware does not support native code generation, any code that would be compiled as native just falls back to the interpreted execution.

When working with [open-source releases](https://github.com/luau-lang/luau/releases), binaries now have native code generation support compiled in by default; you need to pass `--codegen` command line flag to enable it. If you use Luau as a library in a third-party application, you would need to manually link `Luau.CodeGen` library and call the necessary functions to compile specific modules as needed - or keep using the interpreter if you want to! If you work in Roblox Studio, we have integrated native code generation preview [as a beta feature](https://devforum.roblox.com/t/luau-native-code-generation-preview-studio-beta/2572587), which currently requires manual annotation of select scripts with `--!native` comment.

Our goal for the native code generation is to help reach ultimate performance for code that needs to process data very efficiently, but not necessarily to accelerate every line of code, and not to replace the interpreter. We remain committed to maximizing interpreted execution performance, as not all platforms will support native code generation, and it’s not always practical to use native code generation for large code bases because it has a larger memory impact than bytecode. We intend for this to unlock new performance opportunities for complex features and algorithms, e.g. code that spends a lot of time working with numbers and arrays, but not to dramatically change performance on UI code or code that spends a lot of its time calling Lua functions like `table.sort`, or external C functions (like Roblox engine APIs).

Importantly, native code generation does not change our behavior or correctness expectations. Code compiled natively should give the same results when it executes as non-native code (just take a little less time), and it should not result in any memory safety or sandboxing issues. If you ever notice native code giving a different result from non-native code, please submit a bug report.

We continue to work on many code size and performance improvements; here’s a short summary of what we’ve done in the last couple of months, and there’s more to come!

  * Repeated access to table fields with the same object and name are now optimized (e.g. `t.x = t.x + 5` is faster)
  * Numerical `for` loops are now compiled more efficiently, yielding significant speedups on hot loops
  * Bit operations with constants are now compiled more efficiently on X64 (for example, `bit32.lshift(x, 1)` is faster); this optimization was already in place for A64
  * Repeated access to array elements with the same object and index is now faster in certain cases
  * Performance of function calls has been marginally improved on X64 and A64
  * Fix code generation for some `bit32.extract` variants where we could produce incorrect results
  * `table.insert` is now faster when called with two arguments as it’s compiled directly to native code
  * To reduce code size, module code outside of functions is not compiled natively unless it has loops

## Analysis Improvements

The `break` and `continue` keywords can now be used in loop bodies to refine variables. This was contributed by a community member - thank you, [AmberGraceSoftware](https://github.com/AmberGraceSoftware)!

When type information is present, we will now emit a warning 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.

Finally, some uses of `getfenv`/`setfenv` are now flagged as deprecated. We do not plan to remove support for `getfenv`/`setfenv` but we actively discourage its use as it disables many optimizations throughout the compiler, runtime, and native code generation, and interferes with type checking and linting.

## Autocomplete Improvements

We used to have a bug that would arise in the following situation:

If you placed the cursor after the `""` and before `then` in the conditional, we used to suggest `Left` and `Right` even though they are not valid completions at that position. This is now fixed.

We’ve also added a complete suggestion for anonymous functions if one would be valid at the requested position. For example:

You will see a completion suggestion `function (anonymous autofilled)`. Selecting that will cause the following to be inserted into your code:

We also fixed some confusing editor feedback in the following case:

Previously, the signature help tooltip would erroneously tell you that you needed to pass a `self` argument. We now correctly offer the signature `FindFirstChild(name: string, recursive: boolean?): Instance`

## Runtime Improvements

  * `string.format`’s handling of `%*` and `%s` is now 1.5-2x faster
  * `tonumber` and `tostring` are now 1.5x and 2.5x faster respectively when working on primitive types
  * Compiler now recognizes `math.pi` and `math.huge` and performs constant folding on the expressions that involve these at `-O2`; for example, `math.pi*2` is now free.
  * Compiler now optimizes `if...then...else` expressions into AND/OR form when possible (for example, `if x then x else y` now compiles as `x or y`)
  * We had a few bugs around `repeat..until` statements when the `until` condition referred to local variables defined in the loop body. These bugs have been fixed.
  * Fix an oversight that could lead to `string.char` and `string.sub` generating potentially unlimited amounts of garbage and exhausting all available memory.
  * We had a bug that could cause the compiler to unroll loops that it really shouldn’t. This could result in massive bytecode bloat. It is now fixed.

## luau-lang on GitHub

If you’ve been paying attention to our GitHub projects, you may have noticed that we’ve moved `luau` repository to a new [luau-lang GitHub organization](https://github.com/luau-lang)! This is purely an organizational change but it’s helping us split a few repositories for working with documentation and RFCs and be more organized with pull requests in different areas. Make sure to update your bookmarks and [star our main repository](https://github.com/luau-lang/luau) if you haven’t already!

Lastly, a big thanks to our [open source community](https://github.com/luau-lang/luau) for their generous contributions:

  * [MagelessMayhem](https://github.com/MagelessMayhem)
  * [cassanof](https://github.com/cassanof)
  * [LoganDark](https://github.com/LoganDark)
  * [j-hui](https://github.com/j-hui)
  * [xgqt](https://github.com/xgqt)
  * [jdpatdiscord](https://github.com/jdpatdiscord)
  * [Someon1e](https://github.com/Someon1e)
  * [AmberGraceSoftware](https://github.com/AmberGraceSoftware)
  * [RadiantUwU](https://github.com/RadiantUwU)
  * [SamuraiCrow](https://github.com/SamuraiCrow)


# Recap: March 2022

March 31, 2022

## Singleton types

We added support for singleton types! These allow you to use string or boolean literals in types. These types are only inhabited by the literal, for example if a variable `x` has type `"foo"`, then `x == "foo"` is guaranteed to be true.

Singleton types are particularly useful when combined with union types, for example:

or:

In particular, singleton types play well with unions of tables, allowing tagged unions (also known as discriminated unions):

The RFC for singleton types is <https://github.com/Roblox/luau/blob/master/rfcs/syntax-singleton-types.md>

## Width subtyping

A common idiom for programming with tables is to provide a public interface type, but to keep some of the concrete implementation private, for example:

Within a module, a developer might use the concrete type, but export functions using the interface type:

Previously examples like this did not typecheck but now they do!

This language feature is called _width subtyping_ (it allows tables to get _wider_ , that is to have more properties).

The RFC for width subtyping is <https://github.com/Roblox/luau/blob/master/rfcs/sealed-table-subtyping.md>

## Typechecking improvements

  * Generic function type inference now works the same for generic types and generic type packs.
  * We improved some error messages.
  * There are now fewer crashes (hopefully none!) due to mutating types inside the Luau typechecker.
  * We fixed a bug that could cause two incompatible copies of the same class to be created.
  * Luau now copes better with cyclic metatable types (it gives a type error rather than hanging).
  * Fixed a case where types are not properly bound to all of the subtype when the subtype is a union.
  * We fixed a bug that confused union and intersection types of table properties.
  * Functions declared as `function f(x : any)` can now be called as `f()` without a type error.

## API improvements

  * Implement `table.clone` which takes a table and returns a new table that has the same keys/values/metatable. The cloning is shallow - if some keys refer to tables that need to be cloned, that can be done manually by modifying the resulting table.

## Debugger improvements

  * Use the property name as the name of methods in the debugger.

## Performance improvements

  * Optimize table rehashing (~15% faster dictionary table resize on average)
  * Improve performance of freeing tables (~5% lift on some GC benchmarks)
  * Improve gathering performance metrics for GC.
  * Reduce stack memory reallocation.