Skip to content

Commit 6282645

Browse files
authored
Merge pull request #30857 from Microsoft/fixInferenceToIntersection
Fix inference to intersections
2 parents 6cd229b + d78937e commit 6282645

File tree

7 files changed

+109
-30
lines changed

7 files changed

+109
-30
lines changed

src/compiler/checker.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14815,22 +14815,29 @@ namespace ts {
1481514815
// We infer from types that are not naked type variables first so that inferences we
1481614816
// make from nested naked type variables and given slightly higher priority by virtue
1481714817
// of being first in the candidates array.
14818+
let typeVariableCount = 0;
1481814819
for (const t of (<UnionOrIntersectionType>target).types) {
14819-
if (!getInferenceInfoForType(t)) {
14820+
if (getInferenceInfoForType(t)) {
14821+
typeVariableCount++;
14822+
}
14823+
else {
1482014824
inferFromTypes(source, t);
1482114825
}
1482214826
}
1482314827
// Inferences directly to naked type variables are given lower priority as they are
1482414828
// less specific. For example, when inferring from Promise<string> to T | Promise<T>,
14825-
// we want to infer string for T, not Promise<string> | string.
14826-
const savePriority = priority;
14827-
priority |= InferencePriority.NakedTypeVariable;
14828-
for (const t of (<UnionOrIntersectionType>target).types) {
14829-
if (getInferenceInfoForType(t)) {
14830-
inferFromTypes(source, t);
14829+
// we want to infer string for T, not Promise<string> | string. For intersection types
14830+
// we only infer to single naked type variables.
14831+
if (target.flags & TypeFlags.Union ? typeVariableCount !== 0 : typeVariableCount === 1) {
14832+
const savePriority = priority;
14833+
priority |= InferencePriority.NakedTypeVariable;
14834+
for (const t of (<UnionOrIntersectionType>target).types) {
14835+
if (getInferenceInfoForType(t)) {
14836+
inferFromTypes(source, t);
14837+
}
1483114838
}
14839+
priority = savePriority;
1483214840
}
14833-
priority = savePriority;
1483414841
}
1483514842
else if (source.flags & TypeFlags.Union) {
1483614843
// Source is a union or intersection type, infer from each constituent type

tests/baselines/reference/conditionalTypeDoesntSpinForever.types

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ export enum PubSubRecordIsStoredInRedisAsA {
120120

121121
buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) as
122122
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) as BuildPubSubRecordType<SO_FAR & {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}> : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString; }>
123-
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString; }>
123+
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }>
124124
>buildPubSubRecordType : <SO_FAR>(soFar: SO_FAR) => BuildPubSubRecordType<SO_FAR>
125-
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString; }
125+
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }
126126
>Object.assign : { <T, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
127127
>Object : ObjectConstructor
128128
>assign : { <T, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
@@ -144,9 +144,9 @@ export enum PubSubRecordIsStoredInRedisAsA {
144144

145145
buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) as
146146
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) as BuildPubSubRecordType<SO_FAR & {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}> : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.redisHash; }>
147-
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.redisHash; }>
147+
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }>
148148
>buildPubSubRecordType : <SO_FAR>(soFar: SO_FAR) => BuildPubSubRecordType<SO_FAR>
149-
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.redisHash; }
149+
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }
150150
>Object.assign : { <T, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
151151
>Object : ObjectConstructor
152152
>assign : { <T, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
@@ -337,16 +337,16 @@ export enum PubSubRecordIsStoredInRedisAsA {
337337

338338
buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: 0}>,
339339
>buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: 0}> : BuildPubSubRecordType<SO_FAR & { maxMsToWaitBeforePublishing: 0; }>
340-
>buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) : BuildPubSubRecordType<SO_FAR & { maxMsToWaitBeforePublishing: 0; }>
340+
>buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) : BuildPubSubRecordType<SO_FAR & { maxMsToWaitBeforePublishing: number; }>
341341
>buildPubSubRecordType : <SO_FAR>(soFar: SO_FAR) => BuildPubSubRecordType<SO_FAR>
342-
>Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0}) : SO_FAR & { maxMsToWaitBeforePublishing: 0; }
342+
>Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0}) : SO_FAR & { maxMsToWaitBeforePublishing: number; }
343343
>Object.assign : { <T, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
344344
>Object : ObjectConstructor
345345
>assign : { <T, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
346346
>{} : {}
347347
>soFar : SO_FAR
348-
>{maxMsToWaitBeforePublishing: 0} : { maxMsToWaitBeforePublishing: 0; }
349-
>maxMsToWaitBeforePublishing : 0
348+
>{maxMsToWaitBeforePublishing: 0} : { maxMsToWaitBeforePublishing: number; }
349+
>maxMsToWaitBeforePublishing : number
350350
>0 : 0
351351
>maxMsToWaitBeforePublishing : 0
352352
}

tests/baselines/reference/objectSpread.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ let exclusive: { id: string, a: number, b: string, c: string, d: boolean } =
602602
>d : boolean
603603

604604
f({ a: 1, b: 'yes' }, { c: 'no', d: false })
605-
>f({ a: 1, b: 'yes' }, { c: 'no', d: false }) : { a: number; b: string; } & { c: string; d: false; } & { id: string; }
605+
>f({ a: 1, b: 'yes' }, { c: 'no', d: false }) : { a: number; b: string; } & { c: string; d: boolean; } & { id: string; }
606606
>f : <T, U>(t: T, u: U) => T & U & { id: string; }
607607
>{ a: 1, b: 'yes' } : { a: number; b: string; }
608608
>a : number

tests/baselines/reference/unionAndIntersectionInference1.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,16 @@ const createTest = (): ITest => {
8585
}
8686

8787
declare function f1<T, U>(x: T | U): T | U;
88-
declare function f2<T, U>(x: T & U): T & U;
88+
declare function f2<T, U>(x: T, y: U): T | U;
8989

9090
let x1: string = f1('a');
91-
let x2: string = f2('a');
91+
let x2: string = f2('a', 'b');
92+
93+
// Repro from #30442
94+
95+
const func = <T>() => {};
96+
const assign = <T, U>(a: T, b: U) => Object.assign(a, b);
97+
const res: (() => void) & { func: any } = assign(() => {}, { func });
9298

9399

94100
//// [unionAndIntersectionInference1.js]
@@ -134,4 +140,8 @@ const createTest = () => {
134140
return { name: 'test' };
135141
};
136142
let x1 = f1('a');
137-
let x2 = f2('a');
143+
let x2 = f2('a', 'b');
144+
// Repro from #30442
145+
const func = () => { };
146+
const assign = (a, b) => Object.assign(a, b);
147+
const res = assign(() => { }, { func });

tests/baselines/reference/unionAndIntersectionInference1.symbols

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,13 @@ declare function f1<T, U>(x: T | U): T | U;
239239
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 85, 20))
240240
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 85, 22))
241241

