Skip to content

Commit a654a23

Browse files
ahejlsbergtypescript-bot
authored andcommitted
Cherry-pick PR #38565 into release-3.9
Component commits: e03cb80 Perform intersection reduction before and after getApparentType 7af78d3 Add regression tests
1 parent 8037e26 commit a654a23

File tree

6 files changed

+265
-6
lines changed

6 files changed

+265
-6
lines changed

src/compiler/checker.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -10144,7 +10144,7 @@ namespace ts {
1014410144
}
1014510145

1014610146
function getPropertiesOfType(type: Type): Symbol[] {
10147-
type = getApparentType(getReducedType(type));
10147+
type = getReducedApparentType(type);
1014810148
return type.flags & TypeFlags.UnionOrIntersection ?
1014910149
getPropertiesOfUnionOrIntersectionType(<UnionType>type) :
1015010150
getPropertiesOfObjectType(type);
@@ -10500,6 +10500,14 @@ namespace ts {
1050010500
t;
1050110501
}
1050210502

10503+
function getReducedApparentType(type: Type): Type {
10504+
// Since getApparentType may return a non-reduced union or intersection type, we need to perform
10505+
// type reduction both before and after obtaining the apparent type. For example, given a type parameter
10506+
// 'T extends A | B', the type 'T & X' becomes 'A & X | B & X' after obtaining the apparent type, and
10507+
// that type may need futher reduction to remove empty intersections.
10508+
return getReducedType(getApparentType(getReducedType(type)));
10509+
}
10510+
1050310511
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined {
1050410512
let singleProp: Symbol | undefined;
1050510513
let propSet: Map<Symbol> | undefined;
@@ -10721,7 +10729,7 @@ namespace ts {
1072110729
* @param name a name of property to look up in a given type
1072210730
*/
1072310731
function getPropertyOfType(type: Type, name: __String): Symbol | undefined {
10724-
type = getApparentType(getReducedType(type));
10732+
type = getReducedApparentType(type);
1072510733
if (type.flags & TypeFlags.Object) {
1072610734
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
1072710735
const symbol = resolved.members.get(name);
@@ -10759,7 +10767,7 @@ namespace ts {
1075910767
* maps primitive types and type parameters are to their apparent types.
1076010768
*/
1076110769
function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] {
10762-
return getSignaturesOfStructuredType(getApparentType(getReducedType(type)), kind);
10770+
return getSignaturesOfStructuredType(getReducedApparentType(type), kind);
1076310771
}
1076410772

1076510773
function getIndexInfoOfStructuredType(type: Type, kind: IndexKind): IndexInfo | undefined {
@@ -10777,13 +10785,13 @@ namespace ts {
1077710785
// Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and
1077810786
// maps primitive types and type parameters are to their apparent types.
1077910787
function getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined {
10780-
return getIndexInfoOfStructuredType(getApparentType(getReducedType(type)), kind);
10788+
return getIndexInfoOfStructuredType(getReducedApparentType(type), kind);
1078110789
}
1078210790

1078310791
// Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and
1078410792
// maps primitive types and type parameters are to their apparent types.
1078510793
function getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined {
10786-
return getIndexTypeOfStructuredType(getApparentType(getReducedType(type)), kind);
10794+
return getIndexTypeOfStructuredType(getReducedApparentType(type), kind);
1078710795
}
1078810796

1078910797
function getImplicitIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined {
@@ -13154,7 +13162,7 @@ namespace ts {
1315413162
// In the following we resolve T[K] to the type of the property in T selected by K.
1315513163
// We treat boolean as different from other unions to improve errors;
1315613164
// skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'.
13157-
const apparentObjectType = getApparentType(getReducedType(objectType));
13165+
const apparentObjectType = getReducedApparentType(objectType);
1315813166
if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) {
1315913167
const propTypes: Type[] = [];
1316013168
let wasMissingProp = false;

tests/baselines/reference/intersectionReduction.errors.txt

+32
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,36 @@ tests/cases/conformance/types/intersection/intersectionReduction.ts(81,1): error
120120
const f2 = (t: Container<"a"> | (Container<"b"> & Container<"c">)): Container<"a"> => t;
121121
const f3 = (t: Container<"a"> | (Container<"b"> & { dataB: boolean } & Container<"a">)): Container<"a"> => t;
122122
const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)): number => t;
123+
124+
// Repro from #38549
125+
126+
interface A2 {
127+
kind: "A";
128+
a: number;
129+
}
130+
131+
interface B2 {
132+
kind: "B";
133+
b: number;
134+
}
135+
136+
declare const shouldBeB: (A2 | B2) & B2;
137+
const b: B2 = shouldBeB; // works
138+
139+
function inGeneric<T extends A2 | B2>(alsoShouldBeB: T & B2) {
140+
const b: B2 = alsoShouldBeB;
141+
}
142+
143+
// Repro from #38542
144+
145+
interface ABI {
146+
kind: 'a' | 'b';
147+
}
148+
149+
declare class CA { kind: 'a'; a: string; x: number };
150+
declare class CB { kind: 'b'; b: string; y: number };
151+
152+
function bar<T extends CA | CB>(x: T & CA) {
153+
let ab: ABI = x;
154+
}
123155

tests/baselines/reference/intersectionReduction.js

+41
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,38 @@ type Container<Type extends string> = {
107107
const f2 = (t: Container<"a"> | (Container<"b"> & Container<"c">)): Container<"a"> => t;
108108
const f3 = (t: Container<"a"> | (Container<"b"> & { dataB: boolean } & Container<"a">)): Container<"a"> => t;
109109
const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)): number => t;
110+
111+
// Repro from #38549
112+
113+
interface A2 {
114+
kind: "A";
115+
a: number;
116+
}
117+
118+
interface B2 {
119+
kind: "B";
120+
b: number;
121+
}
122+
123+
declare const shouldBeB: (A2 | B2) & B2;
124+
const b: B2 = shouldBeB; // works
125+
126+
function inGeneric<T extends A2 | B2>(alsoShouldBeB: T & B2) {
127+
const b: B2 = alsoShouldBeB;
128+
}
129+
130+
// Repro from #38542
131+
132+
interface ABI {
133+
kind: 'a' | 'b';
134+
}
135+
136+
declare class CA { kind: 'a'; a: string; x: number };
137+
declare class CB { kind: 'b'; b: string; y: number };
138+
139+
function bar<T extends CA | CB>(x: T & CA) {
140+
let ab: ABI = x;
141+
}
110142

111143

112144
//// [intersectionReduction.js]
@@ -128,3 +160,12 @@ var f1 = function (t) { return t; };
128160
var f2 = function (t) { return t; };
129161
var f3 = function (t) { return t; };
130162
var f4 = function (t) { return t; };
163+
var b = shouldBeB; // works
164+
function inGeneric(alsoShouldBeB) {
165+
var b = alsoShouldBeB;
166+
}
167+
;
168+
;
169+
function bar(x) {
170+
var ab = x;
171+
}

tests/baselines/reference/intersectionReduction.symbols

+84
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,87 @@ const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)):
373373
>Container : Symbol(Container, Decl(intersectionReduction.ts, 99, 44))
374374
>t : Symbol(t, Decl(intersectionReduction.ts, 107, 12))
375375

376+
// Repro from #38549
377+
378+
interface A2 {
379+
>A2 : Symbol(A2, Decl(intersectionReduction.ts, 107, 93))
380+
381+
kind: "A";
382+
>kind : Symbol(A2.kind, Decl(intersectionReduction.ts, 111, 14))
383+
384+
a: number;
385+
>a : Symbol(A2.a, Decl(intersectionReduction.ts, 112, 14))
386+
}
387+
388+
interface B2 {
389+
>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1))
390+
391+
kind: "B";
392+
>kind : Symbol(B2.kind, Decl(intersectionReduction.ts, 116, 14))
393+
394+
b: number;
395+
>b : Symbol(B2.b, Decl(intersectionReduction.ts, 117, 14))
396+
}
397+
398+
declare const shouldBeB: (A2 | B2) & B2;
399+
>shouldBeB : Symbol(shouldBeB, Decl(intersectionReduction.ts, 121, 13))
400+
>A2 : Symbol(A2, Decl(intersectionReduction.ts, 107, 93))
401+
>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1))
402+
>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1))
403+
404+
const b: B2 = shouldBeB; // works
405+
>b : Symbol(b, Decl(intersectionReduction.ts, 122, 5))
406+
>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1))
407+
>shouldBeB : Symbol(shouldBeB, Decl(intersectionReduction.ts, 121, 13))
408+
409+
function inGeneric<T extends A2 | B2>(alsoShouldBeB: T & B2) {
410+
>inGeneric : Symbol(inGeneric, Decl(intersectionReduction.ts, 122, 24))
411+
>T : Symbol(T, Decl(intersectionReduction.ts, 124, 19))
412+
>A2 : Symbol(A2, Decl(intersectionReduction.ts, 107, 93))
413+
>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1))
414+
>alsoShouldBeB : Symbol(alsoShouldBeB, Decl(intersectionReduction.ts, 124, 38))
415+
>T : Symbol(T, Decl(intersectionReduction.ts, 124, 19))
416+
>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1))
417+
418+
const b: B2 = alsoShouldBeB;
419+
>b : Symbol(b, Decl(intersectionReduction.ts, 125, 9))
420+
>B2 : Symbol(B2, Decl(intersectionReduction.ts, 114, 1))
421+
>alsoShouldBeB : Symbol(alsoShouldBeB, Decl(intersectionReduction.ts, 124, 38))
422+
}
423+
424+
// Repro from #38542
425+
426+
interface ABI {
427+
>ABI : Symbol(ABI, Decl(intersectionReduction.ts, 126, 1))
428+
429+
kind: 'a' | 'b';
430+
>kind : Symbol(ABI.kind, Decl(intersectionReduction.ts, 130, 15))
431+
}
432+
433+
declare class CA { kind: 'a'; a: string; x: number };
434+
>CA : Symbol(CA, Decl(intersectionReduction.ts, 132, 1))
435+
>kind : Symbol(CA.kind, Decl(intersectionReduction.ts, 134, 18))
436+
>a : Symbol(CA.a, Decl(intersectionReduction.ts, 134, 29))
437+
>x : Symbol(CA.x, Decl(intersectionReduction.ts, 134, 40))
438+
439+
declare class CB { kind: 'b'; b: string; y: number };
440+
>CB : Symbol(CB, Decl(intersectionReduction.ts, 134, 53))
441+
>kind : Symbol(CB.kind, Decl(intersectionReduction.ts, 135, 18))
442+
>b : Symbol(CB.b, Decl(intersectionReduction.ts, 135, 29))
443+
>y : Symbol(CB.y, Decl(intersectionReduction.ts, 135, 40))
444+
445+
function bar<T extends CA | CB>(x: T & CA) {
446+
>bar : Symbol(bar, Decl(intersectionReduction.ts, 135, 53))
447+
>T : Symbol(T, Decl(intersectionReduction.ts, 137, 13))
448+
>CA : Symbol(CA, Decl(intersectionReduction.ts, 132, 1))
449+
>CB : Symbol(CB, Decl(intersectionReduction.ts, 134, 53))
450+
>x : Symbol(x, Decl(intersectionReduction.ts, 137, 32))
451+
>T : Symbol(T, Decl(intersectionReduction.ts, 137, 13))
452+
>CA : Symbol(CA, Decl(intersectionReduction.ts, 132, 1))
453+
454+
let ab: ABI = x;
455+
>ab : Symbol(ab, Decl(intersectionReduction.ts, 138, 7))
456+
>ABI : Symbol(ABI, Decl(intersectionReduction.ts, 126, 1))
457+
>x : Symbol(x, Decl(intersectionReduction.ts, 137, 32))
458+
}
459+

