diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ae7b16234fead..ac984e44ef4fb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22879,6 +22879,19 @@ namespace ts { if (assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType)) { reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types } + // try to narrow to the narrowest constituent. + if (reducedType.flags & TypeFlags.Union) { + reducedType = filterType( + reducedType, + (t) => + !some((reducedType as UnionType).types, (s) => { + if (s === t || !isTypeSubtypeOf(s, t)) { + return false; + } + return everyType(assignedType, type => !(typeMaybeAssignableTo(type, t) && !typeMaybeAssignableTo(type, s))); + }) + ); + } // Our crude heuristic produces an invalid result in some cases: see GH#26130. // For now, when that happens, we give up and don't narrow at all. (This also // means we'll never narrow for erroneous assignments where the assigned type diff --git a/tests/baselines/reference/controlFlowAssignmentExpression.js b/tests/baselines/reference/controlFlowAssignmentExpression.js index 19c9210ea7aa4..19bba27a564df 100644 --- a/tests/baselines/reference/controlFlowAssignmentExpression.js +++ b/tests/baselines/reference/controlFlowAssignmentExpression.js @@ -16,9 +16,56 @@ declare function fn(): D; let o: D; if ((o = fn()).done) { const y: 1 = o.value; -} +} + +// https://github.com/microsoft/TypeScript/issues/47731 +declare let a: object | any[] | undefined + +if (a === undefined) { + a = [] +} else if (!Array.isArray(a)) { + throw new Error() +} +[...a] // any[] + +interface Parent { + parent: string; +} +interface Child extends Parent { + child: string; +} + +declare let p: Parent; +declare let c: Child; +declare let y: Parent | Child | undefined; + +y = p; +y; // Parent + +y = c; +y; // Child + +y = undefined as any as Parent | Child; +y; // Parent | Child + +y = undefined as any as Parent | undefined; +y; // Parent | undefined + +y = undefined as any as Child | undefined; +y; // Child | undefined + //// [controlFlowAssignmentExpression.js] +"use strict"; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; var x; var obj; x = ""; @@ -29,5 +76,22 @@ x = true; x; // number var o; if ((o = fn()).done) { - var y = o.value; + var y_1 = o.value; +} +if (a === undefined) { + a = []; +} +else if (!Array.isArray(a)) { + throw new Error(); } +__spreadArray([], a, true); // any[] +y = p; +y; // Parent +y = c; +y; // Child +y = undefined; +y; // Parent | Child +y = undefined; +y; // Parent | undefined +y = undefined; +y; // Child | undefined diff --git a/tests/baselines/reference/controlFlowAssignmentExpression.symbols b/tests/baselines/reference/controlFlowAssignmentExpression.symbols index b6269056b748d..9c7ab9b08a362 100644 --- a/tests/baselines/reference/controlFlowAssignmentExpression.symbols +++ b/tests/baselines/reference/controlFlowAssignmentExpression.symbols @@ -59,3 +59,93 @@ if ((o = fn()).done) { >o : Symbol(o, Decl(controlFlowAssignmentExpression.ts, 14, 3)) >value : Symbol(value, Decl(controlFlowAssignmentExpression.ts, 12, 22)) } + +// https://github.com/microsoft/TypeScript/issues/47731 +declare let a: object | any[] | undefined +>a : Symbol(a, Decl(controlFlowAssignmentExpression.ts, 20, 11)) + +if (a === undefined) { +>a : Symbol(a, Decl(controlFlowAssignmentExpression.ts, 20, 11)) +>undefined : Symbol(undefined) + + a = [] +>a : Symbol(a, Decl(controlFlowAssignmentExpression.ts, 20, 11)) + +} else if (!Array.isArray(a)) { +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>a : Symbol(a, Decl(controlFlowAssignmentExpression.ts, 20, 11)) + + throw new Error() +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +} +[...a] // any[] +>a : Symbol(a, Decl(controlFlowAssignmentExpression.ts, 20, 11)) + +interface Parent { +>Parent : Symbol(Parent, Decl(controlFlowAssignmentExpression.ts, 27, 6)) + + parent: string; +>parent : Symbol(Parent.parent, Decl(controlFlowAssignmentExpression.ts, 29, 18)) +} +interface Child extends Parent { +>Child : Symbol(Child, Decl(controlFlowAssignmentExpression.ts, 31, 1)) +>Parent : Symbol(Parent, Decl(controlFlowAssignmentExpression.ts, 27, 6)) + + child: string; +>child : Symbol(Child.child, Decl(controlFlowAssignmentExpression.ts, 32, 32)) +} + +declare let p: Parent; +>p : Symbol(p, Decl(controlFlowAssignmentExpression.ts, 36, 11)) +>Parent : Symbol(Parent, Decl(controlFlowAssignmentExpression.ts, 27, 6)) + +declare let c: Child; +>c : Symbol(c, Decl(controlFlowAssignmentExpression.ts, 37, 11)) +>Child : Symbol(Child, Decl(controlFlowAssignmentExpression.ts, 31, 1)) + +declare let y: Parent | Child | undefined; +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) +>Parent : Symbol(Parent, Decl(controlFlowAssignmentExpression.ts, 27, 6)) +>Child : Symbol(Child, Decl(controlFlowAssignmentExpression.ts, 31, 1)) + +y = p; +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) +>p : Symbol(p, Decl(controlFlowAssignmentExpression.ts, 36, 11)) + +y; // Parent +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) + +y = c; +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) +>c : Symbol(c, Decl(controlFlowAssignmentExpression.ts, 37, 11)) + +y; // Child +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) + +y = undefined as any as Parent | Child; +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) +>undefined : Symbol(undefined) +>Parent : Symbol(Parent, Decl(controlFlowAssignmentExpression.ts, 27, 6)) +>Child : Symbol(Child, Decl(controlFlowAssignmentExpression.ts, 31, 1)) + +y; // Parent | Child +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) + +y = undefined as any as Parent | undefined; +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) +>undefined : Symbol(undefined) +>Parent : Symbol(Parent, Decl(controlFlowAssignmentExpression.ts, 27, 6)) + +y; // Parent | undefined +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) + +y = undefined as any as Child | undefined; +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) +>undefined : Symbol(undefined) +>Child : Symbol(Child, Decl(controlFlowAssignmentExpression.ts, 31, 1)) + +y; // Child | undefined +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 38, 11)) + diff --git a/tests/baselines/reference/controlFlowAssignmentExpression.types b/tests/baselines/reference/controlFlowAssignmentExpression.types index d56aee7c76727..a40969bf56500 100644 --- a/tests/baselines/reference/controlFlowAssignmentExpression.types +++ b/tests/baselines/reference/controlFlowAssignmentExpression.types @@ -76,3 +76,99 @@ if ((o = fn()).done) { >o : { done: true; value: 1; } >value : 1 } + +// https://github.com/microsoft/TypeScript/issues/47731 +declare let a: object | any[] | undefined +>a : object | any[] | undefined + +if (a === undefined) { +>a === undefined : boolean +>a : object | any[] | undefined +>undefined : undefined + + a = [] +>a = [] : never[] +>a : object | any[] | undefined +>[] : never[] + +} else if (!Array.isArray(a)) { +>!Array.isArray(a) : boolean +>Array.isArray(a) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>a : object | any[] + + throw new Error() +>new Error() : Error +>Error : ErrorConstructor +} +[...a] // any[] +>[...a] : any[] +>...a : any +>a : any[] + +interface Parent { + parent: string; +>parent : string +} +interface Child extends Parent { + child: string; +>child : string +} + +declare let p: Parent; +>p : Parent + +declare let c: Child; +>c : Child + +declare let y: Parent | Child | undefined; +>y : Parent | Child | undefined + +y = p; +>y = p : Parent +>y : Parent | Child | undefined +>p : Parent + +y; // Parent +>y : Parent + +y = c; +>y = c : Child +>y : Parent | Child | undefined +>c : Child + +y; // Child +>y : Child + +y = undefined as any as Parent | Child; +>y = undefined as any as Parent | Child : Parent | Child +>y : Parent | Child | undefined +>undefined as any as Parent | Child : Parent | Child +>undefined as any : any +>undefined : undefined + +y; // Parent | Child +>y : Parent | Child + +y = undefined as any as Parent | undefined; +>y = undefined as any as Parent | undefined : Parent | undefined +>y : Parent | Child | undefined +>undefined as any as Parent | undefined : Parent | undefined +>undefined as any : any +>undefined : undefined + +y; // Parent | undefined +>y : Parent | undefined + +y = undefined as any as Child | undefined; +>y = undefined as any as Child | undefined : Child | undefined +>y : Parent | Child | undefined +>undefined as any as Child | undefined : Child | undefined +>undefined as any : any +>undefined : undefined + +y; // Child | undefined +>y : Child | undefined + diff --git a/tests/baselines/reference/controlFlowBinaryOrExpression.symbols b/tests/baselines/reference/controlFlowBinaryOrExpression.symbols index 5251973005fcd..3c2bfaa2f7cf0 100644 --- a/tests/baselines/reference/controlFlowBinaryOrExpression.symbols +++ b/tests/baselines/reference/controlFlowBinaryOrExpression.symbols @@ -64,9 +64,9 @@ if (isNodeList(sourceObj)) { >sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3)) sourceObj.length; ->sourceObj.length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33)) +>sourceObj.length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27)) >sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3)) ->length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33)) +>length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27)) } if (isHTMLCollection(sourceObj)) { @@ -74,9 +74,9 @@ if (isHTMLCollection(sourceObj)) { >sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3)) sourceObj.length; ->sourceObj.length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33)) +>sourceObj.length : Symbol(HTMLCollection.length, Decl(controlFlowBinaryOrExpression.ts, 14, 33)) >sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3)) ->length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33)) +>length : Symbol(HTMLCollection.length, Decl(controlFlowBinaryOrExpression.ts, 14, 33)) } if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) { diff --git a/tests/baselines/reference/controlFlowBinaryOrExpression.types b/tests/baselines/reference/controlFlowBinaryOrExpression.types index 8e43d38765357..9a26352cab7b9 100644 --- a/tests/baselines/reference/controlFlowBinaryOrExpression.types +++ b/tests/baselines/reference/controlFlowBinaryOrExpression.types @@ -65,22 +65,22 @@ var sourceObj: EventTargetLike = undefined; if (isNodeList(sourceObj)) { >isNodeList(sourceObj) : boolean >isNodeList : (sourceObj: any) => sourceObj is NodeList ->sourceObj : EventTargetLike +>sourceObj : { a: string; } sourceObj.length; >sourceObj.length : number ->sourceObj : NodeList | HTMLCollection +>sourceObj : { a: string; } & NodeList >length : number } if (isHTMLCollection(sourceObj)) { >isHTMLCollection(sourceObj) : boolean >isHTMLCollection : (sourceObj: any) => sourceObj is HTMLCollection ->sourceObj : EventTargetLike +>sourceObj : { a: string; } sourceObj.length; >sourceObj.length : number ->sourceObj : NodeList | HTMLCollection +>sourceObj : { a: string; } & HTMLCollection >length : number } @@ -88,14 +88,14 @@ if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) { >isNodeList(sourceObj) || isHTMLCollection(sourceObj) : boolean >isNodeList(sourceObj) : boolean >isNodeList : (sourceObj: any) => sourceObj is NodeList ->sourceObj : EventTargetLike +>sourceObj : { a: string; } >isHTMLCollection(sourceObj) : boolean >isHTMLCollection : (sourceObj: any) => sourceObj is HTMLCollection >sourceObj : { a: string; } sourceObj.length; >sourceObj.length : number ->sourceObj : NodeList +>sourceObj : { a: string; } & NodeList >length : number } diff --git a/tests/baselines/reference/discriminantPropertyCheck.symbols b/tests/baselines/reference/discriminantPropertyCheck.symbols index 7fa10ac46e28e..833b4899e0204 100644 --- a/tests/baselines/reference/discriminantPropertyCheck.symbols +++ b/tests/baselines/reference/discriminantPropertyCheck.symbols @@ -348,34 +348,34 @@ const u: U = {} as any; >U : Symbol(U, Decl(discriminantPropertyCheck.ts, 112, 1)) u.a && u.b && f(u.a, u.b); ->u.a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>u.a : Symbol(B.a, Decl(discriminantPropertyCheck.ts, 109, 13)) >u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) ->a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) ->u.b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>a : Symbol(B.a, Decl(discriminantPropertyCheck.ts, 109, 13)) +>u.b : Symbol(B.b, Decl(discriminantPropertyCheck.ts, 110, 12)) >u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) ->b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>b : Symbol(B.b, Decl(discriminantPropertyCheck.ts, 110, 12)) >f : Symbol(f, Decl(discriminantPropertyCheck.ts, 102, 5)) ->u.a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>u.a : Symbol(B.a, Decl(discriminantPropertyCheck.ts, 109, 13)) >u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) ->a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) ->u.b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>a : Symbol(B.a, Decl(discriminantPropertyCheck.ts, 109, 13)) +>u.b : Symbol(B.b, Decl(discriminantPropertyCheck.ts, 110, 12)) >u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) ->b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>b : Symbol(B.b, Decl(discriminantPropertyCheck.ts, 110, 12)) u.b && u.a && f(u.a, u.b); ->u.b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>u.b : Symbol(B.b, Decl(discriminantPropertyCheck.ts, 110, 12)) >u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) ->b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) ->u.a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>b : Symbol(B.b, Decl(discriminantPropertyCheck.ts, 110, 12)) +>u.a : Symbol(B.a, Decl(discriminantPropertyCheck.ts, 109, 13)) >u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) ->a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>a : Symbol(B.a, Decl(discriminantPropertyCheck.ts, 109, 13)) >f : Symbol(f, Decl(discriminantPropertyCheck.ts, 102, 5)) ->u.a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>u.a : Symbol(B.a, Decl(discriminantPropertyCheck.ts, 109, 13)) >u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) ->a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) ->u.b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>a : Symbol(B.a, Decl(discriminantPropertyCheck.ts, 109, 13)) +>u.b : Symbol(B.b, Decl(discriminantPropertyCheck.ts, 110, 12)) >u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) ->b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>b : Symbol(B.b, Decl(discriminantPropertyCheck.ts, 110, 12)) // Repro from #29012 diff --git a/tests/baselines/reference/discriminantPropertyCheck.types b/tests/baselines/reference/discriminantPropertyCheck.types index 10d3b5f117c47..d984c00a2c9dd 100644 --- a/tests/baselines/reference/discriminantPropertyCheck.types +++ b/tests/baselines/reference/discriminantPropertyCheck.types @@ -343,39 +343,39 @@ const u: U = {} as any; >{} : {} u.a && u.b && f(u.a, u.b); ->u.a && u.b && f(u.a, u.b) : void | "" | undefined ->u.a && u.b : string | undefined ->u.a : string | undefined ->u : U ->a : string | undefined ->u.b : string | undefined ->u : U ->b : string | undefined +>u.a && u.b && f(u.a, u.b) : void | "" +>u.a && u.b : string +>u.a : string +>u : B +>a : string +>u.b : string +>u : B +>b : string >f(u.a, u.b) : void >f : (_a: string, _b: string) => void >u.a : string ->u : U +>u : B >a : string >u.b : string ->u : U +>u : B >b : string u.b && u.a && f(u.a, u.b); ->u.b && u.a && f(u.a, u.b) : void | "" | undefined ->u.b && u.a : string | undefined ->u.b : string | undefined ->u : U ->b : string | undefined ->u.a : string | undefined ->u : U ->a : string | undefined +>u.b && u.a && f(u.a, u.b) : void | "" +>u.b && u.a : string +>u.b : string +>u : B +>b : string +>u.a : string +>u : B +>a : string >f(u.a, u.b) : void >f : (_a: string, _b: string) => void >u.a : string ->u : U +>u : B >a : string >u.b : string ->u : U +>u : B >b : string // Repro from #29012 diff --git a/tests/cases/conformance/controlFlow/controlFlowAssignmentExpression.ts b/tests/cases/conformance/controlFlow/controlFlowAssignmentExpression.ts index 44c583a8eea8d..aba7bca20a7e9 100644 --- a/tests/cases/conformance/controlFlow/controlFlowAssignmentExpression.ts +++ b/tests/cases/conformance/controlFlow/controlFlowAssignmentExpression.ts @@ -1,3 +1,5 @@ +// @strict: true + let x: string | boolean | number; let obj: any; @@ -15,4 +17,40 @@ declare function fn(): D; let o: D; if ((o = fn()).done) { const y: 1 = o.value; -} \ No newline at end of file +} + +// https://github.com/microsoft/TypeScript/issues/47731 +declare let a: object | any[] | undefined + +if (a === undefined) { + a = [] +} else if (!Array.isArray(a)) { + throw new Error() +} +[...a] // any[] + +interface Parent { + parent: string; +} +interface Child extends Parent { + child: string; +} + +declare let p: Parent; +declare let c: Child; +declare let y: Parent | Child | undefined; + +y = p; +y; // Parent + +y = c; +y; // Child + +y = undefined as any as Parent | Child; +y; // Parent | Child + +y = undefined as any as Parent | undefined; +y; // Parent | undefined + +y = undefined as any as Child | undefined; +y; // Child | undefined