"t": A Runtime Type Checker for Roblox


#1

I made this for checking types during runtime. It’s designed specifically for Roblox, but also works in Lua5.1.

Give it a try, let me know if you have any questions or if the documentation could be more clear anywhere.


#2

Added error messages as a second return value from type checkers.

If you wrap your type check in an assert you’ll get the error message on failure:

assert(t.number("1")) --> number expected, got string

(these can get pretty complex based on your type definitions)

Different example:

local success, errMsg = t.number("1")
if success then
	-- do stuff
else
	warn(errMsg)
end

#4

I would still prefer “tv”


#5

local tv = require(path.to.t)

:thinking:


#6

this is something ive always wanted to have but ive always been too lazy to make

very good 10/10


#7

How would you check that a number isn’t nan, +inf, or -inf?

(No reason, really. Just thought that someone may need to know this if the domain of a function is all real numbers. I’m using this library for my game <3)


#8

There isn’t a check built in for this, but you could totally make your own. (See tips and tricks in the readme)

I’ll consider adding something like this in the future.


#9

This… is just wonderful.


#10

Thanks, let me know if anything was confusing with the documentation.

Did you find any cool (or not obvious) uses for it?


#11

Not any particularly non-obvious uses, the documentation is good.


#12

Interestingly enough I made a runtime type checker a while ago.

The main difference is that I wanted to bind signatures to functions themselves, here’s an example.

local myFunction = validate("number", "string", function (a, b)
  print("Valid")
end)
myFunction(1, "Hi")
-- Valid
myFunction("Test")
-- Error: bad argument #1 to 'myFunction' (number expected, got string)

I realized that overrides were also quite important so added in support for them too.

myFunction:AddSignature("string")
myFunction("Test")
-- Valid

There is also support for varargs, so you can specify the type as ... and it’ll match any type.

local myOtherFunction = validate("function", "...", function (f, ...)
   f(...)
end)

#13

If there’s one thing I dislike about this library, it’s the name. Too short and makes it hard to search for (via Google, DevForum search)


#14

That’s definitely a downside to the name, but many people dislike having a different variable name from the library name. So a single letter encourages a short variable name. More complex type definitions require you to repeat the variable name often in the same line.


#15

Just wanted to dump a bunch of checker I found a use for in my game:

do
	local function isNan(n)
		return n ~= n
	end
	
	function t.nan(value)
		local success, errMsg = t.number(value)
		if not success then
			return false, errMsg or ""
		end
		
		if isNan(value) then
			return true
		else
			return false, string.format("NaN expected")
		end
	end
	
	function t.notNan(value)
		local success, errMsg = t.number(value)
		if not success then
			return false, errMsg or ""
		end
		
		if not isNan(value) then
			return true
		else
			return false, string.format("non-NaN expected")
		end
	end
end

do
	local function isFinite(n)
		return n > -math.huge and n < math.huge
	end
	
	function t.finite(value)
		local success, errMsg = t.number(value)
		if not success then
			return false, errMsg or ""
		end
	
		if isFinite(value) then
			return true
		else
			return false, string.format("finite expected")
		end
	end
	
	function t.infinite(value)
		local success, errMsg = t.number(value)
		if not success then
			return false, errMsg or ""
		end
	
		if not isFinite(value) then
			return true
		else
			return false, string.format("infinite expected")
		end
	end
end

function t.real(value)
	local success, errMsg = t.notNan(value)
	if not success then
		return false, errMsg or ""
	end
	
	local success, errMsg = t.finite(value)
	if not success then
		return false, errMsg or ""
	end
	
	return true
end

function t.realVector3(vector)
	local success, errMsg = t.Vector3(vector)
	if not success then
		return false, errMsg or ""
	end
	
	local success, errMsg = t.real(vector.magnitude)
	if not success then
		return false, "vector components are not all real"
	end
	
	return true
end

function t.unitVector3(vector)
	local success, errMsg = t.Vector3(vector)
	if not success then
		return false, errMsg or ""
	end
	if vector.magnitude == 1 then
		return true
	else
		return false, "unit vector expected"
	end
end

t.alpha = t.numberConstrained(0, 1)

function t.integerMin(min)
	assert(t.integer(min))
	return function(value)
		local success, errMsg = t.integer(value)
		if not success then
			return false, errMsg or ""
		end
		if value >= min then
			return true
		else
			return false, string.format("integer >= %d expected", min)
		end
	end
end

function t.integerMax(max)
	assert(t.integer(max))
	return function(value)
		local success, errMsg = t.integer(value)
		if not success then
			return false, errMsg or ""
		end
		if value <= max then
			return true
		else
			return false, string.format("integer <= %d expected", max)
		end
	end
end

function t.integerConstrained(min, max)
	assert(t.integer(min) and t.integer(max))
	local minCheck = t.numberMin(min)
	local maxCheck = t.numberMax(max)
	return function(value)
		local integerSuccess, integerErrMsg = t.integer(value)
		if not integerSuccess then
			return false, integerErrMsg or ""
		end
		
		local minSuccess, minErrMsg = minCheck(value)
		if not minSuccess then
			return false, minErrMsg or ""
		end

		local maxSuccess, maxErrMsg = maxCheck(value)
		if not maxSuccess then
			return false, maxErrMsg or ""
		end

		return true
	end
end

t.nonNegativeInteger = t.integerMin(0)

function t.equals(valueToEquals)
	return function(value)
		if value == valueToEquals then
			return true
		end
		
		return false, "two values are not equal."
	end
end

function t.notEquals(valueToNotEquals)
	return function(value)
		if value ~= valueToNotEquals then
			return true
		end
		
		return false, "two values are not unequal."
	end
end

PS: I’m using this library to make sure that clients are not sending bad data to my remotes. Can you think of any reason why I shouldn’t be doing this with your library?


#16

That is a perfectly valid use case! It’s why I originally wrote the library.

And thanks for posting this, these are great examples of custom type checkers.


#17

May need to be a little careful with your unitVector3 - the magnitude of a vector produced by .unit or documented as unitary in the API references is not guaranteed to always equal exactly 1 as far as I’m aware. It may instead be off by a very small amount - you could use an epsilon to catch these.


#18

Do you think vector == vector.unit is a good way of checking whether or a vector is a unitvector? Would be odd if the value of vector.unit changed every time I got it.


#19

I’m not sure. I’d suggest instead that you just do something like

local abs = math.abs
local eps = 0.0001
-- stuff 
return abs(vec.magnitude - 1) < eps

#20

I suppose. I was hoping to avoid using arbitrary eps but then again it’s not like any of my use cases require such precision.


#21

I was able to reproduce this, which makes sense. Not something I’ve thought about.

local vec = Vector3.new(0.0001, 0.0001, 0.0001).Unit
print(vec.Magnitude) -- 0.99999994039536