Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve inference to union and intersection types #29847

Merged
merged 7 commits into from
Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 7 additions & 18 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14473,26 +14473,15 @@ namespace ts {
inferFromTypes(source, getUnionType([getTrueTypeFromConditionalType(<ConditionalType>target), getFalseTypeFromConditionalType(<ConditionalType>target)]));
}
else if (target.flags & TypeFlags.UnionOrIntersection) {
const targetTypes = (<UnionOrIntersectionType>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 (<UnionOrIntersectionType>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<string> to T | Promise<T>,
// we want to infer string for T, not Promise<string> | string.
if (getInferenceInfoForType(t)) {
typeVariable = <InstantiableType>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;
}
}
Expand Down
16 changes: 8 additions & 8 deletions tests/baselines/reference/conditionalTypeDoesntSpinForever.types
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ export enum PubSubRecordIsStoredInRedisAsA {

buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) as
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) as BuildPubSubRecordType<SO_FAR & {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}> : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString; }>
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }>
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString; }>
>buildPubSubRecordType : <SO_FAR>(soFar: SO_FAR) => BuildPubSubRecordType<SO_FAR>
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString; }
>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; }
>Object : ObjectConstructor
>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; }
Expand All @@ -144,9 +144,9 @@ export enum PubSubRecordIsStoredInRedisAsA {

buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) as
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) as BuildPubSubRecordType<SO_FAR & {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}> : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.redisHash; }>
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }>
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.redisHash; }>
>buildPubSubRecordType : <SO_FAR>(soFar: SO_FAR) => BuildPubSubRecordType<SO_FAR>
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.redisHash; }
>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; }
>Object : ObjectConstructor
>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; }
Expand Down Expand Up @@ -337,16 +337,16 @@ export enum PubSubRecordIsStoredInRedisAsA {

buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: 0}>,
>buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: 0}> : BuildPubSubRecordType<SO_FAR & { maxMsToWaitBeforePublishing: 0; }>
>buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) : BuildPubSubRecordType<SO_FAR & { maxMsToWaitBeforePublishing: number; }>
>buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) : BuildPubSubRecordType<SO_FAR & { maxMsToWaitBeforePublishing: 0; }>
>buildPubSubRecordType : <SO_FAR>(soFar: SO_FAR) => BuildPubSubRecordType<SO_FAR>
>Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0}) : SO_FAR & { maxMsToWaitBeforePublishing: number; }
>Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0}) : SO_FAR & { maxMsToWaitBeforePublishing: 0; }
>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; }
>Object : ObjectConstructor
>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; }
>{} : {}
>soFar : SO_FAR
>{maxMsToWaitBeforePublishing: 0} : { maxMsToWaitBeforePublishing: number; }
>maxMsToWaitBeforePublishing : number
>{maxMsToWaitBeforePublishing: 0} : { maxMsToWaitBeforePublishing: 0; }
>maxMsToWaitBeforePublishing : 0
>0 : 0
>maxMsToWaitBeforePublishing : 0
}
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/jqueryInference.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ declare function shouldBeIdentity<T, U>(p: DoNothingAlias<T, U>): MyPromise<T, U

declare const p1: MyPromise<boolean, any>;
var p2 = shouldBeIdentity(p1);
var p2: MyPromise<boolean, {}>;
var p2: MyPromise<boolean, any>;


//// [jqueryInference.js]
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/jqueryInference.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean, {}>;
var p2: MyPromise<boolean, any>;
>p2 : Symbol(p2, Decl(jqueryInference.ts, 11, 3), Decl(jqueryInference.ts, 12, 3))
>MyPromise : Symbol(MyPromise, Decl(jqueryInference.ts, 0, 0))

8 changes: 4 additions & 4 deletions tests/baselines/reference/jqueryInference.types
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ declare const p1: MyPromise<boolean, any>;
>p1 : MyPromise<boolean, any>

