Luau Recap: July 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.

Native Code Generation

As a reminder, Luau native code generation is now available by default on the server and in Studio for all experiences.
If you have missed it, you can find the last update in the announcement: Luau Native Code Generation Preview Update

Native function attribute

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

@native -- function compiles natively
local function foo()
    ...
end

We have also prepared a video for you, going over the native code generation feature and best approaches on using it, including the new attribute:

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.

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:

local function MulAddScaled(a, b, c)
    return a * b * 0.75 + c * 0.25
end

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 Vector3 type?

local intlPos = MulAddScaled(Part.Position, v, Vector3.new(12, 0, 0))

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.

local function MulAddScaled(a: Vector3, b: Vector3, c: Vector3)
    return a * b * 0.75 + c * 0.25
end

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

type Vertex = { p: Vector3, uv: Vector3, n: Vector3, t: Vector3, b: Vector3, h: number }
type Mesh = { vertices: {Vertex}, indices: {number} }

function calculate_normals(mesh: Mesh)
    for i = 1,#mesh.indices,3 do
        local a = mesh.vertices[mesh.indices[i]]
        local b = mesh.vertices[mesh.indices[i + 1]]
        local c = mesh.vertices[mesh.indices[i + 2]]
       
        local vba = a.p - b.p -- Inferred as a Vector3 operation
        local vca = a.p - c.p

        local n = vba:Cross(vca) -- Knows that Cross returns Vector3
       
        a.n += n -- Inferred as a Vector3 operation
        b.n += n
        c.n += n
    end
end

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 that native compilation now supports properties/methods of the Vector3 type like Magnitude, Unit, Dot, Cross, Floor and Ceil allowing generation of CPU code that is multiple times faster than a generic Roblox API call.

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

We are working to extend type inference and faster inline operations for additional Vector3 methods and even operations on Vector2/CFrame in the future.

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, utf8.graphemes, DataStore APIs and other Roblox engine functions. Custom per-character validation logic is no longer required.

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:

local a = 10000000000000001 -- Number literal exceeded available precision and was truncated to closest representable number
local b = 10000000000000001.0 -- Ok, but rounds to 10000000000000000

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:

type Options =
    | { tag: "cat", laziness: number }
    | { tag: "dog", happiness: number }

You can find more information and examples in the proposal

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.
  • Vector3 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.

Buffer library and type

As a reminder, buffer data type announced as a beta here has been out of beta since December with additional use cases added in February. We’ve seen some feedback that people were not aware of the availability, so we use this opportunity as a reminder!

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 for their generous contributions including pushing for broader Luau support.

95 Likes

This topic was automatically opened after 11 minutes.

Not to be that guy, but *utf8.graphemes

23 Likes

image
Not to be that guy, but *Snapshot

19 Likes

Looking forward to the improvements to the typechecking engine, as there are currently many many many issues with it



The typechecking engine doesn’t allow this kind of structure, where I have 1 function make a table, and a second function adding stuff to it. The type inference engine seems to lock the type of that table. The solution I have found is to set my script to !nonstrict, have a variable for each function, and combine their types into a new table variable
local Table = Table2 :: typeof(Table1)&typeof(Table2)


image
The typechecking engine currently doesn’t allow for overwriting the type of a variable. In this case, the function may or may not receive ModelPath, but after the first line in the function, ModelPath is guaranteed to be a string, however, its type doesn’t change. Using :: string also doesn’t work
The solution is to put local in front of ModelPath, creating a new variable (with the same name) for the type to apply correctly


image
It is also very annoying when dealing with numbers. In this example, the function is guaranteed to return 2 models, however, the type doesn’t reflect that. Just like with strings, where we are able to use specific strings (in tables and for variables with “Name1”|“Name2”), it would be very useful to use specific numbers (as shown with the comment in the example, or also with 1|2)
Even more useful would be being able to assign a number range type, and maybe an integer type or unsigned integer, …
image


Moreover, as a bonus, it would be very useful to have an easy way to verify that the types being passed to a function are valid, and have it throw an error if not. This would simplify sanity checks for RemoteEvents or RemoteFunctions, and libraries as well
Could be in the form of a function attribute, or another structure that would allow for custom handling of the “error” (although pcalls could be used)
image


I would love to make a #feature-requests, but I cannot

I’m honestly more interested in typechecking for its autocomplete capabilities than anything else. Performance is a nice to have, but usually not a concern, and debugging the kind of bugs typechecking helps you avoid is also usually very easy (they tend to be the dumb mistakes that take 1 minute to fix)

7 Likes

Leading | and & in types

Are absolutely cool :sunglasses:

1 Like

I completly don’t understand what is this about.

1 Like

It’s about Luau, the scripting language Roblox games use.

4 Likes

I’m a big fan of this change as it feels more natural putting | or & in front of all the values instead of skipping the first one.

One trick I do with types like this is to wrap them with brackets so it feels like a table, and you can also fold it like one too in-script:

type Options = (
    | { tag: "cat", laziness: number }
    | { tag: "dog", happiness: number }
)

-- Folded --
type Options = (
)
3 Likes

Did you guys ever explain if native will ever work on the client? Hard to find answers for specific questions on the forum. 99% of my math heavy code is on the client in all sorts of ambience processing, position checks, and more. My bottleneck is client CPU.

9 Likes

sure but what was added or changed?

2 Likes

@WheretIB

I wish there would be a Script Profiler to profile Studio Plugins :thinking:

depending on the frequency I set for the Script Profiler, I also seem to get different results, so I am not really suuuuure

1 Like

:nerd_face::point_up_2: moment tbh… just let him cook already…

3 Likes

There are issues they are aware of, that’s why they are rewriting it. However most of the issues you point out look like problems with your code, not Luau.

1 Like

It’s a recap on features that have been added/changed/improved over the course of the previous few months, as well as new features that are in the oven and will release eventually:

(yay)


Most of it won’t make sense unless you’re following the Luau update stream and/or are already familiar with the discussed concepts (i.e floating points, buffers, inlining)

2 Likes

Lua type checking was always one of its biggest flaws…(Btw what’s your theme I love it)

4 Likes

Quick question on annotations: I’ve noticed there’s an annotation called @checked, but I’m not sure what it does. Is this intentional or just incomplete?

2 Likes

I’m so excited for this!

One question I have though, whenever I create 2 custom table types, and create a function that may get an argument with a type that may be one of these types (like function a(arg: firstType | secondType)), and create a loop with that argument, the type checker displays a warning. Will this be fixed in the new typechecking engine?

2 Likes

i’m having issues with leading bar and autoindent not working with eachother, any type with a leading bar separated by a newline goes from this:

image

…to this after hitting format document

image

i also recall it breaking in a way that makes it cause a same line statement warning but i can’t reproduce it right now, i’ll figure it out later and submit a bug report since i don’t have too much time right now

2 Likes

Also curious about this - my biggest use cases (bulk calculations in parallel - aerodynamics, waves, projectile simulation) for native code would be practically 100% clientside. Clientside native code seems to be available in Studio playtest, which gives me hope that it’ll make it to the regular client eventually, but it’d be nice to get some clarification on that.

1 Like