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

Fix inference to intersections #30857

Merged
merged 3 commits into from Apr 12, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 15 additions & 8 deletions src/compiler/checker.ts
Expand Up @@ -14773,22 +14773,29 @@ namespace ts {
// 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) {
if (!getInferenceInfoForType(t)) {
if (getInferenceInfoForType(t)) {
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.
const savePriority = priority;
priority |= InferencePriority.NakedTypeVariable;
for (const t of (<UnionOrIntersectionType>target).types) {
if (getInferenceInfoForType(t)) {
inferFromTypes(source, 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) {
Copy link
Member

@weswigham weswigham Apr 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old comment on this behavior was

                    // 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).

should we perhaps be checking the contravariant flag here, rather than (or perhaps alongside) count?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the contravariant flag is relevant here (or maybe I'm just not seeing how).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original in-code comment on this behavior said that it was inferences to intersections in contravariant positions (see above) that gave good results - it's possible that we could capture that sentiment by inferring to only one intersection parameter in covariant positions, but all of them in contravariant positions (and vice-versa for unions, I imagine?)

I dunno really; this is just speculation based on the text of a comment we removed, and assuming that there was insight in that comment which our tests didn't quite capture - without concrete examples, though, I'm not really sure.

const savePriority = priority;
priority |= InferencePriority.NakedTypeVariable;
for (const t of (<UnionOrIntersectionType>target).types) {
if (getInferenceInfoForType(t)) {
inferFromTypes(source, t);
}
}
priority = savePriority;
}
priority = savePriority;
}
else if (source.flags & TypeFlags.Union) {
// Source is a union or intersection type, infer from each constituent type
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 });