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:
@@ -3230,12 +3230,13 @@ def TY_NAMED: i32 = 24;
|
||||
def TY_TUPLE: i32 = 25;
|
||||
def TY_TAGGED: i32 = 26;
|
||||
def TY_ERR: i32 = 27;
|
||||
def TY_UNTYPED_INT: i32 = 28;
|
||||
def TY_UNTYPED_FLOAT: i32 = 29;
|
||||
def TY_UNTYPED_STR: i32 = 30;
|
||||
def TY_UNTYPED_RUNE: i32 = 31;
|
||||
def TY_UNTYPED_BOOL: i32 = 32;
|
||||
def TY_UNTYPED_NIL: i32 = 33;
|
||||
def TY_NEVER: i32 = 28;
|
||||
def TY_UNTYPED_INT: i32 = 29;
|
||||
def TY_UNTYPED_FLOAT: i32 = 30;
|
||||
def TY_UNTYPED_STR: i32 = 31;
|
||||
def TY_UNTYPED_RUNE: i32 = 32;
|
||||
def TY_UNTYPED_BOOL: i32 = 33;
|
||||
def TY_UNTYPED_NIL: i32 = 34;
|
||||
|
||||
// ---- tinfo / tfield / tparam -----------------------------------------
|
||||
|
||||
@@ -3288,6 +3289,7 @@ type tctx = struct {
|
||||
tyf64: *tinfo,
|
||||
tystr: *tinfo,
|
||||
tyerr: *tinfo,
|
||||
tynever: *tinfo,
|
||||
tyuntypedint: *tinfo,
|
||||
tyuntypedfloat: *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.tystr = prim(a, TY_STR, "str", 16u64, 8u64);
|
||||
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.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, "f64", SK_TYPE, c.tc.tyf64, 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
|
||||
// level, no symbol needed.
|
||||
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
||||
@@ -4760,6 +4764,24 @@ fn istaggedtype(t: *node) bool = {
|
||||
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
|
||||
// surface-inferred) type name? `expr: T` casts dictate T directly;
|
||||
// bare strlit/intlit fall back to a primitive name.
|
||||
@@ -6306,9 +6328,21 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
} else {
|
||||
// Bare `return;` in a void fn — zero AX so the caller
|
||||
// sees a deterministic value (matches C cgen, which
|
||||
// always falls through to `cgexpr_int(c, 0)`).
|
||||
// Bare `return;` from a tagged-union-returning fn is
|
||||
// the void variant: emit its tag. Payload is undefined
|
||||
// (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");
|
||||
};
|
||||
// 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);
|
||||
} else {
|
||||
// Bare `return;` in a void fn — zero AX so the caller
|
||||
// sees a deterministic value (matches C cgen, which
|
||||
// always falls through to `cgexpr_int(c, 0)`).
|
||||
// Bare `return;` from a tagged-union-returning fn is
|
||||
// the void variant: emit its tag. Payload is undefined
|
||||
// (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");
|
||||
};
|
||||
// SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX).
|
||||
|
||||
@@ -876,6 +876,24 @@ fn istaggedtype(t: *node) bool = {
|
||||
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
|
||||
// surface-inferred) type name? `expr: T` casts dictate T directly;
|
||||
// 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, "f64", SK_TYPE, c.tc.tyf64, 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
|
||||
// level, no symbol needed.
|
||||
// `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_TAGGED: i32 = 26;
|
||||
def TY_ERR: i32 = 27;
|
||||
def TY_UNTYPED_INT: i32 = 28;
|
||||
def TY_UNTYPED_FLOAT: i32 = 29;
|
||||
def TY_UNTYPED_STR: i32 = 30;
|
||||
def TY_UNTYPED_RUNE: i32 = 31;
|
||||
def TY_UNTYPED_BOOL: i32 = 32;
|
||||
def TY_UNTYPED_NIL: i32 = 33;
|
||||
def TY_NEVER: i32 = 28;
|
||||
def TY_UNTYPED_INT: i32 = 29;
|
||||
def TY_UNTYPED_FLOAT: i32 = 30;
|
||||
def TY_UNTYPED_STR: i32 = 31;
|
||||
def TY_UNTYPED_RUNE: i32 = 32;
|
||||
def TY_UNTYPED_BOOL: i32 = 33;
|
||||
def TY_UNTYPED_NIL: i32 = 34;
|
||||
|
||||
// ---- tinfo / tfield / tparam -----------------------------------------
|
||||
|
||||
@@ -3288,6 +3289,7 @@ type tctx = struct {
|
||||
tyf64: *tinfo,
|
||||
tystr: *tinfo,
|
||||
tyerr: *tinfo,
|
||||
tynever: *tinfo,
|
||||
tyuntypedint: *tinfo,
|
||||
tyuntypedfloat: *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.tystr = prim(a, TY_STR, "str", 16u64, 8u64);
|
||||
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.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, "f64", SK_TYPE, c.tc.tyf64, 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
|
||||
// level, no symbol needed.
|
||||
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
||||
@@ -4760,6 +4764,24 @@ fn istaggedtype(t: *node) bool = {
|
||||
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
|
||||
// surface-inferred) type name? `expr: T` casts dictate T directly;
|
||||
// bare strlit/intlit fall back to a primitive name.
|
||||
@@ -6306,9 +6328,21 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
} else {
|
||||
// Bare `return;` in a void fn — zero AX so the caller
|
||||
// sees a deterministic value (matches C cgen, which
|
||||
// always falls through to `cgexpr_int(c, 0)`).
|
||||
// Bare `return;` from a tagged-union-returning fn is
|
||||
// the void variant: emit its tag. Payload is undefined
|
||||
// (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");
|
||||
};
|
||||
// SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX).
|
||||
|
||||
Reference in New Issue
Block a user