fix(ir): serialize enum-literal global initializers (issue 0082)

A module-global initialized with an enum literal silently zero-initialized
to the first tag (`chosen : Color = .green` read back as `.red`), and an
enum tag inside a global array/struct was rejected as non-constant. The
constant serializer had no enum-literal arm.

Add `Lowering.constEnumLiteral`: serialize an enum literal to a
`ConstantValue.int` holding the variant's tag value, resolved against the
destination enum type and respecting explicit variant values; the global's
type drives the backing width at emit time. Wired into `globalInitValue`
(scalar global) and `constExprValue` (array element / struct field / nested
aggregate). A non-enum destination or unknown variant is diagnosed loudly,
never silently zero-initialized. The compiler-injected OS/ARCH globals now
serialize to their real `.unknown` tag (6 / 4); runtime reads are unchanged
(they resolve through comptime_constants), so only the static initializer in
the pinned .ir snapshots changes.

Remove the silent `func_ref => orelse LLVMConstNull` fallbacks in the LLVM
constant emitters: 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, array,
struct field, explicit-value enum u16 stride, struct-array with enum field);
negative: examples/1127-diagnostics-global-enum-literal-bad-variant.sx.
Mark issue 0082 RESOLVED.
This commit is contained in:
agra
2026-06-04 04:52:42 +03:00
parent d680b320f4
commit 263333bd26
21 changed files with 294 additions and 33 deletions

View File

@@ -0,0 +1,51 @@
// A module-global initialized with an enum literal (`.Variant`) reads back the
// declared tag — scalar, inside a global array, and as a struct field, for both
// a plain enum (tag == declaration index) and an explicit-value enum (`enum u16
// { ok :: 200; ... }`, larger backing for element-stride coverage).
// Regression (issue 0082): `globalInitValue` had an `.enum_literal => null`
// carve-out (kept for the compiler-injected `OS`/`ARCH` globals) that silently
// zero-initialized EVERY enum global to the first tag — so `chosen : Color =
// .green` read back as `.red` — while a global array/struct of enums was
// rejected outright as non-constant. The fix serializes the enum literal to its
// tag value (respecting explicit variant values) against the destination enum
// type, for the scalar global, the array element, and the nested aggregate
// field. (Explicit-value enums print as `.` because the `{}` formatter indexes
// variants by position — a separate, pre-existing limitation — so those are
// asserted by equality, not by their printed name.)
#import "modules/std.sx";
Color :: enum u8 { red; green; blue; }
Code :: enum u16 { ok :: 200; not_found :: 404; teapot :: 418; }
Pair :: struct { a: Color; b: Color; }
Row :: struct { status: Code; pad: s64; }
// scalar enum global
chosen : Color = .green;
// global array of enum
palette : [3]Color = .[ .blue, .green, .red ];
// enum field(s) inside a global struct
pair : Pair = .{ a = .blue, b = .green };
// explicit-value enum: scalar, array (2-byte stride), and inside a struct array
status : Code = .teapot;
codes : [3]Code = .[ .ok, .not_found, .teapot ];
rows : [2]Row = .[ .{ status = .not_found, pad = 11 }, .{ status = .teapot, pad = 22 } ];
main :: () {
print("chosen={}\n", chosen);
print("palette={},{},{}\n", palette[0], palette[1], palette[2]);
print("pair.a={} pair.b={}\n", pair.a, pair.b);
if chosen == .green
and palette[0] == .blue and palette[1] == .green and palette[2] == .red
and pair.a == .blue and pair.b == .green
and status == .teapot
and codes[0] == .ok and codes[1] == .not_found and codes[2] == .teapot
and rows[0].status == .not_found and rows[0].pad == 11
and rows[1].status == .teapot and rows[1].pad == 22 {
print("PASS\n");
} else {
print("FAIL: global enum-literal initializer mis-serialized\n");
}
}

View File

@@ -0,0 +1,16 @@
// A module-global enum-literal initializer naming a variant that does not exist
// must be rejected loudly — never silently zero-initialized to the first tag.
// Regression (issue 0082): the enum-literal global serializer resolves the tag
// against the destination enum type; an unknown variant emits a diagnostic and
// fails the build instead of falling back to a null (zero-tag) initializer.
// Expected: "'.purple' is not a variant of enum 'Color'"; exit 1.
#import "modules/std.sx";
Color :: enum u8 { red; green; blue; }
bad : Color = .purple;
main :: () -> s32 {
print("{}\n", bad);
return 0;
}

