From 885112a5ee41eaf648ce39cf326092f1188af840 Mon Sep 17 00:00:00 2001 From: braxtonhall Date: Thu, 29 Jun 2023 14:50:09 +0200 Subject: [PATCH 1/5] OrElse callback should be able to change Ok type --- src/result-async.ts | 10 +++++++--- src/result.ts | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/result-async.ts b/src/result-async.ts index 87285455..eaaea17d 100644 --- a/src/result-async.ts +++ b/src/result-async.ts @@ -113,9 +113,13 @@ export class ResultAsync implements PromiseLike> { ) } - orElse>(f: (e: E) => R): ResultAsync> - orElse>(f: (e: E) => R): ResultAsync> - orElse(f: (e: E) => Result | ResultAsync): ResultAsync + orElse>( + f: (e: E) => R, + ): ResultAsync | T, InferErrTypes> + orElse>( + f: (e: E) => R, + ): ResultAsync | T, InferAsyncErrTypes> + orElse(f: (e: E) => Result | ResultAsync): ResultAsync // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types orElse(f: any): any { return new ResultAsync( diff --git a/src/result.ts b/src/result.ts index c8f09971..087fa69f 100644 --- a/src/result.ts +++ b/src/result.ts @@ -124,8 +124,10 @@ interface IResult { * @param f A function to apply to an `Err` value, leaving `Ok` values * untouched. */ - orElse>(f: (e: E) => R): Result> - orElse(f: (e: E) => Result): Result + orElse>( + f: (e: E) => R, + ): Result | T, InferErrTypes> + orElse(f: (e: E) => Result): Result /** * Similar to `map` Except you must return a new `Result`. @@ -219,8 +221,10 @@ export class Ok implements IResult { return f(this.value) } - orElse>(_f: (e: E) => R): Result> - orElse(_f: (e: E) => Result): Result + orElse>( + _f: (e: E) => R, + ): Result | T, InferErrTypes> + orElse(_f: (e: E) => Result): Result // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types orElse(_f: any): any { return ok(this.value) @@ -282,8 +286,10 @@ export class Err implements IResult { return err(this.error) } - orElse>(f: (e: E) => R): Result> - orElse(f: (e: E) => Result): Result + orElse>( + f: (e: E) => R, + ): Result | T, InferErrTypes> + orElse(f: (e: E) => Result): Result // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types orElse(f: any): any { return f(this.error) From 4d0bb9b950269ba17b958ceb3671686d539bbed5 Mon Sep 17 00:00:00 2001 From: braxtonhall Date: Thu, 29 Jun 2023 15:25:56 +0200 Subject: [PATCH 2/5] Test and debug --- src/result-async.ts | 4 +- tests/typecheck-tests.ts | 80 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/src/result-async.ts b/src/result-async.ts index eaaea17d..d9210d7b 100644 --- a/src/result-async.ts +++ b/src/result-async.ts @@ -113,10 +113,10 @@ export class ResultAsync implements PromiseLike> { ) } - orElse>( + orElse>( f: (e: E) => R, ): ResultAsync | T, InferErrTypes> - orElse>( + orElse>( f: (e: E) => R, ): ResultAsync | T, InferAsyncErrTypes> orElse(f: (e: E) => Result | ResultAsync): ResultAsync diff --git a/tests/typecheck-tests.ts b/tests/typecheck-tests.ts index 849b2247..a8e494b6 100644 --- a/tests/typecheck-tests.ts +++ b/tests/typecheck-tests.ts @@ -223,10 +223,48 @@ type CreateTuple = (function it(_ = 'allows specifying the E and T types explicitly') { type Expectation = Result<'yo', string> - const result: Expectation = ok<'yo', number>('yo').orElse(val => { + const result: Expectation = ok<'yo', number>('yo').orElse<'yo', string>(val => { return err('yo') }) }); + + (function it(_ = 'Creates a union of ok types for disjoint types') { + type Expectation = Result + + const result: Expectation = err([true]) + .orElse((val) => ok('recovered!')) + }); + + (function it(_ = 'Infers ok type when returning disjoint types') { + type Expectation = Result + + const result: Expectation = err(123) + .orElse((val) => { + switch (val) { + case 1: + return ok('yoooooo dude' + val) + case 2: + return ok(123) + default: + return ok(false) + } + }) + }); + + (function it(_ = 'Infers new type when returning both Ok and Err') { + const initial = err(123) + type Expectation = Result + + const result: Expectation = initial + .orElse((val) => { + switch (val) { + case 1: + return err(false as const) + default: + return ok(true as const) + } + }) + }); }); (function describe(_ = 'asyncAndThen') { @@ -1253,7 +1291,7 @@ type CreateTuple = type Expectation = ResultAsync const result: Expectation = okAsync(123) - .orElse((val) => { + .orElse((val) => { switch (val) { case '1': return ok(1) @@ -1264,6 +1302,44 @@ type CreateTuple = } }) }); + + (function it(_ = 'Creates a union of ok types for disjoint types') { + type Expectation = ResultAsync + + const result: Expectation = errAsync([true]) + .orElse((val) => ok('recovered!')) + }); + + (function it(_ = 'Infers ok type when returning disjoint types') { + type Expectation = ResultAsync + + const result: Expectation = errAsync(123) + .orElse((val) => { + switch (val) { + case 1: + return okAsync('yoooooo dude' + val) + case 2: + return okAsync(123) + default: + return okAsync(false) + } + }) + }); + + (function it(_ = 'Infers new type when returning both Ok and Err') { + const initial = errAsync(123) + type Expectation = ResultAsync + + const result: Expectation = initial + .orElse((val) => { + switch (val) { + case 1: + return err(false as const) + default: + return okAsync(true as const) + } + }) + }); }); (function describe(_ = 'combine') { From e23d12643f7bd59320ea1bbc1395f8114f78c24f Mon Sep 17 00:00:00 2001 From: braxtonhall Date: Thu, 29 Aug 2024 17:06:24 +0200 Subject: [PATCH 3/5] Update README --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 19414d50..c500fb97 100644 --- a/README.md +++ b/README.md @@ -407,9 +407,9 @@ Takes an `Err` value and maps it to a `Result`. This is useful f ```typescript class Result { - orElse( - callback: (error: E) => Result - ): Result { ... } + orElse( + callback: (e: E) => Result + ): Result { ... } } ``` @@ -1179,9 +1179,9 @@ Takes an `Err` value and maps it to a `ResultAsync`. This is use ```typescript class ResultAsync { - orElse( - callback: (error: E) => Result | ResultAsync - ): ResultAsync { ... } + orElse( + callback: (e: E) => Result | ResultAsync + ): ResultAsync { ... } } ``` From 09faf35a5ce701ed55b13b82074da9e50050526d Mon Sep 17 00:00:00 2001 From: braxtonhall Date: Thu, 29 Aug 2024 17:25:20 +0200 Subject: [PATCH 4/5] Add changeset --- .changeset/empty-poets-collect.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .changeset/empty-poets-collect.md diff --git a/.changeset/empty-poets-collect.md b/.changeset/empty-poets-collect.md new file mode 100644 index 00000000..3279cbd6 --- /dev/null +++ b/.changeset/empty-poets-collect.md @@ -0,0 +1,19 @@ +--- +'neverthrow': major +--- + +Allow orElse method to change ok types. +This makes the orElse types match the implementation. + +This is a breaking change for the orElse type argument list, +as the ok type must now be provided before the err type. + +```diff +- result.orElse(foo) ++ result.orElse(foo) +``` + +This only applies if type arguments were +explicitly provided at an orElse callsite. +If the type arguments were inferred, +no updates are needed during the upgrade. From 317582cdb329f8cbb068a04b8f2a4957a4829ec7 Mon Sep 17 00:00:00 2001 From: braxtonhall Date: Thu, 29 Aug 2024 17:26:54 +0200 Subject: [PATCH 5/5] Reduce the footprint of this PR a little --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c500fb97..889d96f1 100644 --- a/README.md +++ b/README.md @@ -408,7 +408,7 @@ Takes an `Err` value and maps it to a `Result`. This is useful f ```typescript class Result { orElse( - callback: (e: E) => Result + callback: (error: E) => Result ): Result { ... } } ``` @@ -1180,7 +1180,7 @@ Takes an `Err` value and maps it to a `ResultAsync`. This is use ```typescript class ResultAsync { orElse( - callback: (e: E) => Result | ResultAsync + callback: (error: E) => Result | ResultAsync ): ResultAsync { ... } } ```