Skip to content

t27b: do not lower bodies no test reaches -- Sema-level refusals in an unreached fn are withdrawn from one plan (Closes #10897) - #10999

Merged
gHashTag merged 5 commits into
masterfrom
t27b/lazy-bodies-10897
Oct 19, 2026
Merged

gHashTag merged 5 commits into
masterfrom
t27b/lazy-bodies-10897

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

Closes #10897. Part of epic #10879 (t27b for t27o), first blocker.

What

Zig type-checks (Sema) only what an analyzed declaration uses. A fn that no test, invariant or constant reaches is parsed and name-checked (AstGen) and nothing more. t27b lowered every fn and refused the first construct it could not lower, so a t27o spec whose only trouble sat in an unreached body (return TelegramStarsGateway;, a Rust unit struct written as a bare type name) was refused while t27c test-report passes it.

This PR carries the lazy rule of lazy_sig_plan.t27 (signatures, #7875) from the signature to the body.

The rule, in the plan

specs/tri/t27b/lazy_sig_plan.t27 (module ls, gen/rust/tri/t27b/lazy_sig_plan.rs), no new spec:

  • body_plan(construct, detail): for each refusal recorded while lowering the body of a fn nothing analyzed reaches, UNRESOLVED (Sema's, withdrawn) or KEEP (AstGen's or the parser's, kept). KEEP classes, each pinned against t27c test-report on a fn no test reaches: break/continue outside a loop, a local shadowing a module var, two else prongs, a char prong, a char or string literal Zig cannot read ('\0'), a struct field set twice, _ = p; of a parameter the fn uses, and the spellings t27c prints verbatim that Zig cannot parse (tuple types, tuple assignment and destructuring, map types, array literals kept as text). Everything else (type mismatch, array literal, builtins t27b lacks, a unit struct as a value, an unresolved callee) is Sema's.
  • withdrawn_come_back(names_missing, bare, eql_used): after every body is lowered, the withdrawn refusals return when AstGen refuses the file anyway: some fn names something declared nowhere, or the module has no test and no invariant (t27c writes std only there) yet t27c prints a string == as std.mem.eql.
  • zig_knows_in(name, prelude), is_undefined: std/assert are Zig's only with the prelude; a -> undefined result is now a lazy signature type.
  • Glue: lazy_body and restore_held in cli/t27b/src/lower/unanalyzed.rs, called from function(). A reached fn, an invariant, a test and a bench keep every refusal.

It subsumes the four per-construct stubs: try_stmt, return undefined, & of a repeated literal, unanalyzed_undefined_ret (all deleted).

Hand-written lines in cli/t27b (src and tests): +70 -79, net -9

lower.rs +14 -41, lower/unanalyzed.rs +38 -23 (the gate's per-file limit is 40), tests/ +18 -15. The gate CI uses (check_budget of gen/c/policy/own_language.c) gives rc=0 on origin/master...HEAD. Gen files regenerated with t27c gen-rust, seals re-saved and verified (MATCH). python3 -I tools/dupe_scan.py ok (no new group) with the corpus copy outside specs/; t27c dupes --name finds each new pub fn in one place.

Measured (binaries built from master b258b90 = "before" and from 2cf87d6, the first commit = "after")

t27o corpus of #10879 step 1, t27b corpus specs/t27o_corpus/{rust,zig} --jobs 3 --timeout-ms 20000 --blockers:

specs pass before pass after fail unsupported before -> after front end
Rust 492 387 (146 + 241 vacuous) 420 (171 + 249) 2 -> 2 97 -> 64 6 -> 6
Zig 724 706 (560 + 146) 710 (562 + 148) 9 -> 9 9 -> 5 0 -> 0
total 1216 1093 (89.9 %) 1130 (92.9 %) 11 106 -> 69 6

JIT/interpreter mismatch 0, codegen limit 0, timeouts 0 in both.

The 58-spec real gap (t27c passes, t27b blocked) shrinks to 21: +37, none lost. All 24 specs whose blocker was ExprIdentifier(undeclared) pass, and 13 more whose unreached bodies held a type mismatch, an array literal or @floatFromInt. The 24: multibots__rust__rings__SILVER_RING_PY00__src__telegram_stars, trios trios_bridge BR_00/01/02, trios_claude CL_00/01/02, trios_cli__src__gh, trios_data DT_00/01/02, trios_doctor SILVER_RING_DR_04, trios_hybrid HY_00/01, trios_kg KG_00/01/02, trios_model MD_00/01, trios_precision_router PR_00/01, trios_sdk SD_00/01/02 (all __src__lib except where named). The 21 left: 10 ConstDecl(slice), 6 ExprArrayLiteral, 2 ConstDecl, 1 ExprCall(undeclared fn), 1 @floatCast, 1 FnDecl(reference not yet implemented); the constants are the siblings #10899 and #10903 (reachability of a module const is a separate set: analyzed in ast_shape.t27 treats every constant as a root), so this PR does not touch module constants.

Reference agreement, all 37 newly passing t27o specs (not a sample), t27b corpus --reference t27c: t27c compiles every one and passes every test (37 of 37), reference_disagree 0 files, 0 tests of 37 compared. A first run (39 specs, before withdrawn_come_back) found two that t27c could not compile (trinity_loss tests/loss: assert in a fn of a module with no test, no __t27_assert_fail; trios_algorithm_arena SR_ALG_00: a string == printed as std.mem.eql with no std); that is withdrawn_come_back, and both are refused now.

Repository corpus, t27b corpus specs --jobs 3 --timeout-ms 20000 (2389 spec files outside the t27o copy):

pass pass, 0 asserts fail blocked timeout codegen
before 1976 4 16 385 7 1
after 1976 6 16 380 10 1

Diff per file: 5 blocked -> pass (compiler/parser, compiler/rust_repeat_counts, t27f/deps_card, port/tools/check_graph_law8, port/tools/check_json_parses), and 3 pass -> timeout: claw/suggest_feed, t27f/spec_graph, tools/t27o/rust_convert, all three at the 20 s limit under a machine load of 20-40; run alone, before and after binaries give the same result (36/36 in 51 s and 35 s, 14/14 in 1.35 s both, 129/129 in 16 s and 18 s). Per-test verdicts of every file that passed before are identical. No spec that passed before fails or is refused now.

The five repository specs against t27c, one t27c test-report each: parser 10/10 and deps_card 22/22 pass in t27c too. The other three t27c cannot compile:

  • rust_repeat_counts: Zig "incompatible types" in a reached fn ([value; COUNT32 * 2 - 1]). t27b passes it with or without the unreached empty() that blocked it before, and the master binary passes the file with empty() removed: an old laxness the first refusal hid, not this rule. Not fixed here.
  • check_graph_law8: @stdout, "invalid builtin function" (AstGen).
  • check_json_parses: text.trim() == "" printed as std.mem.eql in a module with no test.
    The second commit keeps the last two refused (see below).

Second commit: NOT built or run

The shared Mac hit a full disk during this work and the coordinator forbade a cargo build, so 57c3db764 (zig_builtin in lazy_sig_plan.t27, string_eq in ast_shape.t27, one expression in restore_held, str_eq setting eql_used before its coercions) was only checked on the t27 side: t27c test-report lazy_sig_plan 16/16 and ast_shape 14/14, none vacuous, seals verified, gen regenerated, hand-line gate rc=0. The measurements above are all from the first commit. CI builds and runs the second one; expected effect: check_graph_law8 and check_json_parses go back to blocked, the 37 t27o specs are unchanged. If the glue does not compile, drop that commit; the first commit stands alone.

Tests

cargo test --release -p t27b (first commit and the eql_used move): all 14 test targets pass, 161 tests. Eight tests built sources whose fn nothing called and expected that fn's refusal; the reference compiles those sources, so each source now has a test that calls the fn (slice_rejections_are_precise, untyped_locals_follow_the_reference, parameter_discard_rejections, type_alias_rejections, tuples_rejections, wide_integer_constants_fold_or_are_refused, and the helpers of f32_refusals_name_the_construct, f64_refusals_name_the_construct, shift_refusals_name_the_construct). rejected() now prints the source it was given. conformance/lazy_signature.t27 gains the unit struct returned as a value (t27c test-report passes it 3/3); tests/tail.rs pins the negative controls: a test that calls gate_new stays refused (unknown name Gate; t27c BLOCKED: "expected type 'spec.Gate', found 'type'"), and a call to a fn declared nowhere stays refused (t27c BLOCKED: "use of undeclared identifier").

Probes of the KEEP classes against t27c (fn no test reaches): break outside a loop, '\0', a call to a fn declared nowhere, two else prongs, a duplicate struct field, a char prong, a tuple type and _ = p; are BLOCKED by AstGen and t27b refuses each; an enum E::V, try, return undefined, an array repeat, a type mismatch, a missing struct field, an unknown field, an if as a left operand and return 5 in a void fn compile in t27c and t27b passes each.

Not measured, and why

  • tri t27b ratchet: it needs a reference verdict for every spec, a whole-repository --reference run, which the disk alert stopped. CI runs t27b-native-ratchet; expect UNEXPECTED PASS for specs/compiler/parser.t27 and specs/t27f/deps_card.t27 (both are blocked rows in docs/reports/t27b_expectations.json, and t27c passes both). The ledger is left to the steward (t27b ledger steward: rules and job card, so spec PRs stop editing the ledger #8654).

Traps found

  • The reference writes std and __t27_assert_fail only into a module with a test or an invariant. A test-less module whose fn names std or assert, or compares a string with ==, is BLOCKED by AstGen whatever the fn is. t27b passes such a module when the fn lowers (a laxness older than this PR, and visible in the 0-assert passes); this PR only refuses to create new such passes.
  • t27b corpus --reference counts a file the reference cannot compile as "does not count against t27b". Read the BLOCKED list of a reference run, not only reference_disagree.
  • Labels are not enough: ExprStructLit is both Sema's ("Duo is u8, not a struct") and AstGen's ("initialised twice"), so body_plan also reads the detail.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-19 10:57:01 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 48
PRs with All Checks Green 2
READY 1
FAILING 48
PENDING 0
NO CHECKS YET 0

These columns do not partition: 1 + 48 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=2d301cdb4a58 != 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 pushed a commit that referenced this pull request Oct 19, 2026
…ate copy (Closes #10897)

Review of #10999: the body plan's suffix test duplicated ends_with from
specs/tri/t27b/literal_plan.t27, a module in the same directory. The two
modules share no name, so the use splice pulls ends_with alone (#10556 does
not bite). gen/rust regenerated, seal re-saved and verified; t27b test 16/16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-19 11:01:31 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 47
PRs with All Checks Green 3
READY 2
FAILING 47
PENDING 0
NO CHECKS YET 0

These columns do not partition: 2 + 47 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

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

claude and others added 4 commits October 19, 2026 18:32
…n unreached fn are withdrawn, from one plan (Closes #10897)

Zig type-checks only what an analyzed declaration uses, so a fn nothing reaches is parsed and
name-checked (AstGen) but never type-checked (Sema). t27b lowered every fn and refused the first
construct it could not, so a t27o spec whose only trouble is `return TelegramStarsGateway;` (a Rust
unit struct written as a bare type name) was refused while t27c passes it.

The rule is lazy_sig_plan.t27's, carried from the signature to the body:

- lazy_sig_plan.t27 `body_plan(construct, detail)`: which refusals recorded in an unreached body are
  AstGen's (kept: break outside a loop, a local shadowing a module var, two `else` prongs, a char prong,
  a char/string literal Zig cannot read, a struct field set twice, `_ = p;` of a used parameter, and
  the spellings t27c prints that Zig cannot parse: tuple, map type, tuple assignment, array literal
  text) and which are Sema's (withdrawn). Every kept class was pinned against `t27c test-report` on a
  fn no test reaches.
- `withdrawn_come_back(names_missing, bare, eql_used)`: the withdrawn refusals return after every body
  when some fn names something declared nowhere, or when the module has no test and so no `std`
  (t27c writes `std` only into a module with a test or an invariant) yet prints a string `==` as
  `std.mem.eql`.
- `zig_knows_in(name, prelude)` and `is_undefined`: `std`/`assert` are Zig's only with the prelude; the
  `-> undefined` result becomes a lazy signature type.

It subsumes the four per-construct stubs (`try`, `return undefined`, `&` of a repeated literal,
`-> undefined`). A reached fn keeps every refusal; conformance/lazy_signature.t27 gains the unit
struct returned as a value, and tests/tail.rs pins that a test calling it, and a call to a fn
declared nowhere, stay refused. Five tests whose sources never reached the fn they refuse now call
it (the reference compiles them as written).

Hand-written lines in cli/t27b: +69 -78 (net -9).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…file anyway -- invalid builtins and a string == in a module with no test (Closes #10897)

Found by running t27c test-report on the five repository specs that the first commit turned from
blocked to pass: three of them the reference cannot compile.

- specs/tri/t27b/lazy_sig_plan.t27 `zig_builtin`: `@stdout` (specs/port/tools/check_graph_law8.t27) is
  "invalid builtin function" to AstGen in every fn, so `body_plan` keeps an `ExprCall(@name)` refusal unless
  Zig 0.16 has that builtin; `@floatCast`, `@floatFromInt`, `@max` and the rest of the list stay withdrawn.
- specs/tri/t27b/ast_shape.t27 `string_eq`: every `==` / `!=` with a string literal operand, which t27c
  prints as `std.mem.eql` (specs/port/tools/check_json_parses.t27: `text.trim() == ""`); `restore_held` asks
  it besides `eql_used`, which `str_eq` now sets before its coercions (it was set only after they held, so
  a refused `==` never counted: SR-ALG-00 of trios-algorithm-arena).

NOT built or run on this Mac: the disk alert forbade a cargo build, so only the t27 side was run
(`t27c test-report`: lazy_sig_plan 16/16, ast_shape 14/14, none vacuous; seals saved and verified; gen
files regenerated with `t27c gen-rust`). The Rust glue is one expression in restore_held. The first
commit is the last one measured; CI builds this one.

Hand-written lines in cli/t27b: unchanged (+70 -79, net -9).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ate copy (Closes #10897)

Review of #10999: the body plan's suffix test duplicated ends_with from
specs/tri/t27b/literal_plan.t27, a module in the same directory. The two
modules share no name, so the use splice pulls ends_with alone (#10556 does
not bite). gen/rust regenerated, seal re-saved and verified; t27b test 16/16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Gate -- Gate is crm-sellers' type and the types ratchet counted a conflict (Closes #10897)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… its bench block, cap 129 -> 127 (Closes #10897)

The lazy-body rule of this PR lets specs/compiler/parser.t27 and
specs/t27f/deps_card.t27 pass under t27b (the ratchet read UNEXPECTED PASS
for both) and moves notebooklm.t27's first blocker from "type mismatch"
to BenchBlock (MOVED, a finding the ledger steward refuses). Rows and cap
are recounted: 127 not-pass rows, cap 127. Same shape as #10862.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@gHashTag
gHashTag force-pushed the t27b/lazy-bodies-10897 branch from 201d7ca to d11f776 Compare October 19, 2026 11:35
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-19 11:41:02 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 46
PRs with All Checks Green 4
READY 2
FAILING 46
PENDING 0
NO CHECKS YET 0

These columns do not partition: 2 + 46 + 0 + 0 = 48, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=2d301cdb4a58 != 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 enabled auto-merge (squash) October 19, 2026 11:41
@gHashTag
gHashTag merged commit 621a67c into master Oct 19, 2026
52 of 56 checks passed
gHashTag pushed a commit that referenced this pull request Oct 19, 2026
… test reaches (Closes #11053)

AstGen resolves every name in a body, reached or not, so fn f() { var x : Missing = 0; } is "use of
undeclared identifier 'Missing'" whether or not a test calls f (t27c test-report: BLOCKED). Since #10999
body_plan withdrew its refusal (type (undeclared)) as a Sema-level one and t27b passed the spec. body_plan
now keeps it, which also keeps it for a constant (const_plan). Negative control: the plan spec's test,
and a mutant of conformance/lazy_signature.t27 in cli/t27b/tests/tail.rs (refused by t27b, BLOCKED by
t27c). The hand lines stay net <= 0: a comment line of arraylit.rs about the removed tuple-const skip goes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
gHashTag pushed a commit that referenced this pull request Oct 19, 2026
… test reaches (Closes #11053)

AstGen resolves every name in a body, reached or not, so fn f() { var x : Missing = 0; } is "use of
undeclared identifier 'Missing'" whether or not a test calls f (t27c test-report: BLOCKED). Since #10999
body_plan withdrew its refusal (type (undeclared)) as a Sema-level one and t27b passed the spec. body_plan
now keeps it, which also keeps it for a constant (const_plan). Negative control: the plan spec's test,
and a mutant of conformance/lazy_signature.t27 in cli/t27b/tests/tail.rs (refused by t27b, BLOCKED by
t27c). The hand lines stay net <= 0: a comment line of arraylit.rs about the removed tuple-const skip goes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
gHashTag pushed a commit that referenced this pull request Oct 19, 2026
The constant rule of this PR lets specs/lsp/schema.t27 pass under t27b (the
ratchet read UNEXPECTED PASS for it, "ledger blocked (type mismatch), now
pass"). The row and the cap are recounted from the current file: 127 not-pass
rows, cap 127. Same shape as #10862 and #10999.

Co-Authored-By: Claude Sonnet 5.5 <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.

t27b for t27o: ExprIdentifier(undeclared) -- unlocks 24 of 1216 t27o specs (#10879)

2 participants