This is a simple runtime type checker that works for modules.
Here’s a test script that will allow you to test and see how module works:
local StrictModule = require(game:GetService("ReplicatedStorage"):WaitForChild("strict"))
-- Define a module with functions to test
local TestModule = {}
-- Function 1: Add two numbers
function TestModule.addNumbers(a, b)
return a + b
end
-- Function 2: Greet a player (with optional greeting argument)
function TestModule.greetPlayer(player, greeting)
greeting = greeting or "Hello"
return greeting .. ", " .. player.Name
end
-- Function 3: Teleport a player to a part (optional destination)
function TestModule.teleportPlayer(player, destination)
destination = destination or workspace:FindFirstChild("SpawnLocation") or workspace:WaitForChild("Part")
if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
player.Character.HumanoidRootPart.CFrame = destination.CFrame
end
end
-- Wrap the module with strict type checking (module table is frozen after that)
TestModule = StrictModule({
addNumbers = {
func = TestModule.addNumbers,
types = { "number", "number" } -- Both arguments must be numbers
},
greetPlayer = {
func = TestModule.greetPlayer,
types = { "Player", "string?" } -- First argument must be a Player, second is optional string
},
teleportPlayer = {
func = TestModule.teleportPlayer,
types = { "Player", "BasePart?" } -- First argument must be a Player, second is optional BasePart
}
})
-- Test the functions
local player = game.Players.LocalPlayer
-- Test 1: addNumbers (correct usage)
print("Test 1 (Correct):", TestModule.addNumbers(5, 10)) -- Output: 15
-- Test 2: addNumbers (incorrect usage)
local success, err = pcall(function()
TestModule.addNumbers(5, "10") -- Error: Invalid type for argument 2. Expected number, got string.
end)
if not success then
print("Test 2 (Error):", err)
end
-- Test 3: greetPlayer (correct usage)
print("Test 3 (Correct):", TestModule.greetPlayer(player)) -- Output: Hello, [PlayerName]
print("Test 3 (Correct):", TestModule.greetPlayer(player, "Hi")) -- Output: Hi, [PlayerName]
-- Test 4: greetPlayer (incorrect usage)
local success, err = pcall(function()
TestModule.greetPlayer("NotAPlayer") -- Error: Invalid type for argument 1. Expected Player, got string.
end)
if not success then
print("Test 4 (Error):", err)
end
-- Test 5: teleportPlayer (correct usage)
local part = workspace:FindFirstChild("Part") or Instance.new("Part", workspace)
part.Name = "Part"
part.Position = Vector3.new(0, 10, 0)
TestModule.teleportPlayer(player, part) -- Teleports player to the part
print("Test 5 (Correct): Player teleported to part.")
-- Test 6: teleportPlayer (incorrect usage)
local success, err = pcall(function()
TestModule.teleportPlayer("NotAPlayer", part) -- Error: Invalid type for argument 1. Expected Player, got string.
end)
if not success then
print("Test 6 (Error):", err)
end
-- Test 7: teleportPlayer (optional argument)
TestModule.teleportPlayer(player) -- Teleports player to default destination (SpawnLocation or Part)
print("Test 7 (Correct): Player teleported to default destination.")
Yes I know that the difference is that it produces errors when a type is wrong, but isn’t a warning in the script enough to let you know something is wrong?
The only time I’d see an error for a wrong type being necessary is if the passed parameter type isn’t known at runtime so a check is required, and a warning is probably the better option unless the following code will have a knock-on effect like accessing a datastore.
From what I can see, TypeScript is pretty similar to --!strict mode for Luau. Sure, there are cases where you’ll want to confirm types like I mentioned, another example being remote events. But my point is what advantage does your module provide over this:
function TestModule.addNumbers(a: number, b: number): number
if (type(a) ~= "number" or type(b) ~= "number") then
error(`The expected parameters for addNumbers were 'number, number', got {type(a), type(b)}`)
end
return a + b
end
I don’t mean to undermine your effort since you put time into this and I might come across as blunt, but what makes this module stand out from the regular methods of type checking?
ugh, i would pour some hate on not using assert() right here: if (type(a) ~= "number" or type(b) ~= "number") then
But since you’re a modeler fine.
I made this module to not code each assert individually.
The reason I don’t use assert is because when you define an assert function, any string concatenation in the second parameter is done whether the condition is true or false, where as using an if statement only does the string concatenation when the condition fails.
Having a utility module to save repeating yourself isn’t a bad idea, but it seems like your solution is a bit more cumbersome than just putting in the checks.