Luau Recap: November 2022

As always, Luau is our new language that you can read more about at https://luau-lang.org.

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:

type Ok<T> = { tag: "ok", value: T }
type Err = { tag: "error", msg: string }
type Result<T> = Ok<T> | Err

function unwrap<T>(r: Result<T>): T?
    if r.tag == "ok" then
        return r.value
    else
        -- Luau now understands that 'r' here can only be the 'Err' part
        print(r.msg)
        return nil
    end
end

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:

local function f1(a: number?)
    -- 'x' is still a 'number?' and doesn't become 'boolean | number'
    local x = a and 5
end

Error message improvements

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

type string = number -- Now an error: Redefinition of type 'string'

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:

type Foo<T... = > = nil -- Now an error: Expected type, got '>'

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

function myfunction(a: number, b:number) end
myfunction(123)
           ~~~

We now highlight this:

function myfunction(a: number, b:number) end
myfunction(123)
~~~~~~~~~~

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

local function f(t: {number}?)
    for i,v in t do -- Value of type {number}? could be nil
        --...
    end
end

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:

local t = {
    a = 1
    b = 2 -- Expected ',' after table constructor element
    c = 3 -- Expected ',' after table constructor element
}
72 Likes

This topic was automatically opened after 10 minutes.

Nice improvements!

I encountered this errormessage a while ago and was pretty confused.

It still would be nice, if Luau would understand that for example

local function X(Something : string | number )
        if typeof(Something) == "number" then return end
        print(string.len(Something))
end

Something can’t be a ‘number’ in this Situation.
I tend to write many statements like this and I don’t
like the Type Errors

8 Likes

That’s cool but can you also include a english version of your Luau recap? I didn’t quite understand the first part. :slightly_smiling_face:

3 Likes

They’re planning to support this in the future, but for now what you can do is add an assert after the guard clause.

E.g:

local function x( thing: string | number )
      if ( type( thing ) == "number" ) then return end
      assert( type( thing ) == "number" ) -- Errors the code if it's a number...
                   -- but it can never be a number because it returns beforehand.

      print( #thing ) -- No more warning!
end
2 Likes

I’m happy with this part, it makes stuff a whole lot less confusing.

1 Like

Wouldn’t Type 'string' could not be converted into table-type 'string' a better and more thruthy error since thats what the string library’s type is

Better - probably.
Truer? Don’t see how, typeof(string) returns precisely the type of the string library and it’s the way you can reference it inside your code if you want to.

1 Like

Would be really excited for the following changes:

Support this scenario:

function test(v: string?): boolean
  if not v then
    return false
  end
  return v:find("hey") ~= nil
end

Currently, even though the function has returned it will still show it as an error. A current workaround is to just assert it after that if-return statement.

Secondly, it would be great to support typed arrays such as: {3, “Hey”}
I think a simple definition could be like:

type my_array = { number, string }

More could be expanded in this direction, but the current sad workaround is to cast it to ::{any}

Better support for object oriented design around __index would also be helpful, but I understand this is tricky because there are so many ways to achieve the same result (including multiple constructors, etc).

Also this scenario which is found on the roact repository in GitHub:

local function strict(t: { [any]: any }, name: string?)
	-- FIXME Luau: Need to define a new variable since reassigning `name = ...`
	-- doesn't narrow the type
	local newName = name or tostring(t)
....

Additionally, ability to create documentation hovers for functions, parameters, variables, etc…

2 Likes