Implement type functions for getmetatable
and setmetatable
.
setmetatable
Type FunctionThere is currently no way for users to apply metatable type information to a type without the usage of typeof()
. This isn’t ideal, as it adds verbosity and boilerplate to common patterns such as object-oriented programming. For example, the following:
local clock = {}
type Identity = typeof(setmetatable({} :: { time: number }, { __index = clock }))
could be reduced to:
local clock = {}
type Identity = setmetatable<{ time: number }, { __index: typeof(clock) }>
getmetatable
Type FunctionIssue #1435 is caused by a lack of special behavior for getmetatable
when the type’s metatable has a __metatable
field. This is fixable in a variety of ways, however a type function has multiple benefits and exposes more type-level expression for users. It fits in nicely with the new solver, as type functions are a cleaner alternative to magic functions.
setmetatable
Type FunctionIn the following code example, Identity
should evaluate to { sound: string, @metatable: { __index: typeof(animal) } }
:
local animal = {}
type Identity = setmetatable<{
sound: string
}, {
__index: typeof(animal)
}>
When a non-table type (number, boolean) is passed into the type function, it should result in an error. This includes classes.
If the type is an intersection or union of tables, then the metatable should be applied to every component type.
If the second argument of the type function isn’t a table, then it should error.
If the first argument of setmetatable
has a metatable with the __metatable
field specified, then it should also report an error.
getmetatable
Type FunctionIn the following code example, ReversedIdentity
should evaluate to { __index: typeof(animal) }
:
local animal = {}
type Identity = setmetatable<{
sound: string
}, {
__index: typeof(animal)
}>
type ReversedIdentity = getmetatable<Identity>
Due to __metatable
, additional behavior needs to be met. In the following code example, MetatableType
should evaluate to "No metatable here!"
:
type metatable = {
__metatable: "No metatable here!"
}
type MetatableType = getmetatable<setmetatable<{}, metatable>>
If the type passed in does not support metatables in the type system (i.e. at the time of writing, is not any of a primitive, a singleton, a class, a table, or a union or intersection thereof), it should error when passed to getmetatable
.
If the type passed in is an intersection or union of things that support metatables, then it will return an intersection or union of those metatables respectively.
If no metatable is present, but the type system supports metatables for that type, it should return the nil
type indicating that there is no metatable present.
Introducing a large library of “global” type functions can clutter naming space for users.
The name of setmetatable
has an interpretation of implying a side effect; tables are references, and setmetatable
is a side-effecting function. It could be argued that setmetatable
is not a good name for a type function which has no side effects. However, consistency is desirable. A slight rename of a global function could cause confusion, and likely cause mistakes to be made. Type functions also have a precedent of not allowing side effects, so therefore a setmetatable
type function would not have ambiguity when it comes to side effects.
Do nothing, and rely on unique metatable type syntax to achieve what a setmetatable
type function would achieve. However, this still has the issue of getmetatable
. It is flat out incorrect at the moment, and a type function would be an ideal solution to that problem. If a type function is introduced, then any user could reasonably expect a counterpart setmetatable
type function. Both metatable type functions and metatable syntax can exist at the same time, and that might be ideal.
Do nothing. typeof
solves the current issues with accessibility to the current object-oriented programming patterns, and the issue relating to getmetatable can be solved by overloads.