@@ -2614,14 +2614,15 @@ fn file_unsafe_use(toks: read List<Tok>) -> Bool {
26142614// RS0207 argument, RS0208 return). See crate::checks::shared::builtin_value_type_name,
26152615// crate::hir::number_literal_type_name, and checks/body/try_checks.rs.
26162616//
2617- // NOTE: RS0013 is emitted below but is intentionally NOT in CHECKER_TARGET_CODES
2618- // (so it is filtered from the parity comparison and the gate stays green). Two of
2619- // its three oracle sub-rules are reproduced faithfully — (A) `?` in a function
2620- // whose return type is not Result/Option, and (B) a `?` whose operand has a
2621- // confidently-known concrete non-Result/Option type. The third sub-rule
2622- // (error-type mismatch) fires on qualified stdlib calls whose error type needs
2623- // the full stdlib signature DB, which is deliberately kept UNKNOWN here; see the
2624- // ledger milestone for the precise blocker (ast-call-missing-effect-nested.rss).
2617+ // RS0013 is in CHECKER_TARGET_CODES: all three oracle sub-rules are reproduced —
2618+ // (A) `?` in a function whose return type is not Result/Option, (B) a `?` whose
2619+ // operand has a confidently-known concrete non-Result/Option type, and (C) inside
2620+ // a fn returning `Result<T , E>`, a `?` whose operand Result error type is known
2621+ // and differs from E. Sub-rule C reads the operand error type from a small stdlib
2622+ // namespace->error-type map (filesystem->FileError, json/toml/yaml->JsonError) or,
2623+ // for an unqualified same-file call, from that fn's declared error type; anything
2624+ // else stays UNKNOWN (no fire) — the FP-safety mechanism. See stdlib_error_type,
2625+ // return_error_type_at, try_operand_error_type, and checks/body/try_checks.rs.
26252626// ---------------------------------------------------------------------------
26262627
26272628// crate::hir::number_literal_type_name reproduced: a numeric literal is Float iff
@@ -2733,10 +2734,151 @@ fn try_operand_root(toks: read List<Tok>, qPos: read Int, bodyOpen: read Int, po
27332734 return ""
27342735}
27352736
2736- // RS0013 INVALID_TRY_OPERATOR (sub-rules A + B): true iff the fn at `start` uses
2737- // `?` in a way that is invalid. (A) any `?` in a fn whose return root is not
2737+ // Stdlib namespace -> Result error type. Built from the `.rssi` interfaces: each
2738+ // `pub native fn Ns.method(...) -> Result<T , ErrType>` declares a per-module error
2739+ // type, uniform across the module for these two families. Filesystem (File /
2740+ // Directory / Env / Path) -> FileError; the JSON-shaped codecs (Json / Toml /
2741+ // Yaml) -> JsonError. Two families have per-method exceptions that must be keyed
2742+ // by `Ns.method`: `File.bytes_stream` returns ChannelError (async streaming), and
2743+ // `Path.{from_string,resolve_relative,safe_relative}` return a `String` error —
2744+ // all excluded (yield "") rather than mis-asserting an error type. A namespace not
2745+ // in the map also yields "" (unknown => no diagnostic => FP-safe). See
2746+ // stdlib/{fs,env,path,json,toml,yaml}/*.rssi, packages/async/interface/file.rssi,
2747+ // and crate::checks::body::try_checks::result_error_type_name.
2748+ fn stdlib_error_type(ns: read String, method: read String) -> String {
2749+ if ns == "File" {
2750+ if method == "bytes_stream" { return "" }
2751+ return "FileError"
2752+ }
2753+ if ns == "Directory" || ns == "Env" { return "FileError" }
2754+ if ns == "Path" {
2755+ if method == "from_string" || method == "resolve_relative" || method == "safe_relative" { return "" }
2756+ return "FileError"
2757+ }
2758+ if ns == "Json" || ns == "Toml" || ns == "Yaml" { return "JsonError" }
2759+ return ""
2760+ }
2761+
2762+ // Second (error) type-arg root of a `Result<T , E>` return type for the fn whose
2763+ // keyword-anchor sits at `fnkw`, or "" when the return is not a 2-arg Result.
2764+ // Mirrors return_type_root_at but yields E instead of the head root.
2765+ fn return_error_type_at(toks: read List<Tok >, fnkw: read Int) -> String {
2766+ let sigEnd = function_signature_end(toks: read toks, start: read fnkw)
2767+ let open = find_param_open(toks: read toks, from: read fnkw, end: read sigEnd)
2768+ if open < 0 { return "" }
2769+ let close = find_matching(toks: read toks, open: read open, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
2770+ if close < 0 { return "" }
2771+ if at_symbol(toks: read toks, i: read (close + 1), code: read SYM_ARROW) == false { return "" }
2772+ let rs = close + 2
2773+ let mut re = rs
2774+ let mut scanning = true
2775+ while scanning {
2776+ 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)
2777+ if re < sigEnd && stop == false {
2778+ re = re + 1
2779+ } else {
2780+ scanning = false
2781+ }
2782+ }
2783+ return result_error_root(toks: read toks, s: read rs, e: read re)
2784+ }
2785+
2786+ // Error root of a `Result< T , E >` type region [s,e): the root of its second
2787+ // top-level type argument, or "" when the region is not a 2-arg Result.
2788+ fn result_error_root(toks: read List<Tok >, s: read Int, e: read Int) -> String {
2789+ let p = skip_type_prefixes(toks: read toks, s: read s, e: read e)
2790+ if p >= e { return "" }
2791+ if is_ident_tok(toks: read toks, i: read p) == false { return "" }
2792+ if tk_text(toks: read toks, i: read p) != "Result" { return "" }
2793+ let lt = p + 1
2794+ if at_symbol(toks: read toks, i: read lt, code: read SYM_LANGLE) == false { return "" }
2795+ let mut k = lt + 1
2796+ let mut angle = 0
2797+ let mut depth = 0
2798+ let mut commaPos = -1
2799+ let mut rangle = -1
2800+ while k < e && rangle < 0 {
2801+ if at_symbol(toks: read toks, i: read k, code: read SYM_LANGLE) {
2802+ angle = angle + 1
2803+ } else if at_symbol(toks: read toks, i: read k, code: read SYM_RANGLE) {
2804+ if angle == 0 { rangle = k } else { angle = angle - 1 }
2805+ } else if at_symbol(toks: read toks, i: read k, code: read SYM_LPAREN) || at_symbol(toks: read toks, i: read k, code: read SYM_LBRACKET) {
2806+ depth = depth + 1
2807+ } else if at_symbol(toks: read toks, i: read k, code: read SYM_RPAREN) || at_symbol(toks: read toks, i: read k, code: read SYM_RBRACKET) {
2808+ if depth > 0 { depth = depth - 1 }
2809+ } else if angle == 0 && depth == 0 && at_symbol(toks: read toks, i: read k, code: read SYM_COMMA) {
2810+ if commaPos < 0 { commaPos = k }
2811+ }
2812+ k = k + 1
2813+ }
2814+ if commaPos < 0 || rangle < 0 { return "" }
2815+ return type_region_root(toks: read toks, s: read (commaPos + 1), e: read rangle)
2816+ }
2817+
2818+ // Error root of the `Result<T , E>` return of the first unqualified same-file
2819+ // `fn fnName(...)`, or "". Mirrors fn_return_root but yields E.
2820+ fn fn_error_type_by_name(toks: read List<Tok >, fnName: read String) -> String {
2821+ let n = List.len(list: read toks)
2822+ let mut i = 0
2823+ let mut result = ""
2824+ let mut done = false
2825+ while i < n && done == false {
2826+ if at_ident(toks: read toks, i: read i, wid: read WORD_FN) {
2827+ let nameIdx = i + 1
2828+ let named = is_ident_tok(toks: read toks, i: read nameIdx) && tk_text(toks: read toks, i: read nameIdx) == fnName
2829+ if named && at_symbol(toks: read toks, i: read (nameIdx + 1), code: read SYM_DOT) == false {
2830+ result = return_error_type_at(toks: read toks, fnkw: read i)
2831+ done = true
2832+ }
2833+ }
2834+ i = i + 1
2835+ }
2836+ return result
2837+ }
2838+
2839+ // Result error type of the `?`-operand ending just before the `?` at `qPos`, or
2840+ // "" when it is not confidently known. Only two operand shapes yield an error
2841+ // type: a qualified stdlib call `Ns.method(...)?` (via the namespace map) and an
2842+ // unqualified same-file call `name(...)?` (via that fn's declared error type).
2843+ // Anything else (bare ident, index, field, Ok/Err/Some literal) -> "" (FP-safe).
2844+ fn try_operand_error_type(toks: read List<Tok >, qPos: read Int, bodyOpen: read Int, allFns: read Set<String >) -> String {
2845+ let prev = qPos - 1
2846+ if prev <= bodyOpen { return "" }
2847+ if at_symbol(toks: read toks, i: read prev, code: read SYM_RPAREN) == false { return "" }
2848+ let mut d = 0
2849+ let mut j = prev
2850+ let mut op = -1
2851+ while j > bodyOpen && op < 0 {
2852+ if at_symbol(toks: read toks, i: read j, code: read SYM_RPAREN) {
2853+ d = d + 1
2854+ } else if at_symbol(toks: read toks, i: read j, code: read SYM_LPAREN) {
2855+ d = d - 1
2856+ if d == 0 { op = j }
2857+ }
2858+ j = j - 1
2859+ }
2860+ if op <= bodyOpen { return "" }
2861+ let callee = op - 1
2862+ if is_ident_tok(toks: read toks, i: read callee) == false { return "" }
2863+ let method = tk_text(toks: read toks, i: read callee)
2864+ if at_symbol(toks: read toks, i: read (callee - 1), code: read SYM_DOT) {
2865+ let nsPos = callee - 2
2866+ if is_ident_tok(toks: read toks, i: read nsPos) == false { return "" }
2867+ if at_symbol(toks: read toks, i: read (nsPos - 1), code: read SYM_DOT) { return "" }
2868+ return stdlib_error_type(ns: read tk_text(toks: read toks, i: read nsPos), method: read method)
2869+ }
2870+ if Set.contains<String >(set: read allFns, value: read method) {
2871+ return fn_error_type_by_name(toks: read toks, fnName: read method)
2872+ }
2873+ return ""
2874+ }
2875+
2876+ // RS0013 INVALID_TRY_OPERATOR (sub-rules A + B + C): true iff the fn at `start`
2877+ // uses `?` in a way that is invalid. (A) any `?` in a fn whose return root is not
27382878// Result/Option (check_try_operator_result_returns). (B) a `?` whose operand has
27392879// a confidently-known concrete non-Result/Option type (check_try_value_is_result).
2880+ // (C) inside a fn returning `Result<T , E>`, a `?` whose operand's Result error
2881+ // type is confidently known and differs from E (check_try_error_types).
27402882fn fn_invalid_try(toks: read List<Tok >, start: read Int, declared: read Set<String >, allFns: read Set<String >) -> Bool {
27412883 let ns = fn_name_start(toks: read toks, i: read start)
27422884 let sigEnd = function_signature_end(toks: read toks, start: read (ns - 1))
@@ -2750,6 +2892,7 @@ fn fn_invalid_try(toks: read List<Tok>, start: read Int, declared: read Set<Stri
27502892 }
27512893 let retRoot = return_type_root_at(toks: read toks, fnkw: read (ns - 1))
27522894 let returnsResult = retRoot == "Result" || retRoot == "Option"
2895+ let fnErr = return_error_type_at(toks: read toks, fnkw: read (ns - 1))
27532896 let mut k = sigEnd + 1
27542897 let mut hit = false
27552898 while k < bodyClose {
@@ -2759,6 +2902,10 @@ fn fn_invalid_try(toks: read List<Tok>, start: read Int, declared: read Set<Stri
27592902 } else {
27602903 let root = try_operand_root(toks: read toks, qPos: read k, bodyOpen: read sigEnd, popen: read popen, pclose: read pclose, declared: read declared, allFns: read allFns)
27612904 if String.len(value: read root) > 0 && root != "Result" && root != "Option" { hit = true }
2905+ if retRoot == "Result" && String.len(value: read fnErr) > 0 {
2906+ let opErr = try_operand_error_type(toks: read toks, qPos: read k, bodyOpen: read sigEnd, allFns: read allFns)
2907+ if String.len(value: read opErr) > 0 && opErr != fnErr { hit = true }
2908+ }
27622909 }
27632910 }
27642911 k = k + 1
0 commit comments