wcc: array-init accept-if-fits coercion (#130, merges #146)

Align bare-int array-init assignability to Hare's literal-fits rule
(ref/harec/src/types.c promote_flexible): accept untyped-int array
elements that FIT the element type, reject out-of-range loud. cstage
(rejected all bare-int arrays, over-strict) and wwstage (accepted +
silently truncated out-of-range, over-loose) converge to the same
accept-if-fits rule. Per-element: foldable int literal range-checked
against element type [min,max] via def_cast_fits (rule-13 type-table
widths); non-foldable element falls back to type_assignable.

cstage: new arrlit_init_fits, N_LET decl-check fallback after whole-
array type_assignable fails. wwstage: checkletassign array branch +
route top-level lets through checkletassign (were unchecked — only
function-body lets ran assignability; closes #146 wwstage str->u8
over-accept).

Scalar-init range-check (let X:u8=300 truncates, both stages,
pre-existing) deferred to #148 — language-wide, needs bootstrap audit
+ explicit-cast conversion. def-array accept-if-fits deferred to #151
(def constfold machinery, different risk). Both bootstrap-NEUTRAL.

Test 920 (14 rows — accept: in-range u8/u32/u64/i32 + u8/i8 boundary +
typed regression + non-foldable-body; reject: over-range + over-256 +
i8-over + neg-for-unsigned + str->u8 + non-foldable-wider). Non-
foldable else-branch cs==ww verified (matching-type accept + byte-id;
wider-runtime-int reject both stages). Make test: 183/183 incl 990-997
byte-id + combined_ww_fresh.
This commit is contained in:
2026-05-27 09:18:03 +09:00
parent 9e3bc4ea37
commit 0546bda6c4
6 changed files with 537 additions and 1 deletions

View File

@@ -10673,6 +10673,66 @@ fn checkletassign(c: *checker, n: *node) void = {
};
};
};
// #130: array-init accept-if-fits. When lhs is [N]T and rhs is an
// array literal, per-element check: foldable int literal →
// defcastfits range-check (reject out-of-range loud, rule-7/Drew —
// Hare range-checks at literal-value level); non-foldable →
// isassignable to the element type. This BOTH accepts in-range
// bare-int (the #130 headline, matching cstage) AND closes the
// wwstage over-accept where str→u8 / out-of-range silently passed
// (#146 merged). Mirrors cstage check.c arrlit_init_fits. Scoped
// to the array path; scalar-init range-check is a separate
// language-wide gap (#148).
if (n.lhs.kind == nkind.N_TARRAY) {
if (n.rhs.kind == nkind.N_ARRLIT) {
let elemtn: *node = n.lhs.lhs;
let at: *tinfo = tinfofornode(c, n.lhs);
let et: *tinfo = nil;
if (at != nil) { et = at.sub; };
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
let e: *node = n.rhs.list;
for (e != nil) {
let skip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { skip = true; };
};
if (!skip) {
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
let v: u64 = 0u64;
let folded: bool = false;
if (et != nil) {
if (typeisint(et)) {
if (ev != nil) {
folded = foldintliteral(ev, &v);
};
};
};
if (folded) {
if (!defcastfits(et, v)) {
let m: str = "let: array element out of range\n";
os.write(2, m.ptr, m.len: u64);
c.errs += 1;
return;
};
} else {
let est: *node = exprtype(c, ev, elemtn);
let conf2: bool = false;
if (est != nil) {
if (!isassignable(c, elemtn, est, &conf2)) {
if (conf2) {
errnotassign(c, elemtn, est, "let");
return;
};
};
};
};
};
e = e.next;
};
return;
};
};
let conf: bool = false;
let ok: bool = isassignable(c, n.lhs, src, &conf);
if (!conf) { return; };
@@ -11052,6 +11112,13 @@ export fn checkfile(c: *checker, file: *node) void = {
} else { if (k == nkind.N_LET) {
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
if (d.rhs != nil) { resolvewalk(c, d.rhs); };
// #130: top-level let assignability — the subtree
// resolvewalk above stamps types but never runs the
// init-assignability check (function-body lets get it
// via resolvewalk's post-order L247; top-level lets
// were missed). Needed for the array accept-if-fits
// range-check to fire on module-level `let A:[N]u8=[..]`.
checkletassign(c, d);
};};};};
d = d.next;
};

View File

