You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: text/0004-interoperable-exceptions.md
+137-4Lines changed: 137 additions & 4 deletions
Original file line number
Diff line number
Diff line change
@@ -11,14 +11,72 @@ Improve exception syntax to ensure exceptions from JavaScript are safely convert
11
11
12
12
## Motivation
13
13
14
-
ReScript provides a convenient exception syntax. But it's not really useful.
14
+
In the JavaScript/TypeScript world, Exceptions are one of the most akward syntax to use.
15
15
16
-
However, in the ReScript ecosystem, the `result` type is generally preferred over exceptions, so this syntax is rarely used.
16
+
- Exception handling is only available in the try-catch statement, which is not an expression.
17
+
- Since JavaScript allows to throw any values, TypeScript only allows `any` or `unknown` as its type.
18
+
- Users have to hoist the `let` binding themselves and type inference will not work automatically.
17
19
18
-
On the other hand, since it's not compatible with JavaScript exceptions, there is no way to handle executions that may throw JavaScript exceptions safely with exception syntax.
20
+
### TypeScript example
21
+
22
+
```ts
23
+
function isTypeError(exn:unknown):exnisTypeError {
24
+
returnexninstanceofTypeError;
25
+
}
26
+
27
+
// Or using third-party validator library for more complex structs.
28
+
import*asSfrom"sury";
29
+
30
+
// E.g. https://nodejs.org/api/errors.html#nodejs-error-codes
31
+
const AccessDeninedErrorSchema =S.schema({
32
+
code: S.literal("ERR_ACCESS_DENINED"),
33
+
errno: S.number,
34
+
message: S.string,
35
+
});
36
+
37
+
let result:ReturnType<typeofmaybeThrow>;
38
+
try {
39
+
result=maybeThrow()
40
+
} catch (exn:unknown) {
41
+
let result =S.safe(() =>S.parseOrThrow(exn, AccessDeninedError));
42
+
if (result.sucess) {
43
+
let error =exn.value;
44
+
// ?^ S.Output<typeof AccessDeninedErrorSchema>
45
+
} elseif (isTypeError(exn)) {
46
+
let error =exn;
47
+
// ?^ TypeError
48
+
}
49
+
throwexn;
50
+
}
51
+
```
52
+
53
+
ReScript have way better exception syntax.
54
+
55
+
```res
56
+
exception AccessDeninedError(exn)
57
+
exception TypeError(exn)
58
+
59
+
let result = try {
60
+
maybeThrow()
61
+
} catch {
62
+
| AccessDeninedError(exn) => // ...
63
+
| TypeError(exn) => // ...
64
+
}
65
+
66
+
// or in pattern-matching
67
+
let result = switch maybeThrow() {
68
+
| exception AccessDeninedError(exn) => // ...
69
+
| Ok(value) => // ...
70
+
| _ => // ...
71
+
}
72
+
```
73
+
74
+
But it's not really useful because it's not compatible with JavaScript exceptions, there is no way to handle executions that may throw JavaScript exceptions safely with exception syntax.
19
75
20
76
## Rationale
21
77
78
+
Make the error representation checking behavior consistent and customizable with a new attribute syntax.
79
+
22
80
When the compiler processes a `catch` statement, it performs a runtime type check to ensure that the caught value is compatible with the special runtime representation of the ReScript exception.
23
81
24
82
Simply by allowing customization of the runtime type checking, we can make ReScript exceptions interoperable with virtually any JavaScript value.
@@ -42,14 +100,25 @@ exception Invalid(t1, t2) when fn
42
100
43
101
And the identifier in the `when` clause must be a valid binding with type `unknown => bool`.
44
102
103
+
#### Keyword considerations
104
+
105
+
Keywords are only used in the context of exceptions, so we are free to choose them.
106
+
107
+
-`when`: Highlighting works because we've used it in old syntax.
108
+
-`if`: It would be suitable for reducing the number of tokens, but it can be confusing because it looks different from an if expression grammar.
109
+
-`with`
110
+
-`using`
111
+
112
+
Or use an atribute like `@check(fn)`.
113
+
45
114
### Semantics
46
115
47
116
The compiler uses the function in the `when` clause to determine if the caught value can be safely coerced to the expected payload type before entering the specific catch branch.
// Compiler can pass additional arguments for internal usage.
191
+
Symbol.for("Module.ResError"),
192
+
)) {
193
+
result =recover(exn);
194
+
} elseif (JsError.isJsError(exn)) {
195
+
result =recoverFromError(error)
196
+
} else {
197
+
throw exn;
198
+
}
199
+
}
200
+
```
201
+
202
+
203
+
## Questions
204
+
205
+
### Could it use untagged variants?
206
+
207
+
This is similar to the idea of untagged variants match. If we can make the untagged variants are fully customizable, we could leverage the same mechanism like:
208
+
209
+
```res
210
+
@untagged
211
+
type t =
212
+
| @check(isResError) ResError(t)
213
+
| @check(JsError.isJsError) JsError(JsError.t)
214
+
215
+
exception ResException(t)
216
+
```
217
+
218
+
However, variant matches must guarantee exhaustiveness; exception matches are not.
0 commit comments