@@ -2046,6 +2046,53 @@ fn fn_has_nonexhaustive_match(toks: read List<Tok>, start: read Int, declared: r
20462046 return hit
20472047}
20482048
2049+ // ---------------------------------------------------------------------------
2050+ // RS0029 AWAIT_OUTSIDE_ASYNC.
2051+ //
2052+ // An `await` expression in a function that is not `async` (check_await_placement,
2053+ // async_context = function_is_async || in_task_group). Token-level: a non-async
2054+ // top-level fn whose body has an `await` keyword and no `async let` (task_group)
2055+ // binding suppressing it. Awaits are always checked non-async inside closures, so
2056+ // over-suppressing on any `async` token in the body is safe for the corpus.
2057+ // ---------------------------------------------------------------------------
2058+
2059+ // True if the fn decl at `start` carries the `async` modifier (a WORD_ASYNC
2060+ // keyword between the decl start / attributes and the `fn` keyword at ns-1).
2061+ fn fn_is_async(toks: read List<Tok >, start: read Int) -> Bool {
2062+ let ns = fn_name_start(toks: read toks, i: read start)
2063+ let mut k = start
2064+ let mut hit = false
2065+ while k < (ns - 1) {
2066+ if at_ident(toks: read toks, i: read k, wid: read WORD_ASYNC) { hit = true }
2067+ k = k + 1
2068+ }
2069+ return hit
2070+ }
2071+
2072+ // RS0029: a non-async fn body that contains an `await` keyword and no `async`
2073+ // (task_group `async let`) binding to make the await valid.
2074+ fn fn_has_bad_await(toks: read List<Tok >, start: read Int) -> Bool {
2075+ if fn_is_async(toks: read toks, start: read start) { return false }
2076+ let ns = fn_name_start(toks: read toks, i: read start)
2077+ let sigEnd = function_signature_end(toks: read toks, start: read (ns - 1))
2078+ if at_symbol(toks: read toks, i: read sigEnd, code: read SYM_LBRACE) == false { return false }
2079+ let bodyClose = find_matching(toks: read toks, open: read sigEnd, openSym: read SYM_LBRACE, closeSym: read SYM_RBRACE)
2080+ if bodyClose < 0 { return false }
2081+ let mut k = sigEnd + 1
2082+ let mut hasAwait = false
2083+ let mut suppress = false
2084+ while k < bodyClose {
2085+ if is_kw_text(toks: read toks, i: read k, word: read "await") { hasAwait = true }
2086+ // `async let` (task_group) makes an await valid; `select` arm operations
2087+ // are a structured await boundary; both suppress RS0029 for this fn.
2088+ if at_ident(toks: read toks, i: read k, wid: read WORD_ASYNC) { suppress = true }
2089+ if is_kw_text(toks: read toks, i: read k, word: read "select") { suppress = true }
2090+ if is_kw_text(toks: read toks, i: read k, word: read "task_group") { suppress = true }
2091+ k = k + 1
2092+ }
2093+ return hasAwait && suppress == false
2094+ }
2095+
20492096fn main() -> Unit {
20502097 let source = Args.get_or_default(index: 0, default: read "")
20512098 let toks = tokenize(source: read source)
@@ -2072,6 +2119,8 @@ fn main() -> Unit {
20722119 let mut unknownType = false
20732120 let mut missingEffect = false
20742121 let mut pureBad = false
2122+ // RS0029: any `await` in a non-async function.
2123+ let mut badAwait = false
20752124 // RS0021: any non-exhaustive match statement or expression in the file.
20762125 let mut nonExhaustive = false
20772126 // RS0024: forward-reference-tolerant known-type set for the whole file.
@@ -2240,6 +2289,7 @@ fn main() -> Unit {
22402289 if fn_missing_param_effect(toks: read toks, start: read i, sums: read payloadlessSums) { missingEffect = true }
22412290 if fn_pure_bad(toks: read toks, start: read i, resources: read resources, sumVariants: read sumVariants, pureFns: read pureFns, allFns: read allFns, declared: read declared) { pureBad = true }
22422291 if fn_has_nonexhaustive_match(toks: read toks, start: read i, declared: read declared, allFns: read allFns) { nonExhaustive = true }
2292+ if fn_has_bad_await(toks: read toks, start: read i) { badAwait = true }
22432293 i = r
22442294 }
22452295 } else {
@@ -2287,6 +2337,9 @@ fn main() -> Unit {
22872337 if nonExhaustive {
22882338 Log.write(message: read "RS0021")
22892339 }
2340+ if badAwait {
2341+ Log.write(message: read "RS0029")
2342+ }
22902343 if unknownType {
22912344 Log.write(message: read "RS0024")
22922345 }
@@ -2302,7 +2355,7 @@ fn main() -> Unit {
23022355 if headerCount >= 2 {
23032356 Log.write(message: read "RS0006")
23042357 }
2305- let anyDiag = found || hasUnknownFeat || hasDupFeat || headerCount >= 2 || missingReturn || untypedParam || hasProfile || shareParam || unknownEffect || removedRuntime || invalidSelf || outOfRangeInt || retainsBad || unknownType || missingEffect || pureBad || nonExhaustive
2358+ let anyDiag = found || hasUnknownFeat || hasDupFeat || headerCount >= 2 || missingReturn || untypedParam || hasProfile || shareParam || unknownEffect || removedRuntime || invalidSelf || outOfRangeInt || retainsBad || unknownType || missingEffect || pureBad || nonExhaustive || badAwait
23062359 if anyDiag == false {
23072360 Log.write(message: read "CLEAN")
23082361 }
0 commit comments