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
|
||||
// node. Handles literals, identifiers, calls, casts, binary/unary
|
||||
// ops, indexing; returns nil for shapes we don't statically know
|
||||
// (struct field access into non-primitive types, etc).
|
||||
// ops, indexing, module-qualified refs + enum-member folds; returns
|
||||
// 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
|
||||
// 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
|
||||
@@ -8541,6 +8542,105 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||
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) {
|
||||
// success unwrap: the success-variant type of operand's
|
||||
// tagged union.
|
||||
|
||||
@@ -1367,8 +1367,9 @@ fn indexresult(c: *checker, e: *node) *node = {
|
||||
|
||||
// exprtype — best-effort type-AST inference for an expression
|
||||
// node. Handles literals, identifiers, calls, casts, binary/unary
|
||||
// ops, indexing; returns nil for shapes we don't statically know
|
||||
// (struct field access into non-primitive types, etc).
|
||||
// ops, indexing, module-qualified refs + enum-member folds; returns
|
||||
// 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
|
||||
// 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
|
||||
@@ -1609,6 +1610,105 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||
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) {
|
||||
// success unwrap: the success-variant type of operand's
|
||||
// tagged union.
|
||||
|
||||
@@ -8299,8 +8299,9 @@ fn indexresult(c: *checker, e: *node) *node = {
|
||||
|
||||
// exprtype — best-effort type-AST inference for an expression
|
||||
// node. Handles literals, identifiers, calls, casts, binary/unary
|
||||
// ops, indexing; returns nil for shapes we don't statically know
|
||||
// (struct field access into non-primitive types, etc).
|
||||
// ops, indexing, module-qualified refs + enum-member folds; returns
|
||||
// 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
|
||||
// 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
|
||||
@@ -8541,6 +8542,105 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||
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) {
|
||||
// success unwrap: the success-variant type of operand's
|
||||
// tagged union.
|
||||
|
||||
Reference in New Issue
Block a user