Skip to content

Commit f0de46b

Browse files
Merge pull request #30857 from Microsoft/fixInferenceToIntersection (#30968)
Fix inference to intersections
1 parent e0f136b commit f0de46b

File tree

7 files changed

+115
-28
lines changed

7 files changed

+115
-28
lines changed

src/compiler/checker.ts

+21-6
Original file line numberDiff line numberDiff line change
@@ -14709,15 +14709,30 @@ namespace ts {
1470914709
inferFromTypes(source, (<ConditionalType>target).falseType);
1471014710
}
1471114711
else if (target.flags & TypeFlags.UnionOrIntersection) {
14712+
// We infer from types that are not naked type variables first so that inferences we
14713+
// make from nested naked type variables and given slightly higher priority by virtue
14714+
// of being first in the candidates array.
14715+
let typeVariableCount = 0;
1471214716
for (const t of (<UnionOrIntersectionType>target).types) {
14713-
const savePriority = priority;
14714-
// Inferences directly to naked type variables are given lower priority as they are
14715-
// less specific. For example, when inferring from Promise<string> to T | Promise<T>,
14716-
// we want to infer string for T, not Promise<string> | string.
1471714717
if (getInferenceInfoForType(t)) {
14718-
priority |= InferencePriority.NakedTypeVariable;
14718+
typeVariableCount++;
14719+
}
14720+
else {
14721+
inferFromTypes(source, t);
14722+
}
14723+
}
14724+
// Inferences directly to naked type variables are given lower priority as they are
14725+
// less specific. For example, when inferring from Promise<string> to T | Promise<T>,
14726+
// we want to infer string for T, not Promise<string> | string. For intersection types
14727+
// we only infer to single naked type variables.
14728+
if (target.flags & TypeFlags.Union ? typeVariableCount !== 0 : typeVariableCount === 1) {
14729+
const savePriority = priority;
14730+
priority |= InferencePriority.NakedTypeVariable;
14731+
for (const t of (<UnionOrIntersectionType>target).types) {
14732+
if (getInferenceInfoForType(t)) {
14733+
inferFromTypes(source, t);
14734+
}
1471914735
}
14720-
inferFromTypes(source, t);
1472114736
priority = savePriority;
1472214737
}
1472314738
}

tests/baselines/reference/conditionalTypeDoesntSpinForever.types

+8-8
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

+1-1
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

+13-3
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

+29-2
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

+35-6
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

+8-2
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)