1.12. Type Annotations in Quirrel
Quirrel extends the Squirrel language with optional static type annotations that are enforced at runtime. Type checks occur during function calls, return statements, assignments, and destructuring operations, providing safety without requiring a separate compilation step.
1.12.1. Basic Type Syntax
1.12.1.1. Primitive Types
Quirrel supports the following built-in types:
int- Signed integerfloat- Floating-point numbernumber- Union ofint | float(numeric values)bool- Boolean values (true/false)string- Immutable string valuesnull- Null value type
1.12.1.2. Complex Types
table- Associative array / objectarray- Ordered sequence containerfunction- Callable function or closureuserdata- Opaque C/C++ data referencegenerator- Coroutine/generator objectuserpointer- Raw pointer valuethread- Execution threadinstance- Class instanceclass- Class definition objectweakref- Weak reference to an objectany- Accepts any type (opt-out of type checking)
1.12.1.3. Type Unions
Combine multiple types using the pipe operator (|):
local value: int|float = 42
local maybeString: string|null = null
local numeric: number = 3.14 // equivalent to int|float
Parentheses may be used for clarity in complex unions:
local x: (int | null) = null
local y: (string | table | null) = {}
1.12.2. Function Type Annotations
1.12.2.1. Parameters and Return Types
Annotate parameters and return values with type specifiers:
function add(x: int, y: int): int {
return x + y
}
function toString(value: any): string {
return value.tostring()
}
1.12.2.2. Nullable Parameters with Defaults
Combine unions with default values for optional parameters:
function greet(name: string|null = null): string {
return name ? "Hello " + name : "Hello guest"
}
1.12.2.3. Vararg Type Annotations
Specify the expected type for variable arguments using ...:
function sum(first: number, ...: number): number {
local total = first
foreach (v in vargv) total += v
return total
}
1.12.2.4. Lambda/Anonymous Functions
Type annotations apply to lambdas using the @ syntax:
let multiply = @(x: int, y: int): int x * y
1.12.3. Variable Type Annotations
1.12.3.1. Local and Let Bindings
Annotate variables at declaration site:
local count: int = 0
let pi: float = 3.14159
local config: table|array = {timeout = 30}
Type checks occur on assignment:
local id: int = "not a number" // Runtime type error
1.12.4. Destructuring with Types
1.12.4.1. Object Destructuring
Annotate individual properties during destructuring:
local { name: string, age: int|null = 25 } = userData
// With explicit type unions
local { x: int, y: string|int|null } = point
1.12.4.2. Array Destructuring
Specify types for array elements:
local [first: int, second: string] = [42, "hello"]
// Mixed annotated/unannotated elements
local [head: int, value1, value2] = [1, 2, 3]
// Complex unions in arrays
local [value: (int | null), flag: bool] = [null, true]
1.12.4.3. Nested Destructuring in Parameters
Functions can destructure parameters with full type annotations:
function createUser([name: string, age: int], {active: bool = true}) {
// ...
}
function process(
[handler: function|null = null, config: table],
{timeout: int|float = 5.0},
...
) {
// ...
}
1.12.5. Runtime Type Checking Behavior
Quirrel performs dynamic type validation at these points:
Function calls - Parameters are checked against declared types before execution
Return statements - Return values are validated against the function’s return type
Assignments - Values are checked when assigned to typed variables
Destructuring - Extracted values are validated against property/element types
All checks occur at runtime with immediate exceptions on mismatch:
function strict(x: int): int { return x }
strict("wrong") // Throws: type mismatch in parameter 'x'
1.12.6. Best Practices
Type checks add minor runtime overhead; avoid in ultra-hot loops if unneeded
Use
numberinstead ofint|floatfor numeric parameters accepting both typesUse
anysparingly—only when truly necessary for dynamic behaviorCombine defaults with nullable types for ergonomic optional parameters:
function fetch(url: string, timeout: float|null = 30.0) { ... }