tests/baselines/reference/intersectionReduction.types

+62
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,65 @@ const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)):
315315
>dataB : boolean
316316
>t : number
317317

318+
// Repro from #38549
319+
320+
interface A2 {
321+
kind: "A";
322+
>kind : "A"
323+
324+
a: number;
325+
>a : number
326+
}
327+
328+
interface B2 {
329+
kind: "B";
330+
>kind : "B"
331+
332+
b: number;
333+
>b : number
334+
}
335+
336+
declare const shouldBeB: (A2 | B2) & B2;
337+
>shouldBeB : B2
338+
339+
const b: B2 = shouldBeB; // works
340+
>b : B2
341+
>shouldBeB : B2
342+
343+
function inGeneric<T extends A2 | B2>(alsoShouldBeB: T & B2) {
344+
>inGeneric : <T extends A2 | B2>(alsoShouldBeB: T & B2) => void
345+
>alsoShouldBeB : T & B2
346+
347+
const b: B2 = alsoShouldBeB;
348+
>b : B2
349+
>alsoShouldBeB : T & B2
350+
}
351+
352+
// Repro from #38542
353+
354+
interface ABI {
355+
kind: 'a' | 'b';
356+
>kind : "a" | "b"
357+
}
358+
359+
declare class CA { kind: 'a'; a: string; x: number };
360+
>CA : CA
361+
>kind : "a"
362+
>a : string
363+
>x : number
364+
365+
declare class CB { kind: 'b'; b: string; y: number };
366+
>CB : CB
367+
>kind : "b"
368+
>b : string
369+
>y : number
370+
371+
function bar<T extends CA | CB>(x: T & CA) {
372+
>bar : <T extends CA | CB>(x: T & CA) => void
373+
>x : T & CA
374+
375+
let ab: ABI = x;
376+
>ab : ABI
377+
>x : T & CA
378+
}
379+

