ERR/E1.1 (slice 2): error.X value lowering + enum-like == typing
Completes E1.1. All in ir/lower.zig (the IR layer, per slice 1's finding).
- lowerFieldAccess intercepts `error.X` (parsed as field_access(identifier
"error", X)) → lowerErrorTagLiteral: interns the tag; when target_type is a
named error set, types the value as that set and validates X ∈ set (out-of-set
→ diagnostic); otherwise emits the raw u32 global tag id (the spec's
context-free default — not a silent guess).
- tryLowerErrorSetEquality (early branch in lowerBinaryOp) + errorSetTypeOf /
isErrorTagLiteralNode: an error-set value or `error.X` literal forces the other
operand to be one too, else a diagnostic ("compares only with an error.X tag or
another error-set value; coerce with `xx`"). Both sides lower under the set type
as context (error.X resolves + membership-checks); two bare tag literals with no
context compare as global u32 ids. Handles both operand orders.
First ERR examples (end-to-end): 217-error-sets.sx (declared set + error.X +
== true/false + u32 coercion → "error-set result: 25", exit 25) and
218-error-set-typing.sx (out-of-set literal + tag-vs-raw-int → 2 diagnostics).
Failable `!`/`!Named` signatures and raise/try/catch/onfail semantics remain
(E1.2+). zig build, zig build test, and 256/256 examples green.
This commit is contained in:
@@ -2648,6 +2648,14 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// Error-set equality: an error-set value compares only with an
|
||||
// `error.X` tag literal or another error-set value. Comparing to a raw
|
||||
// integer is a type error (coerce with `xx`). `e == error.X` resolves
|
||||
// X against e's set and validates membership.
|
||||
if (bop.op == .eq or bop.op == .neq) {
|
||||
if (self.tryLowerErrorSetEquality(bop)) |result| return result;
|
||||
}
|
||||
|
||||
// Set target_type for null literals to match the other operand's type.
|
||||
// This ensures null gets the same LLVM type as the value being compared.
|
||||
if (bop.op == .eq or bop.op == .neq) {
|
||||
@@ -4346,6 +4354,14 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
fn lowerFieldAccess(self: *Lowering, fa: *const ast.FieldAccess, span: ast.Span) Ref {
|
||||
// `error.X` — an error-tag literal. The `error` keyword in expression
|
||||
// position parses as identifier "error" (E0.2), so `error.X` is a
|
||||
// field access we intercept here. `error` is reserved, so this is
|
||||
// unambiguous (no struct/pack can be named `error`).
|
||||
if (fa.object.data == .identifier and std.mem.eql(u8, fa.object.data.identifier.name, "error")) {
|
||||
return self.lowerErrorTagLiteral(fa.field, span);
|
||||
}
|
||||
|
||||
// Pack-arity intercept: `<pack_name>.len` in a pack-fn mono's
|
||||
// body resolves to the comptime-known N. The mono doesn't
|
||||
// materialise the `[]Any` slice that the inline path used, so
|
||||
@@ -4718,6 +4734,35 @@ pub const Lowering = struct {
|
||||
return self.builder.enumInit(tag, Ref.none, target);
|
||||
}
|
||||
|
||||
/// Lower an `error.X` tag literal to its global tag id (a `u32`). When the
|
||||
/// destination context (`target_type`) is a named error set, the value is
|
||||
/// typed as that set and `X`'s membership is validated; otherwise the value
|
||||
/// is the raw `u32` global tag id (per the spec's context rule).
|
||||
fn lowerErrorTagLiteral(self: *Lowering, tag_name: []const u8, span: ast.Span) Ref {
|
||||
const tag_id = self.module.types.internTag(tag_name);
|
||||
if (self.target_type) |t| {
|
||||
if (!t.isBuiltin()) {
|
||||
const info = self.module.types.get(t);
|
||||
if (info == .error_set) {
|
||||
var in_set = false;
|
||||
for (info.error_set.tags) |member| {
|
||||
if (member == tag_id) {
|
||||
in_set = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!in_set) {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, span, "error tag 'error.{s}' is not in error set '{s}'", .{ tag_name, self.module.types.getString(info.error_set.name) });
|
||||
}
|
||||
}
|
||||
return self.builder.constInt(@as(i64, @intCast(tag_id)), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
return self.builder.constInt(@as(i64, @intCast(tag_id)), .u32);
|
||||
}
|
||||
|
||||
/// Lower a tagged enum construction: .Variant.{ field_inits }
|
||||
/// The struct literal provides the payload fields; we wrap them in an enum_init.
|
||||
fn lowerTaggedEnumLiteral(
|
||||
@@ -14838,6 +14883,57 @@ pub const Lowering = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// The named error-set TypeId of `node`'s type, or null if not an
|
||||
/// error-set-typed expression.
|
||||
fn errorSetTypeOf(self: *Lowering, node: *const Node) ?TypeId {
|
||||
const t = self.inferExprType(node);
|
||||
if (t.isBuiltin()) return null;
|
||||
return if (self.module.types.get(t) == .error_set) t else null;
|
||||
}
|
||||
|
||||
/// True when `node` is an `error.X` tag literal (`field_access` whose
|
||||
/// object is the `error` keyword, parsed as identifier "error").
|
||||
fn isErrorTagLiteralNode(node: *const Node) bool {
|
||||
if (node.data != .field_access) return false;
|
||||
const obj = node.data.field_access.object;
|
||||
return obj.data == .identifier and std.mem.eql(u8, obj.data.identifier.name, "error");
|
||||
}
|
||||
|
||||
/// Lower `==` / `!=` when an error-set value or `error.X` tag is involved.
|
||||
/// Returns null when neither operand is error-related (general path runs).
|
||||
/// Both operands must be a tag (an `error.X` literal or an error-set value);
|
||||
/// otherwise it's a type error (e.g. comparing a tag to a raw integer).
|
||||
fn tryLowerErrorSetEquality(self: *Lowering, bop: *const ast.BinaryOp) ?Ref {
|
||||
const l_set = self.errorSetTypeOf(bop.lhs);
|
||||
const r_set = self.errorSetTypeOf(bop.rhs);
|
||||
const l_tag = isErrorTagLiteralNode(bop.lhs);
|
||||
const r_tag = isErrorTagLiteralNode(bop.rhs);
|
||||
if (l_set == null and r_set == null and !l_tag and !r_tag) return null;
|
||||
|
||||
const l_ok = l_set != null or l_tag;
|
||||
const r_ok = r_set != null or r_tag;
|
||||
if (!l_ok or !r_ok) {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, bop.lhs.span, "an error-set value compares only with an `error.X` tag or another error-set value; coerce with `xx` to compare the raw id", .{});
|
||||
}
|
||||
return self.builder.constBool(false);
|
||||
}
|
||||
|
||||
// Lower both sides with the set type as context so an `error.X` literal
|
||||
// resolves to it (and validates membership). Two bare tag literals with
|
||||
// no set context lower to global u32 ids (cross-set comparison is OK).
|
||||
const set_ty = l_set orelse r_set;
|
||||
const saved = self.target_type;
|
||||
if (set_ty) |st| self.target_type = st;
|
||||
const lv = self.lowerExpr(bop.lhs);
|
||||
const rv = self.lowerExpr(bop.rhs);
|
||||
self.target_type = saved;
|
||||
return if (bop.op == .eq)
|
||||
self.builder.cmpEq(lv, rv)
|
||||
else
|
||||
self.builder.emit(.{ .cmp_ne = .{ .lhs = lv, .rhs = rv } }, .bool);
|
||||
}
|
||||
|
||||
fn binOpSymbol(op: ast.BinaryOp.Op) []const u8 {
|
||||
return switch (op) {
|
||||
.add => "+",
|
||||
|
||||
Reference in New Issue
Block a user