luau-type-expert
Professional Luau type-checking and clean code specialist for Roblox development. Use this skill when: - Writing or reviewing Luau code that needs proper type annotations - Fixing type errors from luau-lsp or luau-analyze - Converting untyped Lua/Luau to strictly typed code - Designing type-safe APIs, modules, or data structures - Understanding Luau type system features (generics, unions, intersections, refinements) - Optimizing code for luau-lsp compatibility - Setting up --!strict mode compliance - Creating type definitions (.d.luau files) - Debugging "Type X is not compatible with Y" errors - Writing metatables with proper type support Triggers: "type error", "type annotation", "luau types", "strict mode", "--!strict", "type checking", "luau-lsp", "type mismatch", "generic type", "union type", "type narrowing", "type refinement", "type cast", "export type", "typeof"
What this skill does
# Luau Type Expert
Expert guidance for writing type-safe, clean Luau code that passes strict type checking.
## Type Modes
Always use `--!strict` at file top. Three modes exist:
| Mode | Behavior |
|------|----------|
| `--!nocheck` | Disables type checking entirely |
| `--!nonstrict` | Unknown types become `any` (default) |
| `--!strict` | Full type tracking, catches mismatches |
## Type Annotation Syntax
```lua
--!strict
-- Variables
local count: number = 0
local name: string = "Player"
local active: boolean = true
-- Functions
local function add(a: number, b: number): number
return a + b
end
-- Optional parameters
local function greet(name: string, title: string?): string
return (title or "") .. name
end
-- Multiple returns
local function divmod(a: number, b: number): (number, number)
return math.floor(a / b), a % b
end
-- Variadic
local function sum(...: number): number
local total = 0
for _, v in {...} do total += v end
return total
end
```
## Type Aliases
```lua
-- Simple alias
type UserId = number
-- Table types
type PlayerData = {
coins: number,
level: number,
inventory: { string },
}
-- Export for cross-module use
export type ItemRecord = {
id: string,
quantity: number,
createdAt: number,
}
-- Function type
type Callback = (player: Player, data: any) -> boolean
-- Generic types
type Result<T, E> = { ok: true, value: T } | { ok: false, error: E }
type Array<T> = { T }
type Map<K, V> = { [K]: V }
```
## Union and Intersection Types
```lua
-- Union: value is one of these types
type StringOrNumber = string | number
type OptionalString = string | nil -- same as string?
-- Literal unions (discriminated)
type Status = "pending" | "active" | "completed"
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"
-- Intersection: value has all these properties
type Named = { name: string }
type Aged = { age: number }
type Person = Named & Aged -- has both name and age
-- Function intersection (overloads)
type Stringify = ((n: number) -> string) & ((b: boolean) -> string)
```
## Type Narrowing (Refinements)
Luau automatically narrows types in conditional blocks:
```lua
local function process(value: string | number)
if type(value) == "string" then
-- value: string here
print(value:upper())
else
-- value: number here
print(value + 1)
end
end
-- typeof() for Roblox instances
local function handlePart(obj: Instance)
if typeof(obj) == "BasePart" then
-- obj: BasePart here
obj.Anchored = true
end
end
-- Truthy narrowing
local function safePrint(msg: string?)
if msg then
-- msg: string (not nil)
print(msg)
end
end
-- Equality narrowing
local function handleStatus(status: "pending" | "done")
if status == "pending" then
-- status: "pending"
else
-- status: "done"
end
end
```
**Early return preserves refinements:**
```lua
local function requirePlayer(player: Player?): Player
if not player then
error("Player required")
end
-- player: Player (narrowed after early return)
return player
end
```
## Type Casts
Use `::` to override inferred types:
```lua
-- Cast to specific type
local data = {} :: { string }
table.insert(data, "hello") -- OK
table.insert(data, 123) -- Error: number not string
-- Cast result of expression
local id = tostring(123) :: string
-- Cast for API returns
local part = workspace:FindFirstChild("Part") :: Part?
```
**Cast rules:** One operand must be subtype of the other, or `any`.
## Generics
```lua
-- Generic function
local function first<T>(arr: { T }): T?
return arr[1]
end
-- Generic with constraint
local function clone<T>(obj: T & {}): T
local copy = {}
for k, v in obj :: any do
copy[k] = v
end
return copy :: T
end
-- Generic type alias
type Container<T> = {
value: T,
set: (self: Container<T>, value: T) -> (),
get: (self: Container<T>) -> T,
}
-- Multiple type parameters
type Pair<K, V> = { key: K, value: V }
```
## Table Types
```lua
-- Array (sequential integer keys)
type StringArray = { string }
type NumberList = { number }
-- Dictionary (string keys)
type Config = { [string]: any }
type Scores = { [string]: number }
-- Mixed table
type Player = {
name: string, -- required field
score: number,
items: { string }, -- array field
metadata: { [string]: any }?, -- optional dictionary
}
-- Exact table (no extra keys allowed in strict)
type Point = { x: number, y: number }
```
## Metatables and OOP
```lua
--!strict
export type Vector2 = {
x: number,
y: number,
}
type Vector2Impl = {
__index: Vector2Impl,
new: (x: number, y: number) -> Vector2,
add: (self: Vector2, other: Vector2) -> Vector2,
magnitude: (self: Vector2) -> number,
}
local Vector2: Vector2Impl = {} :: Vector2Impl
Vector2.__index = Vector2
function Vector2.new(x: number, y: number): Vector2
return setmetatable({ x = x, y = y }, Vector2) :: Vector2
end
function Vector2:add(other: Vector2): Vector2
return Vector2.new(self.x + other.x, self.y + other.y)
end
function Vector2:magnitude(): number
return math.sqrt(self.x^2 + self.y^2)
end
return Vector2
```
## Common Type Errors and Fixes
See [references/common-errors.md](references/common-errors.md) for detailed error solutions.
**Quick fixes:**
| Error | Fix |
|-------|-----|
| `Type 'X' could not be converted into 'Y'` | Add explicit cast `:: Y` or fix the type |
| `Unknown global 'X'` | Import module or declare global type |
| `Property 'X' is not compatible` | Match property types exactly |
| `W_001: Unknown require` | Use proper require path aliases |
## luau-lsp CLI Usage
```bash
# Basic analysis
luau-lsp analyze src/
# With sourcemap for Roblox
luau-lsp analyze --sourcemap=sourcemap.json src/
# With definitions
luau-lsp analyze --definitions:@roblox=globalTypes.d.luau src/
# Disable all FFlags
luau-lsp analyze --no-flags-enabled src/
```
## .luaurc Configuration
```json
{
"languageMode": "strict",
"lint": {
"LocalShadow": "disabled",
"ImportUnused": "enabled"
},
"aliases": {
"@shared": "src/Shared",
"@server": "src/Server"
}
}
```
## Performance-Aware Typing
See [references/performance.md](references/performance.md) for performance patterns.
**Key points:**
- Use `table.field` not `table["field"]`
- Keep metatables shallow (direct `__index` to table)
- Localize builtins: `local max = math.max`
- Avoid `getfenv`/`setfenv` (deoptimizes)
- Use `table.create(n)` for known sizes
## Lint Rules Reference
See [references/lint-rules.md](references/lint-rules.md) for all 28 lint rules.
**Critical rules:**
- `UnknownGlobal` - Catches typos
- `LocalUnused` - Dead code
- `ImplicitReturn` - Inconsistent returns
- `UninitializedLocal` - Use before assign
Related in Writing & Docs
jax-development
IncludedUse this skill when the user is writing, debugging, profiling, refactoring, reviewing, benchmarking, parallelising, exporting, or explaining JAX code, or when they mention JAX, jax.numpy, jit, grad, value_and_grad, vmap, scan, lax, random keys, pytrees, jax.Array, sharding, Mesh, PartitionSpec, NamedSharding, pmap, shard_map, Pallas, XLA, StableHLO, checkify, profiler, or the JAX repo. It helps turn NumPy or PyTorch-style code into pure functional JAX, fix tracer/control-flow/shape/PRNG bugs, remove recompiles and host-device syncs, choose transforms and sharding strategies, inspect jaxpr/lowering/IR, and benchmark compiled code correctly.
nature-article-writer
IncludedDrafts, rewrites, diagnostically critiques, and style-calibrates primary research manuscripts for Nature and Nature Portfolio journals. Use when the user wants a Nature-style title, summary paragraph or abstract, introduction, results, discussion, methods, figure legends, presubmission enquiry, cover letter, reviewer response, or when a scientific draft sounds generic, jargon-heavy, structurally weak, or AI-ish and needs precise, broad-reader-friendly prose without inventing data, analyses, or references. Best for primary research articles and letters rather than reviews or press releases unless explicitly adapting one.
deckrd
IncludedDocument-driven framework that derives requirements, specifications, implementation plans, and executable tasks from goals through structured AI dialogue. Use when user says "write requirements", "create spec", "plan implementation", "derive tasks", "structure this feature", "break down into tasks", or "document this module". Also use for reverse engineering existing code into docs (/deckrd rev). Do NOT use for direct code writing — use /deckrd-coder after tasks are generated. Do NOT use when the user only wants to run or fix existing code without planning.
clinical-decision-support
IncludedGenerate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
handling-sf-data
IncludedSalesforce data operations with 130-point scoring. Use this skill to create, update, delete, bulk import/export, generate test data, and clean up org records using sf CLI and anonymous Apex. TRIGGER when: user creates test data, performs bulk import/export, uses sf data CLI commands, needs data factory patterns for Apex tests, or needs to seed/clean records in a Salesforce org. DO NOT TRIGGER when: SOQL query writing only (use querying-soql), Apex test execution (use running-apex-tests), or metadata deployment (use deploying-metadata).
accelint-ac-to-playwright
IncludedConvert and validate acceptance criteria for Playwright test automation. Use when user asks to (1) review/evaluate/check if AC are ready for automation, (2) assess if AC can be converted as-is, (3) validate AC quality for Playwright, (4) turn AC into tests, (5) generate tests from acceptance criteria, (6) convert .md bullets or .feature Gherkin files to Playwright specs, (7) create test automation from requirements. Handles both bullet-style markdown and Gherkin syntax with JSON test plan generation and validation.