Luau Recap: February 2021

Luau is our new language that you can read more about at Luau - Luau. It’s been a busy few months in Luau!

Infallible parser

Traditional compilers have focused on tasks that can be performed on complete programs, such as type-checking, static analysis and code generation. This is all good, but most programs under development are incomplete! They may have holes, statements that will be filled in later, and lines that are in the middle of being edited. If we’d like to provide support for developers while they are writing code, we need to provide tools for incomplete programs as well as complete ones.

The first step in this is an infallible parser, that always returns an Abstract Syntax Tree, no matter what input it is given. If the program is syntactically incorrect, there will also be some syntax errors, but the parser keeps going and tries to recover from those errors, rather than just giving up.

The Luau parser now recovers from errors, which means, for example, we can give hints about programs in an IDE.

Type assertions

The Luau type checker can’t know everything about your code, and sometimes it will produce type errors even when you know the code is correct. For example, sometimes the type checker can’t work out the intended types, and gives a message such as “Unknown type used… consider adding a type annotation”.

Previously the only way to add an annotation was to put it on the declaration of the variable, but now you can put it on the use too. A use of variable x at type T can be written x :: T . For example the type any can be used almost anywhere, so a common usage of type assertions is to switch off the type system by writing x :: any .

Typechecking improvements

We’ve made various improvements to the Luau typechecker:

  • We allow duplicate function definitions in non-strict mode.
  • Better typechecking of and , (f or g)() , arrays with properties, and string:format() .
  • Improved typechecking of infinite loops.
  • Better error reporting for function type mismatch, type aliases and cyclic types.

Performance improvements

We are continuing to work on optimizing our VM and libraries to make sure idiomatic code keeps improving in performance. Most of these changes are motivated by our benchmark suite; while some improvements may seem small and insignificant, over time these compound and allow us to reach excellent performance.

  • Table key assignments as well as global assignments have been optimized to play nicer with modern CPUs, yielding ~2% speedup in some benchmarks
  • Luau function calls are now ~3% faster in most cases; we also have more call optimizations coming up next month!
  • Modulo operation (%) is now a bit faster on Windows, resulting in ~2% performance improvement on some benchmarks

Debugger improvements

Our Luau VM implementation is focused on performance and provides a different API for implementation of debugger tools. But it does have its caveats and one of them was inability to debug coroutines (breakpoints/stepping).

The good news is that we have lifted that limitation and coroutines can now be debugged just like any regular function. This can especially help people who use Promise libraries that rely on coroutines internally.

Debugging a coroutine

Library changes

table library now has a new method, clear , that removes all keys from the table but keeps the internal table capacity. When working with large arrays, this can be more efficient than assigning a table to {} - the performance gains are similar to that of using table.create instead of {} when you expect the number of elements to stay more or less the same . Note that large empty tables still take memory and are a bit slower for garbage collector to process, so use this with caution.

In addition to that we found a small bug in string.char implementation that allowed creating strings from out-of-range character codes (e.g. string.char(2000) ); the problem has been fixed and these calls now correctly generate an error.

Coming soon…

  • Generic function types will soon be allowed!
function id<a>(x: a): a
  return x
end
  • Typed variadics will soon allow types to be given to functions with varying numbers of arguments!
function sum(...: number): number
  local result = 0
  for i,v in ipairs({...}) do
      result += v
  end
  return result
end

And there will be more!

201 Likes

This topic was automatically opened after 17 minutes.

intensive heavy breathing. you wont believe how much of a godsend this is because i’m a power user who has to use coroutines everywhere. hehe…

I obviously still dont use strict types as well, not my thing lol, but its good to see strictly typed lua is coming along well.

Amazing to see this was also fixed, was actually starting to tick me off

14 Likes

These are awesome changes to see to types. I would love to see the Platform Roadmap updated with Luau types being included. While the :: (or “as”) operator is needed, it would’ve been nice to see the plans to add these operators. Sitting in the dark with Luau isn’t really that fun due to how young it is, but I would love to see the team more involved in letting us know what is currently being worked on!

As a Roblox-TypeScript user, I am excited to see typed Luau updates and variadic types will be very welcomed :slight_smile:

As a request, I would love to see syntax highlighting supported for types. They’re very important to identify and currently they match right in with other normal words because they have no special colors. This is a common feature in other type-safe langs so I would really enjoy having the same thing here.

15 Likes

will this cause an error during run time when the wrong type is pass? I’m assuming no but it would be nice to have something like that built in instead of having to rely on assert(typeof()) or using t library.

5 Likes

Hey! Quick question: are there any plans on adding compound operators for the logical AND/OR operators? I believe it would be really useful to have!

10 Likes

I’m curious about these additions:
How will a generic function be called?
If a generic function is called in the form f<types>(args) then what would happen with this?

local function return_false(_unused_v1:any,_unused_v2:any):boolean
	return false
