wcc: arm asserttyped bail — a nil-typed value node is now fatal
The wwstage asserttyped pass only WARNED on a checked value-node with no result type_, a check-bail-discipline regression that let gate-blind nil-stamp miscompiles ship green (the whole #6 arc: tuple/struct/fn-ptr/enum/binding nil-stamps were all invisible to the byte-id gates). With every nil-gap class now stamped (module-qual calls, fn-ptr-field calls, computed enum value-exprs, for-range/massign binds) the bail can finally arm: warn -> os.exit(1). Exempt exactly the two legitimately-no-type value classes, each a positive cited assertion (never a residual warn): the EXPR_ASSERT family (abort/assert, guarded against a user shadow; harec check.c:877,893) and seeded pseudo-builtin callees (len/append/free/alloc/size — a structural nil-decl-SK_FN predicate, not a name-list). The pre-existing module-ref and dot-lhs filters stay: they identify access-path components that aren't value exprs (harec EXPR_ACCESS), not exemptions. Verified clean over the broadest net — the armed checker over all five self-build combined units (the full selfhost source) plus the 901 gap corpus — zero out-of-class bail; fails-loud confirmed (undeclared call, abort's args, a nil dot-base all bail). Checker-only: 990-997 byte-id hold.
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user