242-
declare function f2<T, U>(x: T & U): T & U;
242+
declare function f2<T, U>(x: T, y: U): T | U;
243243
>f2 : Symbol(f2, Decl(unionAndIntersectionInference1.ts, 85, 43))
244244
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 86, 20))
245245
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 86, 22))
246246
>x : Symbol(x, Decl(unionAndIntersectionInference1.ts, 86, 26))
247247
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 86, 20))
248+
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 86, 31))
248249
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 86, 22))
249250
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 86, 20))
250251
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 86, 22))
@@ -253,7 +254,33 @@ let x1: string = f1('a');
253254
>x1 : Symbol(x1, Decl(unionAndIntersectionInference1.ts, 88, 3))
254255
>f1 : Symbol(f1, Decl(unionAndIntersectionInference1.ts, 83, 1))
255256

256-
let x2: string = f2('a');
257+
let x2: string = f2('a', 'b');
257258
>x2 : Symbol(x2, Decl(unionAndIntersectionInference1.ts, 89, 3))
258259
>f2 : Symbol(f2, Decl(unionAndIntersectionInference1.ts, 85, 43))
259260

261+
// Repro from #30442
262+
263+
const func = <T>() => {};
264+
>func : Symbol(func, Decl(unionAndIntersectionInference1.ts, 93, 5))
265+
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 93, 14))
266+
267+
const assign = <T, U>(a: T, b: U) => Object.assign(a, b);
268+
>assign : Symbol(assign, Decl(unionAndIntersectionInference1.ts, 94, 5))
269+
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 94, 16))
270+
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 94, 18))
271+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 94, 22))
272+
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 94, 16))
273+
>b : Symbol(b, Decl(unionAndIntersectionInference1.ts, 94, 27))
274+
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 94, 18))
275+
>Object.assign : Symbol(ObjectConstructor.assign, Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
276+
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
277+
>assign : Symbol(ObjectConstructor.assign, Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
278+
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 94, 22))
279+
>b : Symbol(b, Decl(unionAndIntersectionInference1.ts, 94, 27))
280+
281+
const res: (() => void) & { func: any } = assign(() => {}, { func });
282+
>res : Symbol(res, Decl(unionAndIntersectionInference1.ts, 95, 5))
283+
>func : Symbol(func, Decl(unionAndIntersectionInference1.ts, 95, 27))
284+
>assign : Symbol(assign, Decl(unionAndIntersectionInference1.ts, 94, 5))
285+
>func : Symbol(func, Decl(unionAndIntersectionInference1.ts, 95, 60))
286+

