Skip to content

Name the fix in compile errors for targets, sources, owners, and final states - #269

Merged
SandroMaglione merged 1 commit into
mainfrom
actionable-type-errors
Nov 7, 2026
Merged

SandroMaglione merged 1 commit into
mainfrom
actionable-type-errors

Conversation

@SandroMaglione

Copy link
Copy Markdown
Member

Summary

Machine.handle used to answer most mistakes with Type 'string' is not assignable to type 'never'. It now names the fix:

Mistake Message
Misspelled target Unknown target "Archivd". Declared: Editing, Checkout, Archived
Root as a target Cannot target "root". Reconstruct it with initialize, or update it with update: { root: ... }
History state as a target "Checkout.recent" is a history state. Restore it with: { history: "Checkout.recent" }
Missing data Target "Checkout" requires data
Misspelled source Unknown source. Registered: load
Unknown owner Cannot update "Typo". Updatable here: Editing, root
on on a final state Final state "Archived" cannot declare on. A final state has no outgoing work
on on a choice Choice state "Checkout.Route" cannot declare on. Declare one transition in choice

One addition beyond the plan: a target below a certainly inactive valued state, such as { target: "Checkout.Paid" } from outside Checkout, used to compile and then fail while planning. It is now a compile error. It found a real bug in hierarchy-routing-example, whose onFailure would have thrown, and that example is fixed here. The check stays conservative: anything below the source or a parallel state may already be active, so those remain runtime checks.

How the messages reach the user:

  • Validation now runs before the handler shape in handle's parameter, so its message is reported first.
  • A plain target needs a fallback union member, because intersecting a misspelled literal with the declared ones collapses to never. A fallback alone would have let a known target omit required data, so ValidateTargetData closes that gap. The member also lists the declared paths, which keeps editor completion working. scripts/invoke-autocomplete.test.mjs caught both of these while I built it.
  • HandlerShape replaces an invocation that has an unknown source with the message, so valid invocations keep their contextual typing.

scripts/type-errors.test.mjs compiles each mistake and reads the whole diagnostic chain, as an agent reading tsc output does. tstyche only exposes the top line, which doesn't always contain the message.

Type-performance tradeoff: machine.handle (3 states, 2 transitions) rises about 14% and atomic construction about 11%, with smaller rises elsewhere, because each transition now also validates its target by path. This is deliberate, per AGENTS.md. Union print order in messages comes from TypeScript and isn't stable, so tests match the message and not the list order.

Changeset

  • Added or updated for a library or package-metadata change
  • Not required because this PR does not change a publishable package

Validation

  • pnpm check
  • Automated type-performance measurement passed or was not required
  • Automated runtime- and memory-performance measurement passed or was not required

🤖 Generated with Claude Code

…l states

Report a misspelled target, source, or owner by listing what is declared, where
these used to surface as `never`. Run validation before the handler shape so its
message wins, and read the whole diagnostic chain in a test, as an agent does.

Reject a target below a state that is certainly inactive and has a value, which
used to fail while planning. This found a devtools example that would have thrown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Type performance

Measured with TypeScript 6.0.3 and skipLibCheck=true.

Scenario Base PR Difference
Effect only 55 55 0 (0.0%)
Import effect-machine 55 55 0 (0.0%)
Machine.state (3 children) 2,664 2,664 0 (0.0%)
Machine.make (3 states, 2 events) 8,849 8,849 0 (0.0%)
machine.handle (3 states, 2 transitions) 38,693 44,284 +5,591 (+14.4%)
declared transition (10 named branches) 67,072 73,171 +6,099 (+9.1%)
atomic construction, guards, and reentry 77,731 86,139 +8,408 (+10.8%)
registered invocation (input-taking Effect) 38,733 40,627 +1,894 (+4.9%)
machine.handle (depth 24) 194,030 194,315 +285 (+0.1%)
machine.handle (wide depth 16) 217,737 218,054 +317 (+0.1%)
machine.handle (parallel/history/choice) 102,455 108,383 +5,928 (+5.8%)
machine definition (3 independent implementations) 92,828 98,915 +6,087 (+6.6%)
machine exact input/output/error/services 80,179 88,283 +8,104 (+10.1%)
execution adapter readiness 73,484 79,009 +5,525 (+7.5%)

Marginal instantiations are measured against the matching setup without that API call:

Scenario Base PR Difference
Import effect-machine 0 0 0
Machine.state (3 children) 2,609 2,609 0 (0.0%)
Machine.make (3 states, 2 events) 6,177 6,177 0 (0.0%)
machine.handle (3 states, 2 transitions) 29,844 35,435 +5,591 (+18.7%)
declared transition (10 named branches) 58,602 64,701 +6,099 (+10.4%)
atomic construction, guards, and reentry 62,474 70,882 +8,408 (+13.5%)
registered invocation (input-taking Effect) 31,679 33,573 +1,894 (+6.0%)
machine.handle (depth 24) 176,804 177,089 +285 (+0.2%)
machine.handle (wide depth 16) 201,289 201,606 +317 (+0.2%)
machine.handle (parallel/history/choice) 90,040 95,968 +5,928 (+6.6%)
machine definition (3 independent implementations) 82,230 88,317 +6,087 (+7.4%)
machine exact input/output/error/services 68,053 76,157 +8,104 (+11.9%)
execution adapter readiness 31,884 31,778 -106 (-0.3%)
Check times (informational)
Scenario Base PR
Effect only 0.03s 0.03s
Import effect-machine 0.03s 0.03s
Machine.state (3 children) 0.08s 0.09s
Machine.make (3 states, 2 events) 0.15s 0.16s
machine.handle (3 states, 2 transitions) 0.30s 0.32s
declared transition (10 named branches) 0.43s 0.46s
atomic construction, guards, and reentry 0.49s 0.55s
registered invocation (input-taking Effect) 0.30s 0.31s
machine.handle (depth 24) 0.61s 0.61s
machine.handle (wide depth 16) 0.66s 0.71s
machine.handle (parallel/history/choice) 0.55s 0.61s
machine definition (3 independent implementations) 0.54s 0.56s
machine exact input/output/error/services 0.50s 0.54s
execution adapter readiness 0.42s 0.47s

