Make luau typeof() preserve generics in methods

Currently there is an inconsistency in the behavior of Luau types when typeof() is used on a table with methods vs. when the methods are explicitly defined in a manual type.

The inconsistency can be observed with the following code:

local Object = {}
Object.__index = Object

function Object:Get<T>(): T
    local self: Object<T> = self::any
    return self.Value
end

function Object.new<T>(value: T): Object<T>
    return setmetatable({Value = value}, Object) :: any
end

In this code, one of two choices can be made when exporting the type.
The first choice is sensible for objects with larger APIs and seems more intuitive:

export type Object<T> = typeof(Object) & {
    Value: T;
}

The second choice creates stricter API compliance requirements:

export type Object<T> = {
    Value: T;
    Get: (self: Object<T>) -> T;
}

This is where the issue occurs. Assuming the first is used, the return type of object:Get() is inferred to be any, whereas using the second one properly infers it to T.

This inconsistency makes generic types quite irritating to deal with (any API changes must be manually reflected in the type), so being able to define type-wide generics in a manner such that any generics with matching names are preserved in the resulting type (that is, export type Object<Foo> = typeof(Object) will include the generic parameter of all methods with Object:name<Foo>(...)).

3 Likes