Add Result
Adds a direct translation of Rust's Result type, for later use in BuildHIR and possibly other passes
Lauren Tan committed
Jan 12, 2023 at 19:35 UTC
73f09248fb9f6642f89580e3de7cfcb37b605186
2 files changed
+374
compiler/forget/src/__tests__/Result-test.ts
new
+130
@@ -0,0 +1,130 @@
1
+import { Err, Ok, Result } from "../lib/Result";
2
+
3
+function addMax10(a: number, b: number): Result<number, string> {
4
+ const n = a + b;
5
+ return n > 10 ? Err(`${n} is too high`) : Ok(n);
6
+}
7
+
8
+function onlyFoo(foo: string): Result<string, string> {
9
+ return foo === "foo" ? Ok(foo) : Err(foo);
10
+}
11
+
12
+describe("Result", () => {
13
+ test(".map", () => {
14
+ expect(addMax10(1, 1).map((n) => n * 2)).toEqual(Ok(4));
15
+ expect(addMax10(10, 10).map((n) => n * 2)).toEqual(Err("20 is too high"));
16
+ });
17
+
18
+ test(".mapErr", () => {
19
+ expect(addMax10(1, 1).mapErr((e) => `not a number: ${e}`)).toEqual(Ok(2));
20
+ expect(addMax10(10, 10).mapErr((e) => `couldn't add: ${e}`)).toEqual(
21
+ Err("couldn't add: 20 is too high")
22
+ );
23
+ });
24
+
25
+ test(".mapOr", () => {
26
+ expect(onlyFoo("foo").mapOr(42, (v) => v.length)).toEqual(3);
27
+ expect(onlyFoo("bar").mapOr(42, (v) => v.length)).toEqual(42);
28
+ });
29
+
30
+ test(".mapOrElse", () => {
31
+ expect(
32
+ onlyFoo("foo").mapOrElse(
33
+ () => 42,
34
+ (v) => v.length
35
+ )
36
+ ).toEqual(3);
37
+ expect(
38
+ onlyFoo("bar").mapOrElse(
39
+ () => 42,
40
+ (v) => v.length
41
+ )
42
+ ).toEqual(42);
43
+ });
44
+
45
+ test(".andThen", () => {
46
+ expect(addMax10(1, 1).andThen((n) => Ok(n * 2))).toEqual(Ok(4));
47
+ expect(addMax10(10, 10).andThen((n) => Ok(n * 2))).toEqual(
48
+ Err("20 is too high")
49
+ );
50
+ });
51
+
52
+ test(".and", () => {
53
+ expect(addMax10(1, 1).and(Ok(4))).toEqual(Ok(4));
54
+ expect(addMax10(10, 10).and(Ok(4))).toEqual(Err("20 is too high"));
55
+ expect(addMax10(1, 1).and(Err("hehe"))).toEqual(Err("hehe"));
56
+ expect(addMax10(10, 10).and(Err("hehe"))).toEqual(Err("20 is too high"));
57
+ });
58
+
59
+ test(".or", () => {
60
+ expect(addMax10(1, 1).or(Ok(4))).toEqual(Ok(2));
61
+ expect(addMax10(10, 10).or(Ok(4))).toEqual(Ok(4));
62
+ expect(addMax10(1, 1).or(Err("hehe"))).toEqual(Ok(2));
63
+ expect(addMax10(10, 10).or(Err("hehe"))).toEqual(Err("hehe"));
64
+ });
65
+
66
+ test(".orElse", () => {
67
+ expect(addMax10(1, 1).orElse((str) => Err(str.toUpperCase()))).toEqual(
68
+ Ok(2)
69
+ );
70
+ expect(addMax10(10, 10).orElse((str) => Err(str.toUpperCase()))).toEqual(
71
+ Err("20 IS TOO HIGH")
72
+ );
73
+ });
74
+
75
+ test(".isOk", () => {
76
+ expect(addMax10(1, 1).isOk()).toBeTruthy();
77
+ expect(addMax10(10, 10).isOk()).toBeFalsy();
78
+ });
79
+
80
+ test(".isErr", () => {
81
+ expect(addMax10(1, 1).isErr()).toBeFalsy();
82
+ expect(addMax10(10, 10).isErr()).toBeTruthy();
83
+ });
84
+
85
+ test(".expect", () => {
86
+ expect(addMax10(1, 1).expect("a number under 10")).toEqual(2);
87
+ expect(() => {
88
+ addMax10(10, 10).expect("a number under 10");
89
+ }).toThrowErrorMatchingInlineSnapshot(
90
+ `"a number under 10: 20 is too high"`
91
+ );
92
+ });
93
+
94
+ test(".expectErr", () => {
95
+ expect(() => {
96
+ addMax10(1, 1).expectErr("a number under 10");
97
+ }).toThrowErrorMatchingInlineSnapshot(`"a number under 10: 2"`);
98
+ expect(addMax10(10, 10).expectErr("a number under 10")).toEqual(
99
+ "20 is too high"
100
+ );
101
+ });
102
+
103
+ test(".unwrap", () => {
104
+ expect(addMax10(1, 1).unwrap()).toEqual(2);
105
+ expect(() => {
106
+ addMax10(10, 10).unwrap();
107
+ }).toThrowErrorMatchingInlineSnapshot(
108
+ `"Can't unwrap \`Err\` to \`Ok\`: 20 is too high"`
109
+ );
110
+ });
111
+
112
+ test(".unwrapOr", () => {
113
+ expect(addMax10(1, 1).unwrapOr(4)).toEqual(2);
114
+ expect(addMax10(10, 10).unwrapOr(4)).toEqual(4);
115
+ });
116
+
117
+ test(".unwrapOrElse", () => {
118
+ expect(addMax10(1, 1).unwrapOrElse(() => 4)).toEqual(2);
119
+ expect(addMax10(10, 10).unwrapOrElse((s) => s.length)).toEqual(14);
120
+ });
121
+
122
+ test(".unwrapErr", () => {
123
+ expect(() => {
124
+ addMax10(1, 1).unwrapErr();
125
+ }).toThrowErrorMatchingInlineSnapshot(
126
+ `"Can't unwrap \`Ok\` to \`Err\`: 2"`
127
+ );
128
+ expect(addMax10(10, 10).unwrapErr()).toEqual("20 is too high");
129
+ });
130
+});
compiler/forget/src/lib/Result.ts
new
+244
@@ -0,0 +1,244 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+// Direct translation of Rust's Result type, although some ownership related methods are omitted.
9
+export interface Result<T, E> {
10
+ /**
11
+ * Maps a `Result<T, E>` to `Result<U, E>` by applying a function to a contained `Ok` value,
12
+ * leaving an `Err` value untouched.
13
+ *
14
+ * This function can be used to compose the results of two functions.
15
+ */
16
+ map<U>(fn: (val: T) => U): Result<U, E>;
17
+ /**
18
+ * Maps a `Result<T, E>` to `Result<T, F>` by applying a function to a contained `Err` value,
19
+ * leaving an `Ok` value untouched.
20
+ *
21
+ * This function can be used to pass through a successful result while handling an error.
22
+ */
23
+ mapErr<F>(fn: (val: E) => F): Result<T, F>;
24
+ /**
25
+ * Returns the provided default (if `Err`), or applies a function to the contained value
26
+ * (if `Ok`).
27
+ *
28
+ * Arguments passed to {@link mapOr} are eagerly evaluated; if you are passing the result of a
29
+ * function call, it is recommended to use {@link mapOrElse}, which is lazily evaluated.
30
+ */
31
+ mapOr<U>(fallback: U, fn: (val: T) => U): U;
32
+ /**
33
+ * Maps a `Result<T, E>` to `U` by applying fallback function default to a contained `Err` value,
34
+ * or function `fn` to a contained `Ok` value.
35
+ *
36
+ * This function can be used to unpack a successful result while handling an error.
37
+ */
38
+ mapOrElse<U>(fallback: () => U, fn: (val: T) => U): U;
39
+ /**
40
+ * Calls `fn` if the result is `Ok`, otherwise returns the `Err` value of self.
41
+ *
42
+ * This function can be used for control flow based on Result values.
43
+ */
44
+ andThen<U>(fn: (val: T) => Result<U, E>): Result<U, E>;
45
+ /**
46
+ * Returns res if the result is `Ok`, otherwise returns the `Err` value of self.
47
+ *
48
+ * Arguments passed to {@link and} are eagerly evaluated; if you are passing the result of a
49
+ * function call, it is recommended to use {@link andThen}, which is lazily evaluated.
50
+ */
51
+ and<U>(res: Result<U, E>): Result<U, E>;
52
+ /**
53
+ * Returns `res` if the result is `Err`, otherwise returns the `Ok` value of self.
54
+ *
55
+ * Arguments passed to {@link or} are eagerly evaluated; if you are passing the result of a
56
+ * function call, it is recommended to use {@link orElse}, which is lazily evaluated.
57
+ */
58
+ or(res: Result<T, E>): Result<T, E>;
59
+ /**
60
+ * Calls `fn` if the result is `Err`, otherwise returns the `Ok` value of self.
61
+ *
62
+ * This function can be used for control flow based on result values.
63
+ */
64
+ orElse<F>(fn: (val: E) => Result<T, F>): Result<T, F>;
65
+ /**
66
+ * Returns `true` if the result is `Ok`.
67
+ */
68
+ isOk(): this is OkImpl<T>;
69
+ /**
70
+ * Returns `true` if the result is `Err`.
71
+ */
72
+ isErr(): this is ErrImpl<E>;
73
+ /**
74
+ * Returns the contained `Ok` value or throws.
75
+ */
76
+ expect(msg: string): T;
77
+ /**
78
+ * Returns the contained `Err` value or throws.
79
+ */
80
+ expectErr(msg: string): E;
81
+ /**
82
+ * Returns the contained `Ok` value.
83
+ */
84
+ unwrap(): T;
85
+ /**
86
+ * Returns the contained `Ok` value or a provided default.
87
+ *
88
+ * Arguments passed to {@link unwrapOr} are eagerly evaluated; if you are passing the result of a
89
+ * function call, it is recommended to use {@link unwrapOrElse}, which is lazily evaluated.
90
+ */
91
+ unwrapOr(fallback: T): T;
92
+ /**
93
+ * Returns the contained `Ok` value or computes it from a closure.
94
+ */
95
+ unwrapOrElse(fallback: (val: E) => T): T;
96
+ /**
97
+ * Returns the contained `Err` value or throws.
98
+ */
99
+ unwrapErr(): E;
100
+}
101
+
102
+export function Ok<T>(val: T): OkImpl<T> {
103
+ return new OkImpl(val);
104
+}
105
+
106
+class OkImpl<T> implements Result<T, never> {
107
+ constructor(private val: T) {}
108
+
109
+ map<U>(fn: (val: T) => U): Result<U, never> {
110
+ return new OkImpl(fn(this.val));
111
+ }
112
+
113
+ mapErr<F>(_fn: (val: never) => F): Result<T, F> {
114
+ return this;
115
+ }
116
+
117
+ mapOr<U>(_fallback: U, fn: (val: T) => U): U {
118
+ return fn(this.val);
119
+ }
120
+
121
+ mapOrElse<U>(_fallback: () => U, fn: (val: T) => U): U {
122
+ return fn(this.val);
123
+ }
124
+
125
+ andThen<U>(fn: (val: T) => Result<U, never>): Result<U, never> {
126
+ return fn(this.val);
127
+ }
128
+
129
+ and<U>(res: Result<U, never>): Result<U, never> {
130
+ return res;
131
+ }
132
+
133
+ or(_res: Result<T, never>): Result<T, never> {
134
+ return this;
135
+ }
136
+
137
+ orElse<F>(_fn: (val: never) => Result<T, F>): Result<T, F> {
138
+ return this;
139
+ }
140
+
141
+ isOk(): boolean {
142
+ return true;
143
+ }
144
+
145
+ isErr(): boolean {
146
+ return false;
147
+ }
148
+
149
+ expect(_msg: string): T {
150
+ return this.val;
151
+ }
152
+
153
+ expectErr(msg: string): never {
154
+ throw new Error(`${msg}: ${this.val}`);
155
+ }
156
+
157
+ unwrap(): T {
158
+ return this.val;
159
+ }
160
+
161
+ unwrapOr(_fallback: T): T {
162
+ return this.val;
163
+ }
164
+
165
+ unwrapOrElse(_fallback: (val: never) => T): T {
166
+ return this.val;
167
+ }
168
+
169
+ unwrapErr(): never {
170
+ throw new Error(`Can't unwrap \`Ok\` to \`Err\`: ${this.val}`);
171
+ }
172
+}
173
+
174
+export function Err<E>(val: E): ErrImpl<E> {
175
+ return new ErrImpl(val);
176
+}
177
+
178
+class ErrImpl<E> implements Result<never, E> {
179
+ constructor(private val: E) {}
180
+
181
+ map<U>(_fn: (val: never) => U): Result<U, E> {
182
+ return this;
183
+ }
184
+
185
+ mapErr<F>(fn: (val: E) => F): Result<never, F> {
186
+ return new ErrImpl(fn(this.val));
187
+ }
188
+
189
+ mapOr<U>(fallback: U, _fn: (val: never) => U): U {
190
+ return fallback;
191
+ }
192
+
193
+ mapOrElse<U>(fallback: () => U, _fn: (val: never) => U): U {
194
+ return fallback();
195
+ }
196
+
197
+ andThen<U>(_fn: (val: never) => Result<U, E>): Result<U, E> {
198
+ return this;
199
+ }
200
+
201
+ and<U>(_res: Result<U, E>): Result<U, E> {
202
+ return this;
203
+ }
204
+
205
+ or(res: Result<never, E>): Result<never, E> {
206
+ return res;
207
+ }
208
+
209
+ orElse<F>(fn: (val: E) => ErrImpl<F>): Result<never, F> {
210
+ return fn(this.val);
211
+ }
212
+
213
+ isOk(): boolean {
214
+ return false;
215
+ }
216
+
217
+ isErr(): boolean {
218
+ return true;
219
+ }
220
+
221
+ expect(msg: string): never {
222
+ throw new Error(`${msg}: ${this.val}`);
223
+ }
224
+
225
+ expectErr(_msg: string): E {
226
+ return this.val;
227
+ }
228
+
229
+ unwrap(): never {
230
+ throw new Error(`Can't unwrap \`Err\` to \`Ok\`: ${this.val}`);
231
+ }
232
+
233
+ unwrapOr<T>(fallback: T): T {
234
+ return fallback;
235
+ }
236
+
237
+ unwrapOrElse<T>(fallback: (val: E) => T): T {
238
+ return fallback(this.val);
239
+ }
240
+
241
+ unwrapErr(): E {
242
+ return this.val;
243
+ }
244
+}