W-ENGINE-SELF-WRITE-DETECTED
Engine self-write detected. Per §51.0.F.1, self-writes are runtime no-ops — informational lint surfacing the intentional or accidental no-op.
What it means
An engine self-write is
@var = .CurrentVariant
or
@var.advance(.CurrentVariant)
where the target equals the engine's currently-active
variant. The v0.3 Option-d ratification (2026-05-12) carves out
self-writes as runtime no-ops rather than
E-ENGINE-INVALID-TRANSITION
violations (the pre-Option-d behavior). The W- prefix is
legacy naming; the severity is Info — informational only.
The lint surfaces two firing patterns:
-
STRICT (inside-state-child fire):
.CurrentVariantmatches the enclosing state-child tag. The static check is exact — the write target IS the surrounding state. -
CONSERVATIVE (outside-state-child fire):
the write site is outside any state-child body AND
.CurrentVariantis a declared variant of the engine. The current state at runtime is dynamic, but the literal `.Variant` write target is statically known — the lint surfaces the pattern where it WILL be a no-op IF the engine happens to be in that variant.
What a self-write skips
Per §51.0.F.1, a self-write is a runtime no-op:
- No <onTransition> handlers fire.
-
No
effect=attribute fires. -
No history capture per §51.0.N (the
historyattribute on composite state-children). - No <onTimeout> rearm (per §51.0.M, a self-write does NOT re-enter the state-child).
- No <onIdle> watchdog reset (per §51.0.R).
- No subscriber notification — downstream reactive cells that read the engine variable do NOT recompute.
Minimal reproducer
type Phase:enum = { Active, Idle }
function refresh() {
@phase = .Active
// ^^^^^^^
// W-ENGINE-SELF-WRITE-DETECTED (CONSERVATIVE):
// .Active is a declared variant; if @phase is currently
// .Active, this write is a runtime no-op.
}
<engine for=Phase initial=.Active>
<Active rule=.Idle>
<button onclick=refresh()>I'm here</button>
</>
<Idle/>
</>
When the lint is signal vs noise
Intentional self-write (lint is noise)
A defensive
set(.Current)
reachable from multiple variants — e.g., a "reset to
Active state" handler that runs from any state including the
Active state. The no-op-when-already-Active behavior is
correct. No action required; the lint is informational.
Accidental self-write (lint is signal)
A state change was expected unconditionally — but the write target equals the current variant by mistake. The adopter intended different state, but a copy-paste, typo, or stale code-shape produced a no-op. The lint surfaces "this write does nothing" so the bug is visible at compile time instead of as silent-runtime-no-effect.
Suppression
-
Rephrase via a derived cell.
Avoid the literal
.Variantform by computing the target dynamically:@phase = computeNext(). The compile-time literal-match no longer fires. - Remove the write if the no-op behavior is accidental and the surrounding logic doesn't actually need the assignment.
-
Suppress per SPEC §28 if the lint is
noise in this specific call site. Config key:
lint.engine-self-write-detected.
Related
- <engine> — element reference.
- E-ENGINE-INVALID-TRANSITION — the pre-Option-d error that v0.3 carved out for self-writes.
- <onTransition> — does NOT fire on self-writes.
- <onIdle> — watchdog does NOT reset on self-writes.
Specification
Normative text: SPEC §51.0.F.1 (idempotent self-write semantics — v0.3 Option-d synthesis), §51.0.F (rule= contract — the pre-Option-d error this lint replaces), §28 (lint suppression configs), §34 catalog row. Spec lives at compiler/SPEC.md .