Skip to content

Commit

Permalink
Merge pull request #484 from braxtonhall/or-else
Browse files Browse the repository at this point in the history
OrElse callback should be able to change Ok type
  • Loading branch information
supermacro authored Sep 8, 2024
2 parents 2d6e1dd + 378b3e5 commit 2d94df3
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 17 deletions.
19 changes: 19 additions & 0 deletions .changeset/empty-poets-collect.md
Original file line number Diff line number Diff line change
@@ -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<ErrType>(foo)
+ result.orElse<OkType, ErrType>(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.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,9 @@ Takes an `Err` value and maps it to a `Result<T, SomeNewType>`. This is useful f

```typescript
class Result<T, E> {
orElse<A>(
callback: (error: E) => Result<T, A>
): Result<T, A> { ... }
orElse<U, A>(
callback: (error: E) => Result<U, A>
): Result<U | T, A> { ... }
}
```

Expand Down Expand Up @@ -1179,9 +1179,9 @@ Takes an `Err` value and maps it to a `ResultAsync<T, SomeNewType>`. This is use

```typescript
class ResultAsync<T, E> {
orElse<A>(
callback: (error: E) => Result<T, A> | ResultAsync<T, A>
): ResultAsync<T, A> { ... }
orElse<U, A>(
callback: (error: E) => Result<U, A> | ResultAsync<U, A>
): ResultAsync<U | T, A> { ... }
}
```

Expand Down
10 changes: 7 additions & 3 deletions src/result-async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,13 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
)
}

orElse<R extends Result<T, unknown>>(f: (e: E) => R): ResultAsync<T, InferErrTypes<R>>
orElse<R extends ResultAsync<T, unknown>>(f: (e: E) => R): ResultAsync<T, InferAsyncErrTypes<R>>
orElse<A>(f: (e: E) => Result<T, A> | ResultAsync<T, A>): ResultAsync<T, A>
orElse<R extends Result<unknown, unknown>>(
f: (e: E) => R,
): ResultAsync<InferOkTypes<R> | T, InferErrTypes<R>>
orElse<R extends ResultAsync<unknown, unknown>>(
f: (e: E) => R,
): ResultAsync<InferAsyncOkTypes<R> | T, InferAsyncErrTypes<R>>
orElse<U, A>(f: (e: E) => Result<U, A> | ResultAsync<U, A>): ResultAsync<U | T, A>
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
orElse(f: any): any {
return new ResultAsync(
Expand Down
18 changes: 12 additions & 6 deletions src/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,10 @@ interface IResult<T, E> {
* @param f A function to apply to an `Err` value, leaving `Ok` values
* untouched.
*/
orElse<R extends Result<unknown, unknown>>(f: (e: E) => R): Result<T, InferErrTypes<R>>
orElse<A>(f: (e: E) => Result<T, A>): Result<T, A>
orElse<R extends Result<unknown, unknown>>(
f: (e: E) => R,
): Result<InferOkTypes<R> | T, InferErrTypes<R>>
orElse<U, A>(f: (e: E) => Result<U, A>): Result<U | T, A>

/**
* Similar to `map` Except you must return a new `Result`.
Expand Down Expand Up @@ -328,8 +330,10 @@ export class Ok<T, E> implements IResult<T, E> {
return ok<T, E>(this.value)
}

orElse<R extends Result<unknown, unknown>>(_f: (e: E) => R): Result<T, InferErrTypes<R>>
orElse<A>(_f: (e: E) => Result<T, A>): Result<T, A>
orElse<R extends Result<unknown, unknown>>(
_f: (e: E) => R,
): Result<InferOkTypes<R> | T, InferErrTypes<R>>
orElse<U, A>(_f: (e: E) => Result<U, A>): Result<U | T, A>
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
orElse(_f: any): any {
return ok(this.value)
Expand Down Expand Up @@ -416,8 +420,10 @@ export class Err<T, E> implements IResult<T, E> {
return err(this.error)
}

orElse<R extends Result<unknown, unknown>>(f: (e: E) => R): Result<T, InferErrTypes<R>>
orElse<A>(f: (e: E) => Result<T, A>): Result<T, A>
orElse<R extends Result<unknown, unknown>>(
f: (e: E) => R,
): Result<InferOkTypes<R> | T, InferErrTypes<R>>
orElse<U, A>(f: (e: E) => Result<U, A>): Result<U | T, A>
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
orElse(f: any): any {
return f(this.error)
Expand Down
80 changes: 78 additions & 2 deletions tests/typecheck-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,48 @@ type CreateTuple<L, V = string> =
(function it(_ = 'allows specifying the E and T types explicitly') {
type Expectation = Result<'yo', string>

const result: Expectation = ok<'yo', number>('yo').orElse<string>(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<string | number, boolean>

const result: Expectation = err<string, boolean[]>([true])
.orElse((val) => ok<string, boolean>('recovered!'))
});

(function it(_ = 'Infers ok type when returning disjoint types') {
type Expectation = Result<string | number | boolean, unknown>

const result: Expectation = err<string, number>(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<string, number>(123)
type Expectation = Result<string | true, false>

const result: Expectation = initial
.orElse((val) => {
switch (val) {
case 1:
return err(false as const)
default:
return ok(true as const)
}
})
});
});

(function describe(_ = 'match') {
Expand Down Expand Up @@ -1604,7 +1642,7 @@ type CreateTuple<L, V = string> =
type Expectation = ResultAsync<number, number | string>

const result: Expectation = okAsync<number, string>(123)
.orElse<number | string>((val) => {
.orElse<number, number | string>((val) => {
switch (val) {
case '1':
return ok(1)
Expand All @@ -1615,6 +1653,44 @@ type CreateTuple<L, V = string> =
}
})
});

(function it(_ = 'Creates a union of ok types for disjoint types') {
type Expectation = ResultAsync<string | number, boolean>

const result: Expectation = errAsync<string, boolean[]>([true])
.orElse((val) => ok<string, boolean>('recovered!'))
});

(function it(_ = 'Infers ok type when returning disjoint types') {
type Expectation = ResultAsync<string | number | boolean, unknown>

const result: Expectation = errAsync<string, number>(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<string, number>(123)
type Expectation = ResultAsync<string | true, false>

const result: Expectation = initial
.orElse((val) => {
switch (val) {
case 1:
return err(false as const)
default:
return okAsync(true as const)
}
})
});
});

(function describe(_ = 'combine') {
Expand Down

0 comments on commit 2d94df3

Please sign in to comment.