wcc: route deref-call N_CALL to fn-VALUE fallback (#181)

Pre-fix the wwstage checker bailed asserttyped on the N_CALL whose
callee was N_UN TK_STAR over a *fn — selfhost/cmd/wcc/check.ww
exprtype's N_CALL arm only resolved IDENT/DOT-named callees and
early-returned nil for any other shape, leaving e.type_ unstamped
so the post-checker invariant fired. cstage worked because cexpr
recurses on the callee — TK_STAR's unop arm returns t->sub which
IS the TY_FN, no name path needed.

Fix: replace the `if (nm.len == 0) return nil` early-bail with
`if (nm.len > 0) { name-lookup }`, so non-named callees fall
through to the existing fn-VALUE fallback below (peel TPTR /
dealias to TFN / stamp the result type). Mirrors harec
check_autodereference at ref/harec/src/check.c:1566. cgen post
-#180+#185 already lowers the deref-call correctly, so lifting
the asserttyped bail is silent-SIGSEGV-safe per drew + ken.

Combined.ww regenerated for selfhost/cmd/{w6c,wwdump}/main
.combined.ww per #110 freshness gate.

Probe: test/wcc/766_star_fn_deref_call.c, 5 rows table-driven —
minimal / branched-callee / alias-chain / fn-with-args / fn
-tuple-return. Gate flip from 765: every row now gates BOTH
stages — cstage runtime, wwstage runtime, AND cs.s == ww.s byte
-id. This is the runtime coverage 765 deferred plus the symmetry
gate that proves both stages emit identical asm for the deref
-call shape. Closes the full c-cluster (#180 + #185 + #181 all 3
commits working together end-to-end).
This commit is contained in:
2026-05-28 20:01:14 +09:00
parent b86b9d76e2
commit ba044e206d
5 changed files with 392 additions and 69 deletions

View File

@@ -2378,7 +2378,6 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
nm.ptr = nil; nm.len = 0;
if (callee.kind == nkind.N_IDENT) { nm = callee.str; };
if (callee.kind == nkind.N_DOT) { nm = callee.str; };
if (nm.len == 0) { return nil; };
// #56: bare-leaf N_IDENT calls go through scopelookupprefer so
// `foo()` inside module M binds to M.foo rather than another
// module's same-leaf foo at the head of the flat scope bucket.
@@ -2408,31 +2407,41 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
// that coexists in the same scope (coexistence-equivalent of
// cstage's use_alias; see lib/ww/sym.ww + memory
// module_type_name_collision).
let s: *sym = nil;
if (callee.kind == nkind.N_IDENT) {
s = scopelookupprefer(c.cur, c.curmod, nm);
} else {
let ms: *sym = nil;
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
ms = 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; };
//
// #181: a non-named callee (N_UN TK_STAR deref of a *fn local,
// `(*f)(...)`; or any other expression-as-callee shape) has no
// leaf to resolve here — skip the SK_FN name-lookup and let the
// fn-VALUE fallback below peel TPTR / dealias to TFN. Mirrors
// harec check_autodereference at ref/harec/src/check.c:1566.
// cgen post-#180+#185 already lowers the deref-call correctly,
// so lifting the asserttyped bail is silent-SIGSEGV-safe.
if (nm.len > 0) {
let s: *sym = nil;
if (callee.kind == nkind.N_IDENT) {
s = scopelookupprefer(c.cur, c.curmod, nm);
} else {
let ms: *sym = nil;
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
ms = 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 && ms.skind == skind.SK_USE) {
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
} else {
s = scopelookup(c.cur, nm);
};
};
if (ms != nil && ms.skind == skind.SK_USE) {
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
} else {
s = scopelookup(c.cur, nm);
};
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
// fn-decl's lhs is the return-type AST node. Mirrors cstage
// cmd/wcc/check.c:984+ regular-CALL `n->type =
// build_fn_type(c, s->decl)->ret` shape.
e.type_ = tinfofornode(c, s.decl.lhs): *void;
return s.decl.lhs;
}; }; };
};
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
// fn-decl's lhs is the return-type AST node. Mirrors cstage
// cmd/wcc/check.c:984+ regular-CALL `n->type =
// build_fn_type(c, s->decl)->ret` shape.
e.type_ = tinfofornode(c, s.decl.lhs): *void;
return s.decl.lhs;
}; }; };
// A callee that is a fn-VALUE — a fn-pointer struct field
// (`w.emit(...)`), local, or param — has no free SK_FN entry, so
// the name lookup above misses. Read the result off the checked