Skip to content

bootstrap: accept pub const Name(T) = struct as a parameterised type declaration - #2168

Open
gHashTag wants to merge 1 commit into
masterfrom
w699-generic-const-decl
Open

bootstrap: accept pub const Name(T) = struct as a parameterised type declaration#2168
gHashTag wants to merge 1 commit into
masterfrom
w699-generic-const-decl

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

Closes #2162

What this changes

pub const Name(T) = struct { ... } now parses as
ConstDecl(name, generic_parameters, StructExpr).

The AST contract was written before the implementation, in
architecture/ADR-008-parameterised-const-type-declaration.md. That ordering is
load-bearing rather than procedural: a test written after a patch tends to
describe whatever the patch happened to do, and would pass just as happily for a
parser that accepted the form for the wrong reason. The ADR states the shape the
tree must have, and the tests check that shape.

Measured [measured]

figure value
declarations of the form in the corpus 33, in 28 files, all with struct on the right
positive fixtures rejected by the old binary 6 of 6
positive fixtures accepted by the candidate 6 of 6
negative fixtures rejected by the candidate 7 of 7
cargo test --test generic_const_decl 16 / 16
cargo test --bin t27c 1537 passed, 0 failed, 2 ignored

Differential over the 634-spec library, /tmp/t27c.m2162base ->
/tmp/t27c.m2162fix: 633 unchanged, 0 field-loss, 1 strict-improvement
(specs/tri/collections/array.t27), 0 unknown.

Read that differential with its coverage caveat. The version of tri diffbin
that produced it counted files neither binary could parse as unchanged, i.e.
as agreement. Measured on another pair, 286 of 616 unchanged files were of that
kind. The figure above therefore bounds regressions over the files that were
actually measured
, not over the whole library. The fix for that conflation is
#2166 and ships separately, on purpose: a measurement tool and the thing it
measures should not change in the same commit.

The seven negative fixtures matter as much as the six positive ones. Without
them, "accepts 6 of 6" is equally consistent with a parser that accepts nearly
anything in that position.

Requires explicit human approval

bootstrap/stage0/FROZEN_HASH moves cd2822f2... -> 8ba6585a.... That is a
GOLD-RING seal. It is not a mechanical consequence of the patch and must not
be waved through as part of it. If the seal move is not approved, this PR does
not merge.

Deliberately not settled here

Both are left as needs-language-ADR. Guessing either from a patch would put a
language decision into a parser diff where no one would think to look for it.

Owner decision now recorded in the issue

An hourly tick independently re-measured #2162 and closed with blocked: waiting
on the language owner
while the decision had already been taken — because the
decision existed only in a working session. It is now written into the issue
itself. A decision that is not in the tracker does not exist as far as any
automated consumer is concerned.

…e declaration (Closes #2162)

The AST contract was written first, in architecture/ADR-008, and the patch was
made to satisfy it. That ordering is the point: a test authored after a patch
tends to describe whatever the patch happened to do, and would have passed for a
parser that accepted the form for the wrong reason.

Measured [measured]:
  * 33 declarations of the form across 28 corpus files, all with `struct` on the
    right-hand side -- so the form is used consistently and is not a one-off
  * old binary rejects 6 of 6 positive fixtures; candidate accepts 6 of 6
  * candidate rejects 7 of 7 negative fixtures, so acceptance is not blanket
  * cargo test --test generic_const_decl: 16 / 16
  * cargo test --bin t27c: 1537 passed, 0 failed, 2 ignored

Differential over the 634-spec library, /tmp/t27c.m2162base -> /tmp/t27c.m2162fix:
633 unchanged, 0 field-loss, 1 strict-improvement (specs/tri/collections/array.t27),
0 unknown. Read that with the coverage caveat now enforced by diffbin: the
`unchanged` category of the tool that produced it counted files neither binary
parsed as agreement, so the figure bounds regressions only over the files that
were actually measured.

This is a parser defect and not a corpus error, so none of the 28 files are
rewritten.

Not settled here, deliberately:
  * #2164, `Name(T)` in type application position. Accepting the declaration form
    does not imply accepting the use form; that needs its own ADR.
  * #2165, whether `test` is a reserved word.

bootstrap/stage0/FROZEN_HASH moves cd2822f2... -> 8ba6585a.... That is a
GOLD-RING seal and requires explicit human approval; it is not a mechanical
consequence of the patch and must not be waved through with it.
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-08-14 21:35:44 UTC

Summary

Status Count
Total Open PRs 24
PRs with Failing Checks 9
PRs with All Checks Green 15
READY 7
FAILING 9
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=8ba6585a3a29 != 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).

