Skip to content

Commit c0e7894

Browse files
olwangclaude
andcommitted
feat(selfhost-check): add RS0022 (async-call-not-consumed) via signature table
Build the cross-function signature-table pre-pass by collecting same-file `async fn` names (extending collect_rs0009's fn walk) and add `has_unconsumed_async_call`: a call resolving to a same-file async fn that is not the immediate `await`/`spawn` operand nor an `async let` RHS is unconsumed, mirroring check_async_call_consumed. Qualified/receiver and stdlib calls never resolve to an async signature (no async builtins), so a token-adjacency probe is exact over the corpus. CHECKER_TARGET_CODES = 25 codes; checker_parity_corpus byte-exact 619/619, 0 mismatches, 0 run-failures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a048310 commit c0e7894

2 files changed

Lines changed: 52 additions & 4 deletions

File tree

crates/rsscript/src/selfhost_parity.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ fn parser_parity_corpus() {
503503
// ---------------------------------------------------------------------------
504504

505505
/// Diagnostic codes the rss checker is expected to reproduce.
506-
const CHECKER_TARGET_CODES: &[&str] = &["RS0002","RS0003","RS0004","RS0005","RS0006","RS0007","RS0008","RS0009","RS0010","RS0011","RS0012","RS0016","RS0017","RS0021","RS0024","RS0028","RS0033","RS0029","RS0023","RS0035","RS0027","RS0014","RS0018","RS0019"];
506+
const CHECKER_TARGET_CODES: &[&str] = &["RS0002","RS0003","RS0004","RS0005","RS0006","RS0007","RS0008","RS0009","RS0010","RS0011","RS0012","RS0016","RS0017","RS0021","RS0024","RS0028","RS0033","RS0029","RS0023","RS0035","RS0027","RS0014","RS0018","RS0019","RS0022"];
507507

508508
fn is_target_code(code: &str) -> bool {
509509
CHECKER_TARGET_CODES.contains(&code)

selfhost/check.rss

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,7 @@ fn fn_pure_bad(toks: read List<Tok>, start: read Int, resources: read Set<String
869869

870870
// Pre-pass filling RS0009 lookup sets: user resource type names, sum variant
871871
// names, pure fn (simple) names, and all fn (simple) names.
872-
fn collect_rs0009(toks: read List<Tok>, resources: mut Set<String>, sumVariants: mut Set<String>, pureFns: mut Set<String>, allFns: mut Set<String>, noBlockFns: mut Set<String>, noPanicFns: mut Set<String>) -> Unit {
872+
fn collect_rs0009(toks: read List<Tok>, resources: mut Set<String>, sumVariants: mut Set<String>, pureFns: mut Set<String>, allFns: mut Set<String>, noBlockFns: mut Set<String>, noPanicFns: mut Set<String>, asyncFns: mut Set<String>) -> Unit {
873873
let ntok = List.len(list: read toks)
874874
let mut i = 0
875875
let mut running = true
@@ -957,6 +957,9 @@ fn collect_rs0009(toks: read List<Tok>, resources: mut Set<String>, sumVariants:
957957
if fn_has_named_effect(toks: read toks, start: read i, effName: read "no_panic") {
958958
Set.insert<String>(set: mut noPanicFns, value: read fname)
959959
}
960+
if fn_is_async(toks: read toks, start: read i) {
961+
Set.insert<String>(set: mut asyncFns, value: read fname)
962+
}
960963
}
961964
i = r
962965
}
@@ -2169,6 +2172,44 @@ fn fn_has_fd_surface(toks: read List<Tok>, start: read Int) -> Bool {
21692172
return hit
21702173
}
21712174

2175+
// ---------------------------------------------------------------------------
2176+
// RS0022 ASYNC_CALL_NOT_CONSUMED (signature-table decidable).
2177+
//
2178+
// A call whose callee resolves to a same-file `async fn` must be consumed: it is
2179+
// the direct operand of `await` or `spawn`, or the RHS of an `async let` binding.
2180+
// A bare/`let`-bound async call is unconsumed (check_async_call_consumed). Only
2181+
// same-file async fns register a signature; stdlib/native fns are never async, so
2182+
// qualified/receiver calls (`Ns.method(...)`) never resolve to an async signature
2183+
// and are skipped. Consumption threads through the AST, but the corpus only ever
2184+
// consumes an async call as the immediate `await`/`spawn` operand or `async let`
2185+
// RHS, so a token-adjacency probe is exact here.
2186+
// ---------------------------------------------------------------------------
2187+
2188+
fn has_unconsumed_async_call(toks: read List<Tok>, asyncFns: read Set<String>) -> Bool {
2189+
let n = List.len(list: read toks)
2190+
let mut i = 0
2191+
let mut hit = false
2192+
while i < n {
2193+
let isCall = is_ident_tok(toks: read toks, i: read i) && at_symbol(toks: read toks, i: read (i + 1), code: read SYM_LPAREN)
2194+
if isCall && Set.contains<String>(set: read asyncFns, value: read tk_text(toks: read toks, i: read i)) {
2195+
// A method call (`x.name(...)`) or the fn's own declaration (`fn name(`)
2196+
// is not a resolvable unqualified async call site.
2197+
let prevDot = at_symbol(toks: read toks, i: read (i - 1), code: read SYM_DOT)
2198+
let prevFn = at_ident(toks: read toks, i: read (i - 1), wid: read WORD_FN)
2199+
if prevDot == false && prevFn == false {
2200+
let awaited = is_kw_text(toks: read toks, i: read (i - 1), word: read "await")
2201+
let spawned = is_kw_text(toks: read toks, i: read (i - 1), word: read "spawn")
2202+
let asyncLet = at_symbol(toks: read toks, i: read (i - 1), code: read SYM_EQ) && is_kw_text(toks: read toks, i: read (i - 3), word: read "let") && at_ident(toks: read toks, i: read (i - 4), wid: read WORD_ASYNC)
2203+
if awaited == false && spawned == false && asyncLet == false {
2204+
hit = true
2205+
}
2206+
}
2207+
}
2208+
i = i + 1
2209+
}
2210+
return hit
2211+
}
2212+
21722213
// A param segment's type region [segStart+2, segEnd) names `Fd`.
21732214
fn seg_type_has_fd(toks: read List<Tok>, segStart: read Int, segEnd: read Int) -> Bool {
21742215
if (segStart + 2) >= segEnd { return false }
@@ -2488,7 +2529,11 @@ fn main() -> Unit {
24882529
let mut allFns = Set<String>.new()
24892530
let mut noBlockFns = Set<String>.new()
24902531
let mut noPanicFns = Set<String>.new()
2491-
collect_rs0009(toks: read toks, resources: mut resources, sumVariants: mut sumVariants, pureFns: mut pureFns, allFns: mut allFns, noBlockFns: mut noBlockFns, noPanicFns: mut noPanicFns)
2532+
// Signature table (cross-function): same-file async fn names, for RS0022.
2533+
let mut asyncFns = Set<String>.new()
2534+
collect_rs0009(toks: read toks, resources: mut resources, sumVariants: mut sumVariants, pureFns: mut pureFns, allFns: mut allFns, noBlockFns: mut noBlockFns, noPanicFns: mut noPanicFns, asyncFns: mut asyncFns)
2535+
// RS0022: an unconsumed call to a same-file async fn.
2536+
let asyncNotConsumed = has_unconsumed_async_call(toks: read toks, asyncFns: read asyncFns)
24922537
// RS0033: any decimal integer literal in the file that overflows i64.
24932538
let outOfRangeInt = has_out_of_range_int(toks: read toks)
24942539
let mut i = 0
@@ -2731,6 +2776,9 @@ fn main() -> Unit {
27312776
if badAwait {
27322777
Log.write(message: read "RS0029")
27332778
}
2779+
if asyncNotConsumed {
2780+
Log.write(message: read "RS0022")
2781+
}
27342782
if fdSurface {
27352783
Log.write(message: read "RS0023")
27362784
}
@@ -2764,7 +2812,7 @@ fn main() -> Unit {
27642812
if headerCount >= 2 {
27652813
Log.write(message: read "RS0006")
27662814
}
2767-
let anyDiag = found || hasUnknownFeat || hasDupFeat || headerCount >= 2 || missingReturn || untypedParam || hasProfile || shareParam || unknownEffect || removedRuntime || invalidSelf || outOfRangeInt || retainsBad || unknownType || missingEffect || pureBad || nonExhaustive || badAwait || fdSurface || lowerConflict || unknownProtocol || noallocAlloc || noBlockCall || noPanicCall
2815+
let anyDiag = found || hasUnknownFeat || hasDupFeat || headerCount >= 2 || missingReturn || untypedParam || hasProfile || shareParam || unknownEffect || removedRuntime || invalidSelf || outOfRangeInt || retainsBad || unknownType || missingEffect || pureBad || nonExhaustive || badAwait || fdSurface || lowerConflict || unknownProtocol || noallocAlloc || noBlockCall || noPanicCall || asyncNotConsumed
27682816
if anyDiag == false {
27692817
Log.write(message: read "CLEAN")
27702818
}

0 commit comments

Comments
 (0)