@@ -2093,6 +2093,84 @@ fn fn_has_bad_await(toks: read List<Tok>, start: read Int) -> Bool {
20932093 return hasAwait && suppress == false
20942094}
20952095
2096+ // ---------------------------------------------------------------------------
2097+ // RS0023 FD_OUTSIDE_INTERNAL_BOUNDARY.
2098+ //
2099+ // `Fd` used in a param/return type of a non-native fn, or in a field type of a
2100+ // non-resource type decl (check_fd_surface, type_ref_contains_name(ty, "Fd")).
2101+ // Native fns and resource types are the trusted internal boundary and skipped.
2102+ // ---------------------------------------------------------------------------
2103+
2104+ // True if the fn decl at `start` carries the `native` modifier.
2105+ fn fn_is_native(toks: read List<Tok >, start: read Int) -> Bool {
2106+ let ns = fn_name_start(toks: read toks, i: read start)
2107+ let mut k = start
2108+ let mut hit = false
2109+ while k < (ns - 1) {
2110+ if at_ident(toks: read toks, i: read k, wid: read WORD_NATIVE) { hit = true }
2111+ k = k + 1
2112+ }
2113+ return hit
2114+ }
2115+
2116+ // RS0023 over a non-native fn: any param type or the return type names `Fd`.
2117+ fn fn_has_fd_surface(toks: read List<Tok >, start: read Int) -> Bool {
2118+ if fn_is_native(toks: read toks, start: read start) { return false }
2119+ let ns = fn_name_start(toks: read toks, i: read start)
2120+ let sigEnd = function_signature_end(toks: read toks, start: read (ns - 1))
2121+ let open = find_param_open(toks: read toks, from: read ns, end: read sigEnd)
2122+ if open < 0 { return false }
2123+ let close = find_matching(toks: read toks, open: read open, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
2124+ if close < 0 { return false }
2125+ let mut k = open + 1
2126+ let mut depth = 0
2127+ let mut angle = 0
2128+ let mut segStart = open + 1
2129+ let mut hit = false
2130+ while k < close {
2131+ let opener = at_symbol(toks: read toks, i: read k, code: read SYM_LPAREN) || at_symbol(toks: read toks, i: read k, code: read SYM_LBRACKET) || at_symbol(toks: read toks, i: read k, code: read SYM_LBRACE)
2132+ let closer = at_symbol(toks: read toks, i: read k, code: read SYM_RPAREN) || at_symbol(toks: read toks, i: read k, code: read SYM_RBRACKET) || at_symbol(toks: read toks, i: read k, code: read SYM_RBRACE)
2133+ if opener {
2134+ depth = depth + 1
2135+ } else if closer {
2136+ if depth > 0 { depth = depth - 1 }
2137+ } else if at_symbol(toks: read toks, i: read k, code: read SYM_LANGLE) {
2138+ angle = angle + 1
2139+ } else if at_symbol(toks: read toks, i: read k, code: read SYM_RANGLE) {
2140+ if angle > 0 { angle = angle - 1 }
2141+ } else if depth == 0 && angle == 0 && at_symbol(toks: read toks, i: read k, code: read SYM_COMMA) {
2142+ if seg_type_has_fd(toks: read toks, segStart: read segStart, segEnd: read k) { hit = true }
2143+ segStart = k + 1
2144+ }
2145+ k = k + 1
2146+ }
2147+ if seg_type_has_fd(toks: read toks, segStart: read segStart, segEnd: read close) { hit = true }
2148+ // Return type region: `-> Type` before effects/`{`/`=`.
2149+ if at_symbol(toks: read toks, i: read (close + 1), code: read SYM_ARROW) {
2150+ let rs = close + 2
2151+ let mut re = rs
2152+ let mut scanning = true
2153+ while scanning {
2154+ let stop = at_ident(toks: read toks, i: read re, wid: read WORD_EFFECTS) || at_symbol(toks: read toks, i: read re, code: read SYM_LBRACE) || at_symbol(toks: read toks, i: read re, code: read SYM_EQ)
2155+ if re < sigEnd && stop == false {
2156+ re = re + 1
2157+ } else {
2158+ scanning = false
2159+ }
2160+ }
2161+ if region_has_text(toks: read toks, s: read rs, e: read re, word: read "Fd") { hit = true }
2162+ }
2163+ return hit
2164+ }
2165+
2166+ // A param segment's type region [segStart+2, segEnd) names `Fd`.
2167+ fn seg_type_has_fd(toks: read List<Tok >, segStart: read Int, segEnd: read Int) -> Bool {
2168+ if (segStart + 2) >= segEnd { return false }
2169+ if is_ident_tok(toks: read toks, i: read segStart) == false { return false }
2170+ if at_symbol(toks: read toks, i: read (segStart + 1), code: read SYM_COLON) == false { return false }
2171+ return region_has_text(toks: read toks, s: read (segStart + 2), e: read segEnd, word: read "Fd")
2172+ }
2173+
20962174fn main() -> Unit {
20972175 let source = Args.get_or_default(index: 0, default: read "")
20982176 let toks = tokenize(source: read source)
@@ -2121,6 +2199,8 @@ fn main() -> Unit {
21212199 let mut pureBad = false
21222200 // RS0029: any `await` in a non-async function.
21232201 let mut badAwait = false
2202+ // RS0023: `Fd` in a non-native fn signature or a non-resource type field.
2203+ let mut fdSurface = false
21242204 // RS0021: any non-exhaustive match statement or expression in the file.
21252205 let mut nonExhaustive = false
21262206 // RS0024: forward-reference-tolerant known-type set for the whole file.
@@ -2203,6 +2283,13 @@ fn main() -> Unit {
22032283 }
22042284 }
22052285 if type_decl_unknown(toks: read toks, i: read i, r: read r, declared: read declared) { unknownType = true }
2286+ // RS0023: an `Fd`-typed field on a non-resource type. Resource
2287+ // types are the trusted internal boundary and are skipped.
2288+ if at_ident(toks: read toks, i: read (ns - 1), wid: read WORD_RESOURCE) == false {
2289+ if ob >= 0 {
2290+ if region_has_text(toks: read toks, s: read (ob + 1), e: read (r - 1), word: read "Fd") { fdSurface = true }
2291+ }
2292+ }
22062293 i = r
22072294 }
22082295 } else if isSum {
@@ -2290,6 +2377,7 @@ fn main() -> Unit {
22902377 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 }
22912378 if fn_has_nonexhaustive_match(toks: read toks, start: read i, declared: read declared, allFns: read allFns) { nonExhaustive = true }
22922379 if fn_has_bad_await(toks: read toks, start: read i) { badAwait = true }
2380+ if fn_has_fd_surface(toks: read toks, start: read i) { fdSurface = true }
22932381 i = r
22942382 }
22952383 } else {
@@ -2340,6 +2428,9 @@ fn main() -> Unit {
23402428 if badAwait {
23412429 Log.write(message: read "RS0029")
23422430 }
2431+ if fdSurface {
2432+ Log.write(message: read "RS0023")
2433+ }
23432434 if unknownType {
23442435 Log.write(message: read "RS0024")
23452436 }
@@ -2355,7 +2446,7 @@ fn main() -> Unit {
23552446 if headerCount >= 2 {
23562447 Log.write(message: read "RS0006")
23572448 }
2358- let anyDiag = found || hasUnknownFeat || hasDupFeat || headerCount >= 2 || missingReturn || untypedParam || hasProfile || shareParam || unknownEffect || removedRuntime || invalidSelf || outOfRangeInt || retainsBad || unknownType || missingEffect || pureBad || nonExhaustive || badAwait
2449+ let anyDiag = found || hasUnknownFeat || hasDupFeat || headerCount >= 2 || missingReturn || untypedParam || hasProfile || shareParam || unknownEffect || removedRuntime || invalidSelf || outOfRangeInt || retainsBad || unknownType || missingEffect || pureBad || nonExhaustive || badAwait || fdSurface
23592450 if anyDiag == false {
23602451 Log.write(message: read "CLEAN")
23612452 }
0 commit comments