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).
528 lines
17 KiB
C
528 lines
17 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, *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 "?";
|
||
}
|