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:
agra
2026-06-01 07:47:32 +03:00
parent 6e32e6c63c
commit a3ff503f47
10 changed files with 684 additions and 569 deletions

View File

@@ -10271,6 +10271,13 @@ pub const Lowering = struct {
.struct_type = ty,
} }, .string);
}
if (std.mem.eql(u8, name, "error_tag_name")) {
// error_tag_name(e) → look the error-set value's runtime tag id up
// in the always-linked tag-name table. The value IS its u32 tag id.
if (c.args.len < 1) return self.builder.constString(self.module.types.internString(""));
const e = self.lowerExpr(c.args[0]);
return self.builder.emit(.{ .error_tag_name_get = .{ .operand = e } }, .string);
}
if (std.mem.eql(u8, name, "field_value")) {
// field_value(s, i) → field_value_get instruction (structs/unions)
// → index_get + box_any (slices/arrays)
@@ -10892,7 +10899,7 @@ pub const Lowering = struct {
}
// Dynamic categories: scan TypeTable for matching types
const Category = enum { @"struct", @"enum", @"union", slice, array, pointer, vector, optional };
const Category = enum { @"struct", @"enum", @"union", slice, array, pointer, vector, optional, error_set };
const cat: ?Category = if (std.mem.eql(u8, name, "struct"))
.@"struct"
else if (std.mem.eql(u8, name, "enum") or std.mem.eql(u8, name, "union"))
@@ -10907,6 +10914,8 @@ pub const Lowering = struct {
.vector
else if (std.mem.eql(u8, name, "optional"))
.optional
else if (std.mem.eql(u8, name, "error_set"))
.error_set
else
null;
@@ -10921,6 +10930,7 @@ pub const Lowering = struct {
.pointer => info == .pointer or info == .many_pointer,
.vector => info == .vector,
.optional => info == .optional,
.error_set => info == .error_set,
};
if (matches) {
tags.append(self.alloc, @intCast(idx)) catch {};
@@ -13797,6 +13807,7 @@ pub const Lowering = struct {
if (std.mem.eql(u8, bare_name, "field_count")) return .s64;
if (std.mem.eql(u8, bare_name, "field_index")) return .s64;
if (std.mem.eql(u8, bare_name, "field_name")) return .string;
if (std.mem.eql(u8, bare_name, "error_tag_name")) return .string;
if (std.mem.eql(u8, bare_name, "is_flags")) return .bool;
if (std.mem.eql(u8, bare_name, "type_of")) return .any;
if (std.mem.eql(u8, bare_name, "field_value")) return .any;