diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index ce14413b..35f626a8 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -12118,7 +12118,8 @@ fn indexresult(c: *checker, e: *node) *node = { // asserttyped pass at L2871 enforces the invariant on every // dispatched node post-checker, with gates for the residual // inherent-IDENT bails (SK_USE, pseudo-builtin sym.decl==nil, -// N_DOT-LHS syntactic position) until #19 retires the bail shape. +// N_DOT-LHS syntactic position, EXPR_ASSERT-family abort/assert) until +// #19 retires the bail shape. fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (e == nil) { return nil; }; let k: nkind = e.kind; @@ -13586,13 +13587,33 @@ fn resolvefnbody(c: *checker, fnnode: *node) void = { c.cur = outer; }; +// isassertfam — `abort`/`assert` are language builtins, not value +// calls. harec models each as a dedicated EXPR_ASSERT whose result is +// builtin void (assert) or never (bare abort) at +// ref/harec/src/check.c:877,893; there is no callee ident, so nothing +// is left untyped. wwstage parses them as an N_CALL over a bare +// N_IDENT callee that binds to no decl, so both the call and its +// callee carry no type by design. Recognized exactly as the cstage +// builtin intercept (cmd/wcc/check.c:1314,1328): the reserved name +// with no shadowing user symbol. +fn isassertfam(c: *checker, id: *node) bool = { + if (id == nil) { return false; }; + if (id.kind != nkind.N_IDENT) { return false; }; + if (!streq(id.str, "abort") && !streq(id.str, "assert")) { + return false; + }; + return scopelookupprefer(c.cur, c.curmod, id.str) == nil; +}; + // asserttyped — post-checker invariant gate (#15, A.6.2.1e). Walks the -// file tree and fires (writes a one-line diagnostic to stderr) for any -// node in resolvewalk's value-producing dispatch set (L474-489) whose -// n.type_ remained nil. Mirror of harec's `assert(expr->result)` at -// ref/harec/src/check.c:3810 — wwstage's checker is lenient (rule 7), -// so this is a soft assertion (diagnostic, not abort) used by the -// 990_selfhost probes to catch regressions in the stamping discipline. +// file tree and fires for any node in resolvewalk's value-producing +// dispatch set (L474-489) whose n.type_ remained nil. Mirror of harec's +// `assert(expr->result)` at ref/harec/src/check.c:3810. The invariant +// is ARMED: a non-exempt nil-typed value node writes its one-line +// diagnostic to stderr and bails (os.exit 1), so a stamping regression +// fails loud rather than shipping a partially-typed tree. The +// 990_selfhost / 901 probes drive the wwstage checker over the resolved +// units that exercise this gate. // // Gates (per Drew 2026-05-22 — "guards value-producing expression // nodes; SK_USE refs and bare builtin callees are syntactic positions, @@ -13612,6 +13633,11 @@ fn resolvefnbody(c: *checker, fnnode: *node) void = { // name half of a member-access expr is a lookup target, not a // value-producing sub-expression. Harec's EXPR_ACCESS stores the // member as a string, not a node. +// 4. The EXPR_ASSERT family — an N_CALL whose callee is `abort` or +// `assert`, and the bare N_IDENT callee itself (see isassertfam). +// harec's EXPR_ASSERT carries a void/never result with no callee +// ident (ref/harec/src/check.c:877,893); wwstage's +// N_CALL-over-bare-ident shape leaves both nodes nil by design. // // `indot` tracks gate 3: true only when the immediate caller is an // N_DOT recursing into its .lhs. @@ -13643,6 +13669,10 @@ fn asserttyped(c: *checker, n: *node, indot: bool) void = { if (s.decl == nil) { skip = true; }; }; }; + if (!skip) { if (isassertfam(c, n)) { skip = true; }; }; + }; + if (isexpr && k == nkind.N_CALL) { + if (isassertfam(c, n.lhs)) { skip = true; }; }; if (isexpr && !skip) { if (n.type_ == nil) { @@ -13662,6 +13692,7 @@ fn asserttyped(c: *checker, n: *node, indot: bool) void = { os.write(2, "'".ptr, 1u64); }; os.write(2, "\n".ptr, 1u64); + os.exit(1); }; }; if (k == nkind.N_DOT) { diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index f0fe1241..2d7a1697 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -2088,7 +2088,8 @@ fn indexresult(c: *checker, e: *node) *node = { // asserttyped pass at L2871 enforces the invariant on every // dispatched node post-checker, with gates for the residual // inherent-IDENT bails (SK_USE, pseudo-builtin sym.decl==nil, -// N_DOT-LHS syntactic position) until #19 retires the bail shape. +// N_DOT-LHS syntactic position, EXPR_ASSERT-family abort/assert) until +// #19 retires the bail shape. fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (e == nil) { return nil; }; let k: nkind = e.kind; @@ -3556,13 +3557,33 @@ fn resolvefnbody(c: *checker, fnnode: *node) void = { c.cur = outer; }; +// isassertfam — `abort`/`assert` are language builtins, not value +// calls. harec models each as a dedicated EXPR_ASSERT whose result is +// builtin void (assert) or never (bare abort) at +// ref/harec/src/check.c:877,893; there is no callee ident, so nothing +// is left untyped. wwstage parses them as an N_CALL over a bare +// N_IDENT callee that binds to no decl, so both the call and its +// callee carry no type by design. Recognized exactly as the cstage +// builtin intercept (cmd/wcc/check.c:1314,1328): the reserved name +// with no shadowing user symbol. +fn isassertfam(c: *checker, id: *node) bool = { + if (id == nil) { return false; }; + if (id.kind != nkind.N_IDENT) { return false; }; + if (!streq(id.str, "abort") && !streq(id.str, "assert")) { + return false; + }; + return scopelookupprefer(c.cur, c.curmod, id.str) == nil; +}; + // asserttyped — post-checker invariant gate (#15, A.6.2.1e). Walks the -// file tree and fires (writes a one-line diagnostic to stderr) for any -// node in resolvewalk's value-producing dispatch set (L474-489) whose -// n.type_ remained nil. Mirror of harec's `assert(expr->result)` at -// ref/harec/src/check.c:3810 — wwstage's checker is lenient (rule 7), -// so this is a soft assertion (diagnostic, not abort) used by the -// 990_selfhost probes to catch regressions in the stamping discipline. +// file tree and fires for any node in resolvewalk's value-producing +// dispatch set (L474-489) whose n.type_ remained nil. Mirror of harec's +// `assert(expr->result)` at ref/harec/src/check.c:3810. The invariant +// is ARMED: a non-exempt nil-typed value node writes its one-line +// diagnostic to stderr and bails (os.exit 1), so a stamping regression +// fails loud rather than shipping a partially-typed tree. The +// 990_selfhost / 901 probes drive the wwstage checker over the resolved +// units that exercise this gate. // // Gates (per Drew 2026-05-22 — "guards value-producing expression // nodes; SK_USE refs and bare builtin callees are syntactic positions, @@ -3582,6 +3603,11 @@ fn resolvefnbody(c: *checker, fnnode: *node) void = { // name half of a member-access expr is a lookup target, not a // value-producing sub-expression. Harec's EXPR_ACCESS stores the // member as a string, not a node. +// 4. The EXPR_ASSERT family — an N_CALL whose callee is `abort` or +// `assert`, and the bare N_IDENT callee itself (see isassertfam). +// harec's EXPR_ASSERT carries a void/never result with no callee +// ident (ref/harec/src/check.c:877,893); wwstage's +// N_CALL-over-bare-ident shape leaves both nodes nil by design. // // `indot` tracks gate 3: true only when the immediate caller is an // N_DOT recursing into its .lhs. @@ -3613,6 +3639,10 @@ fn asserttyped(c: *checker, n: *node, indot: bool) void = { if (s.decl == nil) { skip = true; }; }; }; + if (!skip) { if (isassertfam(c, n)) { skip = true; }; }; + }; + if (isexpr && k == nkind.N_CALL) { + if (isassertfam(c, n.lhs)) { skip = true; }; }; if (isexpr && !skip) { if (n.type_ == nil) { @@ -3632,6 +3662,7 @@ fn asserttyped(c: *checker, n: *node, indot: bool) void = { os.write(2, "'".ptr, 1u64); }; os.write(2, "\n".ptr, 1u64); + os.exit(1); }; }; if (k == nkind.N_DOT) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 17e8a3ed..b16c0522 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -12118,7 +12118,8 @@ fn indexresult(c: *checker, e: *node) *node = { // asserttyped pass at L2871 enforces the invariant on every // dispatched node post-checker, with gates for the residual // inherent-IDENT bails (SK_USE, pseudo-builtin sym.decl==nil, -// N_DOT-LHS syntactic position) until #19 retires the bail shape. +// N_DOT-LHS syntactic position, EXPR_ASSERT-family abort/assert) until +// #19 retires the bail shape. fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (e == nil) { return nil; }; let k: nkind = e.kind; @@ -13586,13 +13587,33 @@ fn resolvefnbody(c: *checker, fnnode: *node) void = { c.cur = outer; }; +// isassertfam — `abort`/`assert` are language builtins, not value +// calls. harec models each as a dedicated EXPR_ASSERT whose result is +// builtin void (assert) or never (bare abort) at +// ref/harec/src/check.c:877,893; there is no callee ident, so nothing +// is left untyped. wwstage parses them as an N_CALL over a bare +// N_IDENT callee that binds to no decl, so both the call and its +// callee carry no type by design. Recognized exactly as the cstage +// builtin intercept (cmd/wcc/check.c:1314,1328): the reserved name +// with no shadowing user symbol. +fn isassertfam(c: *checker, id: *node) bool = { + if (id == nil) { return false; }; + if (id.kind != nkind.N_IDENT) { return false; }; + if (!streq(id.str, "abort") && !streq(id.str, "assert")) { + return false; + }; + return scopelookupprefer(c.cur, c.curmod, id.str) == nil; +}; + // asserttyped — post-checker invariant gate (#15, A.6.2.1e). Walks the -// file tree and fires (writes a one-line diagnostic to stderr) for any -// node in resolvewalk's value-producing dispatch set (L474-489) whose -// n.type_ remained nil. Mirror of harec's `assert(expr->result)` at -// ref/harec/src/check.c:3810 — wwstage's checker is lenient (rule 7), -// so this is a soft assertion (diagnostic, not abort) used by the -// 990_selfhost probes to catch regressions in the stamping discipline. +// file tree and fires for any node in resolvewalk's value-producing +// dispatch set (L474-489) whose n.type_ remained nil. Mirror of harec's +// `assert(expr->result)` at ref/harec/src/check.c:3810. The invariant +// is ARMED: a non-exempt nil-typed value node writes its one-line +// diagnostic to stderr and bails (os.exit 1), so a stamping regression +// fails loud rather than shipping a partially-typed tree. The +// 990_selfhost / 901 probes drive the wwstage checker over the resolved +// units that exercise this gate. // // Gates (per Drew 2026-05-22 — "guards value-producing expression // nodes; SK_USE refs and bare builtin callees are syntactic positions, @@ -13612,6 +13633,11 @@ fn resolvefnbody(c: *checker, fnnode: *node) void = { // name half of a member-access expr is a lookup target, not a // value-producing sub-expression. Harec's EXPR_ACCESS stores the // member as a string, not a node. +// 4. The EXPR_ASSERT family — an N_CALL whose callee is `abort` or +// `assert`, and the bare N_IDENT callee itself (see isassertfam). +// harec's EXPR_ASSERT carries a void/never result with no callee +// ident (ref/harec/src/check.c:877,893); wwstage's +// N_CALL-over-bare-ident shape leaves both nodes nil by design. // // `indot` tracks gate 3: true only when the immediate caller is an // N_DOT recursing into its .lhs. @@ -13643,6 +13669,10 @@ fn asserttyped(c: *checker, n: *node, indot: bool) void = { if (s.decl == nil) { skip = true; }; }; }; + if (!skip) { if (isassertfam(c, n)) { skip = true; }; }; + }; + if (isexpr && k == nkind.N_CALL) { + if (isassertfam(c, n.lhs)) { skip = true; }; }; if (isexpr && !skip) { if (n.type_ == nil) { @@ -13662,6 +13692,7 @@ fn asserttyped(c: *checker, n: *node, indot: bool) void = { os.write(2, "'".ptr, 1u64); }; os.write(2, "\n".ptr, 1u64); + os.exit(1); }; }; if (k == nkind.N_DOT) { diff --git a/test/wcc/901_asserttyped_gap.c b/test/wcc/901_asserttyped_gap.c index 9f65867c..b5419190 100644 --- a/test/wcc/901_asserttyped_gap.c +++ b/test/wcc/901_asserttyped_gap.c @@ -4,25 +4,26 @@ * The live `make test` stdlib _run suite drives the CSTAGE `ww` * (cmd/ check.c), which has no asserttyped pass; 990_selfhost feeds * the ww-stage dumper only -t/-a (tokens/ast), never the checker. So - * the wwstage checker's asserttyped diagnostics (check.ww:3442 — the - * #15 / A.6.2.1e post-checker nil-type invariant gate, currently a - * soft warn) are UNOBSERVED by every other test. Re-arming asserttyped - * to bail would ship green while the known nil-stamp gaps stayed - * latent (the bootstrap-coverage trap). + * the wwstage checker's asserttyped diagnostics (check.ww — the + * #15 / A.6.2.1e post-checker nil-type invariant gate) are UNOBSERVED + * by every other test, yet the gate is now ARMED (a non-exempt + * nil-typed value node writes its diagnostic and os.exit(1)s). This + * probe is the visible counterpart: it counts the per-file diagnostics + * without gating on the bail's exit so a regression names its class. * * This probe runs the wwstage checker (wwdump_ww -c) over the * gap-bearing combined.ww corpus, counts the per-file asserttyped * diagnostics on stderr, and pins each count against the manifest * below. A fresh nil-gap (count up) or a regressed fixed class (count * up from 0) fails loud; a fold that closes a class drives its count - * down, which fails until the manifest is edited to match — i.e. the - * manifest is the expected-fail list that shrinks per fold and reaches - * all-zero exactly when the bail is safe to arm. + * down, which fails until the manifest is edited to match. Every class + * has reached all-zero, so the manifest is now the all-closed floor + * that holds the armed bail green. * * Gap classes (counts verified empirically at this revision): * A module-qual N_DOT call result checked_test 0 (closed) * B fn-ptr struct-field call smoke 0 (closed) - * C abort intrinsic callee utf8 8 + * C abort intrinsic callee utf8 0 (exempt) * D module-leaf == type/fn name fnmatch 0 (closed) * D module-leaf == type/fn name random 0 (closed) * E computed enum-member value-expr enum_corpus 0 (closed) @@ -114,7 +115,7 @@ main(void) { "selfhost/test/smoke.combined.ww", "B fn-ptr struct-field call", 0 }, { "lib/encoding/utf8/utf8.combined.ww", - "C abort intrinsic callee", 8 }, + "C abort intrinsic callee", 0 }, { "lib/fnmatch/fnmatchtest.combined.ww", "D module-leaf == type/fn name", 0 }, { "lib/math/random/random_test.combined.ww", @@ -159,7 +160,7 @@ main(void) fail, n); return 1; } - printf("asserttyped_gap: ww-stage checker warn set matches manifest " - "on %d gap-corpus fixtures (C pinned, A+B+D+E+F+G closed)\n", n); + printf("asserttyped_gap: ww-stage checker diagnostic set matches manifest " + "on %d gap-corpus fixtures (A-G all closed; bail armed)\n", n); return 0; }