diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 99f60ee15cefd..83d89420a7788 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6085,6 +6085,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(source, target); + const reducedTarget = reduceUnionOrIntersectionType(target, 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). @@ -6095,7 +6106,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]) { @@ -6243,6 +6253,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); + } + else { + sourceIndex++; + } + } + if (modified) { + return source.flags & TypeFlags.Union ? getUnionType(sourceTypes, /*noSubtypeReduction*/ true) : getIntersectionType(sourceTypes); + } + return source; + } + function getInferenceCandidates(context: InferenceContext, index: number): Type[] { const inferences = context.inferences[index]; return inferences.primary || inferences.secondary || emptyArray; diff --git a/tests/baselines/reference/unionAndIntersectionInference1.js b/tests/baselines/reference/unionAndIntersectionInference1.js new file mode 100644 index 0000000000000..235eb23ebf847 --- /dev/null +++ b/tests/baselines/reference/unionAndIntersectionInference1.js @@ -0,0 +1,113 @@ +//// [unionAndIntersectionInference1.ts] +// Repro from #2264 + +interface Y { 'i am a very certain type': Y } +var y: Y = undefined; +function destructure( + something: a | Y, + haveValue: (value: a) => r, + haveY: (value: Y) => r +): r { + return something === y ? haveY(y) : haveValue(something); +} + +var value = Math.random() > 0.5 ? 'hey!' : undefined; + +var result = destructure(value, text => 'string', y => 'other one'); // text: string, y: Y + +// Repro from #4212 + +function isVoid(value: void | a): value is void { + return undefined; +} + +function isNonVoid(value: void | a) : value is a { + return undefined; +} + +function foo1(value: void|a): void { + if (isVoid(value)) { + value; // value is void + } else { + value; // value is a + } +} + +function baz1(value: void|a): void { + if (isNonVoid(value)) { + value; // value is a + } else { + value; // value is void + } +} + +// Repro from #5417 + +type Maybe = T | void; + +function get(x: U | void): U { + return null; // just an example +} + +let foo: Maybe; +get(foo).toUpperCase(); // Ok + +// Repro from #5456 + +interface Man { + walks: boolean; +} + +interface Bear { + roars: boolean; +} + +interface Pig { + oinks: boolean; +} + +declare function pigify(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 diff --git a/tests/baselines/reference/unionAndIntersectionInference1.symbols b/tests/baselines/reference/unionAndIntersectionInference1.symbols new file mode 100644 index 0000000000000..5e96790ea3470 --- /dev/null +++ b/tests/baselines/reference/unionAndIntersectionInference1.symbols @@ -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 = 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( +>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(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!' : 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(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(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(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(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 | 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(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; +>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(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)) + diff --git a/tests/baselines/reference/unionAndIntersectionInference1.types b/tests/baselines/reference/unionAndIntersectionInference1.types new file mode 100644 index 0000000000000..5d23688f0b7c3 --- /dev/null +++ b/tests/baselines/reference/unionAndIntersectionInference1.types @@ -0,0 +1,226 @@ +=== tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts === +// Repro from #2264 + +interface Y { 'i am a very certain type': Y } +>Y : Y +>Y : Y + +var y: Y = undefined; +>y : Y +>Y : Y +>undefined : Y +>Y : Y +>undefined : undefined + +function destructure( +>destructure : (something: a | Y, haveValue: (value: a) => r, haveY: (value: Y) => r) => r +>a : a +>r : r + + something: a | Y, +>something : a | Y +>a : a +>Y : Y + + haveValue: (value: a) => r, +>haveValue : (value: a) => r +>value : a +>a : a +>r : r + + haveY: (value: Y) => r +>haveY : (value: Y) => r +>value : Y +>Y : Y +>r : r + +): r { +>r : r + + return something === y ? haveY(y) : haveValue(something); +>something === y ? haveY(y) : haveValue(something) : r +>something === y : boolean +>something : a | Y +>y : Y +>haveY(y) : r +>haveY : (value: Y) => r +>y : Y +>haveValue(something) : r +>haveValue : (value: a) => r +>something : a +>a : a +>something : a | Y +} + +var value = Math.random() > 0.5 ? 'hey!' : undefined; +>value : string | Y +>Math.random() > 0.5 ? 'hey!' : undefined : string | Y +>Math.random() > 0.5 : boolean +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>0.5 : number +>'hey!' : string +>undefined : Y +>Y : Y +>undefined : undefined + +var result = destructure(value, text => 'string', y => 'other one'); // text: string, y: Y +>result : string +>destructure(value, text => 'string', y => 'other one') : string +>destructure : (something: a | Y, haveValue: (value: a) => r, haveY: (value: Y) => r) => r +>value : string | Y +>text => 'string' : (text: string) => string +>text : string +>'string' : string +>y => 'other one' : (y: Y) => string +>y : Y +>'other one' : string + +// Repro from #4212 + +function isVoid(value: void | a): value is void { +>isVoid : (value: void | a) => value is void +>a : a +>value : void | a +>a : a +>value : any + + return undefined; +>undefined : undefined +} + +function isNonVoid(value: void | a) : value is a { +>isNonVoid : (value: void | a) => value is a +>a : a +>value : void | a +>a : a +>value : any +>a : a + + return undefined; +>undefined : undefined +} + +function foo1(value: void|a): void { +>foo1 : (value: void | a) => void +>a : a +>value : void | a +>a : a + + if (isVoid(value)) { +>isVoid(value) : boolean +>isVoid : (value: void | a) => value is void +>value : void | a + + value; // value is void +>value : void + + } else { + value; // value is a +>value : a + } +} + +function baz1(value: void|a): void { +>baz1 : (value: void | a) => void +>a : a +>value : void | a +>a : a + + if (isNonVoid(value)) { +>isNonVoid(value) : boolean +>isNonVoid : (value: void | a) => value is a +>value : void | a + + value; // value is a +>value : a + + } else { + value; // value is void +>value : void + } +} + +// Repro from #5417 + +type Maybe = T | void; +>Maybe : T | void +>T : T +>T : T + +function get(x: U | void): U { +>get : (x: U | void) => U +>U : U +>x : U | void +>U : U +>U : U + + return null; // just an example +>null : null +} + +let foo: Maybe; +>foo : string | void +>Maybe : T | void + +get(foo).toUpperCase(); // Ok +>get(foo).toUpperCase() : string +>get(foo).toUpperCase : () => string +>get(foo) : string +>get : (x: U | void) => U +>foo : string | void +>toUpperCase : () => string + +// Repro from #5456 + +interface Man { +>Man : Man + + walks: boolean; +>walks : boolean +} + +interface Bear { +>Bear : Bear + + roars: boolean; +>roars : boolean +} + +interface Pig { +>Pig : Pig + + oinks: boolean; +>oinks : boolean +} + +declare function pigify(y: T & Bear): T & Pig; +>pigify : (y: T & Bear) => T & Pig +>T : T +>y : T & Bear +>T : T +>Bear : Bear +>T : T +>Pig : Pig + +declare var mbp: Man & Bear; +>mbp : Man & Bear +>Man : Man +>Bear : Bear + +pigify(mbp).oinks; // OK, mbp is treated as Pig +>pigify(mbp).oinks : boolean +>pigify(mbp) : Man & Pig +>pigify : (y: T & Bear) => T & Pig +>mbp : Man & Bear +>oinks : boolean + +pigify(mbp).walks; // Ok, mbp is treated as Man +>pigify(mbp).walks : boolean +>pigify(mbp) : Man & Pig +>pigify : (y: T & Bear) => T & Pig +>mbp : Man & Bear +>walks : boolean + diff --git a/tests/baselines/reference/unionAndIntersectionInference2.js b/tests/baselines/reference/unionAndIntersectionInference2.js new file mode 100644 index 0000000000000..18f08452a2ef5 --- /dev/null +++ b/tests/baselines/reference/unionAndIntersectionInference2.js @@ -0,0 +1,45 @@ +//// [unionAndIntersectionInference2.ts] +declare function f1(x: T | string): T; + +var a1: string; +var b1: string | string[]; +var c1: string[] | string; +var d1: string | { name: string }; +var e1: number | string | boolean; +f1(a1); // string +f1(b1); // string[] +f1(c1); // string[] +f1(d1); // { name: string } +f1(e1); // number | boolean + +declare function f2(x: T & { name: string }): T; + +var a2: string & { name: string }; +var b2: { name: string } & string[]; +var c2: string & { name: string } & number; +var d2: string & { name: string } & number & { name: string }; +f2(a2); // string +f2(b2); // string[] +f2(c2); // string & number +f2(d2); // string & number + + +//// [unionAndIntersectionInference2.js] +var a1; +var b1; +var c1; +var d1; +var e1; +f1(a1); // string +f1(b1); // string[] +f1(c1); // string[] +f1(d1); // { name: string } +f1(e1); // number | boolean +var a2; +var b2; +var c2; +var d2; +f2(a2); // string +f2(b2); // string[] +f2(c2); // string & number +f2(d2); // string & number diff --git a/tests/baselines/reference/unionAndIntersectionInference2.symbols b/tests/baselines/reference/unionAndIntersectionInference2.symbols new file mode 100644 index 0000000000000..24b1a36aa45e6 --- /dev/null +++ b/tests/baselines/reference/unionAndIntersectionInference2.symbols @@ -0,0 +1,85 @@ +=== tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference2.ts === +declare function f1(x: T | string): T; +>f1 : Symbol(f1, Decl(unionAndIntersectionInference2.ts, 0, 0)) +>T : Symbol(T, Decl(unionAndIntersectionInference2.ts, 0, 20)) +>x : Symbol(x, Decl(unionAndIntersectionInference2.ts, 0, 23)) +>T : Symbol(T, Decl(unionAndIntersectionInference2.ts, 0, 20)) +>T : Symbol(T, Decl(unionAndIntersectionInference2.ts, 0, 20)) + +var a1: string; +>a1 : Symbol(a1, Decl(unionAndIntersectionInference2.ts, 2, 3)) + +var b1: string | string[]; +>b1 : Symbol(b1, Decl(unionAndIntersectionInference2.ts, 3, 3)) + +var c1: string[] | string; +>c1 : Symbol(c1, Decl(unionAndIntersectionInference2.ts, 4, 3)) + +var d1: string | { name: string }; +>d1 : Symbol(d1, Decl(unionAndIntersectionInference2.ts, 5, 3)) +>name : Symbol(name, Decl(unionAndIntersectionInference2.ts, 5, 18)) + +var e1: number | string | boolean; +>e1 : Symbol(e1, Decl(unionAndIntersectionInference2.ts, 6, 3)) + +f1(a1); // string +>f1 : Symbol(f1, Decl(unionAndIntersectionInference2.ts, 0, 0)) +>a1 : Symbol(a1, Decl(unionAndIntersectionInference2.ts, 2, 3)) + +f1(b1); // string[] +>f1 : Symbol(f1, Decl(unionAndIntersectionInference2.ts, 0, 0)) +>b1 : Symbol(b1, Decl(unionAndIntersectionInference2.ts, 3, 3)) + +f1(c1); // string[] +>f1 : Symbol(f1, Decl(unionAndIntersectionInference2.ts, 0, 0)) +>c1 : Symbol(c1, Decl(unionAndIntersectionInference2.ts, 4, 3)) + +f1(d1); // { name: string } +>f1 : Symbol(f1, Decl(unionAndIntersectionInference2.ts, 0, 0)) +>d1 : Symbol(d1, Decl(unionAndIntersectionInference2.ts, 5, 3)) + +f1(e1); // number | boolean +>f1 : Symbol(f1, Decl(unionAndIntersectionInference2.ts, 0, 0)) +>e1 : Symbol(e1, Decl(unionAndIntersectionInference2.ts, 6, 3)) + +declare function f2(x: T & { name: string }): T; +>f2 : Symbol(f2, Decl(unionAndIntersectionInference2.ts, 11, 7)) +>T : Symbol(T, Decl(unionAndIntersectionInference2.ts, 13, 20)) +>x : Symbol(x, Decl(unionAndIntersectionInference2.ts, 13, 23)) +>T : Symbol(T, Decl(unionAndIntersectionInference2.ts, 13, 20)) +>name : Symbol(name, Decl(unionAndIntersectionInference2.ts, 13, 31)) +>T : Symbol(T, Decl(unionAndIntersectionInference2.ts, 13, 20)) + +var a2: string & { name: string }; +>a2 : Symbol(a2, Decl(unionAndIntersectionInference2.ts, 15, 3)) +>name : Symbol(name, Decl(unionAndIntersectionInference2.ts, 15, 18)) + +var b2: { name: string } & string[]; +>b2 : Symbol(b2, Decl(unionAndIntersectionInference2.ts, 16, 3)) +>name : Symbol(name, Decl(unionAndIntersectionInference2.ts, 16, 9)) + +var c2: string & { name: string } & number; +>c2 : Symbol(c2, Decl(unionAndIntersectionInference2.ts, 17, 3)) +>name : Symbol(name, Decl(unionAndIntersectionInference2.ts, 17, 18)) + +var d2: string & { name: string } & number & { name: string }; +>d2 : Symbol(d2, Decl(unionAndIntersectionInference2.ts, 18, 3)) +>name : Symbol(name, Decl(unionAndIntersectionInference2.ts, 18, 18)) +>name : Symbol(name, Decl(unionAndIntersectionInference2.ts, 18, 46)) + +f2(a2); // string +>f2 : Symbol(f2, Decl(unionAndIntersectionInference2.ts, 11, 7)) +>a2 : Symbol(a2, Decl(unionAndIntersectionInference2.ts, 15, 3)) + +f2(b2); // string[] +>f2 : Symbol(f2, Decl(unionAndIntersectionInference2.ts, 11, 7)) +>b2 : Symbol(b2, Decl(unionAndIntersectionInference2.ts, 16, 3)) + +f2(c2); // string & number +>f2 : Symbol(f2, Decl(unionAndIntersectionInference2.ts, 11, 7)) +>c2 : Symbol(c2, Decl(unionAndIntersectionInference2.ts, 17, 3)) + +f2(d2); // string & number +>f2 : Symbol(f2, Decl(unionAndIntersectionInference2.ts, 11, 7)) +>d2 : Symbol(d2, Decl(unionAndIntersectionInference2.ts, 18, 3)) + diff --git a/tests/baselines/reference/unionAndIntersectionInference2.types b/tests/baselines/reference/unionAndIntersectionInference2.types new file mode 100644 index 0000000000000..beeb2a261f3ec --- /dev/null +++ b/tests/baselines/reference/unionAndIntersectionInference2.types @@ -0,0 +1,94 @@ +=== tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference2.ts === +declare function f1(x: T | string): T; +>f1 : (x: T | string) => T +>T : T +>x : T | string +>T : T +>T : T + +var a1: string; +>a1 : string + +var b1: string | string[]; +>b1 : string | string[] + +var c1: string[] | string; +>c1 : string[] | string + +var d1: string | { name: string }; +>d1 : string | { name: string; } +>name : string + +var e1: number | string | boolean; +>e1 : number | string | boolean + +f1(a1); // string +>f1(a1) : string +>f1 : (x: T | string) => T +>a1 : string + +f1(b1); // string[] +>f1(b1) : string[] +>f1 : (x: T | string) => T +>b1 : string | string[] + +f1(c1); // string[] +>f1(c1) : string[] +>f1 : (x: T | string) => T +>c1 : string[] | string + +f1(d1); // { name: string } +>f1(d1) : { name: string; } +>f1 : (x: T | string) => T +>d1 : string | { name: string; } + +f1(e1); // number | boolean +>f1(e1) : number | boolean +>f1 : (x: T | string) => T +>e1 : number | string | boolean + +declare function f2(x: T & { name: string }): T; +>f2 : (x: T & { name: string; }) => T +>T : T +>x : T & { name: string; } +>T : T +>name : string +>T : T + +var a2: string & { name: string }; +>a2 : string & { name: string; } +>name : string + +var b2: { name: string } & string[]; +>b2 : { name: string; } & string[] +>name : string + +var c2: string & { name: string } & number; +>c2 : string & { name: string; } & number +>name : string + +var d2: string & { name: string } & number & { name: string }; +>d2 : string & { name: string; } & number & { name: string; } +>name : string +>name : string + +f2(a2); // string +>f2(a2) : string +>f2 : (x: T & { name: string; }) => T +>a2 : string & { name: string; } + +f2(b2); // string[] +>f2(b2) : string[] +>f2 : (x: T & { name: string; }) => T +>b2 : { name: string; } & string[] + +f2(c2); // string & number +>f2(c2) : string & number +>f2 : (x: T & { name: string; }) => T +>c2 : string & { name: string; } & number + +f2(d2); // string & number +>f2(d2) : string & number +>f2 : (x: T & { name: string; }) => T +>d2 : string & { name: string; } & number & { name: string; } + diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts new file mode 100644 index 0000000000000..7066c3e679084 --- /dev/null +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference1.ts @@ -0,0 +1,72 @@ +// Repro from #2264 + +interface Y { 'i am a very certain type': Y } +var y: Y = undefined; +function destructure( + something: a | Y, + haveValue: (value: a) => r, + haveY: (value: Y) => r +): r { + return something === y ? haveY(y) : haveValue(something); +} + +var value = Math.random() > 0.5 ? 'hey!' : undefined; + +var result = destructure(value, text => 'string', y => 'other one'); // text: string, y: Y + +// Repro from #4212 + +function isVoid(value: void | a): value is void { + return undefined; +} + +function isNonVoid(value: void | a) : value is a { + return undefined; +} + +function foo1(value: void|a): void { + if (isVoid(value)) { + value; // value is void + } else { + value; // value is a + } +} + +function baz1(value: void|a): void { + if (isNonVoid(value)) { + value; // value is a + } else { + value; // value is void + } +} + +// Repro from #5417 + +type Maybe = T | void; + +function get(x: U | void): U { + return null; // just an example +} + +let foo: Maybe; +get(foo).toUpperCase(); // Ok + +// Repro from #5456 + +interface Man { + walks: boolean; +} + +interface Bear { + roars: boolean; +} + +interface Pig { + oinks: boolean; +} + +declare function pigify(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 diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference2.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference2.ts new file mode 100644 index 0000000000000..1b6a928ce028c --- /dev/null +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference2.ts @@ -0,0 +1,23 @@ +declare function f1(x: T | string): T; + +var a1: string; +var b1: string | string[]; +var c1: string[] | string; +var d1: string | { name: string }; +var e1: number | string | boolean; +f1(a1); // string +f1(b1); // string[] +f1(c1); // string[] +f1(d1); // { name: string } +f1(e1); // number | boolean + +declare function f2(x: T & { name: string }): T; + +var a2: string & { name: string }; +var b2: { name: string } & string[]; +var c2: string & { name: string } & number; +var d2: string & { name: string } & number & { name: string }; +f2(a2); // string +f2(b2); // string[] +f2(c2); // string & number +f2(d2); // string & number