Files
ww/cmd/wcc/type.c
Hojun-Cho c9cfa52624 wcc/check: #103/#108 inferred untyped-int defaults to int (8B), both stages
cstage type_default(TY_UNTYPED_INT) returned ty_i32 (4B): an unannotated
`let x = <v>` / `let a = [<v>,..]` silently TRUNCATED any value > 2^31
(5000000000 -> 705032704) and strode inferred arrays at 4. wwstage kept
the element raw untyped_int (size 0), which sized INCONSISTENTLY across
cgen — the array STORE strode the 8 sentinel but letslotsize under-
allocated the frame (SEGV) and cgindex strode the READ at 1. The two
stages were each wrong differently; #263 polarity: cstage was the
truncating side. int = machine word = 8B (Go-style, MEMORY
project_int_machine_word_derived_limits); Hare lowers a flexible iconst
to `int`, never a fixed i32 (ref/harec/src/types.c:835).

Fix, one root, both stages (FUSE — the cs default + the ww concrete
element must land together, else the inferred array is transient cs!=ww):
- cmd/wcc/type.c type_default(TY_UNTYPED_INT) ty_i32 -> ty_int. The
  root; stops scalar AND array truncation at source.
- cmd/wcc/check.c N_ARRLIT empty-elt fallback ty_i32 -> ty_int. Symmetric
  pair; count-0 array emits no stores, so byte-id-neutral.
- selfhost/cmd/wcc/check.ww exprtype N_ARRLIT: default the inferred
  element's untyped flavor to concrete (untyped_int->int, _float->f64,
  _str->str, _rune->rune, _bool->bool, mirror cstage type_default),
  empty-elt "i32"->"int", and stamp the synthesized N_TARRAY's .type_ so
  slotsize / elemsizeofc / letslotsize read its real [N]int size via the
  type table (rule-13) — no letslotsize special-case (SSoT).
combined.ww regen (check.ww embed): w6c + wwdump.

ken v2 corpus re-census (160 files): EXACTLY 5 rows move, ALL CONVERGE
(byte-id YES + run exit 0, none both-wrong, zero regression):
  m2_while   #108 scalar via alias-bool loop
  m8_range1  #104 for-range elem over alias [4]int
  m8_range2  #104 over 2-level alias
  m8_slice1  #103 inferred array + alias-slice init
  m8_slice2  #103 + 2-level-alias slice + re-slice
Bootstrap byte-id neutral (5 combined units w6c==w6c_ww; 0 bare inferred
arrays in selfhost). Annotated controls untouched ([4]i32 stride-4,
[4]int stride-8, byte-id). Pinned in test/wcc/813_arrlit_infer_elem_run
(the 2 direct repros incl the >2^31 truncation teeth + all 5 movers +
controls; test-unit 296).

Closes #103 (inferred-array SEGV + truncation), #108 (cstage scalar
untyped-int truncation), #104 (for-range elem alias i32-stamp), and the
m8_slice []int-init acceptance divergence.
2026-06-06 09:23:24 +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_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: {
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 "?";
}