Repository navigation
t27b: @intCast with an integer result type lowers from a t27 plan (Closes #7412) - #7627
Merged
Merged
Conversation
…oses #7412) `@intCast(x)` where the context gives an integer result type (`@as`, a typed binding, a parameter, a return) now lowers the way Zig 0.16 Debug runs it: a lossless conversion widens, a constant whose value is kept folds, any other conversion is a checked Cast that traps outside the result type (unsigned to unsigned included, unlike `as`), and wrap mode truncates. A literal that does not fit, a non-integer operand or result type, and a wrong operand count stay refused. What t27b decides is specs/tri/t27b/int_cast_plan.t27 (11 tests), and `t27c gen-rust` turns it into gen/rust/tri/t27b/int_cast_plan.rs, which cli/t27b/src/lower.rs mounts with #[path]. The glue there is 36 lines. The trap cases are a t27 file, cli/t27b/tests/int_cast_traps.t27, whose test names carry their verdicts (`_expect_trap` / `_expect_pass`, as fuzz_oracle.t27 reads them); a 19-line test in cli/t27b/tests/tail.rs runs it. The passing cases are #7417's conformance spec, specs/tri/t27b/conformance/int_cast.t27. Supersedes draft #7417. Closes #7412 Part of #6063 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) The rows come from `tri t27b ratchet --bless` of the t27b lab's run of d62ac63 (the deployed lab.py's lab_run, private work dir): the two specs whose first blocker was ExprCall(@intcast) move from blocked to pass, and the two new specs (int_cast_plan.t27 and conformance/int_cast.t27) get their first rows, pass. Every other row is master's. Counts: pass 854 -> 858, not_pass 61 -> 59, max_not_pass 61 -> 59; 1067 rows, no duplicate path. Closes #7412 Part of #6063 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gHashTag
enabled auto-merge (squash)
October 7, 2026 23:05
Contributor
This was referenced Oct 8, 2026
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.
Closes #7412. Part of #6063 (t27b coverage, lane 4), which is item C10 of #6488; written in t27 under the port epic #6198. Supersedes draft #7417, which did the same in 58 hand-written lines in
lower.rsand a 120-line Rust test file.What
t27b now lowers
@intCast(x)when the context gives it an integer result type:@as(T, ..), a typed binding, a parameter, or areturn. gen-zig prints the call as written, so t27b follows Zig 0.16 Debug. The semantics are #7417's:Castthat traps when the value is outside the result type. That includes unsigned to unsigned, whereastruncates.--overflow wrap) such a conversion truncates, asasdoes there.@intCastwith no result type stays refused, as before.What t27b decides is a t27 spec.
specs/tri/t27b/int_cast_plan.t27holds:shape: the operand count and the result type, asked before the operand is lowered;widens: Zig's lossless rule over widths and signedness, odd widths such asu4andi21included;plan: the decision table. It maps the operand's kind, the two types, constness, fit and the overflow mode to FOLD, FOLD_WRAP, KEEP, WIDEN, CHECK, TRUNCATE or a refusal;t27c gen-rustturns it intogen/rust/tri/t27b/int_cast_plan.rs.cli/t27b/src/lower.rsmounts that file with#[path], as it mountsbuiltin_plan.rs.What the hand-written glue does (36 lines in
lower.rs):@intCastwith an integer result type fromexpr_as;Ty::fits);plan, and builds the IR node the answer names.Trap cases, written in t27
#7417 kept its trap cases in a 120-line Rust file. Here they are a t27 file,
cli/t27b/tests/int_cast_traps.t27. Each test's name ends in its verdict,_expect_passor_expect_trap, the wayspecs/tri/t27b/fuzz_oracle.t27reads a generated test. Every assert of a trap test expects the truncated value, so a cast that truncated instead of trapping would pass it.cli/t27b/tests/tail.rsruns the file in 19 lines:_expect_traptest must trap withTrapKind::Cast, on a line that holds@intCast, in the interpreter and the JIT alike.The file is not under
specs/. Its tests fail by design, and the corpus counts a spec with a failing test as not passing.The reference agrees with it, test by test:
t27c test-reportfails exactly the 7_expect_traptests and passes the other one.zig test --test-filteron its gen-zig output printspanic: integer does not fit in destination typeat the@intCastline.t27b corpus <dir> --reference <t27c>over that one file gives t27b fail and reference fail, withreference_disagree0 files and 0 tests.The passing cases are #7417's conformance spec,
specs/tri/t27b/conformance/int_cast.t27. Only its header comment changed, to point at the trap file and the plan.Lines
cli/t27b/src/lower.rscli/t27b/tests/tail.rsgen/rust/tri/t27b/int_cast_plan.rst27c gen-rust, not hand-editedspecs/tri/t27b/int_cast_plan.t27specs/tri/t27b/conformance/int_cast.t27cli/t27b/tests/int_cast_traps.t27.trinity/seals/*.json(2)t27c seal --savedocs/reports/t27b_expectations.jsonAGENTS.mdHand-written foreign code is 55 added lines: 36 in
lower.rsand 19 intail.rs. That is under the #7371 cap of 40 per file and 80 per PR. Both files were already in master'stools/policy/foreign-exceptions.txt. This PR does not change that file and needs no label.Hand-written Rust in
cli/t27b, measured withwc -l:src/*.rsgoes from 14812 to 14848, andtests/*.rsfrom 7814 to 7833. The AGENTS.md line now says so.Gates (t27c Railway lab, t27c built from master 1120d30, zig 0.16.0)
check_budget(), master'sgen/c/policy/own_language.cwith the one-line C main fromlefthook.yml, overgit diff --numstat --no-renames origin/master...HEAD: exit 0.check_all(), with the exception list fromorigin/master:tools/policy/foreign-exceptions.txt, in both the--(pre-push) and--ciforms: exit 0.lower.rs;cli/t27b/tests/intcast.rs, which is not on the exception list.tools/l2_regen_check.py --base 1120d30ae:t27c gen-rust specs/tri/t27b/int_cast_plan.t27reproduces the generated file byte for byte. gen-rust gave the same bytes over 3 runs.tools/check_seal_coverage.pyandtools/check_seal_currency.pyexit 0. Both new seals passt27c seal --verifywith all hashes MATCH.tools/check_assertionless_spec_tests.py: ok, 3761 in 30, as on the baseline.tools/dupe_scan.py: it reports only thehex_nibble_ofgroup that its base 1120d30 already reports.--likefinds nothing from either new spec.Spec checks
t27c test-report:int_cast_plan.t27: 11/11 pass, 0 of 11 vacuous, 153 runtime asserts._expect_traptests.Mutants. I ran 18 mutants of the plan spec, each on a copy, under
t27c test-report. All 18 were killed:asdoes;t27b lab: full corpus with the reference
Master base 1120d30. These are the lab's own scheduled run numbers: https://t27b-lab-production.up.railway.app/runs/1120d30aed7f5508c44d1d2a62537d04eb0c330b.json. The run used
qemu-aarch64 t27b corpus specs --json --runner qemu-aarch64 --timeout-ms 60000 --jobs 24and 12 reference workers.Branch d62ac63.
/opt/t27b-lab/lab.py(blob 070419d2a) and ran itslab_run./srvand/workwere not touched.--jobs 12, 3 reference workers, an empty reference cache, fuzz off.a426b79a2e84d3ff....cargo test --release -p t27b(aarch64, qemu)Per file:
ExprCall(@intCast)) to pass, as t27b: @intCast with an integer result type is a checked cast; gen_fuzz passes (Closes #7412) #7417 said they would:specs/port/trinity/src/tri/gen_fuzz.t27: 8 tests, 545 runtime asserts;specs/port/trinity/src/server_main.t27: 4 tests, 22 runtime asserts.int_cast_plan.t27with 11 tests and 153 asserts, the conformance spec with 6 tests and 24 asserts.base/ternary_memory:TestBlock;isa/ternary_arithmetic:ExprCall(@rem);isa/ternary_shift:InvariantBlock;ml/recurrent/attention_mechanismandtri/utils/random:ExprCall(undeclared fn);numeric/gf16andnumeric/tf3:type mismatch;queen/brain_summaries:StmtAssign.numeric/ocp_mxandtri/search/rabin_karpstill have an@intCastblocker. There the call is an array index or an arithmetic operand, which gives it no integer result type.tail.rstest,int_cast_traps_outside_its_result_type, passed under qemu-aarch64 with the JIT.The one mismatch is
specs/port/trinity/src/tri/gen_canvas.t27, on master as well. It is inherited and filed as #7550.Ledger
tri t27b ratchet --blessof the branch run gave the rows of the 4 files whose verdict this PR changes. Every other row is master's.gen_fuzz.t27andserver_main.t27: blocked -> pass.int_cast_plan.t27andconformance/int_cast.t27: new rows, pass.gf16.t27blocker goes fromExprCall(@intCast)totype mismatch. Its verdict stays blocked, so its row is master's.gen-rust gaps
None. gen-rust expressed the whole plan. The plan takes "does this constant fit" as a bool from the existing
Ty::fits, so no value wider than 64 bits crosses into it.For the owner
ty.wrap(c). A FOLD value always fitsty, so the wrap keeps it unchanged. The plan keeps the two answers apart, and its tests pin when each one applies.specs/, as a t27 file whose test names carry their verdicts. A trap test can only fail under the reference, and a conformance spec must pass.Generated with Claude Code