From a654a23e46bf958b07920efaa2d3572fa374c8e8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 19 May 2020 21:22:27 +0000 Subject: [PATCH] Cherry-pick PR #38565 into release-3.9 Component commits: e03cb800d1 Perform intersection reduction before and after getApparentType 7af78d33a0 Add regression tests --- src/compiler/checker.ts | 20 +++-- .../intersectionReduction.errors.txt | 32 +++++++ .../reference/intersectionReduction.js | 41 +++++++++ .../reference/intersectionReduction.symbols | 84 +++++++++++++++++++ .../reference/intersectionReduction.types | 62 ++++++++++++++ .../intersection/intersectionReduction.ts | 32 +++++++ 6 files changed, 265 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e962997ee02e5..c786d329c1bdc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10144,7 +10144,7 @@ namespace ts { } function getPropertiesOfType(type: Type): Symbol[] { - type = getApparentType(getReducedType(type)); + type = getReducedApparentType(type); return type.flags & TypeFlags.UnionOrIntersection ? getPropertiesOfUnionOrIntersectionType(type) : getPropertiesOfObjectType(type); @@ -10500,6 +10500,14 @@ namespace ts { t; } + function getReducedApparentType(type: Type): Type { + // Since getApparentType may return a non-reduced union or intersection type, we need to perform + // type reduction both before and after obtaining the apparent type. For example, given a type parameter + // 'T extends A | B', the type 'T & X' becomes 'A & X | B & X' after obtaining the apparent type, and + // that type may need futher reduction to remove empty intersections. + return getReducedType(getApparentType(getReducedType(type))); + } + function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined { let singleProp: Symbol | undefined; let propSet: Map | undefined; @@ -10721,7 +10729,7 @@ namespace ts { * @param name a name of property to look up in a given type */ function getPropertyOfType(type: Type, name: __String): Symbol | undefined { - type = getApparentType(getReducedType(type)); + type = getReducedApparentType(type); if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type); const symbol = resolved.members.get(name); @@ -10759,7 +10767,7 @@ namespace ts { * maps primitive types and type parameters are to their apparent types. */ function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] { - return getSignaturesOfStructuredType(getApparentType(getReducedType(type)), kind); + return getSignaturesOfStructuredType(getReducedApparentType(type), kind); } function getIndexInfoOfStructuredType(type: Type, kind: IndexKind): IndexInfo | undefined { @@ -10777,13 +10785,13 @@ namespace ts { // Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and // maps primitive types and type parameters are to their apparent types. function getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined { - return getIndexInfoOfStructuredType(getApparentType(getReducedType(type)), kind); + return getIndexInfoOfStructuredType(getReducedApparentType(type), kind); } // Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and // maps primitive types and type parameters are to their apparent types. function getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined { - return getIndexTypeOfStructuredType(getApparentType(getReducedType(type)), kind); + return getIndexTypeOfStructuredType(getReducedApparentType(type), kind); } function getImplicitIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined { @@ -13154,7 +13162,7 @@ namespace ts { // In the following we resolve T[K] to the type of the property in T selected by K. // We treat boolean as different from other unions to improve errors; // skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'. - const apparentObjectType = getApparentType(getReducedType(objectType)); + const apparentObjectType = getReducedApparentType(objectType); if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) { const propTypes: Type[] = []; let wasMissingProp = false; diff --git a/tests/baselines/reference/intersectionReduction.errors.txt b/tests/baselines/reference/intersectionReduction.errors.txt index 7026fcda0a918..82911aca00935 100644 --- a/tests/baselines/reference/intersectionReduction.errors.txt +++ b/tests/baselines/reference/intersectionReduction.errors.txt @@ -120,4 +120,36 @@ tests/cases/conformance/types/intersection/intersectionReduction.ts(81,1): error const f2 = (t: Container<"a"> | (Container<"b"> & Container<"c">)): Container<"a"> => t; const f3 = (t: Container<"a"> | (Container<"b"> & { dataB: boolean } & Container<"a">)): Container<"a"> => t; const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)): number => t; + + // Repro from #38549 + + interface A2 { + kind: "A"; + a: number; + } + + interface B2 { + kind: "B"; + b: number; + } + + declare const shouldBeB: (A2 | B2) & B2; + const b: B2 = shouldBeB; // works + + function inGeneric(alsoShouldBeB: T & B2) { + const b: B2 = alsoShouldBeB; + } + + // Repro from #38542 + + interface ABI { + kind: 'a' | 'b'; + } + + declare class CA { kind: 'a'; a: string; x: number }; + declare class CB { kind: 'b'; b: string; y: number }; + + function bar(x: T & CA) { + let ab: ABI = x; + } \ No newline at end of file diff --git a/tests/baselines/reference/intersectionReduction.js b/tests/baselines/reference/intersectionReduction.js index c04ce9973bcbe..64b1757b09d4c 100644 --- a/tests/baselines/reference/intersectionReduction.js +++ b/tests/baselines/reference/intersectionReduction.js @@ -107,6 +107,38 @@ type Container = { const f2 = (t: Container<"a"> | (Container<"b"> & Container<"c">)): Container<"a"> => t; const f3 = (t: Container<"a"> | (Container<"b"> & { dataB: boolean } & Container<"a">)): Container<"a"> => t; const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)): number => t; + +// Repro from #38549 + +interface A2 { + kind: "A"; + a: number; +} + +interface B2 { + kind: "B"; + b: number; +} + +declare const shouldBeB: (A2 | B2) & B2; +const b: B2 = shouldBeB; // works + +function inGeneric(alsoShouldBeB: T & B2) { + const b: B2 = alsoShouldBeB; +} + +// Repro from #38542 + +interface ABI { + kind: 'a' | 'b'; +} + +declare class CA { kind: 'a'; a: string; x: number }; +declare class CB { kind: 'b'; b: string; y: number }; + +function bar(x: T & CA) { + let ab: ABI = x; +} //// [intersectionReduction.js] @@ -128,3 +160,12 @@ var f1 = function (t) { return t; }; var f2 = function (t) { return t; }; var f3 = function (t) { return t; }; var f4 = function (t) { return t; }; +var b = shouldBeB; // works +function inGeneric(alsoShouldBeB) { + var b = alsoShouldBeB; +} +; +; +function bar(x) { + var ab = x; +} diff --git a/tests/baselines/reference/intersectionReduction.symbols b/tests/baselines/reference/intersectionReduction.symbols index 2949e87b2c72a..02cc8c9ff2fc7 100644 --- a/tests/baselines/reference/intersectionReduction.symbols +++ b/tests/baselines/reference/intersectionReduction.symbols @@ -373,3 +373,87 @@ const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)): >Container : Symbol(Container, Decl(intersectionReduction.ts, 99, 44)) >t : Symbol(t, Decl(intersectionReduction.ts, 107, 12)) +// Repro from #38549 + +interface A2 { +>A2 : Symbol(A2, Decl(intersectionReduction.ts, 107, 93)) + + kind: "A"; +>kind : Symbol(A2.kind, Decl(intersectionReduction.ts, 111, 14)) + + a: number; +>a : Symbol(A2.a, Decl(intersectionReduction.ts, 112, 14)) +} + +interface B2 { +>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1)) + + kind: "B"; +>kind : Symbol(B2.kind, Decl(intersectionReduction.ts, 116, 14)) + + b: number; +>b : Symbol(B2.b, Decl(intersectionReduction.ts, 117, 14)) +} + +declare const shouldBeB: (A2 | B2) & B2; +>shouldBeB : Symbol(shouldBeB, Decl(intersectionReduction.ts, 121, 13)) +>A2 : Symbol(A2, Decl(intersectionReduction.ts, 107, 93)) +>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1)) +>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1)) + +const b: B2 = shouldBeB; // works +>b : Symbol(b, Decl(intersectionReduction.ts, 122, 5)) +>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1)) +>shouldBeB : Symbol(shouldBeB, Decl(intersectionReduction.ts, 121, 13)) + +function inGeneric(alsoShouldBeB: T & B2) { +>inGeneric : Symbol(inGeneric, Decl(intersectionReduction.ts, 122, 24)) +>T : Symbol(T, Decl(intersectionReduction.ts, 124, 19)) +>A2 : Symbol(A2, Decl(intersectionReduction.ts, 107, 93)) +>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1)) +>alsoShouldBeB : Symbol(alsoShouldBeB, Decl(intersectionReduction.ts, 124, 38)) +>T : Symbol(T, Decl(intersectionReduction.ts, 124, 19)) +>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1)) + + const b: B2 = alsoShouldBeB; +>b : Symbol(b, Decl(intersectionReduction.ts, 125, 9)) +>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1)) +>alsoShouldBeB : Symbol(alsoShouldBeB, Decl(intersectionReduction.ts, 124, 38)) +} + +// Repro from #38542 + +interface ABI { +>ABI : Symbol(ABI, Decl(intersectionReduction.ts, 126, 1)) + + kind: 'a' | 'b'; +>kind : Symbol(ABI.kind, Decl(intersectionReduction.ts, 130, 15)) +} + +declare class CA { kind: 'a'; a: string; x: number }; +>CA : Symbol(CA, Decl(intersectionReduction.ts, 132, 1)) +>kind : Symbol(CA.kind, Decl(intersectionReduction.ts, 134, 18)) +>a : Symbol(CA.a, Decl(intersectionReduction.ts, 134, 29)) +>x : Symbol(CA.x, Decl(intersectionReduction.ts, 134, 40)) + +declare class CB { kind: 'b'; b: string; y: number }; +>CB : Symbol(CB, Decl(intersectionReduction.ts, 134, 53)) +>kind : Symbol(CB.kind, Decl(intersectionReduction.ts, 135, 18)) +>b : Symbol(CB.b, Decl(intersectionReduction.ts, 135, 29)) +>y : Symbol(CB.y, Decl(intersectionReduction.ts, 135, 40)) + +function bar(x: T & CA) { +>bar : Symbol(bar, Decl(intersectionReduction.ts, 135, 53)) +>T : Symbol(T, Decl(intersectionReduction.ts, 137, 13)) +>CA : Symbol(CA, Decl(intersectionReduction.ts, 132, 1)) +>CB : Symbol(CB, Decl(intersectionReduction.ts, 134, 53)) +>x : Symbol(x, Decl(intersectionReduction.ts, 137, 32)) +>T : Symbol(T, Decl(intersectionReduction.ts, 137, 13)) +>CA : Symbol(CA, Decl(intersectionReduction.ts, 132, 1)) + + let ab: ABI = x; +>ab : Symbol(ab, Decl(intersectionReduction.ts, 138, 7)) +>ABI : Symbol(ABI, Decl(intersectionReduction.ts, 126, 1)) +>x : Symbol(x, Decl(intersectionReduction.ts, 137, 32)) +} + diff --git a/tests/baselines/reference/intersectionReduction.types b/tests/baselines/reference/intersectionReduction.types index 8c4ee05051b9e..c1ae0685836b6 100644 --- a/tests/baselines/reference/intersectionReduction.types +++ b/tests/baselines/reference/intersectionReduction.types @@ -315,3 +315,65 @@ const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)): >dataB : boolean >t : number +// Repro from #38549 + +interface A2 { + kind: "A"; +>kind : "A" + + a: number; +>a : number +} + +interface B2 { + kind: "B"; +>kind : "B" + + b: number; +>b : number +} + +declare const shouldBeB: (A2 | B2) & B2; +>shouldBeB : B2 + +const b: B2 = shouldBeB; // works +>b : B2 +>shouldBeB : B2 + +function inGeneric(alsoShouldBeB: T & B2) { +>inGeneric : (alsoShouldBeB: T & B2) => void +>alsoShouldBeB : T & B2 + + const b: B2 = alsoShouldBeB; +>b : B2 +>alsoShouldBeB : T & B2 +} + +// Repro from #38542 + +interface ABI { + kind: 'a' | 'b'; +>kind : "a" | "b" +} + +declare class CA { kind: 'a'; a: string; x: number }; +>CA : CA +>kind : "a" +>a : string +>x : number + +declare class CB { kind: 'b'; b: string; y: number }; +>CB : CB +>kind : "b" +>b : string +>y : number + +function bar(x: T & CA) { +>bar : (x: T & CA) => void +>x : T & CA + + let ab: ABI = x; +>ab : ABI +>x : T & CA +} + diff --git a/tests/cases/conformance/types/intersection/intersectionReduction.ts b/tests/cases/conformance/types/intersection/intersectionReduction.ts index 8aeb110fd5f10..0901524e14194 100644 --- a/tests/cases/conformance/types/intersection/intersectionReduction.ts +++ b/tests/cases/conformance/types/intersection/intersectionReduction.ts @@ -108,3 +108,35 @@ type Container = { const f2 = (t: Container<"a"> | (Container<"b"> & Container<"c">)): Container<"a"> => t; const f3 = (t: Container<"a"> | (Container<"b"> & { dataB: boolean } & Container<"a">)): Container<"a"> => t; const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)): number => t; + +// Repro from #38549 + +interface A2 { + kind: "A"; + a: number; +} + +interface B2 { + kind: "B"; + b: number; +} + +declare const shouldBeB: (A2 | B2) & B2; +const b: B2 = shouldBeB; // works + +function inGeneric(alsoShouldBeB: T & B2) { + const b: B2 = alsoShouldBeB; +} + +// Repro from #38542 + +interface ABI { + kind: 'a' | 'b'; +} + +declare class CA { kind: 'a'; a: string; x: number }; +declare class CB { kind: 'b'; b: string; y: number }; + +function bar(x: T & CA) { + let ab: ABI = x; +}