@@ -107,6 +107,21 @@ module ts {
107
107
var diagnostics: Diagnostic[] = [];
108
108
var diagnosticsModified: boolean = false;
109
109
110
+ var primitiveTypeInfo: Map<{ type: Type; flags: TypeFlags }> = {
111
+ "string": {
112
+ type: stringType,
113
+ flags: TypeFlags.StringLike
114
+ },
115
+ "number": {
116
+ type: numberType,
117
+ flags: TypeFlags.NumberLike
118
+ },
119
+ "boolean": {
120
+ type: booleanType,
121
+ flags: TypeFlags.Boolean
122
+ }
123
+ };
124
+
110
125
function addDiagnostic(diagnostic: Diagnostic) {
111
126
diagnostics.push(diagnostic);
112
127
diagnosticsModified = true;
@@ -4482,12 +4497,17 @@ module ts {
4482
4497
Debug.fail("should not get here");
4483
4498
}
4484
4499
4485
- // Remove one or more primitive types from a union type
4486
- function subtractPrimitiveTypes(type: Type, subtractMask: TypeFlags): Type {
4500
+ // For a union type, remove all constituent types that are of the given type kind (when isOfTypeKind is true)
4501
+ // or not of the given type kind (when isOfTypeKind is false)
4502
+ function removeTypesFromUnionType(type: Type, typeKind: TypeFlags, isOfTypeKind: boolean): Type {
4487
4503
if (type.flags & TypeFlags.Union) {
4488
4504
var types = (<UnionType>type).types;
4489
- if (forEach(types, t => t.flags & subtractMask)) {
4490
- return getUnionType(filter(types, t => !(t.flags & subtractMask)));
4505
+ if (forEach(types, t => !!(t.flags & typeKind) === isOfTypeKind)) {
4506
+ // Above we checked if we have anything to remove, now use the opposite test to do the removal
4507
+ var narrowedType = getUnionType(filter(types, t => !(t.flags & typeKind) === isOfTypeKind));
4508
+ if (narrowedType !== emptyObjectType) {
4509
+ return narrowedType;
4510
+ }
4491
4511
}
4492
4512
}
4493
4513
return type;
@@ -4663,8 +4683,8 @@ module ts {
4663
4683
// Stop at the first containing function or module declaration
4664
4684
break loop;
4665
4685
}
4666
- // Use narrowed type if it is a subtype and construct contains no assignments to variable
4667
- if (narrowedType !== type && isTypeSubtypeOf(narrowedType, type) ) {
4686
+ // Use narrowed type if construct contains no assignments to variable
4687
+ if (narrowedType !== type) {
4668
4688
if (isVariableAssignedWithin(symbol, node)) {
4669
4689
break;
4670
4690
}
@@ -4684,20 +4704,30 @@ module ts {
4684
4704
if (left.expression.kind !== SyntaxKind.Identifier || getResolvedSymbol(<Identifier>left.expression) !== symbol) {
4685
4705
return type;
4686
4706
}
4687
- var t = right.text;
4688
- var checkType: Type = t === "string" ? stringType : t === "number" ? numberType : t === "boolean" ? booleanType : emptyObjectType;
4707
+ var typeInfo = primitiveTypeInfo[right.text];
4689
4708
if (expr.operator === SyntaxKind.ExclamationEqualsEqualsToken) {
4690
4709
assumeTrue = !assumeTrue;
4691
4710
}
4692
4711
if (assumeTrue) {
4693
- // The assumed result is true. If check was for a primitive type, that type is the narrowed type. Otherwise we can
4694
- // remove the primitive types from the narrowed type.
4695
- return checkType === emptyObjectType ? subtractPrimitiveTypes(type, TypeFlags.String | TypeFlags.Number | TypeFlags.Boolean) : checkType;
4712
+ // Assumed result is true. If check was not for a primitive type, remove all primitive types
4713
+ if (!typeInfo) {
4714
+ return removeTypesFromUnionType(type, /*typeKind*/ TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.Boolean, /*isOfTypeKind*/ true);
4715
+ }
4716
+ // Check was for a primitive type, return that primitive type if it is a subtype
4717
+ if (isTypeSubtypeOf(typeInfo.type, type)) {
4718
+ return typeInfo.type;
4719
+ }
4720
+ // Otherwise, remove all types that aren't of the primitive type kind. This can happen when the type is
4721
+ // union of enum types and other types.
4722
+ return removeTypesFromUnionType(type, /*typeKind*/ typeInfo.flags, /*isOfTypeKind*/ false);
4696
4723
}
4697
4724
else {
4698
- // The assumed result is false. If check was for a primitive type we can remove that type from the narrowed type.
4725
+ // Assumed result is false. If check was for a primitive type, remove that primitive type
4726
+ if (typeInfo) {
4727
+ return removeTypesFromUnionType(type, /*typeKind*/ typeInfo.flags, /*isOfTypeKind*/ true);
4728
+ }
4699
4729
// Otherwise we don't have enough information to do anything.
4700
- return checkType === emptyObjectType ? type : subtractPrimitiveTypes(type, checkType.flags) ;
4730
+ return type;
4701
4731
}
4702
4732
}
4703
4733
@@ -4758,7 +4788,8 @@ module ts {
4758
4788
return type;
4759
4789
}
4760
4790
4761
- // Narrow the given type based on the given expression having the assumed boolean value
4791
+ // Narrow the given type based on the given expression having the assumed boolean value. The returned type
4792
+ // will be a subtype or the same type as the argument.
4762
4793
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
4763
4794
switch (expr.kind) {
4764
4795
case SyntaxKind.ParenthesizedExpression:
@@ -6789,7 +6820,7 @@ module ts {
6789
6820
// and the right operand to be of type Any or a subtype of the 'Function' interface type.
6790
6821
// The result is always of the Boolean primitive type.
6791
6822
// NOTE: do not raise error if leftType is unknown as related error was already reported
6792
- if (! isTypeOfKind(leftType, TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.TypeParameter )) {
6823
+ if (isTypeOfKind(leftType, TypeFlags.Primitive )) {
6793
6824
error(node.left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
6794
6825
}
6795
6826
// NOTE: do not raise error if right is unknown as related error was already reported
0 commit comments