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>(...)
).