var p2 = shouldBeIdentity(p1);
>p2 : MyPromise<boolean, {}>
>shouldBeIdentity(p1) : MyPromise<boolean, {}>
>p2 : MyPromise<boolean, any>
>shouldBeIdentity(p1) : MyPromise<boolean, any>
>shouldBeIdentity : <T, U>(p: DoNothingAlias<T, U>) => MyPromise<T, U>
>p1 : MyPromise<boolean, any>

var p2: MyPromise<boolean, {}>;
>p2 : MyPromise<boolean, {}>
var p2: MyPromise<boolean, any>;
>p2 : MyPromise<boolean, any>

2 changes: 1 addition & 1 deletion tests/baselines/reference/objectSpread.types
Original file line number Diff line number Diff line change
Expand Up @@ -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, U>(t: T, u: U) => T & U & { id: string; }
>{ a: 1, b: 'yes' } : { a: number; b: string; }
>a : number
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/restTupleElements1.types
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ f0([]); // Error
>[] : never[]

f0([1]);
>f0([1]) : [number, {}]
>f0([1]) : [number, number]
>f0 : <T, U>(x: [T, ...U[]]) => [T, U]
>[1] : [number]
>1 : 1
Expand Down
28 changes: 26 additions & 2 deletions tests/baselines/reference/unionAndIntersectionInference1.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<ITest> => Promise.resolve().then(() => ({ name: 'test' }))

const createTest = (): ITest => {
return { name: 'test' }
}

declare function f1<T, U>(x: T | U): T | U;
declare function f2<T, U>(x: T & U): T & U;

let x1: string = f1('a');
let x2: string = f2('a');


//// [unionAndIntersectionInference1.js]
Expand All @@ -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;
Expand All @@ -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');
58 changes: 57 additions & 1 deletion tests/baselines/reference/unionAndIntersectionInference1.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function destructure<a, r>(
var value = Math.random() > 0.5 ? 'hey!' : <Y>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)
Expand Down Expand Up @@ -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<ITest> => 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<T, U>(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<T, U>(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))

53 changes: 53 additions & 0 deletions tests/baselines/reference/unionAndIntersectionInference1.types
Original file line number Diff line number Diff line change
Expand Up @@ -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<ITest> => Promise.resolve().then(() => ({ name: 'test' }))
>createTestAsync : () => Promise<ITest>
>(): Promise<ITest> => Promise.resolve().then(() => ({ name: 'test' })) : () => Promise<ITest>
>Promise.resolve().then(() => ({ name: 'test' })) : Promise<ITest | { name: "test"; }>
>Promise.resolve().then : <TResult1 = void, TResult2 = never>(onfulfilled?: (value: void) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
>Promise.resolve() : Promise<void>
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
>Promise : PromiseConstructor
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
>then : <TResult1 = void, TResult2 = never>(onfulfilled?: (value: void) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
>() => ({ 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<T, U>(x: T | U): T | U;
>f1 : <T, U>(x: T | U) => T | U
>x : T | U

declare function f2<T, U>(x: T & U): T & U;
>f2 : <T, U>(x: T & U) => T & U
>x : T & U

let x1: string = f1('a');
>x1 : string
>f1('a') : "a"
>f1 : <T, U>(x: T | U) => T | U
>'a' : "a"

let x2: string = f2('a');
>x2 : string
>f2('a') : "a"
>f2 : <T, U>(x: T & U) => T & U
>'a' : "a"

2 changes: 1 addition & 1 deletion tests/cases/compiler/jqueryInference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ declare function shouldBeIdentity<T, U>(p: DoNothingAlias<T, U>): MyPromise<T, U

declare const p1: MyPromise<boolean, any>;
var p2 = shouldBeIdentity(p1);
var p2: MyPromise<boolean, {}>;
var p2: MyPromise<boolean, any>;
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @target: es2015

// Repro from #2264

interface Y { 'i am a very certain type': Y }
Expand Down Expand Up @@ -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<ITest> => Promise.resolve().then(() => ({ name: 'test' }))

const createTest = (): ITest => {
return { name: 'test' }
}

declare function f1<T, U>(x: T | U): T | U;
declare function f2<T, U>(x: T & U): T & U;

let x1: string = f1('a');
let x2: string = f2('a');