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
:param name: The base type name (must match your type definition) :param body: The states and transitions declarations :returns: Generated helper types (enum, union, state procs)
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"
:param stateRepr: Full state repr string :returns: Base name without generic parameters
Parameters
-
stateRepr(string) – Full state repr string
Returns
string – Base name without generic parameters
== ¶
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.
:param a: First transition to compare
:param b: Second transition to compare
:returns: true if transitions are semantically equivalent
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.
:param a: First bridge to compare
:param b: Second bridge to compare
:returns: true if bridges are semantically equivalent
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:
# Given: Closed -> Open, * -> Closed
graph.hasTransition("Closed", "Open") # true
graph.hasTransition("Open", "Closed") # true (via wildcard)
graph.hasTransition("Closed", "Closed") # true (via wildcard)
graph.hasTransition("Open", "Open") # false (not declared)
:param graph: The typestate graph to check
:param fromState: The source state name (base name or full repr)
:param toState: The destination state name (base name or full repr)
:returns: true if the transition is allowed, false otherwise
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:
# Given: Closed -> Open | Errored, * -> Closed
graph.validDestinations("Closed") # @["Open", "Errored", "Closed"]
graph.validDestinations("Open") # @["Closed"]
:param graph: The typestate graph to query :param fromState: The source state to check transitions from :returns: A sequence of state base names that can be transitioned to
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)
:param graph: The typestate graph to check
:param fromState: The source state name
:param toModule: The destination module name (empty string for same module)
:param toTypestate: The destination typestate name
:param toState: The destination state name
:returns: true if the bridge is declared, false otherwise
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:
# Given: Authenticated -> Session.Active
graph.hasBridge("Authenticated", "Session", "Active") # true
# Given: StateA -> mymodule.Session.Active
graph.hasBridge("StateA", "Session", "Active") # true (matches regardless of module)
:param graph: The typestate graph to check
:param fromState: The source state name
:param toTypestate: The destination typestate name
:param toState: The destination state name
:returns: true if the bridge is declared, false otherwise
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:
# Given: Authenticated -> Session.Active, Failed -> othermodule.ErrorLog.Entry
graph.validBridges("Authenticated") # @["Session.Active"]
graph.validBridges("Failed") # @["othermodule.ErrorLog.Entry"]
:param graph: The typestate graph to query :param fromState: The source state to check bridges from :returns: A sequence of dotted destination names
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:
# Given: initial: Disconnected
graph.isInitialState("Disconnected") # true
graph.isInitialState("Connected") # false
:param graph: The typestate graph to check
:param stateName: The state name to check
:returns: true if the state is initial, false otherwise
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:
# Given: terminal: Closed
graph.isTerminalState("Closed") # true
graph.isTerminalState("Open") # false
:param graph: The typestate graph to check
:param stateName: The state name to check
:returns: true if the state is terminal, false otherwise
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"
:param graph: The typestate graph to populate :param node: AST node of the states declaration :raises: Compile-time error if syntax is invalid
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):
:param node: AST node of the transition expression
:returns: A Transition object
:raises: Compile-time error if syntax is invalid
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:
:param graph: The typestate graph to populate :param node: AST node of the bridges block :raises: Compile-time error if block is malformed
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:
:param graph: The typestate graph to populate :param node: AST node of the initial block
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:
:param graph: The typestate graph to populate :param node: AST node of the terminal block
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:
typestate File: # name = "File"
states Closed, Open
transitions:
Closed -> Open
typestate Container[T]: # name = "Container", with type param T
states Empty[T], Full[T]
transitions:
Empty[T] -> Full[T]
:param name: The typestate name (identifier or bracket expression)
:param body: The statement list containing states and transitions
:returns: A fully populated TypestateGraph
:raises: Compile-time error for invalid syntax
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.
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:
:param graph: The typestate graph to register
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.
:param name: The typestate name to look up
:returns: true if registered, false otherwise
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.
:param name: The typestate name to look up
:returns: The TypestateGraph for the typestate
:raises: Compile-time error if not found
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:
# If File typestate has states Closed, Open:
findTypestateForState("Closed") # some(FileGraph)
findTypestateForState("Unknown") # none
# If Container typestate has states Empty[T], Full[T]:
findTypestateForState("Empty") # some(ContainerGraph)
:param stateName: The state type name (base name, e.g., "Closed", "Empty")
:returns: some(graph) if found, none if state is not in any typestate
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
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:
# If typestate has: Created -> (Approved | Declined) as ProcessResult
findBranchTypeInfo("ProcessResult")
# Returns: some(BranchTypeInfo(sourceState: "Created",
# destinations: @["Approved", "Declined"]))
findBranchTypeInfo("NotABranch")
# Returns: none(BranchTypeInfo)
:param typeName: The type name to check
:returns: some(info) if it's a branch type, none otherwise
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.
registerSealedStates compileTime ¶
proc registerSealedStates(modulePath: string; stateNames: seq[string])
Register states from a sealed typestate for external checking.
:param modulePath: The module filename where the typestate is defined :param stateNames: List of state type names to register
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.
:param stateName: The state type name to check
:param currentModule: The current module's filename
:returns: some(modulePath) if from external sealed typestate, none otherwise
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
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)
:param typeParams: Sequence of type parameter nodes :returns: nnkGenericParams node or newEmptyNode()
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 @[]
:param node: A type AST node (ident or bracket expr) :returns: Sequence of type parameter nodes
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.
:param graph: The typestate graph to generate from :returns: AST for the enum type definition
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.
:param graph: The typestate graph to generate from :returns: AST for the union type definition
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:
proc state*[T](f: Empty[T]): ContainerState = fsEmpty
proc state*[T](f: Full[T]): ContainerState = fsFull
:param graph: The typestate graph to generate from :returns: AST for all state() proc definitions
Parameters
-
graph(TypestateGraph) – The typestate graph to generate from
Returns
NimNode – AST for all state() proc definitions
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)
:param graph: The typestate graph to query :returns: Sequence of branching transitions
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.
:param graph: The typestate graph to generate from :returns: AST for all branch type definitions
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:
:param graph: The typestate graph to generate from :returns: AST for all constructor proc definitions
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.
:param graph: The typestate graph to generate from :returns: AST for all copy hook definitions
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.
:param graph: The typestate graph to check
:returns: true if any type parameter uses static
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
:param graph: The typestate graph to check
:returns: true if vulnerable conditions are present
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.
:param graph: The typestate graph to generate from :returns: AST for all operator template definitions
Parameters
-
graph(TypestateGraph) – The typestate graph to generate from
Returns
NimNode – AST for all operator template definitions
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 (
->)
For generic typestates like Container[T], all generated types
and procs include proper type parameters.
:param graph: The typestate graph to generate from :returns: AST containing all generated definitions
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.
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.
:param paths: List of file or directory paths to scan :returns: All parsed typestates and total file count :raises ParseError: on 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.
:param ts: The parsed typestate to visualize :param noStyle: If true, output bare DOT structure with no styling :param splineMode: Edge routing mode (spline, ortho, polyline, line) :returns: DOT format string
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.
:param typestates: List of parsed typestates to visualize :param noStyle: If true, output bare DOT structure with no styling :param splineMode: Edge routing mode (spline, ortho, polyline, line) :returns: DOT format string
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.
:param ts: The parsed typestate to visualize :param noStyle: If true, output bare DOT structure with no styling :param splineMode: Edge routing mode (spline, ortho, polyline, line) :returns: DOT format string
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
:param ts: The parsed typestate to generate code for :returns: Generated Nim code as a string
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.
:param typestates: All parsed typestates :returns: Combined generated Nim code
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.}.
Note: Files with syntax errors cause verification to fail immediately with a clear error message.
:param paths: List of file or directory paths to verify :returns: Verification results with errors, warnings, and counts :raises ParseError: on syntax errors
Parameters
-
paths(seq[string]) – List of file or directory paths to verify
Returns
VerifyResult – Verification results with errors, warnings, and counts