From 321e7ca615358a8dd2b69ff2f218af18aa2d50a3 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 21 May 2026 13:58:25 +0900 Subject: [PATCH] selfhost/cmd/wcc: plumb exprtype hint + resolvewalk dispatch (A.6.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A.6.0: extend `exprtype(c, e)` → `exprtype(c, e, hint)` and dispatch post-order on every expression-yielding node kind in resolvewalk. hint is threaded but unused by every arm; A.6.1's STRUCTLIT/ARRLIT arms consume it (harec's check_expression result_type shape per feedback_hare_frontend_reference.md). Dispatch fires existing literal + ident stamps universally; per-kind stamp coverage lands in A.6.1+. Cgen reads tnode.type_ (not expression-node type_), so byte-identity holds. Verified 132/132 incl. 995_self_rebuild. --- selfhost/cmd/w6c/main.combined.ww | 59 ++++++++++++++++++++++++---- selfhost/cmd/wcc/check.ww | 59 ++++++++++++++++++++++++---- selfhost/cmd/wwdump/main.combined.ww | 59 ++++++++++++++++++++++++---- 3 files changed, 156 insertions(+), 21 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 29393408..8e453e99 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -7316,6 +7316,10 @@ fn resolvewalk(c: *checker, n: *node) void = { // Walk only the base; the .field name is a member, not a // free identifier. if (n.lhs != nil) { resolvewalk(c, n.lhs); }; + // A.6.0: branch returns early; stamp here so the post-walk + // dispatch below sees N_DOT covered. exprtype N_DOT arm is + // added in A.6.1; for now this is a no-op nil return. + let _t: *node = exprtype(c, n, nil); return; }; @@ -7374,7 +7378,7 @@ fn resolvewalk(c: *checker, n: *node) void = { if (n.lhs.kind == nkind.N_IDENT) { let nm: str = n.lhs.str; if (streq(nm, "size") || streq(nm, "align") || streq(nm, "offset")) { - let _t: *node = exprtype(c, n); + let _t: *node = exprtype(c, n, nil); }; }; }; @@ -7398,6 +7402,37 @@ fn resolvewalk(c: *checker, n: *node) void = { scopedefine(c.cur, nm, skind.SK_VAR, nil, n); }; }; + + // A.6.0: post-order dispatch of exprtype on every expression-yielding + // node kind so n.type_ stamps fire universally — not only when reached + // through checkletassign / checkretassign / checktryprop / the size- + // align-offset fold. Mirrors cstage cmd/wcc/check.c cstmt's recursive + // cexpr (cmd/wcc/check.c:1567 N_EXPRSTMT, :1570 N_RETURN, :1584 N_IF + // cond, etc.). Plumbing-only: stamps fire from existing exprtype kind + // arms (literals + idents); per-kind stamp coverage lands in A.6.1. + // Stamps are tinfocache-backed idempotent so multi-walk via let / + // return / try entry points is safe. N_DOT is dispatched in its own + // early-return branch above; not listed here. N_LET / N_RETURN / + // N_EXPRSTMT / N_IF / N_FOR / N_FORRANGE / N_BLOCK / N_MATCH-as-stmt + // 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. + 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 || + k == nkind.N_NIL || k == nkind.N_VOIDLIT || + k == nkind.N_IDENT || k == nkind.N_BIN || + k == nkind.N_UN || k == nkind.N_CALL || + k == nkind.N_INDEX || k == nkind.N_CAST || + k == nkind.N_STRUCTLIT || k == nkind.N_ARRLIT || + k == nkind.N_ALLOC || k == nkind.N_RECV || + k == nkind.N_SLICE || k == nkind.N_SPREAD || + k == nkind.N_TUPLE || k == nkind.N_TRYPROP || + k == nkind.N_TRYUNW || k == nkind.N_TYPETEST || + k == nkind.N_TYPEASSERT || k == nkind.N_YIELD || + k == nkind.N_MATCH) { + let _t: *node = exprtype(c, n, nil); + }; }; // ---- type-level helpers (AST-level, no resolved tinfo) -------------- @@ -8143,7 +8178,15 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { // node. Handles literals, identifiers, calls, and casts; returns // nil for shapes we don't statically know (binary ops, struct // field access into non-primitive types, etc). -fn exprtype(c: *checker, e: *node) *node = { +// `hint`: optional declared-type AST passed by the caller (let +// target, assign target). nil = "no hint, derive from self". Threaded +// for use by A.6.1's STRUCTLIT/ARRLIT arms which can't self-type and +// need the enclosing declared type to resolve. Ignored by every arm +// in A.6.0; the param is plumbed here so the per-kind work that +// follows doesn't ripple a fresh signature change. Mirrors harec's +// `check_expression(..., result_type, ...)` per +// `feedback_hare_frontend_reference.md`. +fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (e == nil) { return nil; }; let k: nkind = e.kind; // #61 audit §1.8 — A.2 widens A.1's single N_INTLIT population to @@ -8253,7 +8296,7 @@ fn exprtype(c: *checker, e: *node) *node = { }; // Value form: `alloc(value)`. if (e.list.next == nil) { - let argt: *node = exprtype(c, e.list); + let argt: *node = exprtype(c, e.list, nil); let ptr: *node = newnode(nkind.N_TPTR, "", 0, 0); ptr.lhs = argt; let nome: *node = mktname(c, "nomem"); @@ -8344,7 +8387,7 @@ fn exprtype(c: *checker, e: *node) *node = { if (k == nkind.N_TRYPROP) { // success unwrap: the success-variant type of operand's // tagged union. - let opt: *node = exprtype(c, e.lhs); + let opt: *node = exprtype(c, e.lhs, nil); let ou: *node = resolvealias(c, unwrapbang(opt)); if (ou == nil) { return nil; }; if (ou.kind != nkind.N_TTAGGED) { return nil; }; @@ -8364,7 +8407,7 @@ fn exprtype(c: *checker, e: *node) *node = { // `e!` abort-on-error unwrap; success variant is what the // receiver gets, identical to `?` shape modulo control flow. // #31: required so `let p: *T = alloc(v)!;` resolves to *T. - let opt: *node = exprtype(c, e.lhs); + let opt: *node = exprtype(c, e.lhs, nil); let ou: *node = resolvealias(c, unwrapbang(opt)); if (ou == nil) { return nil; }; if (ou.kind != nkind.N_TTAGGED) { return nil; }; @@ -8736,7 +8779,9 @@ fn checkletassign(c: *checker, n: *node) void = { if (n == nil) { return; }; if (n.lhs == nil) { return; }; // no declared type, nothing to check if (n.rhs == nil) { return; }; // no init - let src: *node = exprtype(c, n.rhs); + // hint = nil for A.6.0; A.6.1 will pass n.lhs once STRUCTLIT/ARRLIT + // arms consume it. Plumbing-only at this point. + let src: *node = exprtype(c, n.rhs, nil); if (src == nil) { return; }; // can't infer // #45: alloc([], n) defers element type to the let-init context // (Hare-style). exprtype's alloc-slice branch synthesizes @@ -8805,7 +8850,7 @@ fn checkretassign(c: *checker, n: *node) void = { return; }; if (c.fnret == nil) { return; }; - let src: *node = exprtype(c, n.lhs); + let src: *node = exprtype(c, n.lhs, nil); if (src == nil) { return; }; let conf: bool = false; let ok: bool = isassignable(c, c.fnret, src, &conf); diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index be956b10..36cde14b 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -384,6 +384,10 @@ fn resolvewalk(c: *checker, n: *node) void = { // Walk only the base; the .field name is a member, not a // free identifier. if (n.lhs != nil) { resolvewalk(c, n.lhs); }; + // A.6.0: branch returns early; stamp here so the post-walk + // dispatch below sees N_DOT covered. exprtype N_DOT arm is + // added in A.6.1; for now this is a no-op nil return. + let _t: *node = exprtype(c, n, nil); return; }; @@ -442,7 +446,7 @@ fn resolvewalk(c: *checker, n: *node) void = { if (n.lhs.kind == nkind.N_IDENT) { let nm: str = n.lhs.str; if (streq(nm, "size") || streq(nm, "align") || streq(nm, "offset")) { - let _t: *node = exprtype(c, n); + let _t: *node = exprtype(c, n, nil); }; }; }; @@ -466,6 +470,37 @@ fn resolvewalk(c: *checker, n: *node) void = { scopedefine(c.cur, nm, skind.SK_VAR, nil, n); }; }; + + // A.6.0: post-order dispatch of exprtype on every expression-yielding + // node kind so n.type_ stamps fire universally — not only when reached + // through checkletassign / checkretassign / checktryprop / the size- + // align-offset fold. Mirrors cstage cmd/wcc/check.c cstmt's recursive + // cexpr (cmd/wcc/check.c:1567 N_EXPRSTMT, :1570 N_RETURN, :1584 N_IF + // cond, etc.). Plumbing-only: stamps fire from existing exprtype kind + // arms (literals + idents); per-kind stamp coverage lands in A.6.1. + // Stamps are tinfocache-backed idempotent so multi-walk via let / + // return / try entry points is safe. N_DOT is dispatched in its own + // early-return branch above; not listed here. N_LET / N_RETURN / + // N_EXPRSTMT / N_IF / N_FOR / N_FORRANGE / N_BLOCK / N_MATCH-as-stmt + // 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. + 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 || + k == nkind.N_NIL || k == nkind.N_VOIDLIT || + k == nkind.N_IDENT || k == nkind.N_BIN || + k == nkind.N_UN || k == nkind.N_CALL || + k == nkind.N_INDEX || k == nkind.N_CAST || + k == nkind.N_STRUCTLIT || k == nkind.N_ARRLIT || + k == nkind.N_ALLOC || k == nkind.N_RECV || + k == nkind.N_SLICE || k == nkind.N_SPREAD || + k == nkind.N_TUPLE || k == nkind.N_TRYPROP || + k == nkind.N_TRYUNW || k == nkind.N_TYPETEST || + k == nkind.N_TYPEASSERT || k == nkind.N_YIELD || + k == nkind.N_MATCH) { + let _t: *node = exprtype(c, n, nil); + }; }; // ---- type-level helpers (AST-level, no resolved tinfo) -------------- @@ -1211,7 +1246,15 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { // node. Handles literals, identifiers, calls, and casts; returns // nil for shapes we don't statically know (binary ops, struct // field access into non-primitive types, etc). -fn exprtype(c: *checker, e: *node) *node = { +// `hint`: optional declared-type AST passed by the caller (let +// target, assign target). nil = "no hint, derive from self". Threaded +// for use by A.6.1's STRUCTLIT/ARRLIT arms which can't self-type and +// need the enclosing declared type to resolve. Ignored by every arm +// in A.6.0; the param is plumbed here so the per-kind work that +// follows doesn't ripple a fresh signature change. Mirrors harec's +// `check_expression(..., result_type, ...)` per +// `feedback_hare_frontend_reference.md`. +fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (e == nil) { return nil; }; let k: nkind = e.kind; // #61 audit §1.8 — A.2 widens A.1's single N_INTLIT population to @@ -1321,7 +1364,7 @@ fn exprtype(c: *checker, e: *node) *node = { }; // Value form: `alloc(value)`. if (e.list.next == nil) { - let argt: *node = exprtype(c, e.list); + let argt: *node = exprtype(c, e.list, nil); let ptr: *node = newnode(nkind.N_TPTR, "", 0, 0); ptr.lhs = argt; let nome: *node = mktname(c, "nomem"); @@ -1412,7 +1455,7 @@ fn exprtype(c: *checker, e: *node) *node = { if (k == nkind.N_TRYPROP) { // success unwrap: the success-variant type of operand's // tagged union. - let opt: *node = exprtype(c, e.lhs); + let opt: *node = exprtype(c, e.lhs, nil); let ou: *node = resolvealias(c, unwrapbang(opt)); if (ou == nil) { return nil; }; if (ou.kind != nkind.N_TTAGGED) { return nil; }; @@ -1432,7 +1475,7 @@ fn exprtype(c: *checker, e: *node) *node = { // `e!` abort-on-error unwrap; success variant is what the // receiver gets, identical to `?` shape modulo control flow. // #31: required so `let p: *T = alloc(v)!;` resolves to *T. - let opt: *node = exprtype(c, e.lhs); + let opt: *node = exprtype(c, e.lhs, nil); let ou: *node = resolvealias(c, unwrapbang(opt)); if (ou == nil) { return nil; }; if (ou.kind != nkind.N_TTAGGED) { return nil; }; @@ -1804,7 +1847,9 @@ fn checkletassign(c: *checker, n: *node) void = { if (n == nil) { return; }; if (n.lhs == nil) { return; }; // no declared type, nothing to check if (n.rhs == nil) { return; }; // no init - let src: *node = exprtype(c, n.rhs); + // hint = nil for A.6.0; A.6.1 will pass n.lhs once STRUCTLIT/ARRLIT + // arms consume it. Plumbing-only at this point. + let src: *node = exprtype(c, n.rhs, nil); if (src == nil) { return; }; // can't infer // #45: alloc([], n) defers element type to the let-init context // (Hare-style). exprtype's alloc-slice branch synthesizes @@ -1873,7 +1918,7 @@ fn checkretassign(c: *checker, n: *node) void = { return; }; if (c.fnret == nil) { return; }; - let src: *node = exprtype(c, n.lhs); + let src: *node = exprtype(c, n.lhs, nil); if (src == nil) { return; }; let conf: bool = false; let ok: bool = isassignable(c, c.fnret, src, &conf); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index dd01c864..d20c1ab1 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -7316,6 +7316,10 @@ fn resolvewalk(c: *checker, n: *node) void = { // Walk only the base; the .field name is a member, not a // free identifier. if (n.lhs != nil) { resolvewalk(c, n.lhs); }; + // A.6.0: branch returns early; stamp here so the post-walk + // dispatch below sees N_DOT covered. exprtype N_DOT arm is + // added in A.6.1; for now this is a no-op nil return. + let _t: *node = exprtype(c, n, nil); return; }; @@ -7374,7 +7378,7 @@ fn resolvewalk(c: *checker, n: *node) void = { if (n.lhs.kind == nkind.N_IDENT) { let nm: str = n.lhs.str; if (streq(nm, "size") || streq(nm, "align") || streq(nm, "offset")) { - let _t: *node = exprtype(c, n); + let _t: *node = exprtype(c, n, nil); }; }; }; @@ -7398,6 +7402,37 @@ fn resolvewalk(c: *checker, n: *node) void = { scopedefine(c.cur, nm, skind.SK_VAR, nil, n); }; }; + + // A.6.0: post-order dispatch of exprtype on every expression-yielding + // node kind so n.type_ stamps fire universally — not only when reached + // through checkletassign / checkretassign / checktryprop / the size- + // align-offset fold. Mirrors cstage cmd/wcc/check.c cstmt's recursive + // cexpr (cmd/wcc/check.c:1567 N_EXPRSTMT, :1570 N_RETURN, :1584 N_IF + // cond, etc.). Plumbing-only: stamps fire from existing exprtype kind + // arms (literals + idents); per-kind stamp coverage lands in A.6.1. + // Stamps are tinfocache-backed idempotent so multi-walk via let / + // return / try entry points is safe. N_DOT is dispatched in its own + // early-return branch above; not listed here. N_LET / N_RETURN / + // N_EXPRSTMT / N_IF / N_FOR / N_FORRANGE / N_BLOCK / N_MATCH-as-stmt + // 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. + 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 || + k == nkind.N_NIL || k == nkind.N_VOIDLIT || + k == nkind.N_IDENT || k == nkind.N_BIN || + k == nkind.N_UN || k == nkind.N_CALL || + k == nkind.N_INDEX || k == nkind.N_CAST || + k == nkind.N_STRUCTLIT || k == nkind.N_ARRLIT || + k == nkind.N_ALLOC || k == nkind.N_RECV || + k == nkind.N_SLICE || k == nkind.N_SPREAD || + k == nkind.N_TUPLE || k == nkind.N_TRYPROP || + k == nkind.N_TRYUNW || k == nkind.N_TYPETEST || + k == nkind.N_TYPEASSERT || k == nkind.N_YIELD || + k == nkind.N_MATCH) { + let _t: *node = exprtype(c, n, nil); + }; }; // ---- type-level helpers (AST-level, no resolved tinfo) -------------- @@ -8143,7 +8178,15 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { // node. Handles literals, identifiers, calls, and casts; returns // nil for shapes we don't statically know (binary ops, struct // field access into non-primitive types, etc). -fn exprtype(c: *checker, e: *node) *node = { +// `hint`: optional declared-type AST passed by the caller (let +// target, assign target). nil = "no hint, derive from self". Threaded +// for use by A.6.1's STRUCTLIT/ARRLIT arms which can't self-type and +// need the enclosing declared type to resolve. Ignored by every arm +// in A.6.0; the param is plumbed here so the per-kind work that +// follows doesn't ripple a fresh signature change. Mirrors harec's +// `check_expression(..., result_type, ...)` per +// `feedback_hare_frontend_reference.md`. +fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (e == nil) { return nil; }; let k: nkind = e.kind; // #61 audit §1.8 — A.2 widens A.1's single N_INTLIT population to @@ -8253,7 +8296,7 @@ fn exprtype(c: *checker, e: *node) *node = { }; // Value form: `alloc(value)`. if (e.list.next == nil) { - let argt: *node = exprtype(c, e.list); + let argt: *node = exprtype(c, e.list, nil); let ptr: *node = newnode(nkind.N_TPTR, "", 0, 0); ptr.lhs = argt; let nome: *node = mktname(c, "nomem"); @@ -8344,7 +8387,7 @@ fn exprtype(c: *checker, e: *node) *node = { if (k == nkind.N_TRYPROP) { // success unwrap: the success-variant type of operand's // tagged union. - let opt: *node = exprtype(c, e.lhs); + let opt: *node = exprtype(c, e.lhs, nil); let ou: *node = resolvealias(c, unwrapbang(opt)); if (ou == nil) { return nil; }; if (ou.kind != nkind.N_TTAGGED) { return nil; }; @@ -8364,7 +8407,7 @@ fn exprtype(c: *checker, e: *node) *node = { // `e!` abort-on-error unwrap; success variant is what the // receiver gets, identical to `?` shape modulo control flow. // #31: required so `let p: *T = alloc(v)!;` resolves to *T. - let opt: *node = exprtype(c, e.lhs); + let opt: *node = exprtype(c, e.lhs, nil); let ou: *node = resolvealias(c, unwrapbang(opt)); if (ou == nil) { return nil; }; if (ou.kind != nkind.N_TTAGGED) { return nil; }; @@ -8736,7 +8779,9 @@ fn checkletassign(c: *checker, n: *node) void = { if (n == nil) { return; }; if (n.lhs == nil) { return; }; // no declared type, nothing to check if (n.rhs == nil) { return; }; // no init - let src: *node = exprtype(c, n.rhs); + // hint = nil for A.6.0; A.6.1 will pass n.lhs once STRUCTLIT/ARRLIT + // arms consume it. Plumbing-only at this point. + let src: *node = exprtype(c, n.rhs, nil); if (src == nil) { return; }; // can't infer // #45: alloc([], n) defers element type to the let-init context // (Hare-style). exprtype's alloc-slice branch synthesizes @@ -8805,7 +8850,7 @@ fn checkretassign(c: *checker, n: *node) void = { return; }; if (c.fnret == nil) { return; }; - let src: *node = exprtype(c, n.lhs); + let src: *node = exprtype(c, n.lhs, nil); if (src == nil) { return; }; let conf: bool = false; let ok: bool = isassignable(c, c.fnret, src, &conf);