t27c: four defects, three of them silent (Refs #2161) - #2720
Merged
Conversation
The Rust emitter's arm loop tested `arm.kind == NodeKind::Module`. The
parser builds every arm as `ConstDecl`, so the loop matched nothing and
the emitter printed
pub fn trit_negate(a: Trit) -> Trit {
return match a {
};
}
an empty match, with exit code 0, for a construct gen-c and gen-verilog
lower correctly. Three backends agreed and the fourth silently discarded
the function's logic while reporting success -- on the sentence the
language is sold with.
Leaving the pattern unqualified would have been worse than the empty
match: `match a { neg => .. }` is a BINDING in Rust. It compiles, and it
matches everything. The scrutinee's declared type is the enum, and
`var_types` already holds every parameter and local with its Rust type,
so a bare-identifier scrutinee resolves exactly; anything else falls back
to the return type, which is the rule ExprEnumValue already uses for the
same shorthand a few arms above.
Numbers and char literals are patterns in their own right and are not
qualified; `else` becomes `_`.
Measured on specs/base/ops.t27, master binary vs this one:
match arms emitted 0 -> 3
rustc errors 43 -> 42
The remaining 42 are Zig builtins leaking into the Rust output, a
separate defect this does not touch.
FROZEN_HASH resealed in the same commit (M5).
W578 decided paren-less `if cond { .. }` is legal t27 and paid to make
`parse_if_stmt` accept it. `parse_if_expr` was left behind on a hard
`expect(LParen)`, so specs kept failing with the identical
"Expected LParen, got Ident" that the statement parser's comment says was
fixed -- a reader who greps that diagnostic finds a note claiming the
opposite of what the code does.
The fix is the same six lines, including the struct-literal suppression:
without parentheses `Name {` opens the THEN branch, not a struct literal.
Measured, master binary vs this one, over all 747 tracked specs:
specs failing with "Expected LParen, got Ident" 8 -> 0
specs that parse 603 -> 605
specs that regressed 0
The class is gone; the spec count moves by 2 because six of the eight
carry further defects behind this one. That is the shape `t27c backlog`
already documents -- removing the single most frequent cause once moved
the compiling count 151 -> 151 -- and it is why the class count, not the
spec count, is the number this commit is about.
FROZEN_HASH resealed in the same commit (M5).
The lexer emits `..` and then a separate `=`, so the right-operand parser met `=b` and answered "Unexpected token in expression: Equals". 12 specs write this form. One of them is specs/math/constants.t27, which 259 of 746 specs import -- the module #2688 calls a "ceiling, not a backlog". Lowered to the exclusive range over `b + 1` rather than carried as a new operator: every backend already lowers `..` and none would know `..=`. For the integer loops this grammar has the two are the same range, and the alternative is a fifth spelling four emitters must each be taught. The fix belongs in the comparison chain, not in `parse_for_range`. My first attempt put it after `expect(DotDot)` there, which is dead code for this input: `parse_for_range` parses its start bound with the FULL expression grammar, and that grammar already consumes `..` as a binary operator, so control never reaches the `expect`. Found by measuring -- the probe still failed with the fix in place -- and reverted rather than left sitting. Measured over all 747 tracked specs, master binary vs this one: specs that parse 603 -> 609 regressions 0 Newly parsing: specs/math/constants.t27, specs/math/radix_economy.t27, specs/numeric/phi_ratio.t27, specs/vsa/jones_polynomial.t27. FROZEN_HASH resealed in the same commit (M5).
…2161) Both Rust `StmtFor` handlers read the capture from `children[1].name` and the body from `children[2].children`. The parser's bare `for x in xs { }` form builds TWO children -- iterable, body -- and puts the capture in `params`. So `children[1]` is the body block, whose name is the literal string "body", and `children[2]` does not exist. Every range loop in the corpus emitted for body in (1 .. 3) { } with exit code 0: the induction variable renamed to "body" and the loop body gone. Same class as the empty `match` two commits ago -- the fourth backend silently discarding the program's logic while reporting success. The capture is in `params` in BOTH shapes (`for (xs) |x| { }` pushes it there too), and the body is the LAST child in both, so one read serves each form. Verified end to end rather than by inspection -- generated Rust compiled and run: for i in 1..=3 { total = total + i } -> prints 6 for i in 1..3 { total = total + i } -> body present for x in xs { n = n + 1 } -> `for x in xs`, body present Parse count unchanged at 609/747; this is an emitter fix. FROZEN_HASH resealed in the same commit (M5).
This was referenced Aug 26, 2026
Closed
Contributor
PR DashboardGenerated at: 2026-08-26 17:13:32 UTC
Summary
Seal Status
|
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
The ratchet named both halves itself:
UNEXPECTED PASSES : 6
- specs/ar/explainability.t27 [parse] (fixed -- remove from the ledger)
- specs/math/constants.t27 [parse]
- specs/math/radix_economy.t27 [parse]
- specs/numeric/phi_ratio.t27 [parse]
- specs/physics/sacred_verification.t27 [parse]
- specs/vsa/jones_polynomial.t27 [parse]
UNEXPECTED FAILURES: 4
+ specs/math/constants.t27 [parse-no-discard]
+ specs/math/radix_economy.t27 [parse-no-discard]
+ specs/numeric/phi_ratio.t27 [parse-no-discard]
+ specs/vsa/jones_polynomial.t27 [parse-no-discard]
Those are the same four specs on both lists, and that is the whole story:
a spec that could not parse at all now parses and REVEALS that the parser
discards its `forall`-quantified properties. The defect moved from
invisible-because-blocked to visible-and-counted; it did not appear.
The four new entries cite #2474, the discard census, rather than a fresh
issue -- they are that population, not a new one.
Ledger 181 -> 179, max_entries 221 -> 219. Net LOWER, so the cap moves in
the direction the doc says it may move on its own; the two entries of
slack are gone rather than banked.
RATCHET: CLEAN -- 0 unexpected failures, 0 unexpected passes, 0 expiries.
Contributor
PR DashboardGenerated at: 2026-08-27 12:43:25 UTC
Summary
Seal Status
|
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
gHashTag
added a commit
that referenced
this pull request
Aug 27, 2026
* specs: restore the 2544 lines commit 4639b38 overwrote with a counter (Refs #2161) `4639b38cd` -- "fix(l3-purity): replace all Unicode with ASCII in 160 .t27 files" -- did not transliterate. It replaced the i-th non-ASCII character of each file with the ASCII digits of `i`: - fn ternary_not(a: i32) → i32 { + fn ternary_not(a: i32) 257 i32 { That transform is exactly reproducible from the pre-image, which is what makes the repair safe rather than a guess. The model was verified to reproduce the commit's output BYTE FOR BYTE before anything was written. A line is restored only when the line at HEAD is byte-identical to the transform of its pre-image line, so every line edited in the four months since is left untouched. 141 files, 2544 lines. Measured with the MASTER binary, so this is isolated from the compiler fixes in #2720: specs that parse 603 -> 610 regressions 0 The seven are exactly the "Expected LBrace, got Number ('257')" cluster. They failed not because they LOST an arrow -- `→` was never a token, the lexer discards it -- but because they GAINED an integer literal. One restored line, as a sample of what the other 2543 are: - assert jones_trefoil_at_phi() 1222 PHI + PHI * PHI within 0.1 + assert jones_trefoil_at_phi() ≈ PHI + PHI * PHI within 0.1 The comparison operator was that file's 1222nd non-ASCII character. This takes the RESTORE branch of the question in #2713 rather than transliterating, on two grounds. The pre-image is exact and a transliteration is a fresh authoring decision on 2544 lines. And the L3 ASCII rule is enforced by nothing today: 320 of 747 tracked .t27 files carry 88,033 non-ASCII characters at HEAD and no gate objects -- while TASK.md:128 records "0 non-ASCII remaining". scripts/repair/unscar_4639b38cd.py is committed, so the transliterate branch stays available and the operation is reproducible. * ratchet + width baseline: seven specs reach the gates for the first time (Refs #2161, #2713) Restoring the source moved seven specs from "does not parse" to "parses", and both ledgers noticed. Neither is new damage: these files had never reached either gate. RATCHET. The same seven appear on both of its lists -- out of `parse`, into `parse-no-discard`: UNEXPECTED PASSES : 7 specs/{base/ternary_encoding, base/ternary_memory, UNEXPECTED FAILURES: 7 isa/ternary_arithmetic, isa/ternary_bitwise, isa/ternary_deque, isa/ternary_gates, isa/ternary_shift}.t27 They parse and discard top-level tokens, which is the #2474 population. Proof that restoration REVEALED rather than CAUSED it: each restored file is byte-identical to its pre-image, checked with `diff`. Ledger 181 -> 181, `max_entries` untouched, because the move is one-for-one. WIDTH BASELINE. specs/base/ternary_memory.t27 emits `[3801183:0]`, and it is not an underflow -- derived independently and matching the emitter bit for bit: TritCell 32 + 8 + 64 + 32 = 136 TernaryWord 27*136 + 8 + 32 = 3,712 TernaryMemoryBank 1024*3712 + 32 + 64 = 3,801,184 464 KiB in one packed register. The backend computed it faithfully; the spec asked for something with no hardware form. TRIT_CAPACITY is 27 = 3^3, so the size is deliberate. Same class as the stdlib.t27 line already there, and recorded with the derivation for the same reason: so the next reader does not go looking for a subtraction that is not there.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four defects in
t27c, each measured before and after, each in its own commit withFROZEN_HASHresealed alongside (M5).Two of them are the same failure mode the project explicitly guards against: a green exit that is not a result.
1.
gen-rustemitted an emptymatchfor everyswitchThe arm loop tested
arm.kind == NodeKind::Module. The parser builds every arm asConstDecl, so the loop matched nothing:Exit code 0.
gen-candgen-veriloglower the same construct correctly — three backends agreed and the fourth silently discarded the function's logic while reporting success, on the sentence the language is sold with.Leaving the pattern unqualified would have been worse than the empty match.
match a { neg => .. }is a binding in Rust: it compiles, and it matches everything. The scrutinee's declared type is the enum, andvar_typesalready holds every parameter and local with its Rust type, so a bare-identifier scrutinee resolves exactly; anything else falls back to the return type — the ruleExprEnumValuealready uses for the same shorthand a few arms above.specs/base/ops.t27The remaining 42 are Zig builtins leaking into Rust — a separate defect this does not touch.
2. Paren-less
ifwas legal in statement position onlyW578 decided
if cond { .. }is legal t27 and paid to makeparse_if_stmtaccept it.parse_if_exprwas left on a hardexpect(LParen), so specs kept failing with the identical "Expected LParen, got Ident" that the statement parser's own comment says was fixed — a reader who greps that diagnostic finds a note claiming the opposite of what the code does.The class is gone; the count moves by 2 because six of the eight carry further defects behind this one. That is the shape
t27c backlogalready documents — removing the single most frequent cause once moved the compiling count 151 → 151 — and it is why the class count, not the spec count, is the number this commit is about.3. The inclusive range
a..=bThe lexer emits
..and then a separate=, so the right-operand parser met=b. Lowered toa..b + 1rather than carried as a new operator: every backend already lowers.., none would know..=, and the alternative is a fifth spelling four emitters must each be taught.This unblocks
specs/math/constants.t27— the module #2688 calls "a ceiling, not a backlog" — which 259 of 746 specs import.My first attempt at this was dead code. I put it in
parse_for_rangeafterexpect(DotDot). That function parses its start bound with the full expression grammar, and that grammar already consumes..as a binary operator, so control never reaches theexpect. The probe still failed with the fix in place, which is how I found it. Reverted rather than left sitting.Newly parsing:
specs/math/constants.t27,specs/math/radix_economy.t27,specs/numeric/phi_ratio.t27,specs/vsa/jones_polynomial.t27.4. Found in passing: every
forloop body was dropped ingen-rustBoth
StmtForhandlers read the capture fromchildren[1].nameand the body fromchildren[2].children. The barefor x in xs { }form builds two children — iterable, body — and puts the capture inparams. Sochildren[1]is the body block, whose name is the literal string"body", andchildren[2]does not exist:Exit code 0, induction variable renamed to
body, loop body gone. Same class as #1.The capture is in
paramsin both shapes and the body is the last child in both, so one read serves each form.Verified by running it, not by reading it
First time in this campaign a t27 spec reached a running program with the right answer.
Honest deltas across the whole suite
parsePRIMARY (corpus)parse-no-discardseal-verifyTwo numbers got worse and both are mechanical. A spec that could not parse at all now parses and reveals that it discards tokens — it moves from
parseintoparse-no-discard. And changing an emitter's output makes the stored seals stop matching; that is the "emitter drift" class the seal audit already separates from real staleness.Nothing regressed: no spec that parsed on master fails to parse here, checked at each of the three parser commits.
The suite exits 1 on master and exits 1 here, with the same
ACCEPTABLE: noverdict.Refs #2161