Luau Type Checking Beta!

I imagine it might take the form of TypeScript or Flow, where you declare the type annotations per each overload but the function body is one.

This also means there would be minimal impact to the runtime, only affecting the compiler (and by effect, the language server)

Links:

I’ve been switching most of my code to take an array as a parameter instead of varargs for this reason. It seems like something entirely doable with the syntax though, something like
function takesInVarArgs(...: ...unknown)

In general, I think you should avoid varargs when you can use an array, just because it’s much easier to manage and document, and because usually you have to restructure/destructure between varargs and arrays internally, which adds to unnecessary computation.

If course, for something like Resume and FastSpawn you can’t really avoid it, since sometimes parameters can be “nil” but still present in the vararg (if you ever have code that’s using select('#', ...))

The problem there is that the user can retain a reference to the table and start fiddling around with it while it still may be in use. They can also pass a table with a metatable that does who knows what when indexed. I prefer to have “clean” APIs when possible.

Regardless, the current type system still doesn’t have an accurate way to describe actual, sequential arrays, so I’m left disappointed either way.

1 Like
--!strict

local PrintEvent: BindableEvent = Instance.new("BindableEvent")

PrintEvent.Event:Connect(function (a: any)
    print(a)
end)

PrintEvent:Fire(10)

In this code I get the following warnings: image
It’s pretty annoying seeing it as there is nothing wrong with the code.

3 Likes

Same output, but you can remove the warning by setting the event you’re connecting to as a variable:

--!strict

local PrintBindable: BindableEvent = Instance.new("BindableEvent")
local PrintEvent: RBXScriptSignal = PrintBindable.Event

PrintEvent:Connect(function (a: any)
    print(a)
end)

PrintBindable:Fire(10)
2 Likes

Thanks for the swift fix; however, I don’t think it should be necessary as the code I wrote earlier was still valid.

2 Likes

I’m encountering an issue with string.byte; its return type is set to be number|nil, which is accurate, but it means that anything that interacts with string.byte has to also be number|nil.

This can be fixed rather easily by type guarding against it being nil, but that’s cumbersome in some cases. I have some code that looks similiar to this, and despite me knowing that it will never be nil, I have to typecheck against string.byte’s return rather than just casting it directly to a number. We need an equivalent to as sooner rather than later.

--!strict
local function foo(bar: string): {[number]: number}
  local baz: {[number]: number}
  for i = 1, #bar do
    baz[i] = string.byte(bar, i, i)
  end
  return baz
end
1 Like

TypeScript has a syntax for “I’m confident that this actually isn’t nil” by adding an exclamation mark at the end of your statement

e.g.

local x: string = string.byte('a')!

That would be pretty nice for Roblox, especially since there’s lots of cases where you want to make assumptions (indexing instances comes to mind).

11 Likes

sdfdg

W000: Type mismatch nil and RBXScriptConnection

for _,v in ipairs(self.cache) do
	if v.Destroy then
		v:Destroy()
	elseif typeof(v) == "RBXScriptConnection" then
		v:Disconnect()
	end
end

self.cache is an empty table until values are added to be removed at a certain scenario

I’m trying to figure out how to use this, but it’s not working for me. Script Analysis repeatedly tells me to restart studio, and I have several times. Still, nothing is fixing. Any ideas on why I can’t get it to work?

image

1 Like

This means that there is a snippet somewhere causing an internal compiler error. If you could shoot me the RBXL file, I can track it down and find the minimal repro to file a ticket.

1 Like

I apologize if this problem’s been brought up here already, but I haven’t seen it yet.
An array with different types for values doesn’t seem to be working properly:

type mixedArray = {[number]: any}
local mixedTable: mixedArray = {true, 10}

W000: Type mismatch number and boolean

Strangely enough it will let me have different value types if my keys aren’t all numbers:

type mixedArray = {[number | string]: any}
local mixedTable: mixedArray = {[1] = true, ["String"] = 10}

Finally couldnt wait for this feature.

I would love to be able to put the type’s under return’s in modules.
Since types get messy i ussaly just shove them at the bottom of the script but because of the return in modulescripts i cannot put it at the very bottom in modulescripts. Very minor thing but something I would love to be changed.

I would also love for some file which all scripts get their types from, This would be very useful for scripts which use alot of modules.

typed luau really look like rust type definition(look at the example bellow). are you guys inspired from rust for luau type checking?

// variable definition
let mut var: bool = true
// function 
fn return_inverted_boolean(let mut var: bool) -> bool {
  return !var
}
2 Likes

Weird question, but what’s the point of this? I see it removes the need for redundant assert() calls, but is there something special about type checking that I’m missing?

Assert and Type Cheking are totally different, assert gives you errors when executing the code, Type Cheking is while you write the code, for example, if you call a function with an assert with incorrect arguments, when executing it then it will give you an error, but with Type Checking it will give you an error when writing the arguments. And that’s just a comparison, Type Checking does much more than that.

Further performance optimization.

Here’s an example of a simple function written in plain Lua (it doesn’t make much sense, but it shows how to confuse a compiler):

local function Log(boolA,vecB,vecC)
	if boolA then
		print(vecB.X,vecB.Y,vecB.Z)
	else
		print(vecC.X==true and vecC.Y or vecC.Z)
	end
end

Lua doesn’t know what boolA, vecB or vecC are. boolA can be any type and has to check whether it’s a true boolean or a non-nil. It only knows that X, Y and Z are values in vecB and vecC, but it has no idea that they are numbers. No optimizations would be possible after the else statement as it doesn’t know that X, Y and Z are numbers, so it still has to check if they are something else. This bad code cannot be optimized much by a JIT compiler without the rest of the code exposed.

Here’s the same example but with types:

local function Log(boolA:boolean,vecB:Vector3,vecC:Vector3)
	if boolA then
   		print(vecB.X,vecB.Y,vecB.Z)
   	else
   		print(vecC.X==true and vecC.Y or vecC.Z)
   	end
end

The compiler immediately knows that boolA is always a boolean, so it doesn’t have to check if it’s non-nil. vecC is a Vector3, so X, Y and Z are numbers, rendering the second print statement easily simplifiable to print(vecC.Z).

3 Likes

Furthermore, type checking can help you catch bugs in your codebase you probably would never have found without it. Many popular languages implement strict type checking.

Also, it helps the programmer understand the code better. You can look at the code, and you would understand that a certain variable is a certain type.

2 Likes

Hi, I have a couple questions. I’m trying to figure out what I’m doing wrong. The documentation does not seem to be pointing me in any helpful direction.

#1 - Why does this not understand that OnServerInvoke is valid?

#2 - When I union Roblox instance types, it seems that methods like IsA aren’t detected. If I replace the union with Instance, then it works fine:

Using screenshots so I can show the errors.

6 Likes