Merge pull request #1803 from Microsoft/enumUnionTypeGuard
Fixes to union types in type guards and instanceof
This commit is contained in:
commit
9289cfb0b0
8 changed files with 274 additions and 17 deletions
|
@ -107,6 +107,21 @@ module ts {
|
|||
var diagnostics: Diagnostic[] = [];
|
||||
var diagnosticsModified: boolean = false;
|
||||
|
||||
var primitiveTypeInfo: Map<{ type: Type; flags: TypeFlags }> = {
|
||||
"string": {
|
||||
type: stringType,
|
||||
flags: TypeFlags.StringLike
|
||||
},
|
||||
"number": {
|
||||
type: numberType,
|
||||
flags: TypeFlags.NumberLike
|
||||
},
|
||||
"boolean": {
|
||||
type: booleanType,
|
||||
flags: TypeFlags.Boolean
|
||||
}
|
||||
};
|
||||
|
||||
function addDiagnostic(diagnostic: Diagnostic) {
|
||||
diagnostics.push(diagnostic);
|
||||
diagnosticsModified = true;
|
||||
|
@ -4482,12 +4497,17 @@ module ts {
|
|||
Debug.fail("should not get here");
|
||||
}
|
||||
|
||||
// Remove one or more primitive types from a union type
|
||||
function subtractPrimitiveTypes(type: Type, subtractMask: TypeFlags): Type {
|
||||
// For a union type, remove all constituent types that are of the given type kind (when isOfTypeKind is true)
|
||||
// or not of the given type kind (when isOfTypeKind is false)
|
||||
function removeTypesFromUnionType(type: Type, typeKind: TypeFlags, isOfTypeKind: boolean): Type {
|
||||
if (type.flags & TypeFlags.Union) {
|
||||
var types = (<UnionType>type).types;
|
||||
if (forEach(types, t => t.flags & subtractMask)) {
|
||||
return getUnionType(filter(types, t => !(t.flags & subtractMask)));
|
||||
if (forEach(types, t => !!(t.flags & typeKind) === isOfTypeKind)) {
|
||||
// Above we checked if we have anything to remove, now use the opposite test to do the removal
|
||||
var narrowedType = getUnionType(filter(types, t => !(t.flags & typeKind) === isOfTypeKind));
|
||||
if (narrowedType !== emptyObjectType) {
|
||||
return narrowedType;
|
||||
}
|
||||
}
|
||||
}
|
||||
return type;
|
||||
|
@ -4663,8 +4683,8 @@ module ts {
|
|||
// Stop at the first containing function or module declaration
|
||||
break loop;
|
||||
}
|
||||
// Use narrowed type if it is a subtype and construct contains no assignments to variable
|
||||
if (narrowedType !== type && isTypeSubtypeOf(narrowedType, type)) {
|
||||
// Use narrowed type if construct contains no assignments to variable
|
||||
if (narrowedType !== type) {
|
||||
if (isVariableAssignedWithin(symbol, node)) {
|
||||
break;
|
||||
}
|
||||
|
@ -4684,20 +4704,30 @@ module ts {
|
|||
if (left.expression.kind !== SyntaxKind.Identifier || getResolvedSymbol(<Identifier>left.expression) !== symbol) {
|
||||
return type;
|
||||
}
|
||||
var t = right.text;
|
||||
var checkType: Type = t === "string" ? stringType : t === "number" ? numberType : t === "boolean" ? booleanType : emptyObjectType;
|
||||
var typeInfo = primitiveTypeInfo[right.text];
|
||||
if (expr.operator === SyntaxKind.ExclamationEqualsEqualsToken) {
|
||||
assumeTrue = !assumeTrue;
|
||||
}
|
||||
if (assumeTrue) {
|
||||
// The assumed result is true. If check was for a primitive type, that type is the narrowed type. Otherwise we can
|
||||
// remove the primitive types from the narrowed type.
|
||||
return checkType === emptyObjectType ? subtractPrimitiveTypes(type, TypeFlags.String | TypeFlags.Number | TypeFlags.Boolean) : checkType;
|
||||
// Assumed result is true. If check was not for a primitive type, remove all primitive types
|
||||
if (!typeInfo) {
|
||||
return removeTypesFromUnionType(type, /*typeKind*/ TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.Boolean, /*isOfTypeKind*/ true);
|
||||
}
|
||||
// Check was for a primitive type, return that primitive type if it is a subtype
|
||||
if (isTypeSubtypeOf(typeInfo.type, type)) {
|
||||
return typeInfo.type;
|
||||
}
|
||||
// Otherwise, remove all types that aren't of the primitive type kind. This can happen when the type is
|
||||
// union of enum types and other types.
|
||||
return removeTypesFromUnionType(type, /*typeKind*/ typeInfo.flags, /*isOfTypeKind*/ false);
|
||||
}
|
||||
else {
|
||||
// The assumed result is false. If check was for a primitive type we can remove that type from the narrowed type.
|
||||
// Assumed result is false. If check was for a primitive type, remove that primitive type
|
||||
if (typeInfo) {
|
||||
return removeTypesFromUnionType(type, /*typeKind*/ typeInfo.flags, /*isOfTypeKind*/ true);
|
||||
}
|
||||
// Otherwise we don't have enough information to do anything.
|
||||
return checkType === emptyObjectType ? type : subtractPrimitiveTypes(type, checkType.flags);
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4758,7 +4788,8 @@ module ts {
|
|||
return type;
|
||||
}
|
||||
|
||||
// Narrow the given type based on the given expression having the assumed boolean value
|
||||
// Narrow the given type based on the given expression having the assumed boolean value. The returned type
|
||||
// will be a subtype or the same type as the argument.
|
||||
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
|
||||
switch (expr.kind) {
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
|
@ -6789,7 +6820,7 @@ module ts {
|
|||
// and the right operand to be of type Any or a subtype of the 'Function' interface type.
|
||||
// The result is always of the Boolean primitive type.
|
||||
// NOTE: do not raise error if leftType is unknown as related error was already reported
|
||||
if (!isTypeOfKind(leftType, TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.TypeParameter)) {
|
||||
if (isTypeOfKind(leftType, TypeFlags.Primitive)) {
|
||||
error(node.left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
|
||||
}
|
||||
// NOTE: do not raise error if right is unknown as related error was already reported
|
||||
|
|
|
@ -1288,6 +1288,7 @@ module ts {
|
|||
ContainsObjectLiteral = 0x00080000, // Type is or contains object literal type
|
||||
|
||||
Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null,
|
||||
Primitive = String | Number | Boolean | Void | Undefined | Null | StringLiteral | Enum,
|
||||
StringLike = String | StringLiteral,
|
||||
NumberLike = Number | Enum,
|
||||
ObjectType = Class | Interface | Reference | Tuple | Anonymous,
|
||||
|
|
77
tests/baselines/reference/TypeGuardWithEnumUnion.js
Normal file
77
tests/baselines/reference/TypeGuardWithEnumUnion.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
//// [TypeGuardWithEnumUnion.ts]
|
||||
enum Color { R, G, B }
|
||||
|
||||
function f1(x: Color | string) {
|
||||
if (typeof x === "number") {
|
||||
var y = x;
|
||||
var y: Color;
|
||||
}
|
||||
else {
|
||||
var z = x;
|
||||
var z: string;
|
||||
}
|
||||
}
|
||||
|
||||
function f2(x: Color | string | string[]) {
|
||||
if (typeof x === "object") {
|
||||
var y = x;
|
||||
var y: string[];
|
||||
}
|
||||
if (typeof x === "number") {
|
||||
var z = x;
|
||||
var z: Color;
|
||||
}
|
||||
else {
|
||||
var w = x;
|
||||
var w: string | string[];
|
||||
}
|
||||
if (typeof x === "string") {
|
||||
var a = x;
|
||||
var a: string;
|
||||
}
|
||||
else {
|
||||
var b = x;
|
||||
var b: Color | string[];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// [TypeGuardWithEnumUnion.js]
|
||||
var Color;
|
||||
(function (Color) {
|
||||
Color[Color["R"] = 0] = "R";
|
||||
Color[Color["G"] = 1] = "G";
|
||||
Color[Color["B"] = 2] = "B";
|
||||
})(Color || (Color = {}));
|
||||
function f1(x) {
|
||||
if (typeof x === "number") {
|
||||
var y = x;
|
||||
var y;
|
||||
}
|
||||
else {
|
||||
var z = x;
|
||||
var z;
|
||||
}
|
||||
}
|
||||
function f2(x) {
|
||||
if (typeof x === "object") {
|
||||
var y = x;
|
||||
var y;
|
||||
}
|
||||
if (typeof x === "number") {
|
||||
var z = x;
|
||||
var z;
|
||||
}
|
||||
else {
|
||||
var w = x;
|
||||
var w;
|
||||
}
|
||||
if (typeof x === "string") {
|
||||
var a = x;
|
||||
var a;
|
||||
}
|
||||
else {
|
||||
var b = x;
|
||||
var b;
|
||||
}
|
||||
}
|
96
tests/baselines/reference/TypeGuardWithEnumUnion.types
Normal file
96
tests/baselines/reference/TypeGuardWithEnumUnion.types
Normal file
|
@ -0,0 +1,96 @@
|
|||
=== tests/cases/conformance/expressions/typeGuards/TypeGuardWithEnumUnion.ts ===
|
||||
enum Color { R, G, B }
|
||||
>Color : Color
|
||||
>R : Color
|
||||
>G : Color
|
||||
>B : Color
|
||||
|
||||
function f1(x: Color | string) {
|
||||
>f1 : (x: string | Color) => void
|
||||
>x : string | Color
|
||||
>Color : Color
|
||||
|
||||
if (typeof x === "number") {
|
||||
>typeof x === "number" : boolean
|
||||
>typeof x : string
|
||||
>x : string | Color
|
||||
|
||||
var y = x;
|
||||
>y : Color
|
||||
>x : Color
|
||||
|
||||
var y: Color;
|
||||
>y : Color
|
||||
>Color : Color
|
||||
}
|
||||
else {
|
||||
var z = x;
|
||||
>z : string
|
||||
>x : string
|
||||
|
||||
var z: string;
|
||||
>z : string
|
||||
}
|
||||
}
|
||||
|
||||
function f2(x: Color | string | string[]) {
|
||||
>f2 : (x: string | string[] | Color) => void
|
||||
>x : string | string[] | Color
|
||||
>Color : Color
|
||||
|
||||
if (typeof x === "object") {
|
||||
>typeof x === "object" : boolean
|
||||
>typeof x : string
|
||||
>x : string | string[] | Color
|
||||
|
||||
var y = x;
|
||||
>y : string[]
|
||||
>x : string[]
|
||||
|
||||
var y: string[];
|
||||
>y : string[]
|
||||
}
|
||||
if (typeof x === "number") {
|
||||
>typeof x === "number" : boolean
|
||||
>typeof x : string
|
||||
>x : string | string[] | Color
|
||||
|
||||
var z = x;
|
||||
>z : Color
|
||||
>x : Color
|
||||
|
||||
var z: Color;
|
||||
>z : Color
|
||||
>Color : Color
|
||||
}
|
||||
else {
|
||||
var w = x;
|
||||
>w : string | string[]
|
||||
>x : string | string[]
|
||||
|
||||
var w: string | string[];
|
||||
>w : string | string[]
|
||||
}
|
||||
if (typeof x === "string") {
|
||||
>typeof x === "string" : boolean
|
||||
>typeof x : string
|
||||
>x : string | string[] | Color
|
||||
|
||||
var a = x;
|
||||
>a : string
|
||||
>x : string
|
||||
|
||||
var a: string;
|
||||
>a : string
|
||||
}
|
||||
else {
|
||||
var b = x;
|
||||
>b : string[] | Color
|
||||
>x : string[] | Color
|
||||
|
||||
var b: Color | string[];
|
||||
>b : string[] | Color
|
||||
>Color : Color
|
||||
}
|
||||
}
|
||||
|
|
@ -7,10 +7,13 @@ var x2: Function;
|
|||
var a: {};
|
||||
var b: Object;
|
||||
var c: C;
|
||||
var d: string | C;
|
||||
|
||||
var r1 = a instanceof x1;
|
||||
var r2 = b instanceof x2;
|
||||
var r3 = c instanceof x1;
|
||||
var r3 = c instanceof x1;
|
||||
var r4 = d instanceof x1;
|
||||
|
||||
|
||||
//// [instanceofOperatorWithLHSIsObject.js]
|
||||
var C = (function () {
|
||||
|
@ -23,6 +26,8 @@ var x2;
|
|||
var a;
|
||||
var b;
|
||||
var c;
|
||||
var d;
|
||||
var r1 = a instanceof x1;
|
||||
var r2 = b instanceof x2;
|
||||
var r3 = c instanceof x1;
|
||||
var r4 = d instanceof x1;
|
||||
|
|
|
@ -20,6 +20,10 @@ var c: C;
|
|||
>c : C
|
||||
>C : C
|
||||
|
||||
var d: string | C;
|
||||
>d : string | C
|
||||
>C : C
|
||||
|
||||
var r1 = a instanceof x1;
|
||||
>r1 : boolean
|
||||
>a instanceof x1 : boolean
|
||||
|
@ -38,3 +42,9 @@ var r3 = c instanceof x1;
|
|||
>c : C
|
||||
>x1 : any
|
||||
|
||||
var r4 = d instanceof x1;
|
||||
>r4 : boolean
|
||||
>d instanceof x1 : boolean
|
||||
>d : string | C
|
||||
>x1 : any
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@ var x2: Function;
|
|||
var a: {};
|
||||
var b: Object;
|
||||
var c: C;
|
||||
var d: string | C;
|
||||
|
||||
var r1 = a instanceof x1;
|
||||
var r2 = b instanceof x2;
|
||||
var r3 = c instanceof x1;
|
||||
var r3 = c instanceof x1;
|
||||
var r4 = d instanceof x1;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
enum Color { R, G, B }
|
||||
|
||||
function f1(x: Color | string) {
|
||||
if (typeof x === "number") {
|
||||
var y = x;
|
||||
var y: Color;
|
||||
}
|
||||
else {
|
||||
var z = x;
|
||||
var z: string;
|
||||
}
|
||||
}
|
||||
|
||||
function f2(x: Color | string | string[]) {
|
||||
if (typeof x === "object") {
|
||||
var y = x;
|
||||
var y: string[];
|
||||
}
|
||||
if (typeof x === "number") {
|
||||
var z = x;
|
||||
var z: Color;
|
||||
}
|
||||
else {
|
||||
var w = x;
|
||||
var w: string | string[];
|
||||
}
|
||||
if (typeof x === "string") {
|
||||
var a = x;
|
||||
var a: string;
|
||||
}
|
||||
else {
|
||||
var b = x;
|
||||
var b: Color | string[];
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue