Files
sx/issues/0082-global-enum-literal-initializer-zeroes.md
agra d8076b9333 lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
2026-06-12 09:31:53 +03:00

111 lines
4.3 KiB
Markdown

# 0082 - global enum-literal initializer silently zero-initializes
> **RESOLVED.**
> **Root cause:** `Lowering.globalInitValue` (`src/ir/lower.zig`) carried an
> `.enum_literal => null` carve-out: any enum-literal global initializer returned
> a null payload, which the LLVM/interp emitters turn into a zero-initialized
> global — so `chosen : Color = .green` read back as the first tag (`.red`).
> `constExprValue` had no enum-literal arm either, so an enum tag inside a global
> array (`[2]Color = .[.green, .blue]`) or struct field made the whole aggregate
> look non-constant and the global was rejected outright.
> **Fix:** a new `Lowering.constEnumLiteral` serializes an enum literal to a
> `ConstantValue.int` holding the variant's tag value, resolved against the
> destination enum type and respecting explicit variant values (`enum { a; b ::
> 5; }`); the global's type drives the backing width at emit time. Wired into both
> `globalInitValue` (scalar global) and `constExprValue` (array element / struct
> field / nested aggregate). A non-enum destination or an unknown variant is
> diagnosed loudly — never silently zero-initialized. The compiler-injected
> `OS`/`ARCH` globals now serialize to their real `.unknown` tag (6 / 4) instead
> of relying on the null→zero fallback; runtime reads are unchanged because they
> resolve through `comptime_constants`. As part of the same exhaustiveness pass,
> the silent `func_ref => … orelse LLVMConstNull` fallbacks in the LLVM constant
> emitters (`src/ir/emit_llvm.zig`) were removed: aggregate func_ref leaves carry
> a `require_resolved` flag (transient null in Pass 0, loud diagnostic if still
> unresolved in the Pass-1.5 re-emit), a top-level func_ref global is resolved in
> `initVtableGlobals`, and the comptime (`#run`) path bails loudly instead of
> emitting a null function pointer.
> **Regression:** `examples/0139-types-global-enum-literal-init.sx` (scalar enum
> global, global array of enum, enum struct field, explicit-value `enum u16` for
> element-stride, struct-array with enum field) — FAILS on the pre-fix compiler
> (wrong tag / rejected as non-constant), PASSES after. Negative:
> `examples/1127-diagnostics-global-enum-literal-bad-variant.sx` (unknown variant
> rejected loudly, exit 1).
## Symptom
A module-global enum initialized with a non-zero enum literal silently reads back
as the zero tag. Observed: `chosen : Color = .green;` prints `.red` and the
program exits 1. Expected: it should print `.green` and exit 0, or the compiler
should reject unsupported enum-literal global initializers loudly instead of
zero-initializing.
## Reproduction
```sx
#import "modules/std.sx";
Color :: enum u8 { red; green; blue; }
chosen : Color = .green;
main :: () -> i32 {
print("chosen={}\n", chosen);
if chosen == .green {
print("PASS\n");
return 0;
}
print("FAIL\n");
return 1;
}
```
Observed:
```text
chosen=.red
FAIL
```
Expected:
```text
chosen=.green
PASS
```
## Investigation prompt
Fix issue 0082: module-global enum literal initializers silently become the zero
tag.
Suspected area:
- `src/ir/lower.zig`, `Lowering.globalInitValue`: the `.enum_literal => null`
carve-out preserves the stdlib's historical zero-init path for compiler-
injected `OS : OperatingSystem = .unknown`, but it also silently drops any
user-written non-zero enum literal such as `.green`.
- `src/ir/lower.zig`, `Lowering.constExprValue`: aggregate enum-literal fields
are currently not serialized either, so audit both top-level and aggregate
enum literals.
Likely fix:
- Resolve the destination enum type from `var_ty` / `expected_ty` and serialize
the enum tag as a `ConstantValue.int` with the variant index/value.
- If a particular enum literal shape cannot be serialized yet (payload variants,
unsupported explicit tag values, etc.), emit a diagnostic instead of returning
`null`.
- Preserve the compiler-injected `OperatingSystem` / `Architecture` behavior by
making those globals real constants, not by relying on null initializer
fallback.
Verification:
- Run the repro above and expect `chosen=.green` / `PASS` / exit 0.
- Add a pinned regression in the `01xx` types block for a non-zero enum global
and, if supported by the fix, an enum field inside a global aggregate.
- Run:
```sh
zig build
zig build test
bash tests/run_examples.sh
```