Could you be more specific?
I wouldn’t use metatables and typeof to declare types, as the current typechecker produces less-than-ideal types if they have a metatable attached (will be improved soon, though). You only really need to use that syntax if you plan on using getmetatable
on your objects, which is mostly unnecessary. Also, I’m not sure how it’ll fare with the way I decided to define the interface.
I’d use the former to declare the type (see my previous example). You also don’t have to use an intersection, if you feel it makes the generated type easier to read. The following retains the same behavior, so you can pass a Class object into anything that wants an Interface and typecast Class objects to Interface objects.
export type Interface<T> = {
Foo: (self: T) -> (),
Bar: (self: T) -> number,
}
export type Class = {
Foo: (self: Class) -> (),
Bar: (self: Class) -> number,
Value: number
}
If you don’t have the generic, creating a class object will cause an error (I’m using the V2 type solver, so I don’t know if it’s fine with the old one).
type Interface = {
Foo: (self: Interface) -> (),
Bar: (self: Interface) -> number,
}
type Class = Interface & {
Value: number,
}
local function foo(self: Class): ()
return
end
local function bar(self: Class): number
return self.Value
end
-- Not ok because foo and bar expect Interface,
-- but Class is not exactly Interface
local object: Class = {
Foo = foo,
Bar = bar,
Value = 100,
}
Typing a parameter as an interface means only the defined properties of the interface will be available, which is desired behavior.
For example:
local function doSomething<T>(object: Interface<T>)
-- Not ok because we only know object has foo and bar methods
-- TypeError: Key 'Value' not found in table 'Interface<T>'
-- You can typecast object to Class, and access Value that way, though
print(object.Value)
end
local object: Class = {
Foo = foo,
Bar = bar,
Value = 100,
}
doSomething(object) -- This would technically be fine at runtime