From 4ba9609a14af9cbc67c55c3d9d38691991c4be09 Mon Sep 17 00:00:00 2001 From: Vasilev Dmitrii Date: Fri, 28 Aug 2026 01:58:16 +0700 Subject: [PATCH] gen-c: an un-annotated local takes its type from the initialiser (Refs #2161) The fallback for a local with no declared type was C's `int`. Two arms above it already special-case integer literals (u32/u64), so what the fallback actually caught was everything else -- a CALL among them: pub fn big() -> u64 { return 4294967296; } pub fn plus_one() -> u64 { const v = big(); // int v = big(); return v + 1; } C prints 1. Rust and Zig print 4294967297. The C compiles without a diagnostic, so nothing downstream can tell -- a silent wrong answer, which is worse than a loud one. Now GNU `__auto_type` when there is an initialiser, which is the same builtin the tuple-destructure paths a few hundred lines above already emit, so it costs no new portability. Without an initialiser there is nothing for it to follow and `int` stays. Measured over 746 tracked specs, before binary vs after: specs whose C output changes 396 cc -fsyntax-only -std=gnu11 errors 6163 -> 5958 specs whose generated C is clean 19 -> 20 specs that went clean -> broken 0 That last row is the one that matters at this radius. Parse count 620/746 and tests 1629 passed / 6 failed are both unchanged. The probe now agrees across backends: C prints 4294967297, same as Rust. FROZEN_HASH resealed in the same commit (M5). --- bootstrap/src/compiler.rs | 13 +++++++++++++ bootstrap/stage0/FROZEN_HASH | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index 53229d525b..5b83acdb85 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -17850,7 +17850,20 @@ impl CCodegen { // unsigned comparison against it inverts (the same // class the Zig backend fixed as comptime_int). if w == "u64" { "uint64_t".to_string() } else { "uint32_t".to_string() } + } else if !node.children.is_empty() { + // GNU `__auto_type`: the type follows the INITIALISER, + // which is what Rust's `let` and Zig's `const` do. + // The `int` fallback below caught everything the two + // arms above miss -- a CALL among them -- and silently + // truncated it: `const v = big()` with `big() -> u64` + // made C print 1 where Rust and Zig print 4294967297, + // and the C compiled without a diagnostic. + // + // Same builtin the tuple-destructure paths above + // already emit, so it costs no new portability. + "__auto_type".to_string() } else { + // No initialiser: `__auto_type` has nothing to follow. "int".to_string() }; self.write(&format!("{} {}", c_type, node.name)); diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index d268b7fbd8..6bb8eea539 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -b4ed38b1d79afb67e4c2547042227175adc74b5a2c0597b60829c1968ae40661 +afc172c5d980089e0732c9bf98169184b1f9a04e2b4b5b2053b28cd8ce49d761