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

Deduplicate intersections before distributing over unions #12537

Merged
merged 2 commits into from
Nov 28, 2016
Merged
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
21 changes: 12 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5637,6 +5637,7 @@ namespace ts {
containsString?: boolean;
containsNumber?: boolean;
containsStringOrNumberLiteral?: boolean;
unionIndex?: number;
}

function binarySearchTypes(types: Type[], type: Type): number {
Expand Down Expand Up @@ -5831,6 +5832,9 @@ namespace ts {
typeSet.containsAny = true;
}
else if (!(type.flags & TypeFlags.Never) && (strictNullChecks || !(type.flags & TypeFlags.Nullable)) && !contains(typeSet, type)) {
if (type.flags & TypeFlags.Union && typeSet.unionIndex === undefined) {
typeSet.unionIndex = typeSet.length;
}
typeSet.push(type);
}
}
Expand All @@ -5857,15 +5861,6 @@ namespace ts {
if (types.length === 0) {
return emptyObjectType;
}
for (let i = 0; i < types.length; i++) {
const type = types[i];
if (type.flags & TypeFlags.Union) {
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
return getUnionType(map((<UnionType>type).types, t => getIntersectionType(replaceElement(types, i, t))),
/*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments);
}
}
const typeSet = [] as TypeSet;
addTypesToIntersection(typeSet, types);
if (typeSet.containsAny) {
Expand All @@ -5874,6 +5869,14 @@ namespace ts {
if (typeSet.length === 1) {
return typeSet[0];
}
const unionIndex = typeSet.unionIndex;
if (unionIndex !== undefined) {
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
const unionType = <UnionType>typeSet[unionIndex];
return getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))),
/*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments);
}
const id = getTypeListId(typeSet);
let type = intersectionTypes[id];
if (!type) {
Expand Down
50 changes: 50 additions & 0 deletions tests/baselines/reference/intersectionTypeNormalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,51 @@ function getValueAsString(value: IntersectionFail): string {
return '' + value.num;
}
return value.str;
}

// Repro from #12535

namespace enums {
export const enum A {
a1,
a2,
a3,
// ... elements omitted for the sake of clarity
a75,
a76,
a77,
}
export const enum B {
b1,
b2,
// ... elements omitted for the sake of clarity
b86,
b87,
}
export const enum C {
c1,
c2,
// ... elements omitted for the sake of clarity
c210,
c211,
}
export type Genre = A | B | C;
}

type Foo = {
genreId: enums.Genre;
};

type Bar = {
genreId: enums.Genre;
};

type FooBar = Foo & Bar;

function foo(so: any) {
const val = so as FooBar;
const isGenre = val.genreId;
return isGenre;
}

//// [intersectionTypeNormalization.js]
Expand All @@ -77,3 +122,8 @@ function getValueAsString(value) {
}
return value.str;
}
function foo(so) {
var val = so;
var isGenre = val.genreId;
return isGenre;
}
110 changes: 110 additions & 0 deletions tests/baselines/reference/intersectionTypeNormalization.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,113 @@ function getValueAsString(value: IntersectionFail): string {
>value : Symbol(value, Decl(intersectionTypeNormalization.ts, 54, 26))
>str : Symbol(str, Decl(intersectionTypeNormalization.ts, 47, 35))
}

// Repro from #12535

namespace enums {
>enums : Symbol(enums, Decl(intersectionTypeNormalization.ts, 59, 1))

export const enum A {
>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 63, 17))

a1,
>a1 : Symbol(A.a1, Decl(intersectionTypeNormalization.ts, 64, 25))

a2,
>a2 : Symbol(A.a2, Decl(intersectionTypeNormalization.ts, 65, 11))

a3,
>a3 : Symbol(A.a3, Decl(intersectionTypeNormalization.ts, 66, 11))

// ... elements omitted for the sake of clarity
a75,
>a75 : Symbol(A.a75, Decl(intersectionTypeNormalization.ts, 67, 11))

a76,
>a76 : Symbol(A.a76, Decl(intersectionTypeNormalization.ts, 69, 12))

a77,
>a77 : Symbol(A.a77, Decl(intersectionTypeNormalization.ts, 70, 12))
}
export const enum B {
>B : Symbol(B, Decl(intersectionTypeNormalization.ts, 72, 5))

b1,
>b1 : Symbol(B.b1, Decl(intersectionTypeNormalization.ts, 73, 25))

b2,
>b2 : Symbol(B.b2, Decl(intersectionTypeNormalization.ts, 74, 11))

// ... elements omitted for the sake of clarity
b86,
>b86 : Symbol(B.b86, Decl(intersectionTypeNormalization.ts, 75, 11))

b87,
>b87 : Symbol(B.b87, Decl(intersectionTypeNormalization.ts, 77, 12))
}
export const enum C {
>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 79, 5))

c1,
>c1 : Symbol(C.c1, Decl(intersectionTypeNormalization.ts, 80, 25))

c2,
>c2 : Symbol(C.c2, Decl(intersectionTypeNormalization.ts, 81, 11))

// ... elements omitted for the sake of clarity
c210,
>c210 : Symbol(C.c210, Decl(intersectionTypeNormalization.ts, 82, 11))

c211,
>c211 : Symbol(C.c211, Decl(intersectionTypeNormalization.ts, 84, 13))
}
export type Genre = A | B | C;
>Genre : Symbol(Genre, Decl(intersectionTypeNormalization.ts, 86, 5))
>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 63, 17))
>B : Symbol(B, Decl(intersectionTypeNormalization.ts, 72, 5))
>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 79, 5))
}

type Foo = {
>Foo : Symbol(Foo, Decl(intersectionTypeNormalization.ts, 88, 1))

genreId: enums.Genre;
>genreId : Symbol(genreId, Decl(intersectionTypeNormalization.ts, 90, 12))
>enums : Symbol(enums, Decl(intersectionTypeNormalization.ts, 59, 1))
>Genre : Symbol(enums.Genre, Decl(intersectionTypeNormalization.ts, 86, 5))

};

type Bar = {
>Bar : Symbol(Bar, Decl(intersectionTypeNormalization.ts, 92, 2))

genreId: enums.Genre;
>genreId : Symbol(genreId, Decl(intersectionTypeNormalization.ts, 94, 12))
>enums : Symbol(enums, Decl(intersectionTypeNormalization.ts, 59, 1))
>Genre : Symbol(enums.Genre, Decl(intersectionTypeNormalization.ts, 86, 5))

};

type FooBar = Foo & Bar;
>FooBar : Symbol(FooBar, Decl(intersectionTypeNormalization.ts, 96, 2))
>Foo : Symbol(Foo, Decl(intersectionTypeNormalization.ts, 88, 1))
>Bar : Symbol(Bar, Decl(intersectionTypeNormalization.ts, 92, 2))

function foo(so: any) {
>foo : Symbol(foo, Decl(intersectionTypeNormalization.ts, 98, 24))
>so : Symbol(so, Decl(intersectionTypeNormalization.ts, 100, 13))

const val = so as FooBar;
>val : Symbol(val, Decl(intersectionTypeNormalization.ts, 101, 9))
>so : Symbol(so, Decl(intersectionTypeNormalization.ts, 100, 13))
>FooBar : Symbol(FooBar, Decl(intersectionTypeNormalization.ts, 96, 2))

const isGenre = val.genreId;
>isGenre : Symbol(isGenre, Decl(intersectionTypeNormalization.ts, 102, 9))
>val.genreId : Symbol(genreId, Decl(intersectionTypeNormalization.ts, 90, 12), Decl(intersectionTypeNormalization.ts, 94, 12))
>val : Symbol(val, Decl(intersectionTypeNormalization.ts, 101, 9))
>genreId : Symbol(genreId, Decl(intersectionTypeNormalization.ts, 90, 12), Decl(intersectionTypeNormalization.ts, 94, 12))

return isGenre;
>isGenre : Symbol(isGenre, Decl(intersectionTypeNormalization.ts, 102, 9))
}
111 changes: 111 additions & 0 deletions tests/baselines/reference/intersectionTypeNormalization.types
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,114 @@ function getValueAsString(value: IntersectionFail): string {
>value : { kind: "string"; str: string; } & ToString
>str : string
}

// Repro from #12535

namespace enums {
>enums : typeof enums

export const enum A {
>A : A

a1,
>a1 : A.a1

a2,
>a2 : A.a2

a3,
>a3 : A.a3

// ... elements omitted for the sake of clarity
a75,
>a75 : A.a75

a76,
>a76 : A.a76

a77,
>a77 : A.a77
}
export const enum B {
>B : B

b1,
>b1 : B.b1

b2,
>b2 : B.b2

// ... elements omitted for the sake of clarity
b86,
>b86 : B.b86

b87,
>b87 : B.b87
}
export const enum C {
>C : C

c1,
>c1 : C.c1

c2,
>c2 : C.c2

// ... elements omitted for the sake of clarity
c210,
>c210 : C.c210

c211,
>c211 : C.c211
}
export type Genre = A | B | C;
>Genre : Genre
>A : A
>B : B
>C : C
}

type Foo = {
>Foo : Foo

genreId: enums.Genre;
>genreId : enums.Genre
>enums : any
>Genre : enums.Genre

};

type Bar = {
>Bar : Bar

genreId: enums.Genre;
>genreId : enums.Genre
>enums : any
>Genre : enums.Genre

};

type FooBar = Foo & Bar;
>FooBar : FooBar
>Foo : Foo
>Bar : Bar

function foo(so: any) {
>foo : (so: any) => enums.Genre
>so : any

const val = so as FooBar;
>val : FooBar
>so as FooBar : FooBar
>so : any
>FooBar : FooBar

const isGenre = val.genreId;
>isGenre : enums.Genre
>val.genreId : enums.Genre
>val : FooBar
>genreId : enums.Genre

return isGenre;
>isGenre : enums.Genre
}
45 changes: 45 additions & 0 deletions tests/cases/compiler/intersectionTypeNormalization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,49 @@ function getValueAsString(value: IntersectionFail): string {
return '' + value.num;
}
return value.str;
}

// Repro from #12535

namespace enums {
export const enum A {
a1,
a2,
a3,
// ... elements omitted for the sake of clarity
a75,
a76,
a77,
}
export const enum B {
b1,
b2,
// ... elements omitted for the sake of clarity
b86,
b87,
}
export const enum C {
c1,
c2,
// ... elements omitted for the sake of clarity
c210,
c211,
}
export type Genre = A | B | C;
}

type Foo = {
genreId: enums.Genre;
};

type Bar = {
genreId: enums.Genre;
};

type FooBar = Foo & Bar;

function foo(so: any) {
const val = so as FooBar;
const isGenre = val.genreId;
return isGenre;
}