Bookmarked! This is such a great resource for strict typed luau!
Thanks clone trooper!
Bookmarked! This is such a great resource for strict typed luau!
Thanks clone trooper!
Metatables type definitions are the bane of my existence. However, I am pretty intrigued by this particular format by Slietnick (or whoever made it first, I just happened to stumble across this one):
I’ve incorporated this kind of metatable definition into all of my codebase. The unique part about defining metatable types like this is that you’re not forced to use the typeof
keyword to define your types. Personally speaking, typeof
is very “dirty” in the sense that its’ behavior isn’t always expected. Defining the metatable type with this method removes the use of typeof
and thus makes the type “cleaner”.
There is one caveat, however. You must do a little intellisense trickery to actually have it working as intended:
-->> typecasting to unknown then refining it to the class type
local self = (setmetatable({}, Class):: unknown):: ClassType
The main issue I have with this strategy is that it doesn’t strictly enforce the contract of the implementation. The author/maintainer of the module has to make sure the manually defined type is synchronized with the implementation itself.
In other words, there’s no warning produced if you deviate from the type description. The only reason I’d see this being reasonable is if there’s a bug going on with generics and metatables necessitating this… which might be the case if it’s related to this bug I reported:
Either way, it’s not safe unless it’s contractually enforced. Casting to unknown breaks this contract.
Maybe I missed it, but I didn’t see any explanation of typecasting (::)?
Nope, I did forget to cover it.
I’ve added a new typecasting section near the bottom
Sometimes the never type is used for annotating unintended members of a table to be indexed.
For example, __index in a metatable.
Definitely an interesting way to go about that. For me I just prefix stuff intended to be private with an underscore.
I have a question that after reading the guide I still didn’t get the answer and I’m starting to get scared it’s not possible…
Let’s say I have this type:
F: (Instance) -> boolean
But I want to let F accept any subclass of Instance as well, can this be possible without doing an :IsA check?
for example:
F = function(frame: Frame)
...
end
What the type Instance
really means here is any class that derives from Instance
. And, as you know it, all classes from derive from it, meaning any class in the engine is a valid parameter to the function.
That’s what I thought too but it wont let me do it.
I tried:
F = function(frame: Frame)
frame.Visible = false
end
and it made red everywhere
While Frame
might inherit from Instance
, Instance
does not inherit from Frame
, and we cannot assume that to be the case.
-- invalid code
local function hide(frame: Frame)
Frame.Visible = false
end
for _, child: Instance in ipairs(game:GetChildren()) do
hide(child)
end
So here’s the thing: if you know for sure that the object being passed to this function is of the correct type, you can help the type-checker by type-casting the argument:
local frame: Instance = Instance.new("Frame")
hide(frame :: Frame)
-- This is just an example. I know marking `frame` as `Instance` is redundant.
I imagine in your case that when you call F
, the parameter has not been given an appropriate type yet. You could use type casting, as I said before, or even better: strict typing.
It doesn’t work because you cannot plug in a function that takes a different type from Instance into a function that expects to consume an Instance.
You can just do a quick check of IsA to refine the type into Frame.
Something I wanted to add (which I have no idea is working as intended) is that typecasting will trim an ellipsis to its first value.
local function Vararg(...: number)
return ... :: number
end
print(
Vararg(1, 2, 3) -- returns just 1
)
It appears to evaluate faster than select
, so I’ve been using it to discard extra values in functions i.e. string.gsub(A, B) :: string
. If someone could explain why it happens though that would be great.
What do I do if I want to put an unknown (…) amount of functions as a type? Like a weapon with multiple abilities for example.
export type WeaponInfo = {
Class: string,
Damage: number,
Ammo: number,
Auto: boolean,
Abilities: (...any) -> ()
-- i want it to be possible for abilities to be multiple functions, maybe a table of them or something
}
Make it a dictionary with string as keys and functions as values?
Something like this? (keep in mind im stupid)
Abilities: {(...any) -> ()}?
That or
Abilities: {
[string]: (...any) -> ()
}
Depends on how you want to append the functions to the abilities table. Through table.insert, or Abilities.SomeAbility = func
I know this topic hasn’t been checked for a while, but I can’t seem to be able to solve this issue:
type a = {
epic: string?,
amazing: number?
}
type b = a & {
super: string?
}
local tbl: b = {}
local key = "epic"
tbl[key] = "" -- Expected type table, got 'a & { super: string? }' instead.
When defining a custom type that includes another type, and then indexing that table with the custom type with a variable, the typechecker displays this warning.
It doesn’t occur when I do it like tbl["epic"] = ""
though.
The problem is that key
is inferred to have a type of string
, since by default Luau assumes you’ll want strings to be dynamic rather than string constants. At a static analysis level, this means it doesn’t know what string is indexing the sealed table type and assumes it’s unsafe.
Even if you define it explicitly as a constant though, I’m not sure if it’ll work. You should always just write to those fields explicitly if you’re using a dictionary type without an indexer.
How can I define it explicitly as a constant? Like key: string
?