tests/baselines/reference/unionAndIntersectionInference1.types

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,19 +216,48 @@ declare function f1<T, U>(x: T | U): T | U;
216216
>f1 : <T, U>(x: T | U) => T | U
217217
>x : T | U
218218

219-
declare function f2<T, U>(x: T & U): T & U;
220-
>f2 : <T, U>(x: T & U) => T & U
221-
>x : T & U
219+
declare function f2<T, U>(x: T, y: U): T | U;
220+
>f2 : <T, U>(x: T, y: U) => T | U
221+
>x : T
222+
>y : U
222223

223224
let x1: string = f1('a');
224225
>x1 : string
225226
>f1('a') : "a"
226227
>f1 : <T, U>(x: T | U) => T | U
227228
>'a' : "a"
228229

229-
let x2: string = f2('a');
230+
let x2: string = f2('a', 'b');
230231
>x2 : string
231-
>f2('a') : "a"
232-
>f2 : <T, U>(x: T & U) => T & U
232+
>f2('a', 'b') : "a" | "b"
233+
>f2 : <T, U>(x: T, y: U) => T | U
233234
>'a' : "a"
235+
>'b' : "b"
236+
237+
// Repro from #30442
238+
239+
const func = <T>() => {};
240+
>func : <T>() => void
241+
><T>() => {} : <T>() => void
242+
243+
const assign = <T, U>(a: T, b: U) => Object.assign(a, b);
244+
>assign : <T, U>(a: T, b: U) => T & U
245+
><T, U>(a: T, b: U) => Object.assign(a, b) : <T, U>(a: T, b: U) => T & U
246+
>a : T
247+
>b : U
248+
>Object.assign(a, b) : T & U
249+
>Object.assign : { <T, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
250+
>Object : ObjectConstructor
251+
>assign : { <T, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
252+
>a : T
253+
>b : U
254+
255+
const res: (() => void) & { func: any } = assign(() => {}, { func });
256+
>res : (() => void) & { func: any; }
257+
>func : any
258+
>assign(() => {}, { func }) : (() => void) & { func: <T>() => void; }
259+
>assign : <T, U>(a: T, b: U) => T & U
260+
>() => {} : () => void
261+
>{ func } : { func: <T>() => void; }
262+
>func : <T>() => void
234263

tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,13 @@ const createTest = (): ITest => {
8686
}
8787

8888
declare function f1<T, U>(x: T | U): T | U;
89-
declare function f2<T, U>(x: T & U): T & U;
89+
declare function f2<T, U>(x: T, y: U): T | U;
9090

9191
let x1: string = f1('a');
92-
let x2: string = f2('a');
92+
let x2: string = f2('a', 'b');
93+
94+
// Repro from #30442
95+
96+
const func = <T>() => {};
97+
const assign = <T, U>(a: T, b: U) => Object.assign(a, b);
98+
const res: (() => void) & { func: any } = assign(() => {}, { func });

0 commit comments

Comments
 (0)