diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index 7729c49034..9932a30505 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -17876,7 +17876,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 f475699756..fed9ae90ee 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -ff31ebbf36eb1b0ec4a370c8132f27f5d6c79b8d0b2d2c4f8e504b2d42f0f9c2 +78bc6d9b03a1ab33729361e558030cd5c67f82d9acdd782912317eaa6d0ea46f diff --git a/docs/now/2026-08-28-c-typed-an-un-annotated-local-as-int-and-printed-1-for-42949.md b/docs/now/2026-08-28-c-typed-an-un-annotated-local-as-int-and-printed-1-for-42949.md new file mode 100644 index 0000000000..7d9ea823f5 --- /dev/null +++ b/docs/now/2026-08-28-c-typed-an-un-annotated-local-as-int-and-printed-1-for-42949.md @@ -0,0 +1,7 @@ +# NOW -- C typed an un-annotated local as int and printed 1 for 4294967297 (2026-08-28) + +## C typed an un-annotated local as int and printed 1 for 4294967297 (Refs #2161) + +- Refs #2161. gen-c fell back to C int for a local with no declared type. Two arms above already special-case integer literals, so the fallback caught everything else -- a CALL among them. `const v = big()` with `big() -> u64` became `int v = big()`: C prints 1 where Rust and Zig print 4294967297, and the C compiles WITHOUT a diagnostic +- Fixed with GNU __auto_type when there is an initialiser -- the same builtin the tuple-destructure paths a few hundred lines above already emit, so no new portability cost. Without an initialiser there is nothing to follow and int stays +- Measured over 746 specs: C output changes for 396, cc -fsyntax-only -std=gnu11 errors 6163 -> 5958, specs with clean C 19 -> 20, and specs that went clean -> broken ZERO. At a radius of 396 that last number is the one that matters, and counting only the sum would have hidden a swap