wcc/check: #24-sib reject array-payload tagged-union construction, both stages

Constructing an array-typed payload into a tagged-union variant silently
dropped it (cstage MOVQ $0 -> returns 0; wwstage match-loud only). Reject
the construct when the selected variant chases to TY_ARRAY (target
TY_TAGGED, non-tagged source). tagged-struct/slice/scalar/str variants and
the array TYPE-decl stay legal. Faithful array-into-box block-store
deferred (#6). test/wcc/834 (new) + Makefile.
This commit is contained in:
2026-06-09 17:30:34 +09:00
parent 785fe342fa
commit d1ac836fb9
6 changed files with 502 additions and 0 deletions

View File

@@ -125,6 +125,39 @@ variant_present(Tparam *head, Type *vt)
return 0;
}
/* tagged_array_variant — #5/#60: when `src` is boxed into the tagged
* union `dst`, return the variant `src` selects IFF that variant chases
* to TY_ARRAY, else NULL. The array-payload box is unwired in cgen: the
* widen emitter zero-fills the slot at box materialization (a silent
* MOVQ $0 payload drop), so the construct must be rejected at the
* checker until the faithful array-block-store lands (deferred task #6).
* The tagged TYPE-decl with an array variant stays legal — test 944
* declares (void|size|[5]size) and only boxes the narrow `size` variant
* — only the array-variant CONSTRUCTION is refused. Mirrors the
* concrete->tagged variant select in type_assignable (type.c:324-343)
* so the variant chosen here is the one the box would materialize. */
static Type *
tagged_array_variant(Type *dst, Type *src)
{
if (dst == NULL || src == NULL) return NULL;
Type *du = type_chase_named(dst);
Type *su = type_chase_named(src);
if (du == NULL || du->kind != TY_TAGGED) return NULL;
if (su && su->kind == TY_TAGGED) return NULL; /* tagged->tagged */
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 NULL;
continue;
}
if (type_assignable(p->type, src)) {
if (pu && pu->kind == TY_ARRAY) return p->type;
return NULL;
}
}
return NULL;
}
/* match_yield_type — walk a match arm's body looking for the type
* of its first `yield expr;` statement. Returns NULL if no yield
* was found. Doesn't descend into nested match bodies — each match
@@ -2356,6 +2389,11 @@ clet(Checker *c, Node *n)
!assignable_addrfn(c, declared, n->rhs))
err(c, n->pos, "init %s not assignable to declared %s",
type_name(c->a, initt), type_name(c->a, declared));
/* #5/#60: reject boxing an array payload into a tagged variant. */
if (declared && initt && initt != ty_err &&
tagged_array_variant(declared, initt))
err(c, n->pos, "array-typed tagged-union variant "
"construction unwired — reject (task #5 / #60)");
/* #104 fold-2: `let x: f32 = 1.0` — narrow the init literal to f32. */
coerce_floatlit(n->rhs, declared);
/* #258: `let s: []T = arr` borrows the array as a full slice. */
@@ -2399,6 +2437,11 @@ cstmt(Checker *c, Node *n)
&& !assignable_addrfn(c, c->ret, n->lhs))
err(c, n->pos, "return %s not assignable to %s",
type_name(c->a, rt), type_name(c->a, c->ret));
/* #5/#60: reject returning an array payload into a tagged variant. */
else if (c->ret != ty_void && rt != ty_err && c->ret != ty_err &&
tagged_array_variant(c->ret, rt))
err(c, n->pos, "array-typed tagged-union variant "
"construction unwired — reject (task #5 / #60)");
/* #104 fold-2: `fn g() f32 = { return 1.0; }` — narrow to f32. */
coerce_floatlit(n->lhs, c->ret);
/* #258: `return arr` borrows the array as a full slice.