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.
4.2 KiB
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_literalarm. Anullin a pointer (or optional-pointer) field therefore returned no constant, which propagated up throughconstStructLiteral/constArrayLiteraland made the whole aggregate look non-constant, soglobalInitValuerejected it with "must be initialized by a compile-time constant". Anullis 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_valtoconstExprValueso a null leaf serializes to a constant zero pointer. Made the LLVM constant emitters exhaustive while at it:emitConstAggregateand the top-levelinit_valswitch insrc/ir/emit_llvm.zigpreviously ended in a silentelse => LLVMConstNull(...)catch-all (the precise silent-arm class CLAUDE.md mandates rooting out); they now handle everyConstantValuetag explicitly (.null_val/.zeroinit→ all-zero constant,.undef→LLVMGetUndef,.func_refresolved, nested.vtableis a hard@panictripwire 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 negativeexamples/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
#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:
error: global 'boxes' must be initialized by a compile-time constant
Expected:
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_literalarm, soconstStructLiteraltreats a pointer field initialized withnullas non-constant andglobalInitValuereports the whole aggregate.src/ir/emit_llvm.zig, top-levelglobal.init_valemission andLLVMEmitter.emitConstAggregate— both currently rely on catch-allelse => LLVMConstNull(...)for severalConstantValuetags. If.null_valis threaded through aggregate constants, add explicit.null_valhandling there (and explicit.zeroinit/.undefhandling as appropriate) rather than depending on the catch-all.
Likely fix:
- Add
.null_literal => .null_valtoconstExprValuefor constant aggregate serialization. - Ensure LLVM constant emission handles
.null_valexplicitly 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:
ptrs=true true markers=11 22
- Add a pinned regression in the
01xxtypes block covering a global array-of-struct with pointer-null fields (and, if straightforward, optional null fields too). - Run:
zig build
zig build test
bash tests/run_examples.sh