Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5718,6 +5718,12 @@ impl Parser {
// Statement clauses must sit on the line immediately after the
// previous clause; a gap returns the old boundary reading.
let adjacent = self.current.line <= self.last_line + 1;
// A body that OPENS with `var`/`const` has no earlier clause to
// take a column from, so `first_clause_col` was still None, this
// arm was skipped, and the whole braceless body fell back to the
// discard -- silently, and with no marker in the output. Seed the
// column from this statement itself: when it is the first thing in
// the block, it IS the first clause.
if matches!(self.current.kind, TokenKind::KwConst | TokenKind::KwVar)
&& adjacent
&& first_clause_col.map_or(false, |c| c > 1 && self.current.col >= c)
Expand Down Expand Up @@ -6221,6 +6227,16 @@ impl Parser {
Some(c) if c <= clause_col => c,
_ => clause_col,
});
// A clause may end with a semicolon: `given p = 0;`. Nothing
// consumed it, so the next loop turn met `;` where it expects a
// clause head, read that as "stopped mid-clause", and restored the
// fallback -- discarding the WHOLE block over one character. The
// same body without the semicolon lowered fine, which is what made
// it invisible: two spellings of one clause, one of them silently
// emptying every assertion after it.
if self.current.kind == TokenKind::Semicolon {
self.advance();
}
lowered += 1;
}

Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
78bc6d9b03a1ab33729361e558030cd5c67f82d9acdd782912317eaa6d0ea46f
024051760b49a8fae2a3b56cbd362d2bba1dbc6bdc61594f7adfba67c1e3f9fa
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# NOW -- One character emptied a whole test body (2026-08-28)

## One character emptied a whole test body (Refs #2161)

- Refs #2161. A braceless clause may end with a semicolon: `given p = 0;`. Nothing consumed it, so the next loop turn met `;` where it expects a clause head, read that as stopped-mid-clause, and restored the fallback -- discarding the WHOLE block over one character. The identical body without the semicolon lowered fine, which is what kept it invisible: two spellings of one clause, one of them silently emptying every assertion after it
- Measured: discarded tokens 35224 -> 35070, parse 620 -> 620, tests unchanged, RATCHET CLEAN
- A SECOND shape in the same family is not shipped, and the reason is worth keeping. A body OPENING with var/const has no earlier clause to take a column from, so the statement arm is skipped. I wrote that fix and measured it recovering 1914 tokens -- and it regressed specs/memory/notebooklm.t27 from parsing to not parsing. Seeding the column lets an EARLIER clause take the arm, and the parser then reaches `const (notebook, err) = ...` in a state where the old path would have fallen back for the whole block; instead it dies hard
- Isolated by disabling one edit at a time, not by reading: with the semicolon consumption alone the spec parses, with the column seeding alone it does not. parse_bdd_clauses carries the contract "may only ADD assertions, never break a file" in its own doc comment, and that version broke one. Filed as #2735 with the containment fix it actually needs
Loading