diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 90e3a3d2e24de..2a04f34c0de0f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14709,15 +14709,30 @@ namespace ts { inferFromTypes(source, (target).falseType); } else if (target.flags & TypeFlags.UnionOrIntersection) { + // We infer from types that are not naked type variables first so that inferences we + // make from nested naked type variables and given slightly higher priority by virtue + // of being first in the candidates array. + let typeVariableCount = 0; for (const t of (target).types) { - const savePriority = priority; - // Inferences directly to naked type variables are given lower priority as they are - // less specific. For example, when inferring from Promise to T | Promise, - // we want to infer string for T, not Promise | string. if (getInferenceInfoForType(t)) { - priority |= InferencePriority.NakedTypeVariable; + typeVariableCount++; + } + else { + inferFromTypes(source, t); + } + } + // Inferences directly to naked type variables are given lower priority as they are + // less specific. For example, when inferring from Promise to T | Promise, + // we want to infer string for T, not Promise | string. For intersection types + // we only infer to single naked type variables. + if (target.flags & TypeFlags.Union ? typeVariableCount !== 0 : typeVariableCount === 1) { + const savePriority = priority; + priority |= InferencePriority.NakedTypeVariable; + for (const t of (target).types) { + if (getInferenceInfoForType(t)) { + inferFromTypes(source, t); + } } - inferFromTypes(source, t); priority = savePriority; } } diff --git a/tests/baselines/reference/conditionalTypeDoesntSpinForever.types b/tests/baselines/reference/conditionalTypeDoesntSpinForever.types index 1f4b4a62cd293..d4fe4bf48261b 100644 --- a/tests/baselines/reference/conditionalTypeDoesntSpinForever.types +++ b/tests/baselines/reference/conditionalTypeDoesntSpinForever.types @@ -120,9 +120,9 @@ export enum PubSubRecordIsStoredInRedisAsA { buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) as >buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) as BuildPubSubRecordType : BuildPubSubRecordType ->buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) : BuildPubSubRecordType +>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) : BuildPubSubRecordType >buildPubSubRecordType : (soFar: SO_FAR) => BuildPubSubRecordType ->Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString; } +>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; } >Object.assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } >Object : ObjectConstructor >assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } @@ -144,9 +144,9 @@ export enum PubSubRecordIsStoredInRedisAsA { buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) as >buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) as BuildPubSubRecordType : BuildPubSubRecordType ->buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) : BuildPubSubRecordType +>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) : BuildPubSubRecordType >buildPubSubRecordType : (soFar: SO_FAR) => BuildPubSubRecordType ->Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.redisHash; } +>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; } >Object.assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } >Object : ObjectConstructor >assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } @@ -337,16 +337,16 @@ export enum PubSubRecordIsStoredInRedisAsA { buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType, >buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType : BuildPubSubRecordType ->buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) : BuildPubSubRecordType +>buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) : BuildPubSubRecordType >buildPubSubRecordType : (soFar: SO_FAR) => BuildPubSubRecordType ->Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0}) : SO_FAR & { maxMsToWaitBeforePublishing: 0; } +>Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0}) : SO_FAR & { maxMsToWaitBeforePublishing: number; } >Object.assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } >Object : ObjectConstructor >assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } >{} : {} >soFar : SO_FAR ->{maxMsToWaitBeforePublishing: 0} : { maxMsToWaitBeforePublishing: 0; } ->maxMsToWaitBeforePublishing : 0 +>{maxMsToWaitBeforePublishing: 0} : { maxMsToWaitBeforePublishing: number; } +>maxMsToWaitBeforePublishing : number >0 : 0 >maxMsToWaitBeforePublishing : 0 } diff --git a/tests/baselines/reference/objectSpread.types b/tests/baselines/reference/objectSpread.types index e095a4e03bb1c..0ffd5dd20b718 100644 --- a/tests/baselines/reference/objectSpread.types +++ b/tests/baselines/reference/objectSpread.types @@ -602,7 +602,7 @@ let exclusive: { id: string, a: number, b: string, c: string, d: boolean } = >d : boolean f({ a: 1, b: 'yes' }, { c: 'no', d: false }) ->f({ a: 1, b: 'yes' }, { c: 'no', d: false }) : { a: number; b: string; } & { c: string; d: false; } & { id: string; } +>f({ a: 1, b: 'yes' }, { c: 'no', d: false }) : { a: number; b: string; } & { c: string; d: boolean; } & { id: string; } >f : (t: T, u: U) => T & U & { id: string; } >{ a: 1, b: 'yes' } : { a: number; b: string; } >a : number diff --git a/tests/baselines/reference/unionAndIntersectionInference1.js b/tests/baselines/reference/unionAndIntersectionInference1.js index ca9e527d99ed2..fe4d76781d0f4 100644 --- a/tests/baselines/reference/unionAndIntersectionInference1.js +++ b/tests/baselines/reference/unionAndIntersectionInference1.js @@ -85,10 +85,16 @@ const createTest = (): ITest => { } declare function f1(x: T | U): T | U; -declare function f2(x: T & U): T & U; +declare function f2(x: T, y: U): T | U; let x1: string = f1('a'); -let x2: string = f2('a'); +let x2: string = f2('a', 'b'); + +// Repro from #30442 + +const func = () => {}; +const assign = (a: T, b: U) => Object.assign(a, b); +const res: (() => void) & { func: any } = assign(() => {}, { func }); //// [unionAndIntersectionInference1.js] @@ -134,4 +140,8 @@ const createTest = () => { return { name: 'test' }; }; let x1 = f1('a'); -let x2 = f2('a'); +let x2 = f2('a', 'b'); +// Repro from #30442 +const func = () => { }; +const assign = (a, b) => Object.assign(a, b); +const res = assign(() => { }, { func }); diff --git a/tests/baselines/reference/unionAndIntersectionInference1.symbols b/tests/baselines/reference/unionAndIntersectionInference1.symbols index e866f19daa423..9b4e7244cfd98 100644 --- a/tests/baselines/reference/unionAndIntersectionInference1.symbols +++ b/tests/baselines/reference/unionAndIntersectionInference1.symbols @@ -239,12 +239,13 @@ declare function f1(x: T | U): T | U; >T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 85, 20)) >U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 85, 22)) -declare function f2(x: T & U): T & U; +declare function f2(x: T, y: U): T | U; >f2 : Symbol(f2, Decl(unionAndIntersectionInference1.ts, 85, 43)) >T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 86, 20)) >U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 86, 22)) >x : Symbol(x, Decl(unionAndIntersectionInference1.ts, 86, 26)) >T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 86, 20)) +>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 86, 31)) >U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 86, 22)) >T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 86, 20)) >U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 86, 22)) @@ -253,7 +254,33 @@ let x1: string = f1('a'); >x1 : Symbol(x1, Decl(unionAndIntersectionInference1.ts, 88, 3)) >f1 : Symbol(f1, Decl(unionAndIntersectionInference1.ts, 83, 1)) -let x2: string = f2('a'); +let x2: string = f2('a', 'b'); >x2 : Symbol(x2, Decl(unionAndIntersectionInference1.ts, 89, 3)) >f2 : Symbol(f2, Decl(unionAndIntersectionInference1.ts, 85, 43)) +// Repro from #30442 + +const func = () => {}; +>func : Symbol(func, Decl(unionAndIntersectionInference1.ts, 93, 5)) +>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 93, 14)) + +const assign = (a: T, b: U) => Object.assign(a, b); +>assign : Symbol(assign, Decl(unionAndIntersectionInference1.ts, 94, 5)) +>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 94, 16)) +>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 94, 18)) +>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 94, 22)) +>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 94, 16)) +>b : Symbol(b, Decl(unionAndIntersectionInference1.ts, 94, 27)) +>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 94, 18)) +>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, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>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, --, --)) +>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 94, 22)) +>b : Symbol(b, Decl(unionAndIntersectionInference1.ts, 94, 27)) + +const res: (() => void) & { func: any } = assign(() => {}, { func }); +>res : Symbol(res, Decl(unionAndIntersectionInference1.ts, 95, 5)) +>func : Symbol(func, Decl(unionAndIntersectionInference1.ts, 95, 27)) +>assign : Symbol(assign, Decl(unionAndIntersectionInference1.ts, 94, 5)) +>func : Symbol(func, Decl(unionAndIntersectionInference1.ts, 95, 60)) + diff --git a/tests/baselines/reference/unionAndIntersectionInference1.types b/tests/baselines/reference/unionAndIntersectionInference1.types index 4dddcb8bd0f32..72d60545e6a51 100644 --- a/tests/baselines/reference/unionAndIntersectionInference1.types +++ b/tests/baselines/reference/unionAndIntersectionInference1.types @@ -216,9 +216,10 @@ declare function f1(x: T | U): T | U; >f1 : (x: T | U) => T | U >x : T | U -declare function f2(x: T & U): T & U; ->f2 : (x: T & U) => T & U ->x : T & U +declare function f2(x: T, y: U): T | U; +>f2 : (x: T, y: U) => T | U +>x : T +>y : U let x1: string = f1('a'); >x1 : string @@ -226,9 +227,37 @@ let x1: string = f1('a'); >f1 : (x: T | U) => T | U >'a' : "a" -let x2: string = f2('a'); +let x2: string = f2('a', 'b'); >x2 : string ->f2('a') : "a" ->f2 : (x: T & U) => T & U +>f2('a', 'b') : "a" | "b" +>f2 : (x: T, y: U) => T | U >'a' : "a" +>'b' : "b" + +// Repro from #30442 + +const func = () => {}; +>func : () => void +>() => {} : () => void + +const assign = (a: T, b: U) => Object.assign(a, b); +>assign : (a: T, b: U) => T & U +>(a: T, b: U) => Object.assign(a, b) : (a: T, b: U) => T & U +>a : T +>b : U +>Object.assign(a, b) : T & U +>Object.assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } +>Object : ObjectConstructor +>assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } +>a : T +>b : U + +const res: (() => void) & { func: any } = assign(() => {}, { func }); +>res : (() => void) & { func: any; } +>func : any +>assign(() => {}, { func }) : (() => void) & { func: () => void; } +>assign : (a: T, b: U) => T & U +>() => {} : () => void +>{ func } : { func: () => void; } +>func : () => void diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts index d4c7d25615e46..18de9c60f2c2f 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts @@ -86,7 +86,13 @@ const createTest = (): ITest => { } declare function f1(x: T | U): T | U; -declare function f2(x: T & U): T & U; +declare function f2(x: T, y: U): T | U; let x1: string = f1('a'); -let x2: string = f2('a'); +let x2: string = f2('a', 'b'); + +// Repro from #30442 + +const func = () => {}; +const assign = (a: T, b: U) => Object.assign(a, b); +const res: (() => void) & { func: any } = assign(() => {}, { func });