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:
24
examples/217-error-sets.sx
Normal file
24
examples/217-error-sets.sx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Error-set declarations + `error.X` tag values + enum-like `==` typing
|
||||||
|
// (ERR step E1.1). A declared `error { ... }` set is a real type with a u32
|
||||||
|
// runtime layout; `error.X` is its tag value — the named set when context
|
||||||
|
// provides one (membership-checked), else the raw global u32 id. Tags compare
|
||||||
|
// with an `error.X` literal or another error-set value. The rejections live in
|
||||||
|
// `examples/218-error-set-typing.sx`.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
ParseErr :: error { BadDigit, Overflow, Empty }
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
c : ParseErr = error.BadDigit;
|
||||||
|
d : ParseErr = error.Overflow;
|
||||||
|
r : s32 = 0;
|
||||||
|
if c == error.BadDigit { r = r + 1; } // true -> +1
|
||||||
|
if c == error.Overflow { r = r + 2; } // false
|
||||||
|
if c == d { r = r + 4; } // false (BadDigit != Overflow)
|
||||||
|
if d == error.Overflow { r = r + 8; } // true -> +8
|
||||||
|
tag : u32 = error.Empty; // u32 context -> raw global tag id
|
||||||
|
if tag != 0 { r = r + 16; } // tag ids are >= 1 -> +16
|
||||||
|
print("error-set result: {}\n", r); // -> 25
|
||||||
|
return r;
|
||||||
|
}
|
||||||
16
examples/218-error-set-typing.sx
Normal file
16
examples/218-error-set-typing.sx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Error-set value + `==` typing rejections (ERR step E1.1):
|
||||||
|
// - an `error.X` literal must name a tag that is in the destination set,
|
||||||
|
// - an error-set value compares only with an `error.X` tag or another
|
||||||
|
// error-set value; comparing to a raw integer is a type error
|
||||||
|
// (coerce with `xx` to compare the raw id).
|
||||||
|
// The positive cases live in `examples/217-error-sets.sx`.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
ParseErr :: error { BadDigit, Overflow }
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
c : ParseErr = error.NotInSet; // error: NotInSet not in ParseErr
|
||||||
|
if c == 42 { return 1; } // error: error-set value vs raw integer
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -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.
|
// 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.
|
// This ensures null gets the same LLVM type as the value being compared.
|
||||||
if (bop.op == .eq or bop.op == .neq) {
|
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 {
|
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
|
// Pack-arity intercept: `<pack_name>.len` in a pack-fn mono's
|
||||||
// body resolves to the comptime-known N. The mono doesn't
|
// body resolves to the comptime-known N. The mono doesn't
|
||||||
// materialise the `[]Any` slice that the inline path used, so
|
// 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);
|
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 }
|
/// Lower a tagged enum construction: .Variant.{ field_inits }
|
||||||
/// The struct literal provides the payload fields; we wrap them in an enum_init.
|
/// The struct literal provides the payload fields; we wrap them in an enum_init.
|
||||||
fn lowerTaggedEnumLiteral(
|
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 {
|
fn binOpSymbol(op: ast.BinaryOp.Op) []const u8 {
|
||||||
return switch (op) {
|
return switch (op) {
|
||||||
.add => "+",
|
.add => "+",
|
||||||
|
|||||||
1
tests/expected/217-error-sets.exit
Normal file
1
tests/expected/217-error-sets.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
25
|
||||||
1
tests/expected/217-error-sets.txt
Normal file
1
tests/expected/217-error-sets.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
error-set result: 25
|
||||||
1
tests/expected/218-error-set-typing.exit
Normal file
1
tests/expected/218-error-set-typing.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
11
tests/expected/218-error-set-typing.txt
Normal file
11
tests/expected/218-error-set-typing.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
error: error tag 'error.NotInSet' is not in error set 'ParseErr'
|
||||||
|
--> /Users/agra/projects/sx/examples/218-error-set-typing.sx:13:20
|
||||||
|
|
|
||||||
|
13 | c : ParseErr = error.NotInSet; // error: NotInSet not in ParseErr
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: an error-set value compares only with an `error.X` tag or another error-set value; coerce with `xx` to compare the raw id
|
||||||
|
--> /Users/agra/projects/sx/examples/218-error-set-typing.sx:14:8
|
||||||
|
|
|
||||||
|
14 | if c == 42 { return 1; } // error: error-set value vs raw integer
|
||||||
|
| ^
|
||||||
Reference in New Issue
Block a user