I’ve been using typed Luau a bunch in the last few days to rewrite an older project (you’ve probably seen me post a few bug reports and feature requests in the last several days) and one thing I will say stands out is how hard it is to work with without an equivalent to as
.
The bulk of the work has thus far been on modernizing an API tool so that I can easily access information about classes and their members, and part of that involves writing type aliases for the API dump JSON. That in of itself was easy enough, but the API dump has a few optional fields in places.
The finished type alias for something like a class member ended up looking something like this:
type Member = {
Name: string,
MemberType: string,
Security: string | { Read: string, Write: string },
Tags: Array<string>?
}
Note: this isn’t the entire alias as it’s quite large and not important to this post
The ones to focus on are Security
and Tags
.
In order to work with Tags
, I had to do a truthy check on it, which while fine, has an annoying bug that I hope is fixed soon ().
Security
is a different beast though. At a glance, it seems easy to handle:
if type(Security) == "string" then
-- Security will be a string here!
else
-- Security will be a table!
end
This turned out to not work though, which turned out to be a major bummer. I’m not sure if this is a bug or not – if it is, I’ll file a bug report as soon as I’m told.
Refining the type from there is… Outright impossible. To clarify, in a strict environment, to my knowledge it is currently impossible to refine a type such that it will only be a table. Something like typeof(Security) == "table"
doesn’t work, and you can’t directly index Security
to check if it’s a table because then the script analyzer will raise an error about Read
not being a valid member of string
.
I ended up solving this problem by just passing the table through to a non-strict module which solved the problem with a simple if-statement, but that’s not ideal! We need some way to tell the type system that something is a particular type, because it’s currently not smart enough to know these things (and to be honest it’s a bit much to expect it to ever be perfect).
I once thought that a simple type assertion operator (!
in TypeScript) would be enough but I’m no longer of that opinion. I realize there’s no easy way to have as
be a valid keyword because you can call functions like as{}
but there has to be a solution here.
While talking with a few people a while back, we came up with a few potential solutions, ranging from something as simple as requiring expressions be wrapped in parentheses (e.g. (foo as {})
vs foo as {}
) or introducing a symbol (@
might be good but there be dragons). Just… please add some way to do it.