Как объявить тип как всевозможную комбинацию типа объединения? - PullRequest
0 голосов
/ 15 февраля 2019

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 вверху этого поста.

1 Ответ

0 голосов
/ 15 февраля 2019

Создание такого союза - плохая идея.Это выйдет из-под контроля и создаст проблемы при компиляции.Вы, вероятно, можете сделать это с помощью псевдонимов рекурсивного типа, но это крайне нежелательно (т. Е. Вы можете обмануть компилятор, но это может не сработать в будущем)неправильно.Вы говорите, что функция с меньшим количеством параметров назначается функции с большим количеством параметров из-за any.Это не.В целом машинопись позволяет назначать функцию с меньшим количеством параметров там, где ожидается функция с большим количеством параметров.Реализация функции будет игнорировать дополнительные параметры, и никакого вреда от этого не будет:

let fn: (a: number) => void = function () { console.log("Don't care about your args!"); }

fn(1)// 1 is ignored but that is ok 

Мы можем обеспечить строгое равенство числа параметров на основе того факта, что кортежи имеют свойство длины и тот факт, что мыможет вывести фактический тип класса и извлечь фактические параметры из типа:

type FixedLengthFunction<T extends any[]> = (...args: { [k in keyof T]: any }) => void

type ErrorIfDifferentLength<TMethod, TExpected extends any[]> = 
    TMethod extends (...a: infer TParams) => any ? 
    TParams['length'] extends TExpected['length'] ? {}: { "!Error": "Number of parameters differ:", actual:  TParams['length'], expected: TExpected['length'] } : {}

function myDecorator<T extends any[]>(...a: T) {
    return <K extends string, TClass extends Record<K, FixedLengthFunction<T>>>(target: TClass & ErrorIfDifferentLength<TClass[K], T>, key: K): void => {

    }
}

// 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() {}
    // Argument of type 'Foo' is not assignable to parameter of type 'Foo & { "!Error": "Number of parameters differ:"; method: "d"; actual: 0; expected: 1; }'.
    // WAI
}
...