Skip to content

Commit

Permalink
Make identity comparison reflexive across unions or intersections of …
Browse files Browse the repository at this point in the history
…the same type
  • Loading branch information
MichaelMitchell-at committed Dec 29, 2024
1 parent 56a0825 commit 7b7a9b2
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 1 deletion.
16 changes: 15 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
23 changes: 23 additions & 0 deletions tests/baselines/reference/reflexiveIdentityRelation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//// [tests/cases/compiler/reflexiveIdentityRelation.ts] ////

//// [reflexiveIdentityRelation.ts]
namespace reflexiveIdentityRelation {
type Equals<A, B> = (<T>() => T extends B ? 1 : 0) extends (<T>() => 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 {
}
54 changes: 54 additions & 0 deletions tests/baselines/reference/reflexiveIdentityRelation.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//// [tests/cases/compiler/reflexiveIdentityRelation.ts] ////

=== reflexiveIdentityRelation.ts ===
namespace reflexiveIdentityRelation {
>reflexiveIdentityRelation : Symbol(reflexiveIdentityRelation, Decl(reflexiveIdentityRelation.ts, 0, 0))

type Equals<A, B> = (<T>() => T extends B ? 1 : 0) extends (<T>() => 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))
}

67 changes: 67 additions & 0 deletions tests/baselines/reference/reflexiveIdentityRelation.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//// [tests/cases/compiler/reflexiveIdentityRelation.ts] ////

=== reflexiveIdentityRelation.ts ===
namespace reflexiveIdentityRelation {
type Equals<A, B> = (<T>() => T extends B ? 1 : 0) extends (<T>() => T extends A ? 1 : 0) ? true : false;
>Equals : Equals<A, B>
> : ^^^^^^^^^^^^
>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
> : ^
}

14 changes: 14 additions & 0 deletions tests/cases/compiler/reflexiveIdentityRelation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @strict: true
// @declaration: true

namespace reflexiveIdentityRelation {
type Equals<A, B> = (<T>() => T extends B ? 1 : 0) extends (<T>() => 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
}

0 comments on commit 7b7a9b2

Please sign in to comment.