Skip to content

Commit

Permalink
Merge pull request #435 from ghost91-/fix-typing-of-combine
Browse files Browse the repository at this point in the history
Fix types for Result.combine and ResultAsync.combine for arrays witho…
  • Loading branch information
supermacro committed Dec 11, 2022
2 parents 6ebfd2a + 424f4e9 commit ffd4da3
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 59 deletions.
14 changes: 12 additions & 2 deletions src/result-async.ts
Expand Up @@ -11,6 +11,8 @@ import { Err, Ok, Result } from './'
import {
combineResultAsyncList,
combineResultAsyncListWithAllErrors,
ExtractErrAsyncTypes,
ExtractOkAsyncTypes,
InferAsyncErrTypes,
InferAsyncOkTypes,
InferErrTypes,
Expand Down Expand Up @@ -154,10 +156,18 @@ export const fromPromise = ResultAsync.fromPromise
export const fromSafePromise = ResultAsync.fromSafePromise

// Combines the array of async results into one result.
export type CombineResultAsyncs<T> = TraverseAsync<UnwrapAsync<T>>
export type CombineResultAsyncs<
T extends readonly ResultAsync<unknown, unknown>[]
> = IsLiteralArray<T> extends 1
? TraverseAsync<UnwrapAsync<T>>
: ResultAsync<ExtractOkAsyncTypes<T>, ExtractErrAsyncTypes<T>[number]>

// Combines the array of async results into one result with all errors.
export type CombineResultsWithAllErrorsArrayAsync<T> = TraverseWithAllErrorsAsync<UnwrapAsync<T>>
export type CombineResultsWithAllErrorsArrayAsync<
T extends readonly ResultAsync<unknown, unknown>[]
> = IsLiteralArray<T> extends 1
? TraverseWithAllErrorsAsync<UnwrapAsync<T>>
: ResultAsync<ExtractOkAsyncTypes<T>, ExtractErrAsyncTypes<T>[number][]>

// Unwraps the inner `Result` from a `ResultAsync` for all elements.
type UnwrapAsync<T> = IsLiteralArray<T> extends 1
Expand Down
26 changes: 12 additions & 14 deletions src/result.ts
Expand Up @@ -3,6 +3,8 @@ import { createNeverThrowError, ErrorConfig } from './_internals/error'
import {
combineResultList,
combineResultListWithAllErrors,
ExtractErrTypes,
ExtractOkTypes,
InferErrTypes,
InferOkTypes,
} from './_internals/utils'
Expand Down Expand Up @@ -402,6 +404,8 @@ export type Combine<T, Depth extends number = 5> = Transpose<CollectResults<T>,
infer R,
]
? [UnknownMembersToNever<L>, UnknownMembersToNever<R>]
: Transpose<CollectResults<T>, [], Depth> extends []
? [[], []]
: never

// Deduplicates the result, as the result type is a union of Err and Ok types.
Expand Down Expand Up @@ -472,23 +476,17 @@ type TraverseWithAllErrors<T, Depth extends number = 5> = Combine<T, Depth> exte
: never

// Combines the array of results into one result.
export type CombineResults<T> = T extends ReadonlyArray<infer U>
? IsLiteralArray<T> extends 1
? Traverse<T>
: Traverse<MemberListOf<Dedup<U>>> extends Result<infer L, infer R>
? Result<L, EmptyArrayToNever<R>>
: never
: never
export type CombineResults<
T extends readonly Result<unknown, unknown>[]
> = IsLiteralArray<T> extends 1
? Traverse<T>
: Result<ExtractOkTypes<T>, ExtractErrTypes<T>[number]>

// Combines the array of results into one result with all errors.
export type CombineResultsWithAllErrorsArray<
T extends readonly Result<unknown, unknown>[]
> = T extends ReadonlyArray<infer U>
? IsLiteralArray<T> extends 1
? TraverseWithAllErrors<T>
: TraverseWithAllErrors<MemberListOf<Dedup<U>>> extends Result<infer L, infer R>
? Result<EmptyArrayToNever<L>, EmptyArrayToNever<R>>
: never
: never
> = IsLiteralArray<T> extends 1
? TraverseWithAllErrors<T>
: Result<ExtractOkTypes<T>, ExtractErrTypes<T>[number][]>

//#endregion
234 changes: 191 additions & 43 deletions tests/typecheck-tests.ts
Expand Up @@ -243,6 +243,116 @@ import { Transpose } from '../src/result'
.asyncAndThen((val) => errAsync<string, string[]>(['oh nooooo']))
});
});

(function describe(_ = 'combine') {
(function it(_ = 'combines different results into one') {
type Expectation = Result<[ number, string, boolean, boolean ], Error | string | string[]>;

const result = Result.combine([
ok<number, string>(1),
ok<string, string>('string'),
err<boolean, string[]>([ 'string', 'string2' ]),
err<boolean, Error>(new Error('error content')),
])

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines only ok results into one') {
type Expectation = Result<[ number, string ], never>;

const result = Result.combine([
ok(1),
ok('string'),
]);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines only err results into one') {
type Expectation = Result<[ never, never ], number | string>;

const result = Result.combine([
err(1),
err('string'),
]);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines empty list results into one') {
type Expectation = Result<never, never>;
const results: [] = [];

const result = Result.combine(results);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines arrays of results to a result of an array') {
type Expectation = Result<string[], string>;
const results: Result<string, string>[] = [];

const result = Result.combine(results);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});
});

