An experimental runtime typechecker for Roblox Luau, with matching 'tooling-time' Luau types.
t
compatibility (via GreenTea.t
β all t
tests pass!)While this package has had a lot of work put into it, it's largely an experiment. It's a first iteration, so the API can use some improvement β especially the structure of the generated Type objects, which could have a better shape.
Please leave feedback on the issues page. Thank you!
GreenTea = "corecii/greentea@0.4.10"
to your wally.toml
pesde add corecii/greentea
\
or add GreenTea = { name = "corecii/greentea", version = "^0.4.10" }
to your pesde.toml
GreenTea's runtime code looks like a typical runtime typechecker: you call functions to create typecheckers and compose them together.
It has two tricks:
local gt = require(path.to.GreenTea)
local stringType = gt.build(gt.string())
-- equivalent to takesString(value: string)
local function takesString(value: typeof(stringType.type()))
-- will error if value is not a string
stringType:assert(value)
-- ...
end
Using gt
is recommended, because GreenTea
is a lot to type and gt
is not!
local stringTypeRaw = gt.string()
-- stringTypeRaw is typed in Luau as `string`
-- but at runtime, it's really a GreenTea.Type object.
-- This makes it easy to split it up into a runtime
-- typechecker and a Luau type:
-- give a name to the type
type stringTypeLuau = typeof(stringTypeRaw.type())
-- cast the object to the runtime typechecker type it really is
local stringTypeChecker = gt.typecast(stringTypeRaw)
-- You can think of this like "tricking" Luau into
-- "giving" us a `string` type.
-- This is all `GreenTea.build` does -- it typecasts
-- the value into a GreenTea.Type object, but includes
-- the original type os you can use `typeof`.
-- But the magic is this also works for more complex types!
local catTypeRaw = gt.table({
age = gt.number(),
meowSound = gt.string(),
breed = {
[gt.string()] = gt.number(),
},
})
type catTypeLuau = typeof(catTypeRaw)
-- which is the same as...
type catTypeSame = {
age: number,
meowSound: string,
breed: { [string]: number },
}
-- and we can get a runtime typechecker...
local catTypeChecker = gt.typecast(catTypeRaw)
-- OR we can make it really easy on ourselves if we just use `build` from the beginning:
local catType = gt.build(gt.table{
age = gt.number(),
meowSound = gt.string(),
breed = {
[gt.indexer(gt.string())] = gt.number(),
},
}))
-- catType == catTypeChecker
-- catTypeLuau == typeof(catTypeChecker.type())
Using build
is recommended because it makes this mostly type-safe:
build
runs GreenTea.typeof
internally, so you can pass it simple tables
and they'll be converted to the proper GreenTea.table
type.In this way, "built" types are for direct use, and "unbuilt" types are for composition with other GreenTea constructors.
GreenTea is great for libraries that want to do typechecking, like RemoteEvent wrappers, because you can make the API very ergonomic:
-- RemoteWrapper.luau
function RemoteWrapper.new<T>(greenTeaType: T): RemoteWrapper<T>
local self = {
typechecker = GreenTea.build(greenTeaType),
...
}
return setmetatable(self, RemoteWrapper)
end
function RemoteWrapper.onServerEvent<T>(self: RemoteWrapper<T>, fn: (player: Player, params: T) -> ())
self.event:OnServerEvent(function(player: Player, paramsMaybe: any?)
local params = self.typechecker:assert(params)
fn(player, params)
end)
end
-- CatSpawner.luau
local SpawnCat = RemoteWrapper.new({
cat = {
age = gt.number(),
meowSound = gt.string(),
...
},
location = gt.CFrame(),
...
})
SpawnVehicle:onServerEvent(function(player, params)
-- params is properly typed as { cat: { age: number, ...}, location: CFrame, ... }
end)
GreenTea has not been extensively tested on Lune. Some Roblox-specific typecheckers might not work, and there may be other issues. Please report any issues here: https://github.com/corecii/greentea/issues
t
CompatibilityGreenTea.t
includes all of the methods from the t
package. See the t package's readme for more details.
You can include the greentea-t-standalone
package as an easy, drop-in replacement for a codebase which already uses t
. Just add it to your wally.toml
:
with Wally (for Rojo)
- Install Wally
- Add
t = "corecii/greentea-t-standalone@0.4.10"
to yourwally.toml
with pesde (for Rojo)
- Install pesde
- Add
t = { name = "corecii/greentea_t_standalone", version = "^0.4.10" }
to yourpesde.toml
This package just exports GreenTea.t
to make drop-in replacement easy.
This library was largely inspired by t
, but also my experience working with t types, working with Luau types, and working with systems that need inspectable type definitions. This is my first attempt at solving all of these problems. I'd really like to see the inversion of this library -- where inspectable runtime type definitions are built from Luau types. But that's more work, so I'll leave it as a TODO!
GreenTea.luau
into multiple files. It's too long. But I'm tired and want to move on and actually use this library for cool things.