From a9ea118434f276596eb748f2b505c1abadd6c1fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Tsnobiladz=C3=A9?= Date: Tue, 11 Mar 2025 10:04:11 +0100 Subject: [PATCH 1/3] add tests for exception tracking on externals --- .../tests-reanalyze/deadcode/expected/exception.txt | 6 +++++- .../deadcode/src/exception/ExternalTest.res | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExternalTest.res diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt index 4cdad9295c..daad450be5 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt @@ -91,5 +91,9 @@ Exception Analysis ExnA.res:1:5-7 bar might raise Not_found (ExnA.res:1:16) and is not annotated with @raises(Not_found) + + Exception Analysis + ExternalTest.res:7:5-24 + bigIntFromStringExn2 might raise Exn.Error (ExternalTest.res:7:35) and is not annotated with @raises(Exn.Error) - Analysis reported 23 issues (Exception Analysis:23) + Analysis reported 24 issues (Exception Analysis:24) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExternalTest.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExternalTest.res new file mode 100644 index 0000000000..b5a0134c5c --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExternalTest.res @@ -0,0 +1,7 @@ +@raises(Exn.Error) +external bigIntFromStringExn: string => bigint = "BigInt" + +@raises(Exn.Error) +let bigIntFromStringExn = s => s->bigIntFromStringExn + +let bigIntFromStringExn2 = s => s->bigIntFromStringExn From 985f47ad59ea4fdaf0b69c64d464f49f939f46be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Tsnobiladz=C3=A9?= Date: Tue, 11 Mar 2025 10:50:57 +0100 Subject: [PATCH 2/3] add docstring tests to Stdlib.BigInt --- runtime/Stdlib_BigInt.res | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/runtime/Stdlib_BigInt.res b/runtime/Stdlib_BigInt.res index ddceef99b3..d5b43cc022 100644 --- a/runtime/Stdlib_BigInt.res +++ b/runtime/Stdlib_BigInt.res @@ -16,26 +16,20 @@ number as a `bigint` if successfully parsed. Uncaught syntax exception otherwise ## Examples ```rescript -/* returns 123n */ -BigInt.fromStringExn("123") +BigInt.fromStringExn("123")->assertEqual(123n) -/* returns 0n */ -BigInt.fromStringExn("") +BigInt.fromStringExn("")->assertEqual(0n) -/* returns 17n */ -BigInt.fromStringExn("0x11") +BigInt.fromStringExn("0x11")->assertEqual(17n) -/* returns 3n */ -BigInt.fromStringExn("0b11") +BigInt.fromStringExn("0b11")->assertEqual(3n) -/* returns 9n */ -BigInt.fromStringExn("0o11") +BigInt.fromStringExn("0o11")->assertEqual(9n) /* catch exception */ -try { - BigInt.fromStringExn("a") -} catch { -| Exn.Error(_error) => 0n +switch BigInt.fromStringExn("a") { +| exception Exn.Error(_error) => assert(true) +| _bigInt => assert(false) } ``` */ @@ -51,8 +45,7 @@ See [`toString`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Referen ## Examples ```rescript -/* prints "123" */ -BigInt.toString(123n)->Console.log +BigInt.toString(123n)->assertEqual("123") ``` */ external toString: (bigint, ~radix: int=?) => string = "toString" @@ -67,8 +60,7 @@ Returns a string with a language-sensitive representation of this BigInt value. ## Examples ```rescript -/* prints "123" */ -BigInt.toString(123n)->Console.log +BigInt.toString(123n)->assertEqual("123") ``` */ external toLocaleString: bigint => string = "toLocaleString" From 3ee6c96625e99fc03cf0612b0aa68c27f2ac6ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Tsnobiladz=C3=A9?= Date: Tue, 11 Mar 2025 10:52:50 +0100 Subject: [PATCH 3/3] update reanalyze exception tracking of stdlib Fixes rescript-lang/rescript-core#71 --- CHANGELOG.md | 1 + analysis/reanalyze/src/Exn.ml | 2 +- analysis/reanalyze/src/ExnLib.ml | 204 ++++++------------ .../deadcode/expected/exception.txt | 42 ++-- .../deadcode/src/exception/Exn.res | 17 +- .../deadcode/src/exception/StdlibTest.res | 14 ++ 6 files changed, 110 insertions(+), 170 deletions(-) create mode 100644 tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res diff --git a/CHANGELOG.md b/CHANGELOG.md index e32c672d8a..9254ea4a8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Add `Dict.has` and double `Dict.forEachWithKey`/`Dict.mapValues` performance. https://github.com/rescript-lang/rescript/pull/7316 - Add popover attributes to JsxDOM.domProps. https://github.com/rescript-lang/rescript/pull/7317 - Add `inert` attribute to `JsxDOM.domProps`. https://github.com/rescript-lang/rescript/pull/7326 +- Make reanalyze exception tracking work with the new stdlib. https://github.com/rescript-lang/rescript/pull/7328 #### :boom: Breaking Change diff --git a/analysis/reanalyze/src/Exn.ml b/analysis/reanalyze/src/Exn.ml index 65f055d35d..c715c7f9aa 100644 --- a/analysis/reanalyze/src/Exn.ml +++ b/analysis/reanalyze/src/Exn.ml @@ -8,7 +8,7 @@ let endOfFile = "End_of_file" let exit = "exit" let failure = "Failure" let invalidArgument = "Invalid_argument" -let jsExnError = "Js.Exn.Error" +let jsExnError = "Exn.Error" let matchFailure = "Match_failure" let notFound = "Not_found" let sysError = "Sys_error" diff --git a/analysis/reanalyze/src/ExnLib.ml b/analysis/reanalyze/src/ExnLib.ml index d0ddc5721c..32463bbcde 100644 --- a/analysis/reanalyze/src/ExnLib.ml +++ b/analysis/reanalyze/src/ExnLib.ml @@ -1,19 +1,6 @@ let raisesLibTable : (Name.t, Exceptions.t) Hashtbl.t = let table = Hashtbl.create 15 in let open Exn in - let array = - [ - ("get", [invalidArgument]); - ("set", [invalidArgument]); - ("make", [invalidArgument]); - ("init", [invalidArgument]); - ("make_matrix", [invalidArgument]); - ("fill", [invalidArgument]); - ("blit", [invalidArgument]); - ("iter2", [invalidArgument]); - ("map2", [invalidArgument]); - ] - in let beltArray = [("getExn", [assertFailure]); ("setExn", [assertFailure])] in let beltList = [("getExn", [notFound]); ("headExn", [notFound]); ("tailExn", [notFound])] @@ -49,91 +36,10 @@ let raisesLibTable : (Name.t, Exceptions.t) Hashtbl.t = ("either", [decodeError]); ] in - let buffer = - [ - ("sub", [invalidArgument]); - ("blit", [invalidArgument]); - ("nth", [invalidArgument]); - ("add_substitute", [notFound]); - ("add_channel", [endOfFile]); - ("truncate", [invalidArgument]); - ] - in - let bytes = - [ - ("get", [invalidArgument]); - ("set", [invalidArgument]); - ("create", [invalidArgument]); - ("make", [invalidArgument]); - ("init", [invalidArgument]); - ("sub", [invalidArgument]); - ("sub_string", [invalidArgument]); - ("extend", [invalidArgument]); - ("fill", [invalidArgument]); - ("blit", [invalidArgument]); - ("blit_string", [invalidArgument]); - (* ("concat", [invalidArgument]), if longer than {!Sys.max_string_length} - ("cat", [invalidArgument]), if longer than {!Sys.max_string_length} - ("escaped", [invalidArgument]), if longer than {!Sys.max_string_length} *) - ("index", [notFound]); - ("rindex", [notFound]); - ("index_from", [invalidArgument; notFound]); - ("index_from_opt", [invalidArgument]); - ("rindex_from", [invalidArgument; notFound]); - ("rindex_from_opt", [invalidArgument]); - ("contains_from", [invalidArgument]); - ("rcontains_from", [invalidArgument]); - ] - in - let filename = - [ - ("chop_extension", [invalidArgument]); - ("temp_file", [sysError]); - ("open_temp_file", [sysError]); - ] - in - let hashtbl = [("find", [notFound])] in - let list = - [ - ("hd", [failure]); - ("tl", [failure]); - ("nth", [failure; invalidArgument]); - ("nth_opt", [invalidArgument]); - ("init", [invalidArgument]); - ("iter2", [invalidArgument]); - ("map2", [invalidArgument]); - ("fold_left2", [invalidArgument]); - ("fold_right2", [invalidArgument]); - ("for_all2", [invalidArgument]); - ("exists2", [invalidArgument]); - ("find", [notFound]); - ("assoc", [notFound]); - ("combine", [invalidArgument]); - ] - in - let string = - [ - ("get", [invalidArgument]); - ("set", [invalidArgument]); - ("create", [invalidArgument]); - ("make", [invalidArgument]); - ("init", [invalidArgument]); - ("sub", [invalidArgument]); - ("fill", [invalidArgument]); - (* ("concat", [invalidArgument]), if longer than {!Sys.max_string_length} - ("escaped", [invalidArgument]), if longer than {!Sys.max_string_length} *) - ("index", [notFound]); - ("rindex", [notFound]); - ("index_from", [invalidArgument; notFound]); - ("index_from_opt", [invalidArgument]); - ("rindex_from", [invalidArgument; notFound]); - ("rindex_from_opt", [invalidArgument]); - ("contains_from", [invalidArgument]); - ("rcontains_from", [invalidArgument]); - ] - in let stdlib = [ + ("panic", [jsExnError]); + ("assertEqual", [jsExnError]); ("invalid_arg", [invalidArgument]); ("failwith", [failure]); ("/", [divisionByZero]); @@ -142,29 +48,40 @@ let raisesLibTable : (Name.t, Exceptions.t) Hashtbl.t = ("bool_of_string", [invalidArgument]); ("int_of_string", [failure]); ("float_of_string", [failure]); - ("read_int", [failure]); - ("output", [invalidArgument]); - ("close_out", [sysError]); - ("input_char", [endOfFile]); - ("input_line", [endOfFile]); - ("input", [invalidArgument]); - ("really_input", [endOfFile; invalidArgument]); - ("really_input_string", [endOfFile]); - ("input_byte", [endOfFile]); - ("input_binary_int", [endOfFile]); - ("close_in", [sysError]); - ("exit", [exit]); ] in - let str = + let stdlibBigInt = [("fromStringExn", [jsExnError])] in + let stdlibError = [("raise", [jsExnError])] in + let stdlibExn = + [ + ("raiseError", [jsExnError]); + ("raiseEvalError", [jsExnError]); + ("raiseRangeError", [jsExnError]); + ("raiseReferenceError", [jsExnError]); + ("raiseSyntaxError", [jsExnError]); + ("raiseTypeError", [jsExnError]); + ("raiseUriError", [jsExnError]); + ] + in + let stdlibJson = [ - ("search_forward", [notFound]); - ("search_backward", [notFound]); - ("matched_group", [notFound]); - ("group_beginning", [notFound; invalidArgument]); - ("group_end", [notFound; invalidArgument]); + ("parseExn", [jsExnError]); + ("parseExnWithReviver", [jsExnError]); + ("stringifyAny", [jsExnError]); + ("stringifyAnyWithIndent", [jsExnError]); + ("stringifyAnyWithReplacer", [jsExnError]); + ("stringifyAnyWithReplacerAndIndent", [jsExnError]); + ("stringifyAnyWithFilter", [jsExnError]); + ("stringifyAnyWithFilterAndIndent", [jsExnError]); ] in + let stdlibList = + [("headExn", [notFound]); ("tailExn", [notFound]); ("getExn", [notFound])] + in + let stdlibNull = [("getExn", [invalidArgument])] in + let stdlibNullable = [("getExn", [invalidArgument])] in + let stdlibOption = [("getExn", [jsExnError])] in + let stdlibResult = [("getExn", [notFound])] in let yojsonBasic = [("from_string", [yojsonJsonError])] in let yojsonBasicUtil = [ @@ -183,7 +100,6 @@ let raisesLibTable : (Name.t, Exceptions.t) Hashtbl.t = ] in [ - ("Array", array); ("Belt.Array", beltArray); ("Belt_Array", beltArray); ("Belt.List", beltList); @@ -206,6 +122,11 @@ let raisesLibTable : (Name.t, Exceptions.t) Hashtbl.t = ("Belt_MutableMapString", beltMutableMap); ("Belt.MutableQueue", beltMutableQueue); ("Belt_MutableQueue", beltMutableQueue); + ("Belt_MutableSetInt", beltMutableSet); + ("Belt_MutableSetString", beltMutableSet); + ("Belt.MutableSet", beltMutableSet); + ("Belt.MutableSet.Int", beltMutableSet); + ("Belt.MutableSet.String", beltMutableSet); ("Belt.Option", beltOption); ("Belt_Option", beltOption); ("Belt.Result", beltResult); @@ -218,35 +139,42 @@ let raisesLibTable : (Name.t, Exceptions.t) Hashtbl.t = ("Belt_Set.String", beltSet); ("Belt_SetInt", beltSet); ("Belt_SetString", beltSet); - ("Belt.MutableSet", beltMutableSet); - ("Belt.MutableSet.Int", beltMutableSet); - ("Belt.MutableSet.String", beltMutableSet); - ("MutableSet", beltMutableSet); - ("MutableSet.Int", beltMutableSet); - ("MutableSet.String", beltMutableSet); - ("Belt_MutableSetInt", beltMutableSet); - ("Belt_MutableSetString", beltMutableSet); - ("Buffer", buffer); - ("Bytes", bytes); + ("BigInt", stdlibBigInt); ("Char", [("chr", [invalidArgument])]); - ("Filename", filename); - ("Hashtbl", hashtbl); + ("Error", stdlibError); + ("Exn", stdlibExn); ("Js.Json", [("parseExn", [jsExnError])]); + ("JSON", stdlibJson); ("Json_decode", bsJson); ("Json.Decode", bsJson); - ("List", list); + ("List", stdlibList); + ("MutableSet", beltMutableSet); + ("MutableSet.Int", beltMutableSet); + ("MutableSet.String", beltMutableSet); + ("Null", stdlibNull); + ("Nullable", stdlibNullable); + ("Option", stdlibOption); ("Pervasives", stdlib); + ("Result", stdlibResult); ("Stdlib", stdlib); - ("Stdlib.Array", array); - ("Stdlib.Buffer", buffer); - ("Stdlib.Bytes", bytes); - ("Stdlib.Filename", filename); - ("Stdlib.Hashtbl", hashtbl); - ("Stdlib.List", list); - ("Stdlib.Str", str); - ("Stdlib.String", string); - ("Str", str); - ("String", string); + ("Stdlib_BigInt", stdlibBigInt); + ("Stdlib.BigInt", stdlibBigInt); + ("Stdlib_Error", stdlibError); + ("Stdlib.Error", stdlibError); + ("Stdlib_Exn", stdlibExn); + ("Stdlib.Exn", stdlibExn); + ("Stdlib_JSON", stdlibJson); + ("Stdlib.JSON", stdlibJson); + ("Stdlib_List", stdlibList); + ("Stdlib.List", stdlibList); + ("Stdlib_Null", stdlibNull); + ("Stdlib.Null", stdlibNull); + ("Stdlib_Nullable", stdlibNullable); + ("Stdlib.Nullable", stdlibNullable); + ("Stdlib_Option", stdlibOption); + ("Stdlib.Option", stdlibOption); + ("Stdlib_Result", stdlibResult); + ("Stdlib.Result", stdlibResult); ("Yojson.Basic", yojsonBasic); ("Yojson.Basic.Util", yojsonBasicUtil); ] diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt index daad450be5..9d087d9e01 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt @@ -42,51 +42,51 @@ Exception Analysis Exn.res:73:5-24 - parse_json_from_file might raise Error (Exn.res:76:4) and is not annotated with @raises(Error) + parse_json_from_file might raise Error (Exn.res:75:34) and is not annotated with @raises(Error) Exception Analysis - Exn.res:82:5-11 - reRaise might raise B (Exn.res:84:19) and is not annotated with @raises(B) + Exn.res:80:5-11 + reRaise might raise B (Exn.res:82:19) and is not annotated with @raises(B) Exception Analysis - Exn.res:93:5-22 - raiseInInternalLet might raise A (Exn.res:94:14) and is not annotated with @raises(A) + Exn.res:91:5-22 + raiseInInternalLet might raise A (Exn.res:92:14) and is not annotated with @raises(A) Exception Analysis - Exn.res:98:5-16 - indirectCall might raise Not_found (Exn.res:98:31) and is not annotated with @raises(Not_found) + Exn.res:96:5-16 + indirectCall might raise Not_found (Exn.res:96:25) and is not annotated with @raises(Not_found) Exception Analysis - Exn.res:124:5-16 - severalCases might raise Failure (Exn.res:126:13 Exn.res:127:13 Exn.res:128:15) and is not annotated with @raises(Failure) + Exn.res:121:5-16 + severalCases might raise Failure (Exn.res:123:13 Exn.res:124:13 Exn.res:125:15) and is not annotated with @raises(Failure) Exception Analysis - Exn.res:136:5-23 + Exn.res:133:5-23 redundantAnnotation raises nothing and is annotated with redundant @raises(Invalid_argument) Exception Analysis - Exn.res:138:5-6 - _x might raise A (Exn.res:138:9) and is not annotated with @raises(A) + Exn.res:135:5-6 + _x might raise A (Exn.res:135:9) and is not annotated with @raises(A) Exception Analysis - Exn.res:140:5 - _ might raise A (Exn.res:140:8) and is not annotated with @raises(A) + Exn.res:137:5 + _ might raise A (Exn.res:137:8) and is not annotated with @raises(A) Exception Analysis - Exn.res:142:5-6 - () might raise A (Exn.res:142:9) and is not annotated with @raises(A) + Exn.res:139:5-6 + () might raise A (Exn.res:139:9) and is not annotated with @raises(A) Exception Analysis - Exn.res:144:1-16 - Toplevel expression might raise Not_found (Exn.res:144:0) and is not annotated with @raises(Not_found) + Exn.res:141:1-16 + Toplevel expression might raise Not_found (Exn.res:141:0) and is not annotated with @raises(Not_found) Exception Analysis - Exn.res:154:45-46 + Exn.res:151:46-47 expression does not raise and is annotated with redundant @doesNotRaise Exception Analysis - Exn.res:154:5-21 - onResultPipeWrong might raise Assert_failure (Exn.res:154:48) and is not annotated with @raises(Assert_failure) + Exn.res:151:5-21 + onResultPipeWrong might raise Assert_failure (Exn.res:151:50) and is not annotated with @raises(Assert_failure) Exception Analysis ExnA.res:1:5-7 diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Exn.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Exn.res index 2e08e11146..1a51cda096 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Exn.res +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Exn.res @@ -72,10 +72,8 @@ exception Error(string, string, int) let parse_json_from_file = s => { switch 34 { - | exception Error(p1, p2, e) => - raise(Error(p1, p2, e)) - | v => - v + | exception Error(p1, p2, e) => raise(Error(p1, p2, e)) + | v => v } } @@ -95,15 +93,14 @@ let raiseInInternalLet = b => { a + 34 } -let indirectCall = () => () |> raisesWithAnnotaion +let indirectCall = () => raisesWithAnnotaion() -@raises(Invalid_argument) let array = a => a[2] let id = x => x let tryChar = v => { - try id(Char.chr(v)) |> ignore catch { + try ignore(id(Char.chr(v))) catch { | _ => () } 42 @@ -113,12 +110,12 @@ let tryChar = v => { let raiseAtAt = () => \"@@"(raise, Not_found) @raises(Not_found) -let raisePipe = Not_found |> raise +let raisePipe = raise(Not_found) @raises(Not_found) let raiseArrow = Not_found->raise -@raises(Js.Exn.Error) +@raises(Exn.Error) let bar = () => Js.Json.parseExn("!!!") let severalCases = cases => @@ -151,4 +148,4 @@ let onResult = () => @doesNotRaise Belt.Array.getExn([], 0) let onFunctionPipe = () => []->(@doesNotRaise Belt.Array.getExn)(0) -let onResultPipeWrong = () => @doesNotRaise []->Belt.Array.getExn(0) +let onResultPipeWrong = () => (@doesNotRaise [])->Belt.Array.getExn(0) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res new file mode 100644 index 0000000000..2a74754966 --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res @@ -0,0 +1,14 @@ +@raises(Exn.Error) +let optionGetExn = o => o->Option.getExn + +@raises(Not_found) +let resultGetExn = r => r->Result.getExn + +@raises(Invalid_argument) +let nullGetExn = n => n->Null.getExn + +@raises(Exn.Error) +let bigIntFromStringExn = s => s->BigInt.fromStringExn + +@raises(Exn.Error) +let jsonParseExn = s => s->JSON.parseExn