A new linear let qualifier plus a genuinely new context-splitting
checking discipline — not another taint pass. local_taint/local_grade
are flat HashMap<LocalId, bool>s populated by an OR-shaped walk
(mem_tainted/ghost_tainted); linearity needs a real per-binding use
*count* (Zero/One/Many) with join-point reconciliation, because "used
once in each arm, symmetrically" must collapse to one safe use (only
one arm runs per elaboration) while "used in exactly one arm" must be
rejected as a leak — a distinction a single bit cannot express and
that an OR-taint gets backwards in both directions.
- strata-syntax/strata-hir: `linear let` parses and lowers exactly
like ghost let (StatementKind::LinearLet, structurally identical to
the already-landed GhostLet).
- strata-check: local_linear: HashMap<LocalId, LinearBinding> tracks
Zero/One(Span)/Many(Vec<Span>) per binding. linear_uses walks
CheckedExprKind counting and reconciling at the one join point the
checked IR has (Select): symmetric One/One collapses to one safe
use; asymmetric Zero/One rejects with linear_branch_mismatch naming
the unconsumed arm; any straight-line double-use rejects with
linear_double_use (fired once, at the point Many first forms, never
re-fired as it propagates upward). An end-of-scope sweep rejects
bindings still Zero at frame exit with linear_never_consumed —
genuinely new infrastructure, since mem/ghost taint are pure
rejection-at-sink checks with no "did you finish" obligation.
A soundness fix beyond the design doc's literal prose, found and
fixed during implementation: per-statement accumulation is confined
to locals declared in the same block() frame (frame_linear), never
an outer frame's locals — without this, a nested block entered while
checking a Select's arms would double-count uses the owning frame's
own fold already sees, and a bare `if` (no checked representation at
all) would let uses inside it silently leak past the missing-else
obligation. A linear local live across any unchecked/Unsupported
construct (bare if, match, anything without a CheckedExprKind) is
conservatively indistinguishable from "never referenced" and
rejected via the existing never-consumed rule, rather than silently
accepted.
- No sink-rejection pass, unlike grade 0: a linear value legitimately
reaching a register/mem/output is normal (D-039's release-as-
operation), only the count matters, not the destination.
- strata-arch-ir/strata-circt: no changes, confirmed directly — a
passing linear-checked design's compiled MLIR diffs byte-identical
(module name aside) against the same design with an ordinary let.
Verified end-to-end via the real CLI: the three bug fixtures each
produce exactly their targeted diagnostic and fail compile; the good
fixture checks clean and its MLIR was diffed directly against its
plain-let twin, confirming zero lowering difference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3ee33b0500versecafe committed on 8/20/2026, 4:18:11 AMparenta0828f6