Skip to content

One rule, four positions, three treatments - #2897

Merged
gHashTag merged 2 commits into
masterfrom
narrowing-at-declaration
Aug 29, 2026
Merged

One rule, four positions, three treatments#2897
gHashTag merged 2 commits into
masterfrom
narrowing-at-declaration

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

types_compatible rejects narrowing F64 -> F32, with a comment naming the
issue that added it. Where does that rejection actually apply?

position compared? verdict
assignment x = d; yes error
argument p(d) yes warning — printed under a Typecheck OK header
declaration var x: f32 = d; no silent
return -> f32 { return d; } no silent

A soundness fix guarding one of four narrowing sites guards one of four.

What this adds

The declaration comparison, at the severity the argument position already uses
— a warning, so no ratchet can move. Cost across the whole corpus:

18 warnings in 13 files

I expected noise and got a work list.

The check found the bug that made it necessary

Two of the 18 read Str <- F64:

var period_str : &str = "83.333";

infer_expr returns Str when the literal's value starts with a quote. The
parser marks the node extra_kind: "string", and the lexeme does not always
carry the quote — so a quoted string fell through to the float branch, and any
string whose text parses as a number was typed as that number
.

"hello" was fine only by luck: it does not parse as a float, so it landed on
Unknown, which is compatible with everything and therefore silent.

Two silences composed — the declaration was never compared, and the value
that would have failed the comparison was mistyped. Neither was visible alone.

Fixed by reading the marker the parser already sets.

Result

before after
specs that typecheck 627 628 (specs/pins/emitter_xdc.t27)
regressions 0
bootstrap test ratchet "No new failures. Baseline holds."
remaining narrowing warnings 16 (8 U64 <- F64, 7 F32 <- F64)

The remaining 16 are countable debt rather than a silence. 18446744073709551552
in var cleared : u64 = z & 18446744073709551552 is typed F64 because it
exceeds i64 — that is the U64 <- F64 family, named here and not fixed.

The return position is still not compared. Also named here and not fixed:
making the declaration check an error rather than a warning would be the
consistent choice and would fail specs that pass today — the owner's call.

Controls

control expected got
var s : &str = "83.333"; Str, no warning Str
var s : &str = "hello"; Str Str
var x: f32 = d; (d is f64) warning warning
every previously-passing spec unchanged 627 of 627, 0 broken
bootstrap tests no new failures baseline holds

Gates, run locally

specs generate 0 · specs parse 0 · conflict markers 0 · seals fresh 0 ·
types ratchet 0 · skill check 0 · unparsed probe 0 · cargo test -p tri 0 ·
bootstrap test ratchet 0. FROZEN_HASH updated in the same commit, per M5.

Refs #2864

`types_compatible` rejects narrowing F64 -> F32, with a comment naming the
issue that added it. Measured where the rejection actually applies:

  assignment  x = d;                  compared -> ERROR
  argument    p(d)                    compared -> warning, under "Typecheck OK"
  declaration var x: f32 = d;         NOT COMPARED
  return      -> f32 { return d; }    NOT COMPARED

A soundness fix guarding one of four narrowing sites guards one of four.

This adds the declaration comparison, at the severity the argument position
already uses -- a warning, so no ratchet can move. Cost across the corpus: 18
warnings in 13 files. I expected noise and got a work list.

THE CHECK FOUND THE BUG THAT MADE IT NECESSARY. Two of the 18 read
`Str <- F64`:

    var period_str : &str = "83.333";

`infer_expr` returns Str when the literal's VALUE starts with a quote. The
parser marks the node `extra_kind: "string"` and the lexeme does not always
carry the quote, so a quoted string fell through to the float branch, and any
string whose text parses as a number was typed as that number. `"hello"` was
fine only because it does not parse as a float and landed on Unknown, which is
compatible with everything and therefore silent.

Two silences composed: the declaration was never compared, and the value that
would have failed the comparison was mistyped. Neither was visible alone.

