Luau Recap: July 2021

Luau is our new language that you can read more about at https://luau-lang.org. Our team was still busy working on the upcoming Studio Beta feature for the script editor, but we did fit in multiple typechecking improvements.

Typechecking improvements

A common complaint that we’ve received was a false-positive error when table with an optional or union element type is defined:

--!strict
type Foo = {x: number | string}
local foos: {Foo} = {
    {x = 1234567},
    {x = "hello"} -- Type 'string' could not be converted into 'number'
}

This case is now handled and skipping optional fields is allowed as well:

--!strict
type Foo = {
    a: number,
    b: number?
}
local foos: {Foo} = {
    { a = 1 },
    { a = 2, b = 3 } -- now ok
}

Current fix only handles table element type in assignments, but we plan to extend that to function call arguments and individual table fields.

Like we’ve mentioned last time, we will continue working on our new type constraint resolver and this month it learned to handle more complex expressions (including type guards) inside assert conditions:

--!strict
local part = script.Parent:WaitForChild("Part")
assert(part:IsA("BasePart"))
local basepart: BasePart = part -- no longer an error

And speaking of assertions, we applied a minor fix so that the type of the assert function correctly defines a second optional string? parameter.

We have also fixed the type of string.gmatch function reported by one of the community members. We know about issues in a few additional library functions and we’ll work to fix them as well.

Hopefully, you didn’t see ‘free type leak’ errors that underline your whole script, but some of you did and reported them to us. We read those reports and two additional cases have been fixed this month. We now track only a single one that should be fixed next month.

Another false positive error that was fixed involves tables with __call metatable function. We no longer report a type error when this method is invoked and we’ll also make sure that given arguments match the function definition:

--!strict
local t = { x = 2 }

local x = setmetatable(t, {
    __call = function(self, a: number)
        return a * self.x
    end
})
local a = x(2) -- no longer an error

Please note that while call operator on a table is now handled, function types in Luau are distinct from table types and you’ll still get an error if you try to assign this table to a variable of a function type.

Linter improvements

A new ‘TableOperations’ lint check was added that will detect common correctness or performance issues with table.insert and table.remove :

