Bamontan
Bamontan

Reputation: 422

second parameter type depends on first param in typescript

Ok so I need some advanced typescript type tricks. I have a function that take 2 parameters, but I want the second parameter type to depends on the first parameter type. For exemple if my first parameter is a number, that my second param need to be a string, but if my first parameter is Record, that my second param be a number. I know I could do some function prototype overloading, but I want this to be dynamic, so It would look like something like this:

type Test = [number, string] | [Record<string, any>, number];

foo<Test>(34, "bar") // ok
foo<Test>({a:34}, 56) // ok
foo<Test>(34, 56) // error
foo<Test>({a:34}, "bar") // error

I don't have the advanced knowledge in typescript to figure it out on my own, so I come to all of you guys to guide me, if this is even possible.

Upvotes: 1

Views: 2561

Answers (2)

jcalz
jcalz

Reputation: 327624

You are allowed to use tuple types for rest parameters, including unions of such tuples. So you can declare foo() to be a generic function like this:

declare function foo<T extends readonly any[]>(...args: T): void;

which would cause your example to behave exactly as shown when you specify Test as the T parameter:

foo<Test>(34, "bar") // ok
foo<Test>({ a: 34 }, 56) // ok
foo<Test>(34, 56) // error
foo<Test>({ a: 34 }, "bar") // error

If you don't need foo() to be generic then you can hardcode the Test rest tuple parameter:

declare function bar(...args: Test): void;
bar(34, "bar") // ok
bar({ a: 34 }, 56) // ok
bar(34, 56) // error
bar({ a: 34 }, "bar") // error

Either way will work. TypeScript treats unions-of-rest-tuples as similar to overloads, and even the quickinfo in IntelliSense will display them as overloads:

bar(/* hints */)
// 1/2 bar(args_0: number, args_1: string): void
// 2/2 bar(args_0: Record<string, any>, args_1: number): void

Playground link to code

Upvotes: 2

akuiper
akuiper

Reputation: 214927

You can define the first parameter to be generic type T which extends number | Record<string, any> and then second parameter type to conditionally depend on type T:

function foo<T extends number | Record<string, any>>(
  x: T, y: T extends number ? string : number) {
}

Playground

Upvotes: 2

Related Questions