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:
2026-05-12 02:39:54 +09:00
parent fd45aedf6c
commit 594a2bad62
14 changed files with 237 additions and 50 deletions

View File

@@ -170,6 +170,45 @@ cg_variant_match(Type *vt, Type *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`.
* 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. */
@@ -1905,32 +1944,32 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
case N_TRYPROP: {
/* Evaluate tagged value: AX=tag, DX=value0[, CX=value1].
* If tag != 0, propagate as the current function's return.
* On success, unwrap to the success-variant ABI: ≤8B values
* land in AX; str values land in (AX=ptr, BX=len).
*
* Tag remap: when operand and enclosing fn have different
* 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. */
* If the tag matches an error variant, propagate as the
* current function's return (with a tag remap to the
* enclosing fn's variant order). On success, unwrap to the
* success-variant ABI: ≤8B values in AX; str values in
* (AX=ptr, BX=len). */
cgexpr(c, n->lhs, locals);
Type *u = n->lhs ? n->lhs->type : NULL;
if (u && u->kind == TY_NAMED) u = u->under;
Type *r = cg_ret_type;
if (r && r->kind == TY_NAMED) r = r->under;
Type *first = (u && u->kind == TY_TAGGED && u->params)
? u->params->type : NULL;
int success_is_str = type_isstr(first);
int s_tag = cg_tagged_success_tag(u);
Type *succ_t = NULL;
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");
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));
if (u && r && r->kind == TY_TAGGED && u->params) {
char *propret = mklabel(c, "tryprop_ret");
int i = 1;
for (Tparam *p = u->params->next; p;
p = p->next, i++) {
int i = 0;
for (Tparam *p = u->params; p; p = p->next, i++) {
if (!cg_variant_is_error(u, i)) continue;
int j = cg_tag_for_variant(r, p->type);
if (j < 0) j = 0;
if (j == i) continue;
@@ -1957,11 +1996,16 @@ cgexpr(Cg *c, Node *n, Local *locals)
cgexpr(c, n->lhs, locals);
Type *u = n->lhs ? n->lhs->type : NULL;
if (u && u->kind == TY_NAMED) u = u->under;
Type *first = (u && u->kind == TY_TAGGED && u->params)
? u->params->type : NULL;
int success_is_str = type_isstr(first);
int s_tag = cg_tagged_success_tag(u);
Type *succ_t = NULL;
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");
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));
ins2(c, A_MOVQ, aimm(1), areg(D_DI));
ins2(c, A_MOVQ, aimm(60), areg(D_AX));