Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make identity comparison reflexive across unions or intersections of the same type #60868

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
Loading