Home > Net >  Type compatibility for conditional rest parameters with tuple types including union
Type compatibility for conditional rest parameters with tuple types including union

Time:09-22

So, here are the resources I am basing my expectations on:

https://github.com/Microsoft/TypeScript/pull/24897 (Rest parameter with tuple behavior) https://www.typescriptlang.org/docs/handbook/type-compatibility.html (Type compatibility)

interface Theme {
  primary: string;
  secondary: string;
  tertiary: string;
}

type Color<ExtraArgType = void> = (
    theme: Theme,
    ...other: ExtraArgType extends void ? [undefined?] : [ExtraArgType]
  ) => string;

type ButtonMode = 'contained' | 'outlined' | 'text';

// TS does not allow this
const foo: Color<ButtonMode> = (theme) => theme.primary;

// Allowed, but need to include unused argument
const bar: Color<ButtonMode> = (theme, _) => theme.primary;

// What I want to be allowed
const baz: Color<ButtonMode> = (theme) => theme.primary;
const qux: Color<ButtonMode> = (theme, mode) => mode === 'contained' ? theme.primary : theme.secondary;
const foobar: Color = (theme) => theme.primary;

Note, this is not an issue when I use a non-union type such as Color<string>.

How do I avoid having the extra unused argument?

CodePudding user response:

It's actually a simple but unintuitive solution:

type Color<ExtraArgType = void> = (
  theme: Theme,
  ...other: [ExtraArgType] extends [void] ? [undefined?] : [ExtraArgType]
) => string;

This is to prevent the distributive nature of conditional types, as outlined in the last example of the documentation on conditional types. See this question for a more detailed response about this.

In the future, I suggest using never instead of void.

Playground

  • Related