# iter bugfix-over-strict-mode-ctor-rebuild-consume — over-strict-mode FP on ctor-rebuild-from-primitive-fields

**Date:** 2026-05-18
**Started from:** a11cb7cc9f912a0d7ee8a56b3f53255bd101e327
**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
  to `any_sub_binder_consumed_for`'s matched-scrutinee arm loop in
  `crates/ailang-check/src/linearity.rs`, plus two pure private
  helpers (`ctor_uses_any_binder` — finds a fresh-allocation
  `Term::Ctor` reachable 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 bare `Var` arg). Corrected the stale "Known limit" doc
  on `any_sub_binder_consumed_for` and 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
