How to 'trick' Luau Typechecking into returning Instance classes based on function arguments?

Hello! As the title of this topic suggests, I’m trying to make Luau Typechecking recognize a Instance class based on a function argument.

For example:

-- Luau recognizes this as a Part ✅ and autocompletes the properties etc.
local Object = Instance.new("Part")
local function CreateInstance(ClassName: string)
	local Object = Instance.new(ClassName)
	return Object
end

-- Luau does not recognize this as a Part ❌ and does not autocomplete the properties etc.
local Object = CreateInstance("Part")

So how would I make Luau recognize the type/class that the function is creating based upon the ClassName parameter?

I have tried doing this:

local function CreateInstance(ClassName: string)
	-- Adding typeof(Instance.new(ClassName)) to set the type of the variable.
	local Object: typeof(Instance.new(ClassName)) = Instance.new(ClassName)
	return Object
end

-- Luau does not recognize this as a Part ❌ and does not autocomplete the properties etc.
local Object = CreateInstance("Part")

But no luck!

This is mildly infuriating when using --!strict because the typechecker keeps telling me it’s uncertain whether a value is a Part for example.

Anyone have an idea?

3 Likes

You can set the return type of a function by doing this:

function returnPart() : Part
    return Instance.new("Part");
end

--Luau should recognize this as a part, autocompleting as it should.
local part : Part = returnPart();

I’m unsure if this is the solution you’re looking for, as I don’t think typechecking would work with a variable type.

1 Like

I see what you’re doing here, but that is unfortunately not the solution I’m looking for. I rather want it to work universally with any Instance class rather than just a Part.

1 Like

just want to contribute here and say that this does NOT work, but it absolutely seems like it should:

local function CreateInstance(ClassName) : typeof(Instance.new(ClassName))
	
	local obj = Instance.new(ClassName)
	
	return obj
	
end

I did some extra testing and this does not work, as in Luau does not know the type of Instance.new(), which is probably why none of what has been tried works:

local ClassName = "Part"
Instance.new(ClassName)
3 Likes

Have you tried marking the return type as type Instance?

It only recognizes the base instance properties

1 Like

Sorry to necro an old post, but if anyone is curious, you could achieve this like this:

EDITT: I edited the below code to make it slightly cleaner. For the original, scroll down below.

function myCreator(className: string): Instance
    ...
end

local myCreator: typeof(Instance.new) = myCreator :: any
-- local assignment statements where the value
-- is the same variable as the one being assigned
-- are removed by the compiler now.

myCreator("Part").Col -- Typechecking!!!

It’s not perfect but it sort of works.

Original code
function myCreator_(className: string): Instance
  ...
end

local myCreator: typeof(Instance.new) = myCreator_ :: any

myCreator("Part") -- Typechecking!!!

Create a wrap. It’s simple as mapping out instances in a table and then return the values, you should use lua classes for this.

Could you provide an example of this?

local Part = Instance.new("Part")

local Wrap = {
 __Reference = Part;
 Position = Vector3.new();
 ...
}
local mt = {
 __index = function(tab,index)
     return tab.__Reference[index]
end
...
}
setmetatable(Wrap,mt)

This is the simplest examples and all of this could be raised in the complexity.

1 Like

That’s cool but I’m confused as to how it would trick Luau’s type linting to return an Instance type based on the parameter of the function

You can actually typeof the Instance.new function and override your function with it.

type __new = typeof(Instance.new)

local Create: __new = function(Name: string)
	return Instance.new(Name)
end :: any -- Unneeded, but using for Forward-Compatibility

Create("Part") -- Now it works!
-- Do note, we lose the argument names with this
4 Likes

This is really clever, I will try this later myself and see if it fits my situation. Thanks for getting back on an old post :slight_smile:

This is smart, but in my situation I have a function that gets the children of a parent based on the ClassName that has been given. Kind of like this:

function GetChildren(Parent, ClassName) --// How do we tell Luau that we're going to return an array of "ClassName" instances?
    local childrenOfClass = {}
-- get children and run child.ClassName == ClassName then add to array
    return childrenOfClass
end

I don’t think there’s a way to do this though.

1 Like

I’ve tried experimenting with many solutions, but I think we’d need something like Type Conditionals or Generic Overloading to successfully achieve this.

Another thing you can do, but will take a long time to complete, is to use intersection types combined with function types:

type InstanceFactory =
	(("Part") -> Part)
& (("ObjectValue") -> ObjectValue)
& ...

function instanceCreator(className: string): Instance
	...
end

local instanceCreator: InstanceFactory = instanceCreator :: any

instanceCreator("Part"). -- Yay! Typechecking!

Of course, this would take a very VERY long time to complete so I would suggest automating it via a plugin that creates a ModuleScript which exports the type “InstanceCreator” and can be imported from other scritps.

4 Likes

wait bro, that is so smart you just solved 80% of my autocompletion problems.

1 Like

That’s a smart thing to do, but it’s obviously not worth all the work. I just hope that Luau would be smart enough one day to do things like this.

Yeah, that’s true. Although, as I said you could write a plugin to generate type modules for you depending on your input arguments. You could even have it generate other things such as instances in the explorer.

That said, I do agree that this should be a thing in Luau by default, in some form of capacity. But I have heard that they want to keep it very simple and not overcomplicate it like TS so maybe that isn’t so likely.

1 Like

I feel like they should make it available in strict mode though, I know lots of people that would die for this update if it ever came.

1 Like