wcc: match-as-expression with yield

`match (e) { ... }` can now sit in expression position, with each
arm using `yield expr;` to produce the match's value:

    let v = match (r) {
    case let n: i32 => yield n + 1;
    case let s: str => yield s.len: i32 + 100;
    };

TK_YIELD keyword + N_YIELD AST node, both appended at the tail of
their enums to keep prior numeric values byte-stable for the
wwdump-diff gates.

Checker: cexpr for N_MATCH walks each arm's body looking for the
first N_YIELD; the match's type is the unified yield type (or
ty_void if no yield, preserving the statement-form semantics).
Mismatched arm yields are flagged.

Cgen: a yield-target stack (separate from the loop break stack)
holds each enclosing match's end label. N_YIELD evaluates its
expression into AX (and BX for str) and JMPs to the topmost entry.
cgmatch pushes its end label on entry and pops on exit.

Selfhost mirror: lib/ww/lex/tok.ww kwtab+name, lib/ww/ast.ww
N_YIELD def+print, lib/ww/parse/stmt.ww yield-stmt; selfhost cgen
adds a yieldbuf to the cgen struct and a cgyield helper. Verified
end-to-end: a yield-using program compiled via the wwstage cgen
matches the C-cgen build's exit code.
This commit is contained in:
2026-05-12 03:08:00 +09:00
parent 67e27589fd
commit f267f99a2b
15 changed files with 231 additions and 14 deletions

View File

@@ -99,6 +99,33 @@ variant_present(Tparam *head, Type *vt)
return 0;
}
/* match_yield_type — walk a match arm's body looking for the type
* of its first `yield expr;` statement. Returns NULL if no yield
* was found. Doesn't descend into nested match bodies — each match
* is its own yield scope. */
static Type *
match_yield_type(Node *body)
{
if (body == NULL) return NULL;
if (body->kind == N_YIELD) return body->lhs ? body->lhs->type : NULL;
if (body->kind == N_MATCH) return NULL; /* inner match: own scope */
if (body->kind == N_BLOCK) {
for (Node *s = body->list; s; s = s->next) {
Type *t = match_yield_type(s);
if (t) return t;
}
return NULL;
}
if (body->kind == N_IF) {
Type *t = match_yield_type(body->body);
if (t) return t;
return match_yield_type(body->els);
}
if (body->kind == N_FOR || body->kind == N_FORRANGE)
return match_yield_type(body->body);
return NULL;
}
/* 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. */
@@ -913,7 +940,20 @@ cexpr(Checker *c, Node *n)
type_name(c->a, p->type));
}
}
n->type = ty_void;
/* match-as-expression: the type is the common yield type
* across arms. If no arm yields, the match is a statement
* and its type is void. */
Type *yt = NULL;
for (Node *cs = n->list; cs; cs = cs->next) {
Type *t = match_yield_type(cs->body);
if (t == NULL) continue;
if (yt == NULL) yt = t;
else if (!type_eq(yt, t) && !type_assignable(yt, t))
err(c, cs->pos,
"match arm yields %s, expected %s",
type_name(c->a, t), type_name(c->a, yt));
}
n->type = yt ? yt : ty_void;
return n->type;
}
case N_TYPETEST: case N_TYPEASSERT: {
@@ -1221,6 +1261,7 @@ cstmt(Checker *c, Node *n)
break;
}
case N_DEFER: (void)cexpr(c, n->lhs); break;
case N_YIELD: if (n->lhs) (void)cexpr(c, n->lhs); break;
case N_BREAK:
case N_CONTINUE:
if (c->loops == 0)