View File

@@ -1,6 +1,6 @@
@OS = internal global i64 0
@ARCH = internal global i64 0
@OS = internal global i64 6
@ARCH = internal global i64 4
@POINTER_SIZE = internal global i64 8
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
@str = private unnamed_addr constant [2 x i8] c"0\00", align 1

View File

@@ -1,6 +1,6 @@
@OS = internal global i64 0
@ARCH = internal global i64 0
@OS = internal global i64 6
@ARCH = internal global i64 4
@POINTER_SIZE = internal global i64 8
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
@str = private unnamed_addr constant [2 x i8] c"0\00", align 1

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,4 @@
chosen=.green
palette=.blue,.green,.red
pair.a=.blue pair.b=.green
PASS

View File

@@ -1,6 +1,6 @@
@OS = internal global i64 0
@ARCH = internal global i64 0
@OS = internal global i64 6
@ARCH = internal global i64 4
@POINTER_SIZE = internal global i64 8
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
@__Counter__SimpleCounter__vtable = internal constant { ptr, ptr } { ptr @__thunk_SimpleCounter_Counter_inc, ptr @__thunk_SimpleCounter_Counter_get }

View File

@@ -0,0 +1,5 @@
error: '.purple' is not a variant of enum 'Color'
--> examples/1127-diagnostics-global-enum-literal-bad-variant.sx:11:15
|
11 | bad : Color = .purple;
| ^^^^^^^

View File

@@ -1,8 +1,8 @@
@__SxFoo_state_ivar = internal global ptr null
@__SxFoo_class = internal global ptr null
@OS = internal global i64 0
@ARCH = internal global i64 0
@OS = internal global i64 6
@ARCH = internal global i64 4
@POINTER_SIZE = internal global i64 8
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
@__sx_objc_cstr_dealloc = internal global [8 x i8] c"dealloc\00"

View File

@@ -1,8 +1,8 @@
@__SxFoo_state_ivar = internal global ptr null
@__SxFoo_class = internal global ptr null
@OS = internal global i64 0
@ARCH = internal global i64 0
@OS = internal global i64 6
@ARCH = internal global i64 4
@POINTER_SIZE = internal global i64 8
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
@__sx_objc_cstr_dealloc = internal global [8 x i8] c"dealloc\00"

View File

@@ -1,8 +1,8 @@
@__SxBox_state_ivar = internal global ptr null
@__SxBox_class = internal global ptr null
@OS = internal global i64 0
@ARCH = internal global i64 0
@OS = internal global i64 6
@ARCH = internal global i64 4
@POINTER_SIZE = internal global i64 8
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
@OBJC_CLASSLIST_REFERENCES_SxBox = internal global ptr null

View File

@@ -1,6 +1,6 @@
@OS = internal global i64 0
@ARCH = internal global i64 0
@OS = internal global i64 6
@ARCH = internal global i64 4
@POINTER_SIZE = internal global i64 8
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
@OBJC_SELECTOR_REFERENCES_init = internal global ptr null

View File

@@ -1,6 +1,6 @@
@OS = internal global i64 0
@ARCH = internal global i64 0
@OS = internal global i64 6
@ARCH = internal global i64 4
@POINTER_SIZE = internal global i64 8
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
@OBJC_SELECTOR_REFERENCES_tripleValue = internal global ptr null

View File

@@ -1,6 +1,6 @@
@OS = internal global i64 0
@ARCH = internal global i64 0
@OS = internal global i64 6
@ARCH = internal global i64 4
@POINTER_SIZE = internal global i64 8
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
@OBJC_SELECTOR_REFERENCES_length = internal global ptr null

View File

@@ -1,6 +1,6 @@
@OS = internal global i64 0
@ARCH = internal global i64 0
@OS = internal global i64 6
@ARCH = internal global i64 4
@POINTER_SIZE = internal global i64 8
@g_held_view = internal global ptr null
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }