feat(check): let-alias borrow propagation via alias-redirect (#57, 0064 iter 3)
Third iteration of spec 0064 (the #55-cutover hardening, #57). Closes false-positive class 2: Term::Let walked its value in Position::Consume, so (let a t (match a …)) over a borrow-mode t consumed t at the binding and tripped consume-while-borrowed -- a let-alias of a borrowed value read in a borrow position is not a consume. Fix: the Checker gains a scoped aliases: HashMap<String,String>. A (let a t body) whose value is a bare Var resolving to a tracked binder records a -> root(t) for the body scope and skips the consume walk of the value. A new resolve_alias helper maps a name to its root, preferring a real binder at each chain step -- so an inner binder named `a` (nested let, pattern binder, lam param) automatically shadows the alias with NO change to with_binder/walk_arm/Lam (the shadowing is resolved centrally in the resolver, not at three scattered introduction sites). Every binder-state lookup keyed by name resolves through the map first: use_var, the App borrow bump + decrement (pushing the resolved root to `lent` so the decrement matches), callee_arg_modes, and the reuse-as source. Design calls (orchestrator): - Alias REDIRECT, not state clone (spec scope decision 3): consuming the alias marks the single root consumed, so a real double-consume through an alias is still caught. A clone would track two independent binders and miss it (unsound). Pinned by the new let_alias_of_owned_double_consume_still_errors unit test: (let a p0 (seq a p0)) over an own heap p0 still fires use-after-consume. - resolve_alias prefers binders -> shadowing needs no edits to the binder-introduction sites. Lower-risk than clearing aliases at each. - reuse-as source (linearity.rs:663) is resolved through aliases too: it is the 4th binder-state-reading site; the spec's Fix 3 named three illustratively. Without it, (reuse-as <alias> …) would mis-fire reuse-as-source-not-bare-var on what IS a Var. Faithful completion of Fix 3. The diagnostic now reports the resolved root name (the actual consumed resource), not the surface alias. Closes the design/contracts/0008-memory-model.md let-alias carve-out (was "has not shipped yet") and extends its Ratified-by trailer to name linearity.rs, where the propagation now lives -- a contract change riding with the feature that forces it. RED-first: examples/c2_let_alias.ail added to harden_ownership_false_positives_are_clean (RED: consume-while-borrowed on count's t); GREEN after. Verified: c2 RED->GREEN; 120 linearity unit tests green; the soundness test fires; harden false-positive + heap double-consume guards green; cargo test --workspace green; bench/check.py 34/34, compile_check.py 24/24 stable. Diagnostic-only; no schema/type/codegen change. Class 4 (partition_eithers body rewrite) remains for the final iteration of spec 0064. refs #57
This commit is contained in:
@@ -337,12 +337,15 @@ twice (double-free under a hypothetical Consume + scope-close).
|
||||
subsequent work added codegen consumers, not new schema fields.
|
||||
- Does not introduce a new `Type` variant. Mode metadata stays
|
||||
flat on `Type::Fn` (see "Schema additions" above on why).
|
||||
- Does not cover let-aliases of borrowed values. A let-binder
|
||||
whose value is `Term::Var` referencing a `Borrow`-mode
|
||||
param is not yet propagated through; the param-mode gates
|
||||
treat such a binder as "owned" (its `current_param_modes`
|
||||
lookup misses, default = owned). This is a known carve-out
|
||||
shared by Iter A and the pre-tail-call shallow-dec arm; closing it is a propagation pass
|
||||
through let-bindings that has not shipped yet.
|
||||
- Covers let-aliases of borrowed values (spec 0064, class 2). A
|
||||
let-binder whose value is a bare `Term::Var` resolving to a
|
||||
tracked binder is recorded as an alias to that root in the
|
||||
linearity walk (`crates/ailang-check/src/linearity.rs`,
|
||||
`Checker.aliases` + `resolve_alias`); the binding does not
|
||||
consume the source, and every binder-state lookup resolves
|
||||
the alias to its root, so a borrow-position use of the alias
|
||||
does not consume the root while a real double-consume through
|
||||
the alias is still caught.
|
||||
|
||||
Ratified by: `crates/ailang-check/src/uniqueness.rs`.
|
||||
Ratified by: `crates/ailang-check/src/uniqueness.rs`,
|
||||
`crates/ailang-check/src/linearity.rs`.
|
||||
|
||||
Reference in New Issue
Block a user