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.
376 lines
10 KiB
C
376 lines
10 KiB
C
/*
|
|
* type.c — Type values and structural equality.
|
|
*
|
|
* Built-in types are constructed once and exposed as globals so the
|
|
* rest of the compiler can `==`-compare them. Compound types (ptr,
|
|
* slice, array, fn, struct, chan) are constructed on demand and
|
|
* de-duplicated when equality is cheap (only ptr/slice for now).
|
|
*/
|
|
#include "ww.h"
|
|
#include <string.h>
|
|
|
|
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;
|
|
Type *ty_f32, *ty_f64, *ty_str;
|
|
Type *ty_err;
|
|
Type *ty_never;
|
|
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_f32 = prim(a, TY_F32, "f32", 4, 4);
|
|
ty_f64 = prim(a, TY_F64, "f64", 8, 8);
|
|
/* str is { *u8, len } — 16 bytes on amd64. ABI: pointer + u64. */
|
|
ty_str = prim(a, TY_STR, "str", 16, 8);
|
|
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_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; /* { *T, len, cap } */
|
|
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;
|
|
}
|
|
|
|
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_RUNE:
|
|
case TY_UNTYPED_INT:
|
|
case TY_UNTYPED_RUNE:
|
|
return 1;
|
|
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:
|
|
return 1;
|
|
case TY_NAMED: return type_isunsigned(t->under);
|
|
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_i32;
|
|
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 (!type_eq(pa->type, pb->type)) return 0;
|
|
pa = pa->next; pb = pb->next;
|
|
}
|
|
return pa == NULL && pb == NULL;
|
|
}
|
|
case TY_STRUCT: {
|
|
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;
|
|
}
|
|
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 variant inclusion: src is one of dst's variants.
|
|
* Checked before the untyped branch so untyped literals (e.g.
|
|
* 0, "msg") flow through to a variant's typed slot. Unwraps a
|
|
* named alias on either side so `type result = (T | E);` also
|
|
* accepts variants and the inverse. */
|
|
{
|
|
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
|
|
Type *su = (src->kind == TY_NAMED) ? src->under : src;
|
|
if (du && du->kind == TY_TAGGED &&
|
|
!(su && su->kind == TY_TAGGED)) {
|
|
for (Tparam *p = du->params; p; p = p->next)
|
|
if (type_assignable(p->type, src)) return 1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Untyped → typed: only if the typed kind can hold the value. */
|
|
if (type_isuntyped(src)) {
|
|
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 && (dst->kind == TY_STR ||
|
|
(dst->kind == TY_NAMED && dst->under && dst->under->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 && (dst->kind == TY_BOOL ||
|
|
(dst->kind == TY_NAMED && dst->under && dst->under->kind == TY_BOOL))) return 1;
|
|
if (src->kind == TY_UNTYPED_NIL) {
|
|
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
|
|
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: compare to the underlying. NAMED is a
|
|
* distinct type from its under; but assignment from under to
|
|
* named (and vice-versa) is allowed in this minimal checker. */
|
|
if (dst->kind == TY_NAMED && type_eq(dst->under, src)) return 1;
|
|
if (src->kind == TY_NAMED && type_eq(dst, src->under)) return 1;
|
|
|
|
/* Tuple-to-tuple: element-wise assignable. */
|
|
if (dst->kind == TY_TUPLE && src->kind == TY_TUPLE) {
|
|
Tparam *pa = dst->params, *pb = src->params;
|
|
while (pa && pb) {
|
|
if (!type_assignable(pa->type, pb->type)) return 0;
|
|
pa = pa->next; pb = pb->next;
|
|
}
|
|
return pa == NULL && pb == NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *
|
|
type_name(Arena *a, Type *t)
|
|
{
|
|
if (t == NULL) return "<nil>";
|
|
switch (t->kind) {
|
|
case TY_NONE: return "<none>";
|
|
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_F32: return "f32";
|
|
case TY_F64: return "f64";
|
|
case TY_STR: return "str";
|
|
case TY_ERR: return "<err>";
|
|
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 : "<named>";
|
|
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);
|
|
}
|
|
}
|
|
return "?";
|
|
}
|