@@ -3104,6 +3104,66 @@ fn checkletassign(c: *checker, n: *node) void = {
};
};
};
// #130: array-init accept-if-fits. When lhs is [N]T and rhs is an
// array literal, per-element check: foldable int literal →
// defcastfits range-check (reject out-of-range loud, rule-7/Drew —
// Hare range-checks at literal-value level); non-foldable →
// isassignable to the element type. This BOTH accepts in-range
// bare-int (the #130 headline, matching cstage) AND closes the
// wwstage over-accept where str→u8 / out-of-range silently passed
// (#146 merged). Mirrors cstage check.c arrlit_init_fits. Scoped
// to the array path; scalar-init range-check is a separate
// language-wide gap (#148).
if (n.lhs.kind == nkind.N_TARRAY) {
if (n.rhs.kind == nkind.N_ARRLIT) {
let elemtn: *node = n.lhs.lhs;
let at: *tinfo = tinfofornode(c, n.lhs);
let et: *tinfo = nil;
if (at != nil) { et = at.sub; };
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
let e: *node = n.rhs.list;
for (e != nil) {
let skip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { skip = true; };
};
if (!skip) {
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
let v: u64 = 0u64;
let folded: bool = false;
if (et != nil) {
if (typeisint(et)) {
if (ev != nil) {
folded = foldintliteral(ev, &v);
};
};
};
if (folded) {
if (!defcastfits(et, v)) {
let m: str = "let: array element out of range\n";
os.write(2, m.ptr, m.len: u64);
c.errs += 1;
return;
};
} else {
let est: *node = exprtype(c, ev, elemtn);
let conf2: bool = false;
if (est != nil) {
if (!isassignable(c, elemtn, est, &conf2)) {
if (conf2) {
errnotassign(c, elemtn, est, "let");
return;
};
};
};
};
};
e = e.next;
};
return;
};
};
let conf: bool = false;
let ok: bool = isassignable(c, n.lhs, src, &conf);
if (!conf) { return; };
@@ -3483,6 +3543,13 @@ export fn checkfile(c: *checker, file: *node) void = {
} else { if (k == nkind.N_LET) {
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
if (d.rhs != nil) { resolvewalk(c, d.rhs); };
// #130: top-level let assignability — the subtree
// resolvewalk above stamps types but never runs the
// init-assignability check (function-body lets get it
// via resolvewalk's post-order L247; top-level lets
// were missed). Needed for the array accept-if-fits
// range-check to fire on module-level `let A:[N]u8=[..]`.
checkletassign(c, d);
};};};};
d = d.next;
};

View File

@@ -10673,6 +10673,66 @@ fn checkletassign(c: *checker, n: *node) void = {
};
};
};
// #130: array-init accept-if-fits. When lhs is [N]T and rhs is an
// array literal, per-element check: foldable int literal →
// defcastfits range-check (reject out-of-range loud, rule-7/Drew —
// Hare range-checks at literal-value level); non-foldable →
// isassignable to the element type. This BOTH accepts in-range
// bare-int (the #130 headline, matching cstage) AND closes the
// wwstage over-accept where str→u8 / out-of-range silently passed
// (#146 merged). Mirrors cstage check.c arrlit_init_fits. Scoped
// to the array path; scalar-init range-check is a separate
// language-wide gap (#148).
if (n.lhs.kind == nkind.N_TARRAY) {
if (n.rhs.kind == nkind.N_ARRLIT) {
let elemtn: *node = n.lhs.lhs;
let at: *tinfo = tinfofornode(c, n.lhs);
let et: *tinfo = nil;
if (at != nil) { et = at.sub; };
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
let e: *node = n.rhs.list;
for (e != nil) {
let skip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { skip = true; };
};
if (!skip) {
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
let v: u64 = 0u64;
let folded: bool = false;
if (et != nil) {
if (typeisint(et)) {
if (ev != nil) {
folded = foldintliteral(ev, &v);
};
};
};
if (folded) {
if (!defcastfits(et, v)) {
let m: str = "let: array element out of range\n";
os.write(2, m.ptr, m.len: u64);
c.errs += 1;
return;
};
} else {
let est: *node = exprtype(c, ev, elemtn);
let conf2: bool = false;
if (est != nil) {
if (!isassignable(c, elemtn, est, &conf2)) {
if (conf2) {
errnotassign(c, elemtn, est, "let");
return;
};
};
};
};
};
e = e.next;
};
return;
};
};
let conf: bool = false;
let ok: bool = isassignable(c, n.lhs, src, &conf);
if (!conf) { return; };
@@ -11052,6 +11112,13 @@ export fn checkfile(c: *checker, file: *node) void = {
} else { if (k == nkind.N_LET) {
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
if (d.rhs != nil) { resolvewalk(c, d.rhs); };
// #130: top-level let assignability — the subtree
// resolvewalk above stamps types but never runs the
// init-assignability check (function-body lets get it
// via resolvewalk's post-order L247; top-level lets
// were missed). Needed for the array accept-if-fits
// range-check to fire on module-level `let A:[N]u8=[..]`.
checkletassign(c, d);
};};};};
d = d.next;
};