Skip to content

Commit

Permalink
Merge pull request #1765 from Microsoft/unionTypeOperations
Browse files Browse the repository at this point in the history
Correct handling of union types in expressions
  • Loading branch information
ahejlsberg committed Jan 21, 2015
2 parents e13d6fa + 361ea7b commit 2052ac3
Show file tree
Hide file tree
Showing 11 changed files with 1,584 additions and 83 deletions.
39 changes: 24 additions & 15 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5620,7 +5620,7 @@ module ts {
}

var isConstEnum = isConstEnumObjectType(objectType);
if (isConstEnum &&
if (isConstEnum &&
(!node.argumentExpression || node.argumentExpression.kind !== SyntaxKind.StringLiteral)) {
error(node.argumentExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
return unknownType;
Expand Down Expand Up @@ -5652,10 +5652,10 @@ module ts {
}

// Check for compatible indexer types.
if (indexType.flags & (TypeFlags.Any | TypeFlags.StringLike | TypeFlags.NumberLike)) {
if (isTypeOfKind(indexType, TypeFlags.Any | TypeFlags.StringLike | TypeFlags.NumberLike)) {

// Try to use a number indexer.
if (indexType.flags & (TypeFlags.Any | TypeFlags.NumberLike)) {
if (isTypeOfKind(indexType, TypeFlags.Any | TypeFlags.NumberLike)) {
var numberIndexType = getIndexTypeOfType(objectType, IndexKind.Number);
if (numberIndexType) {
return numberIndexType;
Expand Down Expand Up @@ -6556,7 +6556,7 @@ module ts {
}

function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage): boolean {
if (!(type.flags & (TypeFlags.Any | TypeFlags.NumberLike))) {
if (!isTypeOfKind(type, TypeFlags.Any | TypeFlags.NumberLike)) {
error(operand, diagnostic);
return false;
}
Expand Down Expand Up @@ -6707,12 +6707,21 @@ module ts {
return numberType;
}

// Return true if type an object type, a type parameter, or a union type composed of only those kinds of types
function isStructuredType(type: Type): boolean {
// Return true if type has the given flags, or is a union type composed of types that all have those flags
function isTypeOfKind(type: Type, kind: TypeFlags): boolean {
if (type.flags & kind) {
return true;
}
if (type.flags & TypeFlags.Union) {
return !forEach((<UnionType>type).types, t => !isStructuredType(t));
var types = (<UnionType>type).types;
for (var i = 0; i < types.length; i++) {
if (!(types[i].flags & kind)) {
return false;
}
}
return true;
}
return (type.flags & (TypeFlags.ObjectType | TypeFlags.TypeParameter)) !== 0;
return false;
}

function isConstEnumObjectType(type: Type): boolean {
Expand All @@ -6729,7 +6738,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 (!(leftType.flags & TypeFlags.Any || isStructuredType(leftType))) {
if (!isTypeOfKind(leftType, TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.TypeParameter)) {
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
Expand All @@ -6744,10 +6753,10 @@ module ts {
// The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type,
// and the right operand to be of type Any, an object type, or a type parameter type.
// The result is always of the Boolean primitive type.
if (leftType !== anyType && leftType !== stringType && leftType !== numberType) {
if (!isTypeOfKind(leftType, TypeFlags.Any | TypeFlags.StringLike | TypeFlags.NumberLike)) {
error(node.left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_types_any_string_or_number);
}
if (!(rightType.flags & TypeFlags.Any || isStructuredType(rightType))) {
if (!isTypeOfKind(rightType, TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.TypeParameter)) {
error(node.right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
}
return booleanType;
Expand Down Expand Up @@ -6908,16 +6917,16 @@ module ts {
if (rightType.flags & (TypeFlags.Undefined | TypeFlags.Null)) rightType = leftType;

var resultType: Type;
if (leftType.flags & TypeFlags.NumberLike && rightType.flags & TypeFlags.NumberLike) {
if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) {
// Operands of an enum type are treated as having the primitive type Number.
// If both operands are of the Number primitive type, the result is of the Number primitive type.
resultType = numberType;
}
else if (leftType.flags & TypeFlags.StringLike || rightType.flags & TypeFlags.StringLike) {
else if (isTypeOfKind(leftType, TypeFlags.StringLike) || isTypeOfKind(rightType, TypeFlags.StringLike)) {
// If one or both operands are of the String primitive type, the result is of the String primitive type.
resultType = stringType;
}
else if (leftType.flags & TypeFlags.Any || leftType === unknownType || rightType.flags & TypeFlags.Any || rightType === unknownType) {
else if (leftType.flags & TypeFlags.Any || rightType.flags & TypeFlags.Any) {
// Otherwise, the result is of type Any.
// NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we.
resultType = anyType;
Expand Down Expand Up @@ -8273,7 +8282,7 @@ module ts {
var exprType = checkExpression(node.expression);
// unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved
// in this case error about missing name is already reported - do not report extra one
if (!(exprType.flags & TypeFlags.Any || isStructuredType(exprType))) {
if (!isTypeOfKind(exprType, TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.TypeParameter)) {
error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter);
}

Expand Down
24 changes: 23 additions & 1 deletion tests/baselines/reference/additionOperatorWithNumberAndEnum.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//// [additionOperatorWithNumberAndEnum.ts]
enum E { a, b }
enum F { c, d }

var a: number;
var b: E;
var c: E | F;

var r1 = a + a;
var r2 = a + b;
Expand All @@ -12,16 +14,30 @@ var r4 = b + b;
var r5 = 0 + a;
var r6 = E.a + 0;
var r7 = E.a + E.b;
var r8 = E['a'] + E['b'];
var r8 = E['a'] + E['b'];
var r9 = E['a'] + F['c'];

var r10 = a + c;
var r11 = c + a;
var r12 = b + c;
var r13 = c + b;
var r14 = c + c;


//// [additionOperatorWithNumberAndEnum.js]
var E;
(function (E) {
E[E["a"] = 0] = "a";
E[E["b"] = 1] = "b";
})(E || (E = {}));
var F;
(function (F) {
F[F["c"] = 0] = "c";
F[F["d"] = 1] = "d";
})(F || (F = {}));
var a;
var b;
var c;
var r1 = a + a;
var r2 = a + b;
var r3 = b + a;
Expand All @@ -30,3 +46,9 @@ var r5 = 0 + a;
var r6 = 0 /* a */ + 0;
var r7 = 0 /* a */ + 1 /* b */;
var r8 = 0 /* 'a' */ + 1 /* 'b' */;
var r9 = 0 /* 'a' */ + 0 /* 'c' */;
var r10 = a + c;
var r11 = c + a;
var r12 = b + c;
var r13 = c + b;
var r14 = c + c;
48 changes: 48 additions & 0 deletions tests/baselines/reference/additionOperatorWithNumberAndEnum.types
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ enum E { a, b }
>a : E
>b : E

enum F { c, d }
>F : F
>c : F
>d : F

var a: number;
>a : number

var b: E;
>b : E
>E : E

var c: E | F;
>c : E | F
>E : E
>F : F

var r1 = a + a;
>r1 : number
>a + a : number
Expand Down Expand Up @@ -65,3 +75,41 @@ var r8 = E['a'] + E['b'];
>E['b'] : E
>E : typeof E

var r9 = E['a'] + F['c'];
>r9 : number
>E['a'] + F['c'] : number
>E['a'] : E
>E : typeof E
>F['c'] : F
>F : typeof F

var r10 = a + c;
>r10 : number
>a + c : number
>a : number
>c : E | F

var r11 = c + a;
>r11 : number
>c + a : number
>c : E | F
>a : number

var r12 = b + c;
>r12 : number
>b + c : number
>b : E
>c : E | F

var r13 = c + b;
>r13 : number
>c + b : number
>c : E | F
>b : E

var r14 = c + c;
>r14 : number
>c + c : number
>c : E | F
>c : E | F

Loading

0 comments on commit 2052ac3

Please sign in to comment.