Skip to content

Commit 70d3914

Browse files
author
MichaelMitchell-at
committed
Make identity comparison reflexive across unions or intersections of the same type
1 parent 56a0825 commit 70d3914

File tree

5 files changed

+174
-1
lines changed

5 files changed

+174
-1
lines changed

src/compiler/checker.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -22158,7 +22158,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2215822158
if (source === target) return Ternary.True;
2215922159

2216022160
if (relation === identityRelation) {
22161-
if (source.flags !== target.flags) return Ternary.False;
22161+
if (source.flags !== target.flags) {
22162+
// Since normalization doesn't perform subtype reduction, unions and intersections might have duplicates, so
22163+
// one of the types might be a union or intersection whose constituents are all identical to the other type.
22164+
const eachSourceAndTarget: [UnionOrIntersectionType, Type] | undefined =
22165+
source.flags & TypeFlags.Union ? [source as UnionOrIntersectionType, target]
22166+
: target.flags & TypeFlags.Union ? [target as UnionOrIntersectionType, source]
22167+
: source.flags & TypeFlags.Intersection ? [source as UnionOrIntersectionType, target]
22168+
: target.flags & TypeFlags.Intersection ? [target as UnionOrIntersectionType, source]
22169+
: undefined;
22170+
if (eachSourceAndTarget) {
22171+
traceUnionsOrIntersectionsTooLarge(...eachSourceAndTarget);
22172+
return eachTypeRelatedToType(...eachSourceAndTarget, reportErrors, IntersectionState.None);
22173+
}
22174+
return Ternary.False;
22175+
}
22176+
2216222177
if (source.flags & TypeFlags.Singleton) return Ternary.True;
2216322178
traceUnionsOrIntersectionsTooLarge(source, target);
2216422179
return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None, recursionFlags);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//// [tests/cases/compiler/reflexiveIdentityRelation.ts] ////
2+
3+
//// [reflexiveIdentityRelation.ts]
4+
namespace reflexiveIdentityRelation {
5+
type Equals<A, B> = (<T>() => T extends B ? 1 : 0) extends (<T>() => T extends A ? 1 : 0) ? true : false;
6+
7+
type Intersection = Equals<{a: 1} & {a: 1}, {a: 1}>; // true
8+
type Union = Equals<{a: 1} | {a: 1}, {a: 1}>; // true
9+
type UnionOfIntersection = Equals<{a: 1} & {b: 2} | {a: 1} & {b: 2}, {a: 1} & {b: 2}>; // true
10+
11+
// The intersection distributes to `{a: 1} & {a: 1} | {a: 1} & {b: 2} | {b: 2} & {a: 1} | {b: 2} & {b: 2}`
12+
// which is not identical to `{a: 1} | {b: 2}`
13+
type IntersectionOfUnion = Equals<({a: 1} | {b: 2}) & ({a: 1} | {b: 2}), {a: 1} | {b: 2}>; // false
14+
}
15+
16+
17+
//// [reflexiveIdentityRelation.js]
18+
"use strict";
19+
20+
21+
//// [reflexiveIdentityRelation.d.ts]
22+
declare namespace reflexiveIdentityRelation {
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//// [tests/cases/compiler/reflexiveIdentityRelation.ts] ////
2+
3+
=== reflexiveIdentityRelation.ts ===
4+
namespace reflexiveIdentityRelation {
5+
>reflexiveIdentityRelation : Symbol(reflexiveIdentityRelation, Decl(reflexiveIdentityRelation.ts, 0, 0))
6+
7+
type Equals<A, B> = (<T>() => T extends B ? 1 : 0) extends (<T>() => T extends A ? 1 : 0) ? true : false;
8+
>Equals : Symbol(Equals, Decl(reflexiveIdentityRelation.ts, 0, 37))
9+
>A : Symbol(A, Decl(reflexiveIdentityRelation.ts, 1, 16))
10+
>B : Symbol(B, Decl(reflexiveIdentityRelation.ts, 1, 18))
11+
>T : Symbol(T, Decl(reflexiveIdentityRelation.ts, 1, 26))
12+
>T : Symbol(T, Decl(reflexiveIdentityRelation.ts, 1, 26))
13+
>B : Symbol(B, Decl(reflexiveIdentityRelation.ts, 1, 18))
14+
>T : Symbol(T, Decl(reflexiveIdentityRelation.ts, 1, 65))
15+
>T : Symbol(T, Decl(reflexiveIdentityRelation.ts, 1, 65))
16+
>A : Symbol(A, Decl(reflexiveIdentityRelation.ts, 1, 16))
17+
18+
type Intersection = Equals<{a: 1} & {a: 1}, {a: 1}>; // true
19+
>Intersection : Symbol(Intersection, Decl(reflexiveIdentityRelation.ts, 1, 109))
20+
>Equals : Symbol(Equals, Decl(reflexiveIdentityRelation.ts, 0, 37))
21+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 3, 32))
22+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 3, 41))
23+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 3, 49))
24+
25+
type Union = Equals<{a: 1} | {a: 1}, {a: 1}>; // true
26+
>Union : Symbol(Union, Decl(reflexiveIdentityRelation.ts, 3, 56))
27+
>Equals : Symbol(Equals, Decl(reflexiveIdentityRelation.ts, 0, 37))
28+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 4, 25))
29+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 4, 34))
30+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 4, 42))
31+
32+
type UnionOfIntersection = Equals<{a: 1} & {b: 2} | {a: 1} & {b: 2}, {a: 1} & {b: 2}>; // true
33+
>UnionOfIntersection : Symbol(UnionOfIntersection, Decl(reflexiveIdentityRelation.ts, 4, 49))
34+
>Equals : Symbol(Equals, Decl(reflexiveIdentityRelation.ts, 0, 37))
35+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 5, 39))
36+
>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 5, 48))
37+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 5, 57))
38+
>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 5, 66))
39+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 5, 74))
40+
>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 5, 83))
41+
42+
// The intersection distributes to `{a: 1} & {a: 1} | {a: 1} & {b: 2} | {b: 2} & {a: 1} | {b: 2} & {b: 2}`
43+
// which is not identical to `{a: 1} | {b: 2}`
44+
type IntersectionOfUnion = Equals<({a: 1} | {b: 2}) & ({a: 1} | {b: 2}), {a: 1} | {b: 2}>; // false
45+
>IntersectionOfUnion : Symbol(IntersectionOfUnion, Decl(reflexiveIdentityRelation.ts, 5, 90))
46+
>Equals : Symbol(Equals, Decl(reflexiveIdentityRelation.ts, 0, 37))
47+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 9, 40))
48+
>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 9, 49))
49+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 9, 60))
50+
>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 9, 69))
51+
>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 9, 78))
52+
>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 9, 87))
53+
}
54+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//// [tests/cases/compiler/reflexiveIdentityRelation.ts] ////
2+
3+
=== reflexiveIdentityRelation.ts ===
4+
namespace reflexiveIdentityRelation {
5+
type Equals<A, B> = (<T>() => T extends B ? 1 : 0) extends (<T>() => T extends A ? 1 : 0) ? true : false;
6+
>Equals : Equals<A, B>
7+
> : ^^^^^^^^^^^^
8+
>true : true
9+
> : ^^^^
10+
>false : false
11+
> : ^^^^^
12+
13+
type Intersection = Equals<{a: 1} & {a: 1}, {a: 1}>; // true
14+
>Intersection : true
15+
> : ^^^^
16+
>a : 1
17+
> : ^
18+
>a : 1
19+
> : ^
20+
>a : 1
21+
> : ^
22+
23+
type Union = Equals<{a: 1} | {a: 1}, {a: 1}>; // true
24+
>Union : true
25+
> : ^^^^
26+
>a : 1
27+
> : ^
28+
>a : 1
29+
> : ^
30+
>a : 1
31+
> : ^
32+
33+
type UnionOfIntersection = Equals<{a: 1} & {b: 2} | {a: 1} & {b: 2}, {a: 1} & {b: 2}>; // true
34+
>UnionOfIntersection : true
35+
> : ^^^^
36+
>a : 1
37+
> : ^
38+
>b : 2
39+
> : ^
40+
>a : 1
41+
> : ^
42+
>b : 2
43+
> : ^
44+
>a : 1
45+
> : ^
46+
>b : 2
47+
> : ^
48+
49+
// The intersection distributes to `{a: 1} & {a: 1} | {a: 1} & {b: 2} | {b: 2} & {a: 1} | {b: 2} & {b: 2}`
50+
// which is not identical to `{a: 1} | {b: 2}`
51+
type IntersectionOfUnion = Equals<({a: 1} | {b: 2}) & ({a: 1} | {b: 2}), {a: 1} | {b: 2}>; // false
52+
>IntersectionOfUnion : false
53+
> : ^^^^^
54+
>a : 1
55+
> : ^
56+
>b : 2
57+
> : ^
58+
>a : 1
59+
> : ^
60+
>b : 2
61+
> : ^
62+
>a : 1
63+
> : ^
64+
>b : 2
65+
> : ^
66+
}
67+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @strict: true
2+
// @declaration: true
3+
4+
namespace reflexiveIdentityRelation {
5+
type Equals<A, B> = (<T>() => T extends B ? 1 : 0) extends (<T>() => T extends A ? 1 : 0) ? true : false;
6+
7+
type Intersection = Equals<{a: 1} & {a: 1}, {a: 1}>; // true
8+
type Union = Equals<{a: 1} | {a: 1}, {a: 1}>; // true
9+
type UnionOfIntersection = Equals<{a: 1} & {b: 2} | {a: 1} & {b: 2}, {a: 1} & {b: 2}>; // true
10+
11+
// The intersection distributes to `{a: 1} & {a: 1} | {a: 1} & {b: 2} | {b: 2} & {a: 1} | {b: 2} & {b: 2}`
12+
// which is not identical to `{a: 1} | {b: 2}`
13+
type IntersectionOfUnion = Equals<({a: 1} | {b: 2}) & ({a: 1} | {b: 2}), {a: 1} | {b: 2}>; // false
14+
}

0 commit comments

Comments
 (0)