As everyone knows by now, Luau is our new language stack that you can read more about at https://roblox.github.io/luau and the month following June is August so let’s talk about changes, big and small, that happened since June!
If you have missed previous large announcements, here they are:
- Faster Lua VM Released
- Luau Recap: November 2019
- Luau Type Checking Beta!
- Luau Recap: February 2020
- Luau Recap: March 2020
- Luau Recap: May 2020
- Luau Recap: June 2020
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!
The work on parallel Luau has begun
We’ve kept you in the dark for a while about this; this is because we were working on a full specification for how multithreading will work in Luau. This work was finished in June and we’ve started working on an implementation.
Our plans here are grand and expansive, and this work will ship in phases, with each successive phase allowing more and more engine functionality to be accessible from parallel context. The details will be shared when we’re closer to a beta release, which should happen by the end of the year.
Improved type checking for Roblox builtin types
Previously we used to model Roblox userdata types as tables with a shape dictated by the available members. This worked fine most of the time but resulted in a few odd cases where the type checker thought some types were compatible which wasn’t true in runtime; for example:
- Folder was equivalent to Instance because Folder class didn’t introduce new members
- You could pass a CFrame to a function that is declared to accept a Vector3 because CFrame had X/Y/Z members
To fix that we introduced special support for Roblox builtin types called “nominal types”, where some types are defined by their name, not by their shape, and have a subtyping relationship with other types (e.g. Folder is-a Instance). Right now this is limited to Roblox userdata types.
We also fixed some cases in Roblox API where an Instance type accidentally used
any during type checking.
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
Improvements to string. library
We’ve upgraded the Luau string library to follow Lua 5.3 implementation; specifically:
string.unpackare available for your byte packing needs
string.gmatchand other pattern matching functions now support
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)), 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) that allows you to pack a simple character state in 16 bytes like this:
local characterStateFormat = "fffbbbB" local characterState = string.pack(characterStateFormat, posx, posy, posz, dirx * 127, diry * 127, dirz * 127, health)
And unpack it like this after network transmission:
local posx, posy, posz, dirx, diry, dirz, health = string.unpack(characterStateFormat, characterState) dirx /= 127 diry /= 127 dirz /= 127
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
- 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
initargument that could result in reading uninitialized memory
- Fix type confusion for
table.movetarget table argument that could result in reading or writing arbitrary memory
- Fix type confusion for
debug.getinfoin some circumstances (we don’t currently expose
getinfobut have plans to do so in the future)
- Improve out of memory behavior for large string allocations in
string.repand some other functions like
table.concatto handle these conditions more gracefully
- Fix a regression with
os.timefrom 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.
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
math.round is now a thing which didn’t fit into any category above.