selfhost/cmd/wcc: fold N_DOT module/enum in exprtype (A.6.1.5a)
Port cstage cmd/wcc/check.c:740-832 fold cases to exprtype's new
N_DOT arm. Two paths land:
- Module-qualified ref (`pkg.x`): SK_USE leaf via
scopelookupinmodule, type returned from the resolved sym's
decl.lhs. Mirrors cstage L749-775.
- Enum member fold to N_INTLIT: bare `EnumT.MEMBER` (cstage
L780-803) and outer `pkg.EnumT.MEMBER` where the inner N_DOT
folded via case 1 (cstage L805-832, including TPTR peel at L808).
foldtointlit rewrites kind/uval/str/lhs/rhs; type_ stamps from
the enum body.
Stamp surface (struct field + pseudo-field .len/.cap/.ptr) lands
in A.6.1.5b. #56 mod.fn() parser-rep stays parked.
Two documented divergences from cstage:
- No use_alias gate — wwstage uses sym.mod disambiguation per
installdecl L195-207; SK_USE alone suffices.
- Cstage errors on unknown enum member; wwstage falls through
to nil under the lenient-check policy at scruttype L656.
Check-side enum eval is literal-only (N_INTLIT + nil-auto-
increment); cgen.ww's enumevalmember has the full const-folder
for cgen-side. Every selfhost+lib enum uses explicit literals
today (audited 2026-05-21). Tracked as task #7.
Phase 1 A.6 step 5a of ~8 — bisect-clean split per Hare's AST
access_identifier vs access_field axis
(ref/hare/hare/ast/expr.ha:7-38).
Verified 132/132 incl. 995_self_rebuild byte-identity.
This commit is contained in:
@@ -8299,8 +8299,9 @@ fn indexresult(c: *checker, e: *node) *node = {
|
|||||||
|
|
||||||
// exprtype — best-effort type-AST inference for an expression
|
// exprtype — best-effort type-AST inference for an expression
|
||||||
// node. Handles literals, identifiers, calls, casts, binary/unary
|
// node. Handles literals, identifiers, calls, casts, binary/unary
|
||||||
// ops, indexing; returns nil for shapes we don't statically know
|
// ops, indexing, module-qualified refs + enum-member folds; returns
|
||||||
// (struct field access into non-primitive types, etc).
|
// nil for shapes we don't statically know (struct field access into
|
||||||
|
// non-primitive types, etc).
|
||||||
// `hint`: optional declared-type AST passed by the caller (let
|
// `hint`: optional declared-type AST passed by the caller (let
|
||||||
// target, assign target). nil = "no hint, derive from self". Threaded
|
// 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
|
// for use by A.6.1's STRUCTLIT/ARRLIT arms which can't self-type and
|
||||||
@@ -8541,6 +8542,105 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
|||||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||||
return s.decl.lhs;
|
return s.decl.lhs;
|
||||||
};
|
};
|
||||||
|
if (k == nkind.N_DOT) {
|
||||||
|
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
||||||
|
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
||||||
|
// in A.6.1.5b. Wwstage has no use_alias (sym.mod disambiguates
|
||||||
|
// — see installdecl docstring at L195-207); SK_USE alone gates
|
||||||
|
// case 1. Enum-member fold here is best-effort: cgen.ww's
|
||||||
|
// enumevalmember covers N_INTLIT + N_IDENT-backref + N_BIN/N_UN,
|
||||||
|
// but every selfhost+lib enum body uses explicit integer
|
||||||
|
// literals (audited 2026-05-21), so the check-side eval stays
|
||||||
|
// N_INTLIT + auto-increment to avoid duplicating cgen's full
|
||||||
|
// const-folder. A non-literal member's lhs makes the auto-
|
||||||
|
// increment prev-tracking unsafe (we'd fold subsequent members
|
||||||
|
// off a wrong base), so we bail rather than guess — cgen's own
|
||||||
|
// N_DOT enum path still resolves the constant.
|
||||||
|
let lhsn: *node = e.lhs;
|
||||||
|
if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) {
|
||||||
|
let ms: *sym = scopelookupprefer(c.cur, c.curmod, lhsn.str);
|
||||||
|
if (ms != nil) {
|
||||||
|
// Fold case 1: module-qualified ref. Mirror cstage
|
||||||
|
// check.c:749-775. cstage returns ty_err on SK_USE
|
||||||
|
// with missing leaf (extern decl); wwstage falls
|
||||||
|
// through to outer case — cgen has its own module-
|
||||||
|
// qualified resolution and the lenient checker policy
|
||||||
|
// keeps the silent miss documented at scruttype L656.
|
||||||
|
if (ms.skind == skind.SK_USE) {
|
||||||
|
let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str);
|
||||||
|
if (fs != nil) { if (fs.decl != nil) {
|
||||||
|
let tn: *node = fs.decl.lhs;
|
||||||
|
if (tn != nil) {
|
||||||
|
e.type_ = tinfofornode(c, tn): *void;
|
||||||
|
return tn;
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
};
|
||||||
|
// Fold case 2 inner: bare `EnumT.MEMBER` where EnumT
|
||||||
|
// is an SK_TYPE in the flat scope. Mirror cstage
|
||||||
|
// check.c:780-803.
|
||||||
|
if (ms.skind == skind.SK_TYPE) { if (ms.decl != nil) {
|
||||||
|
let body: *node = ms.decl.lhs;
|
||||||
|
let ub: *node = resolvealias(c, unwrapbang(body));
|
||||||
|
if (ub != nil) { if (ub.kind == nkind.N_TENUM) {
|
||||||
|
let prev: u64 = (-1i64): u64;
|
||||||
|
let m: *node = ub.list;
|
||||||
|
for (m != nil) {
|
||||||
|
let val: u64 = 0u64;
|
||||||
|
if (m.lhs == nil) {
|
||||||
|
val = prev + 1u64;
|
||||||
|
} else { if (m.lhs.kind == nkind.N_INTLIT) {
|
||||||
|
val = m.lhs.uval;
|
||||||
|
} else {
|
||||||
|
return nil;
|
||||||
|
}; };
|
||||||
|
prev = val;
|
||||||
|
if (streq(m.str, e.str)) {
|
||||||
|
foldtointlit(c, e, val: i64);
|
||||||
|
e.type_ = tinfofornode(c, body): *void;
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
m = m.next;
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
}; };
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
// Fold case 2 outer: base resolves to enum, e.g.
|
||||||
|
// `pkg.EnumT.MEMBER` where the inner N_DOT (pkg.EnumT) folded
|
||||||
|
// via case 1 above to the enum body. Mirror cstage
|
||||||
|
// check.c:805-832. Peel one TPTR for `(*EnumT).MEMBER` (rare
|
||||||
|
// but cstage handles it at L808).
|
||||||
|
let basetn: *node = exprtype(c, lhsn, nil);
|
||||||
|
if (basetn != nil) {
|
||||||
|
let bu: *node = resolvealias(c, unwrapbang(basetn));
|
||||||
|
if (bu != nil) { if (bu.kind == nkind.N_TPTR) {
|
||||||
|
bu = resolvealias(c, unwrapbang(bu.lhs));
|
||||||
|
}; };
|
||||||
|
if (bu != nil) { if (bu.kind == nkind.N_TENUM) {
|
||||||
|
let prev: u64 = (-1i64): u64;
|
||||||
|
let m: *node = bu.list;
|
||||||
|
for (m != nil) {
|
||||||
|
let val: u64 = 0u64;
|
||||||
|
if (m.lhs == nil) {
|
||||||
|
val = prev + 1u64;
|
||||||
|
} else { if (m.lhs.kind == nkind.N_INTLIT) {
|
||||||
|
val = m.lhs.uval;
|
||||||
|
} else {
|
||||||
|
return nil;
|
||||||
|
}; };
|
||||||
|
prev = val;
|
||||||
|
if (streq(m.str, e.str)) {
|
||||||
|
foldtointlit(c, e, val: i64);
|
||||||
|
e.type_ = tinfofornode(c, basetn): *void;
|
||||||
|
return basetn;
|
||||||
|
};
|
||||||
|
m = m.next;
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
if (k == nkind.N_TRYPROP) {
|
if (k == nkind.N_TRYPROP) {
|
||||||
// success unwrap: the success-variant type of operand's
|
// success unwrap: the success-variant type of operand's
|
||||||
// tagged union.
|
// tagged union.
|
||||||
|
|||||||
@@ -1367,8 +1367,9 @@ fn indexresult(c: *checker, e: *node) *node = {
|
|||||||
|
|
||||||
// exprtype — best-effort type-AST inference for an expression
|
// exprtype — best-effort type-AST inference for an expression
|
||||||
// node. Handles literals, identifiers, calls, casts, binary/unary
|
// node. Handles literals, identifiers, calls, casts, binary/unary
|
||||||
// ops, indexing; returns nil for shapes we don't statically know
|
// ops, indexing, module-qualified refs + enum-member folds; returns
|
||||||
// (struct field access into non-primitive types, etc).
|
// nil for shapes we don't statically know (struct field access into
|
||||||
|
// non-primitive types, etc).
|
||||||
// `hint`: optional declared-type AST passed by the caller (let
|
// `hint`: optional declared-type AST passed by the caller (let
|
||||||
// target, assign target). nil = "no hint, derive from self". Threaded
|
// 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
|
// for use by A.6.1's STRUCTLIT/ARRLIT arms which can't self-type and
|
||||||
@@ -1609,6 +1610,105 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
|||||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||||
return s.decl.lhs;
|
return s.decl.lhs;
|
||||||
};
|
};
|
||||||
|
if (k == nkind.N_DOT) {
|
||||||
|
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
||||||
|
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
||||||
|
// in A.6.1.5b. Wwstage has no use_alias (sym.mod disambiguates
|
||||||
|
// — see installdecl docstring at L195-207); SK_USE alone gates
|
||||||
|
// case 1. Enum-member fold here is best-effort: cgen.ww's
|
||||||
|
// enumevalmember covers N_INTLIT + N_IDENT-backref + N_BIN/N_UN,
|
||||||
|
// but every selfhost+lib enum body uses explicit integer
|
||||||
|
// literals (audited 2026-05-21), so the check-side eval stays
|
||||||
|
// N_INTLIT + auto-increment to avoid duplicating cgen's full
|
||||||
|
// const-folder. A non-literal member's lhs makes the auto-
|
||||||
|
// increment prev-tracking unsafe (we'd fold subsequent members
|
||||||
|
// off a wrong base), so we bail rather than guess — cgen's own
|
||||||
|
// N_DOT enum path still resolves the constant.
|
||||||
|
let lhsn: *node = e.lhs;
|
||||||
|
if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) {
|
||||||
|
let ms: *sym = scopelookupprefer(c.cur, c.curmod, lhsn.str);
|
||||||
|
if (ms != nil) {
|
||||||
|
// Fold case 1: module-qualified ref. Mirror cstage
|
||||||
|
// check.c:749-775. cstage returns ty_err on SK_USE
|
||||||
|
// with missing leaf (extern decl); wwstage falls
|
||||||
|
// through to outer case — cgen has its own module-
|
||||||
|
// qualified resolution and the lenient checker policy
|
||||||
|
// keeps the silent miss documented at scruttype L656.
|
||||||
|
if (ms.skind == skind.SK_USE) {
|
||||||
|
let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str);
|
||||||
|
if (fs != nil) { if (fs.decl != nil) {
|
||||||
|
let tn: *node = fs.decl.lhs;
|
||||||
|
if (tn != nil) {
|
||||||
|
e.type_ = tinfofornode(c, tn): *void;
|
||||||
|
return tn;
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
};
|
||||||
|
// Fold case 2 inner: bare `EnumT.MEMBER` where EnumT
|
||||||
|
// is an SK_TYPE in the flat scope. Mirror cstage
|
||||||
|
// check.c:780-803.
|
||||||
|
if (ms.skind == skind.SK_TYPE) { if (ms.decl != nil) {
|
||||||
|
let body: *node = ms.decl.lhs;
|
||||||
|
let ub: *node = resolvealias(c, unwrapbang(body));
|
||||||
|
if (ub != nil) { if (ub.kind == nkind.N_TENUM) {
|
||||||
|
let prev: u64 = (-1i64): u64;
|
||||||
|
let m: *node = ub.list;
|
||||||
|
for (m != nil) {
|
||||||
|
let val: u64 = 0u64;
|
||||||
|
if (m.lhs == nil) {
|
||||||
|
val = prev + 1u64;
|
||||||
|
} else { if (m.lhs.kind == nkind.N_INTLIT) {
|
||||||
|
val = m.lhs.uval;
|
||||||
|
} else {
|
||||||
|
return nil;
|
||||||
|
}; };
|
||||||
|
prev = val;
|
||||||
|
if (streq(m.str, e.str)) {
|
||||||
|
foldtointlit(c, e, val: i64);
|
||||||
|
e.type_ = tinfofornode(c, body): *void;
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
m = m.next;
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
}; };
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
// Fold case 2 outer: base resolves to enum, e.g.
|
||||||
|
// `pkg.EnumT.MEMBER` where the inner N_DOT (pkg.EnumT) folded
|
||||||
|
// via case 1 above to the enum body. Mirror cstage
|
||||||
|
// check.c:805-832. Peel one TPTR for `(*EnumT).MEMBER` (rare
|
||||||
|
// but cstage handles it at L808).
|
||||||
|
let basetn: *node = exprtype(c, lhsn, nil);
|
||||||
|
if (basetn != nil) {
|
||||||
|
let bu: *node = resolvealias(c, unwrapbang(basetn));
|
||||||
|
if (bu != nil) { if (bu.kind == nkind.N_TPTR) {
|
||||||
|
bu = resolvealias(c, unwrapbang(bu.lhs));
|
||||||
|
}; };
|
||||||
|
if (bu != nil) { if (bu.kind == nkind.N_TENUM) {
|
||||||
|
let prev: u64 = (-1i64): u64;
|
||||||
|
let m: *node = bu.list;
|
||||||
|
for (m != nil) {
|
||||||
|
let val: u64 = 0u64;
|
||||||
|
if (m.lhs == nil) {
|
||||||
|
val = prev + 1u64;
|
||||||
|
} else { if (m.lhs.kind == nkind.N_INTLIT) {
|
||||||
|
val = m.lhs.uval;
|
||||||
|
} else {
|
||||||
|
return nil;
|
||||||
|
}; };
|
||||||
|
prev = val;
|
||||||
|
if (streq(m.str, e.str)) {
|
||||||
|
foldtointlit(c, e, val: i64);
|
||||||
|
e.type_ = tinfofornode(c, basetn): *void;
|
||||||
|
return basetn;
|
||||||
|
};
|
||||||
|
m = m.next;
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
if (k == nkind.N_TRYPROP) {
|
if (k == nkind.N_TRYPROP) {
|
||||||
// success unwrap: the success-variant type of operand's
|
// success unwrap: the success-variant type of operand's
|
||||||
// tagged union.
|
// tagged union.
|
||||||
|
|||||||
@@ -8299,8 +8299,9 @@ fn indexresult(c: *checker, e: *node) *node = {
|
|||||||
|
|
||||||
// exprtype — best-effort type-AST inference for an expression
|
// exprtype — best-effort type-AST inference for an expression
|
||||||
// node. Handles literals, identifiers, calls, casts, binary/unary
|
// node. Handles literals, identifiers, calls, casts, binary/unary
|
||||||
// ops, indexing; returns nil for shapes we don't statically know
|
// ops, indexing, module-qualified refs + enum-member folds; returns
|
||||||
// (struct field access into non-primitive types, etc).
|
// nil for shapes we don't statically know (struct field access into
|
||||||
|
// non-primitive types, etc).
|
||||||
// `hint`: optional declared-type AST passed by the caller (let
|
// `hint`: optional declared-type AST passed by the caller (let
|
||||||
// target, assign target). nil = "no hint, derive from self". Threaded
|
// 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
|
// for use by A.6.1's STRUCTLIT/ARRLIT arms which can't self-type and
|
||||||
@@ -8541,6 +8542,105 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
|||||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||||
return s.decl.lhs;
|
return s.decl.lhs;
|
||||||
};
|
};
|
||||||
|
if (k == nkind.N_DOT) {
|
||||||
|
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
||||||
|
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
||||||
|
// in A.6.1.5b. Wwstage has no use_alias (sym.mod disambiguates
|
||||||
|
// — see installdecl docstring at L195-207); SK_USE alone gates
|
||||||
|
// case 1. Enum-member fold here is best-effort: cgen.ww's
|
||||||
|
// enumevalmember covers N_INTLIT + N_IDENT-backref + N_BIN/N_UN,
|
||||||
|
// but every selfhost+lib enum body uses explicit integer
|
||||||
|
// literals (audited 2026-05-21), so the check-side eval stays
|
||||||
|
// N_INTLIT + auto-increment to avoid duplicating cgen's full
|
||||||
|
// const-folder. A non-literal member's lhs makes the auto-
|
||||||
|
// increment prev-tracking unsafe (we'd fold subsequent members
|
||||||
|
// off a wrong base), so we bail rather than guess — cgen's own
|
||||||
|
// N_DOT enum path still resolves the constant.
|
||||||
|
let lhsn: *node = e.lhs;
|
||||||
|
if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) {
|
||||||
|
let ms: *sym = scopelookupprefer(c.cur, c.curmod, lhsn.str);
|
||||||
|
if (ms != nil) {
|
||||||
|
// Fold case 1: module-qualified ref. Mirror cstage
|
||||||
|
// check.c:749-775. cstage returns ty_err on SK_USE
|
||||||
|
// with missing leaf (extern decl); wwstage falls
|
||||||
|
// through to outer case — cgen has its own module-
|
||||||
|
// qualified resolution and the lenient checker policy
|
||||||
|
// keeps the silent miss documented at scruttype L656.
|
||||||
|
if (ms.skind == skind.SK_USE) {
|
||||||
|
let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str);
|
||||||
|
if (fs != nil) { if (fs.decl != nil) {
|
||||||
|
let tn: *node = fs.decl.lhs;
|
||||||
|
if (tn != nil) {
|
||||||
|
e.type_ = tinfofornode(c, tn): *void;
|
||||||
|
return tn;
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
};
|
||||||
|
// Fold case 2 inner: bare `EnumT.MEMBER` where EnumT
|
||||||
|
// is an SK_TYPE in the flat scope. Mirror cstage
|
||||||
|
// check.c:780-803.
|
||||||
|
if (ms.skind == skind.SK_TYPE) { if (ms.decl != nil) {
|
||||||
|
let body: *node = ms.decl.lhs;
|
||||||
|
let ub: *node = resolvealias(c, unwrapbang(body));
|
||||||
|
if (ub != nil) { if (ub.kind == nkind.N_TENUM) {
|
||||||
|
let prev: u64 = (-1i64): u64;
|
||||||
|
let m: *node = ub.list;
|
||||||
|
for (m != nil) {
|
||||||
|
let val: u64 = 0u64;
|
||||||
|
if (m.lhs == nil) {
|
||||||
|
val = prev + 1u64;
|
||||||
|
} else { if (m.lhs.kind == nkind.N_INTLIT) {
|
||||||
|
val = m.lhs.uval;
|
||||||
|
} else {
|
||||||
|
return nil;
|
||||||
|
}; };
|
||||||
|
prev = val;
|
||||||
|
if (streq(m.str, e.str)) {
|
||||||
|
foldtointlit(c, e, val: i64);
|
||||||
|
e.type_ = tinfofornode(c, body): *void;
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
m = m.next;
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
}; };
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
// Fold case 2 outer: base resolves to enum, e.g.
|
||||||
|
// `pkg.EnumT.MEMBER` where the inner N_DOT (pkg.EnumT) folded
|
||||||
|
// via case 1 above to the enum body. Mirror cstage
|
||||||
|
// check.c:805-832. Peel one TPTR for `(*EnumT).MEMBER` (rare
|
||||||
|
// but cstage handles it at L808).
|
||||||
|
let basetn: *node = exprtype(c, lhsn, nil);
|
||||||
|
if (basetn != nil) {
|
||||||
|
let bu: *node = resolvealias(c, unwrapbang(basetn));
|
||||||
|
if (bu != nil) { if (bu.kind == nkind.N_TPTR) {
|
||||||
|
bu = resolvealias(c, unwrapbang(bu.lhs));
|
||||||
|
}; };
|
||||||
|
if (bu != nil) { if (bu.kind == nkind.N_TENUM) {
|
||||||
|
let prev: u64 = (-1i64): u64;
|
||||||
|
let m: *node = bu.list;
|
||||||
|
for (m != nil) {
|
||||||
|
let val: u64 = 0u64;
|
||||||
|
if (m.lhs == nil) {
|
||||||
|
val = prev + 1u64;
|
||||||
|
} else { if (m.lhs.kind == nkind.N_INTLIT) {
|
||||||
|
val = m.lhs.uval;
|
||||||
|
} else {
|
||||||
|
return nil;
|
||||||
|
}; };
|
||||||
|
prev = val;
|
||||||
|
if (streq(m.str, e.str)) {
|
||||||
|
foldtointlit(c, e, val: i64);
|
||||||
|
e.type_ = tinfofornode(c, basetn): *void;
|
||||||
|
return basetn;
|
||||||
|
};
|
||||||
|
m = m.next;
|
||||||
|
};
|
||||||
|
}; };
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
if (k == nkind.N_TRYPROP) {
|
if (k == nkind.N_TRYPROP) {
|
||||||
// success unwrap: the success-variant type of operand's
|
// success unwrap: the success-variant type of operand's
|
||||||
// tagged union.
|
// tagged union.
|
||||||
|
|||||||
Reference in New Issue
Block a user