(function describe(_ = 'combineWithAllErrors') {
(function it(_ = 'combines different results into one') {
type Expectation = Result<[ number, string, never, never ], [never, never, string[], Error]>;

const result = Result.combineWithAllErrors([
ok(1),
ok('string'),
err([ 'string', 'string2' ]),
err(new Error('error content')),
]);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines only ok results into one') {
type Expectation = Result<[ number, string ], [never, never]>;

const result = Result.combineWithAllErrors([
ok(1),
ok('string'),
]);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines only err results into one') {
type Expectation = Result<[ never, never ], [number, string]>;

const result = Result.combineWithAllErrors([
err(1),
err('string'),
]);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines arrays of results to a result of an array') {
type Expectation = Result<string[], (number | string)[]>;
const results: Result<string, number | string>[] = [];

const result = Result.combineWithAllErrors(results);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});
});
});


Expand Down Expand Up @@ -557,79 +667,117 @@ import { Transpose } from '../src/result'
})
});
});
});


(function describe(_ = 'Combine on Unbounded lists') {
(function describe(_ = 'combine') {
(function it(_ = 'combines different results into one') {
type Expectation = Result<[ number, string, boolean, boolean ], Error | string | string[]>;

const result: Expectation = Result.combine([
ok<number, string>(1),
ok<string, string>('string'),
err<boolean, string[]>([ 'string', 'string2' ]),
err<boolean, Error>(new Error('error content')),
(function it(_ = 'combines different result asyncs into one') {
type Expectation = ResultAsync<[ number, string, boolean, boolean ], Error | string | string[]>;

const result = ResultAsync.combine([
okAsync<number, string>(1),
okAsync<string, string>('string'),
errAsync<boolean, string[]>([ 'string', 'string2' ]),
errAsync<boolean, Error>(new Error('error content')),
])

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines only ok results into one') {
type Expectation = Result<[ number, string ], never>;
(function it(_ = 'combines only ok result asyncs into one') {
type Expectation = ResultAsync<[ number, string ], never>;

const result: Expectation = Result.combine([
ok(1),
ok('string'),
const result = ResultAsync.combine([
okAsync(1),
okAsync('string'),
]);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines only err results into one') {
type Expectation = Result<[ never, never ], number | string>;
type Expectation = ResultAsync<[ never, never ], number | string>;

const result: Expectation = Result.combine([
err(1),
err('string'),
const result = ResultAsync.combine([
errAsync(1),
errAsync('string'),
]);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines empty list results into one') {
type Expectation = Result<never[], never>;
(function it(_ = 'combines empty list result asyncs into one') {
type Expectation = ResultAsync<never, never>;
const results: [] = [];

const result = ResultAsync.combine(results);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

const result: Expectation = Result.combine([]);
(function it(_ = 'combines arrays of result asyncs to a result async of an array') {
type Expectation = ResultAsync<string[], string>;
const results: ResultAsync<string, string>[] = [];

const result = ResultAsync.combine(results);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});
});

(function describe(_ = 'combineWithAllErrors') {
(function it(_ = 'combines different results into one') {
type Expectation = Result<[ number, string, never, never ], [never, never, string[], Error]>;

const result: Expectation = Result.combineWithAllErrors([
ok(1),
ok('string'),
err([ 'string', 'string2' ]),
err(new Error('error content')),
(function it(_ = 'combines different result asyncs into one') {
type Expectation = ResultAsync<[ number, string, never, never ], [never, never, string[], Error]>;

const result = ResultAsync.combineWithAllErrors([
okAsync(1),
okAsync('string'),
errAsync([ 'string', 'string2' ]),
errAsync(new Error('error content')),
]);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines only ok results into one') {
type Expectation = Result<[ number, string ], never[]>;
(function it(_ = 'combines only ok result asyncs into one') {
type Expectation = ResultAsync<[ number, string ], [never, never]>;

const result: Expectation = Result.combineWithAllErrors([
ok(1),
ok('string'),
const result = ResultAsync.combineWithAllErrors([
okAsync(1),
okAsync('string'),
]);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});

(function it(_ = 'combines only err results into one') {
type Expectation = Result<[ never, never ], [number, string]>;
(function it(_ = 'combines only err result asyncs into one') {
type Expectation = ResultAsync<[ never, never ], [number, string]>;

const result: Expectation = Result.combineWithAllErrors([
err(1),
err('string'),
const result = ResultAsync.combineWithAllErrors([
errAsync(1),
errAsync('string'),
]);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});
});
})();

(function it(_ = 'combines arrays of result asyncs to a result of an array') {
type Expectation = ResultAsync<string[], (number | string)[]>;
const results: ResultAsync<string, number | string>[] = [];

const result = ResultAsync.combineWithAllErrors(results);

const assignableToCheck: Expectation = result;
const assignablefromCheck: typeof result = assignableToCheck;
});
});
});

(function describe(_ = 'Utility types') {
(function describe(_ = 'Transpose') {
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Expand Up @@ -7,6 +7,7 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"declaration": true,
"moduleResolution": "Node",
"baseUrl": "./src",
Expand Down

0 comments on commit ffd4da3

Please sign in to comment.