Skip to content

t27c gen-rust: constructs that pass test-report but produce Rust that does not compile (repro specs) #8891

Open

Description

@dmitrii-f-t27

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 test s.len == 3 FAILs.
  • 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 == 10 FAILs.

Already tracked elsewhere (not repeated here)

Re-run on the same binary; each still fails as its open issue describes:

  • []const u8 parameter becomes &'static str (rustc E0277 in a for loop): issues 7449 and 8102.
  • Read-only []const T becomes Vec<T> by value (compiles, but every caller copies): issue 8102.
  • Implicit widening into a typed local (const x: u128 = a; with a: u64, and the same with i128 / 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 same as _ 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)

  1. gen-rust lowering of ?T for enum literals, qualified enum literals everywhere, slice returns, string tables, the u64 field widening, @log2, and escaping of Rust keywords used as names.
  2. Optionally, a Result lowering, so adapters can shrink or disappear.
  3. A gate (or a 7th port acceptance criterion) that compiles the gen-rust output 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

  1. tri-claw commented on Oct 11, 2026

    @tri-claw

    The Queen cannot take this issue yet. A bee starts only on an issue whose body has every section below, and this one lacks:

    • missing: ## Boundary -- list every file this task may touch, one per line (- specs/x.t27); without it the Queen reserves nothing and no bee can take the issue
    • missing: ## Requirements -- numbered obligations, FR-001: ... MUST ...
    • missing: ## Success Criteria -- an outcome a machine can settle: a number with a unit, a backticked file, exit 0, or a backticked command

    She 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.

  2. added
    needs-boundaryNo '## Boundary' section, so the Queen can reserve nothing for it and no bee can take it
    and removed
    needs-boundaryNo '## Boundary' section, so the Queen can reserve nothing for it and no bee can take it
    on Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions