ERR/E3: error-tag {} interpolation via an always-linked tag-name table
`{}` on an error-set value printed `<?>` (any_to_string had no error_set
category). Now it renders the tag name (`BadDigit`), reusing the existing
any_to_string dispatch.
Pieces:
- New `error_tag_name_get` IR op (UnaryOp): tag id -> name. Lowered from a new
`error_tag_name(e) -> string #builtin` (std.sx). Handled across inst.zig
(op def), print.zig, interp.zig (comptime: tags.getName), and emit_llvm.zig.
- emit_llvm getOrBuildTagNameArray: an always-linked `[N x {ptr,i64}]` global
of tag names indexed by global tag id (the TagRegistry namespace, slot 0 =
""). error_tag_name_get zext's the u32 tag value and GEPs into it. Built once;
not trace-gated, so it works in release too (per the spec's "tag-name table
always shipped").
- resolveTypeCategoryTags gains an `error_set` category so the
`case error_set:` arm in any_to_string matches; that arm coerces the Any to
u32 (`xx val`) and calls error_tag_name. (cast(type) didn't recover the tag
id for error-set values; the u32 coercion does.)
examples/240-error-tag-interpolation.sx: bound tags + a catch-bound tag print
their names. Regenerated ffi-objc-call-06-sret-return.ir — pure block-renumber
drift from adding one if-arm to the shared any_to_string (verified
semantically identical after collapsing block numbers).
Gates: zig build, zig build test, bash tests/run_examples.sh (277 passed; lone
failure is the user's uncommitted 213-canonical-map pack WIP).
This commit is contained in:
@@ -143,6 +143,8 @@ pub const LLVMEmitter = struct {
|
||||
|
||||
// Cached field name arrays for reflection (TypeId → LLVM global)
|
||||
field_name_arrays: std.AutoHashMap(u32, c.LLVMValueRef),
|
||||
// The always-linked tag-name table (global tag id → name); built once.
|
||||
tag_name_array: ?c.LLVMValueRef = null,
|
||||
|
||||
// Lazy global `[N x string]` indexed by TypeId.index(), holding
|
||||
// each type's display name. Built on the first dynamic
|
||||
@@ -3304,6 +3306,23 @@ pub const LLVMEmitter = struct {
|
||||
// Switch on index, each case: extractvalue field k → box as Any
|
||||
self.emitFieldValueGet(fr, func_idx);
|
||||
},
|
||||
.error_tag_name_get => |u| {
|
||||
// Tag id → name: GEP into the always-linked tag-name table at
|
||||
// the runtime tag id (the error-set value, a u32). Out-of-range
|
||||
// ids can't occur — ids come from the same registry the table
|
||||
// is built from — so no bounds branch is needed.
|
||||
const global = self.getOrBuildTagNameArray();
|
||||
const tag_raw = self.resolveRef(u.operand);
|
||||
const idx = c.LLVMBuildZExt(self.builder, tag_raw, self.cached_i64, "etn.idx");
|
||||
const string_ty = self.getStringStructType();
|
||||
const n: u32 = @intCast(self.ir_mod.types.tags.names.items.len);
|
||||
const array_ty = c.LLVMArrayType(string_ty, n);
|
||||
const zero = c.LLVMConstInt(self.cached_i64, 0, 0);
|
||||
var indices = [2]c.LLVMValueRef{ zero, idx };
|
||||
const gep = c.LLVMBuildInBoundsGEP2(self.builder, array_ty, global, &indices, 2, "etn.gep");
|
||||
const result = c.LLVMBuildLoad2(self.builder, string_ty, gep, "etn.load");
|
||||
self.mapRef(result);
|
||||
},
|
||||
|
||||
// ── Switch branch ────────────────────────────────────────
|
||||
.switch_br => |sw| {
|
||||
@@ -4662,6 +4681,44 @@ pub const LLVMEmitter = struct {
|
||||
return global;
|
||||
}
|
||||
|
||||
/// The always-linked tag-name table: a `[N x {ptr, i64}]` global of tag
|
||||
/// names indexed by global tag id (the `TagRegistry` namespace; slot 0 is
|
||||
/// the reserved "" no-error name). `error_tag_name_get` GEPs into it at the
|
||||
/// runtime tag id. Built once per module. Always emitted (not trace-gated)
|
||||
/// so `{}` interpolation of an error tag works even in release builds.
|
||||
fn getOrBuildTagNameArray(self: *LLVMEmitter) c.LLVMValueRef {
|
||||
if (self.tag_name_array) |g| return g;
|
||||
|
||||
const string_ty = self.getStringStructType();
|
||||
const names = self.ir_mod.types.tags.names.items;
|
||||
|
||||
var field_vals = std.ArrayList(c.LLVMValueRef).empty;
|
||||
defer field_vals.deinit(self.alloc);
|
||||
for (names) |name_str| {
|
||||
const str_z = self.alloc.dupeZ(u8, name_str) catch unreachable;
|
||||
defer self.alloc.free(str_z);
|
||||
const global_str = c.LLVMAddGlobal(self.llvm_module, c.LLVMArrayType(self.cached_i8, @intCast(name_str.len + 1)), "tag.str");
|
||||
c.LLVMSetInitializer(global_str, c.LLVMConstStringInContext(self.context, str_z.ptr, @intCast(name_str.len + 1), 1));
|
||||
c.LLVMSetGlobalConstant(global_str, 1);
|
||||
c.LLVMSetLinkage(global_str, c.LLVMPrivateLinkage);
|
||||
const len_val = c.LLVMConstInt(self.cached_i64, name_str.len, 0);
|
||||
var struct_fields = [2]c.LLVMValueRef{ global_str, len_val };
|
||||
const const_struct = c.LLVMConstStructInContext(self.context, &struct_fields, 2, 0);
|
||||
field_vals.append(self.alloc, const_struct) catch unreachable;
|
||||
}
|
||||
|
||||
const n: u32 = @intCast(names.len);
|
||||
const array_ty = c.LLVMArrayType(string_ty, n);
|
||||
const array_init = c.LLVMConstArray(string_ty, field_vals.items.ptr, n);
|
||||
const global = c.LLVMAddGlobal(self.llvm_module, array_ty, "tag_names");
|
||||
c.LLVMSetInitializer(global, array_init);
|
||||
c.LLVMSetGlobalConstant(global, 1);
|
||||
c.LLVMSetLinkage(global, c.LLVMPrivateLinkage);
|
||||
|
||||
self.tag_name_array = global;
|
||||
return global;
|
||||
}
|
||||
|
||||
/// Emit field_value_get: switch on runtime index, each case extracts a field and boxes it as Any.
|
||||
fn emitFieldValueGet(self: *LLVMEmitter, fr: ir_inst.FieldReflect, func_idx: u32) void {
|
||||
const base_val = self.resolveRef(fr.base);
|
||||
|
||||
Reference in New Issue
Block a user