/* * Built-in types are constructed once and exposed as globals so the * rest of the compiler can `==`-compare them. */ #include "ww.h" #include Type *ty_void, *ty_bool, *ty_rune; Type *ty_i8, *ty_i16, *ty_i32, *ty_i64; Type *ty_u8, *ty_u16, *ty_u32, *ty_u64; Type *ty_int, *ty_uint, *ty_uintptr, *ty_size; Type *ty_f32, *ty_f64, *ty_str; Type *ty_err; Type *ty_never; Type *ty_nomem; Type *ty_opaque; Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str; Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil; Type * newtype(Arena *a, TypeKind k) { Type *t = amalloc(a, sizeof *t); t->kind = k; return t; } static Type * prim(Arena *a, TypeKind k, const char *nm, u64 sz, u64 al) { Type *t = newtype(a, k); t->name = nm; t->size = sz; t->align = al ? al : sz; return t; } void typesinit(Arena *a) { /* Always re-init: callers create a fresh arena per compilation unit * and free it; old globals point at freed memory. */ ty_void = prim(a, TY_VOID, "void", 0, 1); ty_bool = prim(a, TY_BOOL, "bool", 1, 1); ty_rune = prim(a, TY_RUNE, "rune", 4, 4); ty_i8 = prim(a, TY_I8, "i8", 1, 1); ty_i16 = prim(a, TY_I16, "i16", 2, 2); ty_i32 = prim(a, TY_I32, "i32", 4, 4); ty_i64 = prim(a, TY_I64, "i64", 8, 8); ty_u8 = prim(a, TY_U8, "u8", 1, 1); ty_u16 = prim(a, TY_U16, "u16", 2, 2); ty_u32 = prim(a, TY_U32, "u32", 4, 4); ty_u64 = prim(a, TY_U64, "u64", 8, 8); ty_int = prim(a, TY_INT, "int", 8, 8); /* amd64 */ ty_uint = prim(a, TY_UINT, "uint", 8, 8); ty_uintptr= prim(a, TY_UINTPTR,"uintptr", 8, 8); ty_size = prim(a, TY_SIZE, "size", 8, 8); /* #85: mirrors uintptr */ ty_f32 = prim(a, TY_F32, "f32", 4, 4); ty_f64 = prim(a, TY_F64, "f64", 8, 8); /* str IS []u8: { *u8, len, cap } — 24 bytes, 3-reg ABI (#1/Phase 3). * Size sourced from the slice SSoT (type_slice) so str and []u8 can * never drift; no second hardcoded 24. */ ty_str = prim(a, TY_STR, "str", type_slice(a, ty_u8)->size, 8); ty_str->sub = ty_u8; /* str IS []u8: element is u8 (Phase 2 F1) */ ty_err = prim(a, TY_ERR, "", 0, 1); ty_never = prim(a, TY_NEVER, "never", 0, 1); /* #29: predeclared `type nomem = !void;`. NAMED so variant_match * compares by pointer identity (singleton across all references); * under=ty_void + iserror=1 triggers the `!T` error-tag machinery * the same way a user-declared alias would. */ ty_nomem = newtype(a, TY_NAMED); ty_nomem->name = "nomem"; ty_nomem->under = ty_void; ty_nomem->size = ty_void->size; ty_nomem->align = ty_void->align; ty_nomem->iserror = 1; /* #108(a): abstract + unsized. SIZE_UNDEFINED (not 0) so a bare * `let x: opaque` can't fabricate a 0-byte local; legal only behind * indirection. Mirrors harec builtin_type_opaque (types.c:1446). */ ty_opaque = prim(a, TY_OPAQUE, "opaque", SIZE_UNDEFINED, SIZE_UNDEFINED); 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_str = prim(a, TY_UNTYPED_STR, "untyped_str", 0, 1); ty_untyped_rune = prim(a, TY_UNTYPED_RUNE, "untyped_rune", 0, 1); ty_untyped_bool = prim(a, TY_UNTYPED_BOOL, "untyped_bool", 0, 1); ty_untyped_nil = prim(a, TY_UNTYPED_NIL, "untyped_nil", 0, 1); } Type * type_ptr(Arena *a, Type *sub) { Type *t = newtype(a, TY_PTR); t->sub = sub; t->size = 8; t->align = 8; return t; } Type * type_slice(Arena *a, Type *sub) { Type *t = newtype(a, TY_SLICE); t->sub = sub; t->size = 24; /* Slice headers carry pointer, length, and capacity. */ t->align = 8; return t; } Type * type_array(Arena *a, Type *sub, u64 len) { Type *t = newtype(a, TY_ARRAY); t->sub = sub; t->alen = len; t->size = sub ? sub->size * len : 0; t->align = sub ? sub->align : 1; return t; } Type * type_chan(Arena *a, Type *sub) { Type *t = newtype(a, TY_CHAN); t->sub = sub; t->size = 8; /* opaque ptr */ t->align = 8; return t; } Type * type_named(Arena *a, const char *name, Type *under) { Type *t = newtype(a, TY_NAMED); t->name = name; t->under = under; if (under) { t->size = under->size; t->align = under->align; t->iserror = under->iserror; } return t; } /* Chain-of-aliases (#22): `type b = a; type a = struct;` * stacks two TY_NAMED layers — a single peel leaves `t` pointing at * the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT, * TY_SLICE, TY_TAGGED, TY_PTR) miss and the consumer silently falls * through to a scalar shape. Mirror of wwstage's structlookupchain / * resolvealias and harec's type_dealias (ref/harec/src/types.c:53). * Promoted from cmd/w6c/cgen.c for the #5 alias arc so the checker's * acceptance sites and the cgen classify sites share one peel. */ Type * type_chase_named(Type *t) { while (t && t->kind == TY_NAMED) t = t->under; return t; } int type_isint(Type *t) { if (t == NULL) return 0; switch (t->kind) { case TY_I8: case TY_I16: case TY_I32: case TY_I64: case TY_U8: case TY_U16: case TY_U32: case TY_U64: case TY_INT: case TY_UINT: case TY_UINTPTR: case TY_SIZE: case TY_RUNE: case TY_UNTYPED_INT: case TY_UNTYPED_RUNE: return 1; case TY_ENUM: return type_isint(t->sub); case TY_NAMED: return type_isint(t->under); default: return 0; } } int type_isfloat(Type *t) { if (t == NULL) return 0; switch (t->kind) { case TY_F32: case TY_F64: case TY_UNTYPED_FLOAT: return 1; case TY_NAMED: return type_isfloat(t->under); default: return 0; } } int type_isnum(Type *t) { return type_isint(t) || type_isfloat(t); } int type_isunsigned(Type *t) { if (t == NULL) return 0; switch (t->kind) { case TY_U8: case TY_U16: case TY_U32: case TY_U64: case TY_UINT: case TY_UINTPTR: case TY_SIZE: case TY_RUNE: return 1; case TY_NAMED: return type_isunsigned(t->under); case TY_ENUM: return type_isunsigned(t->sub); default: return 0; } } int type_isuntyped(Type *t) { if (t == NULL) return 0; switch (t->kind) { case TY_UNTYPED_INT: case TY_UNTYPED_FLOAT: case TY_UNTYPED_STR: case TY_UNTYPED_RUNE: case TY_UNTYPED_BOOL: case TY_UNTYPED_NIL: return 1; default: return 0; } } Type * type_default(Type *t) { if (t == NULL) return NULL; switch (t->kind) { case TY_UNTYPED_INT: return ty_int; /* int = machine word (8B); i32 truncated >2^31 (#103/#108) */ case TY_UNTYPED_FLOAT: return ty_f64; case TY_UNTYPED_STR: return ty_str; case TY_UNTYPED_RUNE: return ty_rune; case TY_UNTYPED_BOOL: return ty_bool; case TY_UNTYPED_NIL: return NULL; /* needs context */ default: return t; } } int type_eq(Type *a, Type *b) { if (a == b) return 1; if (a == NULL || b == NULL) return 0; if (a->kind != b->kind) return 0; switch (a->kind) { case TY_PTR: case TY_SLICE: case TY_CHAN: return type_eq(a->sub, b->sub); case TY_ARRAY: return a->alen == b->alen && type_eq(a->sub, b->sub); case TY_FN: { if (a->variadic != b->variadic) return 0; if (!type_eq(a->ret, b->ret)) return 0; Tparam *pa = a->params, *pb = b->params; while (pa && pb) { if (pa->variadic != pb->variadic) return 0; if (!type_eq(pa->type, pb->type)) return 0; pa = pa->next; pb = pb->next; } return pa == NULL && pb == NULL; } case TY_STRUCT: { /* packed is part of struct identity: a packed struct is * not equal to its unpacked twin (harec types.c:621). */ if (a->packed != b->packed) return 0; Tfield *fa = a->fields, *fb = b->fields; while (fa && fb) { if (strcmp(fa->name, fb->name) != 0) return 0; if (!type_eq(fa->type, fb->type)) return 0; fa = fa->next; fb = fb->next; } return fa == NULL && fb == NULL; } case TY_NAMED: return a == b; /* nominally equal only when same node */ case TY_TUPLE: { Tparam *pa = a->params, *pb = b->params; while (pa && pb) { if (!type_eq(pa->type, pb->type)) return 0; pa = pa->next; pb = pb->next; } return pa == NULL && pb == NULL; } case TY_TAGGED: { /* Tagged unions are structurally equal iff variant lists * match position-by-position. Nullable fold is a per-Type * flag, so equal-up-to-fold types compare not-equal here — * the caller can unwrap intentionally if needed. */ if (a->nullable != b->nullable) return 0; Tparam *pa = a->params, *pb = b->params; while (pa && pb) { if (!type_eq(pa->type, pb->type)) return 0; pa = pa->next; pb = pb->next; } return pa == NULL && pb == NULL; } default: return 1; /* primitives */ } } int type_assignable(Type *dst, Type *src) { if (dst == NULL || src == NULL) return 0; 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; /* Tagged-union assignment: * - concrete → tagged: src must match one of dst's variants. * - tagged → tagged: src is assignable when every variant of * src appears as a variant of dst (Hare-style subset). Tag * remap at the use site handles different variant indices. * Checked before the untyped branch so untyped literals flow * through to a variant's typed slot. Chases the NAMED alias chain * on either side so `type result = (T|E);` accepts variants too. */ { Type *du = type_chase_named(dst); Type *su = type_chase_named(src); if (du && du->kind == TY_TAGGED && !(su && su->kind == TY_TAGGED)) { /* #199(α): direct variant only — no transitive drill into * a NAMED-tagged wrapper variant. Cgen has no wrapped-slot * layout (single-level [tag][payload]), so admitting a * nested widen silently miscompiled to tag=0 (#199 repro * io.underread → (size|io.eof|io.error)). ww-stricter than * Hare; harec types.c:702-739 keeps the drill (#199b is * the deferred wrapped-slot port). Restores SSoT with * `is`/`as`'s non-recursive variant lookup. Callers compose * `let inner: Wrapper = sub; let r: parent = inner;`. */ for (Tparam *p = du->params; p; p = p->next) { Type *pu = type_chase_named(p->type); if (pu && pu->kind == TY_TAGGED) { if (type_eq(p->type, src)) return 1; continue; } if (type_assignable(p->type, src)) return 1; } return 0; } if (du && du->kind == TY_TAGGED && su && su->kind == TY_TAGGED) { /* #205: NAMED-variant nominal compare BEFORE subset. * Mirror of the concrete→tagged arm at :316-324 (#199 α). * When src is a NAMED-tagged wrapper and dst has a direct * NAMED-tagged variant equal to src, accept by nominal * identity without recursing into src's variants — those * are wrapper's leaves, not direct variants of dst, so * the subset loop below would reject. SSoT with `is`/`as` * variant lookup (#198 family). */ for (Tparam *dp = du->params; dp; dp = dp->next) { Type *pu = type_chase_named(dp->type); if (pu && pu->kind == TY_TAGGED && type_eq(dp->type, src)) return 1; } for (Tparam *sp = su->params; sp; sp = sp->next) { int ok = 0; for (Tparam *dp = du->params; dp; dp = dp->next) if (type_eq(dp->type, sp->type)) { ok = 1; break; } if (!ok) return 0; } return 1; } } if (type_isuntyped(src)) { Type *du = type_chase_named(dst); if (src->kind == TY_UNTYPED_INT && type_isnum(dst)) return 1; if (src->kind == TY_UNTYPED_FLOAT && type_isfloat(dst)) return 1; if (src->kind == TY_UNTYPED_STR && du && du->kind == TY_STR) return 1; if (src->kind == TY_UNTYPED_RUNE && (type_isint(dst) || dst->kind == TY_RUNE)) return 1; if (src->kind == TY_UNTYPED_BOOL && du && du->kind == TY_BOOL) return 1; if (src->kind == TY_UNTYPED_NIL) { if (du && (du->kind == TY_PTR || du->kind == TY_SLICE || du->kind == TY_CHAN || du->kind == TY_FN)) return 1; } return 0; } /* Named on either side: assignable through the FULL alias chain. * harec type_is_assignable dealiases both sides whenever the dst * is not tagged (ref/harec/src/types.c:993-996; the tagged dst * returned above) — so base↔alias and alias↔alias-of-same-base * all flow. The pre-#5 single peel rejected any 2-level chain * (`return x*2` from a myint2 local louded at the int return). */ if (dst->kind == TY_NAMED || src->kind == TY_NAMED) { if (type_eq(type_chase_named(dst), type_chase_named(src))) return 1; } /* Tuple-to-tuple: element-wise assignable. Chase a TY_NAMED alias on * either side first (#99): `type pair=(int,int); let x: pair = (3,4)` * was rejected because dst->kind is TY_NAMED, skipping this arm — the * direct-tuple path coerces the untyped elements fine. Aliases are * transparent; mirrors the #258 slice-borrow arm just below which * already type_chase_named's both sides. */ { Type *du = type_chase_named(dst); Type *su = type_chase_named(src); if (du && su && du->kind == TY_TUPLE && su->kind == TY_TUPLE) { Tparam *pa = du->params, *pb = su->params; while (pa && pb) { if (!type_assignable(pa->type, pb->type)) return 0; pa = pa->next; pb = pb->next; } return pa == NULL && pb == NULL; } } /* #258: implicit [N]T → []T array-to-slice borrow. Hare admits an * array with a DEFINED length wherever its element slice is expected * — assign / return / call-arg / init alike (ref/harec/src/types.c: * 1080-1097, the SLICE-dst arm). The checker accepts it here; the * acceptance sites (clet / N_ASSIGN / call-arg gather / N_RETURN) * then DESUGAR the array expr to an explicit full slice `arr[0:len * (arr)]` via desugar_arrayslice, reusing the existing slice cgen so * there is ZERO new array→slice store and the borrow header * {.ptr=&arr[0], .len=N, .cap=N} is byte-identical across stages. * Element types must match exactly — no element decay. */ { Type *du = type_chase_named(dst); Type *su = type_chase_named(src); if (du && su && du->kind == TY_SLICE && su->kind == TY_ARRAY && su->alen != SIZE_UNDEFINED && type_eq(du->sub, su->sub)) return 1; } /* #108(c): opaque is a type-erasure sink. Any pointer is assignable * to *opaque, and any slice to []opaque — the universal void-pointer * and erased slice. harec type_is_assignable: ptr→*opaque at ref/harec/src/ * types.c:1053 (`case STORAGE_OPAQUE: break;` inside the pointer arm, * i.e. the referent need not match), slice→[]opaque at :1094 (`if * (to_secondary->storage == STORAGE_OPAQUE) return true;`). * * Array→[]opaque (harec's STORAGE_POINTER-to-array and array→slice * decay, types.c:1080-1099) stays EXCLUDED here: the #258 arm above * desugars a concrete-element borrow only on an EXACT element match, * and this arm fires only when dst and src share a kind (ptr→ptr, * slice→slice), so an array reaches []opaque only after an explicit * `a[0:n]` slice. ww-stricter than Hare; documented divergence. * * Both rules fire only when the destination element is opaque, so * they are inert on the opaque-free selfhost corpus. */ { Type *du = type_chase_named(dst); Type *su = type_chase_named(src); if (du && su && du->kind == su->kind && (du->kind == TY_PTR || du->kind == TY_SLICE) && du->sub && du->sub->kind == TY_OPAQUE) return 1; } return 0; } const char * type_name(Arena *a, Type *t) { if (t == NULL) return ""; switch (t->kind) { case TY_NONE: return ""; case TY_VOID: return "void"; case TY_BOOL: return "bool"; case TY_RUNE: return "rune"; case TY_I8: return "i8"; case TY_I16: return "i16"; case TY_I32: return "i32"; case TY_I64: return "i64"; case TY_U8: return "u8"; case TY_U16: return "u16"; case TY_U32: return "u32"; case TY_U64: return "u64"; case TY_INT: return "int"; case TY_UINT: return "uint"; case TY_UINTPTR: return "uintptr"; case TY_SIZE: return "size"; case TY_OPAQUE: return "opaque"; case TY_F32: return "f32"; case TY_F64: return "f64"; case TY_STR: return "str"; case TY_ERR: return ""; case TY_NEVER: return "never"; case TY_UNTYPED_INT: return "untyped_int"; case TY_UNTYPED_FLOAT: return "untyped_float"; case TY_UNTYPED_STR: return "untyped_str"; case TY_UNTYPED_RUNE: return "untyped_rune"; case TY_UNTYPED_BOOL: return "untyped_bool"; case TY_UNTYPED_NIL: return "untyped_nil"; case TY_PTR: return aprintf(a, "*%s", type_name(a, t->sub)); case TY_SLICE: return aprintf(a, "[]%s", type_name(a, t->sub)); case TY_ARRAY: return aprintf(a, "[%llu]%s", (unsigned long long)t->alen, type_name(a, t->sub)); case TY_CHAN: return aprintf(a, "chan %s", type_name(a, t->sub)); case TY_FN: { const char *r = t->ret ? type_name(a, t->ret) : "void"; const char *acc = ""; for (Tparam *p = t->params; p; p = p->next) { const char *pn = type_name(a, p->type); acc = acc[0] ? aprintf(a, "%s, %s", acc, pn) : pn; } return aprintf(a, "fn(%s) %s", acc, r); } case TY_STRUCT: return t->name ? t->name : "struct{...}"; case TY_NAMED: return t->name ? t->name : ""; case TY_TUPLE: { const char *acc = ""; for (Tparam *p = t->params; p; p = p->next) { const char *pn = type_name(a, p->type); acc = acc[0] ? aprintf(a, "%s, %s", acc, pn) : pn; } return aprintf(a, "(%s)", acc); } case TY_TAGGED: { const char *acc = ""; for (Tparam *p = t->params; p; p = p->next) { const char *pn = type_name(a, p->type); acc = acc[0] ? aprintf(a, "%s | %s", acc, pn) : pn; } return aprintf(a, "(%s)", acc); } case TY_ENUM: return aprintf(a, "enum %s", t->sub ? type_name(a, t->sub) : "i32"); } return "?"; }