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

Propagate return type of call effect with generic arguments #691

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

sebc-vm
Copy link

@sebc-vm sebc-vm commented Jul 20, 2023

I expect the result type of a call effect with generic arguments to be propagated.

However, this does not work:

function* wrappedCall<Args extends any[], RT>(
    saga: (...args: Args) => SagaIterator<RT>,
    ...args: Args
): SagaIterator<RT> {
  return yield* call(saga, ...args); // Type 'SagaReturnType<(...args: Args) => SagaIterator<RT>>' is not assignable to type 'RT'.
}

Changing the type of the arguments to any[] allows the result type to propagate:

function* wrappedCall<RT>(
    saga: (...args: any[]) => SagaIterator<RT>,
    ...args: any[]
): SagaIterator<RT> {
  return yield* call(saga, ...args); // Result type is RT as expected
}

The problem comes from the definition of SagaReturnType:

export type SagaReturnType<S extends Function> =
  S extends (...args: any[]) => SagaIterator<infer RT> ? RT :
  S extends (...args: any[]) => Promise<infer RT> ? RT :
  S extends (...args: any[]) => infer RT ? RT :
  never;

When S is the type of a function whose arguments are a constrained type variable, typescript cannot compare any[] against the type variables and is unable to decide the conditional types.

The following code illustrates this limitation in the type system:

type IsVoid<T extends void> = T extends void ? true : false;

function foo<U extends void>(): [IsVoid<void>, IsVoid<U>, IsVoid<U>] {
  return [true, true, false];
}

Parameterizing SagaReturnType over Args fixes the problem by avoiding the comparison between any[] and a constrained type variable. The following declarations fix the initial wrapped call example above:

import {SagaIterator} from "redux-saga";
import {CallEffect} from "redux-saga/effects";

declare module 'typed-redux-saga' {

  export type SagaReturnType<S extends Function, Args extends any[]> =
    S extends (...args: Args) => SagaIterator<infer RT> ? RT :
    S extends (...args: Args) => Promise<infer RT> ? RT :
    S extends (...args: Args) => infer RT ? RT :
    never;

  export function call<Args extends any[], Fn extends (...args: Args) => any>(
    fn: Fn,
    ...args: Args
  ): SagaGenerator<SagaReturnType<Fn, Args>, CallEffect<SagaReturnType<Fn, Args>>>;

}

This pull request copies and adapts the definition of SagaReturnType from @redux-saga/core as outlined above and makes the necessary changes throughout the type definitions.

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.

1 participant