1

Is TypeScript incapable of using ReturnType on union types?

type NumberParser = (input: string) => number | DiplomacyError;
type StringParser = (input: string) => string | DiplomacyError;
type Parser = NumberParser | StringParser;

export interface Schema {
  [key: string]: Parser | Schema;
}

export type RawType<T extends Schema> = {
  [Property in keyof T]: T[Property] extends Schema
    ? RawType<T[Property]>
    : ReturnType<T[Property]>; // T[Property] marked as error
};

<T[Property]> gives the following error:

Type 'T[Property]' does not satisfy the constraint '(...args: any) => any'.
  Type 'T[keyof T]' is not assignable to type '(...args: any) => any'.
    Type 'T[string] | T[number] | T[symbol]' is not assignable to type '(...args: any) => any'.
      Type 'T[string]' is not assignable to type '(...args: any) => any'.
        Type 'Parser | Schema' is not assignable to type '(...args: any) => any'.
          Type 'Schema' is not assignable to type '(...args: any) => any'.
            Type 'Schema' provides no match for the signature '(...args: any): any'.ts(2344)
Davide Valdo
  • 693
  • 7
  • 18

1 Answers1

2

It is a known issue in TypeScript that the false branch of a conditional type does not get its types narrowed. So in T extends U ? F<T> : G<T> does not take G<T> and replace it with something like G<Exclude<T, U>>. As far as the compiler is concerned, the T in G<T> might still be assignable to U, even though it's obvious to us that it won't be. See microsoft/TypeScript#29188. It looks like there was some work done to address this at microsoft/TypeScript#24821, but it was not merged. It's not clear to me if or when this issue will be resolved.

Until then, it's easy enough (if annoying) to do such narrowing yourself when necessary:

export type RawType<T extends Schema> = {
    [K in keyof T]: T[K] extends Schema
    ? RawType<T[K]>
    : ReturnType<Exclude<T[K], Schema>>;
};

Or possibly

export type RawType<T extends Schema> = {
    [K in keyof T]: T[K] extends Schema
    ? RawType<T[K]>
    : ReturnType<Extract<T[K], Parser>>;
};

Playground link to code

jcalz
  • 125,133
  • 11
  • 145
  • 170
  • Thanks, please see the edited question if you can. – Davide Valdo Apr 10 '21 at 03:04
  • 1
    It looks like the original question has been solved, and if you have followup questions (as opposed to something broken about the original answer) you should probably post a new question about it instead of expanding the scope of an already-answered question. This will also help you by getting new eyes on it (especially since mine are about to close for the night, as it's getting late in my time zone). Good luck! – jcalz Apr 10 '21 at 03:37
  • https://stackoverflow.com/questions/67030605/how-exactly-do-recursive-conditional-types-work but is it actually a follow-up question? Closing my eyes as well, couldn't really sleep because of this – Davide Valdo Apr 10 '21 at 03:48