Type checker does not determine type of Instance.new

I am using the new LuaU type solver, however the same point stands for the previous type solver
Consider the following:

local function create(instanceName : string) : GuiObject
   return Instance.new(instanceName);
end

create("Part")    -- this is incorrectly valid and the type solver believes this to be of type Part

Shouldn’t the type solver deny this as Instance.new will not always return an instance of type GuiObject?
Whereas, if I was to explicitly define the instanceName, the type solver recognises the inconsistency:

local function create() : GuiObject
   return Instance.new("Part");
end

Type pack 'Part' could not be converted into 'GuiObject'; at [0], Part is not a subtype of GuiObject

1 Like

Hi there, thank you for the report! The reason for this is a bit subtle, but effectively, today, Instance.new is given the type (string, ...any) -> any as a baseline, and the type system as embedded in Roblox implements a magic function that understands the relationship between the string being passed to Instance.new when it is a literal and the actual type system. When it is not a literal, the magic function cannot trigger, and you just get any as the result type. As such, in your function case, where you’re taking an arbitrary string as the parameter, you’re always returning something of type any from Instance.new and so no error is reported.

In the old Type Solver, we did not really have any possibility of making the type for Instance.new more accurate than this because the “real” type in some kind of abstract sense is what’s called a dependent type (one of the most powerful and expressive type system features, and one that is not in any language you’re likely to have heard of before). In kind of made up syntax, it’s really something like: (instanceName: string) -> reify<instanceName> where, somehow, reify<instanceName> turns the runtime value of that argument into an appropriate type. Since the old Type Solver is not expressive enough to support this, we made a decision, as an affordance to users, to type it as any since the alternative would likely mean giving it the type Instance and requiring lots of downcasts. So, in a direct sense, this behavior is expected today and a conscious decision about the signature of Instance.new.

In the New Type Solver, it is now possible, albeit rather tricky (and there’s a few extra features you’d clearly want to get it totally right), to implement an accurate type for Instance.new. With the type function machinery described in the Beta announcement, we could get something almost akin to the imagined dependent type above: <InstanceName>(InstanceName) -> InstanceFrom<InstanceName> by providing a builtin type function in Roblox named InstanceFrom that will map string literal types (like "Part" as a type) into the corresponding type in the type system for that instance. In the event of a string type, it’d probably fall back to Instance generally, and error for other types altogether. That probably provides the best overall developer experience here, but it might not happen until after the New Type Solver sees a general release (and also may not happen in exactly this form, frankly). One of the features we’d probably want for a variant of this is “bounded polymorphism” (sometimes called bounded generics or generic type bounds or a million other names) so that the type could express that InstanceName must be a subtype of string rather than expressing it implicitly in the definition of InstanceFrom, e.g. something like <InstanceName: string>(InstanceName) -> InstanceFrom<InstanceName>.

P.S. the language is named Luau, it’s a Hawaiian word pronounced like loo-ow, not the Portuguese word Lua with a capital U tacked on.

4 Likes