local y=typeof or type local function g(g)return function(H)local k=y(H)if k==g then return true else return false,string.format("\037\115 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\115",g,k)end end end local H={}function H.any(y)if y~=nil then return true else return false,"\097\110\121 \101\120\112\101\099\116\101\100\044 \103\111\116 \110\105\108"end end H.boolean=g("\098\111\111\108\101\097\110")H.coroutine=g("\116\104\114\101\097\100")H.callback=g("\102\117\110\099\116\105\111\110")H.none=g("\110\105\108")H.string=g("\115\116\114\105\110\103")H.table=g("\116\097\098\108\101")H.userdata=g("\117\115\101\114\100\097\116\097")function H.number(g)local H=y(g)if H=="\110\117\109\098\101\114"then if g==g then return true else return false,"\117\110\101\120\112\101\099\116\101\100 \078\097\078 \118\097\108\117\101"end else return false,string.format("\110\117\109\098\101\114 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\115",H)end end function H.nan(y)if y~=y then return true else return false,"\117\110\101\120\112\101\099\116\101\100 \110\111\110-\078\097\078 \118\097\108\117\101"end end H.Axes=g("\065\120\101\115")H.BrickColor=g("\066\114\105\099\107\067\111\108\111\114")H.CFrame=g("\067\070\114\097\109\101")H.Color3=g("\067\111\108\111\114\051")H.ColorSequence=g("\067\111\108\111\114\083\101\113\117\101\110\099\101")H.ColorSequenceKeypoint=g("\067\111\108\111\114\083\101\113\117\101\110\099\101\075\101\121\112\111\105\110\116")H.DockWidgetPluginGuiInfo=g("\068\111\099\107\087\105\100\103\101\116\080\108\117\103\105\110\071\117\105\073\110\102\111")H.Faces=g("\070\097\099\101\115")H.Instance=g("\073\110\115\116\097\110\099\101")H.NumberRange=g("\078\117\109\098\101\114\082\097\110\103\101")H.NumberSequence=g("\078\117\109\098\101\114\083\101\113\117\101\110\099\101")H.NumberSequenceKeypoint=g("\078\117\109\098\101\114\083\101\113\117\101\110\099\101\075\101\121\112\111\105\110\116")H.PathWaypoint=g("\080\097\116\104\087\097\121\112\111\105\110\116")H.PhysicalProperties=g("\080\104\121\115\105\099\097\108\080\114\111\112\101\114\116\105\101\115")H.Random=g("\082\097\110\100\111\109")H.Ray=g("\082\097\121")H.Rect=g("\082\101\099\116")H.Region3=g("\082\101\103\105\111\110\051")H.Region3int16=g("\082\101\103\105\111\110\051\105\110\116\049\054")H.TweenInfo=g("\084\119\101\101\110\073\110\102\111")H.UDim=g("\085\068\105\109")H.UDim2=g("\085\068\105\109\050")H.Vector2=g("\086\101\099\116\111\114\050")H.Vector3=g("\086\101\099\116\111\114\051")H.Vector3int16=g("\086\101\099\116\111\114\051\105\110\116\049\054")H.Enum=g("\069\110\117\109")H.EnumItem=g("\069\110\117\109\073\116\101\109")function H.literal(y)return function(g)if g~=y then return false,string.format("\101\120\112\101\099\116\101\100 \037\115\044 \103\111\116 \037\115",tostring(y),tostring(g))end return true end end H.exactly=H.literal function H.integer(y)local g,k=H.number(y)if not g then return false,k or""end if y%1==0 then return true else return false,string.format("\105\110\116\101\103\101\114 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\100",y)end end function H.numberMin(y)return function(g)local k,o=H.number(g)if not k then return false,o or""end if g>=y then return true else return false,string.format("\110\117\109\098\101\114 \062\061 \037\100 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\100",y,g)end end end function H.numberMax(y)return function(g)local k,o=H.number(g)if not k then return false,o end if g<=y then return true else return false,string.format("\110\117\109\098\101\114 \060\061 \037\100 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\100",y,g)end end end function H.numberMinExclusive(y)return function(g)local k,o=H.number(g)if not k then return false,o or""end if y<g then return true else return false,string.format("\110\117\109\098\101\114 \062 \037\100 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\100",y,g)end end end function H.numberMaxExclusive(y)return function(g)local k,o=H.number(g)if not k then return false,o or""end if g<y then return true else return false,string.format("\110\117\109\098\101\114 \060 \037\100 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\100",y,g)end end end H.numberPositive=H.numberMinExclusive(0)H.numberNegative=H.numberMaxExclusive(0)function H.numberConstrained(y,g)assert(H.number(y)and H.number(g))local k=H.numberMin(y)local o=H.numberMax(g)return function(y)local g,H=k(y)if not g then return false,H or""end local T,w=o(y)if not T then return false,w or""end return true end end function H.numberConstrainedExclusive(y,g)assert(H.number(y)and H.number(g))local k=H.numberMinExclusive(y)local o=H.numberMaxExclusive(g)return function(y)local g,H=k(y)if not g then return false,H or""end local T,w=o(y)if not T then return false,w or""end return true end end function H.optional(y)assert(H.callback(y))return function(g)if g==nil then return true end local H,k=y(g)if H then return true else return false,string.format("\040\111\112\116\105\111\110\097\108\041 \037\115",k or"")end end end function H.tuple(...)local y={...}return function(...)local g={...}for H=1,#y,1 do local k,o=y[H](g[H])if k==false then return false,string.format("\066\097\100 \116\117\112\108\101 \105\110\100\101\120 \035\037\100\058\n\t\037\115",H,o or"")end end return true end end function H.keys(y)assert(H.callback(y))return function(g)local k,o=H.table(g)if k==false then return false,o or""end for g in pairs(g)do local H,k=y(g)if H==false then return false,string.format("\098\097\100 \107\101\121 \037\115\058\n\t\037\115",tostring(g),k or"")end end return true end end function H.values(y)assert(H.callback(y))return function(g)local k,o=H.table(g)if k==false then return false,o or""end for g,H in pairs(g)do local k,o=y(H)if k==false then return false,string.format("\098\097\100 \118\097\108\117\101 \102\111\114 \107\101\121 \037\115\058\n\t\037\115",tostring(g),o or"")end end return true end end function H.map(y,g)assert(H.callback(y),H.callback(g))local k=H.keys(y)local o=H.values(g)return function(y)local g,H=k(y)if not g then return false,H or""end local T,w=o(y)if not T then return false,w or""end return true end end do local y=H.keys(H.integer)function H.array(g)assert(H.callback(g))local k=H.values(g)return function(g)local H,o=y(g)if H==false then return false,string.format("\091\097\114\114\097\121\093 \037\115",o or"")end local T=#g for y in pairs(g)do if y<1 or y>T then return false,string.format("\091\097\114\114\097\121\093 \107\101\121 \037\115 \109\117\115\116 \098\101 \115\101\113\117\101\110\116\105\097\108",tostring(y))end end local w,d=k(g)if not w then return false,string.format("\091\097\114\114\097\121\093 \037\115",d or"")end return true end end end do local y=H.array(H.callback)function H.union(...)local g={...}assert(y(g))return function(y)for g,H in pairs(g)do if H(y)then return true end end return false,"\098\097\100 \116\121\112\101 \102\111\114 \117\110\105\111\110"end end function H.intersection(...)local g={...}assert(y(g))return function(y)for g,H in pairs(g)do local k,o=H(y)if not k then return false,o or""end end return true end end end do local y=H.map(H.string,H.callback)function H.interface(g)assert(y(g))return function(y)local k,o=H.table(y)if k==false then return false,o or""end for g,H in pairs(g)do local k,o=H(y[g])if k==false then return false,string.format("\091\105\110\116\101\114\102\097\099\101\093 \098\097\100 \118\097\108\117\101 \102\111\114 \037\115\058\n\t\037\115",g,o or"")end end return true end end function H.strictInterface(g)assert(y(g))return function(y)local k,o=H.table(y)if k==false then return false,o or""end for g,H in pairs(g)do local k,o=H(y[g])if k==false then return false,string.format("\091\105\110\116\101\114\102\097\099\101\093 \098\097\100 \118\097\108\117\101 \102\111\114 \037\115\058\n\t\037\115",g,o or"")end end for y in pairs(y)do if not g[y]then return false,string.format("\091\105\110\116\101\114\102\097\099\101\093 \117\110\101\120\112\101\099\116\101\100 \102\105\101\108\100 \'\037\115\'",y)end end return true end end end function H.instance(y)assert(H.string(y))return function(g)local k,o=H.Instance(g)if not k then return false,o or""end if g.ClassName~=y then return false,string.format("\037\115 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\115",y,g.ClassName)end return true end end function H.instanceIsA(y)assert(H.string(y))return function(g)local k,o=H.Instance(g)if not k then return false,o or""end if not g:IsA(y)then return false,string.format("\037\115 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\115",y,g.ClassName)end return true end end function H.enum(y)assert(H.Enum(y))return function(g)local k,o=H.EnumItem(g)if not k then return false,o end if g.EnumType==y then return true else return false,string.format("\101\110\117\109 \111\102 \037\115 \101\120\112\101\099\116\101\100\044 \103\111\116 \101\110\117\109 \111\102 \037\115",tostring(y),tostring(g.EnumType))end end end do local y=H.tuple(H.callback,H.callback)function H.wrap(g,H)assert(y(g,H))return function(...)assert(H(...))return g(...)end end end function H.strict(y)return function(...)assert(y(...))end end return H

