diff --git a/CHANGELOG.md b/CHANGELOG.md index e3c86559c4..1dcb70b30e 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/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/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 085dc767a9..c4518ae579 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" - /** 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) } ``` */ @val +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" + +/** +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) +} +``` +*/ +@val +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..4535a5f7b3 100644 --- a/runtime/Stdlib_JSON.res +++ b/runtime/Stdlib_JSON.res @@ -10,8 +10,12 @@ 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 +@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" + +@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" diff --git a/runtime/Stdlib_JSON.resi b/runtime/Stdlib_JSON.resi index cc8703c004..af6ed6050e 100644 --- a/runtime/Stdlib_JSON.resi +++ b/runtime/Stdlib_JSON.resi @@ -17,6 +17,52 @@ type rec t = @unboxed type replacer = Keys(array) | Replacer((string, t) => t) +/** +`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. +It returns a JSON type. + +## Examples +```rescript +try { + let _ = JSON.parseOrThrow(`{"foo":"bar","hello":"world"}`) + // { foo: 'bar', hello: 'world' } + + let _ = JSON.parseOrThrow("") + // error +} catch { +| JsExn(_) => Console.log("error") +} + +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}` + +try { + JSON.parseOrThrow(jsonString, ~reviver)->Console.log + // { hello: 'WORLD', someNumber: 42 } + + JSON.parseOrThrow("", ~reviver)->Console.log + // error +} catch { +| JsExn(_) => Console.log("error") +} +``` + +## Exceptions + +- Raises a SyntaxError (Exn.t) if the string isn't valid JSON. +*/ +@raises(Exn.t) @val +external parseOrThrow: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" + /** `parseExn(string, ~reviver=?)` @@ -60,7 +106,7 @@ try { - Raises a SyntaxError (Exn.t) if the string isn't valid JSON. */ -@raises(Exn.t) @val +@deprecated("Use `parseOrThrow` instead") @raises(Exn.t) @val external parseExn: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" /** @@ -81,10 +127,10 @@ let reviver = (_, value: JSON.t) => let jsonString = `{"hello":"world","someNumber":21}` -try { - JSON.parseExnWithReviver(jsonString, reviver)->Console.log - // { hello: 'WORLD', someNumber: 42 } +JSON.parseExnWithReviver(jsonString, reviver)->Console.log +// { hello: 'WORLD', someNumber: 42 } +try { JSON.parseExnWithReviver("", reviver)->Console.log // error } catch { @@ -94,9 +140,9 @@ try { ## Exceptions -- Raises a SyntaxError if the string isn't valid JSON. +- Raises a SyntaxError if the string is not a valid JSON. */ -@deprecated("Use `parseExn` with optional parameter instead") @raises(Exn.t) @val +@deprecated("Use `parseOrThrow` with optional parameter instead") @raises(Exn.t) @val external parseExnWithReviver: (string, (string, t) => t) => t = "JSON.parse" /** @@ -700,10 +746,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 ``` */ @@ -714,10 +760,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 ``` */ @@ -728,10 +774,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 ``` */ @@ -742,10 +788,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 ``` */ @@ -756,10 +802,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 ``` */ @@ -770,10 +816,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/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 diff --git a/tests/docstring_tests/DocTest.res b/tests/docstring_tests/DocTest.res index 18fac8a5da..b38b8ecb04 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 {