Generics
Generic type parameters let you write functions and classes that work with any type while preserving type information through calls.
Generic functions
Declare type parameters with @generic:
---@generic T
---@param value T
---@return T
function identity(value) return value end
local s = identity("hello") -- s: string
local n = identity(42) -- n: numberThe LS infers T from the argument and propagates it to the return type. No explicit type annotation needed at the call site.
Constrained generics
Restrict a type parameter to a specific class or type:
---@generic T: Frame
---@param frame T
---@return T
function configure(frame)
frame:SetPoint("CENTER")
return frame
end
local f = configure(CreateFrame("Frame")) -- T is Frame
configure("not a frame") -- type-mismatch: string doesn't satisfy FrameMultiple type parameters
---@generic K, V
---@param key K
---@param value V
---@return table<K, V>
function makePair(key, value)
return { [key] = value }
endBacktick annotations (`T`)
When a parameter is a string literal that names a class, use backticks to resolve it as a type:
---@generic T
---@param name `T`
---@return T
function CreateObject(name) return {} end
local dog = CreateObject("Dog") -- T resolves to the Dog classThis is how WoW's CreateFrame works — the first argument is a string like "Frame" or "Button", and the return type matches.
Parameterized classes
Classes can declare type parameters that their methods and fields reference:
---@class Container<T>
---@field items T[]
local Container = {}
---@param item T
function Container:Add(item) end
---@return T
function Container:Get() endWhen a value is typed with a concrete parameter, fields and methods resolve accordingly:
---@type Container<string>
local names = {}
names:Add("Arthas") -- T is string
local n = names:Get() -- n: string
names.items -- string[]Methods on a parameterized class automatically inherit the class-level type parameters — no need to redeclare them with @generic:
-- Just use T directly
---@param value T
function MyClass:Set(value) endType parameter constraints
---@class NumericBox<T: number|string>
local NumericBox = {}
---@type NumericBox<string> -- OK
local a = {}
---@type NumericBox<boolean> -- generic-constraint-mismatch
local b = {}Bracket-index fields
Type parameters work in bracket-index field declarations:
---@class TypedMap<K, V>
---@field [K] V
---@type TypedMap<string, number>
local scores = {}
local val = scores["player1"] -- val: numberFunction-type projections
When a generic class wraps a function type, params<F> and returns<F> let methods reference the function's shape:
---@class EventRegistry<F>
local EventRegistry = {}
---@generic F
---@param self EventRegistry<F>
---@param key string
---@param ... params<F>
---@return returns<F>
function EventRegistry:Fire(key, ...) end---@type EventRegistry<fun(name: string, count: number): boolean>
local reg = {}
local ok = reg:Fire("event", "hello", 5) -- ok: boolean
reg:Fire("event", 42, 5) -- type-mismatch: position 1 expects stringparams<F>is only valid in the vararg slot (@param ... params<F>)returns<F>resolves to F's return type
How inference works
The LS infers generic bindings from multiple sources:
- Direct argument types — if
@param x T, and you pass astring, T = string - Structural matching —
T[]extracts T from an array's element type;table<K,V>extracts from map types - Backtick resolution —
`T`resolves a string literal as a class name - Function-type extraction —
fun(): Textracts T from a callback's return type - Receiver type_args — for
@class Foo<T>, calling a method on---@type Foo<string>binds T from the receiver
Inference runs per-call and doesn't persist — each call site resolves its own bindings independently.
