Reputation: 862
This is very straightforward, here is the link to playground
As I have noticed, when you remove tuple type from condition in If<>
everything goes as expected. But I don't want to do it (t2
assertion blows), moreover I'd like to understand wy this happens. Is this just a bug? Why And<>
type always expands to true
?
type If<
TCond extends boolean,
TIfTrue,
TElse
> = [TCond] extends [true] ? TIfTrue : TElse; // if you remove tuples, it works
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]>>;
Upvotes: 2
Views: 444
Reputation: 51609
First, I'll try to explain why If with tuples differs from If without tuples:
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
ok, now to the question. Here's the strange thing I (and Titian Cernicova-Dragomir) found:
type SameAsT<T extends boolean> = Not<Not<T>>;
type X1 = SameAsT<false>; // true !
type X2 = SameAsT<true>; // true again
// but why?
If you expand all the literal types, it works as expected:
type ReallyNotFalse = [false] extends [true] ? false : true; // true
type ReallyNotNotFalse = [([false] extends [true] ? false : true)] extends [true] ? false : true; // false
Looks like a bug in the compiler.
Incidentally, Not
based on If
without tuples works as expected
type SameAsT2<T extends boolean> = Not2<Not2<T>>;
type Y1 = SameAsT2<false>; // false
type Y2 = SameAsT2<true>; // true
So, it's possible to use some other way to suppress distributive union type in conditions and to make the code in question work. One way is to add superfluous condition which always evaluates to true, and does not have TCond
as the type being checked:
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]>>;
Upvotes: 1