Open ghost opened 1 year ago
This seems like it would be a really nice addition.
Currently, I'm trying to make a state wrapper that auto-types its elements for the methods. The problem is there is no current method of getting the types in table, so everything gets inferred as any
:
local newState = State.new {
A = 1,
B = 2
}
newState:SetValue("A", 2) -- "A" is not inferred, and 2 cannot be typed
newState:GetValue("B") -- Returns 'any'
This proposal would solve half of the problem, as now I could type the Key
params with keyof({} :: T)
, such as:
type State<T> = {
GetValue: (Key: keyof({} :: T)) -> any
}
However, this does not fix the problem with the return type or the second param for SetValue
, so I think there would need to be another operator to fix this, like a valueof
function or something of the likes. I'm not sure how this would all fit together, but in my mind, it would look something like:
type GetValue<T = {}, K = string> = (T: keyof({} :: T)) -> valueof({} :: T, "" :: K)
Obviously, fixing my problem is not trivial, but I don't think the addition of keyof
would hurt any future attempts at solving it.
The way TypeScript solves @MagmaBurnsV's problem is using keyof
, a type space indexing operator, and constrained generics, shown here. Implementors of this interface would have type-safe access and mutation of key-value pairs present in T
.
interface State<T> {
GetValue<K extends keyof T>(k: K): T[K];
SetValue<K extends keyof T>(k: K, v: T[K]): void;
}
// note that keyof accepts a type, so OP would need to use 'keyof typeof MY_OPINIONS'
I've seen discussion about constrained generics somewhere in this repo, like #784, but nothing about implementing keyof
(other than this post) or a type space indexing op.
I want to note this RFC outlining what kind of syntax the team might prefer for type functions and why. TL;DR use keyof<T>
over keyof T
or keyof(T)
.
For the record, there's a Index<T, K>
type family that's going to solve the type indexing problem, so I wouldn't bother working on this design space.
As for the Keyof
use case, there's a bunch of nuances to think about. Should Keyof
also include all accessible keys via __index
metamethod? What about if the table has indexers? The story gets blurry in a quick hurry.
I think keyof should also include all accessible keys via __index
metamethod because it's very similar than directly putting keys in the table. I first thought of keyof(T)
because of Luau having typeof(T)
would keep the syntax similar, but I don't mind any alternative syntaxes.
For the record, there's a
Index<T, K>
type family that's going to solve the type indexing problem, so I wouldn't bother working on this design space.As for the
Keyof
use case, there's a bunch of nuances to think about. ShouldKeyof
also include all accessible keys via__index
metamethod? What about if the table has indexers? The story gets blurry in a quick hurry.
I don't believe it should include keys from index because that isn't what you're asking for. If you wanted to do that you should have to directly specify it with keyof(t.index).
I think it's better to not include __index
, since (maybe in the future) a user could implement this behavior themself:
local MyClass = { Foo = "Bar" }
local OtherClass = setmetatable({ Hello = "World" }, { __index = MyClass })
type MyClass = typeof(MyClass)
type OtherClass = typeof(OtherClass)
type TableIndexed = Keyof<OtherClass> & Keyof<typeof(getmetatable({} :: OtherClass).__index)>
It just occurred to me that RawKeyof
could be a thing that explicitly ignores __index
, then Keyof
includes anything in __index
and all of the types in the __index
chain.
Would be awesome to have this this, allows for much stronger and automatic types.
I hope they are doing the same thing for valueof.
valueof
already exists in the new solver, it's called index
and rawget
: https://rfcs.luau-lang.org/index-type-operator.html and https://rfcs.luau-lang.org/rawget-type-operator.html
The problem
Currently, it's inconvenient to type check table content as I have to write the same thing twice, both in the type declaration and in the table.
For instance, a constant that use a bunch of strings as keys.
The solution
Add a keyof type! It returns a union of all the keys of a table! It should be backward compatible like typeof and not so chaotic to integrate.