Skip to content

Commit

Permalink
Cherry-pick PR #38565 into release-3.9
Browse files Browse the repository at this point in the history
Component commits:
e03cb80 Perform intersection reduction before and after getApparentType

7af78d3 Add regression tests
  • Loading branch information
ahejlsberg authored and typescript-bot committed May 19, 2020
1 parent 8037e26 commit a654a23
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 6 deletions.
20 changes: 14 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10144,7 +10144,7 @@ namespace ts {
}

function getPropertiesOfType(type: Type): Symbol[] {
type = getApparentType(getReducedType(type));
type = getReducedApparentType(type);
return type.flags & TypeFlags.UnionOrIntersection ?
getPropertiesOfUnionOrIntersectionType(<UnionType>type) :
getPropertiesOfObjectType(type);
Expand Down Expand Up @@ -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<Symbol> | undefined;
Expand Down Expand Up @@ -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(<ObjectType>type);
const symbol = resolved.members.get(name);
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
32 changes: 32 additions & 0 deletions tests/baselines/reference/intersectionReduction.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends A2 | B2>(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<T extends CA | CB>(x: T & CA) {
let ab: ABI = x;
}

41 changes: 41 additions & 0 deletions tests/baselines/reference/intersectionReduction.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,38 @@ type Container<Type extends string> = {
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<T extends A2 | B2>(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<T extends CA | CB>(x: T & CA) {
let ab: ABI = x;
}


//// [intersectionReduction.js]
Expand All @@ -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;
}
84 changes: 84 additions & 0 deletions tests/baselines/reference/intersectionReduction.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends A2 | B2>(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<T extends CA | CB>(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))
}

62 changes: 62 additions & 0 deletions tests/baselines/reference/intersectionReduction.types
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends A2 | B2>(alsoShouldBeB: T & B2) {
>inGeneric : <T extends A2 | B2>(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<T extends CA | CB>(x: T & CA) {
>bar : <T extends CA | CB>(x: T & CA) => void
>x : T & CA

let ab: ABI = x;
>ab : ABI
>x : T & CA
}

Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,35 @@ type Container<Type extends string> = {
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<T extends A2 | B2>(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<T extends CA | CB>(x: T & CA) {
let ab: ABI = x;
}

0 comments on commit a654a23

Please sign in to comment.