Skip to content

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:

  • typestate macro - 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:

  • FileState enum with fsClosed, fsOpen, fsErrored
  • FileStates union type for generic procs
  • state() procs for runtime inspection

Transition syntax:

  • A -> B - Simple transition
  • A -> 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

State

type State

Transition

type Transition

Bridge

type Bridge

TypestateGraph

type TypestateGraph

==

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:
    states:
      Closed
      Open
      Errored
    
  • Multiline with commas:
    states:
      Closed,
      Open,
      Errored
    

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:

Infix
  Ident "->"
  Ident "Closed"
  Infix
    Ident "|"
    Ident "Open"
    Ident "Errored"

Example AST for * -> Closed (wildcard parsed as nested prefix):

Prefix
  Ident "*"
  Prefix
    Ident "->"
    Ident "Closed"

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

bridges:
  Authenticated -> Session.Active
  Failed -> ErrorLog.Entry
  * -> Shutdown.Terminal

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

initial: Disconnected
# or
initial: Disconnected, Starting
# or
initial:
  Disconnected
  Starting

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

terminal: Closed
# or
terminal: Closed, Failed
# or
terminal:
  Closed
  Failed

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

typestate File:
  states Closed, Open
  transitions:
    Closed -> Open
    Open -> Closed

: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

BranchTypeInfo

type BranchTypeInfo

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:

Error: Undeclared transition: Open -> Locked
  Typestate 'File' does not declare this transition.
  Valid transitions from 'Open': @["Closed"]
  Hint: Add 'Open -> Locked' to the transitions block.
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:

# Side effects without state change
proc write(f: Open, data: string) {.notATransition.} =
  rawWrite(f.handle, data)

# Pure functions don't need this (use `func` instead)
func path(f: Open): string = f.File.path

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: CreatedBranch variant for Created -> 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:

type FileState* = enum
  fsClosed, fsOpen, fsErrored

For generic typestates like Container[T] with states Empty[T], Full[T]:

type ContainerState* = enum
  fsEmpty, fsFull

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:

type FileStates* = Closed | Open | Errored

For generic typestates like Container[T]:

type ContainerStates*[T] = Empty[T] | Full[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:

proc state*(f: Closed): FileState = fsClosed
proc state*(f: Open): FileState = fsOpen

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:

proc toFillResult*[T](s: Full[T]): FillResult[T] =
  FillResult[T](kind: fFull, full: s)

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

FillResult[int] -> Full[int](container)

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:

  1. State enum (FileState)
  2. Union type (FileStates or ContainerStates[T])
  3. State procs (state() for each state)
  4. Copy hooks (=copy error hooks when consumeOnTransition = true)
  5. Branch types for branching transitions (user-named via as TypeName)
  6. Branch constructors (toTypeName)
  7. 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:

typestates verify [paths...]
typestates dot [paths...]

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

VerifyResult

type VerifyResult

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