From 59e266de02979287c1b439277c0113f763483d36 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 12 Jan 2015 14:51:20 -0800 Subject: [PATCH] Improved handing of union types in type guards --- src/compiler/checker.ts | 14 +++++++--- .../reference/TypeGuardWithArrayUnion.js | 23 ++++++++++++++++ .../reference/TypeGuardWithArrayUnion.types | 26 +++++++++++++++++++ .../reference/typeGuardOfFormInstanceOf.types | 4 +-- ...typeGuardOfFormInstanceOfOnInterface.types | 4 +-- .../typeGuards/TypeGuardWithArrayUnion.ts | 9 +++++++ 6 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/TypeGuardWithArrayUnion.js create mode 100644 tests/baselines/reference/TypeGuardWithArrayUnion.types create mode 100644 tests/cases/conformance/expressions/typeGuards/TypeGuardWithArrayUnion.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 843904dd01826..bdaf64d3a54ce 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4709,13 +4709,21 @@ module ts { if (!isTypeSubtypeOf(rightType, globalFunctionType)) { return type; } + // Target type is type of prototype property var prototypeProperty = getPropertyOfType(rightType, "prototype"); if (!prototypeProperty) { return type; } - var prototypeType = getTypeOfSymbol(prototypeProperty); - // Narrow to type of prototype property if it is a subtype of current type - return isTypeSubtypeOf(prototypeType, type) ? prototypeType : type; + var targetType = getTypeOfSymbol(prototypeProperty); + // Narrow to target type if it is a subtype of current type + if (isTypeSubtypeOf(targetType, type)) { + return targetType; + } + // If current type is a union type, remove all constituents that aren't subtypes of target type + if (type.flags && TypeFlags.Union) { + return getUnionType(filter((type).types, t => isTypeSubtypeOf(t, targetType))); + } + return type; } // Narrow the given type based on the given expression having the assumed boolean value diff --git a/tests/baselines/reference/TypeGuardWithArrayUnion.js b/tests/baselines/reference/TypeGuardWithArrayUnion.js new file mode 100644 index 0000000000000..cf98d2c2453ba --- /dev/null +++ b/tests/baselines/reference/TypeGuardWithArrayUnion.js @@ -0,0 +1,23 @@ +//// [TypeGuardWithArrayUnion.ts] +class Message { + value: string; +} + +function saySize(message: Message | Message[]) { + if (message instanceof Array) { + return message.length; // Should have type Message[] here + } +} + + +//// [TypeGuardWithArrayUnion.js] +var Message = (function () { + function Message() { + } + return Message; +})(); +function saySize(message) { + if (message instanceof Array) { + return message.length; // Should have type Message[] here + } +} diff --git a/tests/baselines/reference/TypeGuardWithArrayUnion.types b/tests/baselines/reference/TypeGuardWithArrayUnion.types new file mode 100644 index 0000000000000..557e305ef74d0 --- /dev/null +++ b/tests/baselines/reference/TypeGuardWithArrayUnion.types @@ -0,0 +1,26 @@ +=== tests/cases/conformance/expressions/typeGuards/TypeGuardWithArrayUnion.ts === +class Message { +>Message : Message + + value: string; +>value : string +} + +function saySize(message: Message | Message[]) { +>saySize : (message: Message | Message[]) => number +>message : Message | Message[] +>Message : Message +>Message : Message + + if (message instanceof Array) { +>message instanceof Array : boolean +>message : Message | Message[] +>Array : ArrayConstructor + + return message.length; // Should have type Message[] here +>message.length : number +>message : Message[] +>length : number + } +} + diff --git a/tests/baselines/reference/typeGuardOfFormInstanceOf.types b/tests/baselines/reference/typeGuardOfFormInstanceOf.types index fb44482c86e33..891e8c1bea276 100644 --- a/tests/baselines/reference/typeGuardOfFormInstanceOf.types +++ b/tests/baselines/reference/typeGuardOfFormInstanceOf.types @@ -124,9 +124,9 @@ var r2: D1 | C2 = c2Ord1 instanceof C1 && c2Ord1; // C2 | D1 >r2 : C2 | D1 >D1 : D1 >C2 : C2 ->c2Ord1 instanceof C1 && c2Ord1 : C2 | D1 +>c2Ord1 instanceof C1 && c2Ord1 : D1 >c2Ord1 instanceof C1 : boolean >c2Ord1 : C2 | D1 >C1 : typeof C1 ->c2Ord1 : C2 | D1 +>c2Ord1 : D1 diff --git a/tests/baselines/reference/typeGuardOfFormInstanceOfOnInterface.types b/tests/baselines/reference/typeGuardOfFormInstanceOfOnInterface.types index 7bf67da150092..fe23d6d303e6f 100644 --- a/tests/baselines/reference/typeGuardOfFormInstanceOfOnInterface.types +++ b/tests/baselines/reference/typeGuardOfFormInstanceOfOnInterface.types @@ -154,9 +154,9 @@ var r2: D1 | C2 = c2Ord1 instanceof c1 && c2Ord1; // C2 | D1 >r2 : C2 | D1 >D1 : D1 >C2 : C2 ->c2Ord1 instanceof c1 && c2Ord1 : C2 | D1 +>c2Ord1 instanceof c1 && c2Ord1 : D1 >c2Ord1 instanceof c1 : boolean >c2Ord1 : C2 | D1 >c1 : C1 ->c2Ord1 : C2 | D1 +>c2Ord1 : D1 diff --git a/tests/cases/conformance/expressions/typeGuards/TypeGuardWithArrayUnion.ts b/tests/cases/conformance/expressions/typeGuards/TypeGuardWithArrayUnion.ts new file mode 100644 index 0000000000000..8884754b480e7 --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/TypeGuardWithArrayUnion.ts @@ -0,0 +1,9 @@ +class Message { + value: string; +} + +function saySize(message: Message | Message[]) { + if (message instanceof Array) { + return message.length; // Should have type Message[] here + } +}