# 0081 - global aggregate null literal rejected as non-constant > **RESOLVED.** > **Root cause:** `Lowering.constExprValue` (`src/ir/lower.zig`) — the > constant-aggregate serializer for global initializers — had no > `.null_literal` arm. A `null` in a pointer (or optional-pointer) field > therefore returned no constant, which propagated up through > `constStructLiteral` / `constArrayLiteral` and made the whole aggregate look > non-constant, so `globalInitValue` rejected it with "must be initialized by a > compile-time constant". A `null` is a compile-time constant (the zero > pointer) and a top-level scalar pointer global (`p : *s64 = null;`) already > serialized fine — only the nested-aggregate path was wrong. > **Fix:** add `.null_literal => .null_val` to `constExprValue` so a null leaf > serializes to a constant zero pointer. Made the LLVM constant emitters > exhaustive while at it: `emitConstAggregate` and the top-level `init_val` > switch in `src/ir/emit_llvm.zig` previously ended in a silent > `else => LLVMConstNull(...)` catch-all (the precise silent-arm class CLAUDE.md > mandates rooting out); they now handle every `ConstantValue` tag explicitly > (`.null_val`/`.zeroinit` → all-zero constant, `.undef` → `LLVMGetUndef`, > `.func_ref` resolved, nested `.vtable` is a hard `@panic` tripwire since > vtables are top-level-only). The reject-loud path for genuinely non-constant > fields (a runtime call, etc.) is preserved. > **Regression:** `examples/0138-types-global-aggregate-null-pointer-field.sx` > (array-of-struct with null pointer fields, global array of all-null pointers, > nested struct-in-struct null pointer — asserts null reads + correct neighbors) > and the negative `examples/1126-diagnostics-global-aggregate-non-const-field-rejected.sx` > (a null pointer field beside a non-constant field still errors loudly). > Verified fail-before (pre-fix rejects 0138) / pass-after. ## Symptom A module-global aggregate initializer rejects a `null` literal in a pointer field as "not a compile-time constant"; expected the null pointer to serialize as a constant zero pointer the same way a top-level pointer global does. ## Reproduction ```sx #import "modules/std.sx"; Box :: struct { p: *s64; marker: s64; } boxes : [2]Box = .[ .{ p = null, marker = 11 }, .{ p = null, marker = 22 }, ]; main :: () -> s32 { print("ptrs={} {} markers={} {}\n", boxes[0].p == null, boxes[1].p == null, boxes[0].marker, boxes[1].marker); if boxes[0].p == null and boxes[1].p == null and boxes[0].marker == 11 and boxes[1].marker == 22 { return 0; } return 1; } ``` Observed: ```text error: global 'boxes' must be initialized by a compile-time constant ``` Expected: ```text ptrs=true true markers=11 22 ``` ## Investigation prompt Fix issue 0081: module-global aggregate initializers reject `null` literals in pointer fields even though `null` is a compile-time constant pointer value. Suspected area: - `src/ir/lower.zig`, `Lowering.constExprValue` — the switch has no `.null_literal` arm, so `constStructLiteral` treats a pointer field initialized with `null` as non-constant and `globalInitValue` reports the whole aggregate. - `src/ir/emit_llvm.zig`, top-level `global.init_val` emission and `LLVMEmitter.emitConstAggregate` — both currently rely on catch-all `else => LLVMConstNull(...)` for several `ConstantValue` tags. If `.null_val` is threaded through aggregate constants, add explicit `.null_val` handling there (and explicit `.zeroinit` / `.undef` handling as appropriate) rather than depending on the catch-all. Likely fix: - Add `.null_literal => .null_val` to `constExprValue` for constant aggregate serialization. - Ensure LLVM constant emission handles `.null_val` explicitly for both top-level constants and nested aggregate leaves. - Keep unsupported aggregate expressions loud: non-constant calls/field-accesses should still diagnose instead of zero-initializing. Verification: - Run the repro above and expect: ```text ptrs=true true markers=11 22 ``` - Add a pinned regression in the `01xx` types block covering a global array-of-struct with pointer-null fields (and, if straightforward, optional null fields too). - Run: ```sh zig build zig build test bash tests/run_examples.sh ```