API Reference¶
Auto-generated API documentation from source code.
Main Module¶
typestates
¶
Compile-time state machine verification for Nim.
This library enforces state machine protocols at compile time through Nim's type system. Programs that compile have been verified to contain no invalid state transitions.
This approach is known as correctness by construction: invalid states become unrepresentable rather than checked at runtime.
Exports:
typestatemacro - Declare states and transitions{.transition.}pragma - Mark and validate transition procs{.notATransition.}pragma - Mark non-transition procs
typestateImpl ¶
macro typestateImpl(name: untyped; body: untyped; inferredConstraints: static[seq[tuple[name: string, kind: string,
constraint: string]]]): untyped
Internal implementation that receives inferred constraints.
This macro is called after constraint inference to generate the typestate with properly constrained generic parameters.
Parameters
-
name(untyped) -
body(untyped) -
inferredConstraints(static[seq[tuple[name: string, kind: string, constraint: string]]])
Returns
untyped
typestate ¶
macro typestate(name: untyped; body: untyped): untyped
Define a typestate with states and valid transitions.
The typestate block declares:
- states: The distinct types that represent each state
- transitions: Which state changes are allowed
Basic syntax:
typestate File:
states Closed, Open, Errored
transitions:
Closed -> Open | Errored # Branching
Open -> Closed
* -> Closed # Wildcard
Generic typestates with constraint inference:
type
Base[N: static int] = object
StateA[N: static int] = distinct Base[N]
StateB[N: static int] = distinct Base[N]
# N's constraint is automatically inferred from StateA/StateB
typestate Base[N]:
states StateA[N], StateB[N]
transitions:
StateA -> StateB
What it generates:
FileStateenum withfsClosed,fsOpen,fsErroredFileStatesunion type for generic procsstate()procs for runtime inspection
Transition syntax:
A -> B- Simple transitionA -> B | C- Branching (can go to B or C)* -> X- Wildcard (any state can go to X)
See also: {.transition.} pragma for implementing transitions
To opt into state-aware error messages on transition misuse, call
verifyTypestates() at the bottom of your module. See
docs/guide/error-handling.md for details.
Parameters
-
name(untyped) – The base type name (must match your type definition) -
body(untyped) – The states and transitions declarations
Returns
untyped – Generated helper types (enum, union, state procs)
Submodules¶
Types¶
Core type definitions.
types
¶
Core type definitions for the typestate system.
This module defines the internal representation of typestates, states, and transitions used during compile-time validation.
These types are primarily used internally by the typestate macro and
{.transition.} pragma. Most users won't interact with them directly.
extractBaseName ¶
proc extractBaseName(stateRepr: string): string
Extract the base type name from a state repr string.
Used for comparing state names when generic parameters may differ:
"Empty"->"Empty""Empty[T]"->"Empty""Container[K, V]"->"Container""ref Closed"->"Closed"
Parameters
-
stateRepr(string) – Full state repr string
Returns
string – Base name without generic parameters
State
¶
type State = object
Represents a single state in a typestate machine.
Each state corresponds to a distinct type that the user defines. States can be simple identifiers or generic types.
Examples:
- Simple:
name="Closed",fullRepr="Closed" - Generic:
name="Container",fullRepr="Container[T]" - Ref type:
name="Closed",fullRepr="ref Closed"
Fields
-
namestring -
fullReprstring -
typeNameNimNode
Transition
¶
type Transition = object
Represents a valid state transition in the typestate graph.
Transitions define which state changes are allowed. They can be:
- Simple:
Closed -> Open(one source, one destination) - Branching:
Closed -> (Open | Errored) as OpenResult(one source, multiple destinations) - Wildcard:
* -> Closed(any state can transition to Closed)
Example:
Fields
-
fromStatestring -
toStatesseq[string] -
branchTypeNamestring– Empty for non-branching, required for branching -
branchTypeNodeNimNode– Raw AST node for codegen (supports generics like Result[T]) -
isWildcardbool -
declaredAtLineInfo
Bridge
¶
type Bridge = object
Represents a cross-typestate bridge declaration.
Bridges allow terminal states of one typestate to transition into states of a completely different typestate. They enable modeling resource transformation, wrapping, and protocol handoff.
Example:
# In AuthFlow typestate:
# bridges:
# Authenticated -> Session.Active
# Becomes:
Bridge(
fromState: "Authenticated",
toModule: "", # empty for same-module
toTypestate: "Session",
toState: "Active",
fullDestRepr: "Session.Active"
)
# With module prefix:
# bridges:
# SourceState -> othermodule.Session.Active
# Becomes:
Bridge(
fromState: "SourceState",
toModule: "othermodule",
toTypestate: "Session",
toState: "Active",
fullDestRepr: "othermodule.Session.Active"
)
Fields
-
fromStatestring -
toModulestring -
toTypestatestring -
toStatestring -
fullDestReprstring -
declaredAtLineInfo
TypestateGraph
¶
type TypestateGraph = object
The complete graph of states and transitions for a typestate.
This is the central data structure that holds all information about a typestate declaration. It is built by the parser from the DSL syntax and stored in the compile-time registry for later validation.
Example:
Creates a TypestateGraph with name="File", two states, and two transitions.
Fields
-
namestring -
typeParamsseq[NimNode]– Generic params: @[T] or @[K, V] or @[] -
statesTable[string, State] -
transitionsseq[Transition] -
bridgesseq[Bridge] -
strictTransitionsbool -
consumeOnTransitionbool– If true, states cannot be copied -
inheritsFromRootObjbool– If true, skip static generic bug check -
opaqueStatesbool– Opt-in cast-bypass lint (CLI-side only) -
initialStatesseq[string]– States that cannot be transitioned TO -
terminalStatesseq[string]– States that cannot transition FROM -
declaredAtLineInfo -
declaredInModulestring
== ¶
proc ==(a, b: Transition): bool
Compare two transitions for equality.
Two transitions are equal if they have the same source state, destination states, and wildcard status. The declaration location is not considered for equality.
Parameters
-
a(Transition) – First transition to compare -
b(Transition) – Second transition to compare
Returns
bool – `true` if transitions are semantically equivalent
== ¶
proc ==(a, b: Bridge): bool
Compare two bridges for equality.
Two bridges are equal if they have the same source state, destination module, destination typestate, and destination state. The declaration location is not considered for equality.
Parameters
-
a(Bridge) – First bridge to compare -
b(Bridge) – Second bridge to compare
Returns
bool – `true` if bridges are semantically equivalent
hasTransition ¶
proc hasTransition(graph: TypestateGraph; fromState, toState: string): bool
Check if a transition from fromState to toState is valid.
This proc checks both explicit transitions and wildcard transitions.
A transition is valid if there's an explicit transition
fromState -> toState, or there's a wildcard transition * -> toState.
Comparisons use base names to support generic types:
- hasTransition(g, "Empty", "Full") matches Empty[T] -> Full[T]
Example:
Parameters
-
graph(TypestateGraph) – The typestate graph to check -
fromState(string) – The source state name (base name or full repr) -
toState(string) – The destination state name (base name or full repr)
Returns
bool – `true` if the transition is allowed, `false` otherwise
validDestinations ¶
proc validDestinations(graph: TypestateGraph; fromState: string): seq[string]
Get all valid destination states from a given state.
This includes both explicit transitions from fromState and
destinations reachable via wildcard transitions.
Comparisons use base names to support generic types. Returns base names for clearer error messages.
Example:
Parameters
-
graph(TypestateGraph) – The typestate graph to query -
fromState(string) – The source state to check transitions from
Returns
seq[string] – A sequence of state base names that can be transitioned to
hasBridge ¶
proc hasBridge(graph: TypestateGraph; fromState, toModule, toTypestate, toState: string): bool
Check if a bridge from fromState to toModule.toTypestate.toState is declared.
Comparisons use base names to support generic types. Module matching: both empty = match, both non-empty and equal = match.
Example:
# Given: Authenticated -> Session.Active
graph.hasBridge("Authenticated", "", "Session", "Active") # true (same module)
# Given: StateA -> othermodule.Session.Active
graph.hasBridge("StateA", "othermodule", "Session", "Active") # true
graph.hasBridge("StateA", "", "Session", "Active") # false (module mismatch)
Parameters
-
graph(TypestateGraph) – The typestate graph to check -
fromState(string) – The source state name -
toModule(string) – The destination module name (empty string for same module) -
toTypestate(string) – The destination typestate name -
toState(string) – The destination state name
Returns
bool – `true` if the bridge is declared, `false` otherwise
hasBridge ¶
proc hasBridge(graph: TypestateGraph; fromState, toTypestate, toState: string): bool
Check if a bridge from fromState to any *.toTypestate.toState is declared.
This version matches bridges regardless of the module prefix, making it suitable for validation where we only know the destination typestate and state.
Comparisons use base names to support generic types.
Example:
Parameters
-
graph(TypestateGraph) – The typestate graph to check -
fromState(string) – The source state name -
toTypestate(string) – The destination typestate name -
toState(string) – The destination state name
Returns
bool – `true` if the bridge is declared, `false` otherwise
validBridges ¶
proc validBridges(graph: TypestateGraph; fromState: string): seq[string]
Get all valid bridge destinations from a given state.
Returns dotted notation strings like "Session.Active" or "module.Session.Active".
Example:
Parameters
-
graph(TypestateGraph) – The typestate graph to query -
fromState(string) – The source state to check bridges from
Returns
seq[string] – A sequence of dotted destination names
isInitialState ¶
proc isInitialState(graph: TypestateGraph; stateName: string): bool
Check if a state is declared as initial.
Initial states can only be constructed, not transitioned to. Comparisons use base names to support generic types.
Example:
Parameters
-
graph(TypestateGraph) – The typestate graph to check -
stateName(string) – The state name to check
Returns
bool – `true` if the state is initial, `false` otherwise
isTerminalState ¶
proc isTerminalState(graph: TypestateGraph; stateName: string): bool
Check if a state is declared as terminal.
Terminal states are end states with no outgoing transitions. Comparisons use base names to support generic types.
Example:
Parameters
-
graph(TypestateGraph) – The typestate graph to check -
stateName(string) – The state name to check
Returns
bool – `true` if the state is terminal, `false` otherwise
Parser¶
DSL parser for typestate blocks.
parser
¶
Parser for the typestate DSL.
This module transforms the AST from a typestate macro invocation into
a TypestateGraph structure. It handles parsing of:
- State declarations (
states Closed, Open, Errored) - Transition declarations (
Closed -> Open | Errored) - Wildcard transitions (
* -> Closed)
The parser operates at compile-time within macro context.
Internal module - most users won't interact with this directly.
parseStates ¶
proc parseStates(graph: var TypestateGraph; node: NimNode)
Parse a states declaration and add states to the graph.
Accepts multiple syntax forms:
- Inline:
states Closed, Open, Errored - Multiline block:
- Multiline with commas:
States can be any valid Nim type expression:
- Simple identifiers:
Closed,Open - Generic types:
Container[T],Map[K, V] - Ref types:
ref Closed - Qualified names:
mymodule.State
Example AST inputs:
# Simple: states Closed, Open
Command
Ident "states"
Ident "Closed"
Ident "Open"
# Generic: states Empty[T], Full[T]
Command
Ident "states"
BracketExpr
Ident "Empty"
Ident "T"
BracketExpr
Ident "Full"
Ident "T"
# Multiline: states:
# Closed
# Open
Call
Ident "states"
StmtList
Ident "Closed"
Ident "Open"
Parameters
-
graph(var TypestateGraph) – The typestate graph to populate -
node(NimNode) – AST node of the states declaration
parseTransition ¶
proc parseTransition(node: NimNode): Transition
Parse a single transition declaration.
Supports three forms:
- Simple:
Closed -> Open - Branching:
Closed -> Open | Errored - Wildcard:
* -> Closed
Example AST for Closed -> Open | Errored:
Example AST for * -> Closed (wildcard parsed as nested prefix):
Parameters
-
node(NimNode) – AST node of the transition expression
Returns
Transition – A `Transition` object
parseBridgesBlock ¶
proc parseBridgesBlock(graph: var TypestateGraph; node: NimNode)
Parse the bridges block and add all bridges to the graph.
Example input:
Parameters
-
graph(var TypestateGraph) – The typestate graph to populate -
node(NimNode) – AST node of the bridges block
parseInitialBlock ¶
proc parseInitialBlock(graph: var TypestateGraph; node: NimNode)
Parse the initial states block.
Initial states can only be constructed, not transitioned to.
Example input:
Parameters
-
graph(var TypestateGraph) – The typestate graph to populate -
node(NimNode) – AST node of the initial block
parseTerminalBlock ¶
proc parseTerminalBlock(graph: var TypestateGraph; node: NimNode)
Parse the terminal states block.
Terminal states are end states with no outgoing transitions.
Example input:
Parameters
-
graph(var TypestateGraph) – The typestate graph to populate -
node(NimNode) – AST node of the terminal block
parseTypestateBody ¶
proc parseTypestateBody(name: NimNode; body: NimNode): TypestateGraph
Parse a complete typestate block body into a TypestateGraph.
This is the main entry point for parsing. It processes the full
body of a typestate macro invocation.
The typestate name can be a simple identifier or a generic type:
- Simple:
typestate File: - Generic:
typestate Container[T]:
Examples:
Parameters
-
name(NimNode) – The typestate name (identifier or bracket expression) -
body(NimNode) – The statement list containing states and transitions
Returns
TypestateGraph – A fully populated `TypestateGraph`
Registry¶
Compile-time typestate storage.
registry
¶
Compile-time registry for typestate definitions.
This module provides a global compile-time registry that stores all declared typestates. The registry enables:
- Looking up typestates by name
- Finding which typestate a state type belongs to
- Extending typestates across modules
The registry is used by the {.transition.} pragma to validate that
transitions are allowed.
Internal module - most users won't interact with this directly.
typestateRegistry
¶
var typestateRegistry: Table[string, TypestateGraph]
Global compile-time storage for all registered typestates.
Maps typestate names (e.g., "File") to their graph definitions.
This variable is populated by the typestate macro and queried
by the {.transition.} pragma.
Type: Table[string, TypestateGraph]
registerTypestate ¶
template registerTypestate(graph: TypestateGraph)
Register a typestate graph in the compile-time registry.
Each typestate can only be defined once. Attempting to register a typestate with the same name twice results in a compile error.
Example:
Parameters
-
graph(TypestateGraph) – The typestate graph to register
hasTypestate ¶
template hasTypestate(name: string): bool
Check if a typestate with the given name exists in the registry.
Parameters
-
name(string) – The typestate name to look up
Returns
bool – `true` if registered, `false` otherwise
getTypestate ¶
template getTypestate(name: string): TypestateGraph
Retrieve a typestate graph by name.
Parameters
-
name(string) – The typestate name to look up
Returns
TypestateGraph – The `TypestateGraph` for the typestate
findTypestateForState compileTime ¶
proc findTypestateForState(stateName: string): Option[TypestateGraph]
Find which typestate a given state belongs to.
Searches all registered typestates to find one containing the
specified state. Used by the {.transition.} pragma to determine
which typestate graph to validate against.
Lookups use base names to support generic types:
- findTypestateForState("Empty") finds typestate Container with Empty[T]
Example:
Parameters
-
stateName(string) – The state type name (base name, e.g., "Closed", "Empty")
Returns
Option[TypestateGraph] – `some(graph)` if found, `none` if state is not in any typestate
BranchTypeInfo
¶
type BranchTypeInfo = object
Information about a user-defined branch type.
When a branching transition like Created -> (Approved | Declined) as ProcessResult
is declared, the user provides the type name. This object captures the
relationship between the branch type name and the original transition.
Fields
-
sourceStatestring– The source state name ("Created") -
destinationsseq[string]– The destination states (["Approved", "Declined"])
findBranchTypeInfo compileTime ¶
proc findBranchTypeInfo(typeName: string): Option[BranchTypeInfo]
Check if a type name is a user-defined branch type.
Branch types are named by the user via as TypeName syntax in
branching transitions.
This function searches all registered typestates for branching transitions that declare the given branch type name.
Example:
Parameters
-
typeName(string) – The type name to check
Returns
Option[BranchTypeInfo] – `some(info)` if it's a branch type, `none` otherwise
Pragmas¶
Pragma implementations for transition validation.
pragmas
¶
Pragmas for marking and validating state transitions.
This module provides the pragmas that users apply to their procs:
{.transition.}- Mark a proc as a state transition (validated){.notATransition.}- Mark a proc as intentionally not a transition
The {.transition.} pragma performs compile-time validation to ensure
that only declared transitions are implemented.
sealedTypestateModules
¶
var sealedTypestateModules: Table[string, seq[string]]
Maps module filename -> list of state type names from sealed typestates
Type: Table[string, seq[string]]
transparentWrappers
¶
var transparentWrappers: HashSet[string] = toHashSet(["Result", "Option", "Future"])
Type: HashSet[string]
registerSealedStates compileTime ¶
proc registerSealedStates(modulePath: string; stateNames: seq[string])
Register states from a sealed typestate for external checking.
Parameters
-
modulePath(string) – The module filename where the typestate is defined -
stateNames(seq[string]) – List of state type names to register
isStateFromSealedTypestate compileTime ¶
proc isStateFromSealedTypestate(stateName: string; currentModule: string): Option[string]
Check if a state is from a sealed typestate defined in another module.
Parameters
-
stateName(string) – The state type name to check -
currentModule(string) – The current module's filename
Returns
Option[string] – `some(modulePath)` if from external sealed typestate, `none` otherwise
transparentWrapper pragma ¶
template transparentWrapper()
Marker pragma (cosmetic, does NOT register). Apply to a generic type
and follow with an explicit static: registerTransparentWrapper("YourType")
call to register it as transparent for {.transition.} return-type
validation.
Contract for wrapper authors: the unwrap logic assumes the wrapped
state type is the first generic argument of the wrapper. For
Wrapper[State, ...Extras] this picks up State; for Wrapper[A, B]
with no clear "primary" arg, only A is validated as a typestate
destination. This matches the built-in seeds (Result[T, E],
Option[T], Future[T]). If your wrapper puts the state anywhere
other than position 0, do NOT register it — write a non-transparent
wrapper and validate the transition at the call site instead.
Example:
type MyResult*[T] {.transparentWrapper.} = object
value: T
static:
registerTransparentWrapper("MyResult")
The pragma itself is a no-op in v1; actual registration is the
separate registerTransparentWrapper call. The two-step form keeps
type-level AST interactions simple and consistent with the
notATransition marker pattern.
registerTransparentWrapper compileTime ¶
proc registerTransparentWrapper(name: string)
Register a generic wrapper type as transparent for {.transition.}
return-type validation. Name may be a base name ("MyResult") or a
module-qualified name ("mymod.MyResult"); the lookup checks both
forms via isTransparentWrapper.
Parameters
-
name(string)
unregisterTransparentWrapper compileTime ¶
proc unregisterTransparentWrapper(name: string)
Remove a wrapper from the registry. Intended for users whose
project-local type of the same name is itself a typestate STATE
(rather than an error/option wrapper) and must be validated as the
destination — not unwrapped. Call from a static: block near the
typestate declaration, before the {.transition.} procs that use it.
Parameters
-
name(string)
isTransparentWrapper compileTime ¶
proc isTransparentWrapper(name: string): bool
Predicate: is this type name registered as a transparent wrapper?
Checks both the full name as given and the extracted base name, so
module-qualified forms (results.Result) and bare forms (Result)
both hit the registry.
Parameters
-
name(string)
Returns
bool
transition ¶
macro transition(procDef: untyped): untyped
Mark a proc as a state transition and verify it at compile time.
The compiler checks that the transition from the input state type to the return state type is declared in the corresponding typestate. If not, compilation fails with a diagnostic.
This provides compile-time protocol enforcement: only declared transitions can be implemented.
Example:
proc open(f: Closed): Open {.transition.} =
result = Open(f)
proc close(f: Open): Closed {.transition.} =
result = Closed(f)
Error example:
Parameters
-
procDef(untyped)
Returns
untyped
notATransition pragma ¶
template notATransition()
Mark a proc as intentionally not a state transition.
Use this pragma for procs that operate on state types but don't
change the state. This is required when strictTransitions is
enabled on the typestate.
When to use:
- Procs that read from a state type
- Procs that perform I/O without changing state
- Procs that modify the underlying data without state transition
Example:
Code Generation¶
Code generation for helper types.
codegen
¶
Code generation for typestate helper types.
This module generates the helper types and procs that make typestates easier to use at runtime:
- State enum:
FileState = enum fsClosed, fsOpen, ... - Union type:
FileStates = Closed | Open | ... - State procs:
proc state(f: Closed): FileState - Branch types:
CreatedBranchvariant forCreated -> Approved | Declined - Branch constructors:
toCreatedBranch(s: Approved): CreatedBranch
These are generated automatically by the typestate macro.
buildGenericParams ¶
proc buildGenericParams(typeParams: seq[NimNode]): NimNode
Build a generic params node for proc/type definitions.
For @[T], generates: [T]
For @[K, V], generates: [K, V]
For @[N: static int], generates: [N: static int]
For @[T: SomeInteger], generates: [T: SomeInteger]
For @[], returns empty node (non-generic)
Parameters
-
typeParams(seq[NimNode]) – Sequence of type parameter nodes
Returns
NimNode – nnkGenericParams node or newEmptyNode()
extractTypeParams ¶
proc extractTypeParams(node: NimNode): seq[NimNode]
Extract type parameters from a type node.
For FillResult[T], returns @[T]
For Map[K, V], returns @[K, V]
For Simple, returns @[]
Parameters
-
node(NimNode) – A type AST node (ident or bracket expr)
Returns
seq[NimNode] – Sequence of type parameter nodes
generateStateEnum ¶
proc generateStateEnum(graph: TypestateGraph): NimNode
Generate a runtime enum representing all states.
For a typestate named File with states Closed, Open, Errored,
generates:
For generic typestates like Container[T] with states Empty[T], Full[T]:
The enum values use base names (without type params) prefixed with fs.
Parameters
-
graph(TypestateGraph) – The typestate graph to generate from
Returns
NimNode – AST for the enum type definition
generateUnionType ¶
proc generateUnionType(graph: TypestateGraph): NimNode
Generate a type alias for "any state" using Nim's union types.
For a typestate named File with states Closed, Open, Errored,
generates:
For generic typestates like Container[T]:
This union type is useful for procs that can accept any state.
Parameters
-
graph(TypestateGraph) – The typestate graph to generate from
Returns
NimNode – AST for the union type definition
generateStateProcs ¶
proc generateStateProcs(graph: TypestateGraph): NimNode
Generate state() procs for runtime state inspection.
For each state, generates a proc that returns the enum value:
For generic types:
Parameters
-
graph(TypestateGraph) – The typestate graph to generate from
Returns
NimNode – AST for all state() proc definitions
generateStateDollar ¶
proc generateStateDollar(graph: TypestateGraph): NimNode
Generate $ overload for each leaf state type and the state enum.
For each state, emits a proc returning the bare state name:
Also emits a $ over the generated state enum that strips the fs prefix:
Parameters
-
graph(TypestateGraph) – The typestate graph
Returns
NimNode – AST for `$` overloads (one per state + one over the enum)
hasGenericStates ¶
proc hasGenericStates(graph: TypestateGraph): bool
Check if any states use generic type parameters.
Parameters
-
graph(TypestateGraph)
Returns
bool
getBranchingTransitions ¶
proc getBranchingTransitions(graph: TypestateGraph): seq[Transition]
Get all transitions that have multiple destinations (branching).
A branching transition is one where toStates.len > 1, like:
Created -> (Approved | Declined)
Parameters
-
graph(TypestateGraph) – The typestate graph to query
Returns
seq[Transition] – Sequence of branching transitions
generateBranchTypes ¶
proc generateBranchTypes(graph: TypestateGraph): NimNode
Generate variant types for branching transitions.
For a transition like Created -> (Approved | Declined) as ProcessResult,
generates:
type
ProcessResultKind* = enum pApproved, pDeclined
ProcessResult* = object
case kind*: ProcessResultKind
of pApproved: approved*: Approved
of pDeclined: declined*: Declined
For generic types like Empty[T] -> Full[T] | Error[T] as FillResult[T]:
type
FillResultKind* = enum fFull, fError
FillResult*[T] = object
case kind*: FillResultKind
of fFull: full*: Full[T]
of fError: error*: Error[T]
The type name comes from the as TypeName syntax in the DSL.
Enum prefixes are derived from the first letter of the type name.
Parameters
-
graph(TypestateGraph) – The typestate graph to generate from
Returns
NimNode – AST for all branch type definitions
generateBranchConstructors ¶
proc generateBranchConstructors(graph: TypestateGraph): NimNode
Generate constructor procs for branch types.
For Created -> (Approved | Declined) as ProcessResult, generates:
proc toProcessResult*(s: Approved): ProcessResult =
ProcessResult(kind: pApproved, approved: s)
proc toProcessResult*(s: Declined): ProcessResult =
ProcessResult(kind: pDeclined, declined: s)
For generic types:
Parameters
-
graph(TypestateGraph) – The typestate graph to generate from
Returns
NimNode – AST for all constructor proc definitions
generateCopyHooks ¶
proc generateCopyHooks(graph: TypestateGraph): NimNode
Generate =copy error hooks to prevent state copying.
When consumeOnTransition = true, generates:
proc `=copy`*(dest: var Closed, src: Closed) {.error: "State 'Closed' cannot be copied. Transitions consume the input state.".}
This enforces linear/affine typing - each state value can only be used once.
Parameters
-
graph(TypestateGraph) – The typestate graph to generate from
Returns
NimNode – AST for all copy hook definitions
hasStaticGenericParam ¶
proc hasStaticGenericParam(graph: TypestateGraph): bool
Check if typestate has any static generic parameters (e.g., N: static int).
These are vulnerable to a codegen bug in Nim < 2.2.8 when combined
with =copy hooks on distinct types. Affects ARC, ORC, AtomicARC,
and any memory manager using hooks.
Parameters
-
graph(TypestateGraph) – The typestate graph to check
Returns
bool – `true` if any type parameter uses `static`
hasHookCodegenBugConditions ¶
proc hasHookCodegenBugConditions(graph: TypestateGraph): bool
Check if this typestate has conditions that trigger a codegen bug in Nim < 2.2.8.
The bug occurs when all these conditions are met:
1. Distinct types (implicit - all typestate states are distinct)
2. Plain object (not inheriting from RootObj)
3. Generic with static parameter (e.g., N: static int)
4. Lifecycle hooks are generated (consumeOnTransition = true)
Note: Condition 1 is always true for typestates. Condition 2 is checked
via the inheritsFromRootObj flag (we can't detect inheritance at macro time).
Affects ARC, ORC, AtomicARC, and any memory manager using hooks. Fixed in Nim commit 099ee1ce4a308024781f6f39ddfcb876f4c3629c (>= 2.2.8). See: https://github.com/nim-lang/Nim/issues/25341
Parameters
-
graph(TypestateGraph) – The typestate graph to check
Returns
bool – `true` if vulnerable conditions are present
generateBranchOperators ¶
proc generateBranchOperators(graph: TypestateGraph): NimNode
Generate -> operator templates for branch types.
The -> operator provides syntactic sugar for branch construction.
It takes the branch type on the left and the state value on the right:
# Usage (for: Created -> Approved | Declined as ProcessResult):
ProcessResult -> Approved(c.Payment)
# Equivalent to:
toProcessResult(Approved(c.Payment))
For generic types:
Generated templates:
template `->`*(T: typedesc[ProcessResult], s: Approved): ProcessResult =
toProcessResult(s)
template `->`*[T](T: typedesc[FillResult[T]], s: Full[T]): FillResult[T] =
toFillResult(s)
The typedesc parameter disambiguates when the same state appears
in multiple branch types.
Parameters
-
graph(TypestateGraph) – The typestate graph to generate from
Returns
NimNode – AST for all operator template definitions
generateBranchDollar ¶
proc generateBranchDollar(graph: TypestateGraph): NimNode
Generate $ overload for each branching union type.
For a branching transition Created -> A | B | C as Result, emits:
For generic typestates the union type includes the typestate's type parameters so the proc binds correctly under generic instantiation.
Parameters
-
graph(TypestateGraph) – The typestate graph
Returns
NimNode – AST for `$` overloads over branching union types (one per union)
buildMatchCase ¶
proc buildMatchCase(value: NimNode; arms: NimNode; validNames: seq[string]; kindSyms: seq[NimNode]): NimNode
INTERNAL: this helper is exported for use by the generated match macro
via bindSym. User code should not call it directly.
Helper used by every generated match macro to rewrite an arms block
into a case value.kind statement.
value: NimNode for the matched union value (passed to the macro).arms: NimNode for the StmtList ofCall(StateIdent, bindIdent, body)arms.validNames: bare base names of the union's branches (e.g. @["Approved", "Declined"]).kindSyms: pre-resolved sym nodes for each branch's kind-enum field, in the same order asvalidNames. Resolved at the typestate-decl call site (where the kind enum is in scope) so consumer modules that do not import the kind enum directly can still expandmatchcorrectly.
Errors at the user's call site for malformed arms or unknown-branch names. Exhaustiveness is enforced by Nim's case-statement checker once the resulting AST is sema-checked.
Parameters
-
value(NimNode) -
arms(NimNode) -
validNames(seq[string]) -
kindSyms(seq[NimNode])
Returns
NimNode
buildSingleTargetMatchCase ¶
proc buildSingleTargetMatchCase(value: NimNode; arms: NimNode; validStateName: string): NimNode
INTERNAL: this helper is exported for use by the generated single-target
match macro via bindSym. User code should not call it directly.
Helper used by every generated single-target match macro to rewrite an
arms block of the shape StateName(bindName): body into a hygienic
block: statement that moves the matched value into the bound name and
then runs the body.
value: NimNode for the matched state value (passed to the macro).arms: NimNode for the StmtList ofCall(StateIdent, bindIdent, body)arms.validStateName: bare base name of the only valid state (e.g. "Approved").
Two-path AST emit driven by value.kind:
-
L-value source (
nnkIdent/nnkSym/nnkDotExpr/nnkBracketExpr):move()accepts the l-value directly; no copy hook is invoked. The l-value MUST be avarbinding (e.g.var a = ...; match a:) becausesystem.moverequires avar Tparameter. Alet-bound source emits the standard "expression is immutable, not 'var'" error at the user's call site. -
R-value source (call expressions, etc.):
Materializing the rvalue into avargoes through=sink(sink-on-construction), not=copy, so the{.error.}copy hook on distinct state types is never reached. The temp is required becausemove()demands avarbinding.
The block: wrapper provides hygiene so adjacent matches with the same
bind name don't collide.
Errors at the user's call site for malformed arms or a state-name mismatch.
Parameters
-
value(NimNode) -
arms(NimNode) -
validStateName(string)
Returns
NimNode
generateBranchMatch ¶
proc generateBranchMatch(graph: TypestateGraph): NimNode
Generate a match macro for each branching union type.
For a transition Created -> (Approved | Declined) as ProcessResult,
emits a macro with this signature:
macro match*(value: ProcessResult; body: untyped): untyped =
## Pattern-match on a branching union; rewrites into a `case` over the
## kind discriminator.
Call-site syntax (the body is a list of Call(StateIdent, bindIdent, body)):
Rewritten to:
case value.kind
of pApproved:
let a = move(value.approved)
doSomething(a)
of pDeclined:
let d = move(value.declined)
handleDecline(d)
Exhaustiveness is provided by Nim's case statement: missing branches
produce "not all cases are covered" at compile time. Branches naming a
state outside the union produce an explicit "unknown branch" error.
Multiple branching unions in the same module each get their own match
macro; Nim disambiguates via the typed first parameter (verified by the
F4.A0 probe).
Parameters
-
graph(TypestateGraph) – The typestate graph
Returns
NimNode – AST for one `match` macro per branching union
generateSingleTargetMatch ¶
proc generateSingleTargetMatch(graph: TypestateGraph): NimNode
Generate a match macro for each state, supporting single-target match.
For every state in the graph, emits a macro with this signature:
macro match*(value: <StateType>; arms: untyped): untyped =
## Single-target pattern match; rewrites to `block: let bind = move(value); body`.
Call-site syntax (exactly one arm naming the state):
Rewritten to:
R-value sources (e.g. call expressions) are first materialized into a
gensym'd let so move() has an l-value. Sink-on-construction avoids
the =copy error hook on distinct state types.
The per-state match overloads coexist with the per-branching-union
match overloads emitted by generateBranchMatch; Nim disambiguates
by the typed first parameter. The parser-side collision validator
prevents same-name overload duplication between a state and a branch
wrapper type.
Parameters
-
graph(TypestateGraph) – The typestate graph
Returns
NimNode – AST for one `match` macro per state
generateAll ¶
proc generateAll(graph: TypestateGraph): NimNode
Generate all helper types and procs for a typestate.
This is the main entry point called by the typestate macro.
It generates:
- State enum (
FileState) - Union type (
FileStatesorContainerStates[T]) - State procs (
state()for each state) - Copy hooks (
=copyerror hooks when consumeOnTransition = true) - Branch types for branching transitions (user-named via
as TypeName) - Branch constructors (
toTypeName) - Branch operators (
->) - Branch
$overloads - Per-branching-union
matchmacros - Per-state single-target
matchmacros
For generic typestates like Container[T], all generated types
and procs include proper type parameters.
Parameters
-
graph(TypestateGraph) – The typestate graph to generate from
Returns
NimNode – AST containing all generated definitions
CLI¶
Command-line tool functionality.
cli
¶
Command-line tool for typestates.
Usage:
Parses source files using Nim's AST parser and verifies typestate rules or generates DOT output.
Note: Files must be valid Nim syntax. Parse errors cause verification to fail loudly with a clear error message.
SplineMode
¶
type SplineMode = enum
Edge routing mode for DOT output.
Values
-
smSpline="spline"– Curved splines (default, best edge separation) -
smOrtho="ortho"– Right-angle edges only -
smPolyline="polyline"– Straight line segments -
smLine="line"– Direct straight lines
parseTypestates ¶
proc parseTypestates(paths: seq[string]): ParseResult
Parse all Nim files in the given paths for typestates.
Uses Nim's AST parser for accurate extraction. Fails loudly on files with syntax errors.
Parameters
-
paths(seq[string]) – List of file or directory paths to scan
Returns
ParseResult – All parsed typestates and total file count
generateDot ¶
proc generateDot(ts: ParsedTypestate; noStyle: bool = false; splineMode: SplineMode = smSpline): string
Generate GraphViz DOT output for a typestate.
Creates a directed graph representation suitable for rendering
with dot, neato, or other GraphViz tools.
Parameters
-
ts(ParsedTypestate) – The parsed typestate to visualize -
noStyle(bool) – If true, output bare DOT structure with no styling -
splineMode(SplineMode) – Edge routing mode (spline, ortho, polyline, line)
Returns
string – DOT format string
generateUnifiedDot ¶
proc generateUnifiedDot(typestates: seq[ParsedTypestate]; noStyle: bool = false; splineMode: SplineMode = smSpline): string
Generate a unified GraphViz DOT output showing all typestates.
Creates subgraphs for each typestate with cross-cluster edges for bridges.
Parameters
-
typestates(seq[ParsedTypestate]) – List of parsed typestates to visualize -
noStyle(bool) – If true, output bare DOT structure with no styling -
splineMode(SplineMode) – Edge routing mode (spline, ortho, polyline, line)
Returns
string – DOT format string
generateSeparateDot ¶
proc generateSeparateDot(ts: ParsedTypestate; noStyle: bool = false; splineMode: SplineMode = smSpline): string
Generate GraphViz DOT output for a single typestate.
Bridges are shown as terminal nodes with dashed edges.
Parameters
-
ts(ParsedTypestate) – The parsed typestate to visualize -
noStyle(bool) – If true, output bare DOT structure with no styling -
splineMode(SplineMode) – Edge routing mode (spline, ortho, polyline, line)
Returns
string – DOT format string
generateCode ¶
proc generateCode(ts: ParsedTypestate): string
Generate Nim code for a typestate's helper types and procs.
Generates:
- State enum (FileState = enum fsClosed, fsOpen, ...)
- Union type (FileStates = Closed | Open | ...)
- State procs (proc state(f: Closed): FileState)
- Branch types for branching transitions
- Branch constructors and operators
Parameters
-
ts(ParsedTypestate) – The parsed typestate to generate code for
Returns
string – Generated Nim code as a string
generateCodeForAll ¶
proc generateCodeForAll(typestates: seq[ParsedTypestate]): string
Generate code for all typestates.
Parameters
-
typestates(seq[ParsedTypestate]) – All parsed typestates
Returns
string – Combined generated Nim code
verify ¶
proc verify(paths: seq[string]): VerifyResult
Verify all Nim files in the given paths.
Uses Nim's AST parser to extract typestates, then checks that all
procs operating on state types are properly marked with
{.transition.} or {.notATransition.}. Also runs reachability
analysis when initial: / terminal: blocks are declared and the
opaque-states cast-bypass lint, appending all results to
result.findings.
Files with syntax errors no longer abort the pipeline: they surface as
fcParseError findings routed through the normal output formatters.
A parse error in one file does NOT abort verification of other files —
typestates verify --format=github src/ produces annotations for
every problem the user has, not just the first.
Parameters
-
paths(seq[string]) – List of file or directory paths to verify
Returns
VerifyResult – Verification results with structured findings and counts