Files
ww/cmd/wcc/type.c
Hojun-Cho e92708ecda w6c+wwstage: implicit [N]T->[]T array-to-slice coercion via desugar (#258)
Hare admits an array with a defined length wherever its element slice is
expected (assign / return / call-arg / init) as a borrow; ww rejected it
everywhere (the #108(c) exclusion), so base64 worked around the gap with
explicit a[0:n] slices.

type_assignable / isassignable now admit array->slice on an exact element
match (mirror ref/harec/src/types.c:1080-1097, the SLICE-dst arm). The four
acceptance sites route through one shared helper (desugar_arrayslice /
desugararrayslice) that rewrites the array expr to the explicit full slice
arr[0:len(arr)] — an N_SLICE over the array base. cgen is untouched: the
existing slice lowering (#252/#257/#135 made array bases, incl struct-field
arrays, correct) materialises the borrow header {.ptr=&arr[0], .len=N,
.cap=N}, byte-identically in both stages.

wwstage runs no general call-arg / N_ASSIGN typecheck, so checkassign +
desugarcallargs are added solely to route those two contexts through the
shared desugar (rule-10). desugarcallargs additionally loud-rejects an
element-MISMATCH array into a []T param, scoped to that shape so wwstage's
broader call-arg leniency is untouched.

953_arraytoslice_run covers the four contexts + a borrow-alias proof + the
i32/u8 element axis (dual-stage run + cs==ww byte-id), plus mismatch-reject
rows asserting both stages refuse [4]i32 -> []u8. Regen'd w6c + wwdump
combined.ww (#110).
2026-06-02 05:31:24 +09:00

506 lines
16 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;
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;
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_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_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. Unwraps a NAMED alias on
* either side so `type result = (T|E);` accepts variants too. */
{
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)) {
/* #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 = (p->type && p->type->kind == TY_NAMED)
? p->type->under : 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 = (dp->type && dp->type->kind == TY_NAMED)
? dp->type->under : 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)) {
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;
}
/* #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 = (dst->kind == TY_NAMED) ? dst->under : dst;
Type *su = (src->kind == TY_NAMED) ? src->under : 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 = (dst->kind == TY_NAMED) ? dst->under : dst;
Type *su = (src->kind == TY_NAMED) ? src->under : 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 "?";
}