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:
@@ -940,11 +940,12 @@ pub const Lowering = struct {
|
||||
d.addFmt(.err, v.span, "global '{s}' must be initialized by a compile-time constant; '{s}' is not a usable constant here", .{ vd.name, id.name });
|
||||
break :blk null;
|
||||
},
|
||||
// Enum-literal shorthand globals (`OS : OperatingSystem = .unknown;`)
|
||||
// keep their established zero-init: it is load-bearing for
|
||||
// compile-time `inline if OS == .X` in the stdlib (issue 0071 scope
|
||||
// note). Carved out explicitly — not folded into a silent fallthrough.
|
||||
.enum_literal => null,
|
||||
// An enum-literal global (`chosen : Color = .green;`) serializes to
|
||||
// the variant's tag value against the destination enum type (issue
|
||||
// 0082). The compiler-injected `OS`/`ARCH` globals flow through here
|
||||
// too; their runtime reads resolve via `comptime_constants`, so the
|
||||
// serialized tag only affects the static initializer.
|
||||
.enum_literal => |el| self.constEnumLiteral(&el, var_ty, v.span),
|
||||
// Any other initializer shape (`.field_access` on a const, a call, an
|
||||
// arithmetic expression, …) is not a static constant the compiler can
|
||||
// evaluate here. Diagnose loudly rather than emit a null payload that
|
||||
@@ -1047,10 +1048,44 @@ pub const Lowering = struct {
|
||||
},
|
||||
.array_literal => |al| self.constArrayLiteral(al.elements, expected_ty),
|
||||
.struct_literal => |sl| self.constStructLiteral(&sl, expected_ty),
|
||||
// An enum tag as an aggregate leaf (`[2]Color = .[.green, .blue]`, or
|
||||
// an enum field inside a global struct) serializes to its tag int
|
||||
// against the leaf's declared enum type (issue 0082).
|
||||
.enum_literal => |el| self.constEnumLiteral(&el, expected_ty, expr.span),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Serialize an enum-literal initializer (`.Variant`) into a static
|
||||
/// `ConstantValue.int` holding the variant's tag value, resolved against the
|
||||
/// destination enum type `ty`. The tag respects explicit variant values
|
||||
/// (`enum { a; b :: 5; }`); the enum's backing width is applied by the
|
||||
/// const emitters via the destination type's LLVM type. Plain enums only —
|
||||
/// a tagged-union or non-enum destination is diagnosed loudly rather than
|
||||
/// silently zero-initialized (issue 0082).
|
||||
fn constEnumLiteral(self: *Lowering, el: *const ast.EnumLiteral, ty: TypeId, span: ast.Span) ?inst_mod.ConstantValue {
|
||||
if (!ty.isBuiltin()) {
|
||||
const info = self.module.types.get(ty);
|
||||
if (info == .@"enum") {
|
||||
const e = info.@"enum";
|
||||
const name_id = self.module.types.internString(el.name);
|
||||
for (e.variants, 0..) |variant, i| {
|
||||
if (variant != name_id) continue;
|
||||
if (e.explicit_values) |vals| {
|
||||
if (i < vals.len) return .{ .int = vals[i] };
|
||||
}
|
||||
return .{ .int = @intCast(i) };
|
||||
}
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, span, "'.{s}' is not a variant of enum '{s}'", .{ el.name, self.module.types.getString(e.name) });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, span, "enum-literal global initializer '.{s}' is only supported for a plain enum destination type", .{el.name});
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Try to convert a struct literal into a compile-time ConstantValue.aggregate of the
|
||||
/// struct's fields in declaration order, filling missing fields from the struct's
|
||||
/// field defaults. Returns null if any value is not constant-foldable.
|
||||
|
||||
Reference in New Issue
Block a user