w6c+wwstage: implicit [N]T->[]T array-to-slice coercion via desugar (#258)

Hare admits an array with a defined length wherever its element slice is
expected (assign / return / call-arg / init) as a borrow; ww rejected it
everywhere (the #108(c) exclusion), so base64 worked around the gap with
explicit a[0:n] slices.

type_assignable / isassignable now admit array->slice on an exact element
match (mirror ref/harec/src/types.c:1080-1097, the SLICE-dst arm). The four
acceptance sites route through one shared helper (desugar_arrayslice /
desugararrayslice) that rewrites the array expr to the explicit full slice
arr[0:len(arr)] — an N_SLICE over the array base. cgen is untouched: the
existing slice lowering (#252/#257/#135 made array bases, incl struct-field
arrays, correct) materialises the borrow header {.ptr=&arr[0], .len=N,
.cap=N}, byte-identically in both stages.

wwstage runs no general call-arg / N_ASSIGN typecheck, so checkassign +
desugarcallargs are added solely to route those two contexts through the
shared desugar (rule-10). desugarcallargs additionally loud-rejects an
element-MISMATCH array into a []T param, scoped to that shape so wwstage's
broader call-arg leniency is untouched.

953_arraytoslice_run covers the four contexts + a borrow-alias proof + the
i32/u8 element axis (dual-stage run + cs==ww byte-id), plus mismatch-reject
rows asserting both stages refuse [4]i32 -> []u8. Regen'd w6c + wwdump
combined.ww (#110).
This commit is contained in:
2026-06-02 05:26:55 +09:00
parent 6bcb0929f8
commit e92708ecda
7 changed files with 829 additions and 7 deletions

View File

@@ -11020,6 +11020,16 @@ fn resolvewalk(c: *checker, n: *node) void = {
// are not value-typed nodes; their expression children get stamped on
// the recursive descent into them. Type-expression kinds (N_T*) are
// covered separately by the tinfofornode block above.
// #258: desugar an array arg/rhs into an implicit full slice at the
// call-arg and assignment contexts (let / return drive their own
// desugar in checkletassign / checkretassign). Placed post-child-walk
// so arg/operand types are stamped, and before the end-dispatch
// exprtype below so a regular N_CALL is still an N_CALL (not folded to
// an N_INTLIT by the size/align intercept). Mirrors cstage's post-
// order cexpr desugar at the call-arg / N_ASSIGN sites.
if (k == nkind.N_CALL) { desugarcallargs(c, n); };
if (k == nkind.N_ASSIGN) { checkassign(c, n); };
if (k == nkind.N_INTLIT || k == nkind.N_FLOATLIT ||
k == nkind.N_STRLIT || k == nkind.N_RUNELIT ||
k == nkind.N_TRUE || k == nkind.N_FALSE ||
@@ -13618,6 +13628,19 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
if (du == nil) { *confident = false; return true; };
if (su == nil) { *confident = false; return true; };
if (typeeqast(du, su)) { return true; };
// #258: implicit [N]T -> []T array-to-slice borrow. Hare admits an
// array with a defined length wherever its element slice is expected
// (ref/harec/src/types.c:1080-1097, the SLICE-dst arm). Element types
// must match exactly — no element decay; a mismatch is a CONFIDENT
// reject (mirror cstage type.c type_assignable's #258 arm + fallthrough
// to 0). The acceptance sites then desugar the array expr to an
// explicit full slice via desugararrayslice; cgen is untouched.
if (du.kind == nkind.N_TSLICE) {
if (su.kind == nkind.N_TARRAY) {
if (typeeqast(du.lhs, su.lhs)) { return true; };
return false;
};
};
// untyped numeric → any numeric named type.
if (isuntypedint(su)) {
if (isnumerictname(du)) { return true; };
@@ -14114,6 +14137,139 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
};
};
// desugararrayslice — #258. The single shared lowering for the implicit
// [N]T -> []T borrow. isassignable already admits an array with a defined
// length into a matching []T slot (see isassignable's #258 arm); here we
// rewrite the array expr to the explicit full slice `arr[0:len(arr)]` (an
// N_SLICE over the array base), reusing the existing slice cgen — #252/
// #257/#135 made array bases (incl struct-field arrays) correct. No new
// array->slice store cgen; pushargsrev / cgslice / cglet already lower an
// N_SLICE identically to cstage, so the borrow header is byte-id across
// stages. Twin of cstage cmd/wcc/check.c desugar_arrayslice.
//
// Returns the (possibly new) node for the caller's tree slot: `val`
// unchanged when the shape doesn't match, else a fresh N_SLICE whose base
// is `val` (which keeps its stamped array type_). The original sibling
// link transfers to the N_SLICE so a desugared call-arg keeps its place.
fn desugararrayslice(c: *checker, dsttn: *node, srctn: *node, val: *node) *node = {
if (dsttn == nil) { return val; };
if (srctn == nil) { return val; };
if (val == nil) { return val; };
let du: *node = resolvealias(c, unwrapbang(dsttn));
let su: *node = resolvealias(c, unwrapbang(srctn));
if (du == nil) { return val; };
if (su == nil) { return val; };
if (du.kind != nkind.N_TSLICE) { return val; };
if (su.kind != nkind.N_TARRAY) { return val; };
if (!typeeqast(du.lhs, su.lhs)) { return val; };
let sl: *node = newnode(nkind.N_SLICE, val.file, val.line, val.col);
sl.lhs = val; // sliced base; lo (.rhs) / hi (.cond) nil → 0 : len(arr)
let slt: *node = newnode(nkind.N_TSLICE, "", 0, 0);
slt.lhs = su.lhs;
sl.type_ = tinfofornode(c, slt): *void;
sl.next = val.next;
val.next = nil;
return sl;
};
// calleefndecl — resolve a call's callee to its fn-decl node so the
// arg/param lockstep (desugarcallargs) can read declared param types.
// Mirrors exprtype's N_CALL resolution (bare-leaf via scopelookupprefer,
// module-qualified via the SK_USE receiver). nil for builtins / fn-value
// callees — they have no declared param list to drive the #258 desugar,
// and the selfhost corpus has no array→slice arg there anyway.
fn calleefndecl(c: *checker, callee: *node) *node = {
if (callee == nil) { return nil; };
if (callee.kind == nkind.N_IDENT) {
let s: *sym = scopelookupprefer(c.cur, c.curmod, callee.str);
if (s != nil) {
if (s.skind == skind.SK_FN) { return s.decl; };
};
return nil;
};
if (callee.kind == nkind.N_DOT) {
if (callee.lhs != nil) {
if (callee.lhs.kind == nkind.N_IDENT) {
let ms: *sym = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
if (ms != nil && ms.skind != skind.SK_USE) {
let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str);
if (mu != nil) { ms = mu; };
};
if (ms != nil) {
if (ms.skind == skind.SK_USE) {
let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) { return fs.decl; };
};
};
};
};
};
};
return nil;
};
// desugarcallargs — #258 at the call-arg context. Lockstep the call's
// args against the callee's declared params and desugar an array arg
// passed where a []T param is expected. Mirrors cstage cmd/wcc/check.c's
// non-variadic call-arg arm. Variadic (`T...`) slots are skipped (the
// arg flows into the gather as an element, not the slice itself).
fn desugarcallargs(c: *checker, n: *node) void = {
if (n == nil) { return; };
let decl: *node = calleefndecl(c, n.lhs);
if (decl == nil) { return; };
let param: *node = decl.list;
let prev: *node = nil;
let a: *node = n.list;
for (a != nil) {
let nexta: *node = a.next;
if (param != nil) {
if (param.kind == nkind.N_PARAM) {
if (param.op != tkind.TK_ELLIPSIS) {
let atype: *node = exprtype(c, a, nil);
// #258: an array arg into a []T param with a
// MISMATCHED element is not a borrow — loud reject,
// mirror cstage's call-arg type_assignable failure.
// Gated to the array→slice-param shape so wwstage's
// broader call-arg leniency (it runs no general
// param typecheck) is untouched.
let pu: *node = resolvealias(c, unwrapbang(param.lhs));
let au: *node = resolvealias(c, unwrapbang(atype));
if (pu != nil) { if (au != nil) {
if (pu.kind == nkind.N_TSLICE) { if (au.kind == nkind.N_TARRAY) {
if (!typeeqast(pu.lhs, au.lhs)) {
errnotassign(c, param.lhs, atype, "argument");
};
}; };
}; };
let rep: *node = desugararrayslice(c, param.lhs, atype, a);
if (rep != a) {
if (prev == nil) { n.list = rep; } else { prev.next = rep; };
a = rep;
};
};
if (param.op != tkind.TK_ELLIPSIS) { param = param.next; };
};
};
prev = a;
a = nexta;
};
};
// checkassign — #258 at the assignment context. wwstage runs no other
// N_ASSIGN typecheck (cstage's lives in cexpr); this exists solely to
// route an array→slice rhs through the shared desugar so w6c_ww emits
// the same borrow as w6c (rule-10). No error diagnostics — cstage gates
// the shape.
fn checkassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.lhs == nil) { return; };
if (n.rhs == nil) { return; };
let ltn: *node = exprtype(c, n.lhs, nil);
let rtn: *node = exprtype(c, n.rhs, nil);
n.rhs = desugararrayslice(c, ltn, rtn, n.rhs);
};
fn checkletassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.rhs == nil) { return; }; // no init
@@ -14205,6 +14361,8 @@ fn checkletassign(c: *checker, n: *node) void = {
if (!ok) { if (assignableaddrfn(c, n.lhs, n.rhs)) { ok = true; }; };
if (!conf) { return; };
if (!ok) { errnotassign(c, n.lhs, src, "let"); };
// #258: `let s: []T = arr` borrows the array as a full slice.
n.rhs = desugararrayslice(c, n.lhs, src, n.rhs);
};
fn checkretassign(c: *checker, n: *node) void = {
@@ -14224,6 +14382,8 @@ fn checkretassign(c: *checker, n: *node) void = {
if (!ok) { if (assignableaddrfn(c, c.fnret, n.lhs)) { ok = true; }; };
if (!conf) { return; };
if (!ok) { errnotassign(c, c.fnret, src, "return"); };
// #258: `return arr` borrows the array as a full slice.
n.lhs = desugararrayslice(c, c.fnret, src, n.lhs);
};
// ---- is / as validity ------------------------------------------------

View File

@@ -615,6 +615,16 @@ fn resolvewalk(c: *checker, n: *node) void = {
// are not value-typed nodes; their expression children get stamped on
// the recursive descent into them. Type-expression kinds (N_T*) are
// covered separately by the tinfofornode block above.
// #258: desugar an array arg/rhs into an implicit full slice at the
// call-arg and assignment contexts (let / return drive their own
// desugar in checkletassign / checkretassign). Placed post-child-walk
// so arg/operand types are stamped, and before the end-dispatch
// exprtype below so a regular N_CALL is still an N_CALL (not folded to
// an N_INTLIT by the size/align intercept). Mirrors cstage's post-
// order cexpr desugar at the call-arg / N_ASSIGN sites.
if (k == nkind.N_CALL) { desugarcallargs(c, n); };
if (k == nkind.N_ASSIGN) { checkassign(c, n); };
if (k == nkind.N_INTLIT || k == nkind.N_FLOATLIT ||
k == nkind.N_STRLIT || k == nkind.N_RUNELIT ||
k == nkind.N_TRUE || k == nkind.N_FALSE ||
@@ -3213,6 +3223,19 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
if (du == nil) { *confident = false; return true; };
if (su == nil) { *confident = false; return true; };
if (typeeqast(du, su)) { return true; };
// #258: implicit [N]T -> []T array-to-slice borrow. Hare admits an
// array with a defined length wherever its element slice is expected
// (ref/harec/src/types.c:1080-1097, the SLICE-dst arm). Element types
// must match exactly — no element decay; a mismatch is a CONFIDENT
// reject (mirror cstage type.c type_assignable's #258 arm + fallthrough
// to 0). The acceptance sites then desugar the array expr to an
// explicit full slice via desugararrayslice; cgen is untouched.
if (du.kind == nkind.N_TSLICE) {
if (su.kind == nkind.N_TARRAY) {
if (typeeqast(du.lhs, su.lhs)) { return true; };
return false;
};
};
// untyped numeric → any numeric named type.
if (isuntypedint(su)) {
if (isnumerictname(du)) { return true; };
@@ -3709,6 +3732,139 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
};
};
// desugararrayslice — #258. The single shared lowering for the implicit
// [N]T -> []T borrow. isassignable already admits an array with a defined
// length into a matching []T slot (see isassignable's #258 arm); here we
// rewrite the array expr to the explicit full slice `arr[0:len(arr)]` (an
// N_SLICE over the array base), reusing the existing slice cgen — #252/
// #257/#135 made array bases (incl struct-field arrays) correct. No new
// array->slice store cgen; pushargsrev / cgslice / cglet already lower an
// N_SLICE identically to cstage, so the borrow header is byte-id across
// stages. Twin of cstage cmd/wcc/check.c desugar_arrayslice.
//
// Returns the (possibly new) node for the caller's tree slot: `val`
// unchanged when the shape doesn't match, else a fresh N_SLICE whose base
// is `val` (which keeps its stamped array type_). The original sibling
// link transfers to the N_SLICE so a desugared call-arg keeps its place.
fn desugararrayslice(c: *checker, dsttn: *node, srctn: *node, val: *node) *node = {
if (dsttn == nil) { return val; };
if (srctn == nil) { return val; };
if (val == nil) { return val; };
let du: *node = resolvealias(c, unwrapbang(dsttn));
let su: *node = resolvealias(c, unwrapbang(srctn));
if (du == nil) { return val; };
if (su == nil) { return val; };
if (du.kind != nkind.N_TSLICE) { return val; };
if (su.kind != nkind.N_TARRAY) { return val; };
if (!typeeqast(du.lhs, su.lhs)) { return val; };
let sl: *node = newnode(nkind.N_SLICE, val.file, val.line, val.col);
sl.lhs = val; // sliced base; lo (.rhs) / hi (.cond) nil → 0 : len(arr)
let slt: *node = newnode(nkind.N_TSLICE, "", 0, 0);
slt.lhs = su.lhs;
sl.type_ = tinfofornode(c, slt): *void;
sl.next = val.next;
val.next = nil;
return sl;
};
// calleefndecl — resolve a call's callee to its fn-decl node so the
// arg/param lockstep (desugarcallargs) can read declared param types.
// Mirrors exprtype's N_CALL resolution (bare-leaf via scopelookupprefer,
// module-qualified via the SK_USE receiver). nil for builtins / fn-value
// callees — they have no declared param list to drive the #258 desugar,
// and the selfhost corpus has no array→slice arg there anyway.
fn calleefndecl(c: *checker, callee: *node) *node = {
if (callee == nil) { return nil; };
if (callee.kind == nkind.N_IDENT) {
let s: *sym = scopelookupprefer(c.cur, c.curmod, callee.str);
if (s != nil) {
if (s.skind == skind.SK_FN) { return s.decl; };
};
return nil;
};
if (callee.kind == nkind.N_DOT) {
if (callee.lhs != nil) {
if (callee.lhs.kind == nkind.N_IDENT) {
let ms: *sym = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
if (ms != nil && ms.skind != skind.SK_USE) {
let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str);
if (mu != nil) { ms = mu; };
};
if (ms != nil) {
if (ms.skind == skind.SK_USE) {
let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) { return fs.decl; };
};
};
};
};
};
};
return nil;
};
// desugarcallargs — #258 at the call-arg context. Lockstep the call's
// args against the callee's declared params and desugar an array arg
// passed where a []T param is expected. Mirrors cstage cmd/wcc/check.c's
// non-variadic call-arg arm. Variadic (`T...`) slots are skipped (the
// arg flows into the gather as an element, not the slice itself).
fn desugarcallargs(c: *checker, n: *node) void = {
if (n == nil) { return; };
let decl: *node = calleefndecl(c, n.lhs);
if (decl == nil) { return; };
let param: *node = decl.list;
let prev: *node = nil;
let a: *node = n.list;
for (a != nil) {
let nexta: *node = a.next;
if (param != nil) {
if (param.kind == nkind.N_PARAM) {
if (param.op != tkind.TK_ELLIPSIS) {
let atype: *node = exprtype(c, a, nil);
// #258: an array arg into a []T param with a
// MISMATCHED element is not a borrow — loud reject,
// mirror cstage's call-arg type_assignable failure.
// Gated to the array→slice-param shape so wwstage's
// broader call-arg leniency (it runs no general
// param typecheck) is untouched.
let pu: *node = resolvealias(c, unwrapbang(param.lhs));
let au: *node = resolvealias(c, unwrapbang(atype));
if (pu != nil) { if (au != nil) {
if (pu.kind == nkind.N_TSLICE) { if (au.kind == nkind.N_TARRAY) {
if (!typeeqast(pu.lhs, au.lhs)) {
errnotassign(c, param.lhs, atype, "argument");
};
}; };
}; };
let rep: *node = desugararrayslice(c, param.lhs, atype, a);
if (rep != a) {
if (prev == nil) { n.list = rep; } else { prev.next = rep; };
a = rep;
};
};
if (param.op != tkind.TK_ELLIPSIS) { param = param.next; };
};
};
prev = a;
a = nexta;
};
};
// checkassign — #258 at the assignment context. wwstage runs no other
// N_ASSIGN typecheck (cstage's lives in cexpr); this exists solely to
// route an array→slice rhs through the shared desugar so w6c_ww emits
// the same borrow as w6c (rule-10). No error diagnostics — cstage gates
// the shape.
fn checkassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.lhs == nil) { return; };
if (n.rhs == nil) { return; };
let ltn: *node = exprtype(c, n.lhs, nil);
let rtn: *node = exprtype(c, n.rhs, nil);
n.rhs = desugararrayslice(c, ltn, rtn, n.rhs);
};
fn checkletassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.rhs == nil) { return; }; // no init
@@ -3800,6 +3956,8 @@ fn checkletassign(c: *checker, n: *node) void = {
if (!ok) { if (assignableaddrfn(c, n.lhs, n.rhs)) { ok = true; }; };
if (!conf) { return; };
if (!ok) { errnotassign(c, n.lhs, src, "let"); };
// #258: `let s: []T = arr` borrows the array as a full slice.
n.rhs = desugararrayslice(c, n.lhs, src, n.rhs);
};
fn checkretassign(c: *checker, n: *node) void = {
@@ -3819,6 +3977,8 @@ fn checkretassign(c: *checker, n: *node) void = {
if (!ok) { if (assignableaddrfn(c, c.fnret, n.lhs)) { ok = true; }; };
if (!conf) { return; };
if (!ok) { errnotassign(c, c.fnret, src, "return"); };
// #258: `return arr` borrows the array as a full slice.
n.lhs = desugararrayslice(c, c.fnret, src, n.lhs);
};
// ---- is / as validity ------------------------------------------------

View File

@@ -11020,6 +11020,16 @@ fn resolvewalk(c: *checker, n: *node) void = {
// are not value-typed nodes; their expression children get stamped on
// the recursive descent into them. Type-expression kinds (N_T*) are
// covered separately by the tinfofornode block above.
// #258: desugar an array arg/rhs into an implicit full slice at the
// call-arg and assignment contexts (let / return drive their own
// desugar in checkletassign / checkretassign). Placed post-child-walk
// so arg/operand types are stamped, and before the end-dispatch
// exprtype below so a regular N_CALL is still an N_CALL (not folded to
// an N_INTLIT by the size/align intercept). Mirrors cstage's post-
// order cexpr desugar at the call-arg / N_ASSIGN sites.
if (k == nkind.N_CALL) { desugarcallargs(c, n); };
if (k == nkind.N_ASSIGN) { checkassign(c, n); };
if (k == nkind.N_INTLIT || k == nkind.N_FLOATLIT ||
k == nkind.N_STRLIT || k == nkind.N_RUNELIT ||
k == nkind.N_TRUE || k == nkind.N_FALSE ||
@@ -13618,6 +13628,19 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
if (du == nil) { *confident = false; return true; };
if (su == nil) { *confident = false; return true; };
if (typeeqast(du, su)) { return true; };
// #258: implicit [N]T -> []T array-to-slice borrow. Hare admits an
// array with a defined length wherever its element slice is expected
// (ref/harec/src/types.c:1080-1097, the SLICE-dst arm). Element types
// must match exactly — no element decay; a mismatch is a CONFIDENT
// reject (mirror cstage type.c type_assignable's #258 arm + fallthrough
// to 0). The acceptance sites then desugar the array expr to an
// explicit full slice via desugararrayslice; cgen is untouched.
if (du.kind == nkind.N_TSLICE) {
if (su.kind == nkind.N_TARRAY) {
if (typeeqast(du.lhs, su.lhs)) { return true; };
return false;
};
};
// untyped numeric → any numeric named type.
if (isuntypedint(su)) {
if (isnumerictname(du)) { return true; };
@@ -14114,6 +14137,139 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
};
};
// desugararrayslice — #258. The single shared lowering for the implicit
// [N]T -> []T borrow. isassignable already admits an array with a defined
// length into a matching []T slot (see isassignable's #258 arm); here we
// rewrite the array expr to the explicit full slice `arr[0:len(arr)]` (an
// N_SLICE over the array base), reusing the existing slice cgen — #252/
// #257/#135 made array bases (incl struct-field arrays) correct. No new
// array->slice store cgen; pushargsrev / cgslice / cglet already lower an
// N_SLICE identically to cstage, so the borrow header is byte-id across
// stages. Twin of cstage cmd/wcc/check.c desugar_arrayslice.
//
// Returns the (possibly new) node for the caller's tree slot: `val`
// unchanged when the shape doesn't match, else a fresh N_SLICE whose base
// is `val` (which keeps its stamped array type_). The original sibling
// link transfers to the N_SLICE so a desugared call-arg keeps its place.
fn desugararrayslice(c: *checker, dsttn: *node, srctn: *node, val: *node) *node = {
if (dsttn == nil) { return val; };
if (srctn == nil) { return val; };
if (val == nil) { return val; };
let du: *node = resolvealias(c, unwrapbang(dsttn));
let su: *node = resolvealias(c, unwrapbang(srctn));
if (du == nil) { return val; };
if (su == nil) { return val; };
if (du.kind != nkind.N_TSLICE) { return val; };
if (su.kind != nkind.N_TARRAY) { return val; };
if (!typeeqast(du.lhs, su.lhs)) { return val; };
let sl: *node = newnode(nkind.N_SLICE, val.file, val.line, val.col);
sl.lhs = val; // sliced base; lo (.rhs) / hi (.cond) nil → 0 : len(arr)
let slt: *node = newnode(nkind.N_TSLICE, "", 0, 0);
slt.lhs = su.lhs;
sl.type_ = tinfofornode(c, slt): *void;
sl.next = val.next;
val.next = nil;
return sl;
};
// calleefndecl — resolve a call's callee to its fn-decl node so the
// arg/param lockstep (desugarcallargs) can read declared param types.
// Mirrors exprtype's N_CALL resolution (bare-leaf via scopelookupprefer,
// module-qualified via the SK_USE receiver). nil for builtins / fn-value
// callees — they have no declared param list to drive the #258 desugar,
// and the selfhost corpus has no array→slice arg there anyway.
fn calleefndecl(c: *checker, callee: *node) *node = {
if (callee == nil) { return nil; };
if (callee.kind == nkind.N_IDENT) {
let s: *sym = scopelookupprefer(c.cur, c.curmod, callee.str);
if (s != nil) {
if (s.skind == skind.SK_FN) { return s.decl; };
};
return nil;
};
if (callee.kind == nkind.N_DOT) {
if (callee.lhs != nil) {
if (callee.lhs.kind == nkind.N_IDENT) {
let ms: *sym = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
if (ms != nil && ms.skind != skind.SK_USE) {
let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str);
if (mu != nil) { ms = mu; };
};
if (ms != nil) {
if (ms.skind == skind.SK_USE) {
let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) { return fs.decl; };
};
};
};
};
};
};
return nil;
};
// desugarcallargs — #258 at the call-arg context. Lockstep the call's
// args against the callee's declared params and desugar an array arg
// passed where a []T param is expected. Mirrors cstage cmd/wcc/check.c's
// non-variadic call-arg arm. Variadic (`T...`) slots are skipped (the
// arg flows into the gather as an element, not the slice itself).
fn desugarcallargs(c: *checker, n: *node) void = {
if (n == nil) { return; };
let decl: *node = calleefndecl(c, n.lhs);
if (decl == nil) { return; };
let param: *node = decl.list;
let prev: *node = nil;
let a: *node = n.list;
for (a != nil) {
let nexta: *node = a.next;
if (param != nil) {
if (param.kind == nkind.N_PARAM) {
if (param.op != tkind.TK_ELLIPSIS) {
let atype: *node = exprtype(c, a, nil);
// #258: an array arg into a []T param with a
// MISMATCHED element is not a borrow — loud reject,
// mirror cstage's call-arg type_assignable failure.
// Gated to the array→slice-param shape so wwstage's
// broader call-arg leniency (it runs no general
// param typecheck) is untouched.
let pu: *node = resolvealias(c, unwrapbang(param.lhs));
let au: *node = resolvealias(c, unwrapbang(atype));
if (pu != nil) { if (au != nil) {
if (pu.kind == nkind.N_TSLICE) { if (au.kind == nkind.N_TARRAY) {
if (!typeeqast(pu.lhs, au.lhs)) {
errnotassign(c, param.lhs, atype, "argument");
};
}; };
}; };
let rep: *node = desugararrayslice(c, param.lhs, atype, a);
if (rep != a) {
if (prev == nil) { n.list = rep; } else { prev.next = rep; };
a = rep;
};
};
if (param.op != tkind.TK_ELLIPSIS) { param = param.next; };
};
};
prev = a;
a = nexta;
};
};
// checkassign — #258 at the assignment context. wwstage runs no other
// N_ASSIGN typecheck (cstage's lives in cexpr); this exists solely to
// route an array→slice rhs through the shared desugar so w6c_ww emits
// the same borrow as w6c (rule-10). No error diagnostics — cstage gates
// the shape.
fn checkassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.lhs == nil) { return; };
if (n.rhs == nil) { return; };
let ltn: *node = exprtype(c, n.lhs, nil);
let rtn: *node = exprtype(c, n.rhs, nil);
n.rhs = desugararrayslice(c, ltn, rtn, n.rhs);
};
fn checkletassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.rhs == nil) { return; }; // no init
@@ -14205,6 +14361,8 @@ fn checkletassign(c: *checker, n: *node) void = {
if (!ok) { if (assignableaddrfn(c, n.lhs, n.rhs)) { ok = true; }; };
if (!conf) { return; };
if (!ok) { errnotassign(c, n.lhs, src, "let"); };
// #258: `let s: []T = arr` borrows the array as a full slice.
n.rhs = desugararrayslice(c, n.lhs, src, n.rhs);
};
fn checkretassign(c: *checker, n: *node) void = {
@@ -14224,6 +14382,8 @@ fn checkretassign(c: *checker, n: *node) void = {
if (!ok) { if (assignableaddrfn(c, c.fnret, n.lhs)) { ok = true; }; };
if (!conf) { return; };
if (!ok) { errnotassign(c, c.fnret, src, "return"); };
// #258: `return arr` borrows the array as a full slice.
n.lhs = desugararrayslice(c, c.fnret, src, n.lhs);
};
// ---- is / as validity ------------------------------------------------