Skip to content

Commit 27e2b3d

Browse files
committed
Move bound-checking requirements into API, not in primitives
1 parent a559ca6 commit 27e2b3d

File tree

1 file changed

+45
-7
lines changed

1 file changed

+45
-7
lines changed

text/0001-int.md

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,13 @@ Let `min_value` be $-2^{31}$ and `max_value` be $2^{31}-1$
4343

4444
### `fromNumber: (x: number) => int`
4545

46-
1. If `x` is JavaScript's `Infinity`, return `max_value`.
47-
2. If `x` is JavaScript's `-Infinity`, return `min_value`.
48-
3. Let `int32` be [`ToInt32`]`(x)`, return `int32`.
49-
50-
Actions 1 and 2 are intended to relax confusion when converting from infinite value directly. (e.g. https://github.com/rescript-lang/rescript/issues/6737) However, it can be omitted if the input is obviously not `Infinity` or `-Infinity`.
46+
1. Let `int32` be [`ToInt32`]`(x)`, return `int32`.
5147

5248
The [`ToInt32`] behavior follows the definition in ECMA-262 as is. ReScript compiler uses `bitwiseOR(number, 0)` in action. This is what appears in the output as `number | 0`, which truncates all special numbers defined in IEEE-754.
5349

50+
The `fromNumber` shouldn't be directly exposed to the users. Applying the [`ToInt32`] operation to special numeric values, such as `Infinity`, can lead to subtle bugs (see example: [issue #6737](https://github.com/rescript-lang/rescript/issues/6737)).
51+
Instead, Public APIs should wrap it and perform bounds-checking, if necessary, either emit errors (explained further in the "API Consideration" section below) or notify the user via compiler warning.
52+
5453
`int` never contains the following values:
5554

5655
- `-0`
@@ -61,6 +60,11 @@ The [`ToInt32`] behavior follows the definition in ECMA-262 as is. ReScript comp
6160

6261
`fromNumber(x)` must be idempotent.
6362

63+
### `minus: (x: int) => int`
64+
65+
1. Let `number` be mathematically $-x$.
66+
2. Let `int32` be `fromNumber(number)`, return `int32`.
67+
6468
### `add: (x: int, y: int) => int`
6569

6670
1. Let `number` be mathematically $x + y$.
@@ -125,16 +129,50 @@ let exponentiate = (x, y) => {
125129
### `abs: (x: int) => int`
126130

127131
1. If `x` is `min_value`, raise `Overflow_value`.
132+
2. If `x` is less than `0`, return `minus(x)`.
133+
3. return `x`.
128134

129135
## API consideration
130136

131137
These primitive operations for `int` often don't work as intended by the user due to the `fromNumber` truncation.
132138

133-
APIs that use this should make it safer by providing appropriate errors with standard types.
139+
Public APIs should make it safer by providing appropriate bunnds-checking, and errors with standard types.
134140

135141
### Standard error types
136142

137-
TBD
143+
```res
144+
type int_error =
145+
/** If the operand represents IEEE-754 `NaN`. */
146+
| NaN
147+
/** If the operation cannot be performed in the bounds of int32. */
148+
| Overflow_value
149+
/** If the operation perform devision value (first argument) by `0`. */
150+
| Divide_by_zero(int)
151+
```
152+
153+
`int_error` can be used as error playload in a `result`, and also exception payload.
154+
155+
```res
156+
exception ConversionError(int_error)
157+
158+
let fromFloatUnsafe = (x: float) => {
159+
if x == Primitive_number.NaN {
160+
throw ConversionError(NaN)
161+
} else if x > Primitive_number.max_int32 || x < Primitive_number.min_int32 {
162+
throw ConversionError(Overflow_value)
163+
} else {
164+
Primitive_int32.fromNumber(x)
165+
}
166+
}
167+
168+
let fromFloat = (x: int) => {
169+
switch fromFloatUnsafe(x) {
170+
| catch ConversionError(e) => Error(e)
171+
| x => Ok(x)
172+
}
173+
}
174+
```
175+
138176

139177
## Questions
140178

0 commit comments

Comments
 (0)