Объединить тип охранных подписей - PullRequest
2 голосов
/ 23 марта 2020

Я следовал примеру, объясненному здесь , чтобы создать функцию Or, которая принимает несколько типов защиты и возвращает охрану типа для типа объединения. например: x is A + x is B => x is A | B. Однако я не могу использовать возвращенную функцию в качестве аргумента для Array.filter.

Определения:

type TypeGuard<A, B extends A> = (a: A) => a is B;
type GuardType<T> = T extends (o: any) => o is infer U ? U : never

class A { q: any }
class B { p: any }
declare function isA(x: any): x is A
declare function isB(x: any): x is B

function Or<T extends TypeGuard<any, any>>(guards: T[]): T extends TypeGuard<infer A, any> ? (a: A) => a is GuardType<T> : never;
function Or<T extends TypeGuard<any, any>>(guards: T[]) {
    return function (arg: T) {
        return guards.some(function (predicate) {
            predicate(arg);
        });
    }
}

Пример кода:

let isAOrB = Or([isA, isB]); // inferred as ((a: any) => a is A) | ((a: any) => a is B)
let isOr_value = isAOrB({ q: 'a' }); // here isAOrB is inferred as (a: any) => a is A | B which is what I want
[{}].filter(isAOrB).forEach(x => { }); // here I expected x's type to be inferred as A | B because of the type guard, however filter's overload is the regular one, returning the same {}[] type as the source array

I знаю, что я могу явно написать лямбда-выражение в качестве аргумента filter для принудительного вывода типа:

[{}].filter((x): x is A | B => isAOrB(x)).forEach(x => { });

Но это именно то, чего я хотел бы избежать.

Та же проблема с функцией And, объединяющей охранники типа

Я использовал показанную конструкцию UnionToIntersection здесь , но не могу правильно ввести функцию And, вот моя попытка и ошибка, которую я получаю :

function And<T extends TypeGuard<any, any>>(guards: T[]): T extends TypeGuard<infer A, any> ? (a: A) => a is UnionToIntersection<GuardType<T>> : never;
// the above gives error A type predicate's type must be assignable to its parameter's type.
  Type 'UnionToIntersection<GuardType<T>>' is not assignable to type 'A'.
    Type 'unknown' is not assignable to type 'A'.

1 Ответ

3 голосов
/ 23 марта 2020

Проблема здесь заключается в том, что вы случайно распространяете свой условный тип . Если T является параметром пустого типа, то условный тип T extends Foo ? Bar : Baz в конечном итоге разбьет T на его члены объединения, оценит условное условие для каждого члена и объединит результаты обратно вместе. Это приводит к нежелательному ((a: any) => a is A) | ((a: any) => a is B).

. Самый простой способ отключить такое распределение - это «одеть» параметр «голый тип» в одноэлементный кортеж, например, [T] extends [Foo] ? Bar : Baz:

function Or<T extends TypeGuard<any, any>>(guards: T[]): 
  [T] extends [TypeGuard<infer A, any>] ? (a: A) => a is GuardType<T> : never;

Это должно дать вам поведение, которое вы ищете:

[{}].filter(isAOrB).forEach(x => { }); // x is A | B

То же самое относится к вашему And, с незначительными складками, которые компилятор не поймет, что UnionToIntersection<...> будет правильным сужением для функции защиты типов, поэтому вы можете захотеть обернуть ее в Extract<> следующим образом:

declare function And<T extends TypeGuard<any, any>>(guards: T[]):
    [T] extends [TypeGuard<infer A, any>] ? 
    (a: A) => a is Extract<UnionToIntersection<GuardType<T>>, A> : never;

let isAAndB = And([isA, isB]); 
let isAnd_value = isAAndB({ q: 'a' }); 
[{}].filter(isAAndB).forEach(x => { }); // A & B

Выглядит хорошо для меня сейчас. Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

...