Merge pull request #1803 from Microsoft/enumUnionTypeGuard

Fixes to union types in type guards and instanceof
This commit is contained in:
Anders Hejlsberg 2015-01-27 06:52:03 -08:00
commit 9289cfb0b0
8 changed files with 274 additions and 17 deletions

View file

@ -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

View file

@ -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,

View 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;
}
}

View 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
}
}

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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[];
}
}