tests/cases/conformance/types/intersection/intersectionReduction.ts

+32
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,35 @@ type Container<Type extends string> = {
108108
const f2 = (t: Container<"a"> | (Container<"b"> & Container<"c">)): Container<"a"> => t;
109109
const f3 = (t: Container<"a"> | (Container<"b"> & { dataB: boolean } & Container<"a">)): Container<"a"> => t;
110110
const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)): number => t;
111+
112+
// Repro from #38549
113+
114+
interface A2 {
115+
kind: "A";
116+
a: number;
117+
}
118+
119+
interface B2 {
120+
kind: "B";
121+
b: number;
122+
}
123+
124+
declare const shouldBeB: (A2 | B2) & B2;
125+
const b: B2 = shouldBeB; // works
126+
127+
function inGeneric<T extends A2 | B2>(alsoShouldBeB: T & B2) {
128+
const b: B2 = alsoShouldBeB;
129+
}
130+
131+
// Repro from #38542
132+
133+
interface ABI {
134+
kind: 'a' | 'b';
135+
}
136+
137+
declare class CA { kind: 'a'; a: string; x: number };
138+
declare class CB { kind: 'b'; b: string; y: number };
139+
140+
function bar<T extends CA | CB>(x: T & CA) {
141+
let ab: ABI = x;
142+
}

0 commit comments

Comments
 (0)