end
local function return_one<T>(_unused_self:any,_unused_value:any):T
	return 1
end
local t = setmetatable({},{
	__lt = return_false,
	__call = return_one
})
local number:number = 2
print(t<number>(t))

Currently, after removing the types it will call __lt twice on t. This also looks like it could be a call to __call, since it passes in one type argument (number) and one value argument (t) which matches up with __call. How will this ambiguity be resolved?

With variadic types, how would I specify the return type of this?

local function all(...:any) -- :typeof(...) ???
	return ...
end

Can there be multiple types for a variadic function?
For instance, this function

local function NumberStringPairs(...)
	local Pairs = {}
	if select("#",...)%2 ~= 0 then
		error"Expected an even number of arguments"
	end
	for i=1,select("#",...),2 do
		local Number,String = select(i,...)
		if type(Number) ~= "number" or type(String) ~= "string" then
			error"Expected number and string pairs"
		end
		table.insert(Pairs,{Number,String})
	end
	return Pairs
end

receives pairs of numbers and strings. This can’t be expressed with a single type. string|number would catch most errors, but not something like NumberStringPairs("1","2") or NumberStringPairs(1,"2",3).

8 Likes

I think the recent optimizations to lua u are much needed, and despite not wanting to script to rely on the new optimizations it allows for creators to push the envelope even more and I appreciate that

5 Likes

Type assertions are very cool, thank you. :slight_smile:

Are there any plans for something like !? Type assertions can replace that but they’re a bit verbose comparatively.

8 Likes

One problem I’ve been encountering is that arrays are only allowed to have one type. e.g.

--!strict
type A = number | string
type T = {A}

local a: T = {1, "A"} --> W000: (5,17) Type 'string' could not be converted into 'number'
local b: T = {"A", 1} --> W000: (6,19) Type 'number' could not be converted into 'string'

print(a, b)

It seems that the type of the first element of an array forces the type of the remaining elements.

For comparison, typescript handles this just fine:

type A = number | string
type T = A[]

let a: T = [1, "A"]
let b: T = ["A", 2]

console.log(a, b)
11 Likes

Using the usual call syntax. We currently don’t have plans to allow an easy syntax to specify the argument types because they should almost always be inferrable from the arguments (if that ever fails us we might consider turbofish)

8 Likes

I think this attachment will help to make better games’s although I’m not scripting I think this will help.

4 Likes

I really love the initiative behind typed luau, and I can’t wait to get more features (I’m already using type assertions in certain scenarios and loving it).

However, there’s one big issue with Team Create and strict/nonstrict mode that forces me to completely disable strict/nonstrict mode:

I have to keep all of my code, typed or untyped, on --!nocheck mode until this is fixed. It sucks, but these unknown require warnings slow down my workflow so much that there’s no way around it. I’m hoping, eventually, I’ll be able to mass find and replace --!nocheck with --!strict, but this issue has affected me since the first release of the type syntax.

@zeuxcg

5 Likes

not gonna lie the type checking screwed with my mind a bit (lol)

but i am most definitely looking forward to the generic function types, future is looking bright!

4 Likes

Now I do question.
Once type checking and such is out as in, officially released, no longer in beta.
If enabled, must every script in game use type checking or can it be enabled for individual scripts?
Those are questions I still have about it.

If type checking improves performance, I use it to speed up the game, mostly in scripts that are tied to renderstep or where fast calculations is a must.
But for some scripts where performance/speed is not that important, type checking gets a little tedious to apply to every variable and function so then I choose to not use it.

4 Likes

we’ll be able to create types like in typescript ?

5 Likes

That’s what the --!strict you see at the top of the examples is, you can specify one of several different options after the ! for whether / how strictly to typecheck that specific script.

8 Likes

This is already possible:

type vector1 = {
    x: number
}

local v1: vector1 = { x = 10 } -- ok
local v1_bad: vector1 = { x = 10, y = 5 } -- will lint that this is bad
7 Likes

There is this very annoying bug where classes have a blue underline when they have been added to a part and the script that references it exists before it is added to whatever parent it is in. Another annoying bug is that studio seems to get laggier the longer you use it but if you restart your pc it’s all fine.

5 Likes

Thank you, that’s something I already was aware of.

But I meant, will this still be the case once type checking and all is fully released out of beta, stable, optimized and ready as a complete feature?

Will we still be able to enable and disable for specific scripts or must it then be enabled globally in order to benefit from optimizations and such?

I specifically use type checking for scripts that need to be fast, precise and memory efficient, not for the convenient syntaxing, type locking or such, I’m fine with regular lua for most things.

I specifically use it for optimizing so the interpreter knows for example how much memory is really needed or must be allocated for an variable and all.
I purely use it for speed and memory efficiency, if it just serves as a convenient syntax it practically has no use for me.

4 Likes