Skip to content

Commit 4bc7266

Browse files
authored
Stricter number parsing (#8129)
* feat(stdlib/float): use number coercion instead of parseFloat in fromString for stricter parsing * chore(stdlib): enable experimental LetUnwrap feature * feat(stdlib/int): use number coercion instead of parseInt in fromString for stricter number parsing * docs: update changelog * add comment about number coercion
1 parent 8ab2246 commit 4bc7266

13 files changed

Lines changed: 58 additions & 58 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
#### :boom: Breaking Change
1616

17+
- `Int.fromString` and `Float.fromString` use stricter number parsing and no longer uses an explicit radix argument, but instead supports parsing hexadecimal, binary and exponential notation.
18+
1719
#### :eyeglasses: Spec Compliance
1820

1921
#### :rocket: New Feature

packages/@rescript/runtime/Stdlib_Float.res

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ external toPrecisionWithPrecision: (float, ~digits: int) => string = "toPrecisio
5858
external toStringWithRadix: (float, ~radix: int) => string = "toString"
5959
@send external toLocaleString: float => string = "toLocaleString"
6060

61-
let fromString = i =>
62-
switch parseFloat(i) {
63-
| i if isNaN(i) => None
64-
| i => Some(i)
65-
}
61+
let fromString: string => option<float> = %raw(`str => {
62+
if (!str || !str.trim()) return;
63+
let num = +str; // Number coercion, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_coercion
64+
return isNaN(num) ? undefined : num;
65+
}`)
6666

6767
external toInt: float => int = "%intoffloat"
6868
external fromInt: int => float = "%identity"

packages/@rescript/runtime/Stdlib_Int.res

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,9 @@ external toStringWithRadix: (int, ~radix: int) => string = "toString"
4545
external toFloat: int => float = "%identity"
4646
external fromFloat: float => int = "%intoffloat"
4747

48-
let fromString = (x, ~radix=?) => {
49-
let maybeInt = switch radix {
50-
| Some(radix) => Stdlib_Float.parseInt(x, ~radix)
51-
| None => Stdlib_Float.parseInt(x)
52-
}
53-
54-
if Stdlib_Float.isNaN(maybeInt) {
55-
None
56-
} else if maybeInt > Constants.maxValue->toFloat || maybeInt < Constants.minValue->toFloat {
57-
None
58-
} else {
59-
let asInt = fromFloat(maybeInt)
60-
Some(asInt)
61-
}
48+
let fromString: string => option<int> = str => {
49+
let? Some(num) = str->Stdlib_Float.fromString
50+
num === num->fromFloat->toFloat && Stdlib_Float.isFinite(num) ? Some(num->fromFloat) : None
6251
}
6352

6453
external mod: (int, int) => int = "%modint"

packages/@rescript/runtime/Stdlib_Int.resi

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,18 +295,20 @@ Int.fromFloat(0.9999) == 0
295295
external fromFloat: float => int = "%intoffloat"
296296

297297
/**
298-
`fromString(str, ~radix=?)` return an `option<int>` representing the given value
299-
`str`. `~radix` specifies the radix base to use for the formatted number.
298+
`fromString(str)` return an `option<int>` representing the given value
299+
`str`. Hexadecimal, binary and exponential notation is supported.
300300
301301
## Examples
302302
303303
```rescript
304304
Int.fromString("0") == Some(0)
305+
Int.fromString("0x6") == Some(6)
306+
Int.fromString("2.5e2") == Some(250)
305307
Int.fromString("NaN") == None
306-
Int.fromString("6", ~radix=2) == None
308+
Int.fromString("0b6") == None
307309
```
308310
*/
309-
let fromString: (string, ~radix: int=?) => option<int>
311+
let fromString: string => option<int>
310312

311313
/**
312314
`mod(n1, n2)` calculates the modulo (remainder after division) of two integers.

packages/@rescript/runtime/lib/es6/Stdlib_Float.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@
33

44
let Constants = {};
55

6-
function fromString(i) {
7-
let i$1 = parseFloat(i);
8-
if (Number.isNaN(i$1)) {
9-
return;
10-
} else {
11-
return i$1;
12-
}
13-
}
6+
let fromString = (str => {
7+
if (!str || !str.trim()) return;
8+
let num = +str; // Number coercion, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_coercion
9+
return isNaN(num) ? undefined : num;
10+
});
1411

1512
function clamp(min, max, value) {
1613
let value$1 = max !== undefined && max < value ? max : value;

packages/@rescript/runtime/lib/es6/Stdlib_Int.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11

22

33
import * as Stdlib_Array from "./Stdlib_Array.js";
4+
import * as Stdlib_Float from "./Stdlib_Float.js";
45

5-
function fromString(x, radix) {
6-
let maybeInt = radix !== undefined ? parseInt(x, radix) : parseInt(x);
7-
if (Number.isNaN(maybeInt) || maybeInt > 2147483647 || maybeInt < -2147483648) {
8-
return;
6+
function fromString(str) {
7+
let num = Stdlib_Float.fromString(str);
8+
if (num !== undefined) {
9+
if (num === (num | 0) && isFinite(num)) {
10+
return num | 0;
11+
} else {
12+
return;
13+
}
914
} else {
10-
return maybeInt | 0;
15+
return num;
1116
}
1217
}
1318

packages/@rescript/runtime/lib/js/Stdlib_Float.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@
33

44
let Constants = {};
55

6-
function fromString(i) {
7-
let i$1 = parseFloat(i);
8-
if (Number.isNaN(i$1)) {
9-
return;
10-
} else {
11-
return i$1;
12-
}
13-
}
6+
let fromString = (str => {
7+
if (!str || !str.trim()) return;
8+
let num = +str; // Number coercion, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_coercion
9+
return isNaN(num) ? undefined : num;
10+
});
1411

1512
function clamp(min, max, value) {
1613
let value$1 = max !== undefined && max < value ? max : value;

packages/@rescript/runtime/lib/js/Stdlib_Int.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
'use strict';
22

33
let Stdlib_Array = require("./Stdlib_Array.js");
4+
let Stdlib_Float = require("./Stdlib_Float.js");
45

5-
function fromString(x, radix) {
6-
let maybeInt = radix !== undefined ? parseInt(x, radix) : parseInt(x);
7-
if (Number.isNaN(maybeInt) || maybeInt > 2147483647 || maybeInt < -2147483648) {
8-
return;
6+
function fromString(str) {
7+
let num = Stdlib_Float.fromString(str);
8+
if (num !== undefined) {
9+
if (num === (num | 0) && isFinite(num)) {
10+
return num | 0;
11+
} else {
12+
return;
13+
}
914
} else {
10-
return maybeInt | 0;
15+
return num;
1116
}
1217
}
1318

packages/@rescript/runtime/rescript.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
22
"name": "@rescript/runtime",
3+
"experimental-features": {
4+
"LetUnwrap": true
5+
},
36
"sources": [
47
{
58
"dir": "."

tests/docstring_tests/DocTest.res.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)