diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 57876c871f726..9021a50ef8d60 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14473,26 +14473,15 @@ namespace ts { inferFromTypes(source, getUnionType([getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)])); } else if (target.flags & TypeFlags.UnionOrIntersection) { - const targetTypes = (target).types; - let typeVariableCount = 0; - let typeVariable: TypeParameter | IndexedAccessType | undefined; - // First infer to each type in union or intersection that isn't a type variable - for (const t of targetTypes) { + 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)) { - typeVariable = t; - typeVariableCount++; - } - else { - inferFromTypes(source, t); + priority |= InferencePriority.NakedTypeVariable; } - } - // Next, if target containings a single naked type variable, make a secondary inference to that type - // variable. This gives meaningful results for union types in co-variant positions and intersection - // types in contra-variant positions (such as callback parameters). - if (typeVariableCount === 1) { - const savePriority = priority; - priority |= InferencePriority.NakedTypeVariable; - inferFromTypes(source, typeVariable!); + inferFromTypes(source, t); priority = savePriority; } } diff --git a/tests/baselines/reference/conditionalTypeDoesntSpinForever.types b/tests/baselines/reference/conditionalTypeDoesntSpinForever.types index d4fe4bf48261b..1f4b4a62cd293 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; } +>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString; } >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; } +>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.redisHash; } >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: number; } +>Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0}) : SO_FAR & { maxMsToWaitBeforePublishing: 0; } >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: number; } ->maxMsToWaitBeforePublishing : number +>{maxMsToWaitBeforePublishing: 0} : { maxMsToWaitBeforePublishing: 0; } +>maxMsToWaitBeforePublishing : 0 >0 : 0 >maxMsToWaitBeforePublishing : 0 } diff --git a/tests/baselines/reference/jqueryInference.js b/tests/baselines/reference/jqueryInference.js index 05664a889deae..cea70bc4391dd 100644 --- a/tests/baselines/reference/jqueryInference.js +++ b/tests/baselines/reference/jqueryInference.js @@ -11,7 +11,7 @@ declare function shouldBeIdentity(p: DoNothingAlias): MyPromise; var p2 = shouldBeIdentity(p1); -var p2: MyPromise; +var p2: MyPromise; //// [jqueryInference.js] diff --git a/tests/baselines/reference/jqueryInference.symbols b/tests/baselines/reference/jqueryInference.symbols index ce3836aacd478..3d6c4f2f9609f 100644 --- a/tests/baselines/reference/jqueryInference.symbols +++ b/tests/baselines/reference/jqueryInference.symbols @@ -48,7 +48,7 @@ var p2 = shouldBeIdentity(p1); >shouldBeIdentity : Symbol(shouldBeIdentity, Decl(jqueryInference.ts, 6, 58)) >p1 : Symbol(p1, Decl(jqueryInference.ts, 10, 13)) -var p2: MyPromise; +var p2: MyPromise; >p2 : Symbol(p2, Decl(jqueryInference.ts, 11, 3), Decl(jqueryInference.ts, 12, 3)) >MyPromise : Symbol(MyPromise, Decl(jqueryInference.ts, 0, 0)) diff --git a/tests/baselines/reference/jqueryInference.types b/tests/baselines/reference/jqueryInference.types index 5f055fe16b248..558b0f53df36c 100644 --- a/tests/baselines/reference/jqueryInference.types +++ b/tests/baselines/reference/jqueryInference.types @@ -22,11 +22,11 @@ declare const p1: MyPromise; >p1 : MyPromise var p2 = shouldBeIdentity(p1); ->p2 : MyPromise ->shouldBeIdentity(p1) : MyPromise +>p2 : MyPromise +>shouldBeIdentity(p1) : MyPromise >shouldBeIdentity : (p: DoNothingAlias) => MyPromise >p1 : MyPromise -var p2: MyPromise; ->p2 : MyPromise +var p2: MyPromise; +>p2 : MyPromise diff --git a/tests/baselines/reference/objectSpread.types b/tests/baselines/reference/objectSpread.types index 0ffd5dd20b718..e095a4e03bb1c 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: boolean; } & { id: string; } +>f({ a: 1, b: 'yes' }, { c: 'no', d: false }) : { a: number; b: string; } & { c: string; d: false; } & { 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/restTupleElements1.types b/tests/baselines/reference/restTupleElements1.types index 9a8b3aaeb7ef1..ffa20bf503ede 100644 --- a/tests/baselines/reference/restTupleElements1.types +++ b/tests/baselines/reference/restTupleElements1.types @@ -173,7 +173,7 @@ f0([]); // Error >[] : never[] f0([1]); ->f0([1]) : [number, {}] +>f0([1]) : [number, number] >f0 : (x: [T, ...U[]]) => [T, U] >[1] : [number] >1 : 1 diff --git a/tests/baselines/reference/unionAndIntersectionInference1.js b/tests/baselines/reference/unionAndIntersectionInference1.js index 235eb23ebf847..ca9e527d99ed2 100644 --- a/tests/baselines/reference/unionAndIntersectionInference1.js +++ b/tests/baselines/reference/unionAndIntersectionInference1.js @@ -71,6 +71,24 @@ declare var mbp: Man & Bear; pigify(mbp).oinks; // OK, mbp is treated as Pig pigify(mbp).walks; // Ok, mbp is treated as Man + +// Repros from #29815 + +interface ITest { + name: 'test' +} + +const createTestAsync = (): Promise => Promise.resolve().then(() => ({ name: 'test' })) + +const createTest = (): ITest => { + return { name: 'test' } +} + +declare function f1(x: T | U): T | U; +declare function f2(x: T & U): T & U; + +let x1: string = f1('a'); +let x2: string = f2('a'); //// [unionAndIntersectionInference1.js] @@ -80,7 +98,7 @@ function destructure(something, haveValue, haveY) { return something === y ? haveY(y) : haveValue(something); } var value = Math.random() > 0.5 ? 'hey!' : undefined; -var result = destructure(value, function (text) { return 'string'; }, function (y) { return 'other one'; }); // text: string, y: Y +var result = destructure(value, text => 'string', y => 'other one'); // text: string, y: Y // Repro from #4212 function isVoid(value) { return undefined; @@ -107,7 +125,13 @@ function baz1(value) { function get(x) { return null; // just an example } -var foo; +let foo; get(foo).toUpperCase(); // Ok pigify(mbp).oinks; // OK, mbp is treated as Pig pigify(mbp).walks; // Ok, mbp is treated as Man +const createTestAsync = () => Promise.resolve().then(() => ({ name: 'test' })); +const createTest = () => { + return { name: 'test' }; +}; +let x1 = f1('a'); +let x2 = f2('a'); diff --git a/tests/baselines/reference/unionAndIntersectionInference1.symbols b/tests/baselines/reference/unionAndIntersectionInference1.symbols index 38bcd416dff8f..e866f19daa423 100644 --- a/tests/baselines/reference/unionAndIntersectionInference1.symbols +++ b/tests/baselines/reference/unionAndIntersectionInference1.symbols @@ -50,7 +50,7 @@ function destructure( var value = Math.random() > 0.5 ? 'hey!' : undefined; >value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 12, 3)) >Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) ->Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) >random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) >Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0)) >undefined : Symbol(undefined) @@ -201,3 +201,59 @@ pigify(mbp).walks; // Ok, mbp is treated as Man >mbp : Symbol(mbp, Decl(unionAndIntersectionInference1.ts, 68, 11)) >walks : Symbol(Man.walks, Decl(unionAndIntersectionInference1.ts, 55, 15)) +// Repros from #29815 + +interface ITest { +>ITest : Symbol(ITest, Decl(unionAndIntersectionInference1.ts, 71, 18)) + + name: 'test' +>name : Symbol(ITest.name, Decl(unionAndIntersectionInference1.ts, 75, 17)) +} + +const createTestAsync = (): Promise => Promise.resolve().then(() => ({ name: 'test' })) +>createTestAsync : Symbol(createTestAsync, Decl(unionAndIntersectionInference1.ts, 79, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>ITest : Symbol(ITest, Decl(unionAndIntersectionInference1.ts, 71, 18)) +>Promise.resolve().then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>name : Symbol(name, Decl(unionAndIntersectionInference1.ts, 79, 77)) + +const createTest = (): ITest => { +>createTest : Symbol(createTest, Decl(unionAndIntersectionInference1.ts, 81, 5)) +>ITest : Symbol(ITest, Decl(unionAndIntersectionInference1.ts, 71, 18)) + + return { name: 'test' } +>name : Symbol(name, Decl(unionAndIntersectionInference1.ts, 82, 10)) +} + +declare function f1(x: T | U): T | U; +>f1 : Symbol(f1, Decl(unionAndIntersectionInference1.ts, 83, 1)) +>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 85, 20)) +>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 85, 22)) +>x : Symbol(x, Decl(unionAndIntersectionInference1.ts, 85, 26)) +>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 85, 20)) +>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 85, 22)) +>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 85, 20)) +>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 85, 22)) + +declare function f2(x: T & 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)) +>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 86, 22)) +>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 86, 20)) +>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 86, 22)) + +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'); +>x2 : Symbol(x2, Decl(unionAndIntersectionInference1.ts, 89, 3)) +>f2 : Symbol(f2, Decl(unionAndIntersectionInference1.ts, 85, 43)) + diff --git a/tests/baselines/reference/unionAndIntersectionInference1.types b/tests/baselines/reference/unionAndIntersectionInference1.types index 7b2515c6b581f..4dddcb8bd0f32 100644 --- a/tests/baselines/reference/unionAndIntersectionInference1.types +++ b/tests/baselines/reference/unionAndIntersectionInference1.types @@ -179,3 +179,56 @@ pigify(mbp).walks; // Ok, mbp is treated as Man >mbp : Man & Bear >walks : boolean +// Repros from #29815 + +interface ITest { + name: 'test' +>name : "test" +} + +const createTestAsync = (): Promise => Promise.resolve().then(() => ({ name: 'test' })) +>createTestAsync : () => Promise +>(): Promise => Promise.resolve().then(() => ({ name: 'test' })) : () => Promise +>Promise.resolve().then(() => ({ name: 'test' })) : Promise +>Promise.resolve().then : (onfulfilled?: (value: void) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>Promise.resolve() : Promise +>Promise.resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>Promise : PromiseConstructor +>resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>then : (onfulfilled?: (value: void) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>() => ({ name: 'test' }) : () => { name: "test"; } +>({ name: 'test' }) : { name: "test"; } +>{ name: 'test' } : { name: "test"; } +>name : "test" +>'test' : "test" + +const createTest = (): ITest => { +>createTest : () => ITest +>(): ITest => { return { name: 'test' }} : () => ITest + + return { name: 'test' } +>{ name: 'test' } : { name: "test"; } +>name : "test" +>'test' : "test" +} + +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 + +let x1: string = f1('a'); +>x1 : string +>f1('a') : "a" +>f1 : (x: T | U) => T | U +>'a' : "a" + +let x2: string = f2('a'); +>x2 : string +>f2('a') : "a" +>f2 : (x: T & U) => T & U +>'a' : "a" + diff --git a/tests/cases/compiler/jqueryInference.ts b/tests/cases/compiler/jqueryInference.ts index 6016591625789..5784638e453ba 100644 --- a/tests/cases/compiler/jqueryInference.ts +++ b/tests/cases/compiler/jqueryInference.ts @@ -10,4 +10,4 @@ declare function shouldBeIdentity(p: DoNothingAlias): MyPromise; var p2 = shouldBeIdentity(p1); -var p2: MyPromise; +var p2: MyPromise; diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts index 7066c3e679084..d4c7d25615e46 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts @@ -1,3 +1,5 @@ +// @target: es2015 + // Repro from #2264 interface Y { 'i am a very certain type': Y } @@ -70,3 +72,21 @@ declare var mbp: Man & Bear; pigify(mbp).oinks; // OK, mbp is treated as Pig pigify(mbp).walks; // Ok, mbp is treated as Man + +// Repros from #29815 + +interface ITest { + name: 'test' +} + +const createTestAsync = (): Promise => Promise.resolve().then(() => ({ name: 'test' })) + +const createTest = (): ITest => { + return { name: 'test' } +} + +declare function f1(x: T | U): T | U; +declare function f2(x: T & U): T & U; + +let x1: string = f1('a'); +let x2: string = f2('a');