I thought I’d make this it’s own post, since I haven’t been able to find any recent posts pertaining to it, and I don’t want to resurrect dead threads. (doesn’t that mean it’s not a useful post..?)
I’ve personally encountered this a few times, and I can imagine it may trip someone up who doesn’t know how to fix it, so I wanted to share my solution.
I’m sorry that I cannot provide a very technical explanation of the underlying mechanism, but it should prove adequate.
But please note if you’re iterating through a dictionary with mixed types, you would have to explicitly check types (using typeof()), as the iterator doesn’t know which of the applicable types it is, which personally I’d avoid doing in every circumstance.
(Note that this is NOT limited to only a defined dict, it can of course be done with defined types, too, simply do not use typeof() in that case)
-- Automatic example:
local AutoDict = {
VariableA = 200,
VariableB = 250
}
AutoDict = AutoDict :: {[keyof<typeof(AutoDict)>]: number} -- Now iterable, specifically a number
AutoDict["VariableA"] -- Autocompletes, doesn't get mad!
AutoDict.VariableA -- Still autocompletes! This is the standard for the following example.
But this can be dangerous, because you won’t be told if the type ‘number’ is not applicable to the dictionary. So, you can create a helper to ensure it’s accurate:
type HelperDictItem = number
local function NewDictItem(a: HelperDictItem): HelperDictItem
return a
end
local HelperDict = {
VariableA = NewDictItem(200),
VariableB = NewDictItem(250)
}
HelperDict = HelperDict :: {[keyof<typeof(HelperDict)>]: HelperDictItem} -- We successfully moved the type declaration to one the dict must follow.
This is more helpful when your values are unions, but you should avoid distinct unions as types in dicts.
An example of a dict that is hard to work with would be:
type ExampleKeys = "VariableA" | "VariableB" -- At it's roots, keyof<> really just creates a union similar to this, this is just manual not automatic.
type ExampleDict = {[ExampleKeys]: boolean | number | string}
It’s filled with different types (boolean and number and string), which makes it hard to work with especially when iterating.
But a clean use of unions for a dictionary would be something like:
type ExampleKeys = "VariableA" | "VariableB"
type ExampleDict = {[ExampleKeys]: "ValueA" | "ValueB" | "ValueC"}
Please know however that there are many pitfalls when type checking and you may encounter difficult situations because of your own design, so be careful. I suggest never getting yourself in the latter example’s situation, but if you must you can whilst still maintaining types to the best of the solver’s ability.
I hope that you learned something from this, that you don’t mind the bizarre inhuman names, and that this adequately explains this common scenario briefly and accurately (I haven’t slept)
If I missed something, or I’m actually scripting with terrible habits, please tell me, people!
If you have anything helpful to add that is similar to this, please share for everyone.
I wish you all the best, especially if just learning type-checking, it can be pretty tricky sometimes.
I haven’t made many posts, so I’m inviting any feedback on the post itself, thank you for reading!