wcc: tagged-union foundations (never, void, flatten, exhaust)
- `never` bottom type: TY_NEVER, assignable to anything; size 0.
- Type-set normalization for N_TTAGGED in resolve_type:
- flatten nested anonymous (A|B)|C → (A|B|C); named aliases stay
nominal (not flattened through)
- dedup duplicates (NAMED pointer-id; others structural)
- drop `never` variants
- collapse single-element set: (T|never) → T, (T|T) → T
- Match exhaustiveness: error when a variant is unhandled and no
default arm covers it. Multi-pattern `case T1 | T2 =>` counts
each alt.
- (T | void) optionals: bare `return;` from a tagged-union-returning
fn emits the void variant's tag (payload undefined; void size 0).
selfhost mirrored: TY_NEVER constant + tynever in tctx + seedprim
entry; voidvariantindex helper; cgreturn bare-return handling.
This commit is contained in:
@@ -2691,7 +2691,24 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
*
|
*
|
||||||
* Tagged-return ABI: AX=tag, DX=value0[, CX=value1]. CX is
|
* Tagged-return ABI: AX=tag, DX=value0[, CX=value1]. CX is
|
||||||
* only meaningful when the union has a >8B variant (e.g.
|
* only meaningful when the union has a >8B variant (e.g.
|
||||||
* str, where ptr→DX and len→CX). */
|
* str, where ptr→DX and len→CX).
|
||||||
|
*
|
||||||
|
* Bare `return;` from a tagged-union-returning function: this
|
||||||
|
* is producing the void variant. Emit its tag; the payload is
|
||||||
|
* undefined (void has size 0). */
|
||||||
|
if (n->lhs == NULL && cg_ret_type) {
|
||||||
|
Type *rt = cg_ret_type;
|
||||||
|
if (rt->kind == TY_NAMED) rt = rt->under;
|
||||||
|
if (rt && rt->kind == TY_TAGGED) {
|
||||||
|
int tag = cg_tag_for_variant(rt, ty_void);
|
||||||
|
if (tag < 0) tag = 0;
|
||||||
|
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||||
|
ins1(c, A_POPQ, areg(D_BP));
|
||||||
|
ins0(c, A_RET);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (n->lhs && cg_ret_type) {
|
if (n->lhs && cg_ret_type) {
|
||||||
Type *rt = cg_ret_type;
|
Type *rt = cg_ret_type;
|
||||||
if (rt->kind == TY_NAMED) rt = rt->under;
|
if (rt->kind == TY_NAMED) rt = rt->under;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ lookup_builtin(const char *name)
|
|||||||
if (strcmp(name, "f32") == 0) return ty_f32;
|
if (strcmp(name, "f32") == 0) return ty_f32;
|
||||||
if (strcmp(name, "f64") == 0) return ty_f64;
|
if (strcmp(name, "f64") == 0) return ty_f64;
|
||||||
if (strcmp(name, "str") == 0) return ty_str;
|
if (strcmp(name, "str") == 0) return ty_str;
|
||||||
|
if (strcmp(name, "never") == 0) return ty_never;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +79,26 @@ resolve_typename(Checker *c, Node *n)
|
|||||||
return s->type;
|
return s->type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Variant identity for tagged unions. Mirrors cg_variant_match in
|
||||||
|
* cgen: NAMED types are nominal (pointer-identical) and don't unify
|
||||||
|
* with their underlying; everything else is structural type_eq. */
|
||||||
|
static int
|
||||||
|
variant_match(Type *a, Type *b)
|
||||||
|
{
|
||||||
|
if (a == NULL || b == NULL) return 0;
|
||||||
|
if (a->kind == TY_NAMED && b->kind == TY_NAMED) return a == b;
|
||||||
|
if (a->kind == TY_NAMED || b->kind == TY_NAMED) return 0;
|
||||||
|
return type_eq(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
variant_present(Tparam *head, Type *vt)
|
||||||
|
{
|
||||||
|
for (Tparam *p = head; p; p = p->next)
|
||||||
|
if (variant_match(p->type, vt)) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static Type *
|
static Type *
|
||||||
resolve_type(Checker *c, Node *n)
|
resolve_type(Checker *c, Node *n)
|
||||||
{
|
{
|
||||||
@@ -123,19 +144,53 @@ resolve_type(Checker *c, Node *n)
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
case N_TTAGGED: {
|
case N_TTAGGED: {
|
||||||
/* (T1 | T2 | ...) — tag (8B) followed by the largest variant. */
|
/* (T1 | T2 | ...) — tag (8B) followed by the largest variant.
|
||||||
|
* Type-set normalization (Hare-style):
|
||||||
|
* - Flatten nested anonymous (A | B) | C → (A | B | C). Named
|
||||||
|
* aliases over tagged unions stay nominal — not flattened.
|
||||||
|
* - Drop `never`: bottom contributes no values.
|
||||||
|
* - Dedup variants. Equality follows cg_variant_match: NAMED
|
||||||
|
* types compare by pointer-identity, others structurally.
|
||||||
|
* - If exactly one variant remains, the tagged union collapses
|
||||||
|
* to that variant. (i32 | never) → i32.
|
||||||
|
* - If zero remain (all variants were `never`), the type is
|
||||||
|
* `never` itself. */
|
||||||
Type *t = newtype(c->a, TY_TAGGED);
|
Type *t = newtype(c->a, TY_TAGGED);
|
||||||
Tparam *head = NULL, *tail = NULL;
|
Tparam *head = NULL, *tail = NULL;
|
||||||
u64 maxsz = 0, al = 8;
|
u64 maxsz = 0, al = 8;
|
||||||
|
int nv = 0;
|
||||||
for (Node *e = n->list; e; e = e->next) {
|
for (Node *e = n->list; e; e = e->next) {
|
||||||
|
Type *vt = resolve_type(c, e);
|
||||||
|
if (vt == ty_never) continue;
|
||||||
|
if (vt && vt->kind == TY_TAGGED) {
|
||||||
|
/* flatten anonymous nested tagged */
|
||||||
|
for (Tparam *src = vt->params; src; src = src->next) {
|
||||||
|
Type *st = src->type;
|
||||||
|
if (st == ty_never) continue;
|
||||||
|
if (variant_present(head, st)) continue;
|
||||||
Tparam *tp = amalloc(c->a, sizeof *tp);
|
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||||
tp->type = resolve_type(c, e);
|
tp->type = st;
|
||||||
if (tp->type && tp->type->size > maxsz) maxsz = tp->type->size;
|
if (st && st->size > maxsz) maxsz = st->size;
|
||||||
if (tp->type && tp->type->align > al) al = tp->type->align;
|
if (st && st->align > al) al = st->align;
|
||||||
if (head == NULL) head = tp;
|
if (head == NULL) head = tp;
|
||||||
else tail->next = tp;
|
else tail->next = tp;
|
||||||
tail = tp;
|
tail = tp;
|
||||||
|
nv++;
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (variant_present(head, vt)) continue;
|
||||||
|
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||||
|
tp->type = vt;
|
||||||
|
if (vt && vt->size > maxsz) maxsz = vt->size;
|
||||||
|
if (vt && vt->align > al) al = vt->align;
|
||||||
|
if (head == NULL) head = tp;
|
||||||
|
else tail->next = tp;
|
||||||
|
tail = tp;
|
||||||
|
nv++;
|
||||||
|
}
|
||||||
|
if (nv == 0) return ty_never;
|
||||||
|
if (nv == 1 && head) return head->type;
|
||||||
t->params = head;
|
t->params = head;
|
||||||
t->size = 8 + maxsz;
|
t->size = 8 + maxsz;
|
||||||
t->align = al;
|
t->align = al;
|
||||||
@@ -728,6 +783,7 @@ cexpr(Checker *c, Node *n)
|
|||||||
return n->type = err(c, n->pos,
|
return n->type = err(c, n->pos,
|
||||||
"match on non-tagged-union %s", type_name(c->a, st));
|
"match on non-tagged-union %s", type_name(c->a, st));
|
||||||
}
|
}
|
||||||
|
int has_default = 0;
|
||||||
for (Node *cs = n->list; cs; cs = cs->next) {
|
for (Node *cs = n->list; cs; cs = cs->next) {
|
||||||
Scope *saved = c->cur;
|
Scope *saved = c->cur;
|
||||||
c->cur = newscope(c->a, saved);
|
c->cur = newscope(c->a, saved);
|
||||||
@@ -736,7 +792,9 @@ cexpr(Checker *c, Node *n)
|
|||||||
* =>` get this — `case =>` (default) leaves cs->type NULL.
|
* =>` get this — `case =>` (default) leaves cs->type NULL.
|
||||||
* For multi-pattern `case T1 | T2 =>` each alternative in
|
* For multi-pattern `case T1 | T2 =>` each alternative in
|
||||||
* cs->list also gets its type resolved in place. */
|
* cs->list also gets its type resolved in place. */
|
||||||
if (cs->lhs) {
|
if (cs->lhs == NULL) {
|
||||||
|
has_default = 1;
|
||||||
|
} else {
|
||||||
Type *vt = resolve_type(c, cs->lhs);
|
Type *vt = resolve_type(c, cs->lhs);
|
||||||
cs->type = vt;
|
cs->type = vt;
|
||||||
for (Node *alt = cs->list; alt; alt = alt->next)
|
for (Node *alt = cs->list; alt; alt = alt->next)
|
||||||
@@ -747,6 +805,31 @@ cexpr(Checker *c, Node *n)
|
|||||||
cstmt(c, cs->body);
|
cstmt(c, cs->body);
|
||||||
c->cur = saved;
|
c->cur = saved;
|
||||||
}
|
}
|
||||||
|
/* Exhaustiveness: every variant must be handled. A default arm
|
||||||
|
* absorbs anything not otherwise covered. */
|
||||||
|
if (!has_default) {
|
||||||
|
for (Tparam *p = u->params; p; p = p->next) {
|
||||||
|
int covered = 0;
|
||||||
|
for (Node *cs = n->list; cs && !covered;
|
||||||
|
cs = cs->next) {
|
||||||
|
if (variant_match(cs->type, p->type)) {
|
||||||
|
covered = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (Node *alt = cs->list; alt;
|
||||||
|
alt = alt->next)
|
||||||
|
if (variant_match(alt->type,
|
||||||
|
p->type)) {
|
||||||
|
covered = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!covered)
|
||||||
|
err(c, n->pos,
|
||||||
|
"match: variant %s not handled",
|
||||||
|
type_name(c->a, p->type));
|
||||||
|
}
|
||||||
|
}
|
||||||
n->type = ty_void;
|
n->type = ty_void;
|
||||||
return n->type;
|
return n->type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Type *ty_u8, *ty_u16, *ty_u32, *ty_u64;
|
|||||||
Type *ty_int, *ty_uint, *ty_uintptr;
|
Type *ty_int, *ty_uint, *ty_uintptr;
|
||||||
Type *ty_f32, *ty_f64, *ty_str;
|
Type *ty_f32, *ty_f64, *ty_str;
|
||||||
Type *ty_err;
|
Type *ty_err;
|
||||||
|
Type *ty_never;
|
||||||
Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str;
|
Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str;
|
||||||
Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil;
|
Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil;
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ typesinit(Arena *a)
|
|||||||
/* str is { *u8, len } — 16 bytes on amd64. ABI: pointer + u64. */
|
/* str is { *u8, len } — 16 bytes on amd64. ABI: pointer + u64. */
|
||||||
ty_str = prim(a, TY_STR, "str", 16, 8);
|
ty_str = prim(a, TY_STR, "str", 16, 8);
|
||||||
ty_err = prim(a, TY_ERR, "<err>", 0, 1);
|
ty_err = prim(a, TY_ERR, "<err>", 0, 1);
|
||||||
|
ty_never = prim(a, TY_NEVER, "never", 0, 1);
|
||||||
|
|
||||||
ty_untyped_int = prim(a, TY_UNTYPED_INT, "untyped_int", 0, 1);
|
ty_untyped_int = prim(a, TY_UNTYPED_INT, "untyped_int", 0, 1);
|
||||||
ty_untyped_float = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0, 1);
|
ty_untyped_float = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0, 1);
|
||||||
@@ -248,6 +250,7 @@ type_assignable(Type *dst, Type *src)
|
|||||||
{
|
{
|
||||||
if (dst == NULL || src == NULL) return 0;
|
if (dst == NULL || src == NULL) return 0;
|
||||||
if (dst == ty_err || src == ty_err) return 1; /* swallow */
|
if (dst == ty_err || src == ty_err) return 1; /* swallow */
|
||||||
|
if (src == ty_never) return 1; /* bottom flows into anything */
|
||||||
if (type_eq(dst, src)) return 1;
|
if (type_eq(dst, src)) return 1;
|
||||||
|
|
||||||
/* Tagged-union variant inclusion: src is one of dst's variants.
|
/* Tagged-union variant inclusion: src is one of dst's variants.
|
||||||
@@ -327,6 +330,7 @@ type_name(Arena *a, Type *t)
|
|||||||
case TY_F64: return "f64";
|
case TY_F64: return "f64";
|
||||||
case TY_STR: return "str";
|
case TY_STR: return "str";
|
||||||
case TY_ERR: return "<err>";
|
case TY_ERR: return "<err>";
|
||||||
|
case TY_NEVER: return "never";
|
||||||
case TY_UNTYPED_INT: return "untyped_int";
|
case TY_UNTYPED_INT: return "untyped_int";
|
||||||
case TY_UNTYPED_FLOAT: return "untyped_float";
|
case TY_UNTYPED_FLOAT: return "untyped_float";
|
||||||
case TY_UNTYPED_STR: return "untyped_str";
|
case TY_UNTYPED_STR: return "untyped_str";
|
||||||
|
|||||||
@@ -364,6 +364,7 @@ typedef enum {
|
|||||||
TY_TUPLE,
|
TY_TUPLE,
|
||||||
TY_TAGGED, /* (T1 | T2 | ...) — Hare-style sum type */
|
TY_TAGGED, /* (T1 | T2 | ...) — Hare-style sum type */
|
||||||
TY_ERR,
|
TY_ERR,
|
||||||
|
TY_NEVER, /* bottom: assignable to anything; size 0 */
|
||||||
/* untyped constants (not surfaced to users; checker-internal) */
|
/* untyped constants (not surfaced to users; checker-internal) */
|
||||||
TY_UNTYPED_INT,
|
TY_UNTYPED_INT,
|
||||||
TY_UNTYPED_FLOAT,
|
TY_UNTYPED_FLOAT,
|
||||||
@@ -408,6 +409,7 @@ extern Type *ty_u8, *ty_u16, *ty_u32, *ty_u64;
|
|||||||
extern Type *ty_int, *ty_uint, *ty_uintptr;
|
extern Type *ty_int, *ty_uint, *ty_uintptr;
|
||||||
extern Type *ty_f32, *ty_f64, *ty_str;
|
extern Type *ty_f32, *ty_f64, *ty_str;
|
||||||
extern Type *ty_err;
|
extern Type *ty_err;
|
||||||
|
extern Type *ty_never;
|
||||||
extern Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str;
|
extern Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str;
|
||||||
extern Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil;
|
extern Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil;
|
||||||
|
|
||||||
|
|||||||
@@ -42,12 +42,13 @@ def TY_NAMED: i32 = 24;
|
|||||||
def TY_TUPLE: i32 = 25;
|
def TY_TUPLE: i32 = 25;
|
||||||
def TY_TAGGED: i32 = 26;
|
def TY_TAGGED: i32 = 26;
|
||||||
def TY_ERR: i32 = 27;
|
def TY_ERR: i32 = 27;
|
||||||
def TY_UNTYPED_INT: i32 = 28;
|
def TY_NEVER: i32 = 28;
|
||||||
def TY_UNTYPED_FLOAT: i32 = 29;
|
def TY_UNTYPED_INT: i32 = 29;
|
||||||
def TY_UNTYPED_STR: i32 = 30;
|
def TY_UNTYPED_FLOAT: i32 = 30;
|
||||||
def TY_UNTYPED_RUNE: i32 = 31;
|
def TY_UNTYPED_STR: i32 = 31;
|
||||||
def TY_UNTYPED_BOOL: i32 = 32;
|
def TY_UNTYPED_RUNE: i32 = 32;
|
||||||
def TY_UNTYPED_NIL: i32 = 33;
|
def TY_UNTYPED_BOOL: i32 = 33;
|
||||||
|
def TY_UNTYPED_NIL: i32 = 34;
|
||||||
|
|
||||||
// ---- tinfo / tfield / tparam -----------------------------------------
|
// ---- tinfo / tfield / tparam -----------------------------------------
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ type tctx = struct {
|
|||||||
tyf64: *tinfo,
|
tyf64: *tinfo,
|
||||||
tystr: *tinfo,
|
tystr: *tinfo,
|
||||||
tyerr: *tinfo,
|
tyerr: *tinfo,
|
||||||
|
tynever: *tinfo,
|
||||||
tyuntypedint: *tinfo,
|
tyuntypedint: *tinfo,
|
||||||
tyuntypedfloat: *tinfo,
|
tyuntypedfloat: *tinfo,
|
||||||
tyuntypedstr: *tinfo,
|
tyuntypedstr: *tinfo,
|
||||||
@@ -144,6 +146,7 @@ export fn typesinit(c: *tctx, a: *arena) void = {
|
|||||||
c.tyf64 = prim(a, TY_F64, "f64", 8u64, 8u64);
|
c.tyf64 = prim(a, TY_F64, "f64", 8u64, 8u64);
|
||||||
c.tystr = prim(a, TY_STR, "str", 16u64, 8u64);
|
c.tystr = prim(a, TY_STR, "str", 16u64, 8u64);
|
||||||
c.tyerr = prim(a, TY_ERR, "<err>", 0u64, 1u64);
|
c.tyerr = prim(a, TY_ERR, "<err>", 0u64, 1u64);
|
||||||
|
c.tynever = prim(a, TY_NEVER, "never", 0u64, 1u64);
|
||||||
|
|
||||||
c.tyuntypedint = prim(a, TY_UNTYPED_INT, "untyped_int", 0u64, 1u64);
|
c.tyuntypedint = prim(a, TY_UNTYPED_INT, "untyped_int", 0u64, 1u64);
|
||||||
c.tyuntypedfloat = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0u64, 1u64);
|
c.tyuntypedfloat = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0u64, 1u64);
|
||||||
|
|||||||
@@ -3230,12 +3230,13 @@ def TY_NAMED: i32 = 24;
|
|||||||
def TY_TUPLE: i32 = 25;
|
def TY_TUPLE: i32 = 25;
|
||||||
def TY_TAGGED: i32 = 26;
|
def TY_TAGGED: i32 = 26;
|
||||||
def TY_ERR: i32 = 27;
|
def TY_ERR: i32 = 27;
|
||||||
def TY_UNTYPED_INT: i32 = 28;
|
def TY_NEVER: i32 = 28;
|
||||||
def TY_UNTYPED_FLOAT: i32 = 29;
|
def TY_UNTYPED_INT: i32 = 29;
|
||||||
def TY_UNTYPED_STR: i32 = 30;
|
def TY_UNTYPED_FLOAT: i32 = 30;
|
||||||
def TY_UNTYPED_RUNE: i32 = 31;
|
def TY_UNTYPED_STR: i32 = 31;
|
||||||
def TY_UNTYPED_BOOL: i32 = 32;
|
def TY_UNTYPED_RUNE: i32 = 32;
|
||||||
def TY_UNTYPED_NIL: i32 = 33;
|
def TY_UNTYPED_BOOL: i32 = 33;
|
||||||
|
def TY_UNTYPED_NIL: i32 = 34;
|
||||||
|
|
||||||
// ---- tinfo / tfield / tparam -----------------------------------------
|
// ---- tinfo / tfield / tparam -----------------------------------------
|
||||||
|
|
||||||
@@ -3288,6 +3289,7 @@ type tctx = struct {
|
|||||||
tyf64: *tinfo,
|
tyf64: *tinfo,
|
||||||
tystr: *tinfo,
|
tystr: *tinfo,
|
||||||
tyerr: *tinfo,
|
tyerr: *tinfo,
|
||||||
|
tynever: *tinfo,
|
||||||
tyuntypedint: *tinfo,
|
tyuntypedint: *tinfo,
|
||||||
tyuntypedfloat: *tinfo,
|
tyuntypedfloat: *tinfo,
|
||||||
tyuntypedstr: *tinfo,
|
tyuntypedstr: *tinfo,
|
||||||
@@ -3332,6 +3334,7 @@ export fn typesinit(c: *tctx, a: *arena) void = {
|
|||||||
c.tyf64 = prim(a, TY_F64, "f64", 8u64, 8u64);
|
c.tyf64 = prim(a, TY_F64, "f64", 8u64, 8u64);
|
||||||
c.tystr = prim(a, TY_STR, "str", 16u64, 8u64);
|
c.tystr = prim(a, TY_STR, "str", 16u64, 8u64);
|
||||||
c.tyerr = prim(a, TY_ERR, "<err>", 0u64, 1u64);
|
c.tyerr = prim(a, TY_ERR, "<err>", 0u64, 1u64);
|
||||||
|
c.tynever = prim(a, TY_NEVER, "never", 0u64, 1u64);
|
||||||
|
|
||||||
c.tyuntypedint = prim(a, TY_UNTYPED_INT, "untyped_int", 0u64, 1u64);
|
c.tyuntypedint = prim(a, TY_UNTYPED_INT, "untyped_int", 0u64, 1u64);
|
||||||
c.tyuntypedfloat = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0u64, 1u64);
|
c.tyuntypedfloat = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0u64, 1u64);
|
||||||
@@ -3687,6 +3690,7 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "f32", SK_TYPE, c.tc.tyf32, nil);
|
scopedefine(c.top, "f32", SK_TYPE, c.tc.tyf32, nil);
|
||||||
scopedefine(c.top, "f64", SK_TYPE, c.tc.tyf64, nil);
|
scopedefine(c.top, "f64", SK_TYPE, c.tc.tyf64, nil);
|
||||||
scopedefine(c.top, "str", SK_TYPE, c.tc.tystr, nil);
|
scopedefine(c.top, "str", SK_TYPE, c.tc.tystr, nil);
|
||||||
|
scopedefine(c.top, "never", SK_TYPE, c.tc.tynever, nil);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
||||||
@@ -4760,6 +4764,24 @@ fn istaggedtype(t: *node) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// voidvariantindex — find the 0-based index of the `void` variant in a
|
||||||
|
// tagged-union type expr, -1 if absent. Used by cgreturn to map bare
|
||||||
|
// `return;` in a tagged-union-returning fn to the void variant's tag.
|
||||||
|
fn voidvariantindex(tagged: *node) i32 = {
|
||||||
|
if (tagged == nil) { return -1; };
|
||||||
|
if (tagged.kind != N_TTAGGED) { return -1; };
|
||||||
|
let v: *node = tagged.list;
|
||||||
|
let idx: i32 = 0;
|
||||||
|
for (v != nil) {
|
||||||
|
if (v.kind == N_TNAME) {
|
||||||
|
if (streq(v.str, "void")) { return idx; };
|
||||||
|
};
|
||||||
|
v = v.next;
|
||||||
|
idx += 1;
|
||||||
|
};
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
// rhstargetname — for a returned value, what's its declared (or
|
// rhstargetname — for a returned value, what's its declared (or
|
||||||
// surface-inferred) type name? `expr: T` casts dictate T directly;
|
// surface-inferred) type name? `expr: T` casts dictate T directly;
|
||||||
// bare strlit/intlit fall back to a primitive name.
|
// bare strlit/intlit fall back to a primitive name.
|
||||||
@@ -6306,9 +6328,21 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
} else {
|
} else {
|
||||||
// Bare `return;` in a void fn — zero AX so the caller
|
// Bare `return;` from a tagged-union-returning fn is
|
||||||
// sees a deterministic value (matches C cgen, which
|
// the void variant: emit its tag. Payload is undefined
|
||||||
// always falls through to `cgexpr_int(c, 0)`).
|
// (void has size 0). Otherwise zero AX for determinism.
|
||||||
|
if (istaggedtype(c.fnret)) {
|
||||||
|
let idx: i32 = voidvariantindex(c.fnret);
|
||||||
|
if (idx < 0) { idx = 0; };
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(idx: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tBP, SP\n");
|
||||||
|
emitline("\tPOPQ\tBP\n");
|
||||||
|
emitline("\tRET\n");
|
||||||
|
c.lastwasreturn = 1;
|
||||||
|
return;
|
||||||
|
};
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
};
|
};
|
||||||
// SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX).
|
// SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX).
|
||||||
|
|||||||
@@ -120,9 +120,21 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
} else {
|
} else {
|
||||||
// Bare `return;` in a void fn — zero AX so the caller
|
// Bare `return;` from a tagged-union-returning fn is
|
||||||
// sees a deterministic value (matches C cgen, which
|
// the void variant: emit its tag. Payload is undefined
|
||||||
// always falls through to `cgexpr_int(c, 0)`).
|
// (void has size 0). Otherwise zero AX for determinism.
|
||||||
|
if (istaggedtype(c.fnret)) {
|
||||||
|
let idx: i32 = voidvariantindex(c.fnret);
|
||||||
|
if (idx < 0) { idx = 0; };
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(idx: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tBP, SP\n");
|
||||||
|
emitline("\tPOPQ\tBP\n");
|
||||||
|
emitline("\tRET\n");
|
||||||
|
c.lastwasreturn = 1;
|
||||||
|
return;
|
||||||
|
};
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
};
|
};
|
||||||
// SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX).
|
// SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX).
|
||||||
|
|||||||
@@ -876,6 +876,24 @@ fn istaggedtype(t: *node) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// voidvariantindex — find the 0-based index of the `void` variant in a
|
||||||
|
// tagged-union type expr, -1 if absent. Used by cgreturn to map bare
|
||||||
|
// `return;` in a tagged-union-returning fn to the void variant's tag.
|
||||||
|
fn voidvariantindex(tagged: *node) i32 = {
|
||||||
|
if (tagged == nil) { return -1; };
|
||||||
|
if (tagged.kind != N_TTAGGED) { return -1; };
|
||||||
|
let v: *node = tagged.list;
|
||||||
|
let idx: i32 = 0;
|
||||||
|
for (v != nil) {
|
||||||
|
if (v.kind == N_TNAME) {
|
||||||
|
if (streq(v.str, "void")) { return idx; };
|
||||||
|
};
|
||||||
|
v = v.next;
|
||||||
|
idx += 1;
|
||||||
|
};
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
// rhstargetname — for a returned value, what's its declared (or
|
// rhstargetname — for a returned value, what's its declared (or
|
||||||
// surface-inferred) type name? `expr: T` casts dictate T directly;
|
// surface-inferred) type name? `expr: T` casts dictate T directly;
|
||||||
// bare strlit/intlit fall back to a primitive name.
|
// bare strlit/intlit fall back to a primitive name.
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "f32", SK_TYPE, c.tc.tyf32, nil);
|
scopedefine(c.top, "f32", SK_TYPE, c.tc.tyf32, nil);
|
||||||
scopedefine(c.top, "f64", SK_TYPE, c.tc.tyf64, nil);
|
scopedefine(c.top, "f64", SK_TYPE, c.tc.tyf64, nil);
|
||||||
scopedefine(c.top, "str", SK_TYPE, c.tc.tystr, nil);
|
scopedefine(c.top, "str", SK_TYPE, c.tc.tystr, nil);
|
||||||
|
scopedefine(c.top, "never", SK_TYPE, c.tc.tynever, nil);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
||||||
|
|||||||
@@ -3230,12 +3230,13 @@ def TY_NAMED: i32 = 24;
|
|||||||
def TY_TUPLE: i32 = 25;
|
def TY_TUPLE: i32 = 25;
|
||||||
def TY_TAGGED: i32 = 26;
|
def TY_TAGGED: i32 = 26;
|
||||||
def TY_ERR: i32 = 27;
|
def TY_ERR: i32 = 27;
|
||||||
def TY_UNTYPED_INT: i32 = 28;
|
def TY_NEVER: i32 = 28;
|
||||||
def TY_UNTYPED_FLOAT: i32 = 29;
|
def TY_UNTYPED_INT: i32 = 29;
|
||||||
def TY_UNTYPED_STR: i32 = 30;
|
def TY_UNTYPED_FLOAT: i32 = 30;
|
||||||
def TY_UNTYPED_RUNE: i32 = 31;
|
def TY_UNTYPED_STR: i32 = 31;
|
||||||
def TY_UNTYPED_BOOL: i32 = 32;
|
def TY_UNTYPED_RUNE: i32 = 32;
|
||||||
def TY_UNTYPED_NIL: i32 = 33;
|
def TY_UNTYPED_BOOL: i32 = 33;
|
||||||
|
def TY_UNTYPED_NIL: i32 = 34;
|
||||||
|
|
||||||
// ---- tinfo / tfield / tparam -----------------------------------------
|
// ---- tinfo / tfield / tparam -----------------------------------------
|
||||||
|
|
||||||
@@ -3288,6 +3289,7 @@ type tctx = struct {
|
|||||||
tyf64: *tinfo,
|
tyf64: *tinfo,
|
||||||
tystr: *tinfo,
|
tystr: *tinfo,
|
||||||
tyerr: *tinfo,
|
tyerr: *tinfo,
|
||||||
|
tynever: *tinfo,
|
||||||
tyuntypedint: *tinfo,
|
tyuntypedint: *tinfo,
|
||||||
tyuntypedfloat: *tinfo,
|
tyuntypedfloat: *tinfo,
|
||||||
tyuntypedstr: *tinfo,
|
tyuntypedstr: *tinfo,
|
||||||
@@ -3332,6 +3334,7 @@ export fn typesinit(c: *tctx, a: *arena) void = {
|
|||||||
c.tyf64 = prim(a, TY_F64, "f64", 8u64, 8u64);
|
c.tyf64 = prim(a, TY_F64, "f64", 8u64, 8u64);
|
||||||
c.tystr = prim(a, TY_STR, "str", 16u64, 8u64);
|
c.tystr = prim(a, TY_STR, "str", 16u64, 8u64);
|
||||||
c.tyerr = prim(a, TY_ERR, "<err>", 0u64, 1u64);
|
c.tyerr = prim(a, TY_ERR, "<err>", 0u64, 1u64);
|
||||||
|
c.tynever = prim(a, TY_NEVER, "never", 0u64, 1u64);
|
||||||
|
|
||||||
c.tyuntypedint = prim(a, TY_UNTYPED_INT, "untyped_int", 0u64, 1u64);
|
c.tyuntypedint = prim(a, TY_UNTYPED_INT, "untyped_int", 0u64, 1u64);
|
||||||
c.tyuntypedfloat = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0u64, 1u64);
|
c.tyuntypedfloat = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0u64, 1u64);
|
||||||
@@ -3687,6 +3690,7 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "f32", SK_TYPE, c.tc.tyf32, nil);
|
scopedefine(c.top, "f32", SK_TYPE, c.tc.tyf32, nil);
|
||||||
scopedefine(c.top, "f64", SK_TYPE, c.tc.tyf64, nil);
|
scopedefine(c.top, "f64", SK_TYPE, c.tc.tyf64, nil);
|
||||||
scopedefine(c.top, "str", SK_TYPE, c.tc.tystr, nil);
|
scopedefine(c.top, "str", SK_TYPE, c.tc.tystr, nil);
|
||||||
|
scopedefine(c.top, "never", SK_TYPE, c.tc.tynever, nil);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
||||||
@@ -4760,6 +4764,24 @@ fn istaggedtype(t: *node) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// voidvariantindex — find the 0-based index of the `void` variant in a
|
||||||
|
// tagged-union type expr, -1 if absent. Used by cgreturn to map bare
|
||||||
|
// `return;` in a tagged-union-returning fn to the void variant's tag.
|
||||||
|
fn voidvariantindex(tagged: *node) i32 = {
|
||||||
|
if (tagged == nil) { return -1; };
|
||||||
|
if (tagged.kind != N_TTAGGED) { return -1; };
|
||||||
|
let v: *node = tagged.list;
|
||||||
|
let idx: i32 = 0;
|
||||||
|
for (v != nil) {
|
||||||
|
if (v.kind == N_TNAME) {
|
||||||
|
if (streq(v.str, "void")) { return idx; };
|
||||||
|
};
|
||||||
|
v = v.next;
|
||||||
|
idx += 1;
|
||||||
|
};
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
// rhstargetname — for a returned value, what's its declared (or
|
// rhstargetname — for a returned value, what's its declared (or
|
||||||
// surface-inferred) type name? `expr: T` casts dictate T directly;
|
// surface-inferred) type name? `expr: T` casts dictate T directly;
|
||||||
// bare strlit/intlit fall back to a primitive name.
|
// bare strlit/intlit fall back to a primitive name.
|
||||||
@@ -6306,9 +6328,21 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
} else {
|
} else {
|
||||||
// Bare `return;` in a void fn — zero AX so the caller
|
// Bare `return;` from a tagged-union-returning fn is
|
||||||
// sees a deterministic value (matches C cgen, which
|
// the void variant: emit its tag. Payload is undefined
|
||||||
// always falls through to `cgexpr_int(c, 0)`).
|
// (void has size 0). Otherwise zero AX for determinism.
|
||||||
|
if (istaggedtype(c.fnret)) {
|
||||||
|
let idx: i32 = voidvariantindex(c.fnret);
|
||||||
|
if (idx < 0) { idx = 0; };
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(idx: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tBP, SP\n");
|
||||||
|
emitline("\tPOPQ\tBP\n");
|
||||||
|
emitline("\tRET\n");
|
||||||
|
c.lastwasreturn = 1;
|
||||||
|
return;
|
||||||
|
};
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
};
|
};
|
||||||
// SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX).
|
// SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX).
|
||||||
|
|||||||
@@ -105,6 +105,25 @@ static const struct row rows[] = {
|
|||||||
{ "fn f(a: i32) i32 = { return a; }; fn g() i32 = { return f(); };",
|
{ "fn f(a: i32) i32 = { return a; }; fn g() i32 = { return f(); };",
|
||||||
"not enough arguments" },
|
"not enough arguments" },
|
||||||
{ "fn f() void = { if (1) { }; };", "if condition" },
|
{ "fn f() void = { if (1) { }; };", "if condition" },
|
||||||
|
|
||||||
|
/* tagged-union normalization & exhaustiveness */
|
||||||
|
{ "fn f() (i32 | never) = { return 7; }; "
|
||||||
|
"fn g() i32 = { return f(); };", "ok" }, /* (T|never) → T */
|
||||||
|
{ "fn f() (i32 | i32) = { return 5; }; "
|
||||||
|
"fn g() i32 = { return f(); };", "ok" }, /* dedup → i32 */
|
||||||
|
{ "fn f() (i32 | void) = { return; };", "ok" }, /* bare return → void variant */
|
||||||
|
{ "fn f(b: bool) (i32 | void) = { if (b) { return 1; }; return; };",
|
||||||
|
"ok" },
|
||||||
|
{ "fn f() (i32 | str | bool) = { return 1; }; "
|
||||||
|
"fn g() void = { let v: (i32 | str | bool) = f(); "
|
||||||
|
"match (v) { case let x: i32 => { }; case let x: str => { }; }; };",
|
||||||
|
"variant bool not handled" },
|
||||||
|
{ "fn f() (i32 | str | bool) = { return 1; }; "
|
||||||
|
"fn g() void = { let v: (i32 | str | bool) = f(); "
|
||||||
|
"match (v) { case let x: i32 => { }; case => { }; }; };",
|
||||||
|
"ok" }, /* default arm absorbs missing variants */
|
||||||
|
{ "fn die() never = { for (true) { let _: i32 = 1; }; }; "
|
||||||
|
"fn f() i32 = { die(); };", "ok" }, /* never assignable to anything */
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|||||||
Reference in New Issue
Block a user