mcpField
L
luau-type-expert

luau-type-expert

github.com/dig1tDevelopment5291 installsUpdated 5w ago
Repository

About luau-type-expert

Luau Type Expert is a guidance skill for developers writing type-safe Luau code, especially in projects that use strict checking. It explains type syntax, narrowing, generics, common errors, lint rules, and performance-aware typing.

Key features

  1. 01Explain strict type-checking modes
  2. 02Cover annotations and type aliases
  3. 03Guide type narrowing and casts
  4. 04Describe generics and metatables
  5. 05Troubleshoot common type errors
  6. 06Reference lint and performance rules

Use cases

  1. 01Enable strict checking in Luau files
  2. 02Resolve type-checking errors
  3. 03Design reusable generic types
  4. 04Review code for lint and performance patterns

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/dig1t/skills --skill luau-type-expert

After installing, type/luau-type-expertin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
api_reference.md
common-errors.md
lint-rules.md
performance.md

Details

From the project README

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:

ModeBehavior
--!nocheckDisables type checking entirely
--!nonstrictUnknown types become any (default)
--!strictFull type tracking, catches mismatches

Syntax Essentials

Standard annotation syntax (variables, function params/returns, optionals ?, multiple returns, variadics, table types, aliases) is covered in references/api_reference.md. The parts worth remembering:

-- Export for cross-module use
export type ItemRecord = { id: string, quantity: number }

-- Function type
type Callback = (player: Player, data: any) -> boolean

-- Generic aliases
type Result<T, E> = { ok: true, value: T } | { ok: false, error: E }
type Map<K, V> = { [K]: V }

-- Literal unions (discriminated)
type Status = "pending" | "active" | "completed"

-- Intersection: value has all these properties
type Person = Named & Aged

-- Function intersection (overloads)
type Stringify = ((n: number) -> string) & ((b: boolean) -> string)

Type Narrowing (Refinements)

Luau narrows types in conditional blocks via type(), typeof() (Roblox instances), truthiness, and equality checks:

local function process(value: string | number)
    if type(value) == "string" then
        print(value:upper())  -- value: string here
    else
        print(value + 1)      -- value: number here
    end
end

local function safePrint(msg: string?)
    if msg then
        print(msg)  -- msg: string (not nil)
    end
end

Early return preserves refinements:

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:

-- 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

-- 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 }

Metatables and OOP

--!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 for detailed error solutions.

Quick fixes:

ErrorFix
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 compatibleMatch property types exactly
W_001: Unknown requireUse proper require path aliases

luau-lsp CLI Usage

# 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

{
    "languageMode": "strict",
    "lint": {
        "LocalShadow": "disabled",
        "ImportUnused": "enabled"
    },
    "aliases": {
        "@shared": "src/Shared",
        "@server": "src/Server"
    }
}

Performance-Aware Typing

See 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 for all 28 lint rules.

Critical rules:

  • UnknownGlobal - Catches typos
  • LocalUnused - Dead code
  • ImplicitReturn - Inconsistent returns
  • UninitializedLocal - Use before assign

Comments

Markdown supported