@samitouri / QOS-React-2 / commits / 73f09248fb

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 +}