From 7c9d5fa9aed224d38d78f1a0f32a65812976769e Mon Sep 17 00:00:00 2001 From: MichaelMitchell-at <=> Date: Thu, 14 Nov 2024 22:04:53 +0800 Subject: [PATCH] Merge trivially mergeable intersection types for identity comparison --- src/compiler/checker.ts | 51 ++- .../identityRelationIntersectionTypes.js | 70 ++++ .../identityRelationIntersectionTypes.symbols | 285 ++++++++++++++ .../identityRelationIntersectionTypes.types | 371 ++++++++++++++++++ .../identityRelationIntersectionTypes.ts | 57 +++ 5 files changed, 830 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/identityRelationIntersectionTypes.js create mode 100644 tests/baselines/reference/identityRelationIntersectionTypes.symbols create mode 100644 tests/baselines/reference/identityRelationIntersectionTypes.types create mode 100644 tests/cases/compiler/identityRelationIntersectionTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 690b17c376445..4f06541f18801 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14485,17 +14485,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getAugmentedPropertiesOfType(unionType); } + const props = getMembersOfUnionOrIntersection(unionType as UnionType); + return arrayFrom(props.values()); + } + + function getMembersOfUnionOrIntersection(type: UnionOrIntersectionType): SymbolTable { const props = createSymbolTable(); - for (const memberType of types) { + for (const memberType of type.types) { for (const { escapedName } of getAugmentedPropertiesOfType(memberType)) { if (!props.has(escapedName)) { - const prop = createUnionOrIntersectionProperty(unionType as UnionType, escapedName); + const prop = createUnionOrIntersectionProperty(type, escapedName); // May be undefined if the property is private if (prop) props.set(escapedName, prop); } } } - return arrayFrom(props.values()); + return props; } function getConstraintOfType(type: InstantiableType | UnionOrIntersectionType): Type | undefined { @@ -21730,6 +21735,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { hasSubstitution ||= isNarrowingSubstitutionType(t); // This avoids displaying error messages with types like `T & T` when narrowing a return type if (hasInstantiable && hasNullableOrEmpty || hasSubstitution) return true; } + + return false; + } + + function isTypeMergeableIntersectionConstituent(type: Type) { + if ( + type.flags === TypeFlags.Object && + !!((type as ObjectType).objectFlags & ObjectFlags.Anonymous) && + !((type as ObjectType).objectFlags & ObjectFlags.Instantiated) + ) { + if ((type as ObjectType).objectFlags & ObjectFlags.ReverseMapped) { + return isTypeMergeableIntersectionConstituent((type as ReverseMappedType).source); + } + + return !typeHasCallOrConstructSignatures(type) && getIndexInfosOfType(type).length === 0; + } return false; } @@ -22151,12 +22172,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // turn deferred type references into regular type references, simplify indexed access and // conditional types, and resolve substitution types to either the substitution (on the source // side) or the type variable (on the target side). - const source = getNormalizedType(originalSource, /*writing*/ false); + let source = getNormalizedType(originalSource, /*writing*/ false); let target = getNormalizedType(originalTarget, /*writing*/ true); if (source === target) return Ternary.True; if (relation === identityRelation) { + if (source.flags & TypeFlags.Intersection) { + source = mergeIntersectionTypeIfPossible(source as IntersectionType, /*writing*/ false); + } + if (target.flags & TypeFlags.Intersection) { + target = mergeIntersectionTypeIfPossible(target as IntersectionType, /*writing*/ true); + } + if (source.flags !== target.flags) return Ternary.False; if (source.flags & TypeFlags.Singleton) return Ternary.True; traceUnionsOrIntersectionsTooLarge(source, target); @@ -22244,6 +22272,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return Ternary.False; } + function mergeIntersectionTypeIfPossible(type: IntersectionType, writing: boolean) { + if (every(type.types, isTypeMergeableIntersectionConstituent)) { + const reduced = getReducedType(type); + if (reduced.flags & TypeFlags.Intersection) { + type = reduced as IntersectionType; + const members = getMembersOfUnionOrIntersection(type); + const intersection = createAnonymousType(/*symbol*/ undefined, members, emptyArray, emptyArray, emptyArray); + intersection.aliasSymbol = type.aliasSymbol; + intersection.aliasTypeArguments = type.aliasTypeArguments; + return getNormalizedType(intersection, writing); + } + } + return type; + } + function reportErrorResults(originalSource: Type, originalTarget: Type, source: Type, target: Type, headMessage: DiagnosticMessage | undefined) { const sourceHasBase = !!getSingleBaseForNonAugmentingSubtype(originalSource); const targetHasBase = !!getSingleBaseForNonAugmentingSubtype(originalTarget); diff --git a/tests/baselines/reference/identityRelationIntersectionTypes.js b/tests/baselines/reference/identityRelationIntersectionTypes.js new file mode 100644 index 0000000000000..9aa3b8f8c6ad1 --- /dev/null +++ b/tests/baselines/reference/identityRelationIntersectionTypes.js @@ -0,0 +1,70 @@ +//// [tests/cases/compiler/identityRelationIntersectionTypes.ts] //// + +//// [identityRelationIntersectionTypes.ts] +namespace identityRelationIntersectionTypes { + type Equals = (() => T extends B ? 1 : 0) extends (() => T extends A ? 1 : 0) ? true : false; + + type GoodIntersection = Equals<{a: 1} & {b: 2}, {a: 1; b: 2}>; // true + + // Interfaces aren't mergeable + interface I {i: 3}; + type BadIntersection1 = Equals<{a: 1} & I, {a: 1; i: 3}>; // false + + // Objects with call or constructor signatures aren't mergeable + type BadIntersection2 = Equals<{a: 1} & {b: 2; (): void}, {a: 1; b: 2; (): void}>; // false + type BadIntersection3 = Equals<{a: 1} & {b: 2; new (): void}, {a: 1; b: 2; new (): void}>; // false + + // Objects with index signatures aren't mergeable + type BadIntersection4 = Equals<{a: 1} & {b: 2; [key: string]: number}, {a: 1; b: 2; [key: string]: number}>; // false + + // Shouldn't merge intersection if any constituents aren't mergeable + type StillBadIntersection1 = Equals<{a: 1} & {b: 2} & I, {a: 1; b: 2; i: 3}>; // false + type StillBadIntersection2 = Equals<{a: 1} & {b: 2} & I, {a: 1; b: 2} & I>; // false + + // Parentheses don't matter because intersections are flattened + type StillBadIntersection3 = Equals<({a: 1} & {b: 2}) & I, {a: 1; b: 2; i: 3}>; // false + type StillBadIntersection4 = Equals<({a: 1} & {b: 2}) & I, {a: 1; b: 2} & I>; // false + + // Type aliases also don't prevent flattening + type AB = {a: 1} & {b: 2}; + type StillBadIntersection5 = Equals; // false + type StillBadIntersection6 = Equals; // false + + type GoodDeepIntersection1 = Equals<{a: 0 | 1} & {a: 1 | 2}, {a: 1}>; // true + type GoodDeepIntersection2 = Equals<{a: {x: 1}} & {a: {y: 2}}, {a: {x: 1; y: 2}}>; // true + + type GoodShallowBadDeepIntersection1 = Equals<{a: {x: 1}} & {a: {y: 2} & I}, {a: {x: 1; y: 2} & I}>; // false + type GoodShallowBadDeepIntersection2 = Equals<{a: {x: 1}} & {a: {y: 2} & I}, {a: {x: 1} & {y: 2} & I}>; // true + + // Reduction applies to nested intersections + type DeepReduction = Equals<{a: {x: 1}} & {a: {x: 2}}, {a: never}>; // true + + // Intersections are distributed and merged if possible with union constituents + type Distributed = Equals< + {a: 1} & {b: 2} & ({c: 3} | {d: 4} | I), + {a: 1; b: 2; c: 3} | {a: 1; b: 2; d: 4} | {a: 1} & {b: 2} & I + >; // true + + // Should work with recursive types + type R1 = {a: R1; x: 1}; + type R2 = {a: R2; y: 1}; + type R = R1 & R2; + + type Recursive1 = Equals; // true + type Recursive2 = Equals; // true + type Recursive3 = Equals; // true + type Recursive4 = Equals; // false +} + + +//// [identityRelationIntersectionTypes.js] +"use strict"; +var identityRelationIntersectionTypes; +(function (identityRelationIntersectionTypes) { + ; +})(identityRelationIntersectionTypes || (identityRelationIntersectionTypes = {})); + + +//// [identityRelationIntersectionTypes.d.ts] +declare namespace identityRelationIntersectionTypes { +} diff --git a/tests/baselines/reference/identityRelationIntersectionTypes.symbols b/tests/baselines/reference/identityRelationIntersectionTypes.symbols new file mode 100644 index 0000000000000..363891b26c6ef --- /dev/null +++ b/tests/baselines/reference/identityRelationIntersectionTypes.symbols @@ -0,0 +1,285 @@ +//// [tests/cases/compiler/identityRelationIntersectionTypes.ts] //// + +=== identityRelationIntersectionTypes.ts === +namespace identityRelationIntersectionTypes { +>identityRelationIntersectionTypes : Symbol(identityRelationIntersectionTypes, Decl(identityRelationIntersectionTypes.ts, 0, 0)) + + type Equals = (() => T extends B ? 1 : 0) extends (() => T extends A ? 1 : 0) ? true : false; +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>A : Symbol(A, Decl(identityRelationIntersectionTypes.ts, 1, 16)) +>B : Symbol(B, Decl(identityRelationIntersectionTypes.ts, 1, 18)) +>T : Symbol(T, Decl(identityRelationIntersectionTypes.ts, 1, 26)) +>T : Symbol(T, Decl(identityRelationIntersectionTypes.ts, 1, 26)) +>B : Symbol(B, Decl(identityRelationIntersectionTypes.ts, 1, 18)) +>T : Symbol(T, Decl(identityRelationIntersectionTypes.ts, 1, 65)) +>T : Symbol(T, Decl(identityRelationIntersectionTypes.ts, 1, 65)) +>A : Symbol(A, Decl(identityRelationIntersectionTypes.ts, 1, 16)) + + type GoodIntersection = Equals<{a: 1} & {b: 2}, {a: 1; b: 2}>; // true +>GoodIntersection : Symbol(GoodIntersection, Decl(identityRelationIntersectionTypes.ts, 1, 109)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 3, 36)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 3, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 3, 53)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 3, 58)) + + // Interfaces aren't mergeable + interface I {i: 3}; +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) +>i : Symbol(I.i, Decl(identityRelationIntersectionTypes.ts, 6, 17)) + + type BadIntersection1 = Equals<{a: 1} & I, {a: 1; i: 3}>; // false +>BadIntersection1 : Symbol(BadIntersection1, Decl(identityRelationIntersectionTypes.ts, 6, 23)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 7, 36)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 7, 48)) +>i : Symbol(i, Decl(identityRelationIntersectionTypes.ts, 7, 53)) + + // Objects with call or constructor signatures aren't mergeable + type BadIntersection2 = Equals<{a: 1} & {b: 2; (): void}, {a: 1; b: 2; (): void}>; // false +>BadIntersection2 : Symbol(BadIntersection2, Decl(identityRelationIntersectionTypes.ts, 7, 61)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 10, 36)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 10, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 10, 63)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 10, 68)) + + type BadIntersection3 = Equals<{a: 1} & {b: 2; new (): void}, {a: 1; b: 2; new (): void}>; // false +>BadIntersection3 : Symbol(BadIntersection3, Decl(identityRelationIntersectionTypes.ts, 10, 86)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 11, 36)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 11, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 11, 67)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 11, 72)) + + // Objects with index signatures aren't mergeable + type BadIntersection4 = Equals<{a: 1} & {b: 2; [key: string]: number}, {a: 1; b: 2; [key: string]: number}>; // false +>BadIntersection4 : Symbol(BadIntersection4, Decl(identityRelationIntersectionTypes.ts, 11, 94)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 14, 36)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 14, 45)) +>key : Symbol(key, Decl(identityRelationIntersectionTypes.ts, 14, 52)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 14, 76)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 14, 81)) +>key : Symbol(key, Decl(identityRelationIntersectionTypes.ts, 14, 89)) + + // Shouldn't merge intersection if any constituents aren't mergeable + type StillBadIntersection1 = Equals<{a: 1} & {b: 2} & I, {a: 1; b: 2; i: 3}>; // false +>StillBadIntersection1 : Symbol(StillBadIntersection1, Decl(identityRelationIntersectionTypes.ts, 14, 112)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 17, 41)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 17, 50)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 17, 62)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 17, 67)) +>i : Symbol(i, Decl(identityRelationIntersectionTypes.ts, 17, 73)) + + type StillBadIntersection2 = Equals<{a: 1} & {b: 2} & I, {a: 1; b: 2} & I>; // false +>StillBadIntersection2 : Symbol(StillBadIntersection2, Decl(identityRelationIntersectionTypes.ts, 17, 81)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 18, 41)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 18, 50)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 18, 62)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 18, 67)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) + + // Parentheses don't matter because intersections are flattened + type StillBadIntersection3 = Equals<({a: 1} & {b: 2}) & I, {a: 1; b: 2; i: 3}>; // false +>StillBadIntersection3 : Symbol(StillBadIntersection3, Decl(identityRelationIntersectionTypes.ts, 18, 79)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 21, 42)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 21, 51)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 21, 64)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 21, 69)) +>i : Symbol(i, Decl(identityRelationIntersectionTypes.ts, 21, 75)) + + type StillBadIntersection4 = Equals<({a: 1} & {b: 2}) & I, {a: 1; b: 2} & I>; // false +>StillBadIntersection4 : Symbol(StillBadIntersection4, Decl(identityRelationIntersectionTypes.ts, 21, 83)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 22, 42)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 22, 51)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 22, 64)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 22, 69)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) + + // Type aliases also don't prevent flattening + type AB = {a: 1} & {b: 2}; +>AB : Symbol(AB, Decl(identityRelationIntersectionTypes.ts, 22, 81)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 25, 15)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 25, 24)) + + type StillBadIntersection5 = Equals; // false +>StillBadIntersection5 : Symbol(StillBadIntersection5, Decl(identityRelationIntersectionTypes.ts, 25, 30)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>AB : Symbol(AB, Decl(identityRelationIntersectionTypes.ts, 22, 81)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 26, 49)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 26, 54)) +>i : Symbol(i, Decl(identityRelationIntersectionTypes.ts, 26, 60)) + + type StillBadIntersection6 = Equals; // false +>StillBadIntersection6 : Symbol(StillBadIntersection6, Decl(identityRelationIntersectionTypes.ts, 26, 68)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>AB : Symbol(AB, Decl(identityRelationIntersectionTypes.ts, 22, 81)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 27, 49)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 27, 54)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) + + type GoodDeepIntersection1 = Equals<{a: 0 | 1} & {a: 1 | 2}, {a: 1}>; // true +>GoodDeepIntersection1 : Symbol(GoodDeepIntersection1, Decl(identityRelationIntersectionTypes.ts, 27, 66)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 29, 41)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 29, 54)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 29, 66)) + + type GoodDeepIntersection2 = Equals<{a: {x: 1}} & {a: {y: 2}}, {a: {x: 1; y: 2}}>; // true +>GoodDeepIntersection2 : Symbol(GoodDeepIntersection2, Decl(identityRelationIntersectionTypes.ts, 29, 73)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 30, 41)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 30, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 30, 55)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 30, 59)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 30, 68)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 30, 72)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 30, 77)) + + type GoodShallowBadDeepIntersection1 = Equals<{a: {x: 1}} & {a: {y: 2} & I}, {a: {x: 1; y: 2} & I}>; // false +>GoodShallowBadDeepIntersection1 : Symbol(GoodShallowBadDeepIntersection1, Decl(identityRelationIntersectionTypes.ts, 30, 86)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 32, 51)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 32, 55)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 32, 65)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 32, 69)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 32, 82)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 32, 86)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 32, 91)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) + + type GoodShallowBadDeepIntersection2 = Equals<{a: {x: 1}} & {a: {y: 2} & I}, {a: {x: 1} & {y: 2} & I}>; // true +>GoodShallowBadDeepIntersection2 : Symbol(GoodShallowBadDeepIntersection2, Decl(identityRelationIntersectionTypes.ts, 32, 104)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 33, 51)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 33, 55)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 33, 65)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 33, 69)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 33, 82)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 33, 86)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 33, 95)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) + + // Reduction applies to nested intersections + type DeepReduction = Equals<{a: {x: 1}} & {a: {x: 2}}, {a: never}>; // true +>DeepReduction : Symbol(DeepReduction, Decl(identityRelationIntersectionTypes.ts, 33, 107)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 36, 33)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 36, 37)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 36, 47)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 36, 51)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 36, 60)) + + // Intersections are distributed and merged if possible with union constituents + type Distributed = Equals< +>Distributed : Symbol(Distributed, Decl(identityRelationIntersectionTypes.ts, 36, 71)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) + + {a: 1} & {b: 2} & ({c: 3} | {d: 4} | I), +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 40, 9)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 40, 18)) +>c : Symbol(c, Decl(identityRelationIntersectionTypes.ts, 40, 28)) +>d : Symbol(d, Decl(identityRelationIntersectionTypes.ts, 40, 37)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) + + {a: 1; b: 2; c: 3} | {a: 1; b: 2; d: 4} | {a: 1} & {b: 2} & I +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 41, 9)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 41, 14)) +>c : Symbol(c, Decl(identityRelationIntersectionTypes.ts, 41, 20)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 41, 30)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 41, 35)) +>d : Symbol(d, Decl(identityRelationIntersectionTypes.ts, 41, 41)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 41, 51)) +>b : Symbol(b, Decl(identityRelationIntersectionTypes.ts, 41, 60)) +>I : Symbol(I, Decl(identityRelationIntersectionTypes.ts, 3, 66)) + + >; // true + + // Should work with recursive types + type R1 = {a: R1; x: 1}; +>R1 : Symbol(R1, Decl(identityRelationIntersectionTypes.ts, 42, 6)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 45, 15)) +>R1 : Symbol(R1, Decl(identityRelationIntersectionTypes.ts, 42, 6)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 45, 21)) + + type R2 = {a: R2; y: 1}; +>R2 : Symbol(R2, Decl(identityRelationIntersectionTypes.ts, 45, 28)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 46, 15)) +>R2 : Symbol(R2, Decl(identityRelationIntersectionTypes.ts, 45, 28)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 46, 21)) + + type R = R1 & R2; +>R : Symbol(R, Decl(identityRelationIntersectionTypes.ts, 46, 28)) +>R1 : Symbol(R1, Decl(identityRelationIntersectionTypes.ts, 42, 6)) +>R2 : Symbol(R2, Decl(identityRelationIntersectionTypes.ts, 45, 28)) + + type Recursive1 = Equals; // true +>Recursive1 : Symbol(Recursive1, Decl(identityRelationIntersectionTypes.ts, 47, 21)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>R : Symbol(R, Decl(identityRelationIntersectionTypes.ts, 46, 28)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 49, 33)) +>R1 : Symbol(R1, Decl(identityRelationIntersectionTypes.ts, 42, 6)) +>R2 : Symbol(R2, Decl(identityRelationIntersectionTypes.ts, 45, 28)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 49, 44)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 49, 50)) + + type Recursive2 = Equals; // true +>Recursive2 : Symbol(Recursive2, Decl(identityRelationIntersectionTypes.ts, 49, 58)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>R : Symbol(R, Decl(identityRelationIntersectionTypes.ts, 46, 28)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 50, 33)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 50, 37)) +>R1 : Symbol(R1, Decl(identityRelationIntersectionTypes.ts, 42, 6)) +>R2 : Symbol(R2, Decl(identityRelationIntersectionTypes.ts, 45, 28)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 50, 48)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 50, 54)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 50, 61)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 50, 67)) + + type Recursive3 = Equals; // true +>Recursive3 : Symbol(Recursive3, Decl(identityRelationIntersectionTypes.ts, 50, 75)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>R : Symbol(R, Decl(identityRelationIntersectionTypes.ts, 46, 28)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 51, 33)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 51, 37)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 51, 41)) +>R1 : Symbol(R1, Decl(identityRelationIntersectionTypes.ts, 42, 6)) +>R2 : Symbol(R2, Decl(identityRelationIntersectionTypes.ts, 45, 28)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 51, 52)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 51, 58)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 51, 65)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 51, 71)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 51, 78)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 51, 84)) + + type Recursive4 = Equals; // false +>Recursive4 : Symbol(Recursive4, Decl(identityRelationIntersectionTypes.ts, 51, 92)) +>Equals : Symbol(Equals, Decl(identityRelationIntersectionTypes.ts, 0, 45)) +>R : Symbol(R, Decl(identityRelationIntersectionTypes.ts, 46, 28)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 52, 33)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 52, 37)) +>a : Symbol(a, Decl(identityRelationIntersectionTypes.ts, 52, 41)) +>R1 : Symbol(R1, Decl(identityRelationIntersectionTypes.ts, 42, 6)) +>R2 : Symbol(R2, Decl(identityRelationIntersectionTypes.ts, 45, 28)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 52, 52)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 52, 58)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 52, 65)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 52, 71)) +>x : Symbol(x, Decl(identityRelationIntersectionTypes.ts, 52, 78)) +>y : Symbol(y, Decl(identityRelationIntersectionTypes.ts, 52, 84)) +} + diff --git a/tests/baselines/reference/identityRelationIntersectionTypes.types b/tests/baselines/reference/identityRelationIntersectionTypes.types new file mode 100644 index 0000000000000..d7f702ff86d3f --- /dev/null +++ b/tests/baselines/reference/identityRelationIntersectionTypes.types @@ -0,0 +1,371 @@ +//// [tests/cases/compiler/identityRelationIntersectionTypes.ts] //// + +=== identityRelationIntersectionTypes.ts === +namespace identityRelationIntersectionTypes { +>identityRelationIntersectionTypes : typeof identityRelationIntersectionTypes +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + type Equals = (() => T extends B ? 1 : 0) extends (() => T extends A ? 1 : 0) ? true : false; +>Equals : Equals +> : ^^^^^^^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + + type GoodIntersection = Equals<{a: 1} & {b: 2}, {a: 1; b: 2}>; // true +>GoodIntersection : true +> : ^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ + + // Interfaces aren't mergeable + interface I {i: 3}; +>i : 3 +> : ^ + + type BadIntersection1 = Equals<{a: 1} & I, {a: 1; i: 3}>; // false +>BadIntersection1 : false +> : ^^^^^ +>a : 1 +> : ^ +>a : 1 +> : ^ +>i : 3 +> : ^ + + // Objects with call or constructor signatures aren't mergeable + type BadIntersection2 = Equals<{a: 1} & {b: 2; (): void}, {a: 1; b: 2; (): void}>; // false +>BadIntersection2 : false +> : ^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ + + type BadIntersection3 = Equals<{a: 1} & {b: 2; new (): void}, {a: 1; b: 2; new (): void}>; // false +>BadIntersection3 : false +> : ^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ + + // Objects with index signatures aren't mergeable + type BadIntersection4 = Equals<{a: 1} & {b: 2; [key: string]: number}, {a: 1; b: 2; [key: string]: number}>; // false +>BadIntersection4 : false +> : ^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>key : string +> : ^^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>key : string +> : ^^^^^^ + + // Shouldn't merge intersection if any constituents aren't mergeable + type StillBadIntersection1 = Equals<{a: 1} & {b: 2} & I, {a: 1; b: 2; i: 3}>; // false +>StillBadIntersection1 : false +> : ^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>i : 3 +> : ^ + + type StillBadIntersection2 = Equals<{a: 1} & {b: 2} & I, {a: 1; b: 2} & I>; // false +>StillBadIntersection2 : false +> : ^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ + + // Parentheses don't matter because intersections are flattened + type StillBadIntersection3 = Equals<({a: 1} & {b: 2}) & I, {a: 1; b: 2; i: 3}>; // false +>StillBadIntersection3 : false +> : ^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>i : 3 +> : ^ + + type StillBadIntersection4 = Equals<({a: 1} & {b: 2}) & I, {a: 1; b: 2} & I>; // false +>StillBadIntersection4 : false +> : ^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ + + // Type aliases also don't prevent flattening + type AB = {a: 1} & {b: 2}; +>AB : AB +> : ^^ +>a : 1 +> : ^ +>b : 2 +> : ^ + + type StillBadIntersection5 = Equals; // false +>StillBadIntersection5 : false +> : ^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>i : 3 +> : ^ + + type StillBadIntersection6 = Equals; // false +>StillBadIntersection6 : false +> : ^^^^^ +>a : 1 +> : ^ +>b : 2 +> : ^ + + type GoodDeepIntersection1 = Equals<{a: 0 | 1} & {a: 1 | 2}, {a: 1}>; // true +>GoodDeepIntersection1 : true +> : ^^^^ +>a : 0 | 1 +> : ^^^^^ +>a : 1 | 2 +> : ^^^^^ +>a : 1 +> : ^ + + type GoodDeepIntersection2 = Equals<{a: {x: 1}} & {a: {y: 2}}, {a: {x: 1; y: 2}}>; // true +>GoodDeepIntersection2 : true +> : ^^^^ +>a : { x: 1; } +> : ^^^^^ ^^^ +>x : 1 +> : ^ +>a : { y: 2; } +> : ^^^^^ ^^^ +>y : 2 +> : ^ +>a : { x: 1; y: 2; } +> : ^^^^^ ^^^^^ ^^^ +>x : 1 +> : ^ +>y : 2 +> : ^ + + type GoodShallowBadDeepIntersection1 = Equals<{a: {x: 1}} & {a: {y: 2} & I}, {a: {x: 1; y: 2} & I}>; // false +>GoodShallowBadDeepIntersection1 : false +> : ^^^^^ +>a : { x: 1; } +> : ^^^^^ ^^^ +>x : 1 +> : ^ +>a : { y: 2; } & I +> : ^^^^^ ^^^^^^^ +>y : 2 +> : ^ +>a : { x: 1; y: 2; } & I +> : ^^^^^ ^^^^^ ^^^^^^^ +>x : 1 +> : ^ +>y : 2 +> : ^ + + type GoodShallowBadDeepIntersection2 = Equals<{a: {x: 1}} & {a: {y: 2} & I}, {a: {x: 1} & {y: 2} & I}>; // true +>GoodShallowBadDeepIntersection2 : true +> : ^^^^ +>a : { x: 1; } +> : ^^^^^ ^^^ +>x : 1 +> : ^ +>a : { y: 2; } & I +> : ^^^^^ ^^^^^^^ +>y : 2 +> : ^ +>a : { x: 1; } & { y: 2; } & I +> : ^^^^^ ^^^^^^^^^^^ ^^^^^^^ +>x : 1 +> : ^ +>y : 2 +> : ^ + + // Reduction applies to nested intersections + type DeepReduction = Equals<{a: {x: 1}} & {a: {x: 2}}, {a: never}>; // true +>DeepReduction : true +> : ^^^^ +>a : { x: 1; } +> : ^^^^^ ^^^ +>x : 1 +> : ^ +>a : { x: 2; } +> : ^^^^^ ^^^ +>x : 2 +> : ^ +>a : never +> : ^^^^^ + + // Intersections are distributed and merged if possible with union constituents + type Distributed = Equals< +>Distributed : true +> : ^^^^ + + {a: 1} & {b: 2} & ({c: 3} | {d: 4} | I), +>a : 1 +> : ^ +>b : 2 +> : ^ +>c : 3 +> : ^ +>d : 4 +> : ^ + + {a: 1; b: 2; c: 3} | {a: 1; b: 2; d: 4} | {a: 1} & {b: 2} & I +>a : 1 +> : ^ +>b : 2 +> : ^ +>c : 3 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ +>d : 4 +> : ^ +>a : 1 +> : ^ +>b : 2 +> : ^ + + >; // true + + // Should work with recursive types + type R1 = {a: R1; x: 1}; +>R1 : R1 +> : ^^ +>a : R1 +> : ^^ +>x : 1 +> : ^ + + type R2 = {a: R2; y: 1}; +>R2 : R2 +> : ^^ +>a : R2 +> : ^^ +>y : 1 +> : ^ + + type R = R1 & R2; +>R : R +> : ^ + + type Recursive1 = Equals; // true +>Recursive1 : true +> : ^^^^ +>a : R1 & R2 +> : ^^^^^^^ +>x : 1 +> : ^ +>y : 1 +> : ^ + + type Recursive2 = Equals; // true +>Recursive2 : true +> : ^^^^ +>a : { a: R1 & R2; x: 1; y: 1; } +> : ^^^^^ ^^^^^ ^^^^^ ^^^ +>a : R1 & R2 +> : ^^^^^^^ +>x : 1 +> : ^ +>y : 1 +> : ^ +>x : 1 +> : ^ +>y : 1 +> : ^ + + type Recursive3 = Equals; // true +>Recursive3 : true +> : ^^^^ +>a : { a: { a: R1 & R2; x: 1; y: 1; }; x: 1; y: 1; } +> : ^^^^^ ^^^^^ ^^^^^ ^^^ +>a : { a: R1 & R2; x: 1; y: 1; } +> : ^^^^^ ^^^^^ ^^^^^ ^^^ +>a : R1 & R2 +> : ^^^^^^^ +>x : 1 +> : ^ +>y : 1 +> : ^ +>x : 1 +> : ^ +>y : 1 +> : ^ +>x : 1 +> : ^ +>y : 1 +> : ^ + + type Recursive4 = Equals; // false +>Recursive4 : false +> : ^^^^^ +>a : { a: { a: R1 & R2; x: 1; y: 0; }; x: 1; y: 1; } +> : ^^^^^ ^^^^^ ^^^^^ ^^^ +>a : { a: R1 & R2; x: 1; y: 0; } +> : ^^^^^ ^^^^^ ^^^^^ ^^^ +>a : R1 & R2 +> : ^^^^^^^ +>x : 1 +> : ^ +>y : 0 +> : ^ +>x : 1 +> : ^ +>y : 1 +> : ^ +>x : 1 +> : ^ +>y : 1 +> : ^ +} + diff --git a/tests/cases/compiler/identityRelationIntersectionTypes.ts b/tests/cases/compiler/identityRelationIntersectionTypes.ts new file mode 100644 index 0000000000000..a63ae68e33baa --- /dev/null +++ b/tests/cases/compiler/identityRelationIntersectionTypes.ts @@ -0,0 +1,57 @@ +// @strict: true +// @declaration: true + +namespace identityRelationIntersectionTypes { + type Equals = (() => T extends B ? 1 : 0) extends (() => T extends A ? 1 : 0) ? true : false; + + type GoodIntersection = Equals<{a: 1} & {b: 2}, {a: 1; b: 2}>; // true + + // Interfaces aren't mergeable + interface I {i: 3}; + type BadIntersection1 = Equals<{a: 1} & I, {a: 1; i: 3}>; // false + + // Objects with call or constructor signatures aren't mergeable + type BadIntersection2 = Equals<{a: 1} & {b: 2; (): void}, {a: 1; b: 2; (): void}>; // false + type BadIntersection3 = Equals<{a: 1} & {b: 2; new (): void}, {a: 1; b: 2; new (): void}>; // false + + // Objects with index signatures aren't mergeable + type BadIntersection4 = Equals<{a: 1} & {b: 2; [key: string]: number}, {a: 1; b: 2; [key: string]: number}>; // false + + // Shouldn't merge intersection if any constituents aren't mergeable + type StillBadIntersection1 = Equals<{a: 1} & {b: 2} & I, {a: 1; b: 2; i: 3}>; // false + type StillBadIntersection2 = Equals<{a: 1} & {b: 2} & I, {a: 1; b: 2} & I>; // false + + // Parentheses don't matter because intersections are flattened + type StillBadIntersection3 = Equals<({a: 1} & {b: 2}) & I, {a: 1; b: 2; i: 3}>; // false + type StillBadIntersection4 = Equals<({a: 1} & {b: 2}) & I, {a: 1; b: 2} & I>; // false + + // Type aliases also don't prevent flattening + type AB = {a: 1} & {b: 2}; + type StillBadIntersection5 = Equals; // false + type StillBadIntersection6 = Equals; // false + + type GoodDeepIntersection1 = Equals<{a: 0 | 1} & {a: 1 | 2}, {a: 1}>; // true + type GoodDeepIntersection2 = Equals<{a: {x: 1}} & {a: {y: 2}}, {a: {x: 1; y: 2}}>; // true + + type GoodShallowBadDeepIntersection1 = Equals<{a: {x: 1}} & {a: {y: 2} & I}, {a: {x: 1; y: 2} & I}>; // false + type GoodShallowBadDeepIntersection2 = Equals<{a: {x: 1}} & {a: {y: 2} & I}, {a: {x: 1} & {y: 2} & I}>; // true + + // Reduction applies to nested intersections + type DeepReduction = Equals<{a: {x: 1}} & {a: {x: 2}}, {a: never}>; // true + + // Intersections are distributed and merged if possible with union constituents + type Distributed = Equals< + {a: 1} & {b: 2} & ({c: 3} | {d: 4} | I), + {a: 1; b: 2; c: 3} | {a: 1; b: 2; d: 4} | {a: 1} & {b: 2} & I + >; // true + + // Should work with recursive types + type R1 = {a: R1; x: 1}; + type R2 = {a: R2; y: 1}; + type R = R1 & R2; + + type Recursive1 = Equals; // true + type Recursive2 = Equals; // true + type Recursive3 = Equals; // true + type Recursive4 = Equals; // false +}