-- table.insert will insert the value before the last element, which is likely a bug; consider removing the second argument or wrap it in parentheses to silence
table.insert(t, #t, 42)

-- table.insert will append the value to the table; consider removing the second argument for efficiency
table.insert(t, #t + 1, 42)

-- table.insert uses index 0 but arrays are 1-based; did you mean 1 instead?
table.insert(t, 0, 42)

-- table.remove uses index 0 but arrays are 1-based; did you mean 1 instead?
table.remove(t, 0)

-- table.remove will remove the value before the last element, which is likely a bug; consider removing the second argument or wrap it in parentheses to silence
table.remove(t, #t - 1)

-- table.insert may change behavior if the call returns more than one result; consider adding parentheses around second argument
table.insert(t, string.find("hello", "h"))

Another new check is ‘DuplicateConditions’. The name speaks for itself, if statement chains with duplicate conditions and expressions containing and / or operations with redundant parts will now be detected:

if x then
    -- ...
elseif not x then
    -- ...
elseif x̳ then -- Condition has already been checked on line 1
    -- ...
end
local success = a and a̳ -- Condition has already been checked on column 17
local good = (a or b) or a̳ -- Condition has already been checked on column 15

We’ve also fixed an incorrect lint warning when typeof is used to check for EnumItem .

Editor features

An issue was fixed that prevented the debugger from displaying values inside Roblox callback functions when an error was reported inside of it.

Behavior changes

table.insert will no longer move elements forward 1 spot when index is negative or 0.

This change also fixed a performance issue when table.insert was called with a large negative index.

The ‘TableOperations’ lint mentioned earlier will flag cases where insertion at index 0 is performed.

123 Likes

This topic was automatically opened after 7 minutes.

Nice! Can’t wait to see Roblox scripting upgrade. I was actually confused at people saying Luau but now I get it. Honestly, excellent feature!

9 Likes

I’m sure you guys love hearing about issues with the type checker so here I am yet again. :slightly_smiling_face:

Intersections and type assertions do not play well with each other whatsoever. Trying to coerce something into a type that happens to be an intersection just straight up fails even though the coercion is completely valid. Here’s an example of that:

type XVector = {x: number}
type YVector = {y: number}

type Vector2 = XVector & YVector

local a: Vector2 = { --perfectly fine
    x = 0,
    y = 0
}

local b = {  --Error: Type 'XVector & YVector' could not be converted into {x: any, y:any} 
    x = 0,
    y = 0
} :: Vector2

Note that the TypeScript equivalent to the code above produces no errors and b is correctly typed as Vector2

On a side note, its great to see table types getting some love this time around. Arrays with union types erroring out for no reason has been a long standing issue and I’m glad to see the effort being put in. I’m also curious about an ETA for the re-release of generic functions? Ever since they got reverted I haven’t seen much about them and they would be extremely useful.

13 Likes

This sounds wrong, should it be?

Our team was still busy working on the upcoming Studio Beta feature for the script editor

7 Likes

I think this warning should also be applied to tonumber, a few times I’ve accidentally passed both results of string.gsub to tonumber.
Example:

local Num = tonumber(string.gsub(Str,"%s",""))
-- supposed to remove all spaces and convert that to a number
-- but it also passes the amount of replacements as the base argument
7 Likes

These updates to the type system get me worried cause I always expect there to be hundreds of false positive warnings in my large codebase which aren’t my fault. Of course, if it is my fault I’m always open to warnings appearing.

But, it looks like I have zero warnings this time in 59K lines of --!strict mode code!

I’m glad Luau is finally getting close to its final, more sound form as a language! Avoiding false positives for otherwise sound code is a very important issue, especially for new developers who are just picking up Lua as a language. I’m glad there’s a push to both refine the type safety of code and avoid these false positives.

5 Likes

Our team consists of programming language experts but they are not necessarily English experts :slight_smile:

Yeah we owe y’all an update on that. When we implemented generic functions initially, we discovered subtle soundness problems in the existing generic support in the core of the type checker. We decided to “fix” it by restricting the generic support to a certain subset of what was already there but unfortunately that broke existing user scripts (by “broke” I mean “correct code that type checked previously started generating type errors and there was no good way to fix these”). So this required a somewhat deep revision of how generic types are handled by the type checker core, but right as we learned that we were already deeply engaged in working on autocomplete support. Now that that’s done we’re going to come back to generic functions - realistically I’d expect them to ship by the September recap (so some time early-mid September).

28 Likes

That’s nice to hear. On the topic of the unreleased upcoming Luau-powered Autocomplete beta and generic functions, I was playing around with the autocomplete beta in it’s current state.

I found a few small issues like incorrect type declarations on some of the globals and a bit “unreliable” behaviour of require call autocompletes (Though as far as I know, latter mentioned problem is already being worked on so I think can cross that problem off the list.), but other than that, it was doing a very good job for the most part.

While playing around with it I couldn’t help but notice some of the global functions (Like the rawget, rawset, rawequal functions for example.) are declared as generic functions (At least autocomplete shows them like they are unless I’m interpreting the autocomplete result very wrongly.):
image

This finding raised 2 question in my head that I was waiting a good opportunity to ask about it to you guys:

1. Are you guys actually planning to release some of the global functions as generic functions when your guys re-release the generic functions?

And:

2.If the answer to the first question is yes, are you guys planning to expand the generic function trend to instance methods like :FindFirstChild, for example?

7 Likes

Yeah I really loved the condition already being checked update. This is helpful in reducing lag as well as unnecessary code.

The table operations update confused me a little bit at first but adding parenthesis did indeed remove the annoying little tick under the table addition.

3 Likes

I reported a bug in February in the Luau linter, where if you index script.NameGoesHere, it will show that “Key ‘NameGoesHere’ not found in class ‘Script’” if there is no such Instance when the line of code is written, but even after inserting something named “NameGoesHere” it just won’t go away until restarting Studio.

The new linter improvements are great, but please fix this or tell us that there’s something that prevents an easy fix. It’s very distracting, and the only reason I don’t have the “Scripts Are Non-Strict by Default” beta on.

5 Likes

Little tip—you can actually drag the script up in the explorer (reparenting it to something else), and then drag it back to where it was to refresh the linter and get rid of these warnings. I usually do this whenever I’m referencing children of scripts which I add as I write the code, and I have strict mode on all of my code right now.

It is annoying how some warnings can cache like this though.

4 Likes

what is confusing about people saying luau?

1 Like

It was because I thought the Roblox language was Lua.

2 Likes

task.wait seems to be implemented for testing, wondering if there’s any plans to make it better for mass use, even though polling is bad of course, I guess there’s people that need heavy usage, and even task.wait from my understanding shouldn’t be fixing the entire wait lagging with multiple threads being resumed and yielded at once,

which this library helps with:

1 Like

Can someone show me a code example of task.wait?

1 Like

This looks great! I’ll keep waiting for luau to have destructuring dictionaries syntax like that of Javascript :joy:

Something like:

local names = {
   ["harry"] = {
      location = "usa",
      age = 17
   },
   ["bob"] = {
      location = "canada",
      age = 14
   }
}
local { harry, { age } } = names -- data of harry and age of bob destructured.   
1 Like

had parallel Lua been released? I realized that the task library is implemented, and I haven’t had the beta enabled for weeks

2 Likes

It’s basically a more accurate version of wait() .

task.wait(2)
See reply below.

The biggest difference isn’t so much the precision, it’s the fact that it doesn’t throttle: task.wait(delay) will unsuspend after the delay regardless of what the consequences may be, compared to wait(delay) which “helpfully” throttled execution for you, sometimes delaying resumption by extra frames to keep the frame rate smooth (something which frequently frustrated devs because they didn’t have much control over the throttling).

5 Likes