Files
ww/cmd/wcc/type.c
Hojun-Cho 4b118fa8f8 cgen: B7 emitter elem chases + tools/peellint gate — #5 alias-arc cs side closed by construction
The last four raw `->under` reads outside the whitelist were the
static-DATA emitters' ELEMENT-type single peels (the outer type already
chased): emit_array_lit_bytes:14356, emit_strarray_data:14574,
emit_slice_data:14788, let_pre_intern:15088 -> type_chase_named.
:15088 is the :14574 row's label-order leg and must flip in the same
commit or _S_ labels intern in emit order, not decl order (the in-tree
comment at the site); the strarr row's byte-id is the coupling proof.

Behavior moves (ken B7 first-position oracle + impl pre-state, all
pre-observed at 05f7af7):
- [N]alias-struct + [N]alias-str globals graduate cs link-ERR
  ("undefined reference") -> 0/0 BYTE-ID (cs emits ww's DATAW).
- zero-consumer latent silence closed: a never-referenced
  2-level-elem-alias global silently lacked DATA (no reference, no
  link error); now emits, pinned by the byte-id cell.
- []alias-str diagnostic routing: the alias escaped the 3-way
  slice-of-{str,slice,tagged} fatal onto the downstream "not a
  foldable constant" text — now the intended 3-way text (== control).
- []alias-tagged DESIGNED NARROWING: the alias dodged the 3-way fatal
  ENTIRELY — cs silently accepted + RAN WRONG for reachable consumer
  shapes (review-verified at base: a len+payload-read probe exits 1;
  the len-only row was luck-correct). Now loud with the 3-way text;
  widen what the gate SEES, never what it ACCEPTS (B6-c2 precedent).
- kb7_slc/slc0 scalar legs byte-NEUTRAL (the synthesized-array
  choke-point already handled them); full kb corpus sweep: movers are
  exactly the two graduation shapes, nothing else.

tools/peellint (sizelint clone, dep of test/test-unit): character-scan
strips comments and string/char literals, then matches the under-token
accessor-spelling-wide — `->under`/`.under` in C (deref-dot is the
same peel), `.under` in ww, optional whitespace after the operator,
and the line-split continuation (operator at EOL, `under` next line).
Scope cmd/wcc + cmd/w6c + selfhost/cmd/wcc + lib/ww (lib/ww/typ.ww
ruled IN — it is type.c's ww mirror, the accessor layer itself);
`peel-ok`/`peellint-ok` annotations exempt a 10-line window. Green at
this tip = zero unwhitelisted raw peels survive; the gate lands in the
commit that deletes the last raw read (the-funnel-completing-commit-
carries-the-gate; sizelint precedent). Whitelist, 27 entries:
  cmd/wcc/type.c    :78 :141 construction, :162 chase body,
                    :180 :193 :214 recursive chase
  cmd/wcc/check.c   :102 :2572 resolve-state probes, :2586 construction
  cmd/w6c/cgen.c    :731 probe-cleared scan peel (B5-c1),
                    :813/:814 :834/:835 peel-ok #218 variant-match
  lib/ww/typ.ww     :316 construction, :374 :385 :410 :437 :447 :463
                    :475 :488 :514 recursive chase
  selfhost/cmd/wcc/cgenutil.ww :1302 chase body (tichase),
                    :2759 probe-cleared peel
  selfhost/cmd/wcc/check.ww    :1815 construction (peellint-ok)

Negative validation wired into 944_peellint_gate (B4 precedent):
re-introduced raw peel (C and ww spellings) REDS the lint; corrupted
annotation (peel-okk-…, token-bounded matcher) REDS the lint; the
check.ww:3683 "io.underread" prose, a code read of a longer field, and
comment-quoted tokens are pinned green regression rows; real tree must
lint clean. 944_alias_emit_b7_run pins all four emit paths
table-driven (14 rows / 36 checks) incl. ken's ww observation cells
(ww checker rejects slice-literal globals, "let: not assignable" —
unmoved; plain []str louds at ww's own emitslicedata 3-way, pinned by
the shared needle).

REVIEW AMENDMENT (reviewer-B7, fix-what-you-find): the frozen tip's
regex matcher passed five compiling evasion spellings green — `t ->
under` spacing, `t->`/EOL + `under` next-line (both stages; ww parses
`t.`/EOL too), C deref-dot `(*t).under`, ww `t. under`, and a string
literal containing a block-comment opener that blinded the regex
comment-strip for the rest of the file. The matcher is now a
character scan (comments + string/char literals stripped before
matching) with the widened token rule above; all six spellings are
pinned RED rows in 944_peellint_gate (checks 10 -> 16). The 10-line
annotation window stays as designed (a peel within an annotation's
window is exempt by construction — the window IS the exemption
mechanism). Lint + test bytes only; zero compiler-source bytes moved
in review.

What this does NOT close, said out loud (f2-ruling): a consumer that
never spells `under` at all — a switch on t->kind that simply never
peels — has no token for the lint to see. The accessor+lint closes the
WRONG-PEEL class (single-peel where chase was needed) by construction;
the NO-PEEL class is closed only at sites where classification routes
through the internalized chasing helpers, and contained elsewhere by
the acceptance-commit-carries-tripwires doctrine, which stays standing
for every future acceptance widening. The gate does not make alias
bugs impossible; it makes the four-times-burned shape unwritable.

Rule-11 note: forced fuse — the four conversions ARE the last raw-read
deletions; peellint cannot be green one commit earlier (consumer-graph
-forces-the-fuse precedent, #61).

Invariants: cs asm byte-NEUTRAL on the whole bootstrap corpus (five
mains + smoke, base-input pre==post); five mains cs==ww byte-id at
tip; _ww binary quartet bit-identical to the W2 baseline (ww changes
are comment-only annotation bytes — codegen-inert, proven by the md5
hold); w6c_ww+wwdump main.combined.ww regen'd via make, idempotent;
989 lib ratchet zero flips (31 byte-id / 9 pinned-divergent / 3
pinned-wwreject across 43 units); sizelint 0; peellint 0;
make test-unit "all 294 tests passed" (292 + the two new suites).
2026-06-06 06:05:46 +09:00

528 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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, *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, "<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; /* peel-ok: construction */
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; /* { *T, len, cap } */ /* sizelint-ok: SSoT for ty_slice (#64) */
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; /* peel-ok: construction */
if (under) {
t->size = under->size;
t->align = under->align;
t->iserror = under->iserror;
}
return t;
}
/* type_chase_named — walk the TY_NAMED.under chain to the deepest non-
* named type. 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; /* peel-ok: chase body */
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: /* peel-ok: recursive chase */
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: /* peel-ok: recursive chase */
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: /* peel-ok: recursive chase */
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_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 (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: {
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;
}
}
/* Untyped → typed: only if the typed kind can hold the value. */
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. */
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;
}
/* #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 "<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_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 "<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);
}
case TY_ENUM:
return aprintf(a, "enum %s",
t->sub ? type_name(a, t->sub) : "i32");
}
return "?";
}