TL, DR;
Здесь мне нужно как-то объявить объединение всех возможных комбинаций данного объединения.
type Combinations<SomeUnion, T extends any[]> = /* Some magic */
// ^^^^^^^^^^^^^^^
// this type argument provides the information
// about what is the length of expected combination.
// then
Combinations<string | number, ['x', 'y']> =
[string, string] |
[string, number] |
[number, string] |
[number, number]
Combinations<string | number | boolean, ['x', 'y']> =
[string, string] |
[string, number] |
[string, boolean] |
[number, string] |
[number, number] |
[number, boolean] |
[boolean, string] |
[boolean, number] |
[boolean, boolean]
Combinations<string | number, ['x', 'y', 'z']> =
[string, string, string] |
[string, string, number] |
[string, number, string] |
[string, number, number] |
[number, string, string] |
[number, string, number] |
[number, number, string] |
[number, number, number]
Detail
Я хочу определить декоратор метода, который может безошибочно гарантировать, что количество аргументов декорируемого метода точно равно количеству аргументов, переданных этому декоратору.
type FixedLengthFunction<T extends any[]> = (...args: { [k in keyof T]: any }) => void
function myDecorator<T extends any[]>(...args: T) {
return <K extends string>(
target: { [k in K]: FixedLengthFunction<T> },
methodName: K,
desc: any
) => {}
}
// Note: WAI => Works as intented
class Foo {
@myDecorator()
a() {}
// expected to be correct,
// and actually passes the type system.
// WAI
@myDecorator()
b(x: number) {}
// expected to be incorrect since 'b' has one more argument,
// and actually catched by the type system.
// WAI
@myDecorator('m')
c(x: number) {}
// expected to be correct,
// and actually passes the type system.
// WAI
@myDecorator('m')
d() {}
// expected to be incorrect since 'd' has one less argument,
// but still passes the type system.
// not WAI
}
То же самое относится ко всемсценарии, в которых декорированный метод имеет меньше аргументов, чем вызов декоратора.
Основная причина: (a: SomeType) => void
совместим с (a: any, b: any) => void
, поскольку any
может быть неопределенным.
Затем я изменилFixedLengthFunction
в
type Defined = string | number | boolean | symbol | object
type FixedLengthFunction<T extends any[]> =
(...args: { [k in keyof T]: Defined }) => void
// ^^^^^^^
// changes: any -> Defined
Однако, он становится «ложно положительным» и жалуется, что
@myDecorator('m')
c(x: number) {}
неверен.
На этот раз причина (x: number) => void
несовместим с (arg_0: Defined) => void
.number
является суженной версией Defined
, и сужение типа параметров приводит к исчезновению LSP, что приводит к ошибке.
Проблема заключается в том, что FixedLengthFunction<['m', 'n']>
разрешается до (...args: [Defined, Defined]) => void
, который далее разрешается как (arg_0: Defined, arg_1: Defined) => void
.
В то время как я на самом деле хочу:
(...args:
[number, number] |
[string, number] |
[boolean, string]
/* ...and all other possible combinations of length 2 */
) => void
Итак, мне нужен магический тип Combinations
вверху этого поста.