# Recap: July 2024

July 23, 2024

Hello everyone!

While the Luau team is actively working on a big rewrite of the type inference and type checking engines (more news about that in the near future), we wanted to go over other changes and updates since our last recap back in October.

## Official Luau mascot

Luau has recently adopted a Hawaiian monk seal mascot named Hina, after the Hawaiian goddess of the moon.

Please welcome Hina the Seal!

## Native Code Generation

We are happy to announce that the native code feature is out from the ‘Preview’ state and is fully supported for X64 (Intel/AMD) or A64 (ARM) processor architectures.

As a refresher, native code generation is the feature that allows Luau scripts that have been previously executed by interpreting bytecode inside the Luau VM to instead compile to machine code that the CPU understands and executes directly.

Since the release of the Preview, we have worked on improving code performance, memory use of the system, correctness and stability.

Some highlights:

  * Improved performance of the [bit32 library](https://luau-lang.org/library#bit32-library) functions
  * Improved performance of numerical loops
  * Optimized table array and property lookups
  * Added native support for new [buffer type](https://luau-lang.org/library#buffer-library) operations
  * Code optimizations based on knowing which types are returned from operations
  * Code optimizations based on function argument type annotations
    * This includes support for [SIMD operations](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data) for annotated vector arguments

There are many other small improvements in generated code performance and size and we have plans for additional optimizations.

### Native function attribute

For a better control of what code runs natively, we have introduced new syntax for function attributes:

This is the first attribute to become available and we are working on the ability to mark functions as deprecated using the `@deprecated` attribute. More on that [here](https://github.com/luau-lang/rfcs/blob/2335ab6db9353223fad0065294d15fdcd127c4ea/docs/syntax-attribute-functions-deprecated.md).

### Type information for runtime optimizations

Native code generation works on any code without having to modify it. In certain situations, this means that the native compiler cannot be sure about the types involved in the operation.

Consider a simple function, working on a few values:

Native compiler assumes that operations are most likely being performed on numbers and generates the appropriate fast path.

But what if the function is actually called with a vector type?

To handle this, a slower path was generated to handle any other potential type of the argument. Because this path is not chosen as the first possible option, extra checking overhead prevents code from running as fast as it can.

When we announced the last update, we had already added some support for following the types used as arguments.

> **_NOTE:_ ** `vector` type is not enabled by default, check out `defaultOptions` and `setupVectorHelpers` functions in `Conformance.test.cpp` file as an example of the `vector` library setup.

Since then, we have extended this to support type information on locals, following complex types and even inferring results of additional operations.

As can be seen, often it’s enough to annotate the type of the data structure and correct fast-path vector code will be generated from that without having to specify the type of each local or temporary.

> **_NOTE:_ ** Advanced inference and operation lowering is enabled by using custom `HostIrHooks` callbacks. Check out ‘Vector’ test with ‘IrHooks’ option in `Conformance.test.cpp` and `ConformanceIrHooks.h` file for an example of the setup.

Note that support for native lowering hooks allows generation of CPU code that is multiple times faster than a generic metatable call.

Even when native compiler doesn’t have a specific optimization for a type, if the type can be resolved, shorter code sequences are generated and more optimizations can be made between separate operations.

> **_NOTE:_ ** `HostIrHooks` callbacks also enable type inference and lowering for your custom userdata types. Check out ‘NativeUserdata’ test with ‘IrHooks’ option in `Conformance.test.cpp` and `ConformanceIrHooks.h` file for an example of the setup.

## Runtime changes

### Stricter `utf8` library validation

`utf8` library will now correctly validate UTF-8 and reject inputs that have surrogates. `utf8.len` will return `nil` followed by the byte offset, `utf8.codepoint` and `utf8.codes` will error. This matches how other kinds of input errors were previously handled by those functions.

Strings that are validated using `utf8.len` will now always work properly with `utf8.nfcnormalize` and `utf8.graphemes` functions. Custom per-character validation logic is no longer required to check if a string is valid under `utf8` requirements.

### Imprecise integer number warning

Luau stores numbers as 64-bit floating-point values. Integer values up to 2^53 are supported, but higher numbers might experience rounding.

For example, both 10000000000000000 and 9223372036854775808 are larger than 2^53, but match the rounding, while 10000000000000001 gets rounded down to 10000000000000000.

In cases where rounding takes place, you will get a warning message. If the large value is intended and rounding can be ignored, just add “.0” to the number to remove the warning:

### Leading `|` and `&` in types

It is now possible to start your union and intersection types with a symbol. This can help align the type components more cleanly:

You can find more information and examples in [the proposal](https://github.com/luau-lang/rfcs/blob/leading-bar-ampersand/docs/syntax-leading-bar-and-ampersand.md)

## Analysis Improvements

While our main focus is on a type-checking engine rewrite that is nearing completion, we have fixed some of the issues in the current one.

  * Relational operator errors are more conservative now and generate less false positive errors
  * It is not an error to iterate over table properties when indexer is not part of the type
  * Type packs with cycles are now correctly described in error messages
  * Improved error message when value that is not a function is being used in a call
  * Fixed stability issues which caused Studio to crash
  * Improved performance for code bases with large number of scripts and complex types

## Runtime Improvements

When converting numbers to strings in scientific notation, we will now skip the trailing ’.’.

For example, `tostring(1e+30)` now outputs ‘1e+30’ instead of ‘1.e+30’. This improves compatibility with data formats like JSON. But please keep in mind that unless you are using JSON5, Luau can still output ‘inf’ and ‘nan’ numbers which might not be supported.

  * Construction of tables with 17-32 properties or 33-64 array elements is now 30% faster.
  * `table.concat` method is now 2x faster when the separator is not used and 40% faster otherwise.
  * `table.maxn` method is now 5-14x faster.
  * vector constants are now stored in the constant table and avoid runtime construction.
  * Operations like 5/x and 5-x with any constant on the left-hand-side are now performed faster, one less minor thing to think about!
  * It is no longer possible to crash the server on a hang in the `string` library methods.

## Luau as a supported language on GitHub

Lastly, if you have open-source or even private projects on GitHub which use Luau, you might be happy to learn that Luau now has official support on GitHub for `.luau` file extension. This includes recognizing files as using Luau programming language and having support for syntax highlighting.

A big thanks goes to our [open source community](https://github.com/luau-lang/luau) for their generous contributions including pushing for broader Luau support:

  * [birds3345](https://github.com/birds3345)
  * [bjornbytes](https://github.com/bjornbytes)
  * [Gskartwii](https://github.com/Gskartwii)
  * [jackdotink](https://github.com/jackdotink)
  * [JohnnyMorganz](https://github.com/JohnnyMorganz)
  * [khvzak](https://github.com/khvzak)
  * [kostadinsh](https://github.com/kostadinsh)
  * [mttsner](https://github.com/mttsner)
  * [mxruben](https://github.com/mxruben)
  * [petrihakkinen](https://github.com/petrihakkinen)
  * [zeux](https://github.com/zeux)


# Luau Type Checking Release

November 19, 2020

10 months ago, we’ve started upon the journey of helping Roblox programmers write robust code by introducing [an early beta of type checking](https://devforum.roblox.com/t/luau-type-checking-release). We’ve received a lot of enthusiastic feedback and worked with the community on trying to make sure critical issues are addressed, usability is improved and the type system is ready for prime time.

Today I’m incredibly excited to announce that the first release of [Luau](https://roblox.github.io/luau/) type checking is officially released! Thanks a lot to @Apakovtac, @EthicalRobot, @fun_enthusiast, @machinamentum, @mrow_pizza and @zeuxcg!

## What is type checking?

When Luau code runs, every value has a certain type at runtime - a kind of value it stores. It could be a number, a string, a table, a Roblox Instance or one of many others. Thing is, some operations work on some types but don’t work on others!

Consider this:

Is this code correct? No - there’s a typo. The way you get to find this typo is by running your code and eventually seeing an error message. Type checker tries to analyze your code before running, by assigning a type to each value based on what we know about how that value was produced, or based on the type you’ve explicitly told us using a new syntax extension, and can produce an error ahead of time:

This can require some effort up front, especially if you use strict mode, but it can save you valuable time in the future. It can be especially valuable if you have a large complex code base you need to maintain for years, as is the case with many top Roblox games.

## How do I use type checking?

A very important feature of Luau type checking you need to know about is that it has three modes:

  * `nocheck`, where we don’t type check the script in question.
  * `nonstrict`, where we type check the script but try to be lenient to allow commonly seen patterns even if they may violate type safety
  * `strict`, where we try to make sure that every single line of code you write is correct, and every value has a known type.

The modes can be selected per script by writing a comment at the top of the script that starts with `--!`, e.g. `--!strict`.

As of this release, the default mode is nocheck. This means by default you actually won’t see the type checking produce feedback on your code! We had to use nocheck by default because we aren’t fully ready to unleash nonstrict mode on unsuspecting users - we need to do a bit more work to make sure that most cases where we tell you that something is wrong are cases where yes, something is actually wrong.

However we highly encourage trying at least non-strict mode on your codebase. You can do this by opting into a different default via a Studio beta:

This beta only changes the default mode. Another way to change the mode is to prepend a `--!` comment to the script - you can do this manually for now, but if anyone in the community wants to release a plugin that does it automatically on selected scripts (+ descendants), that would be swell!

If you really want your code to be rock solid, we recommend trying out strict mode. Strict mode will require you to use type annotations.

## What are type annotations and how do I use them?

Glad you asked! (please pretend you did) Type annotations are a way to tell the type checker what the type of a variable is. Consider this code in strict mode:

Is this code correct? Well, that depends. `add(2, 3)` will work just fine. `add(Vector3.new(1, 2, 3), Vector3.new(4, 5, 6))` will work as well. But `add({}, nil)` probably isn’t a good idea.

In strict mode, we will insist that the type checker knows the type of all variables, and you’ll need to help the type checker occasionally - by adding types after variable names separated by `:`:

If you want to tell the type checker “assume this value can be anything and I will take responsibility”, you can use `any` type which will permit any value of any type.

If you want to learn more about the type annotation syntax, you should read this [documentation on syntax](https://luau.org/syntax#type-annotations). We also have a somewhat more complete guide to type checking than this post can provide, that goes into more details on table types, OOP, Roblox classes and enums, interaction with require and other topics - [read it if you’re curious!](https://luau.org/typecheck).

## What happens when I get a type error?

One concept that’s very important to understand is that right now type errors do not influence whether the code will run or not.

If you have a type error, this means that our type checker thinks your code has a bug, or doesn’t have enough information to prove the code works fine. But if you really want to forge ahead and run the code - you should feel free to do so!

This means that you can gradually convert your code to strict mode by adding type annotations and have the code runnable at all times even if it has type errors.

This also means that it’s safe to publish scripts even if type checker is not fully happy with them - type issues won’t affect script behavior on server/client, they are only displayed in Studio.

## Do I have to re-learn Lua now?!?

This is a question we get often! The answer is “no”.

The way the type system is designed is that it’s completely optional, and you can use as many or as few types as you’d like in your code.

In non-strict mode, types are meant as a lightweight helper - if your code is likely wrong, we’re going to tell you about it, and it’s up to you on whether to fix the issue, or even disable the type checker on a given problematic file if you really don’t feel like dealing with this.

In strict mode, types are meant as a power user tool - they will require more time to develop your code, but they will give you a safety net, where changing code will be much less likely to trigger errors at runtime.

## Is there a performance difference?

Right now type annotations are ignored by our bytecode compiler; this means that performance of the code you write doesn’t actually depend on whether you use strict, nonstrict or nocheck modes or if you have type annotations.

This is likely going to change! We have plans for using the type information to generate better bytecode in certain cases, and types are going to be instrumental to just-in-time compilation, something that we’re going to invest time into next year as well.

Today, however, there’s no difference - type information is completely elided when the bytecode is built, so there is zero runtime impact one way or another.

## What is next for types?

This is the first full release of type checking, but it’s by far the last one. We have a lot more ground to cover. Here’s a few things that we’re excited about that will come next:

  * Making nonstrict mode better to the point where we can enable it as a default for all Roblox scripts

  * Adding several features to make strict mode more powerful/friendly, such as typed variadics, type ascription and better generics support

  * Improving type refinements for type/typeof and nil checks

  * Making it possible to view the type of a variable in Studio

  * Reworking autocomplete to use type information instead of the current system

If you have any feedback on the type system, please don’t hesitate to share it here or in dedicated bug report threads. We’re always happy to fix corner cases that we’ve missed, fix stability issues if they are discovered, improve documentation when it’s not clear or improve error messages when they are hard to understand.


# Recap: May 2021

May 31, 2021

This month we have added a new small feature to the language and spent a lot of time improving our typechecker.

## Named function type arguments

We’ve updated Luau syntax to support optional names of arguments inside function types. The syntax follows the same format as regular function argument declarations: `(a: number, b: string)`

Names can be provided in any place where function type is used, for example:

  * in type aliases:

  * for variables:

Variadic arguments cannot have an extra name, they are already written as …: number.

These names are used for documentation purposes and we also plan to display them in Roblox Studio auto-complete and type hovers. They do not affect how the typechecking of Luau scripts is performed.

## Typechecking improvements

Speaking of typechecking, we’ve implemented many improvements this month:

  * Typechecker will now visit bodies of all member functions, previously it didn’t check methods if the self type was unknown
  * Made improvements to cyclic module import detection and error reporting
  * Fixed incorrect error on modification of table intersection type fields
  * When using an ‘or’ between a nillable type and a value, the resulting type is now inferred to be non-nil
  * We have improved error messages that suggest to use ’:’ for a method call
  * Fixed order of types in type mismatch error that was sometimes reversed
  * Fixed an issue with `table.insert` function signature
  * Fixed a bug which caused spurious unknown global errors

We’ve also added new checks to our linter:

  * A new check will report uses of deprecated Roblox APIs
  * Linter will now suggest replacing globals with locals in more cases
  * New warning is generated if array loop starts or ends on index ‘0’, but the array is indexed from ‘1’
  * FormatString lint will now check string patterns for `find`/`match` calls via `:` when object type is known to be a string

We also fixed one of the sources for “Free types leaked into this module’s public interface” error message and we are working to fix the remaining ones.

As usual, typechecking improvements will not break execution of your games even if new errors get reported.

## Editor features

We continue to improve our built-in support for auto-complete that will be used in future Roblox Studio updates and will make it easier to implement custom extensions for applications that support Language Server Protocol.

As part of this work we will improve the type information provided by Roblox APIs to match actual arguments and results.

## Behavior changes

When a relational comparison fails at runtime, the error message now specifies the comparison direction (e.g. `attempt to compare nil <= number`)

## Performance improvements

  * Improved performance of table lookup with an index operator and a literal string: `t["name"]`
  * Bytecode compilation is now ~5% faster which can improve server startup time for games with lots of scripts


# Recap: March 2023

March 31, 2023

How the time flies! The team has been busy since the last November Recap:working on some large updates that are coming in the future, but before those arrive, we have some improvements that you can already use!

## Improved type refinements

Type refinements handle constraints placed on variables inside conditional blocks.

In the following example, while variable `a` is declared to have type `number?`, inside the `if` block we know that it cannot be `nil`:

One limitation we had previously is that after a conditional block, refinements were discarded.

But there are cases where `if` is used to exit the function early, making the following code essentially act as a hidden `else` block.

We now correctly preserve such refinements and you should be able to remove `assert` function calls that were only used to get rid of false positive errors about types being `nil`.

Throwing calls like `error()` or `assert(false)` instead of a `return` statement are also recognized.

Existing complex refinements like `type`/`typeof`, tagged union checks and other are expected to work as expected.

## Marking table.getn/foreach/foreachi as deprecated

`table.getn`, `table.foreach` and `table.foreachi` were deprecated in Lua 5.1 that Luau is based on, and removed in Lua 5.2.

`table.getn(x)` is equivalent to `rawlen(x)` when ‘x’ is a table; when ‘x’ is not a table, `table.getn` produces an error.

It’s difficult to imagine code where `table.getn(x)` is better than either `#x` (idiomatic) or `rawlen(x)` (fully compatible replacement).

`table.getn` is also slower than both alternatives and was marked as deprecated.

`table.foreach` is equivalent to a `for .. pairs` loop; `table.foreachi` is equivalent to a `for .. ipairs` loop; both may also be replaced by generalized iteration.

Both functions are significantly slower than equivalent for loop replacements, are more restrictive because the function can’t yield.

Because both functions bring no value over other library or language alternatives, they were marked deprecated as well.

You may have noticed linter warnings about places where these functions are used. For compatibility, these functions are not going to be removed.

## Autocomplete improvements

When table key type is defined to be a union of string singletons, those keys can now autocomplete in locations marked as ’^’:

We also fixed incorrect and incomplete suggestions inside the header of `if`, `for` and `while` statements.

## Runtime improvements

On the runtime side, we added multiple optimizations.

`table.sort` is now ~4.1x faster (when not using a predicate) and ~2.1x faster when using a simple predicate.

We also have ideas on how improve the sorting performance in the future.

`math.floor`, `math.ceil` and `math.round` now use specialized processor instructions. We have measured ~7-9% speedup in math benchmarks that heavily used those functions.

A small improvement was made to builtin library function calls, getting a 1-2% improvement in code that contains a lot of fastcalls.

Finally, a fix was made to table array part resizing that brings large improvement to performance of large tables filled as an array, but at an offset (for example, starting at 10000 instead of 1).

Aside from performance, a correctness issue was fixed in multi-assignment expressions.

In this example, `n - 1` was assigned to `n` before `n` was assigned to `arr[1]`. This issue has now been fixed.

## Analysis improvements

Multiple changes were made to improve error messages and type presentation.

  * Table type strings are now shown with newlines, to make them easier to read
  * Fixed unions of `nil` types displaying as a single `?` character
  * “Type pack A cannot be converted to B” error is not reported instead of a cryptic “Failed to unify type packs”
  * Improved error message for value count mismatch in assignments like `local a, b = 2`

You may have seen error messages like `Type 'string' cannot be converted to 'string?'` even though usually it is valid to assign `local s: string? = 'hello'` because `string` is a sub-type of `string?`.

This is true in what is called Covariant use contexts, but doesn’t hold in Invariant use contexts, like in the example below:

In this example, while `Model` is a sub-type of `Instance` and can be used where `Instance` is required.

The same is not true for a table field because when using table `b`, `b.x` can be assigned an `Instance` that is not a `Model`. When `b` is an alias to `a`, this assignment is not compatible with `a`’s type annotation.

Some other light changes to type inference include:

  * `string.match` and `string.gmatch` are now defined to return optional values as match is not guaranteed at runtime
  * Added an error when unrelated types are compared with `==`/`~=`
  * Fixed issues where variable after `typeof(x) == 'table'` could not have been used as a table

## Thanks

A very special thanks to all of our open source contributors:

  * [niansa/tuxifan](https://github.com/niansa)
  * [B. Gibbons](https://github.com/bmg817)
  * [Epix](https://github.com/EpixScripts)
  * [Harold Cindy](https://github.com/HaroldCindy)
  * [Qualadore](https://github.com/Qualadore)


# Recap: January 2022

January 27, 2022

## Performance improvements

The implementation of `tostring` has been rewritten. This change replaces the default number->string conversion with a new algorithm called Schubfach, which allows us to produce the shortest precise round-trippable representation of any input number very quickly.

While performance is not the main driving factor, this also happens to be significantly faster than our old implementation (up to 10x depending on the number and the platform).

Make `tonumber(x)` ~2x faster by avoiding reparsing string arguments.

The Luau compiler now optimizes table literals where keys are constant variables the same way as if they were constants, eg

## Improvements to type assertions

The `::` type assertion operator can now be used to coerce a value between any two related types. Previously, it could only be used for downcasts or casts to `any`. The following used to be invalid, but is now valid:

## Typechecking improvements

An issue surrounding table literals and indexers has been fixed:

Accessing a property whose base expression was previously refined will now return the correct result.

## Linter improvements

`table.create(N, {})` will now produce a static analysis warning since the element is going to be shared for all table entries.

## Error reporting improvements

When a type error involves a union (or an option), we now provide more context in the error message.

For instance, given the following code:

We now report the following:

    Type 'x' could not be converted into 'T?'

    caused by:

      None of the union options are compatible. For example: Table type 'x' not compatible with type 'T' because the former is missing field 'x'

Luau now gives up and reports an `*unknown*` type in far fewer cases when typechecking programs that have type errors.

## New APIs

We have brought in the [`coroutine.close`](https://luau-lang.org/library#coroutine-library) function from Lua 5.4. It accepts a suspended coroutine and marks it as non-runnable. In Roblox, this can be useful in combination with `task.defer` to implement cancellation.

## REPL improvements

The `luau` REPL application can be compiled from source or downloaded from [releases page](https://github.com/Roblox/luau/releases). It has grown some new features:

  * Added `--interactive` option to run the REPL after running the last script file.
  * Allowed the compiler optimization level to be specified.
  * Allowed methods to be tab completed
  * Allowed methods on string instances to be completed
  * Improved Luau REPL argument parsing and error reporting
  * Input history is now saved/loaded

## Thanks

A special thanks to all the fine folks who contributed PRs over the last few months!

  * [Halalaluyafail3](https://github.com/Halalaluyafail3)
  * [JohnnyMorganz](https://github.com/JohnnyMorganz)
  * [Kampfkarren](https://github.com/Kampfkarren)
  * [kunitoki](https://github.com/kunitoki)
  * [MathematicalDessert](https://github.com/MathematicalDessert)
  * [metatablecat](https://github.com/metatablecat)
  * [petrihakkinen](https://github.com/petrihakkinen)
  * [rafa_br34](https://github.com/rafa_br34)
  * [Rerumu](https://github.com/Rerumu)
  * [Slappy826](https://github.com/Slappy826)
  * [SnowyShiro](https://github.com/SnowyShiro)
  * [vladmarica](https://github.com/vladmarica)
  * [xgladius](https://github.com/xgladius)

[Contribution guide](https://github.com/Roblox/luau/blob/2f989fc049772f36de1a4281834c375858507bda/CONTRIBUTING.md)


# Recap: October 2021

October 31, 2021

## `if-then-else` expression

In addition to supporting standard if _statements_ , Luau adds support for if _expressions_. Syntactically, `if-then-else` expressions look very similar to if statements. However instead of conditionally executing blocks of code, if expressions conditionally evaluate expressions and return the value produced as a result. Also, unlike if statements, if expressions do not terminate with the `end` keyword.

Here is a simple example of an `if-then-else` expression:

`if-then-else` expressions may occur in any place a regular expression is used. The `if-then-else` expression must match `if <expr> then <expr> else <expr>`; it can also contain an arbitrary number of `elseif` clauses, like `if <expr> then <expr> elseif <expr> then <expr> else <expr>`. Note that in either case, `else` is mandatory.

Here’s is an example demonstrating `elseif`:

**Note:** In Luau, the `if-then-else` expression is preferred vs the standard Lua idiom of writing `a and b or c` (which roughly simulates a ternary operator). However, the Lua idiom may return an unexpected result if `b` evaluates to false. The `if-then-else` expression will behave as expected in all situations.

## Library improvements

New additions to the `table` library have arrived:

Given a non-frozen table, freezes it such that all subsequent attempts to modify the table or assign its metatable raise an error. If the input table is already frozen or has a protected metatable, the function raises an error; otherwise it returns the input table. Note that the table is frozen in-place and is not being copied. Additionally, only `t` is frozen, and keys/values/metatable of `t` don’t change their state and need to be frozen separately if desired.

Returns `true` if and only if the input table is frozen.

## Typechecking improvements

We continue work on our type constraint resolver and have multiple improvements this month.

We now resolve constraints that are created by `or` expressions. In the following example, by checking against multiple type alternatives, we learn that value is a union of those types:

Support for `or` constraints allowed us to handle additional scenarios with `and` and `not` expressions to reduce false positives after specific type guards.

And speaking of type guards, we now correctly handle sub-class relationships in those checks:

One more fix handles the `a and b or c` expression when ‘b’ depends on ‘a’:

Of course, our new if-then-else expressions handle this case as well.

We have extended bidirectional typechecking that was announced last month to propagate types in additional statements and expressions.

We’ve also improved some of our messages with union types and optional types (unions types with `nil`).

When optional types are used incorrectly, you get better messages. For example:

When a property of a union type is accessed, but is missing from some of the options, we will report which options are not valid:

When we enabled generic functions last month, some users might have seen a strange error about generic functions not being compatible with regular ones.

This was caused by undefined behaviour of recursive types. We have now added a restriction on how generic type parameters can be used in recursive types: [RFC: Recursive type restriction](https://github.com/Roblox/luau/blob/master/rfcs/recursive-type-restriction.md)

## Performance improvements

An improvement to the Stop-The-World (atomic in Lua terms) stage of the garbage collector was made to reduce time taken by that step by 4x factor. While this step only happens once during a GC cycle, it cannot be split into small parts and long times were visible as frame time spikes.

Table construction and resize was optimized further; as a result, many instances of table construction see 10-20% improvements for smaller tables on all platforms and 20%+ improvements on Windows.

Bytecode compiler has been optimized for giant table literals, resulting in 3x higher compilation throughput for certain files on AMD Zen architecture.

Coroutine resumption has been optimized and is now ~10% faster for coroutine-heavy code.

Array reads and writes are also now a bit faster resulting in 1-3% lift in array-heavy benchmarks.


# Recap: February 2020

February 25, 2020

We continue to iterate on our language stack, working on many features for type checking, performance, and quality of life. Some of them come with announcements, some come with release notes, and some just ship - here we will talk about all things that happened since November last year.

A lot of people work on these improvements; thanks @Apakovtac, @EthicalRobot, @fun_enthusiast, @xyzzyismagic, @zeuxcg!

We were originally intending to ship the beta last year but had to delay it due to last minute bugs. However, it’s now live as a beta option on production! Go here to learn more:

EDIT: Please DO NOT publish places with type annotations just yet as they will not work on production! This is why it’s a beta 🙂 However, please continue to experiment in Studio and give us feedback. We are reading everything and will be fixing reported bugs and discussing syntax / semantics issues some people brought up. Hello! We’ve been quietly working on building a type checker for Lua for quite some time now. It is now far enough along that we’d really like to hear what…

We’re continuing to iterate on the feedback we have received here. Something that will happen next is that we will enable type annotations on live server/clients - which will mean that you will be able to publish source code with type annotations without breaking your games. We still have work to do on the non-strict and strict mode type checking before the feature can move out of beta though, in particular we’ve implemented support for require statement and that should ship next week 🤞

We also fixed a few bugs in the type definitions for built-in functions/API and the type checker itself:

  * `table.concat` was accidentally treating the arguments as required
  * `string.byte` and `string.find` now have a correct precise type
  * `typeof` comparisons in if condition incorrectly propagated the inferred type into `elseif` branches

We are also making the type checker more ergonomic and more correct. Two changes I want to call out are:

  * Type aliases declared with `type X = Y` are now co-recursive, meaning that they can refer to each other, e.g.

  * We now support type intersections `(A & B)` in addition to type unions `(A | B)`. Intersections are critical to modeling overloaded functions correctly - while Lua as a language doesn’t support function overloads, we have various APIs that have complex overloaded semantics - one of them that people who tried the beta had problems with was UDim2.new - and it turns out that to correctly specify the type, we had to add support for intersections. This isn’t really intended as a feature that is used often in scripts developers write, but it’s important for internal use.

## Debugger (beta)

When we shipped the original version of the VM last year, we didn’t have the debugger fully working. Debugger relies on low-level implementation of the old VM that we decided to remove from the new VM - as such we had to make a new low-level debugging engine.

This is now live under the Luau VM beta feature, see [this post](https://devforum.roblox.com/t/luau-in-studio-beta/456529) for details.

If you use the debugger at all, please enable the beta feature and try it out - we want to fix all the bugs we can find, and this is blocking us enabling the new VM everywhere.

(a quick aside: today the new VM is enabled on all servers and all clients, and it’s enabled in Studio “edit” mode for plugins - but not in Studio play modes, and full debugger support is what prevents us from doing so)

## Language

This section is short and sweet this time around:

  * You can now use continue statement in for/while/repeat loops. :tada:

Please note that we only support this in the new VM, so you have to be enrolled in Luau VM beta to be able to use it in Studio. It will work in game regardless of the beta setting as per above.

## Performance

While we have some really forward looking ideas around multi-threading and native code compilation that we’re starting to explore, we also continue to improve performance across the board based on our existing performance backlog and your feedback.

In particular, there are several memory and performance optimizations that shipped in the last few months:

  * Checking for truth (`if foo or foo and bar`) is now a bit faster, giving 2-3% performance improvements on some benchmarks
  * `table.create` (with value argument) and `table.pack` have been reimplemented and are ~1.5x faster than before
  * Internal mechanism for filling arrays has been made faster as well, which makes `Terrain:ReadVoxels` ~10% faster
  * Catching engine-generated errors with pcall/xpcall is now ~1.5x faster (this only affects performance of calls that generated errors)
  * String objects now take 8 bytes less memory per object (and in an upcoming change we’ll save a further 4 bytes)
  * Capturing local variables that are never assigned to in closures is now much faster, takes much less memory and generates much less GC pressure. This can make closure creation up to 2x faster, and improves some Roact benchmarks by 10%. This is live in Studio and will ship everywhere else shortly.
  * The performance of various for loops (numeric & ipairs) on Windows regressed after a VS2017 upgrade; this regression has been fixed, making all types of loops perform roughly equally. VS2017 upgrade also improved Luau performance on Windows by ~10% across the board.
  * Lua function calls have been optimized a bit more, gaining an extra 10% of performance in call-heavy benchmarks on Windows.
  * Variadic table constructors weren’t compiled very efficiently, resulting in surprisingly low performance of constructs like `{...}`. Fixing that made `{...}` ~3x faster for a typical number of variadic arguments.

## Diagnostics

We spent some time to improve error messages in various layers of the stack based on the reports from community. Specifically:

  * The static analysis warning about incorrect bounds for numeric for loops is now putting squigglies in the right place.
  * Fixed false positive static analysis warnings about unreachable code inside repeat…until loops in certain cases.
  * Multiline table construction expressions have a more precise line information now which helps in debugging since callstacks are now easier to understand
  * Incomplete statements (e.g. foo) now produce a more easily understandable parsing error
  * In some cases when calling the method with a `.` instead of `:`, we emitted a confusing error message at runtime (e.g. humanoid.LoadAnimation(animation)). We now properly emit the error message asking the user if `:` was intended.
  * The legacy global `ypcall` is now flagged as deprecated by script analysis
  * If you use a Unicode symbol in your source program outside of comments or string literals, we now produce a much more clear message, for example:

produces `Unexpected Unicode character: U+2024. Did you mean '.'?`

## LoadLibrary removal

Last but not least, let’s all press [F for LoadLibrary](https://devforum.roblox.com/t/loadlibrary-is-going-to-be-removed-on-february-3rd/382516).

It was fun while it lasted, but supporting it caused us a lot of pain over the years and prevented some forward-looking changes to the VM. We don’t like removing APIs from the platform, but in this case it was necessary. Thanks to the passionate feedback from the community we adjusted our initial rollout plans to be less aggressive and batch-processed a lot of gear items that used this function to stop using this function. The update is in effect and LoadLibrary is no more.

As usual, if you have any feedback about any of these updates, suggestions, bug reports, etc., post them in this thread or (preferably for bugs) as separate posts in the bug report category.


# Recap: June 2020

June 20, 2020

… otherwise known as “This Month in Luau” I guess? You know the drill by now. We’ll talk about exciting things that happened to Luau - our new language stack.

anxiously glances at FIB3 thread that casts a huge shadow on this announcement, but hopefully somebody will read this

Many people work on these improvements; thanks @Apakovtac, @EthicalRobot, @fun_enthusiast, @zeuxcg!

## We have a website!

Many developers told us on many occasions that as much as they love the recaps, it’s hard to know what the state of the language or libraries is if the only way to find out is to read through all updates. What’s the syntax extensions that Luau supports now? How do I use type checking? What’s the status of <my favorite feature> from Lua 5.x?

Well, you can find all of this out here now: <https://roblox.github.io/luau/>

Please let us know if this documentation can be improved - what are you missing, what could be improved. For now to maximize change velocity this documentation is separate from DevHub; it’s also meant as an external resource for people who don’t really use the language but are curious about the differences and novelties.

Also, `_VERSION` now returns “Luau” because we definitely aren’t using Lua 5.1 anymore.

## Compound assignments

A long-standing feature request for Lua is compound assignments. Somehow Lua never got this feature, but Luau now implements `+=`, `-=`, `*=`, `/=`, `%=`, `^=` and `..=` operators. We decided to implement them because they are absolutely ubiquitous among most frequently used programming languages, both those with C descent and those with different lineage (Ruby, Python). They result in code that’s easier to read and harder to make mistakes in.

We do not implement `++` and `--`. These aren’t universally adopted, `--` conflicts with comment syntax and they are arguably not as intuitively obvious. We trust everyone to type a few extra characters for `+= 1` without too much trouble.

Two important semantical notes are that the expressions on the left hand side are only evaluated once, so for example `table[makeIndex()] += 1` only runs `makeIndex` once, and that compound assignments still call all the usual metamethod (`__add` et al, and `__index`/`__newindex`) when necessary - you don’t need to change any data structures to work with these.

There’s no noticeable performance improvement from these operators (nor does using them carry a cost) - use them when they make sense for readability.

## Nicer error messages

Good errors are critical to be able to use Luau easily. We’ve spent some time to improve the quality of error messages during parsing and runtime execution:

  * In runtime type errors, we now often use the “Roblox” type name instead of plain userdata, e.g. `math.abs(v)` now says `number` expected, got `Vector3`

  * When arguments are just missing, we now explicitly say that they are missing in libraries like math/table; the old message was slightly more confusing

  * `string.format` in some cases produced error messages that confused missing arguments for incorrect types, which has been fixed

  * When a builtin function such as `math.abs` fails, we now add the function name to the error message. This is something that used to happen in Lua, then we lost this in Luau because Luau removes a very fragile mechanism that supported that, but we now have a new, robust way to report this so you can have the function name back! The message looks like this now: `invalid argument #1 to 'abs' (number expected, got nil)`

  * In compile-time type errors, we now can identify the case when the field was mistyped with a wrong case (ha), and tell you to use the correct case instead.

  * When you forget an `end` statement, we now try to be more helpful and point you to the problematic statement instead of telling you that the end is missing at the very end of the program. This one is using indentation as a heuristic so it doesn’t always work perfectly.

  * We now have slightly more helpful messages for cases when you forget parentheses after a function call

  * We now have slightly more helpful messages for some cases when you accidentally use `( ... )` instead of `{ ... }` to create a table literal Additionally two places had very lax error checking that made the code more fragile, and we fixed those:

  * `xpcall` now fails immediately when the error function argument is not a function; it used to work up until you get an error, and failed at that point, which made it hard to find these bugs

  * `tostring` now enforces the return type of the result to be a string - previously `__tostring` could return a non-string result, which worked fine up until you tried to do something like passing the resulting value to `string.format` for `%s`. Now `tostring` will fail early. Our next focus here is better error messages during type checking - please let us know if there are other errors you find confusing and we could improve!

## Type checker improvements

We’re getting closer and closer to be able to move out of beta. A big focus this month was on fixing all critical bugs in the type checker - it now should never hang or crash Studio during type checking, which took a bit of work to iron out all the problems.

Notably, typing function string.length no longer crashes Studio (although why you’d do that is unclear), and Very Large Scripts With Tons Of Nested Statements And Expressions should be stable as well.

We’ve also cleaned up the type information for builtin libraries to make it even more precise, including a few small fixes to `string/math` functions, and a much more precise coroutine library type information. For the latter we’ve introduced a primitive type `thread`, which is what `coroutine` library works with.

## Linter improvements

Linter is the component that produces warnings about scripts; it’s otherwise known as “Static Analysis” in Studio, although that is now serving as a place where we show type errors as well.

Most of the changes here this month are internal as they concern warnings that aren’t yet enabled in Studio (the web site linked above documents all warnings including ones that aren’t active yet but may become active), but once notable feature is that you can now opt out of individual warnings on a script-by-script basis by adding a —!nolint comment to the top of the script. For example, if you really REALLY _REALLY_ like the `Game` global, you can add this to the top of the script:

Or, if you basically just want us to not issue any warnings ever, I guess you can add this:

and live happily ignorant of all possible errors up until you run your code. (please don’t do that)

## os. enhancements

Our overall goal is to try to be reasonably compatible with Lua 5.x in terms of library functions we expose. This doesn’t always work - in some cases we have to remove library features for sandboxing reasons, and in others the library functions don’t make sense in context of Roblox. However, some of these decisions can be revised later. In particular, when we re-added `os.` library to Roblox, we limited it to `os.date`, `os.time` and `os.difftime` (although why `difftime` is a thing isn’t clear), omitting `os.clock` and restricting inputs to `os.date` to return a table with date components, whereas Lua 5.x supports format strings.

Well, this changes today. `os.clock` is now available if you need a high-precision time for benchmarking, and `os.date` can now return formatted date using Lua 5.x format string that you can read about here <https://www.lua.org/pil/22.1.html> (we support all these specifiers: aAbBcdHIjmMpSUwWxXyYzZ).

While `os.date()` is hopefully welcome, `os.clock` may raise some eyebrows - aren’t there enough timing functions in Roblox already? Well, this is nice if you are trying to port code from Lua 5.x to Luau, and there’s this

But really, most existing Roblox timing functions are… problematic.

  * `time()` returns the total amount of time the game has been running simulation for, it’s monotonic and has reasonable precision. It’s fine - you can use it to update internal gameplay systems without too much trouble. It should’ve been called “tick” perhaps but that ship has sailed.
  * `elapsedTime` and its close cousin `ElapsedTime`, are telling you “how much time has elapsed since the current instance of Roblox was started.”. While technically true, this isn’t actually useful because on mobile the “start” time here can be days in the past. It’s also inadequate for performance measurements as on Windows, it has a 1ms resolution which isn’t really enough for anything interesting. We’re going to deprecate this in the future.
  * `tick()` sounds perfect - it has a high resolution (usually around 1 microsecond), and a well-defined baseline - it counts since UNIX epoch! Or, well, it actually doesn’t. On Windows, it returns you a variant of the UNIX timestamp in local time zone. In addition, it can be off by 1 second from the actual, real UNIX timestamp, and might have other idiosyncrasies on non-Windows platforms. We’re going to deprecate this in the future

So, if you need a UNIX timestamp, you should use `os.time()`. You get a stable baseline (from 1970’s) and 1s resolution. If you need to measure performance, you should use `os.clock()`. You don’t get a stable baseline, but you get ~1us resolution. If you need to do anything else, you should probably use `time()`.

## Performance optimizations

As you can never have too much performance, we’re continuing to work on performance! We’re starting to look into making Vector3 faster and improving the garbage collector, with some small changes already shipping, but overall it’s a long way out so here are the things that did get visibly better:

  * A few `string.` methods, notably `string.byte` and `string.char`, were optimized to make it easier to write performant deserialization code. string.byte is now ~4x faster than before for small numbers of returned characters. For optimization to be effective, it’s important to call the function directly ( `string.byte(foo, 5)` ) instead of using method calls ( `foo:byte(5)` )
  * Optimize coroutine resumption, making some code that is heavily reliant on `coroutine`. library ~10% faster. We have plans to improve this further, watch this space.
  * Optimize `typeof()` to run ~6x faster. It used to be that `type()` was much faster than `typeof()` but they now should be more or less comparable.
  * Some secret internal optimizations make some scripts a few percent faster
  * The memory allocator used in Luau was rewritten using a new, more efficient, implementation. There might be more changes here in the future to save some memory, but for now this makes some allocation-intensive benchmarks ~15% faster.
  * Using tables with keys that are not strings or numbers is a fair bit more efficient now (most commonly comes up when Instance is used as a key in a hash table), on par with using strings.

Also we found a bug with some of our optimizations (which delayed the string. performance improvement above, but also could affect some math. calls) where in some complex functions you would see valid calls to math. etc. breaking with non-sensical errors such as “expected number, got table” - this has been fixed!

## Memory optimizations

As with performance, our goal here is simple - the more efficient internal Luau structures can become, the less memory will Lua heap take. This is great for both memory consumption, and for garbage collection performance as the collector needs to traverse less data. There’s a few exciting changes in this area this month:

  * Non-array-like tables now take 20% less space. This doesn’t affect arrays but can be observed on object-like tables, both big and small. This is great because some of you are using a lot of large tables apparently, since this resulted in very visible reduction in overall Lua heap sizes across all games.
  * Function objects now take up to 30% less space. This isn’t as impactful since typically function objects are not created very frequently and/or don’t live for very long, but it’s nice nonetheless.
  * New allocator mentioned in the previous section can save up to 5-6% of Lua heap memory as well, although these gains are highly dependent on the workload, and we usually see savings in the 1-2% range.

And that’s it! Till next time. As usual let us know if you have questions, suggestions or bug reports.


# Recap: November 2022

November 30, 2022

While the team is busy to bring some bigger things in the future, we have made some small improvements this month.

## Analysis improvements

We have improved tagged union type refinements to only include unhandled type cases in the `else` branch of the `if` statement:

For better inference, we updated the definition of `Enum.SomeType:GetEnumItems()` to return `{Enum.SomeType}` instead of common `{EnumItem}` and the return type of `next` function now includes the possibility of key being `nil`.

Finally, if you use `and` operator on non-boolean values, `boolean` type will no longer be added by the type inference:

## Error message improvements

We now give an error when built-in types are being redefined:

We also had a parse error missing in case you forgot your default type pack parameter value. We accepted the following code silently without raising an issue:

Error about function argument count mismatch no longer points at the last argument, but instead at the function in question. So, instead of:

We now highlight this:

If you iterate over a table value that could also be `nil`, you get a better explanation in the error message:

Previously it was `Cannot call non-function {number}?` which was confusing.

And speaking of confusing, some of you might have seen an error like `Type 'string' could not be converted into 'string'`.

This was caused by Luau having both a primitive type `string` and a table type coming from `string` library. Since the way you can get the type of the `string` library table is by using `typeof(string)`, the updated error message will mirror that and report `Type 'string' could not be converted into 'typeof(string)'`.

Parsing now recovers with a more precise error message if you forget a comma in table constructor spanning multiple lines:


# Recap: August 2021

August 31, 2021

## Editor features

The Roblox Studio [Luau-Powered Autocomplete & Language Features Beta](https://devforum.roblox.com/t/script-editor-luau-powered-autocomplete-language-features-beta) that our team has been working on has finally been released! Be sure to check that out and leave your feedback for things we can improve.

To support that feature, a lot of work went into:

  * Improving fault-tolerant parser recovery scenarios
  * Storing additional information in the AST, including comments, better location information and partial syntax data
  * Tracking additional information about types and their fields, including tracking definition locations, function argument names, deprecation state and custom Roblox-specific tags
  * Updating reflection information to provide more specific `Instance` types and correct previously missing or wrong type annotations
  * Hybrid typechecking mode which tries to infer types even in scripts with no typechecking enabled
  * Support for types that are attached to the `DataModel` tree elements to provide instance member information
  * Placing limits to finish typechecking in a finite space/time
  * Adding Autocomplete API for the Roblox Studio to get location-based entity information and appropriate suggestions
  * Additional type inference engine improvements and fixes

While our work continues to respond to the feedback we receive, our team members are shifting focus to add generic functions, improve type refinements in conditionals, extend Parallel Luau, improve Lua VM performance and provide documentation.

## Typechecking improvements

Type constraint resolver now remembers constraints placed on individual table fields.

This should fix false-positive errors reported after making sure the optional table field is present:

And it can also refine field type to a more specific one:

Like before, combining multiple conditions using ‘and’ and ‘not’ is also supported.

Constructing arrays with different values for optional/union types are now also supported for individual table fields and in functions call arguments:

Finally, we have fixed an issue with Roblox class field access using indexing like `part["Anchored"] = true`.

## Linter improvements

We have added a new linter check for duplicate local variable definitions.

It is created to find duplicate names in cases like these:

Our UnknownType linter warning was extended to check for correct class names passed into `FindFirstChildOfClass`, `FindFirstChildWhichIsA`, `FindFirstAncestorOfClass` and `FindFirstAncestorWhichIsA` functions.

## Performance improvements

We have added an optimization to ‘table.unpack’ for 2x performance improvement.

We’ve also implemented an extra optimization for tables to predict required table capacity based on fields that are assigned to it in the code after construction. This can reduce the need to reallocate tables.

Variadic call performance was fine-tuned and is now ~10% faster.

Construction of array literals was optimized for a ~7% improvement.

Another optimization this month changes the location and rate of garbage collection invocations. We now try to avoid calling GC during the script execution and perform all the work in the GcJob part of the frame (it could be seen in the performance profiler). When possible, we can now skip that job in the frame completely, if we have some memory budget available.

## Other improvements

For general stability improvements we fixed a crash when strange types like ‘`nil??`’ are used and when users have their own global functions named ‘`require`’.

Indexing a table with an incompatible type will now show squiggly error lines under the index instead of the whole expression, which was a bit misleading.

An issue with debug information that caused `repeat ... until` condition line to be skipped when stepping was fixed.

Type output was improved to replace display of types like ‘`{<g405>(g405) -> g405}`’ with ‘`{<a>(a) -> a}`’.


# Semantic Subtyping in Luau

October 31, 2022

Luau is the first programming language to put the power of semantic subtyping in the hands of millions of creators.

## Minimizing false positives

One of the issues with type error reporting in tools like the Script Analysis widget in Roblox Studio is _false positives_. These are warnings that are artifacts of the analysis, and don’t correspond to errors which can occur at runtime. For example, the program

reports a type error which cannot happen at runtime, since `CFrame` supports multiplication by both `Vector3` and `CFrame`. (Its type is `((CFrame, CFrame) -> CFrame) & ((CFrame, Vector3) -> Vector3)`.)

False positives are especially poor for onboarding new users. If a type-curious creator switches on typechecking and is immediately faced with a wall of spurious red squiggles, there is a strong incentive to immediately switch it off again.

Inaccuracies in type errors are inevitable, since it is impossible to decide ahead of time whether a runtime error will be triggered. Type system designers have to choose whether to live with false positives or false negatives. In Luau this is determined by the mode: `strict` mode errs on the side of false positives, and `nonstrict` mode errs on the side of false negatives.

While inaccuracies are inevitable, we try to remove them whenever possible, since they result in spurious errors, and imprecision in type-driven tooling like autocomplete or API documentation.

## Subtyping as a source of false positives

One of the sources of false positives in Luau (and many other similar languages like TypeScript or Flow) is _subtyping_. Subtyping is used whenever a variable is initialized or assigned to, and whenever a function is called: the type system checks that the type of the expression is a subtype of the type of the variable. For example, if we add types to the above program

then the type system checks that the type of `CFrame` multiplication is a subtype of `(CFrame, Vector3 | CFrame) -> (Vector3 | CFrame)`.

Subtyping is a very useful feature, and it supports rich type constructs like type union (`T | U`) and intersection (`T & U`). For example, `number?` is implemented as a union type `(number | nil)`, inhabited by values that are either numbers or `nil`.

Unfortunately, the interaction of subtyping with intersection and union types can have odd results. A simple (but rather artificial) case in older Luau was:

This error is caused by a failure of subtyping, the old subtyping algorithm reports that `(number?) & (string?)` is not a subtype of `nil`. This is a false positive, since `number & string` is uninhabited, so the only possible inhabitant of `(number?) & (string?)` is `nil`.

This is an artificial example, but there are real issues raised by creators caused by the problems, for example <https://devforum.roblox.com/t/luau-recap-july-2021/1382101/5>. Currently, these issues mostly affect creators making use of sophisticated type system features, but as we make type inference more accurate, union and intersection types will become more common, even in code with no type annotations.

This class of false positives no longer occurs in Luau, as we have moved from our old approach of _syntactic subtyping_ to an alternative called _semantic subtyping_.

## Syntactic subtyping

AKA “what we did before.”

Syntactic subtyping is a syntax-directed recursive algorithm. The interesting cases to deal with intersection and union types are:

  * Reflexivity: `T` is a subtype of `T`
  * Intersection L: `(T₁ & … & Tⱼ)` is a subtype of `U` whenever some of the `Tᵢ` are subtypes of `U`
  * Union L: `(T₁ | … | Tⱼ)` is a subtype of `U` whenever all of the `Tᵢ` are subtypes of `U`
  * Intersection R: `T` is a subtype of `(U₁ & … & Uⱼ)` whenever `T` is a subtype of all of the `Uᵢ`
  * Union R: `T` is a subtype of `(U₁ | … | Uⱼ)` whenever `T` is a subtype of some of the `Uᵢ`.

For example:

  * By Reflexivity: `nil` is a subtype of `nil`
  * so by Union R: `nil` is a subtype of `number?`
  * and: `nil` is a subtype of `string?`
  * so by Intersection R: `nil` is a subtype of `(number?) & (string?)`.

Yay! Unfortunately, using these rules:

  * `number` isn’t a subtype of `nil`
  * so by Union L: `(number?)` isn’t a subtype of `nil`
  * and: `string` isn’t a subtype of `nil`
  * so by Union L: `(string?)` isn’t a subtype of `nil`
  * so by Intersection L: `(number?) & (string?)` isn’t a subtype of `nil`.

This is typical of syntactic subtyping: when it returns a “yes” result, it is correct, but when it returns a “no” result, it might be wrong. The algorithm is a _conservative approximation_ , and since a “no” result can lead to type errors, this is a source of false positives.

## Semantic subtyping

AKA “what we do now.”

Rather than thinking of subtyping as being syntax-directed, we first consider its semantics, and later return to how the semantics is implemented. For this, we adopt semantic subtyping:

  * The semantics of a type is a set of values.
  * Intersection types are thought of as intersections of sets.
  * Union types are thought of as unions of sets.
  * Subtyping is thought of as set inclusion.

For example:

Type| Semantics
`number`| { 1, 2, 3, … }
`string`| { “foo”, “bar”, … }
`nil`| { nil }
`number?`| { nil, 1, 2, 3, … }
`string?`| { nil, “foo”, “bar”, … }
`(number?) & (string?)`| { nil, 1, 2, 3, … } ∩ { nil, “foo”, “bar”, … } = { nil }

and since subtypes are interpreted as set inclusions:

Subtype| Supertype| Because
---|---|---
`nil`| `number?`| { nil } ⊆ { nil, 1, 2, 3, … }
`nil`| `string?`| { nil } ⊆ { nil, “foo”, “bar”, … }
`nil`| `(number?) & (string?)`| { nil } ⊆ { nil }
`(number?) & (string?)`| `nil`| { nil } ⊆ { nil }

So according to semantic subtyping, `(number?) & (string?)` is equivalent to `nil`, but syntactic subtyping only supports one direction.

This is all fine and good, but if we want to use semantic subtyping in tools, we need an algorithm, and it turns out checking semantic subtyping is non-trivial.

## Semantic subtyping is hard

NP-hard to be precise.

We can reduce graph coloring to semantic subtyping by coding up a graph as a Luau type such that checking subtyping on types has the same result as checking for the impossibility of coloring the graph

For example, coloring a three-node, two color graph can be done using types:

Then a graph can be encoded as an overload function type with subtype `Uncolorable` and supertype `Coloring`, as an overloaded function which returns `false` when a constraint is violated. Each overload encodes one constraint. For example a line has constraints saying that adjacent nodes cannot have the same color:

A triangle is similar, but the end points also cannot have the same color:

Now, `Triangle` is a subtype of `Uncolorable`, but `Line` is not, since the line can be 2-colored. This can be generalized to any finite graph with any finite number of colors, and so subtype checking is NP-hard.

We deal with this in two ways:

  * we cache types to reduce memory footprint, and
  * give up with a “Code Too Complex” error if the cache of types gets too large.

Hopefully this doesn’t come up in practice much. There is good evidence that issues like this don’t arise in practice from experience with type systems like that of Standard ML, which is [EXPTIME-complete](https://dl.acm.org/doi/abs/10.1145/96709.96748), but in practice you have to go out of your way to code up Turing Machine tapes as types.

## Type normalization

The algorithm used to decide semantic subtyping is _type normalization_. Rather than being directed by syntax, we first rewrite types to be normalized, then check subtyping on normalized types.

A normalized type is a union of:

  * a normalized nil type (either `never` or `nil`)
  * a normalized number type (either `never` or `number`)
  * a normalized boolean type (either `never` or `true` or `false` or `boolean`)
  * a normalized function type (either `never` or an intersection of function types) etc

Once types are normalized, it is straightforward to check semantic subtyping.

Every type can be normalized (sigh, with some technical restrictions around generic type packs). The important steps are:

  * removing intersections of mismatched primitives, e.g. `number & bool` is replaced by `never`, and
  * removing unions of functions, e.g. `((number?) -> number) | ((string?) -> string)` is replaced by `(nil) -> (number | string)`.

For example, normalizing `(number?) & (string?)` removes `number & string`, so all that is left is `nil`.

Our first attempt at implementing type normalization applied it liberally, but this resulted in dreadful performance (complex code went from typechecking in less than a minute to running overnight). The reason for this is annoyingly simple: there is an optimization in Luau’s subtyping algorithm to handle reflexivity (`T` is a subtype of `T`) that performs a cheap pointer equality check. Type normalization can convert pointer-identical types into semantically-equivalent (but not pointer-identical) types, which significantly degrades performance.

Because of these performance issues, we still use syntactic subtyping as our first check for subtyping, and only perform type normalization if the syntactic algorithm fails. This is sound, because syntactic subtyping is a conservative approximation to semantic subtyping.

## Pragmatic semantic subtyping

Off-the-shelf semantic subtyping is slightly different from what is implemented in Luau, because it requires models to be _set-theoretic_ , which requires that inhabitants of function types “act like functions.” There are two reasons why we drop this requirement.

**Firstly**, we normalize function types to an intersection of functions, for example a horrible mess of unions and intersections of functions:

    ((number?) -> number?) | (((number) -> number) & ((string?) -> string?))

normalizes to an overloaded function:

    ((number) -> number?) & ((nil) -> (number | string)?)

Set-theoretic semantic subtyping does not support this normalization, and instead normalizes functions to _disjunctive normal form_ (unions of intersections of functions). We do not do this for ergonomic reasons: overloaded functions are idiomatic in Luau, but DNF is not, and we do not want to present users with such non-idiomatic types.

Our normalization relies on rewriting away unions of function types:

    ((A) -> B) | ((C) -> D)   →   (A & C) -> (B | D)

This normalization is sound in our model, but not in set-theoretic models.

**Secondly**, in Luau, the type of a function application `f(x)` is `B` if `f` has type `(A) -> B` and `x` has type `A`. Unexpectedly, this is not always true in set-theoretic models, due to uninhabited types. In set-theoretic models, if `x` has type `never` then `f(x)` has type `never`. We do not want to burden users with the idea that function application has a special corner case, especially since that corner case can only arise in dead code.

In set-theoretic models, `(never) -> A` is a subtype of `(never) -> B`, no matter what `A` and `B` are. This is not true in Luau.

For these two reasons (which are largely about ergonomics rather than anything technical) we drop the set-theoretic requirement, and use _pragmatic_ semantic subtyping.

## Negation types

The other difference between Luau’s type system and off-the-shelf semantic subtyping is that Luau does not support all negated types.

The common case for wanting negated types is in typechecking conditionals:

This uses a negated type `~string` inhabited by values that are not strings.

In Luau, we only allow this kind of typing refinement on _test types_ like `string`, `function`, `Part` and so on, and _not_ on structural types like `(A) -> B`, which avoids the common case of general negated types.

## Prototyping and verification

During the design of Luau’s semantic subtyping algorithm, there were changes made (for example initially we thought we were going to be able to use set-theoretic subtyping). During this time of rapid change, it was important to be able to iterate quickly, so we initially implemented a [prototype](https://github.com/luau-lang/agda-typeck) rather than jumping straight to a production implementation.

Validating the prototype was important, since subtyping algorithms can have unexpected corner cases. For this reason, we adopted Agda as the prototyping language. As well as supporting unit testing, Agda supports mechanized verification, so we are confident in the design.

The prototype does not implement all of Luau, just the functional subset, but this was enough to discover subtle feature interactions that would probably have surfaced as difficult-to-fix bugs in production.

Prototyping is not perfect, for example the main issues that we hit in production were about performance and the C++ standard library, which are never going to be caught by a prototype. But the production implementation was otherwise fairly straightforward (or at least as straightforward as a 3kLOC change can be).

## Next steps

Semantic subtyping has removed one source of false positives, but we still have others to track down:

  * overloaded function applications and operators,
  * property access on expressions of complex type,
  * read-only properties of tables,
  * variables that change type over time (aka typestates),
  * …

The quest to remove spurious red squiggles continues!

## Acknowledgments

Thanks to Giuseppe Castagna and Ben Greenman for helpful comments on drafts of this post.

## Further reading

If you want to find out more about Luau and semantic subtyping, you might want to check out…

  * Luau. <https://luau-lang.org/>
  * Lily Brown, Andy Friesen and Alan Jeffrey, _Goals of the Luau Type System_ , Human Aspects of Types and Reasoning Assistants (HATRA), 2021. <https://arxiv.org/abs/2109.11397>
  * Luau Typechecker Prototype. <https://github.com/luau-lang/agda-typeck>
  * Agda. <https://agda.readthedocs.io/>
  * Andrew M. Kent. _Down and Dirty with Semantic Set-theoretic Types_ , 2021. <https://pnwamk.github.io/sst-tutorial/>
  * Giuseppe Castagna, _Covariance and Contravariance_ , Logical Methods in Computer Science 16(1), 2022. <https://arxiv.org/abs/1809.01427>
  * Giuseppe Castagna and Alain Frisch, _A gentle introduction to semantic subtyping_ , Proc. Principles and practice of declarative programming (PPDP), pp 198–208, 2005. <https://doi.org/10.1145/1069774.1069793>
  * Giuseppe Castagna, Mickaël Laurent, Kim Nguyễn, Matthew Lutze, _On Type-Cases, Union Elimination, and Occurrence Typing_ , Principles of Programming Languages (POPL), 2022. <https://doi.org/10.1145/3498674>
  * Giuseppe Castagna, _Programming with union, intersection, and negation types_ , 2022. <https://arxiv.org/abs/2111.03354>
  * Sam Tobin-Hochstadt and Matthias Felleisen, _Logical types for untyped languages_. International Conference on Functional Programming (ICFP), 2010. <https://doi.org/10.1145/1863543.1863561>
  * José Valim, _My Future with Elixir: set-theoretic types_ , 2022. <https://elixir-lang.org/blog/2022/10/05/my-future-with-elixir-set-theoretic-types/>

Some other languages which support semantic subtyping…

  * [ℂDuce](https://www.cduce.org/)
  * [Ballerina](https://ballerina.io)
  * [Elixir](https://elixir-lang.org/)
  * [eqWAlizer](https://github.com/WhatsApp/eqwalizer)

And if you want to see the production code, it’s in the C++ definitions of [tryUnifyNormalizedTypes](https://github.com/Roblox/luau/blob/d6aa35583e4be14304d2a17c7d11c8819756beb6/Analysis/src/Unifier.cpp#L868) and [NormalizedType](https://github.com/Roblox/luau/blob/d6aa35583e4be14304d2a17c7d11c8819756beb6/Analysis/include/Luau/Normalize.h#L134) in the [open source Luau repo](https://github.com/Roblox/luau).


# Recap: November 2021

November 29, 2021

## Type packs in type aliases

Type packs are the construct Luau uses to represent a sequence of types. We’ve had syntax for generic type packs for a while now, and it sees use in generic functions, but it hasn’t been available in type aliases. That has changed, and it is now syntactically legal to write the following type alias:

We’ve also added support for explicit type packs. Previously, it was impossible to instantiate a generic with two or more type pack parameters, because it wasn’t clear where the first pack ended and the second one began. We have introduced a new syntax for this use case:

For more information, check out [the documentation](https://luau-lang.org/typecheck#type-packs) or [the RFC](https://github.com/Roblox/luau/blob/f86d4c6995418e489a55be0100159009492778ff/rfcs/syntax-type-alias-type-packs.md) for this feature.

## Luau is open-source!

We announced this in early November but it deserves repeating: Luau is now an open-source project! You can use Luau outside of Roblox, subject to MIT License, and - importantly - we accept contributions.

Many changes contributed by community, both Roblox and external, have been merged since we’ve made Luau open source. Of note are two visible changes that shipped on Roblox platform:

  * The type error “Expected to return X values, but Y values are returned here” actually had X and Y swapped! This is now fixed.
  * Luau compiler dutifully computed the length of the string when using `#` operator on a string literal; this is now fixed and `#"foo"` compiles to 3.

You might think that C++ is a scary language and you can’t contribute to Luau. If so, you’d be happy to know that the contents of <https://luau-lang.org>, where we host our documentation, is also hosted on GitHub in the same repository (<https://github.com/Roblox/luau/tree/master/docs>) and that we’d love the community to contribute improvements to documentation among other changes! For example see [issues in this list](https://github.com/Roblox/luau/issues?q=is%3Aissue+is%3Aopen+label%3A%22pr+welcome%22) that start with “Documentation”, but all other changes and additions to documentation are also welcome.

## Library improvements

Given a number, returns the number of preceding left or trailing right-hand bits that are `0`.

See [the RFC for these functions](https://github.com/Roblox/luau/blob/f86d4c6995418e489a55be0100159009492778ff/rfcs/function-bit32-countlz-countrz.md) for more information.

## Type checking improvements

We have enabled a rewrite of how Luau handles `require` tracing. This has two main effects: firstly, in strict mode, `require` statements that Luau can’t resolve will trigger type errors; secondly, Luau now understands the `FindFirstAncestor` method in `require` expressions.

Luau now warns when the index to `table.move` is 0, as this is non-idiomatic and performs poorly. If this behavior is intentional, wrap the index in parentheses to suppress the warning.

Luau now provides additional context in table and class type mismatch errors.

## Performance improvements

We have enabled several changes that aim to avoid allocating a new closure object in cases where it’s not necessary to. This is helpful in cases where many closures are being allocated; in our benchmark suite, the two benchmarks that allocate a large number of closures improved by 15% and 5%, respectively.

When checking union types, we now try possibilities whose synthetic names match. This will speed up type checking unions in cases where synthetic names are populated.

We have also enabled an optimization that shares state in a hot path on the type checker. This will improve type checking performance.

The Luau VM now attempts to cache the length of tables’ array portion. This change showed a small performance improvement in benchmarks, and should speed up `#` expressions.

The Luau type checker now caches a specific category of table unification results. This can improve type checking performance significantly when the same set of types is used frequently.

When Luau is not retaining type graphs, the type checker now discards more of a module’s type surface after type checking it. This improves memory usage significantly.

## Bug fixes

We’ve fixed a bug where on ARM systems (mobile), packing negative numbers using unsigned formats in `string.pack` would produce the wrong result.

We’ve fixed an issue with type aliases that reuse generic type names that caused them to be instantiated incorrectly.

We’ve corrected a subtle bug that could cause free types to leak into a table type when a free table is bound to that table.

We’ve fixed an issue that could cause Luau to report an infinitely recursive type error when the type was not infinitely recursive.


# Recap: June 2022

July 7, 2022

# Lower bounds calculation

A common problem that Luau has is that it primarily works by inspecting expressions in your program and narrowing the _upper bounds_ of the values that can inhabit particular variables. In other words, each time we see a variable used, we eliminate possible sets of values from that variable’s domain.

There are some important cases where this doesn’t produce a helpful result. Take this function for instance:

Luau scans the function from top to bottom and first sees the line `return i`. It draws from this the inference that `find_first_if` must return the type of `i`, namely `number`.

This is fine, but things go sour when we see the line `return nil`. Since we are always narrowing, we take from this line the judgement that the return type of the function is `nil`. Since we have already concluded that the function must return `number`, Luau reports an error.

What we actually want to do in this case is to take these `return` statements as inferences about the _lower_ bound of the function’s return type. Instead of saying “this function must return values of type `nil`,” we should instead say “this function may _also_ return values of type `nil`.”

Lower bounds calculation does precisely this. Moving forward, Luau will instead infer the type `number?` for the above function.

This does have one unfortunate consequence: If a function has no return type annotation, we will no longer ever report a type error on a `return` statement. We think this is the right balance but we’ll be keeping an eye on things just to be sure.

Lower-bounds calculation is larger and a little bit riskier than other things we’ve been working on so we’ve set up a beta feature in Roblox Studio to enable them. It is called “Experimental Luau language features.”

Please try it out and let us know what you think!

## Known bug

We have a known bug with certain kinds of cyclic types when lower-bounds calculation is enabled. The following, for instance, is known to be problematic.

We hope to have this fixed soon.

# All table literals now result in unsealed tables

Previously, the only way to create a sealed table was by with a literal empty table. We have relaxed this somewhat: Any table created by a `{}` expression is considered to be unsealed within the scope where it was created:

# Other fixes

  * Adjust indentation and whitespace when creating multiline string representations of types, resulting in types that are easier to read.
  * Some small bugfixes to autocomplete
  * Fix a case where accessing a nonexistent property of a table would not result in an error being reported.
  * Improve parser recovery for the incorrect code `function foo() -> ReturnType` (the correct syntax is `function foo(): ReturnType`)
  * Improve the parse error offered for code that improperly uses the `function` keyword to start a type eg `type T = function`
  * Some small crash fixes and performance improvements

# Thanks!

A very special thanks to all of our open source contributors:

  * [Allan N Jeremy](https://github.com/AllanJeremy)
  * [Daniel Nachun](https://github.com/danielnachun)
  * [JohnnyMorganz](https://github.com/JohnnyMorganz/)
  * [Petri Häkkinen](https://github.com/petrihakkinen)
  * [Qualadore](https://github.com/Qualadore)


# Recap: May 2022

June 1, 2022

This month Luau team has worked to bring you a new language feature together with more typechecking improvements and bugfixes!

## Generalized iteration

We have extended the semantics of standard Lua syntax for iterating through containers, `for vars in values` with support for generalized iteration.
In Lua, to iterate over a table you need to use an iterator like `next` or a function that returns one like `pairs` or `ipairs`. In Luau, you can now simply iterate over a table:

This works for tables but can also be customized for tables or userdata by implementing `__iter` metamethod. It is called before the iteration begins, and should return an iterator function like `next` (or a custom one):

The default iteration order for tables is specified to be consecutive for elements `1..#t` and unordered after that, visiting every element.
Similar to iteration using `pairs`, modifying the table entries for keys other than the current one results in unspecified behavior.

## Typechecking improvements

We have added a missing check to compare implicit table keys against the key type of the table indexer:

Rules for `==` and `~=` have been relaxed for union types, if any of the union parts can be compared, operation succeeds:

Table value type propagation now correctly works with `[any]` key type:

If a generic function doesn’t provide type annotations for all arguments and the return value, additional generic type parameters might be added automatically:

We have also fixed various issues that have caused crashes, with many of them coming from your bug reports.

## Linter improvements

`GlobalUsedAsLocal` lint warning has been extended to notice when global variable writes always happen before their use in a local scope, suggesting that they can be replaced with a local variable:

## Performance improvements

Garbage collection CPU utilization has been tuned to further reduce frame time spikes of individual collection steps and to bring different GC stages to the same level of CPU utilization.

Returning a type-cast local (`return a :: type`) as well as returning multiple local variables (`return a, b, c`) is now a little bit more efficient.

### Function inlining and loop unrolling

In the open-source release of Luau, when optimization level 2 is enabled, the compiler will now perform 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.


# Recap: August 2020

August 11, 2020

Let’s talk about changes, big and small, that happened since June!

Many people work on these improvements, with the team slowly growing - thanks @Apakovtac, @EthicalRobot, @fun_enthusiast, @mrow_pizza and @zeuxcg!

## Type annotations are safe to use in production!

When we started the Luau type checking beta, we’ve had a big warning sign in the post saying to not publish the type-annotated scripts to your production games which some of you did anyway. This was because we didn’t want to commit to specific syntax for types, and were afraid that changing the syntax would break your games.

This restriction is lifted now. All scripts with type annotations that parse & execute will continue to parse & execute forever. Crucially, for this to be true you must not be using old fat arrow syntax for functions, which we warned you about for about a month now:

… and must not be using the `__meta` property which no longer holds special meaning and we now warn you about that:

Part of the syntax finalization also involved changing the precedence on some type annotations and adding support for parentheses; notably, you can now mix unions and intersections if you know what that means (`(A & B) | C` is valid type syntax). Some complex type annotations changed their structure because of this - previously `(number) -> string & (string) -> string` was a correct way to declare an intersection of two function types, but now to keep it parsing the same way you need to put each function type in parentheses: `((number) -> string) & ((string) -> string)`.

Type checking is not out of beta yet - we still have some work to do on the type checker itself. The items on our list before going out of beta right now include:

  * Better type checking for unary/binary operators
  * Improving error messages to make type errors more clear
  * Fixing a few remaining crashes for complex scripts
  * Fixing conflation of warnings/errors between different scripts with the same path in the tree
  * Improving type checking of globals in nonstrict mode (strict mode will continue to frown upon globals)

Of course this doesn’t mark the end of work on the feature - after type checking goes out of beta we plan to continue working on both syntax and semantics, but that list currently represents the work we believe we have left to do in the first phase - please let us know if there are other significant issues you are seeing with beta beyond future feature requests!

## Format string analysis

A few standard functions in Luau are using format strings to dictate the behavior of the code. There’s `string.format` for building strings, `string.gmatch` for pattern matching, `string.gsub`’s replacement string, `string.pack` binary format specification and `os.date` date formatting.

In all of these cases, it’s important to get the format strings right - typos in the format string can result in unpredictable behavior at runtime including errors. To help with that, we now have a new lint rule that parses the format strings and validates them according to the expected format.

Right now this support is limited to direct library calls (`string.format("%.2f", ...)` and literal strings used in these calls - we may lift some of these limitations later to include e.g. support for constant locals.

Additionally, if you have type checking beta enabled, string.format will now validate the argument types according to the format string to help you get your `%d`s and `%s`es right.

## Improvements to string. library

We’ve upgraded the Luau string library to follow Lua 5.3 implementation; specifically:

  * `string.pack/string.packsize/string.unpack` are available for your byte packing needs
  * `string.gmatch` and other pattern matching functions now support `%g` and `\0` in patterns

This change also [inadvertently] makes `string.gsub` validation rules for replacement string stricter - previously `%` followed by a non-digit character was silently accepted in a replacement string, but now it generates an error. This accidentally broke our own localization script [Purchase Prompt broken in some games (% character in title)](https://devforum.roblox.com/t/purchase-prompt-broken-in-some-games-character-in-title/686237)), but we got no other reports, and this in retrospect is a good change as it makes future extensions to string replacement safe… It was impossible for us to roll the change back and due to a long release window because of an internal company holiday we decided to keep the change as is, although we’ll try to be more careful in the future.

On a happier note, string.pack may seem daunting but is pretty easy to use to pack binary data to reduce your network traffic (note that binary strings aren’t safe to use in DataStores currently); I’ve posted an example in the release notes thread [Release Notes for 441](https://devforum.roblox.com/t/release-notes-for-441/686773) that allows you to pack a simple character state in 16 bytes like this:

And unpack it like this after network transmission:

## Assorted fixes

As usual we fixed a few small problems discovered through testing. We now have an automated process that generates random Luau code in semi-intelligent ways to try to break different parts of our system, and a few fixes this time are a direct result of that.

  * Fix line debug information for multi-line function calls to make sure errors for code like `foo.Bar(...)` are generated in the appropriate location when foo is nil
  * Fix debug information for constant upvalues; this fixes some bugs with watching local variables from the nested functions during debugging
  * Fix an off-by-one range check in string.find for init argument that could result in reading uninitialized memory
  * Fix type confusion for table.move target table argument that could result in reading or writing arbitrary memory
  * Fix type confusion for `debug.getinfo` in some circumstances (we don’t currently expose getinfo but have plans to do so in the future)
  * Improve out of memory behavior for large string allocations in string.rep and some other functions like `table.concat` to handle these conditions more gracefully
  * Fix a regression with `os.time` from last update, where it erroneously reverted to Lua 5.x behavior of treating the time as a local time. Luau version (intentionally) deviates from this by treating the input table as UTC, which matches `os.time()` behavior with no arguments.

## Performance improvements

Only two changes in this category here this time around; some larger scale performance / memory improvements are still pending implementation.

  * Constant locals are now completely eliminated in cases when debugging is not available (so on server/client), making some scripts ~1-2% faster
  * Make script compilation ~5% faster by tuning the compiler analysis and code generation more carefully Oh, also `math.round` is now a thing which didn’t fit into any category above.


# Recap: February 2022

February 28, 2022

## Default type alias type parameters

We have introduced a syntax to provide default type arguments inside the type alias type parameter list.

It is now possible to have type functions where the instantiation can omit some type arguments.

You can provide concrete types:

Or reference parameters defined earlier in the list:

Type pack parameters can also have a default type pack:

If all type parameters have a default type, it is now possible to reference that without providing any type arguments:

For more details, you can read the original [RFC proposal](https://github.com/Roblox/luau/blob/master/rfcs/syntax-default-type-alias-type-parameters.md).

## Typechecking improvements

This month we had many fixes to improve our type inference and reduce false positive errors.

if-then-else expression can now have different types in each branch:

And if the expected result type is known, you will not get an error in cases like these:

`assert` result is now known to not be ‘falsy’ (`false` or `nil`):

We fixed cases where length operator `#` reported an error when used on a compatible type:

Functions with different variadic argument/return types are no longer compatible:

We have also fixed:

  * false positive errors caused by incorrect reuse of generic types across different function declarations
  * issues with forward-declared intersection types
  * wrong return type annotation for table.move
  * various crashes reported by developers

## Linter improvements

A new static analysis warning was introduced to mark incorrect use of a ‘`a and b or c`’ pattern. When ‘b’ is ‘falsy’ (`false` or `nil`), result will always be ‘c’, even if the expression ‘a’ was true:

Like we say in the warning, new if-then-else expression doesn’t have this pitfall:

We have also introduced a check for misspelled comment directives:

## Performance improvements

For performance, we have changed how our Garbage Collector collects unreachable memory.
This rework makes it possible to free memory 2.5x faster and also comes with a small change to how we store Luau objects in memory. For example, each table now uses 16 fewer bytes on 64-bit platforms.

Another optimization was made for `select(_, ...)` call.
It is now using a special fast path that has constant-time complexity in number of arguments (~3x faster with 10 arguments).

## Thanks

A special thanks to all the fine folks who contributed PRs this month!

  * [mikejsavage](https://github.com/mikejsavage)
  * [TheGreatSageEqualToHeaven](https://github.com/TheGreatSageEqualToHeaven)
  * [petrihakkinen](https://github.com/petrihakkinen)


# Luau Goes Open-Source

November 3, 2021

When Roblox was created 15 years ago, we chose Lua as the scripting language. Lua was small, fast, easy to embed and learn and opened up enormous possibilities for our developers.

A lot in Roblox was built on Lua including hundreds of thousands of lines of internally-developed code that powers Roblox App and Roblox Studio to this day, and the millions of experiences that developers have created. For many of them, it was the first programming language they’ve learned.

A few years ago, we started looking into how we can evolve Lua to be even faster, have better ergonomics, make it easier to write robust code and to unlock an ecosystem of rich tooling—from better static analysis to IDE integrations.

This is how Luau was born.

Luau is a new language that started from Lua 5.1 and kept evolving while keeping backwards compatibility and preserving the original design goals: simplicity, performance, embeddability.

We’re incredibly grateful for the foundation that Lua has been—it’s been a joy to build on top of! So now we want to give back to the community at large.

Starting today, [Luau](https://luau-lang.org) is no longer an inseparable part of Roblox platform; it’s a separate, open-source language.

Luau is available at <https://github.com/luau-lang/luau> and comes with the source code for the language runtime and all associated tooling: compiler, type checker, linter. The code is available to anyone, free of charge, under the terms of MIT License. We’re happy to accept contributions to the language, whether that’s documentation or source code.

The language evolution is driven by an RFC process that is also open to the public.

We are committed to improving Luau going forward—it remains a central piece of technology at Roblox. The team that works on the language keeps growing, and we have lots of ideas! The language will become even faster, even nicer to work with, even more powerful.

We can’t wait to see what we can build, together.