@github-actions

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

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

@gHashTag

Copy link
Copy Markdown
Owner Author

Review package for #2168 — measured, not merged

Prepared under the owner's order of 2026-08-15: prepare a reproducible commit,
a full uniform run and a review package, but do not merge.
Nothing here asks
for a merge, and merge-readiness is not claimed.

1. Reproducible pair, provenance captured at build time

commit SHA-256 seal
base b92872507f6c7619acce43e5ae262b1dc9c4cbf2 (master) 836e8bc4d9a8bafa… docs/evidence/seal_master-baseline.json
candidate a33413de5c0f3bf859c715238b847c9a88fb21b9 (this PR's head) ef99cc164c0c9d63… docs/evidence/seal_pr2168-candidate.json

Both were built by scripts/ci/rebuild_evidence.sh <commit> <label>, which
builds from a named commit and seals in the same step. The earlier pair
behind #2151 lives in /tmp with no recorded commit, and that is the difference
being corrected here: provenance not captured at build time cannot be recovered
afterwards, only invented.

The build path is a constant (/tmp/t27_seal_build) shared by builder and
verifier. This is load-bearing: two builds of the same commit from
differently named worktrees were measured to differ in 39,830,933 bytes,
because a debug build embeds the absolute path of its source tree.

2. Full uniform run over specs/

One timeout, 15 s, applied identically to both binaries. Five categories,
field-loss evaluated before strict-improvement:

corpus: 634 specs under specs (scratch excluded)
    348  unchanged
      0  field-loss
      1  strict-improvement      specs/tri/collections/array.t27
      0  malformed-input-tradeoff
      0  unknown
    285  not-evaluated           (285 both-error)
MEASURED COVERAGE: 349/634 = 55.0%

Coverage is 55.0 %, and it is printed next to the regression count rather than
folded into it.
The clean result covers the 349 files on which both binaries
returned a verdict and says nothing about the other 285, which neither binary
parses and which are therefore not evidence of agreement.

specs/scratch is not-evaluated, with the reason stated: at the same
uniform 15 s timeout the run exceeded this tick's budget and was stopped after
594 of the files. That partial output is not reported as a result — the walk is
alphabetical, so a prefix is not a sample.

3. What the change actually achieves — the measurement that matters

The corpus-wide run answers "did anything get worse". It does not answer "did
the declared thing get better". Measured over the 28 files that contain a
parameterised const declaration:

  27  error -> error
   1  error -> ok       specs/tri/collections/array.t27

ADR-008 acceptance is necessary and not sufficient: it unblocks one file in
twenty-eight.
The other 27 now fail later, on type application in type
position
fn empty() -> List(void), fn map(io: IO(T)). Filed separately
as #2174, because a repair that exposes a previously unobservable failure is a
new fact rather than a remainder of the old one.

This bounds the PR's claim: it removes one blocking cause and should not be
described as unblocking the standard library.

4. Fixture evidential force, and one unexpected result

Every fixture was run against the base binary as well, because a test that
passes before the change is a regression guard, not evidence of a fix:

fixture base candidate
pos_01pos_06 error ok as declared — the fix is load-bearing
neg_01neg_05 error error guard only, states no fix
neg_06_value_rhs ok error not as declared
neg_07_no_rhs ok error not as declared

The last two are a behaviour change beyond the ADR, so they are disclosed rather
than described as "hardening". Inspected: base does not accept them either. It
emits a ConstDecl named Answer / Opaque with empty children plus a
stray sibling StmtAssign / StmtExpr — a hollow declaration and a loose
statement, from one source line. That is the silent-loss class: exit code 0 over
a dropped declaration. The candidate replaces it with a specific diagnostic.

So base=ok in my own table was a metric of the exit code, not of
acceptance. The classifier was the first suspect and it was guilty. Recorded as
such, because it is the same failure mode as an aggregate that printed
"0 regressions" while thirteen files lost declared fields.

An owner decision is still required on whether narrowing these two forms is
wanted, since the ADR settled the struct RHS and is silent on a value RHS and on
no RHS at all.

5. Not claimed, stated so it travels with the numbers

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.

parser: generic const struct declaration 'pub const Name(T) = struct' does not parse

1 participant