wcc: Hare-style !T error marker on tagged-union variants
A type prefixed with `!` is flagged as an error variant. When any variant in a tagged union carries the flag, `?` propagation uses those (and only those) as the error subset; the unflagged variant is the success type. The legacy "first variant = success" rule still applies when no `!`-flag is present, so existing code keeps working. - TK_NOT in parsetype → N_TBANG wrapper (lhs = inner type expr). Appended to Nkind tail for wwdump-diff byte stability. - resolve_type N_TBANG: wraps primitives in a fresh Type copy so the iserror bit doesn't taint shared globals like ty_str/ty_i32; flips the bit in place on NAMED (already unique per alias decl). - Type.iserror; type_named and typedecl inherit it from under. - New check.c helpers: tagged_has_errflag, tagged_is_error_variant, tagged_success_type. N_TRYPROP uses them to find the error subset and verify each error variant is propagatable to the enclosing return. - cgen mirrors with cg_tagged_success_tag + cg_variant_is_error. `?` compares AX against the success tag (no longer always 0) and remaps each error variant's tag for the enclosing fn. `!` aborts on any non-success tag. strconv.invalid and strconv.overflow now use `!`-flagged shape (`!i32` and `!void`) — visible signal in the API surface that they are error types, matching Hare. The (i64 | invalid | overflow) return shape and behavior are unchanged for callers; their match arms still bind the same way. Selfhost: lib/ww/parse/parse.ww recognises `!T` and emits N_TBANG. The selfhost typechecker and cgen ignore the flag — none of the selfhost sources use `!`, so byte-identity gates are unaffected. The selfhost mirror catches up when there's a source using it.
This commit is contained in:
@@ -170,6 +170,45 @@ cg_variant_match(Type *vt, Type *src)
|
|||||||
return type_eq(vt, src);
|
return type_eq(vt, src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* cg_tagged_success_tag — index of the success variant in a tagged
|
||||||
|
* union. Mirrors check.c tagged_success_type: explicit-flag mode
|
||||||
|
* picks the first non-`!`-marked variant; legacy mode picks index 0. */
|
||||||
|
static int
|
||||||
|
cg_tagged_success_tag(Type *t)
|
||||||
|
{
|
||||||
|
if (t == NULL) return 0;
|
||||||
|
if (t->kind == TY_NAMED) t = t->under;
|
||||||
|
if (t == NULL || t->kind != TY_TAGGED) return 0;
|
||||||
|
int has_err = 0;
|
||||||
|
for (Tparam *p = t->params; p; p = p->next)
|
||||||
|
if (p->type && p->type->iserror) { has_err = 1; break; }
|
||||||
|
if (!has_err) return 0;
|
||||||
|
int idx = 0;
|
||||||
|
for (Tparam *p = t->params; p; p = p->next, idx++)
|
||||||
|
if (p->type && !p->type->iserror) return idx;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
cg_variant_is_error(Type *t, int idx)
|
||||||
|
{
|
||||||
|
if (t == NULL) return 0;
|
||||||
|
if (t->kind == TY_NAMED) t = t->under;
|
||||||
|
if (t == NULL || t->kind != TY_TAGGED) return 0;
|
||||||
|
int has_err = 0;
|
||||||
|
for (Tparam *p = t->params; p; p = p->next)
|
||||||
|
if (p->type && p->type->iserror) { has_err = 1; break; }
|
||||||
|
int i = 0;
|
||||||
|
for (Tparam *p = t->params; p; p = p->next, i++) {
|
||||||
|
if (i == idx) {
|
||||||
|
if (has_err) return p->type && p->type->iserror;
|
||||||
|
/* legacy: index 0 is success, rest are errors */
|
||||||
|
return idx != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Find the variant-tag index of `vt` inside the tagged-union type `t`.
|
/* Find the variant-tag index of `vt` inside the tagged-union type `t`.
|
||||||
* Returns -1 if `t` is not tagged or `vt` does not match a variant.
|
* Returns -1 if `t` is not tagged or `vt` does not match a variant.
|
||||||
* Used by N_MATCH dispatch and by the let/assign/return tag synthesis. */
|
* Used by N_MATCH dispatch and by the let/assign/return tag synthesis. */
|
||||||
@@ -1905,32 +1944,32 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
case N_TRYPROP: {
|
case N_TRYPROP: {
|
||||||
/* Evaluate tagged value: AX=tag, DX=value0[, CX=value1].
|
/* Evaluate tagged value: AX=tag, DX=value0[, CX=value1].
|
||||||
* If tag != 0, propagate as the current function's return.
|
* If the tag matches an error variant, propagate as the
|
||||||
* On success, unwrap to the success-variant ABI: ≤8B values
|
* current function's return (with a tag remap to the
|
||||||
* land in AX; str values land in (AX=ptr, BX=len).
|
* enclosing fn's variant order). On success, unwrap to the
|
||||||
*
|
* success-variant ABI: ≤8B values in AX; str values in
|
||||||
* Tag remap: when operand and enclosing fn have different
|
* (AX=ptr, BX=len). */
|
||||||
* variant orderings, the operand's error tag must be
|
|
||||||
* translated to the enclosing fn's tag for the same variant
|
|
||||||
* type. For each non-first variant V_i in operand at index
|
|
||||||
* i, if i != enclosing's index for V (call it j), emit a
|
|
||||||
* conditional MOV $j → AX. Identity cases emit nothing. */
|
|
||||||
cgexpr(c, n->lhs, locals);
|
cgexpr(c, n->lhs, locals);
|
||||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||||
if (u && u->kind == TY_NAMED) u = u->under;
|
if (u && u->kind == TY_NAMED) u = u->under;
|
||||||
Type *r = cg_ret_type;
|
Type *r = cg_ret_type;
|
||||||
if (r && r->kind == TY_NAMED) r = r->under;
|
if (r && r->kind == TY_NAMED) r = r->under;
|
||||||
Type *first = (u && u->kind == TY_TAGGED && u->params)
|
int s_tag = cg_tagged_success_tag(u);
|
||||||
? u->params->type : NULL;
|
Type *succ_t = NULL;
|
||||||
int success_is_str = type_isstr(first);
|
if (u && u->kind == TY_TAGGED) {
|
||||||
|
int i = 0;
|
||||||
|
for (Tparam *p = u->params; p; p = p->next, i++)
|
||||||
|
if (i == s_tag) { succ_t = p->type; break; }
|
||||||
|
}
|
||||||
|
int success_is_str = type_isstr(succ_t);
|
||||||
char *cont = mklabel(c, "tryprop_ok");
|
char *cont = mklabel(c, "tryprop_ok");
|
||||||
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
ins2(c, A_CMPQ, aimm(s_tag), areg(D_AX));
|
||||||
ins1(c, A_JE, abranch(cont));
|
ins1(c, A_JE, abranch(cont));
|
||||||
if (u && r && r->kind == TY_TAGGED && u->params) {
|
if (u && r && r->kind == TY_TAGGED && u->params) {
|
||||||
char *propret = mklabel(c, "tryprop_ret");
|
char *propret = mklabel(c, "tryprop_ret");
|
||||||
int i = 1;
|
int i = 0;
|
||||||
for (Tparam *p = u->params->next; p;
|
for (Tparam *p = u->params; p; p = p->next, i++) {
|
||||||
p = p->next, i++) {
|
if (!cg_variant_is_error(u, i)) continue;
|
||||||
int j = cg_tag_for_variant(r, p->type);
|
int j = cg_tag_for_variant(r, p->type);
|
||||||
if (j < 0) j = 0;
|
if (j < 0) j = 0;
|
||||||
if (j == i) continue;
|
if (j == i) continue;
|
||||||
@@ -1957,11 +1996,16 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
cgexpr(c, n->lhs, locals);
|
cgexpr(c, n->lhs, locals);
|
||||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||||
if (u && u->kind == TY_NAMED) u = u->under;
|
if (u && u->kind == TY_NAMED) u = u->under;
|
||||||
Type *first = (u && u->kind == TY_TAGGED && u->params)
|
int s_tag = cg_tagged_success_tag(u);
|
||||||
? u->params->type : NULL;
|
Type *succ_t = NULL;
|
||||||
int success_is_str = type_isstr(first);
|
if (u && u->kind == TY_TAGGED) {
|
||||||
|
int i = 0;
|
||||||
|
for (Tparam *p = u->params; p; p = p->next, i++)
|
||||||
|
if (i == s_tag) { succ_t = p->type; break; }
|
||||||
|
}
|
||||||
|
int success_is_str = type_isstr(succ_t);
|
||||||
char *cont = mklabel(c, "tryunw_ok");
|
char *cont = mklabel(c, "tryunw_ok");
|
||||||
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
ins2(c, A_CMPQ, aimm(s_tag), areg(D_AX));
|
||||||
ins1(c, A_JE, abranch(cont));
|
ins1(c, A_JE, abranch(cont));
|
||||||
ins2(c, A_MOVQ, aimm(1), areg(D_DI));
|
ins2(c, A_MOVQ, aimm(1), areg(D_DI));
|
||||||
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ nkname(Nkind k)
|
|||||||
case N_TYPETEST: return "typetest";
|
case N_TYPETEST: return "typetest";
|
||||||
case N_TYPEASSERT: return "typeassert";
|
case N_TYPEASSERT: return "typeassert";
|
||||||
case N_VOIDLIT: return "voidlit";
|
case N_VOIDLIT: return "voidlit";
|
||||||
|
case N_TBANG: return "tbang";
|
||||||
case N_LAST: return "last";
|
case N_LAST: return "last";
|
||||||
}
|
}
|
||||||
return "?";
|
return "?";
|
||||||
|
|||||||
@@ -99,11 +99,66 @@ variant_present(Tparam *head, Type *vt)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* tagged_has_errflag — true iff any variant is `!`-marked. Determines
|
||||||
|
* whether the union uses Hare's explicit error subset or the legacy
|
||||||
|
* "first variant = success" convention. */
|
||||||
|
static int
|
||||||
|
tagged_has_errflag(Type *u)
|
||||||
|
{
|
||||||
|
if (u == NULL || u->kind != TY_TAGGED) return 0;
|
||||||
|
for (Tparam *p = u->params; p; p = p->next)
|
||||||
|
if (p->type && p->type->iserror) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tagged_is_error_variant — does `v` (a variant of `u`) belong to
|
||||||
|
* the error subset? Explicit-flag mode: only variants with iserror=1.
|
||||||
|
* Legacy mode (no flags): everything except the first variant. */
|
||||||
|
static int
|
||||||
|
tagged_is_error_variant(Type *u, Type *v)
|
||||||
|
{
|
||||||
|
if (u == NULL || u->kind != TY_TAGGED || v == NULL) return 0;
|
||||||
|
if (tagged_has_errflag(u)) return v->iserror != 0;
|
||||||
|
/* legacy: first variant is success, rest are errors */
|
||||||
|
return u->params && u->params->type != v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tagged_success_type — the success variant's type. Explicit-flag
|
||||||
|
* mode: the first non-flagged variant. Legacy: the first variant. */
|
||||||
|
static Type *
|
||||||
|
tagged_success_type(Type *u)
|
||||||
|
{
|
||||||
|
if (u == NULL || u->kind != TY_TAGGED) return NULL;
|
||||||
|
if (tagged_has_errflag(u)) {
|
||||||
|
for (Tparam *p = u->params; p; p = p->next)
|
||||||
|
if (p->type && !p->type->iserror) return p->type;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return u->params ? u->params->type : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static Type *
|
static Type *
|
||||||
resolve_type(Checker *c, Node *n)
|
resolve_type(Checker *c, Node *n)
|
||||||
{
|
{
|
||||||
if (n == NULL) return ty_void;
|
if (n == NULL) return ty_void;
|
||||||
switch (n->kind) {
|
switch (n->kind) {
|
||||||
|
case N_TBANG: {
|
||||||
|
/* `!T` — mark the resolved type as an error type. Wrap
|
||||||
|
* primitives in a fresh NAMED-less copy so we don't taint
|
||||||
|
* the shared ty_void / ty_str / ty_i32 globals. NAMED
|
||||||
|
* types are already unique per alias decl, so we can flip
|
||||||
|
* the bit in place. */
|
||||||
|
Type *t = resolve_type(c, n->lhs);
|
||||||
|
if (t == NULL || t == ty_err) return t;
|
||||||
|
if (t->kind == TY_NAMED) {
|
||||||
|
t->iserror = 1;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
Type *t2 = newtype(c->a, t->kind);
|
||||||
|
*t2 = *t;
|
||||||
|
t2->iserror = 1;
|
||||||
|
return t2;
|
||||||
|
}
|
||||||
case N_TNAME:
|
case N_TNAME:
|
||||||
return resolve_typename(c, n);
|
return resolve_typename(c, n);
|
||||||
case N_TPTR:
|
case N_TPTR:
|
||||||
@@ -890,15 +945,20 @@ cexpr(Checker *c, Node *n)
|
|||||||
n->kind == N_TRYPROP ? "?" : "!",
|
n->kind == N_TRYPROP ? "?" : "!",
|
||||||
type_name(c->a, t));
|
type_name(c->a, t));
|
||||||
}
|
}
|
||||||
/* Convention: first variant is the success type; the remaining
|
/* Error subset = `!`-flagged variants (Hare semantics) or
|
||||||
* variants are the error subset.
|
* everything-but-first when no flags are present (legacy).
|
||||||
*
|
*
|
||||||
* For ? : each error variant must be propagatable — i.e. it
|
* For ? : each error variant must be propagatable — i.e. it
|
||||||
* must be a variant of the enclosing function's return type
|
* must be a variant of the enclosing function's return type
|
||||||
* (so the caller can match on it). cgen does the tag remap.
|
* (so the caller can match on it). cgen does the tag remap.
|
||||||
* For ! : no propagation, so no subset check. */
|
* For ! : no propagation, so no subset check. */
|
||||||
Tparam *first = u->params;
|
Type *succ = tagged_success_type(u);
|
||||||
if (n->kind == N_TRYPROP && first && first->next) {
|
int has_errors = 0;
|
||||||
|
for (Tparam *p = u->params; p; p = p->next)
|
||||||
|
if (tagged_is_error_variant(u, p->type)) {
|
||||||
|
has_errors = 1; break;
|
||||||
|
}
|
||||||
|
if (n->kind == N_TRYPROP && has_errors) {
|
||||||
Type *r = c->ret;
|
Type *r = c->ret;
|
||||||
Type *ru = (r && r->kind == TY_NAMED) ? r->under : r;
|
Type *ru = (r && r->kind == TY_NAMED) ? r->under : r;
|
||||||
if (ru == NULL || ru->kind != TY_TAGGED) {
|
if (ru == NULL || ru->kind != TY_TAGGED) {
|
||||||
@@ -907,7 +967,9 @@ cexpr(Checker *c, Node *n)
|
|||||||
"union to propagate errors (got %s)",
|
"union to propagate errors (got %s)",
|
||||||
type_name(c->a, r));
|
type_name(c->a, r));
|
||||||
} else {
|
} else {
|
||||||
for (Tparam *e = first->next; e; e = e->next) {
|
for (Tparam *e = u->params; e; e = e->next) {
|
||||||
|
if (!tagged_is_error_variant(u, e->type))
|
||||||
|
continue;
|
||||||
int ok = 0;
|
int ok = 0;
|
||||||
for (Tparam *p = ru->params; p;
|
for (Tparam *p = ru->params; p;
|
||||||
p = p->next)
|
p = p->next)
|
||||||
@@ -923,7 +985,7 @@ cexpr(Checker *c, Node *n)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return n->type = first ? first->type : ty_err;
|
return n->type = succ ? succ : ty_err;
|
||||||
}
|
}
|
||||||
case N_TUPLE: {
|
case N_TUPLE: {
|
||||||
/* keep untyped element types; assignability is checked
|
/* keep untyped element types; assignability is checked
|
||||||
@@ -1223,6 +1285,7 @@ check_file(Checker *c, Node *file)
|
|||||||
if (under) {
|
if (under) {
|
||||||
d->type->size = under->size;
|
d->type->size = under->size;
|
||||||
d->type->align = under->align;
|
d->type->align = under->align;
|
||||||
|
d->type->iserror = under->iserror;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Node *d = file->list; d; d = d->next) {
|
for (Node *d = file->list; d; d = d->next) {
|
||||||
|
|||||||
@@ -151,6 +151,16 @@ parsetype(Parser *p)
|
|||||||
{
|
{
|
||||||
Pos pp = p->cur.pos;
|
Pos pp = p->cur.pos;
|
||||||
switch (p->cur.kind) {
|
switch (p->cur.kind) {
|
||||||
|
case TK_NOT: {
|
||||||
|
/* `!T` — error-flagged type. The flag propagates through
|
||||||
|
* NAMED aliases and lives on the underlying Type, not on
|
||||||
|
* a wrapper. The AST keeps an N_TBANG wrapper so prints
|
||||||
|
* and selfhost can recognise the marker. */
|
||||||
|
advance(p);
|
||||||
|
Node *n = newnode(p->a, N_TBANG, pp);
|
||||||
|
n->lhs = parsetype(p);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
case TK_STAR: {
|
case TK_STAR: {
|
||||||
advance(p);
|
advance(p);
|
||||||
Node *n = newnode(p->a, N_TPTR, pp);
|
Node *n = newnode(p->a, N_TPTR, pp);
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ type_named(Arena *a, const char *name, Type *under)
|
|||||||
if (under) {
|
if (under) {
|
||||||
t->size = under->size;
|
t->size = under->size;
|
||||||
t->align = under->align;
|
t->align = under->align;
|
||||||
|
t->iserror = under->iserror;
|
||||||
}
|
}
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,6 +296,7 @@ typedef enum {
|
|||||||
N_TYPETEST, /* lhs is T → bool */
|
N_TYPETEST, /* lhs is T → bool */
|
||||||
N_TYPEASSERT, /* lhs as T → T (abort if tag mismatch) */
|
N_TYPEASSERT, /* lhs as T → T (abort if tag mismatch) */
|
||||||
N_VOIDLIT, /* `void` as expression — zero-size void value */
|
N_VOIDLIT, /* `void` as expression — zero-size void value */
|
||||||
|
N_TBANG, /* `!T` — error-flagged type. lhs = inner type. */
|
||||||
|
|
||||||
N_LAST
|
N_LAST
|
||||||
} Nkind;
|
} Nkind;
|
||||||
@@ -403,6 +404,10 @@ struct Type {
|
|||||||
int variadic;
|
int variadic;
|
||||||
const char *name; /* named alias / debug */
|
const char *name; /* named alias / debug */
|
||||||
Type *under; /* underlying resolved type for NAMED */
|
Type *under; /* underlying resolved type for NAMED */
|
||||||
|
int iserror;/* Hare-style `!T` error mark; propagates
|
||||||
|
* through NAMED aliases. Variants with
|
||||||
|
* iserror=1 are the propagation target of
|
||||||
|
* the `?` operator. */
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Type *ty_void, *ty_bool, *ty_rune;
|
extern Type *ty_void, *ty_bool, *ty_rune;
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
||||||
|
|
||||||
// invalid — input wasn't a valid number in the requested format.
|
// invalid — input wasn't a valid number in the requested format.
|
||||||
// Payload is the byte index of the first offending position (Hare
|
// Payload is the byte index of the first offending position. Mirrors
|
||||||
// strconv::invalid is `!size` carrying the same).
|
// Hare's strconv::invalid = !size (we use i32 instead of size).
|
||||||
export type invalid = i32;
|
export type invalid = !i32;
|
||||||
|
|
||||||
// overflow — input was valid but doesn't fit the target type. No
|
// overflow — input was valid but doesn't fit the target type. No
|
||||||
// payload (a single yes/no signal). Mirrors Hare's `!void` shape.
|
// payload (a single yes/no signal). Mirrors Hare's !void shape.
|
||||||
export type overflow = void;
|
export type overflow = !void;
|
||||||
|
|
||||||
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
||||||
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
||||||
|
|||||||
@@ -89,8 +89,9 @@ def N_MASSIGN: i32 = 59;
|
|||||||
def N_TYPETEST: i32 = 60;
|
def N_TYPETEST: i32 = 60;
|
||||||
def N_TYPEASSERT: i32 = 61;
|
def N_TYPEASSERT: i32 = 61;
|
||||||
def N_VOIDLIT: i32 = 62;
|
def N_VOIDLIT: i32 = 62;
|
||||||
|
def N_TBANG: i32 = 63;
|
||||||
|
|
||||||
def N_LAST: i32 = 63;
|
def N_LAST: i32 = 64;
|
||||||
|
|
||||||
// ---- Node -------------------------------------------------------------
|
// ---- Node -------------------------------------------------------------
|
||||||
|
|
||||||
@@ -192,6 +193,7 @@ fn nkname(k: i32) str = {
|
|||||||
if (k == N_TYPETEST) { return "typetest"; };
|
if (k == N_TYPETEST) { return "typetest"; };
|
||||||
if (k == N_TYPEASSERT) { return "typeassert"; };
|
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||||
if (k == N_VOIDLIT) { return "voidlit"; };
|
if (k == N_VOIDLIT) { return "voidlit"; };
|
||||||
|
if (k == N_TBANG) { return "tbang"; };
|
||||||
if (k == N_LAST) { return "last"; };
|
if (k == N_LAST) { return "last"; };
|
||||||
return "?";
|
return "?";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -129,6 +129,14 @@ fn parsetype(p: *parser) *node = {
|
|||||||
let pl: i32 = p.curline;
|
let pl: i32 = p.curline;
|
||||||
let pc: i32 = p.curcol;
|
let pc: i32 = p.curcol;
|
||||||
|
|
||||||
|
if (p.curkind == TK_NOT) {
|
||||||
|
// `!T` — Hare error-flagged type wrapper.
|
||||||
|
advance(p);
|
||||||
|
let n: *node = newnode(p.a, N_TBANG, pf, pl, pc);
|
||||||
|
n.lhs = parsetype(p);
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
if (p.curkind == TK_STAR) {
|
if (p.curkind == TK_STAR) {
|
||||||
advance(p);
|
advance(p);
|
||||||
let n: *node = newnode(p.a, N_TPTR, pf, pl, pc);
|
let n: *node = newnode(p.a, N_TPTR, pf, pl, pc);
|
||||||
|
|||||||
@@ -329,13 +329,13 @@ export fn freearena(a: *arena) void = {
|
|||||||
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
||||||
|
|
||||||
// invalid — input wasn't a valid number in the requested format.
|
// invalid — input wasn't a valid number in the requested format.
|
||||||
// Payload is the byte index of the first offending position (Hare
|
// Payload is the byte index of the first offending position. Mirrors
|
||||||
// strconv::invalid is `!size` carrying the same).
|
// Hare's strconv::invalid = !size (we use i32 instead of size).
|
||||||
export type invalid = i32;
|
export type invalid = !i32;
|
||||||
|
|
||||||
// overflow — input was valid but doesn't fit the target type. No
|
// overflow — input was valid but doesn't fit the target type. No
|
||||||
// payload (a single yes/no signal). Mirrors Hare's `!void` shape.
|
// payload (a single yes/no signal). Mirrors Hare's !void shape.
|
||||||
export type overflow = void;
|
export type overflow = !void;
|
||||||
|
|
||||||
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
||||||
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
||||||
@@ -1735,8 +1735,9 @@ def N_MASSIGN: i32 = 59;
|
|||||||
def N_TYPETEST: i32 = 60;
|
def N_TYPETEST: i32 = 60;
|
||||||
def N_TYPEASSERT: i32 = 61;
|
def N_TYPEASSERT: i32 = 61;
|
||||||
def N_VOIDLIT: i32 = 62;
|
def N_VOIDLIT: i32 = 62;
|
||||||
|
def N_TBANG: i32 = 63;
|
||||||
|
|
||||||
def N_LAST: i32 = 63;
|
def N_LAST: i32 = 64;
|
||||||
|
|
||||||
// ---- Node -------------------------------------------------------------
|
// ---- Node -------------------------------------------------------------
|
||||||
|
|
||||||
@@ -1838,6 +1839,7 @@ fn nkname(k: i32) str = {
|
|||||||
if (k == N_TYPETEST) { return "typetest"; };
|
if (k == N_TYPETEST) { return "typetest"; };
|
||||||
if (k == N_TYPEASSERT) { return "typeassert"; };
|
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||||
if (k == N_VOIDLIT) { return "voidlit"; };
|
if (k == N_VOIDLIT) { return "voidlit"; };
|
||||||
|
if (k == N_TBANG) { return "tbang"; };
|
||||||
if (k == N_LAST) { return "last"; };
|
if (k == N_LAST) { return "last"; };
|
||||||
return "?";
|
return "?";
|
||||||
};
|
};
|
||||||
@@ -2982,6 +2984,14 @@ fn parsetype(p: *parser) *node = {
|
|||||||
let pl: i32 = p.curline;
|
let pl: i32 = p.curline;
|
||||||
let pc: i32 = p.curcol;
|
let pc: i32 = p.curcol;
|
||||||
|
|
||||||
|
if (p.curkind == TK_NOT) {
|
||||||
|
// `!T` — Hare error-flagged type wrapper.
|
||||||
|
advance(p);
|
||||||
|
let n: *node = newnode(p.a, N_TBANG, pf, pl, pc);
|
||||||
|
n.lhs = parsetype(p);
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
if (p.curkind == TK_STAR) {
|
if (p.curkind == TK_STAR) {
|
||||||
advance(p);
|
advance(p);
|
||||||
let n: *node = newnode(p.a, N_TPTR, pf, pl, pc);
|
let n: *node = newnode(p.a, N_TPTR, pf, pl, pc);
|
||||||
|
|||||||
@@ -329,13 +329,13 @@ export fn freearena(a: *arena) void = {
|
|||||||
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
||||||
|
|
||||||
// invalid — input wasn't a valid number in the requested format.
|
// invalid — input wasn't a valid number in the requested format.
|
||||||
// Payload is the byte index of the first offending position (Hare
|
// Payload is the byte index of the first offending position. Mirrors
|
||||||
// strconv::invalid is `!size` carrying the same).
|
// Hare's strconv::invalid = !size (we use i32 instead of size).
|
||||||
export type invalid = i32;
|
export type invalid = !i32;
|
||||||
|
|
||||||
// overflow — input was valid but doesn't fit the target type. No
|
// overflow — input was valid but doesn't fit the target type. No
|
||||||
// payload (a single yes/no signal). Mirrors Hare's `!void` shape.
|
// payload (a single yes/no signal). Mirrors Hare's !void shape.
|
||||||
export type overflow = void;
|
export type overflow = !void;
|
||||||
|
|
||||||
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
||||||
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
||||||
@@ -1735,8 +1735,9 @@ def N_MASSIGN: i32 = 59;
|
|||||||
def N_TYPETEST: i32 = 60;
|
def N_TYPETEST: i32 = 60;
|
||||||
def N_TYPEASSERT: i32 = 61;
|
def N_TYPEASSERT: i32 = 61;
|
||||||
def N_VOIDLIT: i32 = 62;
|
def N_VOIDLIT: i32 = 62;
|
||||||
|
def N_TBANG: i32 = 63;
|
||||||
|
|
||||||
def N_LAST: i32 = 63;
|
def N_LAST: i32 = 64;
|
||||||
|
|
||||||
// ---- Node -------------------------------------------------------------
|
// ---- Node -------------------------------------------------------------
|
||||||
|
|
||||||
@@ -1838,6 +1839,7 @@ fn nkname(k: i32) str = {
|
|||||||
if (k == N_TYPETEST) { return "typetest"; };
|
if (k == N_TYPETEST) { return "typetest"; };
|
||||||
if (k == N_TYPEASSERT) { return "typeassert"; };
|
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||||
if (k == N_VOIDLIT) { return "voidlit"; };
|
if (k == N_VOIDLIT) { return "voidlit"; };
|
||||||
|
if (k == N_TBANG) { return "tbang"; };
|
||||||
if (k == N_LAST) { return "last"; };
|
if (k == N_LAST) { return "last"; };
|
||||||
return "?";
|
return "?";
|
||||||
};
|
};
|
||||||
@@ -2982,6 +2984,14 @@ fn parsetype(p: *parser) *node = {
|
|||||||
let pl: i32 = p.curline;
|
let pl: i32 = p.curline;
|
||||||
let pc: i32 = p.curcol;
|
let pc: i32 = p.curcol;
|
||||||
|
|
||||||
|
if (p.curkind == TK_NOT) {
|
||||||
|
// `!T` — Hare error-flagged type wrapper.
|
||||||
|
advance(p);
|
||||||
|
let n: *node = newnode(p.a, N_TBANG, pf, pl, pc);
|
||||||
|
n.lhs = parsetype(p);
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
if (p.curkind == TK_STAR) {
|
if (p.curkind == TK_STAR) {
|
||||||
advance(p);
|
advance(p);
|
||||||
let n: *node = newnode(p.a, N_TPTR, pf, pl, pc);
|
let n: *node = newnode(p.a, N_TPTR, pf, pl, pc);
|
||||||
|
|||||||
@@ -221,13 +221,13 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
|||||||
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
||||||
|
|
||||||
// invalid — input wasn't a valid number in the requested format.
|
// invalid — input wasn't a valid number in the requested format.
|
||||||
// Payload is the byte index of the first offending position (Hare
|
// Payload is the byte index of the first offending position. Mirrors
|
||||||
// strconv::invalid is `!size` carrying the same).
|
// Hare's strconv::invalid = !size (we use i32 instead of size).
|
||||||
export type invalid = i32;
|
export type invalid = !i32;
|
||||||
|
|
||||||
// overflow — input was valid but doesn't fit the target type. No
|
// overflow — input was valid but doesn't fit the target type. No
|
||||||
// payload (a single yes/no signal). Mirrors Hare's `!void` shape.
|
// payload (a single yes/no signal). Mirrors Hare's !void shape.
|
||||||
export type overflow = void;
|
export type overflow = !void;
|
||||||
|
|
||||||
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
||||||
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
||||||
|
|||||||
@@ -135,6 +135,16 @@ static const struct row rows[] = {
|
|||||||
{ "fn inner() (i32 | str | bool) = { return 1; }; "
|
{ "fn inner() (i32 | str | bool) = { return 1; }; "
|
||||||
"fn outer() (i64 | bool | str) = { let v: i32 = inner()?; return v: i64; };",
|
"fn outer() (i64 | bool | str) = { let v: i32 = inner()?; return v: i64; };",
|
||||||
"ok" }, /* reversed-order error subset is OK */
|
"ok" }, /* reversed-order error subset is OK */
|
||||||
|
|
||||||
|
/* `!`-flagged error variants (Hare's explicit error marker) */
|
||||||
|
{ "type invalid = !i32; "
|
||||||
|
"fn parse() (i64 | invalid) = { return 5: invalid; }; "
|
||||||
|
"fn caller() (i64 | invalid) = { let v: i64 = parse()?; return v + 1; };",
|
||||||
|
"ok" },
|
||||||
|
{ "type invalid = !i32; type overflow = !void; "
|
||||||
|
"fn parse() (invalid | i64 | overflow) = { return 0i64; }; "
|
||||||
|
"fn caller() i32 = { let v: i64 = parse()?; return v: i32; };",
|
||||||
|
"enclosing function must return a tagged union" },
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|||||||
@@ -538,6 +538,29 @@ static const struct row rows[] = {
|
|||||||
" };\n"
|
" };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};", 3 },
|
"};", 3 },
|
||||||
|
/* `!`-flagged error variants: success picked by absence of `!`,
|
||||||
|
* errors picked by presence. Tag remap still works across
|
||||||
|
* different variant orders between operand and enclosing fn. */
|
||||||
|
{ "type invalid = !i32;\n"
|
||||||
|
"type overflow = !void;\n"
|
||||||
|
"fn inner(n: i32) (invalid | i64 | overflow) = {\n"
|
||||||
|
" if (n == 0) { return 7: invalid; };\n"
|
||||||
|
" if (n < 0) { return void: overflow; };\n"
|
||||||
|
" return n: i64 + 1000;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn outer(n: i32) (overflow | i64 | invalid) = {\n"
|
||||||
|
" let v: i64 = inner(n)?;\n"
|
||||||
|
" return v + 1;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let r: (overflow | i64 | invalid) = outer(0);\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let v: i64 => return v: i32;\n"
|
||||||
|
" case let e: invalid => return e + 100;\n"
|
||||||
|
" case let e: overflow => return 999;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};", 107 },
|
||||||
/* ? with reversed error variant order: operand (i32 | str | bool),
|
/* ? with reversed error variant order: operand (i32 | str | bool),
|
||||||
* enclosing (i64 | bool | str). Tag remap maps str:1→2, bool:2→1. */
|
* enclosing (i64 | bool | str). Tag remap maps str:1→2, bool:2→1. */
|
||||||
{ "fn inner(n: i32) (i32 | str | bool) = {\n"
|
{ "fn inner(n: i32) (i32 | str | bool) = {\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user