Skip to content

Generator: Create State Pattern #46

@JerrettDavis

Description

@JerrettDavis

Summary

Add a source generator that produces a complete implementation of the State pattern as a deterministic state machine with:

  • explicit states and triggers
  • deterministic transitions
  • optional guards
  • optional entry/exit hooks
  • sync + async support (favor ValueTask)

The generator lives in PatternKit.Generators and emits self-contained C# with no runtime PatternKit dependency.


Motivation / Problem

State pattern implementations tend to devolve into:

  • giant switch statements
  • scattered transition rules
  • inconsistent guard behavior
  • unclear concurrency/ordering

We want a generator that produces:

  • explicit, readable transition tables
  • deterministic semantics
  • actionable diagnostics
  • predictable performance (no reflection, minimal allocations)

Supported Targets (must-have)

The generator must support:

  • partial class
  • partial struct
  • partial record class
  • partial record struct

The annotated type represents the state machine host, exposing Fire(...) / FireAsync(...) and a State property.


Proposed User Experience

A) Enum-based states/triggers

public enum OrderState { Draft, Submitted, Paid, Shipped, Cancelled }
public enum OrderTrigger { Submit, Pay, Ship, Cancel }

[StateMachine(typeof(OrderState), typeof(OrderTrigger))]
public partial class OrderFlow
{
    [StateTransition(From = OrderState.Draft, Trigger = OrderTrigger.Submit, To = OrderState.Submitted)]
    private void OnSubmit();

    [StateGuard(From = OrderState.Submitted, Trigger = OrderTrigger.Pay)]
    private bool CanPay() => true;

    [StateTransition(From = OrderState.Submitted, Trigger = OrderTrigger.Pay, To = OrderState.Paid)]
    private ValueTask OnPayAsync(CancellationToken ct);

    [StateEntry(OrderState.Shipped)]
    private void OnEnterShipped();

    [StateExit(OrderState.Paid)]
    private void OnExitPaid();
}

Generated (representative shape):

public partial class OrderFlow
{
    public OrderState State { get; private set; }

    public bool CanFire(OrderTrigger trigger);

    public void Fire(OrderTrigger trigger);
    public ValueTask FireAsync(OrderTrigger trigger, CancellationToken ct = default);
}

B) Type-based states (optional v2)

V1 scope can restrict states/triggers to enums for simplicity and performance.


Attributes / Surface Area

Namespace: PatternKit.Generators.State

Core

  • [StateMachine(Type stateType, Type triggerType)] on the host

    • string FireMethodName = "Fire"
    • string FireAsyncMethodName = "FireAsync"
    • string CanFireMethodName = "CanFire"
    • bool GenerateAsync (default: inferred)
    • bool ForceAsync (default: false)
    • StateMachineInvalidTriggerPolicy InvalidTrigger (default: Throw)
    • StateMachineGuardFailurePolicy GuardFailure (default: Throw)
  • [StateTransition] on methods that execute on a transition

    • TState From
    • TTrigger Trigger
    • TState To
  • [StateGuard] on methods that determine whether a transition is allowed

    • From, Trigger
  • [StateEntry] / [StateExit] on hook methods

    • TState State

Policies:

  • InvalidTriggerPolicy: Throw | Ignore | ReturnFalse (if using TryFire)
  • GuardFailurePolicy: Throw | Ignore | ReturnFalse

Semantics (must-have)

Deterministic transition resolution

  • A transition is uniquely identified by (FromState, Trigger).
  • Duplicate transitions are an error.

Guard evaluation

  • If a guard exists for (FromState, Trigger):

    • evaluate guard first
    • if guard fails, apply GuardFailurePolicy

Execution order

When a transition occurs:

  1. Exit hooks for FromState (if any)
  2. Transition action method ([StateTransition]) (if any)
  3. Update State = ToState
  4. Entry hooks for ToState (if any)

This order is deterministic and must be documented.

Async

  • If any transition action or entry/exit hook is async (ValueTask), generate FireAsync.
  • Prefer ValueTask across generated signatures.
  • Cancellation token passed through to async hooks when applicable.

Concurrency

V1 scope:

  • Not thread-safe by default.
  • Optional Locking mode can be added later. If we include in v1, it must be explicit and tested.

Diagnostics (must-have)

Stable IDs, actionable:

  • PKST001 Type marked [StateMachine] must be partial.
  • PKST002 State type must be an enum (v1).
  • PKST003 Trigger type must be an enum (v1).
  • PKST004 Duplicate transition detected for (From, Trigger).
  • PKST005 Transition method signature invalid.
  • PKST006 Guard method signature invalid (must return bool / ValueTask if async guards supported).
  • PKST007 Entry/Exit hook signature invalid.
  • PKST008 Async method detected but async generation disabled; enable GenerateAsync/ForceAsync.

Generated Code Layout

  • TypeName.StateMachine.g.cs

Determinism:

  • Transition tables generated in stable order: by FromState, then Trigger, then method name (for diagnostics).

Testing Expectations

  • Happy path transitions update state and call hooks in correct order.

  • Guard prevents transition and applies policy correctly.

  • Invalid trigger behavior matches policy.

  • Async transitions use ValueTask and propagate cancellation.

  • Diagnostics:

    • duplicate transitions
    • invalid signatures
    • non-enum state/trigger types (v1)

Acceptance Criteria

  • [StateMachine] works for class/struct/record class/record struct.
  • State/Trigger enums supported (v1).
  • Deterministic transition resolution and execution order.
  • Guards supported with documented policies.
  • Entry/Exit hooks supported.
  • Async path uses ValueTask when needed.
  • Diagnostics cover invalid usage and conflicts.
  • Tests cover transitions, guards, hooks, async, and diagnostics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions