You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The type checker currently reduces/compares unions and intersections using the laws of idempotence (e.g., A & A = A), associativity (e.g., (A | B) | C = A | (B | C)), commutativity (e.g., A & B = B & A), and distributivity (e.g., A | (B & C) = (A | B) & (A | C)).
I think it would be great to introduce the absorption laws: That is, A | (A & B) and A & (A | B) should reduce to A.
Related are laws of subtype collapsing; that is, if B extends A, then A | B should reduce to A, and A & B should reduce to B. (Issue Spec Preview: Union types #805 implies that this already happens for unions, but I am not seeing the type checker doing an actual reduction.)
Finally, maybe slightly off-topic, it would be nice to reduce intersections of known-disjoint types to never or undefined|null depending on the strictness of null checks.
I should note that currently the type checker does consider all such equivalent types mutually compatible (meaning that a value of type A | (A & B) can be assigned to a variable of type A and vice-versa), even though they don't reduce to the same type. This is good, but actual reductions for absorption laws would:
improve completeness (right now there are expressions incorrectly flagged as type errors)
increase transparency/legibility (neither the type checker nor developers should want to drag around such unwieldy beasts as string & (string | number) & (string | object) & (string | {ad: number, nauseum: string}) when string would be better for everyone involved).
I don't know how expensive it would be to perform such reduction rules in the compiler. Before I or anyone looks into that, though, I'd be interested in finding out if such a feature would even be considered useful. Thoughts?
Code examples! Everywhere below that produces the error // Error, subsequent variable declarations must have the same type should type-check:
varo={};// Absorption Lawfunctionabsorption<A,B>(){varx: A&(A|B);varx: A|(A&B);varx: A;// Error, subsequent variable declarations must have the same type.// but these assignments workvary: A=oasA|(A&B);varz: A|(A&B)=oasA;}// Subtype CollapsingfunctionabsorptionExtends<A,BextendsA>(){varx: A&B;varx: B;// Error, subsequent variable declarations must have the same type.// but these assignments workvary: B=oasA&B;varz: A&B=oasB;varu: A|B;varu: A;// Error, subsequent variable declarations must have the same type.// but these assignments workvarv: A=oasA|B;varw: A|B=oasA;}// Intersections of known-disjoint types:varx: string&numberx='0';// Error as expectedx=0;// Error as expectedx=undefined;// Error as expected with strictNullChecks x=null;// Error as expected with strictNullChecks // butvarx: undefined|null;// Error, subsequent variable declarations must have the same type.varx: never;// Error, subsequent variable declarations must have the same type.// EDIT: maybe plays badly with tagged types?vary: string&object;y='hmm';// Error as expectedy={};// Error as expectedy=undefined;// Error as expected with strictNullChecks y=null;// Error as expected with strictNullChecks// butvary: undefined|null;// Error, subsequent variable declarations must have the same type.vary: never;// Error, subsequent variable declarations must have the same type./*---*/// if absorptionExtends is too expensive in general, // at least some specifics for string/number literals would be nice:absorptionExtends<string,'a'|'b'>();absorptionExtends<number,0|1>();// bottoms should absorb all intersections and be absorbed by all unions// but see https://github.com/Microsoft/TypeScript/issues/16038functionneverAbsorption<T>(){absorptionExtends<T,never>();}// tops should absorb all unions and be absorbed by all intersectionsfunctionemptyObjectAbsorption<T>(){absorptionExtends<{},T>();}// I guess "any" is a special case// as mentioned in https://github.com/Microsoft/TypeScript/issues/10715functionanyAbsorption<T>(){absorptionExtends<any,T>();absorptionExtends<T,any>();}// here we build up a complex type with a simple reductionvari: string;varj: typeofi|number;vark: typeofi|object;varl: typeofi|{ad: number,nauseum: string};varm: typeofi&typeofj&typeofk&typeofl;// let's look at typeof m:varm: string|(string&{ad: number;nauseum: string;})|(string&object)|(string&object&{ad: number;nauseum: string;})|(string&number)|(string&number&{ad: number;nauseum: string;})|(string&number&object)|(string&number&object&{ad: number;nauseum: string;});// yuck! typeof m should be stringvarm: string;// Error, subsequent variable declarations must have the same type.// but these assignments workvarn: string;n=m;n=null;m=n;
The type checker currently reduces/compares unions and intersections using the laws of idempotence (e.g.,
A & A=A), associativity (e.g.,(A | B) | C=A | (B | C)), commutativity (e.g.,A & B=B & A), and distributivity (e.g.,A | (B & C)=(A | B) & (A | C)).I think it would be great to introduce the absorption laws: That is,
A | (A & B)andA & (A | B)should reduce toA.Related are laws of subtype collapsing; that is, if
B extends A, thenA | Bshould reduce toA, andA & Bshould reduce toB. (Issue Spec Preview: Union types #805 implies that this already happens for unions, but I am not seeing the type checker doing an actual reduction.)Finally, maybe slightly off-topic, it would be nice to reduce intersections of known-disjoint types to
neverorundefined|nulldepending on the strictness of null checks.I should note that currently the type checker does consider all such equivalent types mutually compatible (meaning that a value of type
A | (A & B)can be assigned to a variable of typeAand vice-versa), even though they don't reduce to the same type. This is good, but actual reductions for absorption laws would:string & (string | number) & (string | object) & (string | {ad: number, nauseum: string})whenstringwould be better for everyone involved).I don't know how expensive it would be to perform such reduction rules in the compiler. Before I or anyone looks into that, though, I'd be interested in finding out if such a feature would even be considered useful. Thoughts?
Code examples! Everywhere below that produces the error
// Error, subsequent variable declarations must have the same typeshould type-check:Cheers!