Skip to content

Support Recursive Conditional Types #36142

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

Closed
5 tasks done
Roaders opened this issue Jan 11, 2020 · 3 comments
Closed
5 tasks done

Support Recursive Conditional Types #36142

Roaders opened this issue Jan 11, 2020 · 3 comments
Labels
Duplicate An existing issue was already created

Comments

@Roaders
Copy link

Roaders commented Jan 11, 2020

Search Terms

recursive conditional

Suggestion

I want to be able to use recursive conditional types.

Use Cases

In this type:

type MapParams<T extends any[]> =
    ((...t: T) => any) extends ((first: string, ...tail: infer TRest) => any) ?
    Prepend<First<T>, MapParams<TRest>> :
    Partial<T>;

I am making all elements optional except for the ones at the start that are strings. This fails as MapParams references itself so I get a Type alias 'MapParams' circularly references itself.(2456) error.

Examples

type Prepend<TFirst, TRest extends any[]> =
    ((head: TFirst, ...args: TRest) => any) extends ((...args: infer U) => any)
    ? U
    : TRest;

type First<T extends any[]> = T extends [infer TFirst, ...any[]] ? TFirst : never;

type MapParams<T extends any[]> =
    ((...t: T) => any) extends ((first: string, ...tail: infer TRest) => any) ?
    Prepend<First<T>, Partial<TRest>> : // should be Prepend<First<T>, MapParams<TRest>>
    Partial<T>;

function myFunction(one: string, two: string, three: number, four: boolean) { }
function mappedFunction(...args: MapParams<Parameters<typeof myFunction>>) { }

mappedFunction("") // should NOT work - string at index 1 should be required
mappedFunction("", "") // should work
mappedFunction(undefined) // should not work
mappedFunction() // should not work

Playground

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@Roaders
Copy link
Author

Roaders commented Jan 11, 2020

I can up with this inelegant solution that works but only handles up to 10 params

type MapOne<T extends any[]> = ((...t: T) => any) extends ((f: string, ...t: infer TRest) => any) ? Prepend<First<T>, MapTwo<TRest>> : Partial<T>;
type MapTwo<T extends any[]> = ((...t: T) => any) extends ((f: string, ...t: infer TRest) => any) ? Prepend<First<T>, MapThree<TRest>> : Partial<T>;
type MapThree<T extends any[]> = ((...t: T) => any) extends ((f: string, ...t: infer TRest) => any) ? Prepend<First<T>, MapFour<TRest>> : Partial<T>;
type MapFour<T extends any[]> = ((...t: T) => any) extends ((f: string, ...t: infer TRest) => any) ? Prepend<First<T>, MapFive<TRest>> : Partial<T>;
type MapFive<T extends any[]> = ((...t: T) => any) extends ((f: string, ...t: infer TRest) => any) ? Prepend<First<T>, MapSix<TRest>> : Partial<T>;
type MapSix<T extends any[]> = ((...t: T) => any) extends ((f: string, ...t: infer TRest) => any) ? Prepend<First<T>, MapSeven<TRest>> : Partial<T>;
type MapSeven<T extends any[]> = ((...t: T) => any) extends ((f: string, ...t: infer TRest) => any) ? Prepend<First<T>, MapEight<TRest>> : Partial<T>;
type MapEight<T extends any[]> = ((...t: T) => any) extends ((f: string, ...t: infer TRest) => any) ? Prepend<First<T>, MapNine<TRest>> : Partial<T>;
type MapNine<T extends any[]> = ((...t: T) => any) extends ((f: string, ...t: infer TRest) => any) ? Prepend<First<T>, MapTen<TRest>> : Partial<T>;
type MapTen<T extends any[]> = ((...t: T) => any) extends ((f: string, ...t: infer TRest) => any) ? Prepend<First<T>, Partial<TRest>> : Partial<T>;

Playground

@jcalz
Copy link
Contributor

jcalz commented Jan 11, 2020

Duplicate of #26980, but yes, please.

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Jan 12, 2020

@Roaders
Sounds like you could use trampolines (as a workaround)
#26980 (comment)

Or use ts-toolbelt and how they do recursion.

@DanielRosenwasser DanielRosenwasser added the Duplicate An existing issue was already created label Jan 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants