Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix types for Result.combine and ResultAsync.combine for arrays witho… #435

Merged
merged 1 commit into from Dec 11, 2022

Conversation

ghost91-
Copy link
Contributor

@ghost91- ghost91- commented Nov 13, 2022

…ut statically know length

The fix works be using the old implementation for the return types of combine and combineWithAllErrors in the case that the given array of Results/ResultAsyncs is not a tuple type.

While implementing this, I actually stumbled across another issue: The types for combineWithAllErrors are incorrect in the tuple case. At the moment, it is typed so that the error case also is a tuple of all the error types. But this is not what happens in reality, because not all of the results necessarily are errors. Here is an example:

const result1: Result<string, string> = ok('foo');
const result2: Result<number, number> = err(42);
const results = Result.combine([result1, result2]); // inferred as Result<[string, number], [string, number]>, but it should be Result<[string, number], (string | number)[]>, because we don't statically know how many of the results are errors.
expect(results).toEqual(err([42]));

There was actually a single test case that should have covered this, but it didn't catch it, because so far, we were only testing assignability of the result to the expectation, but not the other way round (and there are other test cases that expect the incorrect types...). I added checks for the other direction, too, so now we actually check for type equality.

The problem itself is however not addressed by this PR and should be addressed separately. I just wanted to write it down so I don't forget. I’ll create an issue for it, when I find the time.

Fixes #434

@ghost91-
Copy link
Contributor Author

ghost91- commented Nov 14, 2022

In order to solve all of this properly, I think we should bite the bullet and accept that we need a more modern TypeScript version. I now also have a variant that implements this properly (afaik), but it also relies on spreading:

// utils.ts

// Given a list of Results, this extracts all the different `T` types from that list
export type ExtractOkTypes<T extends readonly Result<unknown, unknown>[]> = {
  [idx in keyof T]: T[idx] extends Err<unknown, unknown>
    ? never
    : T[idx] extends Result<infer U, unknown>
    ? U
    : never
}

// Given a list of Results, get the first possible `T` type
export type FirstPossibleOk<T extends readonly Result<unknown, unknown>[]> = T extends [
  infer U,
  ...infer V
]
  ? U extends Ok<infer W, unknown>
    ? W
    : V extends readonly Result<unknown, unknown>[]
    ? FirstPossibleOk<V>
    : never
  : ExtractOkTypes<T>[number]

// Given a list of Results, this extracts all the different `E` types from that list
export type ExtractErrTypes<T extends readonly Result<unknown, unknown>[]> = {
  [idx in keyof T]: T[idx] extends Ok<unknown, unknown>
    ? never
    : T[idx] extends Result<unknown, infer E>
    ? E
    : never
}

// Given a list of Results, get the first possible `E` type
export type FirstPossibleErr<T extends readonly Result<unknown, unknown>[]> = T extends [
  infer U,
  ...infer V
]
  ? U extends Err<unknown, infer W>
    ? W
    : V extends readonly Result<unknown, unknown>[]
    ? FirstPossibleErr<V>
    : never
  : ExtractErrTypes<T>[number]

// Given a list of Results, get the first statically known `E` type
export type FirstKnownErr<T extends readonly Result<unknown, unknown>[]> = T extends [
  infer U,
  ...infer V
]
  ? [U] extends [Err<unknown, infer W>]
    ? W
    : V extends readonly Result<unknown, unknown>[]
    ? FirstKnownErr<V>
    : never
  : never

// result.ts

// Combines the array of results into one result.
export type CombineResults<
  T extends readonly Result<unknown, unknown>[]
> = FirstKnownErr<T> extends never
  ? FirstPossibleErr<T> extends never
    ? Ok<ExtractOkTypes<T>, never>
    : FirstPossibleOk<T> extends never
    ? Err<never, FirstPossibleErr<T>>
    : Result<ExtractOkTypes<T>, FirstPossibleErr<T>>
  : Err<never, FirstPossibleErr<T>>

// Combines the array of results into one result with all errors.
export type CombineResultsWithAllErrorsArray<
  T extends readonly Result<unknown, unknown>[]
> = FirstKnownErr<T> extends never
  ? FirstPossibleErr<T> extends never
    ? Ok<ExtractOkTypes<T>, never>
    : FirstPossibleOk<T> extends never
    ? Err<never, ExtractErrTypes<T>[number][]>
    : Result<ExtractOkTypes<T>, ExtractErrTypes<T>[number][]>
  : Err<never, ExtractErrTypes<T>[number][]>

Am I missing anything?

@supermacro
Copy link
Owner

Thanks for opening a PR @ghost91- , I will be reviewing this week. I also want to cc @incetarik since he worked on a lot of related code here + he's also involved in the associated issue #434

@incetarik
Copy link
Contributor

Thanks for opening a PR @ghost91- , I will be reviewing this week. I also want to cc @incetarik since he worked on a lot of related code here + he's also involved in the associated issue #434

Thanks! I'm working on something else that keeps me busy but I think I'll have time to check this out and I think we can fix this together! But really, I don't know when I can be free for this. Apart from this, I'm also interested in the another issue that was about map or something I don't recall it well. So I wish the best for the community.

@lukebrandonfarrell
Copy link

This is a positive improvement, as I usually run into typing issues with combine in all our applications. Hoping this get merged soon 🤞

Copy link
Owner

@supermacro supermacro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took quite a while to get to this - thank you for your patience and contribution!

@supermacro supermacro merged commit ffd4da3 into supermacro:master Dec 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Result.combine is broken for array types that are not tuple types
4 participants