Fixed by reading the marker the parser already sets:

  specs that typecheck   627 -> 628   (specs/pins/emitter_xdc.t27)
  regressions            0
  bootstrap ratchet      "No new failures. Baseline holds."
  remaining warnings     16  (8 U64 <- F64, 7 F32 <- F64)

The remaining 16 are countable debt rather than a silence. The return position
is still not compared; that is named here and not fixed.

FROZEN_HASH updated in the same commit, per M5.

Refs #2864

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gHashTag
gHashTag enabled auto-merge (squash) August 29, 2026 22:07
…ation

# Conflicts:
#	.claude/skills/ci-gates/SKILL.md
@github-actions

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-08-29 22:09:27 UTC

Summary

Status Count
Total Open PRs 8
PRs with Failing Checks 7
PRs with All Checks Green 1
READY 0
FAILING 7
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=472b902a8cef != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@gHashTag
gHashTag merged commit c5d130d into master Aug 29, 2026
26 of 28 checks passed
gHashTag added a commit that referenced this pull request Aug 29, 2026
#2897 found that #920's F64 -> F32 rule held at an assignment (error) and a
call argument (warning), and nowhere else. It added the declaration. This adds
the RETURN -- the fourth and last position -- by threading the enclosing
function's declared return type through `check_stmt`.

It reported 277 warnings in 31 files, 255 of them identical: `returns F64 where
F32 is declared`. That is not a work list.

ROOT CAUSE. A float literal committed to F64 while a non-negative INTEGER
literal, six lines above in the same match arm, was already context-polymorphic
-- with a comment explaining that pinning it had caused 27 false errors. Nobody
had applied the same reasoning to floats, so `var x: f32 = 1.0;` was a
narrowing and `return 1.0` from an f32 function was a warning.

Made symmetric. The rule survives where it was aimed, and the control is the
rule's own case rather than the noise:

  x = d      (d: f64, x: f32)   still an ERROR
  x = 2.0    (literal)          accepted
  return d   (computed)         warning
  return 1.0 (literal)          accepted

A binary expression is not a literal, so a computed narrowing still errors.

  specs that typecheck   608 -> 615   zero regressions
  narrowing warnings     293 -> 21    all 21 integer, a real work list
  bootstrap ratchet      "No new failures. Baseline holds."

The +7 are exactly the family the census pointed at from the start: gf8, gf12,
gf20, gf24, gf32, and the two goldring compound-assignment specs.

ALSO: an integer literal above i64::MAX failed the i64 parse, reached the float
branch and became F64, so `var cleared : u64 = z & 18446744073709551552` read
as a float initialising an integer -- eight sites across five ternary specs,
all bit masks. It now parses as u64/u128 and stays context-polymorphic, matching
the branch above it.

FROZEN_HASH updated in the same commit, per M5.

Refs #2864

Co-authored-by: Claude <noreply@anthropic.com>
gHashTag added a commit that referenced this pull request Aug 29, 2026
* Remove emitter_xdc from the corpus ledger; it passes now

The corpus ratchet has been red on master for at least three consecutive runs
and the reason is mine. #2897 fixed the string-literal inference bug and
`specs/pins/emitter_xdc.t27` began to typecheck. I updated
`tools/specs_generate_baseline.txt` and did not know about
`docs/reports/suite_expectations.json`, which still listed the spec as
expected-to-fail.

An UNEXPECTED PASS is a ratchet failure by design: docs/CORPUS-RATCHET.md says
"you fixed something. Remove the entry and lower max_entries".

  entries      151 -> 150
  max_entries  151 -> 150      (down, the only direction the cap may move)
  RATCHET: CLEAN

Control both ways: with the entry restored the ratchet reports
`UNEXPECTED PASSES: 1` and exits 1; without it, exit 0.

This is #2892's lesson one level up. That day I found a fix that had not
travelled from one command to its neighbour; here it did not travel from one
LEDGER to its sibling. When a repair makes a spec pass, grep every file that
names the spec -- not only the ledger you happen to know.

Refs #2864

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* skill: the second ledger

Refs #2864

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants