Skip to content

Commit

Permalink
Merge pull request #30857 from Microsoft/fixInferenceToIntersection (#…
Browse files Browse the repository at this point in the history
…30968)

Fix inference to intersections
  • Loading branch information
RyanCavanaugh committed Apr 16, 2019
1 parent e0f136b commit f0de46b
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 28 deletions.
27 changes: 21 additions & 6 deletions src/compiler/checker.ts
Expand Up @@ -14709,15 +14709,30 @@ namespace ts {
inferFromTypes(source, (<ConditionalType>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 (<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)) {
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<string> to T | Promise<T>,
// we want to infer string for T, not Promise<string> | 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 (<UnionOrIntersectionType>target).types) {
if (getInferenceInfoForType(t)) {
inferFromTypes(source, t);
}
}
inferFromTypes(source, t);
priority = savePriority;
}
}
Expand Down
16 changes: 8 additions & 8 deletions tests/baselines/reference/conditionalTypeDoesntSpinForever.types
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.jsonEncodedRedisString; }>
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }>
>buildPubSubRecordType : <SO_FAR>(soFar: SO_FAR) => BuildPubSubRecordType<SO_FAR>
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString; }
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }
>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.redisHash; }>
>buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) : BuildPubSubRecordType<SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }>
>buildPubSubRecordType : <SO_FAR>(soFar: SO_FAR) => BuildPubSubRecordType<SO_FAR>
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA.redisHash; }
>Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}) : SO_FAR & { storedAs: PubSubRecordIsStoredInRedisAsA; }
>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: 0; }>
>buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) : BuildPubSubRecordType<SO_FAR & { maxMsToWaitBeforePublishing: number; }>
>buildPubSubRecordType : <SO_FAR>(soFar: SO_FAR) => BuildPubSubRecordType<SO_FAR>
>Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0}) : SO_FAR & { maxMsToWaitBeforePublishing: 0; }
>Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0}) : SO_FAR & { maxMsToWaitBeforePublishing: number; }
>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: 0; }
>maxMsToWaitBeforePublishing : 0
>{maxMsToWaitBeforePublishing: 0} : { maxMsToWaitBeforePublishing: number; }
>maxMsToWaitBeforePublishing : number
>0 : 0
>maxMsToWaitBeforePublishing : 0
}
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/objectSpread.types
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: false; } & { id: string; }
>f({ a: 1, b: 'yes' }, { c: 'no', d: false }) : { a: number; b: string; } & { c: string; d: boolean; } & { 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
16 changes: 13 additions & 3 deletions tests/baselines/reference/unionAndIntersectionInference1.js
Expand Up @@ -85,10 +85,16 @@ const createTest = (): ITest => {
}

declare function f1<T, U>(x: T | U): T | U;
declare function f2<T, U>(x: T & U): T & U;
declare function f2<T, U>(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 = <T>() => {};
const assign = <T, U>(a: T, b: U) => Object.assign(a, b);
const res: (() => void) & { func: any } = assign(() => {}, { func });


//// [unionAndIntersectionInference1.js]
Expand Down Expand Up @@ -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 });
31 changes: 29 additions & 2 deletions tests/baselines/reference/unionAndIntersectionInference1.symbols
Expand Up @@ -239,12 +239,13 @@ declare function f1<T, U>(x: T | U): T | U;
>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;
declare function f2<T, U>(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))
Expand All @@ -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 = <T>() => {};
>func : Symbol(func, Decl(unionAndIntersectionInference1.ts, 93, 5))
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 93, 14))

const assign = <T, U>(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))

41 changes: 35 additions & 6 deletions tests/baselines/reference/unionAndIntersectionInference1.types
Expand Up @@ -216,19 +216,48 @@ 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
declare function f2<T, U>(x: T, y: U): T | U;
>f2 : <T, U>(x: T, y: U) => T | U
>x : T
>y : 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');
let x2: string = f2('a', 'b');
>x2 : string
>f2('a') : "a"
>f2 : <T, U>(x: T & U) => T & U
>f2('a', 'b') : "a" | "b"
>f2 : <T, U>(x: T, y: U) => T | U
>'a' : "a"
>'b' : "b"

// Repro from #30442

const func = <T>() => {};
>func : <T>() => void
><T>() => {} : <T>() => void

const assign = <T, U>(a: T, b: U) => Object.assign(a, b);
>assign : <T, U>(a: T, b: U) => T & U
><T, U>(a: T, b: U) => Object.assign(a, b) : <T, U>(a: T, b: U) => T & U
>a : T
>b : U
>Object.assign(a, b) : T & U
>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; }
>a : T
>b : U

const res: (() => void) & { func: any } = assign(() => {}, { func });
>res : (() => void) & { func: any; }
>func : any
>assign(() => {}, { func }) : (() => void) & { func: <T>() => void; }
>assign : <T, U>(a: T, b: U) => T & U
>() => {} : () => void
>{ func } : { func: <T>() => void; }
>func : <T>() => void

Expand Up @@ -86,7 +86,13 @@ const createTest = (): ITest => {
}

declare function f1<T, U>(x: T | U): T | U;
declare function f2<T, U>(x: T & U): T & U;
declare function f2<T, U>(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 = <T>() => {};
const assign = <T, U>(a: T, b: U) => Object.assign(a, b);
const res: (() => void) & { func: any } = assign(() => {}, { func });

0 comments on commit f0de46b

Please sign in to comment.