Drives the RED test from a11cb7c to GREEN. Bug-fix iter
bugfix-over-strict-mode-ctor-rebuild-consume (debug -> implement
mini). Check-only lint-precision fix; zero codegen / runtime / ABI /
schema / DESIGN.md change.
The [over-strict-mode] lint's consume-detection
(any_sub_binder_consumed_for / pattern_has_consumed_heap_binder,
crates/ailang-check/src/linearity.rs) only recognised a consume of an
(own (con T)) param when a heap-typed pattern-binder was moved out of
`match p`. When p was destructured into purely primitive fields fed
into a Term::Ctor rebuilding p's own ctor, that genuine
dismantle+rebuild consume was invisible, so the lint spuriously
advised "(borrow ...) would suffice". An LLM author "fixing" that by
flipping an export own->borrow would silently invert the ABI
ownership contract — why a low-severity advisory FP got a real
RED-first fix.
Fix: a second recognition path in any_sub_binder_consumed_for — a
`match p` arm destructuring binders out of p's ctor that references
any of them (primitive or not) inside a Term::Ctor's args in the arm
body genuinely consumes p. Two pure helpers: ctor_uses_any_binder
(finds a fresh-allocation Term::Ctor reachable in the arm body) +
term_mentions_any_binder (deep free-var scan, so binder flow through
an intervening expr like (+ acc px) is recognised, not only a bare
Var arg). Conservative toward NOT suppressing: a ctor ignoring p's
payload still warns (negative-control verified). Over-strict-only —
by-name-shadowing imprecision is extra-silence, never under-strict
(Known debt in the journal + the fn doc). The two stale doc comments
that mis-attributed this FP to a nested `match` corrected for
doc-honesty (debugger concern #2 — same code region, in-scope).
Boss-verified independently: RED test now GREEN; full
cargo test -p ailang-check 108/0 lib + every binary 0-failed with NO
existing test modified; ail check examples/embed_backtest_step_tick.ail
no longer warns over-strict on st/tick (exit 0);
embed_backtest_step_tick_borrow.ail + M3 embed_backtest_step_record.ail
still clean; embed_tick_e2e + bench posture untouched.
+166/-20 in crates/ailang-check/src/linearity.rs only. Journal +
stats + INDEX line in this commit.
3.2 KiB
iter bugfix-over-strict-mode-ctor-rebuild-consume — over-strict-mode FP on ctor-rebuild-from-primitive-fields
Date: 2026-05-18
Started from: a11cb7cc9f
Status: DONE
Tasks completed: 1 of 1
Summary
The over-strict-mode lint spuriously fired (borrow ...) would suffice on (own (con T)) params whose body destructures T into
purely primitive fields and then feeds those fields into a
Term::Ctor that builds a fresh allocation. The dismantle+rebuild
is a genuine consume of the scrutinee's box (a borrowed value's
payload cannot be taken apart and re-packaged), but the lint's
consume-detection only recognised a heap-typed pattern-binder
being moved out — primitive fields are filtered by
pattern_has_consumed_heap_binder, so the rebuild was invisible.
This is the false positive behind embed_backtest_step_tick.ail
warning on both st and tick. The fix adds a second recognition
path in any_sub_binder_consumed_for: for a match p arm that
destructures binders out of p's ctor, if any of those binders
(primitive or not) is mentioned anywhere inside a Term::Ctor's
argument subtrees in the arm body, the param is genuinely consumed
and the lint stays silent. Over-strict-only: a param merely read
(no ctor build) still warns, proven by a negative control. No
existing test changed; doc-honesty corrections applied to the two
stale comments that mis-attributed this defect to nested match.
Per-task notes
- iter bugfix-over-strict-mode-ctor-rebuild-consume.1: added
collect_pattern_binders-based ctor-rebuild-consume recognition toany_sub_binder_consumed_for's matched-scrutinee arm loop incrates/ailang-check/src/linearity.rs, plus two pure private helpers (ctor_uses_any_binder— finds a fresh-allocationTerm::Ctorreachable in the arm body;term_mentions_any_binder— deep free-var scan of that ctor's args, so the binder reaching the ctor through an expression like(+ acc px)is recognised, not only a bareVararg). Corrected the stale "Known limit" doc onany_sub_binder_consumed_forand the matching "Known debt: deeply-nested-match-on-sub-binder" block in the module header — both previously mis-attributed the FP to a nested match; the real mechanism is the unrecognised ctor-rebuild consume, independent of match nesting.
Concerns
(none)
Known debt
term_mentions_any_binder's binder scan is by name and does not track shadowing. A destructured primitive binder shadowed and rebound before reaching the ctor would still be counted. This is not a corpus shape, and the resulting imprecision is strictly in the over-strict (extra-silence) direction the cause explicitly permits — never under-strict, so no soundness risk. Documented in the function's own doc comment; left as-is per the minimal-fix constraint.
Blocked detail
(not blocked)
Files touched
- crates/ailang-check/src/linearity.rs (+166 / −20): lint-detection fix + two helpers + two doc-honesty corrections. Check-only; no codegen / runtime / ABI / schema / DESIGN.md change.
Stats
bench/orchestrator-stats/2026-05-18-iter-bugfix-over-strict-mode-ctor-rebuild-consume.json