jrr
jrr

Reputation: 1997

Restrict function parameter types to a single case of a union type parameter

So I have a union of tuples, and I can use it to declare locals:

type KnownPair = ["dog", "paws"] | ["fish", "scales"];

const goodPair: KnownPair = ["dog", "paws"];

//@ts-expect-error you can't mix them:
const badPair: KnownPair = ["dog", "scales"];

I want to declare a function that uses the type to describe multiple parameters. I can do it with spread syntax:

function foo<T extends KnownPair>(...args: T) {
  console.log(`${args[0]} has ${args[1]}`);
}

const goodCall = foo("fish", "scales");

//@ts-expect-error you can't mix these, either:
const badCall = foo("fish", "paws");

But when I try to use conventional function parameters, it gets interesting:

function bar<T extends KnownPair>(a: T[0], b: T[1]) {
  console.log(`${a} has ${b}`);
}

const goodCall2 = bar("dog", "paws");

//@ts-expect-error Typescript rejects a bad explicit type argument:
const badCall2 = bar<["dog","scales"]>("dog", "scales");

// but it doesn't see any problems when inferring the type:
const shouldBeBad = bar("dog", "scales");

It's like each function parameter is individually checked against each case of the union. What's going on? Is it possible to declare bar in a way that enforces as well as foo?

References:

Upvotes: 1

Views: 160

Answers (1)

Michael Mrozek
Michael Mrozek

Reputation: 175335

The problem with

function bar<T extends KnownPair>(a: T[0], b: T[1]) {

is T[0] is 'dog | 'fish' and T[1] is 'paws' | 'scales', but nothing in the type information tells Typescript that they're related. I think the easiest fix is to still use your spread parameters solution, but destructure them into separate variables:

function bar<T extends KnownPair>(...[a, b]: T) {
  console.log(`${a} has ${b}`);
}

Upvotes: 1

Related Questions