Type instantiations are the comparison metric. Check time varies with runner load and is informational only.

@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Runtime performance

Median of 5 independent benchmark processes on Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz with Node v24.22.2.

Pull request baseline

Scenario Effect Machine
Plan counter transitions 118,166 transitions/s
Drain burst with terminal fence 414,603 increments/s
Drain burst with a change observer 379,939 increments/s
Lookup and send to one child 312,195 increments/s
Start and stop a machine 120,091 machines/s
Start and stop a parent with one child 31,848 families/s
Complete an Effect invocation with tracing enabled 8,381 invocations/s
Complete an Effect invocation with tracing disabled 10,252 invocations/s
Plan transitions through a compound state 106,356 transitions/s
Plan transitions through parallel regions 86,094 transitions/s
Drain burst through a compound state 348,506 events/s
Drain burst through two parallel regions 310,657 events/s
Drain a compound-state burst with a change observer 319,706 events/s

Process runtime reference points

Scenario Effect Machine
Start and stop a raw generic process 36,526 processes/s
Start and stop a raw compiled process 67,558 processes/s
Memory profile Effect Machine
Idle machine 1.9 KiB
Raw generic managed process 11.4 KiB
Raw compiled process 3.1 KiB
Two independent idle machines 3.6 KiB
Idle parent with one child 6.0 KiB
Parent with observed child registry 9.6 KiB
Parent with observed invoked child snapshots 7.0 KiB

Effect Machine change from base

Metric Base Base variability PR PR variability Difference
Plan counter transitions 117,220 transitions/s 0.3% MAD 118,166 transitions/s 0.8% MAD +0.8%
Drain burst with terminal fence 419,602 increments/s 0.8% MAD 414,603 increments/s 1.3% MAD -1.2%
Drain burst with a change observer 385,585 increments/s 0.7% MAD 379,939 increments/s 0.8% MAD -1.5%
Lookup and send to one child 316,596 increments/s 0.5% MAD 312,195 increments/s 0.4% MAD -1.4%
Start and stop a machine 118,807 machines/s 0.3% MAD 120,091 machines/s 0.1% MAD +1.1%
Start and stop a parent with one child 31,472 families/s 0.6% MAD 31,848 families/s 1.2% MAD +1.2%
Complete an Effect invocation with tracing enabled 8,413 invocations/s 0.4% MAD 8,381 invocations/s 0.9% MAD -0.4%
Complete an Effect invocation with tracing disabled 10,288 invocations/s 1.2% MAD 10,252 invocations/s 0.5% MAD -0.3%
Plan transitions through a compound state 105,422 transitions/s 0.1% MAD 106,356 transitions/s 0.2% MAD +0.9%
Plan transitions through parallel regions 84,519 transitions/s 0.5% MAD 86,094 transitions/s 0.9% MAD +1.9%
Drain burst through a compound state 349,359 events/s 0.5% MAD 348,506 events/s 0.5% MAD -0.2%
Drain burst through two parallel regions 312,134 events/s 0.2% MAD 310,657 events/s 1.5% MAD -0.5%
Drain a compound-state burst with a change observer 325,641 events/s 0.5% MAD 319,706 events/s 0.9% MAD -1.8%
Idle machine heap per unit 1.9 KiB 0.2% MAD 1.9 KiB 0.1% MAD +0.0%
Raw generic managed process heap per unit 11.3 KiB 0.0% MAD 11.4 KiB 0.0% MAD +0.9%
Raw compiled process heap per unit 3.1 KiB 0.1% MAD 3.1 KiB 0.1% MAD +0.6%
Two independent idle machines heap per unit 3.6 KiB 0.0% MAD 3.6 KiB 0.2% MAD -0.1%
Idle parent with one child heap per unit 6.0 KiB 0.0% MAD 6.0 KiB 0.0% MAD -0.0%
Parent with observed child registry heap per unit 9.6 KiB 0.0% MAD 9.6 KiB 0.0% MAD -0.0%
Parent with observed invoked child snapshots heap per unit 7.0 KiB 0.1% MAD 7.0 KiB 0.1% MAD -0.0%

Process runtime reference change from base

Metric Base Base variability PR PR variability Difference
Start and stop a raw generic process 36,738 processes/s 0.4% MAD 36,526 processes/s 0.3% MAD -0.6%
Start and stop a raw compiled process 68,217 processes/s 0.3% MAD 67,558 processes/s 0.9% MAD -1.0%

Regression guard

No large, noise-adjusted throughput or heap regressions detected.

Versions and interpretation
  • Effect Machine: 0.46.0

Higher throughput is better; lower heap is better. Variability is the median absolute deviation across independent processes, relative to their median. Small differences on shared GitHub-hosted hardware remain informational; the required guard rejects only large changes beyond the measured noise allowance.

@SandroMaglione
SandroMaglione merged commit 67f11cc into main Nov 7, 2026
9 checks passed
@SandroMaglione
SandroMaglione deleted the actionable-type-errors branch November 7, 2026 06:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant