Repository navigation
t27c gen-rust: constructs that pass test-report but produce Rust that does not compile (repro specs) #8891
Description
This is a bug report for the compiler owner. It is not a task for an agent: please do not shape or dispatch it. The compiler core is frozen, so a fix is a decision for gHashTag. There is no Boundary on purpose.
Summary
The port acceptance criteria run only the Zig path (t27c gen, t27c test-report). The constructs below pass that path (case R3 is BLOCKED on the Zig side too) and give Rust that rustc refuses. Nothing in the port gate compiles the gen-rust output, so these reach master: the merged trios-chat CR-CHAT-05 envelope-size port passes test-report 10/10, its seal records gen_hash_rust sha256:d7f513d54f677fa6d130919948a22e6afbdf6c326cf4ef580849fdcf261c9b5b, and that is the sha256 of a gen-rust output that rustc refuses with 2 x E0308 (case R1). Re-measured on 2026-10-11 with the binary below.
Environment: t27c 0.5.2 built from gHashTag/t27 master 67cbf82849e40a74e5cc92d4a25c764dc5862f90 (2026-10-10, cargo build --release -p t27c). Master is now 39d5af36b86c; the 12 commits in between do not touch the compiler core (GitHub compare). Zig 0.16.0, rustc 1.94.0. For every spec: t27c test-report X.t27, then t27c gen-rust X.t27 > X.rs && rustc --edition 2021 --crate-type lib --out-dir ../../o X.rs. Outputs are pasted as printed: the test-report summary lines, the gen-rust lines that matter, and every rustc error line with its location.
R1. ?Enum return of an enum literal is not wrapped in Some()
Issue 7534 (closed as completed on 2026-10-07) fixed this for plain values: return x / 2; in a ?u32 fn now gives return Some((x / 2)); (re-checked with the 7534 repro). An enum literal is still emitted bare; rustc says "expected Option<E>, found E".
Spec r1_optional_enum_return.t27:
module probe::p27_optional_enum_return;
pub enum E { Small, Big }
pub fn check(x: u32) -> ?E {
if (x > 10) {
return E.Big;
}
return null;
}
test p27 {
assert(check(1) == null);
assert(check(11).? == E.Big);
}
Outputs (t27c test-report exit 0; t27c gen-rust lines; rustc):
tests 1
pass 1
FAIL 0
--- t27c gen-rust r1_optional_enum_return.t27 > r1_optional_enum_return.rs
14: pub fn check(x: u32) -> Option<E> {
16: return E::Big;
18: return None;
--- rustc --edition 2021 --crate-type lib --out-dir ../../o r1_optional_enum_return.rs
error[E0308]: mismatched types
--> r1_optional_enum_return.rs:16:16
R2. An enum literal outside a return is emitted unqualified
Writing E.AtMax instead of .AtMax compiles (control spec return e == E.AtMax;: test-report 1/1, rustc exit 0).
Spec r2_enum_literal_unqualified.t27:
module probe::p15_error_message;
pub enum E { AtMax, DangerZone }
pub fn message(e: E) -> []const u8 {
if (e == .AtMax) {
return "epoch at u64::MAX";
}
return "epoch in danger zone";
}
test p15 {
const m = message(.AtMax);
assert(m.len == 17);
}
Outputs (t27c test-report exit 0; t27c gen-rust lines; rustc):
tests 1
pass 1
FAIL 0
--- t27c gen-rust r2_enum_literal_unqualified.t27 > r2_enum_literal_unqualified.rs
14: pub fn message(e: E) -> &'static str {
15: if (e == AtMax) {
--- rustc --edition 2021 --crate-type lib --out-dir ../../o r2_enum_literal_unqualified.rs
error[E0425]: cannot find value `AtMax` in this scope
--> r2_enum_literal_unqualified.rs:15:14
R3. Payload enum variants produce garbage in both backends
spec-status prints IMPLEMENTED for this spec. Zig: the enum gets a member named u32 and return (.Gap, x); is BLOCKED. Rust: a variant named u32 and return (Gap, x);.
Spec r5_payload_enum.t27:
module probe::p03_payload_enum;
pub enum E {
Gap(u32),
Empty,
}
pub fn f(x: u32) -> ?E {
if (x > 3) {
return .Gap(x);
}
return null;
}
test p03 {
assert(f(1) == null);
}
Outputs (t27c test-report exit 2; t27c gen-rust lines; rustc):
BLOCKED does not compile: spec.zig:25:21: error: expected ')', found ','
--- t27c gen-rust r5_payload_enum.t27 > r5_payload_enum.rs
10: Gap,
11: u32,
17: return (Gap, x);
--- rustc --edition 2021 --crate-type lib --out-dir ../../o r5_payload_enum.rs
error[E0425]: cannot find value `Gap` in this scope
--> r5_payload_enum.rs:17:17
R4. [N][]const u8 becomes Vec<const u8>
Spec r6_str_array.t27:
module probe::p19_str_array;
pub const NAMES: [3][]const u8 = ["a", "bb", "ccc"];
test p19 {
assert(NAMES[2].len == 3);
}
Outputs (t27c test-report exit 0; t27c gen-rust lines; rustc):
tests 1
pass 1
FAIL 0
--- t27c gen-rust r6_str_array.t27 > r6_str_array.rs
7: pub const NAMES: Vec<const u8> = ["a", "bb", "ccc"];
--- rustc --edition 2021 --crate-type lib --out-dir ../../o r6_str_array.rs
error: expected `:`, found `>`
--> r6_str_array.rs:7:30
error: unexpected `const` parameter declaration
--> r6_str_array.rs:7:22
error[E0747]: constant provided when a type was expected
--> r6_str_array.rs:7:22
R5. Anonymous struct return type
A named struct works.
Spec r7_anon_struct_return.t27:
module probe::p28_anon_struct_return;
pub fn check(x: u32) -> struct { ok: bool, code: u32 } {
if (x > 10) {
return .{ .ok = false, .code = 1 };
}
return .{ .ok = true, .code = 0 };
}
test p28 {
const r = check(11);
assert(r.ok == false);
assert(r.code == 1);
}
Outputs (t27c test-report exit 0; t27c gen-rust lines; rustc):
tests 1
pass 1
FAIL 0
--- t27c gen-rust r7_anon_struct_return.t27 > r7_anon_struct_return.rs
7: pub fn check(x: u32) -> struct { ok : bool , code : u32 } {
--- rustc --edition 2021 --crate-type lib --out-dir ../../o r7_anon_struct_return.rs
error: expected type, found keyword `struct`
--> r7_anon_struct_return.rs:7:25
R6. Returning a []u8 slice
The return type becomes Vec<u8> and the body returns &xs[..]: rustc says "expected Vec<u8>, found &[u8]".
Spec r8_return_slice.t27:
module probe::p06b_return_slice_typed;
pub fn head(xs: []u8, n: usize) -> []u8 {
return xs[0..n];
}
test p06b {
var a: [3]u8 = [3]u8{9, 8, 7};
const h = head(&a, 2);
assert(h.len == 2);
assert(h[1] == 8);
}
Outputs (t27c test-report exit 0; t27c gen-rust lines; rustc):
tests 1
pass 1
FAIL 0
--- t27c gen-rust r8_return_slice.t27 > r8_return_slice.rs
7: pub fn head(xs: Vec<u8>, n: usize) -> Vec<u8> {
8: return &xs[(0) as usize..(n) as usize];
--- rustc --edition 2021 --crate-type lib --out-dir ../../o r8_return_slice.rs
error[E0308]: mismatched types
--> r8_return_slice.rs:8:12
R7. @log2 is emitted verbatim
For an integer argument Zig is BLOCKED as well (@log2 is float-only in Zig). For f64 Zig passes and the Rust keeps @log2.
Spec g2f_log2_float.t27:
module probe::p38_log2_float;
pub fn lg(v: f64) -> f64 {
return @log2(v);
}
test p38 {
assert(lg(8.0) == 3.0);
}
Outputs (t27c test-report exit 0; t27c gen-rust lines; rustc):
tests 1
pass 1
FAIL 0
--- t27c gen-rust g2f_log2_float.t27 > g2f_log2_float.rs
7: pub fn lg(v: f64) -> f64 {
8: return @log2(v);
--- rustc --edition 2021 --crate-type lib --out-dir ../../o g2f_log2_float.rs
error: expected one of `;`, `}`, or an operator, found `@`
--> g2f_log2_float.rs:8:12
R8. A usize value stored into a u64 struct field is not cast
Zig widens implicitly; Rust gets R { n: xs.len() } with no as u64 ("expected u64, found usize"). The typed-local form of this defect is issue 8812; this is the struct-field initializer.
Spec g4_usize_into_u64_field.t27:
module probe::p31_usize_field;
pub struct R {
n: u64,
}
pub fn count(xs: []u8) -> R {
return R { n: xs.len, };
}
test p31 {
var a: [3]u8 = [3]u8{1, 2, 3};
const r = count(&a);
assert(r.n == 3);
}
Outputs (t27c test-report exit 0; t27c gen-rust lines; rustc):
tests 1
pass 1
FAIL 0
--- t27c gen-rust g4_usize_into_u64_field.t27 > g4_usize_into_u64_field.rs
13: pub fn count(xs: Vec<u8>) -> R {
14: return R { n: xs.len() };
--- rustc --edition 2021 --crate-type lib --out-dir ../../o g4_usize_into_u64_field.rs
error[E0308]: mismatched types
--> g4_usize_into_u64_field.rs:14:19
R9. A parameter named self is kept
Zig accepts self as an ordinary parameter name; Rust rejects it outside an impl. Renaming the parameter (c: *Counter) compiles and lowers to &mut Counter.
Spec g8_self_param.t27:
module probe::p36_self_param;
pub struct Counter {
n: u64,
}
pub fn counter_bump(self: *Counter, by: u64) -> u64 {
self.n = self.n + by;
return self.n;
}
test p36 {
var c: Counter = Counter { n: 1, };
assert(counter_bump(&c, 2) == 3);
}
Outputs (t27c test-report exit 0; t27c gen-rust lines; rustc):
tests 1
pass 1
FAIL 0
--- t27c gen-rust g8_self_param.t27 > g8_self_param.rs
13: pub fn counter_bump(self: &mut Counter, by: u64) -> u64 {
14: self.n = (self.n + by);
15: return self.n;
--- rustc --edition 2021 --crate-type lib --out-dir ../../o g8_self_param.rs
error: `self` parameter is only allowed in associated functions
--> g8_self_param.rs:13:21
Also found: string escapes and UTF-8 are wrong in both backends
These do not pass test-report, so the Zig gate catches them, but the Rust compiles with the wrong bytes:
const s = "a\x00b";is emitted as"a\\x00b"in both the Zig and the Rust output (four characters instead of one NUL byte); the tests.len == 3FAILs.- A raw UTF-8 literal (U+03C6, bytes CF 86) is double-encoded to C3 8F C2 86 in both outputs; the test
s.len == 10FAILs.
Already tracked elsewhere (not repeated here)
Re-run on the same binary; each still fails as its open issue describes:
[]const u8parameter becomes&'static str(rustc E0277 in aforloop): issues 7449 and 8102.- Read-only
[]const TbecomesVec<T>by value (compiles, but every caller copies): issue 8102. - Implicit widening into a typed local (
const x: u128 = a;witha: u64, and the same withi128/i64): rustc E0308 x2 each; issue 8812 describes the u8 -> usize form. @as(u8, @intCast(x & 0xFF))lowers to(((x & 0xFF) as _) as u8): rustc E0282, the sameas _lowering as issue 8259.- Tests are not lowered: every gen-rust file says "NOT LOWERED BY THIS BACKEND": issue 6574.
The cross-module use probe of the closed issue 4298 (two specs, use x::a::double_it;) was re-run. It now resolves when the spec path given to t27c has a directory named specs above it: the used fn is spliced into both outputs, test-report 1/1, rustc exit 0. Run from inside the directory with a bare file name, test-report is BLOCKED ("use of undeclared identifier 'double_it'") and gen only warns, as issue 7176 says. So that probe is not part of this report.
Workarounds used by the trios-chat rewrite until a decision
Qualified enum literals; a named verdict struct with a payload-free enum instead of ?Enum or payload variants; fixed [N]u8 or []u8 with typed test arrays; flat byte tables with offsets instead of [N][]const u8; @as(u128, x) and @as(u64, x) for widening, @truncate(x) for narrowing; no @log2; no parameter named self. A hand-written Rust adapter maps the generated verdict back to Result<(), E>. The conventions, every snippet compile-checked with the binary above: gHashTag/trios#1826
What would help (for gHashTag to decide)
gen-rustlowering of?Tfor enum literals, qualified enum literals everywhere, slice returns, string tables, the u64 field widening,@log2, and escaping of Rust keywords used as names.- Optionally, a
Resultlowering, so adapters can shrink or disappear. - A gate (or a 7th port acceptance criterion) that compiles the
gen-rustoutput with rustc:t27c gen-rust X > x.rs && rustc --edition 2021 --crate-type lib --out-dir D x.rs.
Dmitrii Fedorov (@dmitrii-f-t27)
Boundary
bootstrap/src/compiler.rs
Activity
tri-claw commentedon Oct 11, 2026
The Queen cannot take this issue yet. A bee starts only on an issue whose body has every section below, and this one lacks:
- specs/x.t27); without it the Queen reserves nothing and no bee can take the issueFR-001: ... MUST ...exit 0, or a backticked commandShe tried to write them 2 times and could not (last: not a task: The issue explicitly states it is a bug report for the compiler owner, not a task for an agent, and the compiler core is frozen.). Add them and the next round takes it. The rule is
specs/policy/issue_shape.t27.seals: re-seal 134 specs whose gen-rust output moved (gen_hash_rust o…
backend_behaviour: codegen_conformance's gen-rust output compiles and…
compiler.rs: delete 69 dead lines so foreign code shrinks in this PR
seals: re-seal the same 134 specs with t27c built at 8cc6f03 (built_b…
codegen_conformance: the @log2 case moves to backend_behaviour (t27b …
Merge master into fix/gen-rust-8891; FROZEN_HASH recomputed from the …