-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Improved union/intersection type inference #5738
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6086,6 +6086,17 @@ namespace ts { | |
} | ||
|
||
function inferFromTypes(source: Type, target: Type) { | ||
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union || | ||
source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) { | ||
// Source and target are both unions or both intersections. To improve the quality of | ||
// inferences we first reduce the types by removing constituents that are identically | ||
// matched by a constituent in the other type. For example, when inferring from | ||
// 'string | string[]' to 'string | T', we reduce the types to 'string[]' and 'T'. | ||
const reducedSource = reduceUnionOrIntersectionType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target); | ||
const reducedTarget = reduceUnionOrIntersectionType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>source); | ||
source = reducedSource; | ||
target = reducedTarget; | ||
} | ||
if (target.flags & TypeFlags.TypeParameter) { | ||
// If target is a type parameter, make an inference, unless the source type contains | ||
// the anyFunctionType (the wildcard type that's used to avoid contextually typing functions). | ||
|
@@ -6096,7 +6107,6 @@ namespace ts { | |
if (source.flags & TypeFlags.ContainsAnyFunctionType) { | ||
return; | ||
} | ||
|
||
const typeParameters = context.typeParameters; | ||
for (let i = 0; i < typeParameters.length; i++) { | ||
if (target === typeParameters[i]) { | ||
|
@@ -6244,6 +6254,41 @@ namespace ts { | |
} | ||
} | ||
|
||
function typeIdenticalToSomeType(source: Type, target: UnionOrIntersectionType): boolean { | ||
for (const t of target.types) { | ||
if (isTypeIdenticalTo(source, t)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Return the reduced form of the source type. This type is computed by by removing all source | ||
* constituents that have an identical match in the target type. | ||
*/ | ||
function reduceUnionOrIntersectionType(source: UnionOrIntersectionType, target: UnionOrIntersectionType) { | ||
let sourceTypes = source.types; | ||
let sourceIndex = 0; | ||
let modified = false; | ||
while (sourceIndex < sourceTypes.length) { | ||
if (typeIdenticalToSomeType(sourceTypes[sourceIndex], target)) { | ||
if (!modified) { | ||
sourceTypes = sourceTypes.slice(0); | ||
modified = true; | ||
} | ||
sourceTypes.splice(sourceIndex, 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think I understand the mutability discipline for the checker. It looks like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Never mind, I just saw the call to .slice(0) above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, while it seems obvious now, it'd be worthwhile to add a quick comment so that people taking a cursory glance don't think they've found a bug. |
||
} | ||
else { | ||
sourceIndex++; | ||
} | ||
} | ||
if (modified) { | ||
return source.flags & TypeFlags.Union ? getUnionType(sourceTypes, /*noSubtypeReduction*/ true) : getIntersectionType(sourceTypes); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you break this into multiple lines? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use wordwrapping man. |
||
} | ||
return source; | ||
} | ||
|
||
function getInferenceCandidates(context: InferenceContext, index: number): Type[] { | ||
const inferences = context.inferences[index]; | ||
return inferences.primary || inferences.secondary || emptyArray; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
//// [unionAndIntersectionInference1.ts] | ||
// Repro from #2264 | ||
|
||
interface Y { 'i am a very certain type': Y } | ||
var y: Y = <Y>undefined; | ||
function destructure<a, r>( | ||
something: a | Y, | ||
haveValue: (value: a) => r, | ||
haveY: (value: Y) => r | ||
): r { | ||
return something === y ? haveY(y) : haveValue(<a>something); | ||
} | ||
|
||
var value = Math.random() > 0.5 ? 'hey!' : <Y>undefined; | ||
|
||
var result = destructure(value, text => 'string', y => 'other one'); // text: string, y: Y | ||
|
||
// Repro from #4212 | ||
|
||
function isVoid<a>(value: void | a): value is void { | ||
return undefined; | ||
} | ||
|
||
function isNonVoid<a>(value: void | a) : value is a { | ||
return undefined; | ||
} | ||
|
||
function foo1<a>(value: void|a): void { | ||
if (isVoid(value)) { | ||
value; // value is void | ||
} else { | ||
value; // value is a | ||
} | ||
} | ||
|
||
function baz1<a>(value: void|a): void { | ||
if (isNonVoid(value)) { | ||
value; // value is a | ||
} else { | ||
value; // value is void | ||
} | ||
} | ||
|
||
// Repro from #5417 | ||
|
||
type Maybe<T> = T | void; | ||
|
||
function get<U>(x: U | void): U { | ||
return null; // just an example | ||
} | ||
|
||
let foo: Maybe<string>; | ||
get(foo).toUpperCase(); // Ok | ||
|
||
// Repro from #5456 | ||
|
||
interface Man { | ||
walks: boolean; | ||
} | ||
|
||
interface Bear { | ||
roars: boolean; | ||
} | ||
|
||
interface Pig { | ||
oinks: boolean; | ||
} | ||
|
||
declare function pigify<T>(y: T & Bear): T & Pig; | ||
declare var mbp: Man & Bear; | ||
|
||
pigify(mbp).oinks; // OK, mbp is treated as Pig | ||
pigify(mbp).walks; // Ok, mbp is treated as Man | ||
|
||
|
||
//// [unionAndIntersectionInference1.js] | ||
// Repro from #2264 | ||
var y = undefined; | ||
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 | ||
// Repro from #4212 | ||
function isVoid(value) { | ||
return undefined; | ||
} | ||
function isNonVoid(value) { | ||
return undefined; | ||
} | ||
function foo1(value) { | ||
if (isVoid(value)) { | ||
value; // value is void | ||
} | ||
else { | ||
value; // value is a | ||
} | ||
} | ||
function baz1(value) { | ||
if (isNonVoid(value)) { | ||
value; // value is a | ||
} | ||
else { | ||
value; // value is void | ||
} | ||
} | ||
function get(x) { | ||
return null; // just an example | ||
} | ||
var foo; | ||
get(foo).toUpperCase(); // Ok | ||
pigify(mbp).oinks; // OK, mbp is treated as Pig | ||
pigify(mbp).walks; // Ok, mbp is treated as Man |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
=== tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts === | ||
// Repro from #2264 | ||
|
||
interface Y { 'i am a very certain type': Y } | ||
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0)) | ||
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0)) | ||
|
||
var y: Y = <Y>undefined; | ||
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 3, 3)) | ||
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0)) | ||
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0)) | ||
>undefined : Symbol(undefined) | ||
|
||
function destructure<a, r>( | ||
>destructure : Symbol(destructure, Decl(unionAndIntersectionInference1.ts, 3, 24)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21)) | ||
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23)) | ||
|
||
something: a | Y, | ||
>something : Symbol(something, Decl(unionAndIntersectionInference1.ts, 4, 27)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21)) | ||
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0)) | ||
|
||
haveValue: (value: a) => r, | ||
>haveValue : Symbol(haveValue, Decl(unionAndIntersectionInference1.ts, 5, 21)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 6, 16)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21)) | ||
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23)) | ||
|
||
haveY: (value: Y) => r | ||
>haveY : Symbol(haveY, Decl(unionAndIntersectionInference1.ts, 6, 31)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 7, 12)) | ||
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0)) | ||
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23)) | ||
|
||
): r { | ||
>r : Symbol(r, Decl(unionAndIntersectionInference1.ts, 4, 23)) | ||
|
||
return something === y ? haveY(y) : haveValue(<a>something); | ||
>something : Symbol(something, Decl(unionAndIntersectionInference1.ts, 4, 27)) | ||
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 3, 3)) | ||
>haveY : Symbol(haveY, Decl(unionAndIntersectionInference1.ts, 6, 31)) | ||
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 3, 3)) | ||
>haveValue : Symbol(haveValue, Decl(unionAndIntersectionInference1.ts, 5, 21)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 4, 21)) | ||
>something : Symbol(something, Decl(unionAndIntersectionInference1.ts, 4, 27)) | ||
} | ||
|
||
var value = Math.random() > 0.5 ? 'hey!' : <Y>undefined; | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 12, 3)) | ||
>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) | ||
>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) | ||
>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) | ||
>Y : Symbol(Y, Decl(unionAndIntersectionInference1.ts, 0, 0)) | ||
>undefined : Symbol(undefined) | ||
|
||
var result = destructure(value, text => 'string', y => 'other one'); // text: string, y: Y | ||
>result : Symbol(result, Decl(unionAndIntersectionInference1.ts, 14, 3)) | ||
>destructure : Symbol(destructure, Decl(unionAndIntersectionInference1.ts, 3, 24)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 12, 3)) | ||
>text : Symbol(text, Decl(unionAndIntersectionInference1.ts, 14, 31)) | ||
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 14, 49)) | ||
|
||
// Repro from #4212 | ||
|
||
function isVoid<a>(value: void | a): value is void { | ||
>isVoid : Symbol(isVoid, Decl(unionAndIntersectionInference1.ts, 14, 68)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 18, 16)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 18, 19)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 18, 16)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 18, 19)) | ||
|
||
return undefined; | ||
>undefined : Symbol(undefined) | ||
} | ||
|
||
function isNonVoid<a>(value: void | a) : value is a { | ||
>isNonVoid : Symbol(isNonVoid, Decl(unionAndIntersectionInference1.ts, 20, 1)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 22, 19)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 22, 22)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 22, 19)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 22, 22)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 22, 19)) | ||
|
||
return undefined; | ||
>undefined : Symbol(undefined) | ||
} | ||
|
||
function foo1<a>(value: void|a): void { | ||
>foo1 : Symbol(foo1, Decl(unionAndIntersectionInference1.ts, 24, 1)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 26, 14)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 26, 14)) | ||
|
||
if (isVoid(value)) { | ||
>isVoid : Symbol(isVoid, Decl(unionAndIntersectionInference1.ts, 14, 68)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17)) | ||
|
||
value; // value is void | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17)) | ||
|
||
} else { | ||
value; // value is a | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 26, 17)) | ||
} | ||
} | ||
|
||
function baz1<a>(value: void|a): void { | ||
>baz1 : Symbol(baz1, Decl(unionAndIntersectionInference1.ts, 32, 1)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 34, 14)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17)) | ||
>a : Symbol(a, Decl(unionAndIntersectionInference1.ts, 34, 14)) | ||
|
||
if (isNonVoid(value)) { | ||
>isNonVoid : Symbol(isNonVoid, Decl(unionAndIntersectionInference1.ts, 20, 1)) | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17)) | ||
|
||
value; // value is a | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17)) | ||
|
||
} else { | ||
value; // value is void | ||
>value : Symbol(value, Decl(unionAndIntersectionInference1.ts, 34, 17)) | ||
} | ||
} | ||
|
||
// Repro from #5417 | ||
|
||
type Maybe<T> = T | void; | ||
>Maybe : Symbol(Maybe, Decl(unionAndIntersectionInference1.ts, 40, 1)) | ||
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 44, 11)) | ||
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 44, 11)) | ||
|
||
function get<U>(x: U | void): U { | ||
>get : Symbol(get, Decl(unionAndIntersectionInference1.ts, 44, 25)) | ||
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 46, 13)) | ||
>x : Symbol(x, Decl(unionAndIntersectionInference1.ts, 46, 16)) | ||
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 46, 13)) | ||
>U : Symbol(U, Decl(unionAndIntersectionInference1.ts, 46, 13)) | ||
|
||
return null; // just an example | ||
} | ||
|
||
let foo: Maybe<string>; | ||
>foo : Symbol(foo, Decl(unionAndIntersectionInference1.ts, 50, 3)) | ||
>Maybe : Symbol(Maybe, Decl(unionAndIntersectionInference1.ts, 40, 1)) | ||
|
||
get(foo).toUpperCase(); // Ok | ||
>get(foo).toUpperCase : Symbol(String.toUpperCase, Decl(lib.d.ts, --, --)) | ||
>get : Symbol(get, Decl(unionAndIntersectionInference1.ts, 44, 25)) | ||
>foo : Symbol(foo, Decl(unionAndIntersectionInference1.ts, 50, 3)) | ||
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.d.ts, --, --)) | ||
|
||
// Repro from #5456 | ||
|
||
interface Man { | ||
>Man : Symbol(Man, Decl(unionAndIntersectionInference1.ts, 51, 23)) | ||
|
||
walks: boolean; | ||
>walks : Symbol(walks, Decl(unionAndIntersectionInference1.ts, 55, 15)) | ||
} | ||
|
||
interface Bear { | ||
>Bear : Symbol(Bear, Decl(unionAndIntersectionInference1.ts, 57, 1)) | ||
|
||
roars: boolean; | ||
>roars : Symbol(roars, Decl(unionAndIntersectionInference1.ts, 59, 16)) | ||
} | ||
|
||
interface Pig { | ||
>Pig : Symbol(Pig, Decl(unionAndIntersectionInference1.ts, 61, 1)) | ||
|
||
oinks: boolean; | ||
>oinks : Symbol(oinks, Decl(unionAndIntersectionInference1.ts, 63, 15)) | ||
} | ||
|
||
declare function pigify<T>(y: T & Bear): T & Pig; | ||
>pigify : Symbol(pigify, Decl(unionAndIntersectionInference1.ts, 65, 1)) | ||
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 67, 24)) | ||
>y : Symbol(y, Decl(unionAndIntersectionInference1.ts, 67, 27)) | ||
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 67, 24)) | ||
>Bear : Symbol(Bear, Decl(unionAndIntersectionInference1.ts, 57, 1)) | ||
>T : Symbol(T, Decl(unionAndIntersectionInference1.ts, 67, 24)) | ||
>Pig : Symbol(Pig, Decl(unionAndIntersectionInference1.ts, 61, 1)) | ||
|
||
declare var mbp: Man & Bear; | ||
>mbp : Symbol(mbp, Decl(unionAndIntersectionInference1.ts, 68, 11)) | ||
>Man : Symbol(Man, Decl(unionAndIntersectionInference1.ts, 51, 23)) | ||
>Bear : Symbol(Bear, Decl(unionAndIntersectionInference1.ts, 57, 1)) | ||
|
||
pigify(mbp).oinks; // OK, mbp is treated as Pig | ||
>pigify(mbp).oinks : Symbol(Pig.oinks, Decl(unionAndIntersectionInference1.ts, 63, 15)) | ||
>pigify : Symbol(pigify, Decl(unionAndIntersectionInference1.ts, 65, 1)) | ||
>mbp : Symbol(mbp, Decl(unionAndIntersectionInference1.ts, 68, 11)) | ||
>oinks : Symbol(Pig.oinks, Decl(unionAndIntersectionInference1.ts, 63, 15)) | ||
|
||
pigify(mbp).walks; // Ok, mbp is treated as Man | ||
>pigify(mbp).walks : Symbol(Man.walks, Decl(unionAndIntersectionInference1.ts, 55, 15)) | ||
>pigify : Symbol(pigify, Decl(unionAndIntersectionInference1.ts, 65, 1)) | ||
>mbp : Symbol(mbp, Decl(unionAndIntersectionInference1.ts, 68, 11)) | ||
>walks : Symbol(Man.walks, Decl(unionAndIntersectionInference1.ts, 55, 15)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely an option, but not quite as efficient. I will keep what's there.