Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct handling of union types in expressions #1765

Merged
merged 5 commits into from
Jan 21, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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