Skip to content

Commit 5b873fa

Browse files
authored
Merge pull request #12537 from Microsoft/fixIntersectionNormalization
Deduplicate intersections before distributing over unions
2 parents 88b7d53 + 0be4ede commit 5b873fa

File tree

5 files changed

+328
-9
lines changed

5 files changed

+328
-9
lines changed

src/compiler/checker.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5637,6 +5637,7 @@ namespace ts {
56375637
containsString?: boolean;
56385638
containsNumber?: boolean;
56395639
containsStringOrNumberLiteral?: boolean;
5640+
unionIndex?: number;
56405641
}
56415642

56425643
function binarySearchTypes(types: Type[], type: Type): number {
@@ -5831,6 +5832,9 @@ namespace ts {
58315832
typeSet.containsAny = true;
58325833
}
58335834
else if (!(type.flags & TypeFlags.Never) && (strictNullChecks || !(type.flags & TypeFlags.Nullable)) && !contains(typeSet, type)) {
5835+
if (type.flags & TypeFlags.Union && typeSet.unionIndex === undefined) {
5836+
typeSet.unionIndex = typeSet.length;
5837+
}
58345838
typeSet.push(type);
58355839
}
58365840
}
@@ -5857,15 +5861,6 @@ namespace ts {
58575861
if (types.length === 0) {
58585862
return emptyObjectType;
58595863
}
5860-
for (let i = 0; i < types.length; i++) {
5861-
const type = types[i];
5862-
if (type.flags & TypeFlags.Union) {
5863-
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
5864-
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
5865-
return getUnionType(map((<UnionType>type).types, t => getIntersectionType(replaceElement(types, i, t))),
5866-
/*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments);
5867-
}
5868-
}
58695864
const typeSet = [] as TypeSet;
58705865
addTypesToIntersection(typeSet, types);
58715866
if (typeSet.containsAny) {
@@ -5874,6 +5869,14 @@ namespace ts {
58745869
if (typeSet.length === 1) {
58755870
return typeSet[0];
58765871
}
5872+
const unionIndex = typeSet.unionIndex;
5873+
if (unionIndex !== undefined) {
5874+
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
5875+
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
5876+
const unionType = <UnionType>typeSet[unionIndex];
5877+
return getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))),
5878+
/*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments);
5879+
}
58775880
const id = getTypeListId(typeSet);
58785881
let type = intersectionTypes[id];
58795882
if (!type) {

tests/baselines/reference/intersectionTypeNormalization.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,51 @@ function getValueAsString(value: IntersectionFail): string {
5858
return '' + value.num;
5959
}
6060
return value.str;
61+
}
62+
63+
// Repro from #12535
64+
65+
namespace enums {
66+
export const enum A {
67+
a1,
68+
a2,
69+
a3,
70+
// ... elements omitted for the sake of clarity
71+
a75,
72+
a76,
73+
a77,
74+
}
75+
export const enum B {
76+
b1,
77+
b2,
78+
// ... elements omitted for the sake of clarity
79+
b86,
80+
b87,
81+
}
82+
export const enum C {
83+
c1,
84+
c2,
85+
// ... elements omitted for the sake of clarity
86+
c210,
87+
c211,
88+
}
89+
export type Genre = A | B | C;
90+
}
91+
92+
type Foo = {
93+
genreId: enums.Genre;
94+
};
95+
96+
type Bar = {
97+
genreId: enums.Genre;
98+
};
99+
100+
type FooBar = Foo & Bar;
101+
102+
function foo(so: any) {
103+
const val = so as FooBar;
104+
const isGenre = val.genreId;
105+
return isGenre;
61106
}
62107

63108
//// [intersectionTypeNormalization.js]
@@ -77,3 +122,8 @@ function getValueAsString(value) {
77122
}
78123
return value.str;
79124
}
125+
function foo(so) {
126+
var val = so;
127+
var isGenre = val.genreId;
128+
return isGenre;
129+
}

tests/baselines/reference/intersectionTypeNormalization.symbols

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,113 @@ function getValueAsString(value: IntersectionFail): string {
240240
>value : Symbol(value, Decl(intersectionTypeNormalization.ts, 54, 26))
241241
>str : Symbol(str, Decl(intersectionTypeNormalization.ts, 47, 35))
242242
}
243+
244+
// Repro from #12535
245+
246+
namespace enums {
247+
>enums : Symbol(enums, Decl(intersectionTypeNormalization.ts, 59, 1))
248+
249+
export const enum A {
250+
>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 63, 17))
251+
252+
a1,
253+
>a1 : Symbol(A.a1, Decl(intersectionTypeNormalization.ts, 64, 25))
254+
255+
a2,
256+
>a2 : Symbol(A.a2, Decl(intersectionTypeNormalization.ts, 65, 11))
257+
258+
a3,
259+
>a3 : Symbol(A.a3, Decl(intersectionTypeNormalization.ts, 66, 11))
260+
261+
// ... elements omitted for the sake of clarity
262+
a75,
263+
>a75 : Symbol(A.a75, Decl(intersectionTypeNormalization.ts, 67, 11))
264+
265+
a76,
266+
>a76 : Symbol(A.a76, Decl(intersectionTypeNormalization.ts, 69, 12))
267+
268+
a77,
269+
>a77 : Symbol(A.a77, Decl(intersectionTypeNormalization.ts, 70, 12))
270+
}
271+
export const enum B {
272+
>B : Symbol(B, Decl(intersectionTypeNormalization.ts, 72, 5))
273+
274+
b1,
275+
>b1 : Symbol(B.b1, Decl(intersectionTypeNormalization.ts, 73, 25))
276+
277+
b2,
278+
>b2 : Symbol(B.b2, Decl(intersectionTypeNormalization.ts, 74, 11))
279+
280+
// ... elements omitted for the sake of clarity
281+
b86,
282+
>b86 : Symbol(B.b86, Decl(intersectionTypeNormalization.ts, 75, 11))
283+
284+
b87,
285+
>b87 : Symbol(B.b87, Decl(intersectionTypeNormalization.ts, 77, 12))
286+
}
287+
export const enum C {
288+
>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 79, 5))
289+
290+
c1,
291+
>c1 : Symbol(C.c1, Decl(intersectionTypeNormalization.ts, 80, 25))
292+
293+
c2,
294+
>c2 : Symbol(C.c2, Decl(intersectionTypeNormalization.ts, 81, 11))
295+
296+
// ... elements omitted for the sake of clarity
297+
c210,
298+
>c210 : Symbol(C.c210, Decl(intersectionTypeNormalization.ts, 82, 11))
299+
300+
c211,
301+
>c211 : Symbol(C.c211, Decl(intersectionTypeNormalization.ts, 84, 13))
302+
}
303+
export type Genre = A | B | C;
304+
>Genre : Symbol(Genre, Decl(intersectionTypeNormalization.ts, 86, 5))
305+
>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 63, 17))
306+
>B : Symbol(B, Decl(intersectionTypeNormalization.ts, 72, 5))
307+
>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 79, 5))
308+
}
309+
310+
type Foo = {
311+
>Foo : Symbol(Foo, Decl(intersectionTypeNormalization.ts, 88, 1))
312+
313+
genreId: enums.Genre;
314+
>genreId : Symbol(genreId, Decl(intersectionTypeNormalization.ts, 90, 12))
315+
>enums : Symbol(enums, Decl(intersectionTypeNormalization.ts, 59, 1))
316+
>Genre : Symbol(enums.Genre, Decl(intersectionTypeNormalization.ts, 86, 5))
317+
318+
};
319+
320+
type Bar = {
321+
>Bar : Symbol(Bar, Decl(intersectionTypeNormalization.ts, 92, 2))
322+
323+
genreId: enums.Genre;
324+
>genreId : Symbol(genreId, Decl(intersectionTypeNormalization.ts, 94, 12))
325+
>enums : Symbol(enums, Decl(intersectionTypeNormalization.ts, 59, 1))
326+
>Genre : Symbol(enums.Genre, Decl(intersectionTypeNormalization.ts, 86, 5))
327+
328+
};
329+
330+
type FooBar = Foo & Bar;
331+
>FooBar : Symbol(FooBar, Decl(intersectionTypeNormalization.ts, 96, 2))
332+
>Foo : Symbol(Foo, Decl(intersectionTypeNormalization.ts, 88, 1))
333+
>Bar : Symbol(Bar, Decl(intersectionTypeNormalization.ts, 92, 2))
334+
335+
function foo(so: any) {
336+
>foo : Symbol(foo, Decl(intersectionTypeNormalization.ts, 98, 24))
337+
>so : Symbol(so, Decl(intersectionTypeNormalization.ts, 100, 13))
338+
339+
const val = so as FooBar;
340+
>val : Symbol(val, Decl(intersectionTypeNormalization.ts, 101, 9))
341+
>so : Symbol(so, Decl(intersectionTypeNormalization.ts, 100, 13))
342+
>FooBar : Symbol(FooBar, Decl(intersectionTypeNormalization.ts, 96, 2))
343+
344+
const isGenre = val.genreId;
345+
>isGenre : Symbol(isGenre, Decl(intersectionTypeNormalization.ts, 102, 9))
346+
>val.genreId : Symbol(genreId, Decl(intersectionTypeNormalization.ts, 90, 12), Decl(intersectionTypeNormalization.ts, 94, 12))
347+
>val : Symbol(val, Decl(intersectionTypeNormalization.ts, 101, 9))
348+
>genreId : Symbol(genreId, Decl(intersectionTypeNormalization.ts, 90, 12), Decl(intersectionTypeNormalization.ts, 94, 12))
349+
350+
return isGenre;
351+
>isGenre : Symbol(isGenre, Decl(intersectionTypeNormalization.ts, 102, 9))
352+
}

tests/baselines/reference/intersectionTypeNormalization.types

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,114 @@ function getValueAsString(value: IntersectionFail): string {
244244
>value : { kind: "string"; str: string; } & ToString
245245
>str : string
246246
}
247+
248+
// Repro from #12535
249+
250+
namespace enums {
251+
>enums : typeof enums
252+
253+
export const enum A {
254+
>A : A
255+
256+
a1,
257+
>a1 : A.a1
258+
259+
a2,
260+
>a2 : A.a2
261+
262+
a3,
263+
>a3 : A.a3
264+
265+
// ... elements omitted for the sake of clarity
266+
a75,
267+
>a75 : A.a75
268+
269+
a76,
270+
>a76 : A.a76
271+
272+
a77,
273+
>a77 : A.a77
274+
}
275+
export const enum B {
276+
>B : B
277+
278+
b1,
279+
>b1 : B.b1
280+
281+
b2,
282+
>b2 : B.b2
283+
284+
// ... elements omitted for the sake of clarity
285+
b86,
286+
>b86 : B.b86
287+
288+
b87,
289+
>b87 : B.b87
290+
}
291+
export const enum C {
292+
>C : C
293+
294+
c1,
295+
>c1 : C.c1
296+
297+
c2,
298+
>c2 : C.c2
299+
300+
// ... elements omitted for the sake of clarity
301+
c210,
302+
>c210 : C.c210
303+
304+
c211,
305+
>c211 : C.c211
306+
}
307+
export type Genre = A | B | C;
308+
>Genre : Genre
309+
>A : A
310+
>B : B
311+
>C : C
312+
}
313+
314+
type Foo = {
315+
>Foo : Foo
316+
317+
genreId: enums.Genre;
318+
>genreId : enums.Genre
319+
>enums : any
320+
>Genre : enums.Genre
321+
322+
};
323+
324+
type Bar = {
325+
>Bar : Bar
326+
327+
genreId: enums.Genre;
328+
>genreId : enums.Genre
329+
>enums : any
330+
>Genre : enums.Genre
331+
332+
};
333+
334+
type FooBar = Foo & Bar;
335+
>FooBar : FooBar
336+
>Foo : Foo
337+
>Bar : Bar
338+
339+
function foo(so: any) {
340+
>foo : (so: any) => enums.Genre
341+
>so : any
342+
343+
const val = so as FooBar;
344+
>val : FooBar
345+
>so as FooBar : FooBar
346+
>so : any
347+
>FooBar : FooBar
348+
349+
const isGenre = val.genreId;
350+
>isGenre : enums.Genre
351+
>val.genreId : enums.Genre
352+
>val : FooBar
353+
>genreId : enums.Genre
354+
355+
return isGenre;
356+
>isGenre : enums.Genre
357+
}

tests/cases/compiler/intersectionTypeNormalization.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,49 @@ function getValueAsString(value: IntersectionFail): string {
5757
return '' + value.num;
5858
}
5959
return value.str;
60+
}
61+
62+
// Repro from #12535
63+
64+
namespace enums {
65+
export const enum A {
66+
a1,
67+
a2,
68+
a3,
69+
// ... elements omitted for the sake of clarity
70+
a75,
71+
a76,
72+
a77,
73+
}
74+
export const enum B {
75+
b1,
76+
b2,
77+
// ... elements omitted for the sake of clarity
78+
b86,
79+
b87,
80+
}
81+
export const enum C {
82+
c1,
83+
c2,
84+
// ... elements omitted for the sake of clarity
85+
c210,
86+
c211,
87+
}
88+
export type Genre = A | B | C;
89+
}
90+
91+
type Foo = {
92+
genreId: enums.Genre;
93+
};
94+
95+
type Bar = {
96+
genreId: enums.Genre;
97+
};
98+
99+
type FooBar = Foo & Bar;
100+
101+
function foo(so: any) {
102+
const val = so as FooBar;
103+
const isGenre = val.genreId;
104+
return isGenre;
60105
}

0 commit comments

Comments
 (0)