From aa5e4def155d11f596332dc7c8152f5e1b209cb5 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Sat, 24 May 2025 19:04:23 +0400 Subject: [PATCH 1/3] Rename functions ending with `Exn` to `OrThrow` --- CHANGELOG.md | 10 +++ lib/es6/Stdlib_BigInt.js | 9 +++ lib/es6/Stdlib_Bool.js | 7 +- lib/js/Stdlib_BigInt.js | 9 +++ lib/js/Stdlib_Bool.js | 7 +- runtime/Stdlib_BigInt.res | 72 ++++++++++++++++--- runtime/Stdlib_Bool.res | 7 +- runtime/Stdlib_Bool.resi | 17 +++++ runtime/Stdlib_JSON.res | 7 +- runtime/Stdlib_JSON.resi | 58 ++++++++------- .../deadcode/src/exception/StdlibTest.res | 4 +- tests/docstring_tests/DocTest.res | 2 +- tests/tests/src/core/Core_ObjectTests.mjs | 15 ++-- tests/tests/src/core/Core_TempTests.res | 4 +- tests/tests/src/core/Core_TypedArrayTests.res | 6 +- 15 files changed, 174 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79c368e4f8..b42274c3b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,16 @@ # 12.0.0-alpha.14 (Unreleased) +#### :boom: Breaking Change + +- Rename functions ending with `Exn` to end with `OrThrow`. The old `Exn` functions are now deprecated: + - `Bool.fromStringExn` → `Bool.fromStringOrThrow` + - `BigInt.fromStringExn` → `BigInt.fromStringOrThrow` + - `JSON.parseExn` → `JSON.parseOrThrow` + - Changed `BigInt.fromFloat` to return an option rather than throwing an error. + - Added `BigInt.fromFloatOrThrow` + - Old functions remain available but are marked as deprecated with guidance to use the new `OrThrow` variants. + #### :rocket: New Feature - Add `RegExp.flags`. https://github.com/rescript-lang/rescript/pull/7461 diff --git a/lib/es6/Stdlib_BigInt.js b/lib/es6/Stdlib_BigInt.js index 326071c643..f7cb07f3b3 100644 --- a/lib/es6/Stdlib_BigInt.js +++ b/lib/es6/Stdlib_BigInt.js @@ -1,6 +1,14 @@ +function fromString(value) { + try { + return BigInt(value); + } catch (exn) { + return; + } +} + function fromFloat(value) { try { return BigInt(value); @@ -14,6 +22,7 @@ function toInt(t) { } export { + fromString, fromFloat, toInt, } diff --git a/lib/es6/Stdlib_Bool.js b/lib/es6/Stdlib_Bool.js index 9d19ce0902..2544dd5672 100644 --- a/lib/es6/Stdlib_Bool.js +++ b/lib/es6/Stdlib_Bool.js @@ -20,7 +20,7 @@ function fromString(s) { } } -function fromStringExn(param) { +function fromStringOrThrow(param) { switch (param) { case "false" : return false; @@ -29,15 +29,18 @@ function fromStringExn(param) { default: throw { RE_EXN_ID: "Invalid_argument", - _1: "Bool.fromStringExn: value is neither \"true\" nor \"false\"", + _1: "Bool.fromStringOrThrow: value is neither \"true\" nor \"false\"", Error: new Error() }; } } +let fromStringExn = fromStringOrThrow; + export { toString, fromString, + fromStringOrThrow, fromStringExn, } /* No side effect */ diff --git a/lib/js/Stdlib_BigInt.js b/lib/js/Stdlib_BigInt.js index 7965b80715..e4f9712092 100644 --- a/lib/js/Stdlib_BigInt.js +++ b/lib/js/Stdlib_BigInt.js @@ -1,6 +1,14 @@ 'use strict'; +function fromString(value) { + try { + return BigInt(value); + } catch (exn) { + return; + } +} + function fromFloat(value) { try { return BigInt(value); @@ -13,6 +21,7 @@ function toInt(t) { return Number(t) | 0; } +exports.fromString = fromString; exports.fromFloat = fromFloat; exports.toInt = toInt; /* No side effect */ diff --git a/lib/js/Stdlib_Bool.js b/lib/js/Stdlib_Bool.js index 1cbc4d4216..a7ce1d1ea0 100644 --- a/lib/js/Stdlib_Bool.js +++ b/lib/js/Stdlib_Bool.js @@ -20,7 +20,7 @@ function fromString(s) { } } -function fromStringExn(param) { +function fromStringOrThrow(param) { switch (param) { case "false" : return false; @@ -29,13 +29,16 @@ function fromStringExn(param) { default: throw { RE_EXN_ID: "Invalid_argument", - _1: "Bool.fromStringExn: value is neither \"true\" nor \"false\"", + _1: "Bool.fromStringOrThrow: value is neither \"true\" nor \"false\"", Error: new Error() }; } } +let fromStringExn = fromStringOrThrow; + exports.toString = toString; exports.fromString = fromString; +exports.fromStringOrThrow = fromStringOrThrow; exports.fromStringExn = fromStringExn; /* No side effect */ diff --git a/runtime/Stdlib_BigInt.res b/runtime/Stdlib_BigInt.res index 05ea2a917d..9d73675df1 100644 --- a/runtime/Stdlib_BigInt.res +++ b/runtime/Stdlib_BigInt.res @@ -6,39 +6,89 @@ type t = bigint @val external asIntN: (~width: int, bigint) => bigint = "BigInt.asIntN" @val external asUintN: (~width: int, bigint) => bigint = "BigInt.asUintN" -@val external fromString: string => bigint = "BigInt" - @val /** Parses the given `string` into a `bigint` using JavaScript semantics. Return the -number as a `bigint` if successfully parsed. Uncaught syntax exception otherwise. +number as a `bigint` if successfully parsed. Throws a syntax exception otherwise. ## Examples ```rescript -BigInt.fromStringExn("123")->assertEqual(123n) +BigInt.fromStringOrThrow("123")->assertEqual(123n) -BigInt.fromStringExn("")->assertEqual(0n) +BigInt.fromStringOrThrow("")->assertEqual(0n) -BigInt.fromStringExn("0x11")->assertEqual(17n) +BigInt.fromStringOrThrow("0x11")->assertEqual(17n) -BigInt.fromStringExn("0b11")->assertEqual(3n) +BigInt.fromStringOrThrow("0b11")->assertEqual(3n) -BigInt.fromStringExn("0o11")->assertEqual(9n) +BigInt.fromStringOrThrow("0o11")->assertEqual(9n) /* catch exception */ -switch BigInt.fromStringExn("a") { +switch BigInt.fromStringOrThrow("a") { | exception JsExn(_error) => assert(true) | _bigInt => assert(false) } ``` */ +external fromStringOrThrow: string => bigint = "BigInt" + +/** +Parses the given `string` into a `bigint` using JavaScript semantics. Returns +`Some(bigint)` if the string can be parsed, `None` otherwise. + +## Examples + +```rescript +BigInt.fromString("123")->assertEqual(Some(123n)) + +BigInt.fromString("")->assertEqual(Some(0n)) + +BigInt.fromString("0x11")->assertEqual(Some(17n)) + +BigInt.fromString("0b11")->assertEqual(Some(3n)) + +BigInt.fromString("0o11")->assertEqual(Some(9n)) + +BigInt.fromString("invalid")->assertEqual(None) +``` +*/ +let fromString = (value: string) => { + try Some(fromStringOrThrow(value)) catch { + | _ => None + } +} + +@deprecated("Use `fromStringOrThrow` instead") @val external fromStringExn: string => bigint = "BigInt" + @val external fromInt: int => bigint = "BigInt" -@val external fromFloat: float => bigint = "BigInt" + +@val +/** +Converts a `float` to a `bigint` using JavaScript semantics. +Throws an exception if the float is not an integer or is infinite/NaN. + +## Examples + +```rescript +BigInt.fromFloatOrThrow(123.0)->assertEqual(123n) + +BigInt.fromFloatOrThrow(0.0)->assertEqual(0n) + +BigInt.fromFloatOrThrow(-456.0)->assertEqual(-456n) + +/* This will throw an exception */ +switch BigInt.fromFloatOrThrow(123.5) { +| exception JsExn(_error) => assert(true) +| _bigInt => assert(false) +} +``` +*/ +external fromFloatOrThrow: float => bigint = "BigInt" let fromFloat = (value: float) => { - try Some(fromFloat(value)) catch { + try Some(fromFloatOrThrow(value)) catch { | _ => None } } diff --git a/runtime/Stdlib_Bool.res b/runtime/Stdlib_Bool.res index 27f04f31df..f6b467fbaf 100644 --- a/runtime/Stdlib_Bool.res +++ b/runtime/Stdlib_Bool.res @@ -15,13 +15,16 @@ let fromString = s => { } } -let fromStringExn = param => +let fromStringOrThrow = param => switch param { | "true" => true | "false" => false - | _ => throw(Invalid_argument(`Bool.fromStringExn: value is neither "true" nor "false"`)) + | _ => throw(Invalid_argument(`Bool.fromStringOrThrow: value is neither "true" nor "false"`)) } +@deprecated("Use `fromStringOrThrow` instead") +let fromStringExn = fromStringOrThrow + external compare: (bool, bool) => Stdlib_Ordering.t = "%compare" external equal: (bool, bool) => bool = "%equal" diff --git a/runtime/Stdlib_Bool.resi b/runtime/Stdlib_Bool.resi index 8f69613134..718c944e7e 100644 --- a/runtime/Stdlib_Bool.resi +++ b/runtime/Stdlib_Bool.resi @@ -31,6 +31,22 @@ Bool.fromString("notAValidBoolean")->assertEqual(None) */ let fromString: string => option +/** +Converts a string to a boolean. +Throws an `Invalid_argument` exception if the string is not a valid boolean. + +## Examples +```rescript +Bool.fromStringOrThrow("true")->assertEqual(true) +Bool.fromStringOrThrow("false")->assertEqual(false) +switch Bool.fromStringOrThrow("notAValidBoolean") { +| exception Invalid_argument(_) => assert(true) +| _ => assert(false) +} +``` +*/ +let fromStringOrThrow: string => bool + /** Converts a string to a boolean. Beware, this function will throw an `Invalid_argument` exception @@ -46,6 +62,7 @@ switch Bool.fromStringExn("notAValidBoolean") { } ``` */ +@deprecated("Use `fromStringOrThrow` instead") let fromStringExn: string => bool external compare: (bool, bool) => Stdlib_Ordering.t = "%compare" diff --git a/runtime/Stdlib_JSON.res b/runtime/Stdlib_JSON.res index 3368a65a9c..e7a66bfb64 100644 --- a/runtime/Stdlib_JSON.res +++ b/runtime/Stdlib_JSON.res @@ -10,9 +10,10 @@ type rec t = @unboxed type replacer = Keys(array) | Replacer((string, t) => t) -@raises @val external parseExn: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" -@deprecated("Use `parseExn` with optional parameter instead") @raises @val -external parseExnWithReviver: (string, (string, t) => t) => t = "JSON.parse" +@raises @val external parseOrThrow: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" + +@deprecated("Use `parseOrThrow` instead") @raises @val +external parseExn: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" @val external stringify: (t, ~replacer: replacer=?, ~space: int=?) => string = "JSON.stringify" @deprecated("Use `stringify` with optional parameter instead") @val diff --git a/runtime/Stdlib_JSON.resi b/runtime/Stdlib_JSON.resi index 9b816feb02..71b5b1397c 100644 --- a/runtime/Stdlib_JSON.resi +++ b/runtime/Stdlib_JSON.resi @@ -18,7 +18,7 @@ type rec t = type replacer = Keys(array) | Replacer((string, t) => t) /** -`parseExn(string, ~reviver=?)` +`parseOrThrow(string, ~reviver=?)` Parses a JSON string or throws a JavaScript exception (SyntaxError), if the string isn't valid. The reviver describes how the value should be transformed. It is a function which receives a key and a value. @@ -27,10 +27,10 @@ It returns a JSON type. ## Examples ```rescript try { - let _ = JSON.parseExn(`{"foo":"bar","hello":"world"}`) + let _ = JSON.parseOrThrow(`{"foo":"bar","hello":"world"}`) // { foo: 'bar', hello: 'world' } - let _ = JSON.parseExn("") + let _ = JSON.parseOrThrow("") // error } catch { | JsExn(_) => Console.log("error") @@ -46,10 +46,10 @@ let reviver = (_, value: JSON.t) => let jsonString = `{"hello":"world","someNumber":21}` try { - JSON.parseExn(jsonString, ~reviver)->Console.log + JSON.parseOrThrow(jsonString, ~reviver)->Console.log // { hello: 'WORLD', someNumber: 42 } - JSON.parseExn("", ~reviver)->Console.log + JSON.parseOrThrow("", ~reviver)->Console.log // error } catch { | JsExn(_) => Console.log("error") @@ -62,10 +62,10 @@ try { */ @raises(Exn.t) @val -external parseExn: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" +external parseOrThrow: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" /** -`parseExnWithReviver(string, reviver)` +`parseExn(string, ~reviver=?)` Parses a JSON string or throws a JavaScript exception (SyntaxError), if the string isn't valid. The reviver describes how the value should be transformed. It is a function which receives a key and a value. @@ -73,6 +73,16 @@ It returns a JSON type. ## Examples ```rescript +try { + let _ = JSON.parseExn(`{"foo":"bar","hello":"world"}`) + // { foo: 'bar', hello: 'world' } + + let _ = JSON.parseExn("") + // error +} catch { +| JsExn(_) => Console.log("error") +} + let reviver = (_, value: JSON.t) => switch value { | String(string) => string->String.toUpperCase->JSON.Encode.string @@ -83,10 +93,10 @@ let reviver = (_, value: JSON.t) => let jsonString = `{"hello":"world","someNumber":21}` try { - JSON.parseExnWithReviver(jsonString, reviver)->Console.log + JSON.parseExn(jsonString, ~reviver)->Console.log // { hello: 'WORLD', someNumber: 42 } - JSON.parseExnWithReviver("", reviver)->Console.log + JSON.parseExn("", ~reviver)->Console.log // error } catch { | JsExn(_) => Console.log("error") @@ -95,12 +105,12 @@ try { ## Exceptions -- Raises a SyntaxError if the string isn't valid JSON. +- Raises a SyntaxError (Exn.t) if the string isn't valid JSON. */ -@deprecated("Use `parseExn` with optional parameter instead") +@deprecated("Use `parseOrThrow` instead") @raises(Exn.t) @val -external parseExnWithReviver: (string, (string, t) => t) => t = "JSON.parse" +external parseExn: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" /** `stringify(json, ~replacer=?, ~space=?)` @@ -719,10 +729,10 @@ module Decode: { ## Examples ```rescript - JSON.parseExn(`true`)->JSON.Decode.bool + JSON.parseOrThrow(`true`)->JSON.Decode.bool // Some(true) - JSON.parseExn(`"hello world"`)->JSON.Decode.bool + JSON.parseOrThrow(`"hello world"`)->JSON.Decode.bool // None ``` */ @@ -733,10 +743,10 @@ module Decode: { ## Examples ```rescript - JSON.parseExn(`null`)->JSON.Decode.null + JSON.parseOrThrow(`null`)->JSON.Decode.null // Some(null) - JSON.parseExn(`"hello world"`)->JSON.Decode.null + JSON.parseOrThrow(`"hello world"`)->JSON.Decode.null // None ``` */ @@ -747,10 +757,10 @@ module Decode: { ## Examples ```rescript - JSON.parseExn(`"hello world"`)->JSON.Decode.string + JSON.parseOrThrow(`"hello world"`)->JSON.Decode.string // Some("hello world") - JSON.parseExn(`42`)->JSON.Decode.string + JSON.parseOrThrow(`42`)->JSON.Decode.string // None ``` */ @@ -761,10 +771,10 @@ module Decode: { ## Examples ```rescript - JSON.parseExn(`42.0`)->JSON.Decode.float + JSON.parseOrThrow(`42.0`)->JSON.Decode.float // Some(42.0) - JSON.parseExn(`"hello world"`)->JSON.Decode.float + JSON.parseOrThrow(`"hello world"`)->JSON.Decode.float // None ``` */ @@ -775,10 +785,10 @@ module Decode: { ## Examples ```rescript - JSON.parseExn(`{"foo":"bar"}`)->JSON.Decode.object + JSON.parseOrThrow(`{"foo":"bar"}`)->JSON.Decode.object // Some({ foo: 'bar' }) - JSON.parseExn(`"hello world"`)->JSON.Decode.object + JSON.parseOrThrow(`"hello world"`)->JSON.Decode.object // None ``` */ @@ -789,10 +799,10 @@ module Decode: { ## Examples ```rescript - JSON.parseExn(`["foo", "bar"]`)->JSON.Decode.array + JSON.parseOrThrow(`["foo", "bar"]`)->JSON.Decode.array // Some([ 'foo', 'bar' ]) - JSON.parseExn(`"hello world"`)->JSON.Decode.array + JSON.parseOrThrow(`"hello world"`)->JSON.Decode.array // None ``` */ diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res index 13b1136e82..8a099f432e 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/StdlibTest.res @@ -8,7 +8,7 @@ let resultGetExn = r => r->Result.getExn let nullGetExn = n => n->Null.getExn @raises(JsExn) -let bigIntFromStringExn = s => s->BigInt.fromStringExn +let bigIntFromStringExn = s => s->BigInt.fromStringOrThrow @raises(JsExn) -let jsonParseExn = s => s->JSON.parseExn +let jsonParseExn = s => s->JSON.parseOrThrow diff --git a/tests/docstring_tests/DocTest.res b/tests/docstring_tests/DocTest.res index 305b2ced66..a3f74fe15c 100644 --- a/tests/docstring_tests/DocTest.res +++ b/tests/docstring_tests/DocTest.res @@ -54,7 +54,7 @@ let extractDocFromFile = async file => { try { stdout ->getOutput - ->JSON.parseExn + ->JSON.parseOrThrow ->Docgen.decodeFromJson } catch { | JsExn(_) => JsError.panic(`Failed to generate docstrings from ${file}`) diff --git a/tests/tests/src/core/Core_ObjectTests.mjs b/tests/tests/src/core/Core_ObjectTests.mjs index 0da00125a9..4143a8bef7 100644 --- a/tests/tests/src/core/Core_ObjectTests.mjs +++ b/tests/tests/src/core/Core_ObjectTests.mjs @@ -1,6 +1,7 @@ // Generated by ReScript, PLEASE EDIT WITH CARE import * as Test from "./Test.mjs"; +import * as Stdlib_BigInt from "rescript/lib/es6/Stdlib_BigInt.js"; import * as Stdlib_Option from "rescript/lib/es6/Stdlib_Option.js"; import * as Primitive_object from "rescript/lib/es6/Primitive_object.js"; @@ -344,9 +345,7 @@ Test.run([ "is: zeros" ], Object.is(0.0, -0.0), eq, false); -function mkBig(s) { - return BigInt(s); -} +let mkBig = Stdlib_BigInt.fromString; Test.run([ [ @@ -356,7 +355,7 @@ Test.run([ 32 ], "is: bigint" -], Object.is(BigInt("123456789"), BigInt("123456789")), eq, true); +], Object.is(Stdlib_BigInt.fromString("123456789"), Stdlib_BigInt.fromString("123456789")), eq, true); Test.run([ [ @@ -366,7 +365,7 @@ Test.run([ 32 ], "is: bigint" -], Object.is(BigInt("123489"), BigInt("123456789")), eq, false); +], Object.is(Stdlib_BigInt.fromString("123489"), Stdlib_BigInt.fromString("123456789")), eq, false); Test.run([ [ @@ -376,7 +375,7 @@ Test.run([ 32 ], "is: bigint" -], Object.is(BigInt("000000000"), BigInt("0")), eq, true); +], Object.is(Stdlib_BigInt.fromString("000000000"), Stdlib_BigInt.fromString("0")), eq, true); Test.run([ [ @@ -386,7 +385,7 @@ Test.run([ 32 ], "is: bigint" -], BigInt("123") === BigInt("123"), eq, true); +], Primitive_object.equal(Stdlib_BigInt.fromString("123"), Stdlib_BigInt.fromString("123")), eq, true); Test.run([ [ @@ -396,7 +395,7 @@ Test.run([ 32 ], "is: bigint" -], BigInt("123") === BigInt("123"), eq, true); +], Stdlib_BigInt.fromString("123") === Stdlib_BigInt.fromString("123"), eq, true); Test.run([ [ diff --git a/tests/tests/src/core/Core_TempTests.res b/tests/tests/src/core/Core_TempTests.res index 748e6a1dff..e7182d5585 100644 --- a/tests/tests/src/core/Core_TempTests.res +++ b/tests/tests/src/core/Core_TempTests.res @@ -44,7 +44,7 @@ Console.log("0.1"->Float.fromString) Console.info("") Console.info("JSON") Console.info("---") -let json = JSON.parseExn(`{"foo": "bar"}`) +let json = JSON.parseOrThrow(`{"foo": "bar"}`) Console.log( switch JSON.Classify.classify(json) { | Object(json) => @@ -182,7 +182,7 @@ if globalThis["hello"] !== undefined { let z = Float.mod(1.2, 1.4) -let intFromBigInt = BigInt.fromString("10000000000")->BigInt.toInt +let intFromBigInt = BigInt.fromStringOrThrow("10000000000")->BigInt.toInt module Bugfix = { @obj external foo: (~bar: string=?, unit) => _ = "" diff --git a/tests/tests/src/core/Core_TypedArrayTests.res b/tests/tests/src/core/Core_TypedArrayTests.res index b0719a1f62..64a9385bb8 100644 --- a/tests/tests/src/core/Core_TypedArrayTests.res +++ b/tests/tests/src/core/Core_TypedArrayTests.res @@ -4,9 +4,9 @@ let eq = (a, b) => a == b Test.run(__POS_OF__("bytes per element is 8"), BigInt64Array.Constants.bytesPerElement, eq, 8) -let num1 = BigInt.fromString("123456789") -let num2 = BigInt.fromString("987654321") -let num3 = BigInt.fromString("555555555") +let num1 = BigInt.fromStringOrThrow("123456789") +let num2 = BigInt.fromStringOrThrow("987654321") +let num3 = BigInt.fromStringOrThrow("555555555") let assertTrue = (message, predicate) => { try { From 063b6b372b8886963c24ee9e595b0c5c9fbab41e Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Wed, 28 May 2025 13:25:19 +0400 Subject: [PATCH 2/3] Update reanalize tests --- .../tests-reanalyze/deadcode/expected/exception.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt index 9bd59a72cc..79c748d1a0 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt @@ -95,5 +95,13 @@ Exception Analysis ExternalTest.res:7:5-24 bigIntFromStringExn2 might raise JsExn (ExternalTest.res:7:35) and is not annotated with @raises(JsExn) + + Exception Analysis + StdlibTest.res:11:5-23 + bigIntFromStringExn raises nothing and is annotated with redundant @raises(JsExn) + + Exception Analysis + StdlibTest.res:14:5-16 + jsonParseExn raises nothing and is annotated with redundant @raises(JsExn) - Analysis reported 24 issues (Exception Analysis:24) + Analysis reported 26 issues (Exception Analysis:26) From a2839b52e8d3f21ac3de606bb1c96061418c72d8 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Tue, 3 Jun 2025 09:24:19 +0400 Subject: [PATCH 3/3] Final fixes --- analysis/reanalyze/src/ExnLib.ml | 16 +++- runtime/Stdlib_BigInt.res | 4 +- runtime/Stdlib_JSON.res | 3 + runtime/Stdlib_JSON.resi | 81 +++++++++++-------- .../deadcode/expected/exception.txt | 10 +-- .../not_compiled/expected/DocTemplate.res.txt | 3 +- .../expected/DocTemplate.resi.txt | 3 +- 7 files changed, 71 insertions(+), 49 deletions(-) diff --git a/analysis/reanalyze/src/ExnLib.ml b/analysis/reanalyze/src/ExnLib.ml index 16798d7c36..705f38a125 100644 --- a/analysis/reanalyze/src/ExnLib.ml +++ b/analysis/reanalyze/src/ExnLib.ml @@ -50,8 +50,19 @@ let raisesLibTable : (Name.t, Exceptions.t) Hashtbl.t = ("float_of_string", [failure]); ] in - let stdlibBigInt = [("fromStringExn", [jsExn])] in - let stdlibBool = [("fromStringExn", [invalidArgument])] in + let stdlibBigInt = + [ + ("fromStringExn", [jsExn]); + ("fromStringOrThrow", [jsExn]); + ("fromFloatOrThrow", [jsExn]); + ] + in + let stdlibBool = + [ + ("fromStringExn", [invalidArgument]); + ("fromStringOrThrow", [invalidArgument]); + ] + in let stdlibError = [("raise", [jsExn])] in let stdlibExn = [ @@ -68,6 +79,7 @@ let raisesLibTable : (Name.t, Exceptions.t) Hashtbl.t = [ ("parseExn", [jsExn]); ("parseExnWithReviver", [jsExn]); + ("parseOrThrow", [jsExn]); ("stringifyAny", [jsExn]); ("stringifyAnyWithIndent", [jsExn]); ("stringifyAnyWithReplacer", [jsExn]); diff --git a/runtime/Stdlib_BigInt.res b/runtime/Stdlib_BigInt.res index 243f4a8171..c4518ae579 100644 --- a/runtime/Stdlib_BigInt.res +++ b/runtime/Stdlib_BigInt.res @@ -6,7 +6,6 @@ type t = bigint @val external asIntN: (~width: int, bigint) => bigint = "BigInt.asIntN" @val external asUintN: (~width: int, bigint) => bigint = "BigInt.asUintN" -@val /** Parses the given `string` into a `bigint` using JavaScript semantics. Return the number as a `bigint` if successfully parsed. Throws a syntax exception otherwise. @@ -31,6 +30,7 @@ switch BigInt.fromStringOrThrow("a") { } ``` */ +@val external fromStringOrThrow: string => bigint = "BigInt" /** @@ -64,7 +64,6 @@ external fromStringExn: string => bigint = "BigInt" @val external fromInt: int => bigint = "BigInt" -@val /** Converts a `float` to a `bigint` using JavaScript semantics. Throws an exception if the float is not an integer or is infinite/NaN. @@ -85,6 +84,7 @@ switch BigInt.fromFloatOrThrow(123.5) { } ``` */ +@val external fromFloatOrThrow: float => bigint = "BigInt" let fromFloat = (value: float) => { diff --git a/runtime/Stdlib_JSON.res b/runtime/Stdlib_JSON.res index e7a66bfb64..4535a5f7b3 100644 --- a/runtime/Stdlib_JSON.res +++ b/runtime/Stdlib_JSON.res @@ -15,6 +15,9 @@ type replacer = Keys(array) | Replacer((string, t) => t) @deprecated("Use `parseOrThrow` instead") @raises @val external parseExn: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" +@deprecated("Use `parseOrThrow` with optional parameter instead") @raises @val +external parseExnWithReviver: (string, (string, t) => t) => t = "JSON.parse" + @val external stringify: (t, ~replacer: replacer=?, ~space: int=?) => string = "JSON.stringify" @deprecated("Use `stringify` with optional parameter instead") @val external stringifyWithIndent: (t, @as(json`null`) _, int) => string = "JSON.stringify" diff --git a/runtime/Stdlib_JSON.resi b/runtime/Stdlib_JSON.resi index 71b5b1397c..af6ed6050e 100644 --- a/runtime/Stdlib_JSON.resi +++ b/runtime/Stdlib_JSON.resi @@ -60,8 +60,7 @@ try { - Raises a SyntaxError (Exn.t) if the string isn't valid JSON. */ -@raises(Exn.t) -@val +@raises(Exn.t) @val external parseOrThrow: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" /** @@ -107,11 +106,45 @@ try { - Raises a SyntaxError (Exn.t) if the string isn't valid JSON. */ -@deprecated("Use `parseOrThrow` instead") -@raises(Exn.t) -@val +@deprecated("Use `parseOrThrow` instead") @raises(Exn.t) @val external parseExn: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" +/** +`parseExnWithReviver(string, reviver)` + +Parses a JSON string or throws a JavaScript exception (SyntaxError), if the string isn't valid. +The reviver describes how the value should be transformed. It is a function which receives a key and a value. +It returns a JSON type. + +## Examples +```rescript +let reviver = (_, value: JSON.t) => + switch value { + | String(string) => string->String.toUpperCase->JSON.Encode.string + | Number(number) => (number *. 2.0)->JSON.Encode.float + | _ => value + } + +let jsonString = `{"hello":"world","someNumber":21}` + +JSON.parseExnWithReviver(jsonString, reviver)->Console.log +// { hello: 'WORLD', someNumber: 42 } + +try { + JSON.parseExnWithReviver("", reviver)->Console.log + // error +} catch { +| JsExn(_) => Console.log("error") +} +``` + +## Exceptions + +- Raises a SyntaxError if the string is not a valid JSON. +*/ +@deprecated("Use `parseOrThrow` with optional parameter instead") @raises(Exn.t) @val +external parseExnWithReviver: (string, (string, t) => t) => t = "JSON.parse" + /** `stringify(json, ~replacer=?, ~space=?)` @@ -181,8 +214,7 @@ JSON.stringifyWithIndent(json, 2) // } ``` */ -@deprecated("Use `stringify` with optional parameter instead") -@val +@deprecated("Use `stringify` with optional parameter instead") @val external stringifyWithIndent: (t, @as(json`null`) _, int) => string = "JSON.stringify" /** @@ -214,8 +246,7 @@ JSON.stringifyWithReplacer(json, replacer) // {"foo":"BAR","hello":"WORLD","someNumber":42} ``` */ -@deprecated("Use `stringify` with optional parameter instead") -@val +@deprecated("Use `stringify` with optional parameter instead") @val external stringifyWithReplacer: (t, (string, t) => t) => string = "JSON.stringify" /** @@ -251,8 +282,7 @@ JSON.stringifyWithReplacerAndIndent(json, replacer, 2) // } ``` */ -@deprecated("Use `stringify` with optional parameters instead") -@val +@deprecated("Use `stringify` with optional parameters instead") @val external stringifyWithReplacerAndIndent: (t, (string, t) => t, int) => string = "JSON.stringify" /** @@ -275,8 +305,7 @@ JSON.stringifyWithFilter(json, ["foo", "someNumber"]) // {"foo":"bar","someNumber":42} ``` */ -@deprecated("Use `stringify` with optional parameter instead") -@val +@deprecated("Use `stringify` with optional parameter instead") @val external stringifyWithFilter: (t, array) => string = "JSON.stringify" /** @@ -302,8 +331,7 @@ JSON.stringifyWithFilterAndIndent(json, ["foo", "someNumber"], 2) // } ``` */ -@deprecated("Use `stringify` with optional parameters instead") -@val +@deprecated("Use `stringify` with optional parameters instead") @val external stringifyWithFilterAndIndent: (t, array, int) => string = "JSON.stringify" /** @@ -372,8 +400,7 @@ switch BigInt.fromInt(0)->JSON.stringifyAny { - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ -@raises(Exn.t) -@val +@raises(Exn.t) @val external stringifyAny: ('a, ~replacer: replacer=?, ~space: int=?) => option = "JSON.stringify" @@ -416,9 +443,7 @@ switch BigInt.fromInt(0)->JSON.stringifyAny { - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ -@deprecated("Use `stringifyAny` with optional parameter instead") -@raises(Exn.t) -@val +@deprecated("Use `stringifyAny` with optional parameter instead") @raises(Exn.t) @val external stringifyAnyWithIndent: ('a, @as(json`null`) _, int) => option = "JSON.stringify" /** @@ -465,9 +490,7 @@ switch BigInt.fromInt(0)->JSON.stringifyAny { - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ -@deprecated("Use `stringifyAny` with optional parameter instead") -@raises -@val +@deprecated("Use `stringifyAny` with optional parameter instead") @raises @val external stringifyAnyWithReplacer: ('a, (string, t) => t) => option = "JSON.stringify" /** @@ -515,9 +538,7 @@ switch BigInt.fromInt(0)->JSON.stringifyAny { - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ -@deprecated("Use `stringifyAny` with optional parameters instead") -@raises -@val +@deprecated("Use `stringifyAny` with optional parameters instead") @raises @val external stringifyAnyWithReplacerAndIndent: ('a, (string, t) => t, int) => option = "JSON.stringify" @@ -556,9 +577,7 @@ switch BigInt.fromInt(0)->JSON.stringifyAny { - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ -@deprecated("Use `stringifyAny` with optional parameter instead") -@raises -@val +@deprecated("Use `stringifyAny` with optional parameter instead") @raises @val external stringifyAnyWithFilter: ('a, array) => string = "JSON.stringify" /** @@ -611,9 +630,7 @@ switch BigInt.fromInt(0)->JSON.stringifyAny { - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ -@deprecated("Use `stringifyAny` with optional parameters instead") -@raises -@val +@deprecated("Use `stringifyAny` with optional parameters instead") @raises @val external stringifyAnyWithFilterAndIndent: ('a, array, int) => string = "JSON.stringify" module Classify: { diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt index 79c748d1a0..9bd59a72cc 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt @@ -95,13 +95,5 @@ Exception Analysis ExternalTest.res:7:5-24 bigIntFromStringExn2 might raise JsExn (ExternalTest.res:7:35) and is not annotated with @raises(JsExn) - - Exception Analysis - StdlibTest.res:11:5-23 - bigIntFromStringExn raises nothing and is annotated with redundant @raises(JsExn) - - Exception Analysis - StdlibTest.res:14:5-16 - jsonParseExn raises nothing and is annotated with redundant @raises(JsExn) - Analysis reported 26 issues (Exception Analysis:26) + Analysis reported 24 issues (Exception Analysis:24) diff --git a/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.res.txt b/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.res.txt index fcb084a8ca..f94561bdc5 100644 --- a/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.res.txt +++ b/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.res.txt @@ -24,8 +24,7 @@ newText: /** */ -@unboxed -type name = Name(string) +@unboxed type name = Name(string) Xform not_compiled/DocTemplate.res 8:4 can't find module DocTemplate diff --git a/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.resi.txt b/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.resi.txt index ef4987a7cf..aa8c967590 100644 --- a/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.resi.txt +++ b/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.resi.txt @@ -22,8 +22,7 @@ newText: /** */ -@unboxed -type name = Name(string) +@unboxed type name = Name(string) Xform not_compiled/DocTemplate.resi 8:4 Hit: Add Documentation template