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
.