Skip to content

Commit 2d94df3

Browse files
authored
Merge pull request #484 from braxtonhall/or-else
OrElse callback should be able to change Ok type
2 parents 2d6e1dd + 378b3e5 commit 2d94df3

File tree

5 files changed

+122
-17
lines changed

5 files changed

+122
-17
lines changed

.changeset/empty-poets-collect.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'neverthrow': major
3+
---
4+
5+
Allow orElse method to change ok types.
6+
This makes the orElse types match the implementation.
7+
8+
This is a breaking change for the orElse type argument list,
9+
as the ok type must now be provided before the err type.
10+
11+
```diff
12+
- result.orElse<ErrType>(foo)
13+
+ result.orElse<OkType, ErrType>(foo)
14+
```
15+
16+
This only applies if type arguments were
17+
explicitly provided at an orElse callsite.
18+
If the type arguments were inferred,
19+
no updates are needed during the upgrade.

README.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,9 @@ Takes an `Err` value and maps it to a `Result<T, SomeNewType>`. This is useful f
407407

408408
```typescript
409409
class Result<T, E> {
410-
orElse<A>(
411-
callback: (error: E) => Result<T, A>
412-
): Result<T, A> { ... }
410+
orElse<U, A>(
411+
callback: (error: E) => Result<U, A>
412+
): Result<U | T, A> { ... }
413413
}
414414
```
415415

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

11801180
```typescript
11811181
class ResultAsync<T, E> {
1182-
orElse<A>(
1183-
callback: (error: E) => Result<T, A> | ResultAsync<T, A>
1184-
): ResultAsync<T, A> { ... }
1182+
orElse<U, A>(
1183+
callback: (error: E) => Result<U, A> | ResultAsync<U, A>
1184+
): ResultAsync<U | T, A> { ... }
11851185
}
11861186
```
11871187

src/result-async.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,13 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
163163
)
164164
}
165165

