fix(ir): serialize null pointer fields in global aggregates (issue 0081)
A module-global aggregate initializer rejected a `null` literal in a pointer (or optional-pointer) field as "must be initialized by a compile-time constant". `Lowering.constExprValue` had no `.null_literal` arm, so the null leaf returned no constant and the whole aggregate looked non-constant — even though `null` is the compile-time zero pointer (a top-level scalar `p : *s64 = null;` already serialized fine). Add `.null_literal => .null_val` to constExprValue. While here, make the two LLVM constant emitters exhaustive: emitConstAggregate and the top-level init_val switch in emit_llvm.zig previously ended in a silent `else => LLVMConstNull(...)` catch-all (the 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). The reject-loud path for genuinely non-constant fields is preserved. Regression: examples/0138 (array-of-struct null ptr fields, array of all-null pointers, nested struct-in-struct null ptr) and the negative examples/1126 (null ptr field beside a non-const field still errors). Fail-before/pass-after verified.
This commit is contained in:
@@ -902,7 +902,12 @@ pub const LLVMEmitter = struct {
|
||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||
.aggregate => |agg| self.emitConstAggregate(agg, llvm_ty),
|
||||
.vtable => c.LLVMConstNull(llvm_ty), // placeholder — initialized in initVtableGlobals after function declarations
|
||||
else => c.LLVMConstNull(llvm_ty),
|
||||
// A top-level null-pointer global (`p : *s64 = null;`) and a
|
||||
// zero-initialized global both emit as the all-zero constant
|
||||
// of the global's type (issue 0081).
|
||||
.null_val, .zeroinit => c.LLVMConstNull(llvm_ty),
|
||||
.undef => c.LLVMGetUndef(llvm_ty),
|
||||
.func_ref => |fid| self.func_map.get(fid.index()) orelse c.LLVMConstNull(llvm_ty),
|
||||
};
|
||||
c.LLVMSetInitializer(llvm_global, init_val);
|
||||
} else {
|
||||
@@ -2433,7 +2438,13 @@ pub const LLVMEmitter = struct {
|
||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty),
|
||||
.func_ref => |fid| self.func_map.get(fid.index()) orelse c.LLVMConstNull(elem_ty),
|
||||
else => c.LLVMConstNull(elem_ty),
|
||||
// A null pointer field and a zero-initialized field both emit as
|
||||
// the all-zero constant of the leaf type (issue 0081).
|
||||
.null_val, .zeroinit => c.LLVMConstNull(elem_ty),
|
||||
.undef => c.LLVMGetUndef(elem_ty),
|
||||
// Vtable constants are only ever produced for top-level protocol
|
||||
// vtable globals (lower.zig), never as a nested aggregate leaf.
|
||||
.vtable => @panic("nested vtable constant in aggregate is unsupported — vtables are top-level globals only"),
|
||||
};
|
||||
}
|
||||
if (is_struct) {
|
||||
|
||||
@@ -1033,6 +1033,10 @@ pub const Lowering = struct {
|
||||
.float_literal => |fl| .{ .float = fl.value },
|
||||
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
|
||||
.undef_literal => .zeroinit,
|
||||
// A `null` in a pointer (or optional-pointer) field is a
|
||||
// compile-time constant: the zero pointer. Without this arm the
|
||||
// aggregate is wrongly rejected as non-constant (issue 0081).
|
||||
.null_literal => .null_val,
|
||||
.unary_op => |uo| switch (uo.op) {
|
||||
.negate => switch (uo.operand.data) {
|
||||
.int_literal => |il| .{ .int = -il.value },
|
||||
|
||||
Reference in New Issue
Block a user