Issue #585: NK2302 follow-ups: over-refusals, the coarse binding of a local, one caret at a declaration

Open and answer this issue on GitHub.

language, compiler, ready · opened 2026-10-12 · updated 2026-10-13 · 2 comments

How it came up

#580 (ADR-283 D25/D26) is built (46a0ba06 … 61e21b9d). The builders measured over-refusals (sound programs refused) and left three imprecisions. Nothing here makes an unsound program compile; they refuse more than needed. A rustc error must never reach a user (Part III C.1), so each fix keeps that.

Today

  1. A local without a let annotation that a method call or a mut parameter touches always binds the other arguments (B1/B2). Over-refused: a few valid programs. Measured: 0 over-refusals in tools/*.nika, tests/**, examples/**, benches/**.
  2. The result walk does not know the fields of a type declared in another file (world.words.resolve(..) beside a second view parameter is refused; the previous builder worked around it by passing the interner as a parameter). declared.nika / the ledger would have to give the fields. Noted in docs/mitigation-hurdles.md section 1.
  3. A declaration-site refusal (D26) has one caret; a way out in a body has one label per way out.
  4. Std entries with no ref(..) map to every argument including the key (self.cache.get(world.key) is refused); a result typed as the receiver’s type parameter should map to the receiver only.

To build

  1. Tests first: the over-refused programs of 1, 2 and 4 (accepted and compiled in rustc after the fix) and the dangle-attempt set that must stay NK2302 (tests/result_source.rs).
  2. Tighten B1/B2 by the type of the local (unannotated: infer it from the checker’s type) and the callee’s mut parameter type.
  3. Read the fields of a type from another file from the ledger.
  4. D26: carets on every parameter named.
  5. A std entry whose result is typed as the receiver’s type parameter maps to the receiver only.
  6. Callgrind (Ir + mispredictions, --branch-sim=yes) on a large nikaia lower before and after; scripts/c6/oracle.py 0 differences; Fixes #<this issue>.

Related: #580, #579.

Comments

keywan-ghadami, 2026-10-12

A fifth over-refusal: a view stored in a local struct that never leaves the function (found 12 Oct, 0.0.861)

struct Holder {
    name: String,
}

fn two(s: ref String, c: bool) -> i64 {
    if c {
        let h = Holder { name: s }
        return h.name.len()
    }
    let g = Holder { name: f"{c}" }
    return g.name.len()
}

This is refused with NK2302: “two keeps s after this call … It’s stored in Holder.name. Holder.name is a ref String“. Two things are wrong:

The same holds when the field is fed by if c { s } else { f"{c}" } directly. There is no if/match in two, so this is not #596’s or #562’s. It is sound, and nothing reaches rustc. A program must write s.clone() it does not need.

For the To build list:


Generated by Claude Code

keywan-ghadami, 2026-10-13

The fifth over-refusal (comment above) is built in a58fca3f (0.0.866).

Items 1-4 of the body are untouched.


Generated by Claude Code

Back to Open Issues