wcc/ww: reject value-less return in a non-void fn (catB-24)

wwstage's checkretassign short-circuited on a value-less `return;`
("skip flagging for now"), so `fn f() i32 = { return; }` built and
RET'd a garbage register. Mirror cstage cmd/wcc/check.c:2428-2439:
the no-value return has type void, then run isassignable(c.fnret,
void) — void→void and void→(T|void) accept, void→i32 is a confident
reject. cstage already rejected; this aligns wwstage's w6c_ww UP.

Regen w6c/wwdump combined.ww (checker embeds in both). Valid-program
codegen unchanged → cs==ww byte-id gate stays green.
This commit is contained in:
2026-06-15 03:52:16 +09:00
parent 6b7de54272
commit d0adfe5aab
5 changed files with 228 additions and 9 deletions

View File

@@ -16253,9 +16253,18 @@ fn checkletassign(c: *checker, n: *node) void = {
fn checkretassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.lhs == nil) {
// bare `return;` — OK iff fnret is void or a tagged union
// with a void variant. Skip flagging for now; cgen handles
// the void-variant tag synthesis already.
// bare `return;` — mirror cstage cmd/wcc/check.c:2428-2439:
// the value-less return has type void, then type_assignable
// (c.fnret, void). A void fnret or a tagged union carrying a
// void variant accepts; a non-void scalar fnret rejects (the
// value-less return would RET a garbage register). isassignable
// reaches the same verdict (void→void typeeqast; void→tagged via
// the void variant; void→i32 a confident primitive mismatch).
if (c.fnret == nil) { return; };
let vt: *node = mktname(c, "void");
let vconf: bool = false;
let vok: bool = isassignable(c, c.fnret, vt, &vconf);
if (vconf) { if (!vok) { errnotassign(c, c.fnret, vt, "return"); }; };
return;
};
if (c.fnret == nil) { return; };

View File

@@ -5779,9 +5779,18 @@ fn checkletassign(c: *checker, n: *node) void = {
fn checkretassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.lhs == nil) {
// bare `return;` — OK iff fnret is void or a tagged union
// with a void variant. Skip flagging for now; cgen handles
// the void-variant tag synthesis already.
// bare `return;` — mirror cstage cmd/wcc/check.c:2428-2439:
// the value-less return has type void, then type_assignable
// (c.fnret, void). A void fnret or a tagged union carrying a
// void variant accepts; a non-void scalar fnret rejects (the
// value-less return would RET a garbage register). isassignable
// reaches the same verdict (void→void typeeqast; void→tagged via
// the void variant; void→i32 a confident primitive mismatch).
if (c.fnret == nil) { return; };
let vt: *node = mktname(c, "void");
let vconf: bool = false;
let vok: bool = isassignable(c, c.fnret, vt, &vconf);
if (vconf) { if (!vok) { errnotassign(c, c.fnret, vt, "return"); }; };
return;
};
if (c.fnret == nil) { return; };

View File

@@ -16253,9 +16253,18 @@ fn checkletassign(c: *checker, n: *node) void = {
fn checkretassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.lhs == nil) {
// bare `return;` — OK iff fnret is void or a tagged union
// with a void variant. Skip flagging for now; cgen handles
// the void-variant tag synthesis already.
// bare `return;` — mirror cstage cmd/wcc/check.c:2428-2439:
// the value-less return has type void, then type_assignable
// (c.fnret, void). A void fnret or a tagged union carrying a
// void variant accepts; a non-void scalar fnret rejects (the
// value-less return would RET a garbage register). isassignable
// reaches the same verdict (void→void typeeqast; void→tagged via
// the void variant; void→i32 a confident primitive mismatch).
if (c.fnret == nil) { return; };
let vt: *node = mktname(c, "void");
let vconf: bool = false;
let vok: bool = isassignable(c, c.fnret, vt, &vconf);
if (vconf) { if (!vok) { errnotassign(c, c.fnret, vt, "return"); }; };
return;
};
if (c.fnret == nil) { return; };