diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 80b61edd3657a..4a44fac0382a1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22158,7 +22158,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (source === target) return Ternary.True; if (relation === identityRelation) { - if (source.flags !== target.flags) return Ternary.False; + if (source.flags !== target.flags) { + // Since normalization doesn't perform subtype reduction, unions and intersections might have duplicates, so + // one of the types might be a union or intersection whose constituents are all identical to the other type. + const eachSourceAndTarget: [UnionOrIntersectionType, Type] | undefined = source.flags & TypeFlags.Union ? [source as UnionOrIntersectionType, target] + : target.flags & TypeFlags.Union ? [target as UnionOrIntersectionType, source] + : source.flags & TypeFlags.Intersection ? [source as UnionOrIntersectionType, target] + : target.flags & TypeFlags.Intersection ? [target as UnionOrIntersectionType, source] + : undefined; + if (eachSourceAndTarget) { + traceUnionsOrIntersectionsTooLarge(...eachSourceAndTarget); + return eachTypeRelatedToType(...eachSourceAndTarget, reportErrors, IntersectionState.None); + } + return Ternary.False; + } + if (source.flags & TypeFlags.Singleton) return Ternary.True; traceUnionsOrIntersectionsTooLarge(source, target); return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None, recursionFlags); diff --git a/tests/baselines/reference/reflexiveIdentityRelation.js b/tests/baselines/reference/reflexiveIdentityRelation.js new file mode 100644 index 0000000000000..a8fae38808eb2 --- /dev/null +++ b/tests/baselines/reference/reflexiveIdentityRelation.js @@ -0,0 +1,23 @@ +//// [tests/cases/compiler/reflexiveIdentityRelation.ts] //// + +//// [reflexiveIdentityRelation.ts] +namespace reflexiveIdentityRelation { + type Equals = (() => T extends B ? 1 : 0) extends (() => T extends A ? 1 : 0) ? true : false; + + type Intersection = Equals<{a: 1} & {a: 1}, {a: 1}>; // true + type Union = Equals<{a: 1} | {a: 1}, {a: 1}>; // true + type UnionOfIntersection = Equals<{a: 1} & {b: 2} | {a: 1} & {b: 2}, {a: 1} & {b: 2}>; // true + + // The intersection distributes to `{a: 1} & {a: 1} | {a: 1} & {b: 2} | {b: 2} & {a: 1} | {b: 2} & {b: 2}` + // which is not identical to `{a: 1} | {b: 2}` + type IntersectionOfUnion = Equals<({a: 1} | {b: 2}) & ({a: 1} | {b: 2}), {a: 1} | {b: 2}>; // false +} + + +//// [reflexiveIdentityRelation.js] +"use strict"; + + +//// [reflexiveIdentityRelation.d.ts] +declare namespace reflexiveIdentityRelation { +} diff --git a/tests/baselines/reference/reflexiveIdentityRelation.symbols b/tests/baselines/reference/reflexiveIdentityRelation.symbols new file mode 100644 index 0000000000000..414e66557af67 --- /dev/null +++ b/tests/baselines/reference/reflexiveIdentityRelation.symbols @@ -0,0 +1,54 @@ +//// [tests/cases/compiler/reflexiveIdentityRelation.ts] //// + +=== reflexiveIdentityRelation.ts === +namespace reflexiveIdentityRelation { +>reflexiveIdentityRelation : Symbol(reflexiveIdentityRelation, Decl(reflexiveIdentityRelation.ts, 0, 0)) + + type Equals = (() => T extends B ? 1 : 0) extends (() => T extends A ? 1 : 0) ? true : false; +>Equals : Symbol(Equals, Decl(reflexiveIdentityRelation.ts, 0, 37)) +>A : Symbol(A, Decl(reflexiveIdentityRelation.ts, 1, 16)) +>B : Symbol(B, Decl(reflexiveIdentityRelation.ts, 1, 18)) +>T : Symbol(T, Decl(reflexiveIdentityRelation.ts, 1, 26)) +>T : Symbol(T, Decl(reflexiveIdentityRelation.ts, 1, 26)) +>B : Symbol(B, Decl(reflexiveIdentityRelation.ts, 1, 18)) +>T : Symbol(T, Decl(reflexiveIdentityRelation.ts, 1, 65)) +>T : Symbol(T, Decl(reflexiveIdentityRelation.ts, 1, 65)) +>A : Symbol(A, Decl(reflexiveIdentityRelation.ts, 1, 16)) + + type Intersection = Equals<{a: 1} & {a: 1}, {a: 1}>; // true +>Intersection : Symbol(Intersection, Decl(reflexiveIdentityRelation.ts, 1, 109)) +>Equals : Symbol(Equals, Decl(reflexiveIdentityRelation.ts, 0, 37)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 3, 32)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 3, 41)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 3, 49)) + + type Union = Equals<{a: 1} | {a: 1}, {a: 1}>; // true +>Union : Symbol(Union, Decl(reflexiveIdentityRelation.ts, 3, 56)) +>Equals : Symbol(Equals, Decl(reflexiveIdentityRelation.ts, 0, 37)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 4, 25)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 4, 34)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 4, 42)) + + type UnionOfIntersection = Equals<{a: 1} & {b: 2} | {a: 1} & {b: 2}, {a: 1} & {b: 2}>; // true +>UnionOfIntersection : Symbol(UnionOfIntersection, Decl(reflexiveIdentityRelation.ts, 4, 49)) +>Equals : Symbol(Equals, Decl(reflexiveIdentityRelation.ts, 0, 37)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 5, 39)) +>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 5, 48)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 5, 57)) +>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 5, 66)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 5, 74)) +>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 5, 83)) + + // The intersection distributes to `{a: 1} & {a: 1} | {a: 1} & {b: 2} | {b: 2} & {a: 1} | {b: 2} & {b: 2}` + // which is not identical to `{a: 1} | {b: 2}` + type IntersectionOfUnion = Equals<({a: 1} | {b: 2}) & ({a: 1} | {b: 2}), {a: 1} | {b: 2}>; // false +>IntersectionOfUnion : Symbol(IntersectionOfUnion, Decl(reflexiveIdentityRelation.ts, 5, 90)) +>Equals : Symbol(Equals, Decl(reflexiveIdentityRelation.ts, 0, 37)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 9, 40)) +>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 9, 49)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 9, 60)) +>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 9, 69)) +>a : Symbol(a, Decl(reflexiveIdentityRelation.ts, 9, 78)) +>b : Symbol(b, Decl(reflexiveIdentityRelation.ts, 9, 87)) +} + diff --git a/tests/baselines/reference/reflexiveIdentityRelation.types b/tests/baselines/reference/reflexiveIdentityRelation.types new file mode 100644 index 0000000000000..691a7d8d3dafc --- /dev/null +++ b/tests/baselines/reference/reflexiveIdentityRelation.types @@ -0,0 +1,67 @@ +//// [tests/cases/compiler/reflexiveIdentityRelation.ts] //// + +=== reflexiveIdentityRelation.ts === +namespace reflexiveIdentityRelation { + type Equals = (() => T extends B ? 1 : 0) extends (() => T extends A ? 1 : 0) ? true : false; +>Equals : Equals +> : ^^^^^^^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + + type Intersection = Equals<{a: 1} & {a: 1}, {a: 1}>; // true +>Intersection : true +> : ^^^^ +>a : 1 +> : ^ +>a : 1 +> : ^ +>a : 1 +> : ^ + + type Union = Equals<{a: 1} | {a: 1}, {a: 1}>; // true +>Union : true +> : ^^^^ +>a : 1 +> : ^ +>a : 1 +> : ^ +>a : 1 +> : ^ + + type UnionOfIntersection = Equals<{a: 1} & {b: 2} | {a: 1} & {b: 2}, {a: 1} & {b: 2}>; // true +>UnionOfIntersection : true +> : ^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ + + // The intersection distributes to `{a: 1} & {a: 1} | {a: 1} & {b: 2} | {b: 2} & {a: 1} | {b: 2} & {b: 2}` + // which is not identical to `{a: 1} | {b: 2}` + type IntersectionOfUnion = Equals<({a: 1} | {b: 2}) & ({a: 1} | {b: 2}), {a: 1} | {b: 2}>; // false +>IntersectionOfUnion : false +> : ^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ +} + diff --git a/tests/cases/compiler/reflexiveIdentityRelation.ts b/tests/cases/compiler/reflexiveIdentityRelation.ts new file mode 100644 index 0000000000000..6ab165aaf196c --- /dev/null +++ b/tests/cases/compiler/reflexiveIdentityRelation.ts @@ -0,0 +1,14 @@ +// @strict: true +// @declaration: true + +namespace reflexiveIdentityRelation { + type Equals = (() => T extends B ? 1 : 0) extends (() => T extends A ? 1 : 0) ? true : false; + + type Intersection = Equals<{a: 1} & {a: 1}, {a: 1}>; // true + type Union = Equals<{a: 1} | {a: 1}, {a: 1}>; // true + type UnionOfIntersection = Equals<{a: 1} & {b: 2} | {a: 1} & {b: 2}, {a: 1} & {b: 2}>; // true + + // The intersection distributes to `{a: 1} & {a: 1} | {a: 1} & {b: 2} | {b: 2} & {a: 1} | {b: 2} & {b: 2}` + // which is not identical to `{a: 1} | {b: 2}` + type IntersectionOfUnion = Equals<({a: 1} | {b: 2}) & ({a: 1} | {b: 2}), {a: 1} | {b: 2}>; // false +}