I’m trying to create a type-safe fluent/chaining API in Luau where each chained call accumulates new types using intersections.
Example:
export type ContextProperties<A = {[string]: ActionInstance?}> = {
Context: InputContext,
Name: string,
Priority: number,
Enabled: boolean,
Sink: boolean,
Actions: A,
}
export type InputInstance<A = {[string]: ActionInstance?}> = {
CreateContext: (Name: string) -> InputInstance<A>,
CreateAction: <N>(
self: InputInstance<A>,
Name: N & string,
Cooldown: number?
) -> InputInstance<A & {[N]: ActionInstance}>,
Destroy: (self: any) -> ()
} & ContextProperties<A>
Usage:
local ctx =
CreateContext()
:CreateAction("Jump")
:CreateAction("Dash")
The goal is for Luau to infer:
ctx.Actions.Jump
ctx.Actions.Dash
correctly after chaining.
However, Luau throws type expansion / recursive type errors when using:
A & {[N]: Action}
inside the recursive generic alias.
I discovered that the issue disappears if I remove the intersection accumulation and simply return:
InputInstance<A>
which makes me think the problem is related to recursive generic intersection expansion in fluent APIs.
I also noticed that moving the generic accumulation out of the type alias and into implementation casts (:: any) avoids the error.
Is this a current limitation of Luau’s type system, or is there a recommended pattern for implementing type-safe builder/fluent APIs with accumulating generics?
If you have any questions. Or you need full version of my type module. Feel free to ask!
Have a nice day:).