Сначала я попытаюсь объяснить, почему If с кортежами отличается от If без кортежей:
type If<
TCond extends boolean,
TIfTrue,
TElse
> = [TCond] extends [true] ? TIfTrue : TElse; // if you remove tuples, it works
type If2<
TCond extends boolean,
TIfTrue,
TElse
> = TCond extends true ? TIfTrue : TElse;
type Not<T extends boolean> = If<(T), false, true>;
type Not2<T extends boolean> = If2<(T), false, true>;
// with tuples, Not<boolean> is true because that's how you set it up
type A1 = Not<boolean>; // true
type A2 = Not<true>; // false
type A3 = Not<false>; // true
// without typles, TCond extends true is distributive over union types,
// and boolean is really just a union of true | false,
// so Not2<boolean> is boolean
type B1 = Not2<boolean>; // boolean
type B2 = Not2<true>; // false
type B3 = Not2<false>; // true
Хорошо, теперь к вопросу. Вот странная вещь, которую я (и Тициан Черникова-Драгомир) нашел:
type SameAsT<T extends boolean> = Not<Not<T>>;
type X1 = SameAsT<false>; // true !
type X2 = SameAsT<true>; // true again
// but why?
Если развернуть все литеральные типы, все будет работать как положено:
type ReallyNotFalse = [false] extends [true] ? false : true; // true
type ReallyNotNotFalse = [([false] extends [true] ? false : true)] extends [true] ? false : true; // false
Похоже на ошибку в компиляторе.
Кстати, Not
на основе If
без кортежей работает как положено
type SameAsT2<T extends boolean> = Not2<Not2<T>>;
type Y1 = SameAsT2<false>; // false
type Y2 = SameAsT2<true>; // true
Таким образом, можно использовать другой способ подавления типа распределительного объединения в условиях и заставить рассматриваемый код работать. Одним из способов является добавление лишнего условия, которое всегда оценивается как true и не имеет TCond
в качестве проверяемого типа:
type If<
TCond extends boolean,
TIfTrue,
TElse
> = {} extends TCond ? TCond extends true ? TIfTrue : TElse : never;
type Not<T extends boolean> = If<(T), false, true>;
type IsNever<TSuspect> = TSuspect extends never ? true : false;
type AssertFalse<TSuspect extends false> = TSuspect;
type AssertTrue <TSuspect extends true> = TSuspect;
// V~~ always true
type And<T extends boolean[]> = Not<Not<IsNever<Extract<T[number], false>>>>;
type AndExpected<T extends boolean[]> = IsNever<Extract<T[number], false>>;
type t0 = AssertFalse<Not<true>>;
type t1 = AssertTrue<Not<false>>;
type t2 = AssertTrue<Not<boolean>>;
type t3 = AssertFalse<And<[false]>>;
type t4 = AssertFalse<AndExpected<[false]>>;