166-
orElse<R extends Result<T, unknown>>(f: (e: E) => R): ResultAsync<T, InferErrTypes<R>>
167-
orElse<R extends ResultAsync<T, unknown>>(f: (e: E) => R): ResultAsync<T, InferAsyncErrTypes<R>>
168-
orElse<A>(f: (e: E) => Result<T, A> | ResultAsync<T, A>): ResultAsync<T, A>
166+
orElse<R extends Result<unknown, unknown>>(
167+
f: (e: E) => R,
168+
): ResultAsync<InferOkTypes<R> | T, InferErrTypes<R>>
169+
orElse<R extends ResultAsync<unknown, unknown>>(
170+
f: (e: E) => R,
171+
): ResultAsync<InferAsyncOkTypes<R> | T, InferAsyncErrTypes<R>>
172+
orElse<U, A>(f: (e: E) => Result<U, A> | ResultAsync<U, A>): ResultAsync<U | T, A>
169173
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
170174
orElse(f: any): any {
171175
return new ResultAsync(

src/result.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,10 @@ interface IResult<T, E> {
212212
* @param f A function to apply to an `Err` value, leaving `Ok` values
213213
* untouched.
214214
*/
215-
orElse<R extends Result<unknown, unknown>>(f: (e: E) => R): Result<T, InferErrTypes<R>>
216-
orElse<A>(f: (e: E) => Result<T, A>): Result<T, A>
215+
orElse<R extends Result<unknown, unknown>>(
216+
f: (e: E) => R,
217+
): Result<InferOkTypes<R> | T, InferErrTypes<R>>
218+
orElse<U, A>(f: (e: E) => Result<U, A>): Result<U | T, A>
217219

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

331-
orElse<R extends Result<unknown, unknown>>(_f: (e: E) => R): Result<T, InferErrTypes<R>>
332-
orElse<A>(_f: (e: E) => Result<T, A>): Result<T, A>
333+
orElse<R extends Result<unknown, unknown>>(
334+
_f: (e: E) => R,
335+
): Result<InferOkTypes<R> | T, InferErrTypes<R>>
336+
orElse<U, A>(_f: (e: E) => Result<U, A>): Result<U | T, A>
333337
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
334338
orElse(_f: any): any {
335339
return ok(this.value)
@@ -416,8 +420,10 @@ export class Err<T, E> implements IResult<T, E> {
416420
return err(this.error)
417421
}
418422

419-
orElse<R extends Result<unknown, unknown>>(f: (e: E) => R): Result<T, InferErrTypes<R>>
420-
orElse<A>(f: (e: E) => Result<T, A>): Result<T, A>
423+
orElse<R extends Result<unknown, unknown>>(
424+
f: (e: E) => R,
425+
): Result<InferOkTypes<R> | T, InferErrTypes<R>>
426+
orElse<U, A>(f: (e: E) => Result<U, A>): Result<U | T, A>
421427
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
422428
orElse(f: any): any {
423429
return f(this.error)

tests/typecheck-tests.ts

+78-2
Original file line numberDiff line numberDiff line change
@@ -349,10 +349,48 @@ type CreateTuple<L, V = string> =
349349
(function it(_ = 'allows specifying the E and T types explicitly') {
350350
type Expectation = Result<'yo', string>
351351

352-
const result: Expectation = ok<'yo', number>('yo').orElse<string>(val => {
352+
const result: Expectation = ok<'yo', number>('yo').orElse<'yo', string>(val => {
353353
return err('yo')
354354
})
355355
});
356+
357+
(function it(_ = 'Creates a union of ok types for disjoint types') {
358+
type Expectation = Result<string | number, boolean>
359+
360+
const result: Expectation = err<string, boolean[]>([true])
361+
.orElse((val) => ok<string, boolean>('recovered!'))
362+
});
363+
364+
(function it(_ = 'Infers ok type when returning disjoint types') {
365+
type Expectation = Result<string | number | boolean, unknown>
366+
367+
const result: Expectation = err<string, number>(123)
368+
.orElse((val) => {
369+
switch (val) {
370+
case 1:
371+
return ok('yoooooo dude' + val)
372+
case 2:
373+
return ok(123)
374+
default:
375+
return ok(false)
376+
}
377+
})
378+
});
379+
380+
(function it(_ = 'Infers new type when returning both Ok and Err') {
381+
const initial = err<string, number>(123)
382+
type Expectation = Result<string | true, false>
383+
384+
const result: Expectation = initial
385+
.orElse((val) => {
386+
switch (val) {
387+
case 1:
388+
return err(false as const)
389+
default:
390+
return ok(true as const)
391+
}
392+
})
393+
});
356394
});
357395

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

16061644
const result: Expectation = okAsync<number, string>(123)
1607-
.orElse<number | string>((val) => {
1645+
.orElse<number, number | string>((val) => {
16081646
switch (val) {
16091647
case '1':
16101648
return ok(1)
@@ -1615,6 +1653,44 @@ type CreateTuple<L, V = string> =
16151653
}
16161654
})
16171655
});
1656+
1657+
(function it(_ = 'Creates a union of ok types for disjoint types') {
1658+
type Expectation = ResultAsync<string | number, boolean>
1659+
1660+
const result: Expectation = errAsync<string, boolean[]>([true])
1661+
.orElse((val) => ok<string, boolean>('recovered!'))
1662+
});
1663+
1664+
(function it(_ = 'Infers ok type when returning disjoint types') {
1665+
type Expectation = ResultAsync<string | number | boolean, unknown>
1666+
1667+
const result: Expectation = errAsync<string, number>(123)
1668+
.orElse((val) => {
1669+
switch (val) {
1670+
case 1:
1671+
return okAsync('yoooooo dude' + val)
1672+
case 2:
1673+
return okAsync(123)
1674+
default:
1675+
return okAsync(false)
1676+
}
1677+
})
1678+
});
1679+
1680+
(function it(_ = 'Infers new type when returning both Ok and Err') {
1681+
const initial = errAsync<string, number>(123)
1682+
type Expectation = ResultAsync<string | true, false>
1683+
1684+
const result: Expectation = initial
1685+
.orElse((val) => {
1686+
switch (val) {
1687+
case 1:
1688+
return err(false as const)
1689+
default:
1690+
return okAsync(true as const)
1691+
}
1692+
})
1693+
});
16181694
});
16191695

16201696
(function describe(_ = 'combine') {

0 commit comments

Comments
 (0)