scrml.dev v0.7.1
Reference › Errors

I-MATCH-PROMOTABLE

Info-level lint. An if chain over an enum-typed state cell is mechanically promotable to a <match for=Type> block (Tier 1).

Info Compile-time SPEC §56 (normative)

What it means

The compiler detected an if / else if chain whose branches all discriminate on the same enum-typed state cell with clean variant predicates — exactly the shape that lifts mechanically to a <match for=Type> block. The lint does not nudge; the chain is valid scrml. It names the opportunity.

The pairing is intentional: scrml surfaces compiler signal AND mechanical execution, but keeps the dev in the loop for both. Silent rewrites (Prettier-style auto-promote on save) would defeat the deliberateness of tier-ladder promotion as a ceremony — promotions are when the dev formally tells the system “this surface is now a state machine” and gets back stronger guarantees in exchange.

Fire conditions

The lint fires when ALL of the following hold (per SPEC §56.2):

  1. The chain's leading condition reads a @cell whose declared type resolves to an enum.
  2. Each branch's condition is one of: @cell == .Variant, @cell.is(.Variant), @cell == .Variant(payload), or @cell == .Variant msg (single-field bind).
  3. All branches reference the SAME discriminator. Mixed-discriminator chains do not fire.
  4. No compound conditions (&&, ||, negation, side-effect guards). Compound branches surface a separate advisory (§56.4).

Three message shapes

Exhaustive (clean promotion)

I-MATCH-PROMOTABLE at app.scrml:42 — this if-else exhaustively covers Phase
(.Idle, .Loading, .Error, .Success). Run `bun scrml promote --match
app.scrml:42` to convert.

Near-miss (concrete actionable)

I-MATCH-PROMOTABLE at app.scrml:42 — this if-else covers Phase partially
(.Idle, .Loading, .Error). Missing .Success. Add the missing arm, then
run `bun scrml promote --match app.scrml:42` to convert. Once promoted,
the compiler will catch any future variant-add at the <match> site
automatically.

Wrong-discriminator (folds into W-LIFECYCLE-CANDIDATE)

When the discriminator is a string-typed cell whose RHS values are enum-tag-shaped, the lint defers to W-LIFECYCLE-CANDIDATE rather than firing I-MATCH-PROMOTABLE. Two-step path: lift to enum first, then re-run lints to surface the match-promotion.

Minimal reproducer

type Phase:enum = { Idle, Loading, Error(msg: string), Success(count: int) }

<phase> = .Idle

function load() { /* ... */ }

// I-MATCH-PROMOTABLE fires here — exhaustive shape.
${
    if (@phase == .Idle) { lift <button onclick=load()>Load</button> }
    else if (@phase == .Loading) { lift <p>Loading...</p> }
    else if (@phase == .Error msg) { lift <p class="text-rose-700">${msg}</p> }
    else if (@phase == .Success count) { lift <p>Got ${count} rows</p> }
}

How to use

  1. Promote to <match> via the CLI. Run bun scrml promote --match <file>:<line>. The CLI rewrites per-branch by the table in SPEC §56.5.2; state-children carry forward verbatim. The file gains structural exhaustiveness checking (any future variant addition fails compilation at the match site).
  2. Preview before lifting. bun scrml promote --dry-run --match <file> emits a unified diff with no file mutations. Useful on large files or recursive directory runs.
  3. Recurse a directory. bun scrml promote --match src/ walks all .scrml files. Idempotent — already-promoted sites are skipped. Non-promotable sites are reported with a one-line reason and left untouched.
  4. Ignore the lint. It's info-level; the chain is valid scrml. Suppress via lint config lint.match-promotable per SPEC §28 if the call site is intentionally pre-promotion (e.g., prototyping).

Why info, not warning

Apps don't START at the tier ladder's top; they EVOLVE toward it. A Tier-0 if-chain on an enum-typed cell is a legitimate prototype shape — punishing it with a warning (let alone an error) would discourage the very migration path scrml is designed to support. I-MATCH-PROMOTABLE names the opportunity without nudging.

Sibling shape: W-MATCH-RULE-INERT is the next tier's analog — warning-level when a rule= on a <match> arm is legal-but-inert, nudging up to <engine>. The pair maps the two rungs of the ladder.

Related

  • <match> — Tier-1 element. Structural exhaustiveness over an enum.
  • <engine> — Tier-2 element. Full state-machine surface.
  • W-MATCH-RULE-INERT — warning surfacing the next-rung lift (match → engine).
  • E-MATCH-NOT-EXHAUSTIVE — the error you'd hit if a promoted <match> left a variant uncovered.

Specification

Normative text: SPEC §56 (promotion ergonomics + CLI surface), §56.2 (fire conditions), §56.3 (three message shapes), §56.5 (CLI verb shape + per-branch rewrite rules), §18.0.1 (target <match for=Type> block-form), §28 (lint suppression configs), §34 catalog row. Spec lives at compiler/SPEC.md .