selfhost/cmd/wcc: plumb exprtype hint + resolvewalk dispatch (A.6.0)

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.
This commit is contained in:
2026-05-21 13:58:25 +09:00
parent 17765942f9
commit 321e7ca615
3 changed files with 156 additions and 21 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);