Luau Type Checking Beta!

When typeof appears in type position, it means something a little bit different than the typeof() function that you see in regular Lua.

“Type position” means a position in your code where the parser is expecting to see a type and not a value. Type annotations and after the = in a type definition, in other words.

In type position, typeof is a way to say “whatever type this expression produces.” Some examples:

--!strict
local a = 55 -- Luau infers that a: number
local b: typeof(a) = 22 -- b has whatever type a has.  Number, in this case.

local c = Player.CFrame -- c has type CFrame
local v = Vector3.new(0, 0, 0) -- v has type Vector3

local d: typeof(c * v) = c -- d has whatever type c * v would have, if you
                           -- were to run it.  c * v is not actually
                           -- executed!
3 Likes

Okay I see, thank you. Though I have a few more questions.

Why does local x: typeof({ }) = { } work, but local x: table = { } doesn’t? Why aren’t tables supported? Will more datatypes like function, thread and userdata (excluding roblox ones like Instance, Vector3, etc) be supported in the future?

Quoted from zuexcg.

Is there any way to express inheritance at the moment? The most obvious use for something like this is having a piece of code that accepts all BaseParts, not just ones that have been explicitly whitelisted in a union (that and Part|WedgePart|CornerWedgePart|MeshPart|Union and so forth is really not intuitive or fun to write).

Being able to instead write something like inherits<BasePart> (but with better syntax obviously) would be nice.

Additionally, I don’t think I ever got an answer on including an Array type by default. It’s not a very big deal but arrays are very common so it should probably be built in.

5 Likes

I not am good with Roblox inheritance, but hope it help you:

From here: All about Object Oriented Programming

@Dekkonot, a idea would be:

local MyPart = {}

function MyPart.new()
    local NewMyPart = {} --Your new Part
    return setmetatable(NewClass, BasePart) --If i am following the tutorial i linked you, so you can inherit from BasePart class. But i am unsure if Roblox will leave you do this…
end

return MyPart

And if you want to make that the default Instance.new() function can use your default class, so just read this: Is it possible to make own Instances? - #11 by sjr04

Is here any method to make that a function return nil, also was a void type function?

While I appreciate the lesson, I’m actually referring to the Roblox class inheritance that already exists. There isn’t any documentation for this on the developer hub but the various classes all inherit from other classes on Roblox.

For example, Part inherits from a class called BasePart. You can use Instance:IsA in code to check inheritance like this, which is helpful since abstract classes like BasePart cover a lot of ground. Like I said above, it includes all of the part classes, meshparts, and unions.

I’m specifically asking for a way to specify this inheritance. There’s an obvious benefit to allowing all Parts (whether they be wedges, blocks, or meshes) to be specified as a type easily.

1 Like

Why not just use BasePart?

local t:BasePart = Instance.new"Part"
-- or with generics
type inherits<T> = T
local t2:inherits<BasePart> = Instance.new"Part"

It doesn’t check when IsA is used, so this gives a warning.

local t:BasePart = Instance.new"Part"
if t:IsA"Part" then
	print(t.Shape)
end

Tables and functions aren’t represented with function and table, instead they have their own syntax

type f = () => ()
-- takes no arguments, returns no values
type f2 = (string) => boolean
-- takes a string argument, returns a boolean
type f3 = (string,number?) => (number,number?)
-- takes a string and optional number, returns a number and an optional number
type t = {[number]:string}
-- keys are numbers, values are strings
type t2<T> = {[number]:T}
-- keys are numbers, values are the specified generic type
type t3 = {a:number,b:string,c:t3?}
-- key 'a' is a number, key 'b' is a string, key 'c' is a t3 or nil

Hopefully we get thread and userdata, although userdata would only be for userdata created with newproxy.

1 Like

I suppose I could just use BasePart. I’ll admit I forgot that the type system was just checking for various members.

Nevermind, then – the point about an array type still stands though.

2 Likes

Bug…?

RobloxStudioBeta_81qo3Bc3ac

1 Like

I suppose that would work but that won’t prevent non-sequential keys from being used, or non-integer keys

It seems like compound if statements have some issues:

local part0: BasePart? = workspace:FindFirstChild("Part0")
local part1: BasePart? = workspace:FindFirstChild("Part1")

if part0 and part1 then
	-- This gives the following warning in Script Analysis:
	-- Non-table type BasePart | nil does not have key "Name"
	print(part0.Name)
	print(part1.Name)
end

if part0 then
	if part1 then
		-- This gives no warnings
		print(part0.Name)
		print(part1.Name)
	end
end

Also, explicit nil checking like this gives the same warning as the above:

if part0 ~= nil then
	print(part0.Name)
end

Side note, it would be really cool if type annotating as BasePart / Model / some other type of instance would pass onto FindFirstChild and have it only return that type. Or maybe there needs to be stricter checks for FindFirstChild/similar methods and types. Right now declaring something as BasePart like I did above but having FindFirstChild still able to return any type of Instance (or none) without warning me seems bad.

Using SetAsync on a DataStore and passing a table with a string and two tables (that have three numbers in them) seems to not work. Worked in nonstrict mode, but not strict.
image

type is a keyword for defining custom types that aren’t part of Luau normally. Think of it as aliasing a word to a type. It’s not a function. The {x: number, y: number} is defining what the type is. Later on, you can use the custom type like any other type:

function point_thing(point: Point) => boolean
    return point.x == point.y
end

Just makes it easier than putting {x: number, y: number} every time.

And I have realized that you posted in January. Oops.

Not sure if this was mentioned already, but functions such as FindFirstChild get warnings on them even when their ‘third’ argument (recursive?) is optional.


The tooltip also doesn’t show me the return type of my function or the argument types. It also says “3 arguments required” when one is optional.


Another thing was that it was giving me a warning for trying to use this function even though the expected return type is completely viable

Also, even though I make sure the object is of type Instance, it does not autocomplete the expected functions from an Instance type.

FindFirstChild doesn’t even take 3 arguments, only 2 (the second one being optional as you said).

Also, why does Luau annotations have to be the only way to mark types? Isn’t comment based deduction better and cleaner?
I honestly find these much easier to read.




This is JSDocs for JavaScript / Node.js that is used in IDEs like Visual Studio and it prevents code bloat by using comments, while also being able to provide detailed and precise descriptions of variables, parameters, functions, and etc as you type them out which is very very useful and not currently possible when all you can do is annotate a type.

FindFirstChild Takes 3 arguments.
When you call a findfirstchild(), you use a syntax suger “:”. When this is used, lua automatically takes the object “:” was used as a first argument, which can be referred by using the “self” global.

local obj = {}
obj.__index = obj

function obj.new(name: string) => object
    local newObj = setmetatable({}, obj)
    newObj.Name = name
    return newObj
end

function obj:printName()
   print(self.Name) --self refers to newObject created in script after this module script
end

type object = typeof(obj.new)

return obj

--Script
local objModule = require(obj)
local newObject = objModule.new("Bob")

newObject:printName() --By using ":", we tell the function that self refers to newObject
>>Bob

In same manner, the FindFirstChild(name, recursive)
takes 3 arguments because it uses “:” to the object you are searching in

tl;dr
Object:Method(…) == Object.Method(Object, …)
no matter how the function’s made

1 Like

image
This is correct. You need to cast your return type to Instance first because it can either be an Instance, which has the properties/methods you desire, or nil, which has no properties or methods. Your function can return an Instance, but it can also return nil since the return type is optional, hence Instance | nil. The code you provided does not cast to Instance, so the IDE gives the warning.

The other two things though are funky behavior, I presume Luau doesn’t check if an argument is optional yet.