-- t: a runtime typechecker for Roblox

-- regular lua compatibility
local typeof = typeof or type

local function primitive(typeName)
	return function(value)
		local valueType = typeof(value)
		if valueType == typeName then
			return true
		else
			return false, string.format("%s expected, got %s", typeName, valueType)
		end
	end
end

local t = {}

--[[**
	matches any type except nil

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
function t.any(value)
	if value ~= nil then
		return true
	else
		return false, "any expected, got nil"
	end
end

--Lua primitives

--[[**
	ensures Lua primitive boolean type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.boolean = primitive("boolean")

--[[**
	ensures Lua primitive coroutine type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.coroutine = primitive("thread")

--[[**
	ensures Lua primitive callback type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.callback = primitive("function")

--[[**
	ensures Lua primitive none type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.none = primitive("nil")

--[[**
	ensures Lua primitive string type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.string = primitive("string")

--[[**
	ensures Lua primitive table type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.table = primitive("table")

--[[**
	ensures Lua primitive userdata type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.userdata = primitive("userdata")

--[[**
	ensures value is a number and non-NaN

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
function t.number(value)
	local valueType = typeof(value)
	if valueType == "number" then
		if value == value then
			return true
		else
			return false, "unexpected NaN value"
		end
	else
		return false, string.format("number expected, got %s", valueType)
	end
end

--[[**
	ensures value is NaN

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
function t.nan(value)
	if value ~= value then
		return true
	else
		return false, "unexpected non-NaN value"
	end
end

-- roblox types

--[[**
	ensures Roblox Axes type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Axes = primitive("Axes")

--[[**
	ensures Roblox BrickColor type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.BrickColor = primitive("BrickColor")

--[[**
	ensures Roblox CFrame type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.CFrame = primitive("CFrame")

--[[**
	ensures Roblox Color3 type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Color3 = primitive("Color3")

--[[**
	ensures Roblox ColorSequence type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.ColorSequence = primitive("ColorSequence")

--[[**
	ensures Roblox ColorSequenceKeypoint type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.ColorSequenceKeypoint = primitive("ColorSequenceKeypoint")

--[[**
	ensures Roblox DockWidgetPluginGuiInfo type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.DockWidgetPluginGuiInfo = primitive("DockWidgetPluginGuiInfo")

--[[**
	ensures Roblox Faces type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Faces = primitive("Faces")

--[[**
	ensures Roblox Instance type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Instance = primitive("Instance")

--[[**
	ensures Roblox NumberRange type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.NumberRange = primitive("NumberRange")

--[[**
	ensures Roblox NumberSequence type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.NumberSequence = primitive("NumberSequence")

--[[**
	ensures Roblox NumberSequenceKeypoint type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.NumberSequenceKeypoint = primitive("NumberSequenceKeypoint")

--[[**
	ensures Roblox PathWaypoint type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.PathWaypoint = primitive("PathWaypoint")

--[[**
	ensures Roblox PhysicalProperties type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.PhysicalProperties = primitive("PhysicalProperties")

--[[**
	ensures Roblox Random type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Random = primitive("Random")

--[[**
	ensures Roblox Ray type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Ray = primitive("Ray")

--[[**
	ensures Roblox Rect type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Rect = primitive("Rect")

--[[**
	ensures Roblox Region3 type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Region3 = primitive("Region3")

--[[**
	ensures Roblox Region3int16 type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Region3int16 = primitive("Region3int16")

--[[**
	ensures Roblox TweenInfo type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.TweenInfo = primitive("TweenInfo")

--[[**
	ensures Roblox UDim type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.UDim = primitive("UDim")

--[[**
	ensures Roblox UDim2 type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.UDim2 = primitive("UDim2")

--[[**
	ensures Roblox Vector2 type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Vector2 = primitive("Vector2")

--[[**
	ensures Roblox Vector3 type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Vector3 = primitive("Vector3")

--[[**
	ensures Roblox Vector3int16 type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Vector3int16 = primitive("Vector3int16")

-- roblox enum types

--[[**
	ensures Roblox Enum type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.Enum = primitive("Enum")

--[[**
	ensures Roblox EnumItem type

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
t.EnumItem = primitive("EnumItem")

--[[**
	ensures value is a given literal value

	@param literal The literal to use

	@returns A function that will return true iff the condition is passed
**--]]
function t.literal(literal)
	return function(value)
		if value ~= literal then
			return false, string.format("expected %s, got %s", tostring(literal), tostring(value))
		end
		return true
	end
end

--[[**
	DEPRECATED
	Please use t.literal
**--]]
t.exactly = t.literal

--[[**
	ensures value is an integer

	@param value The value to check against

	@returns True iff the condition is satisfied, false otherwise
**--]]
function t.integer(value)
	local success, errMsg = t.number(value)
	if not success then
		return false, errMsg or ""
	end
	if value%1 == 0 then
		return true
	else
		return false, string.format("integer expected, got %d", value)
	end
end

--[[**
	ensures value is a number where min <= value

	@param min The minimum to use

	@returns A function that will return true iff the condition is passed
**--]]
function t.numberMin(min)
	return function(value)
		local success, errMsg = t.number(value)
		if not success then
			return false, errMsg or ""
		end
		if value >= min then
			return true
		else
			return false, string.format("number >= %d expected, got %d", min, value)
		end
	end
end

--[[**
	ensures value is a number where value <= max

	@param max The maximum to use

	@returns A function that will return true iff the condition is passed
**--]]
function t.numberMax(max)
	return function(value)
		local success, errMsg = t.number(value)
		if not success then
			return false, errMsg
		end
		if value <= max then
			return true
		else
			return false, string.format("number <= %d expected, got %d", max, value)
		end
	end
end

--[[**
	ensures value is a number where min < value

	@param min The minimum to use

	@returns A function that will return true iff the condition is passed
**--]]
function t.numberMinExclusive(min)
	return function(value)
		local success, errMsg = t.number(value)
		if not success then
			return false, errMsg or ""
		end
		if min < value then
			return true
		else
			return false, string.format("number > %d expected, got %d", min, value)
		end
	end
end

--[[**
	ensures value is a number where value < max

	@param max The maximum to use

	@returns A function that will return true iff the condition is passed
**--]]
function t.numberMaxExclusive(max)
	return function(value)
		local success, errMsg = t.number(value)
		if not success then
			return false, errMsg or ""
		end
		if value < max then
			return true
		else
			return false, string.format("number < %d expected, got %d", max, value)
		end
	end
end

--[[**
	ensures value is a number where value > 0

	@returns A function that will return true iff the condition is passed
**--]]
t.numberPositive = t.numberMinExclusive(0)

--[[**
	ensures value is a number where value < 0

	@returns A function that will return true iff the condition is passed
**--]]
t.numberNegative = t.numberMaxExclusive(0)

--[[**
	ensures value is a number where min <= value <= max

	@param min The minimum to use
	@param max The maximum to use

	@returns A function that will return true iff the condition is passed
**--]]
function t.numberConstrained(min, max)
	assert(t.number(min) and t.number(max))
	local minCheck = t.numberMin(min)
	local maxCheck = t.numberMax(max)
	return function(value)
		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

--[[**
	ensures value is a number where min < value < max

	@param min The minimum to use
	@param max The maximum to use

	@returns A function that will return true iff the condition is passed
**--]]
function t.numberConstrainedExclusive(min, max)
	assert(t.number(min) and t.number(max))
	local minCheck = t.numberMinExclusive(min)
	local maxCheck = t.numberMaxExclusive(max)
	return function(value)
		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

--[[**
	ensures value is either nil or passes check

	@param check The check to use

	@returns A function that will return true iff the condition is passed
**--]]
function t.optional(check)
	assert(t.callback(check))
	return function(value)
		if value == nil then
			return true
		end
		local success, errMsg = check(value)
		if success then
			return true
		else
			return false, string.format("(optional) %s", errMsg or "")
		end
	end
end

--[[**
	matches given tuple against tuple type definition

	@param ... The type definition for the tuples

	@returns A function that will return true iff the condition is passed
**--]]
function t.tuple(...)
	local checks = {...}
	return function(...)
		local args = {...}
		for i = 1, #checks do
			local success, errMsg = checks[i](args[i])
			if success == false then
				return false, string.format("Bad tuple index #%d:\n\t%s", i, errMsg or "")
			end
		end
		return true
	end
end

--[[**
	ensures all keys in given table pass check

	@param check The function to use to check the keys

	@returns A function that will return true iff the condition is passed
**--]]
function t.keys(check)
	assert(t.callback(check))
	return function(value)
		local tableSuccess, tableErrMsg = t.table(value)
		if tableSuccess == false then
			return false, tableErrMsg or ""
		end

		for key in pairs(value) do
			local success, errMsg = check(key)
			if success == false then
				return false, string.format("bad key %s:\n\t%s", tostring(key), errMsg or "")
			end
		end

		return true
	end
end

--[[**
	ensures all values in given table pass check

	@param check The function to use to check the values

	@returns A function that will return true iff the condition is passed
**--]]
function t.values(check)
	assert(t.callback(check))
	return function(value)
		local tableSuccess, tableErrMsg = t.table(value)
		if tableSuccess == false then
			return false, tableErrMsg or ""
		end

		for key, val in pairs(value) do
			local success, errMsg = check(val)
			if success == false then
				return false, string.format("bad value for key %s:\n\t%s", tostring(key), errMsg or "")
			end
		end

		return true
	end
end

--[[**
	ensures value is a table and all keys pass keyCheck and all values pass valueCheck

	@param keyCheck The function to use to check the keys
	@param valueCheck The function to use to check the values

	@returns A function that will return true iff the condition is passed
**--]]
function t.map(keyCheck, valueCheck)
	assert(t.callback(keyCheck), t.callback(valueCheck))
	local keyChecker = t.keys(keyCheck)
	local valueChecker = t.values(valueCheck)
	return function(value)
		local keySuccess, keyErr = keyChecker(value)
		if not keySuccess then
			return false, keyErr or ""
		end

		local valueSuccess, valueErr = valueChecker(value)
		if not valueSuccess then
			return false, valueErr or ""
		end

		return true
	end
end

do
	local arrayKeysCheck = t.keys(t.integer)
	--[[**
		ensures value is an array and all values of the array match check

		@param check The check to compare all values with

		@returns A function that will return true iff the condition is passed
	**--]]
	function t.array(check)
		assert(t.callback(check))
		local valuesCheck = t.values(check)
		return function(value)
			local keySuccess, keyErrMsg = arrayKeysCheck(value)
			if keySuccess == false then
				return false, string.format("[array] %s", keyErrMsg or "")
			end

			-- all keys are sequential
			local arraySize = #value
			for key in pairs(value) do
				if key < 1 or key > arraySize then
					return false, string.format("[array] key %s must be sequential", tostring(key))
				end
			end

			local valueSuccess, valueErrMsg = valuesCheck(value)
			if not valueSuccess then
				return false, string.format("[array] %s", valueErrMsg or "")
			end

			return true
		end
	end
end

do
	local callbackArray = t.array(t.callback)
	--[[**
		creates a union type

		@param ... The checks to union

		@returns A function that will return true iff the condition is passed
	**--]]
	function t.union(...)
		local checks = {...}
		assert(callbackArray(checks))
		return function(value)
			for _, check in pairs(checks) do
				if check(value) then
					return true
				end
			end
			return false, "bad type for union"
		end
	end

	--[[**
		creates an intersection type

		@param ... The checks to intersect

		@returns A function that will return true iff the condition is passed
	**--]]
	function t.intersection(...)
		local checks = {...}
		assert(callbackArray(checks))
		return function(value)
			for _, check in pairs(checks) do
				local success, errMsg = check(value)
				if not success then
					return false, errMsg or ""
				end
			end
			return true
		end
	end
end

do
	local checkInterface = t.map(t.string, t.callback)
	--[[**
		ensures value matches given interface definition

		@param checkTable The interface definition

		@returns A function that will return true iff the condition is passed
	**--]]
	function t.interface(checkTable)
		assert(checkInterface(checkTable))
		return function(value)
			local tableSuccess, tableErrMsg = t.table(value)
			if tableSuccess == false then
				return false, tableErrMsg or ""
			end

			for key, check in pairs(checkTable) do
				local success, errMsg = check(value[key])
				if success == false then
					return false, string.format("[interface] bad value for %s:\n\t%s", key, errMsg or "")
				end
			end
			return true
		end
	end

	--[[**
		ensures value matches given interface definition strictly

		@param checkTable The interface definition

		@returns A function that will return true iff the condition is passed
	**--]]
	function t.strictInterface(checkTable)
		assert(checkInterface(checkTable))
		return function(value)
			local tableSuccess, tableErrMsg = t.table(value)
			if tableSuccess == false then
				return false, tableErrMsg or ""
			end

			for key, check in pairs(checkTable) do
				local success, errMsg = check(value[key])
				if success == false then
					return false, string.format("[interface] bad value for %s:\n\t%s", key, errMsg or "")
				end
			end

			for key in pairs(value) do
				if not checkTable[key] then
					return false, string.format("[interface] unexpected field '%s'", key)
				end
			end

			return true
		end
	end
end

--[[**
	ensure value is an Instance and it's ClassName matches the given ClassName

	@param className The class name to check for

	@returns A function that will return true iff the condition is passed
**--]]
function t.instance(className)
	assert(t.string(className))
	return function(value)
		local instanceSuccess, instanceErrMsg = t.Instance(value)
		if not instanceSuccess then
			return false, instanceErrMsg or ""
		end

		if value.ClassName ~= className then
			return false, string.format("%s expected, got %s", className, value.ClassName)
		end

		return true
	end
end

--[[**
	ensure value is an Instance and it's ClassName matches the given ClassName by an IsA comparison

	@param className The class name to check for

	@returns A function that will return true iff the condition is passed
**--]]
function t.instanceIsA(className)
	assert(t.string(className))
	return function(value)
		local instanceSuccess, instanceErrMsg = t.Instance(value)
		if not instanceSuccess then
			return false, instanceErrMsg or ""
		end

		if not value:IsA(className) then
			return false, string.format("%s expected, got %s", className, value.ClassName)
		end

		return true
	end
end

--[[**
	ensures value is an enum of the correct type

	@param enum The enum to check

	@returns A function that will return true iff the condition is passed
**--]]
function t.enum(enum)
	assert(t.Enum(enum))
	return function(value)
		local enumItemSuccess, enumItemErrMsg = t.EnumItem(value)
		if not enumItemSuccess then
			return false, enumItemErrMsg
		end

		if value.EnumType == enum then
			return true
		else
			return false, string.format("enum of %s expected, got enum of %s", tostring(enum), tostring(value.EnumType))
		end
	end
end

do
	local checkWrap = t.tuple(t.callback, t.callback)

	--[[**
		wraps a callback in an assert with checkArgs

		@param callback The function to wrap
		@param checkArgs The functon to use to check arguments in the assert

		@returns A function that first asserts using checkArgs and then calls callback
	**--]]
	function t.wrap(callback, checkArgs)
		assert(checkWrap(callback, checkArgs))
		return function(...)
			assert(checkArgs(...))
			return callback(...)
		end
	end
end

--[[**
	asserts a given check

	@param check The function to wrap with an assert

	@returns A function that simply wraps the given check in an assert
**--]]
function t.strict(check)
	return function(...)
		assert(check(...))
	end
end

return t
