# 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 : *i64 = 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: *i64;
    marker: i64;
}

boxes : [2]Box = .[
    .{ p = null, marker = 11 },
    .{ p = null, marker = 22 },
];

main :: () -> i32 {
    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
```
