Skip to content

Commit 2052ac3

Browse files
committed
Merge pull request #1765 from Microsoft/unionTypeOperations
Correct handling of union types in expressions
2 parents e13d6fa + 361ea7b commit 2052ac3

11 files changed

+1584
-83
lines changed

src/compiler/checker.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5620,7 +5620,7 @@ module ts {
56205620
}
56215621

56225622
var isConstEnum = isConstEnumObjectType(objectType);
5623-
if (isConstEnum &&
5623+
if (isConstEnum &&
56245624
(!node.argumentExpression || node.argumentExpression.kind !== SyntaxKind.StringLiteral)) {
56255625
error(node.argumentExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
56265626
return unknownType;
@@ -5652,10 +5652,10 @@ module ts {
56525652
}
56535653

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

56575657
// Try to use a number indexer.
5658-
if (indexType.flags & (TypeFlags.Any | TypeFlags.NumberLike)) {
5658+
if (isTypeOfKind(indexType, TypeFlags.Any | TypeFlags.NumberLike)) {
56595659
var numberIndexType = getIndexTypeOfType(objectType, IndexKind.Number);
56605660
if (numberIndexType) {
56615661
return numberIndexType;
@@ -6556,7 +6556,7 @@ module ts {
65566556
}
65576557

65586558
function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage): boolean {
6559-
if (!(type.flags & (TypeFlags.Any | TypeFlags.NumberLike))) {
6559+
if (!isTypeOfKind(type, TypeFlags.Any | TypeFlags.NumberLike)) {
65606560
error(operand, diagnostic);
65616561
return false;
65626562
}
@@ -6707,12 +6707,21 @@ module ts {
67076707
return numberType;
67086708
}
67096709

6710-
// Return true if type an object type, a type parameter, or a union type composed of only those kinds of types
6711-
function isStructuredType(type: Type): boolean {
6710+
// Return true if type has the given flags, or is a union type composed of types that all have those flags
6711+
function isTypeOfKind(type: Type, kind: TypeFlags): boolean {
6712+
if (type.flags & kind) {
6713+
return true;
6714+
}
67126715
if (type.flags & TypeFlags.Union) {
6713-
return !forEach((<UnionType>type).types, t => !isStructuredType(t));
6716+
var types = (<UnionType>type).types;
6717+
for (var i = 0; i < types.length; i++) {
6718+
if (!(types[i].flags & kind)) {
6719+
return false;
6720+
}
6721+
}
6722+
return true;
67146723
}
6715-
return (type.flags & (TypeFlags.ObjectType | TypeFlags.TypeParameter)) !== 0;
6724+
return false;
67166725
}
67176726

67186727
function isConstEnumObjectType(type: Type): boolean {
@@ -6729,7 +6738,7 @@ module ts {
67296738
// and the right operand to be of type Any or a subtype of the 'Function' interface type.
67306739
// The result is always of the Boolean primitive type.
67316740
// NOTE: do not raise error if leftType is unknown as related error was already reported
6732-
if (!(leftType.flags & TypeFlags.Any || isStructuredType(leftType))) {
6741+
if (!isTypeOfKind(leftType, TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.TypeParameter)) {
67336742
error(node.left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
67346743
}
67356744
// NOTE: do not raise error if right is unknown as related error was already reported
@@ -6744,10 +6753,10 @@ module ts {
67446753
// The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type,
67456754
// and the right operand to be of type Any, an object type, or a type parameter type.
67466755
// The result is always of the Boolean primitive type.
6747-
if (leftType !== anyType && leftType !== stringType && leftType !== numberType) {
6756+
if (!isTypeOfKind(leftType, TypeFlags.Any | TypeFlags.StringLike | TypeFlags.NumberLike)) {
67486757
error(node.left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_types_any_string_or_number);
67496758
}
6750-
if (!(rightType.flags & TypeFlags.Any || isStructuredType(rightType))) {
6759+
if (!isTypeOfKind(rightType, TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.TypeParameter)) {
67516760
error(node.right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
67526761
}
67536762
return booleanType;
@@ -6908,16 +6917,16 @@ module ts {
69086917
if (rightType.flags & (TypeFlags.Undefined | TypeFlags.Null)) rightType = leftType;
69096918

69106919
var resultType: Type;
6911-
if (leftType.flags & TypeFlags.NumberLike && rightType.flags & TypeFlags.NumberLike) {
6920+
if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) {
69126921
// Operands of an enum type are treated as having the primitive type Number.
69136922
// If both operands are of the Number primitive type, the result is of the Number primitive type.
69146923
resultType = numberType;
69156924
}
6916-
else if (leftType.flags & TypeFlags.StringLike || rightType.flags & TypeFlags.StringLike) {
6925+
else if (isTypeOfKind(leftType, TypeFlags.StringLike) || isTypeOfKind(rightType, TypeFlags.StringLike)) {
69176926
// If one or both operands are of the String primitive type, the result is of the String primitive type.
69186927
resultType = stringType;
69196928
}
6920-
else if (leftType.flags & TypeFlags.Any || leftType === unknownType || rightType.flags & TypeFlags.Any || rightType === unknownType) {
6929+
else if (leftType.flags & TypeFlags.Any || rightType.flags & TypeFlags.Any) {
69216930
// Otherwise, the result is of type Any.
69226931
// NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we.
69236932
resultType = anyType;
@@ -8273,7 +8282,7 @@ module ts {
82738282
var exprType = checkExpression(node.expression);
82748283
// unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved
82758284
// in this case error about missing name is already reported - do not report extra one
8276-
if (!(exprType.flags & TypeFlags.Any || isStructuredType(exprType))) {
8285+
if (!isTypeOfKind(exprType, TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.TypeParameter)) {
82778286
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);
82788287
}
82798288

tests/baselines/reference/additionOperatorWithNumberAndEnum.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
//// [additionOperatorWithNumberAndEnum.ts]
22
enum E { a, b }
3+
enum F { c, d }
34

45
var a: number;
56
var b: E;
7+
var c: E | F;
68

79
var r1 = a + a;
810
var r2 = a + b;
@@ -12,16 +14,30 @@ var r4 = b + b;
1214
var r5 = 0 + a;
1315
var r6 = E.a + 0;
1416
var r7 = E.a + E.b;
15-
var r8 = E['a'] + E['b'];
17+
var r8 = E['a'] + E['b'];
18+
var r9 = E['a'] + F['c'];
19+
20+
var r10 = a + c;
21+
var r11 = c + a;
22+
var r12 = b + c;
23+
var r13 = c + b;
24+
var r14 = c + c;
25+
1626

1727
//// [additionOperatorWithNumberAndEnum.js]
1828
var E;
1929
(function (E) {
2030
E[E["a"] = 0] = "a";
2131
E[E["b"] = 1] = "b";
2232
})(E || (E = {}));
33+
var F;
34+
(function (F) {
35+
F[F["c"] = 0] = "c";
36+
F[F["d"] = 1] = "d";
37+
})(F || (F = {}));
2338
var a;
2439
var b;
40+
var c;
2541
var r1 = a + a;
2642
var r2 = a + b;
2743
var r3 = b + a;
@@ -30,3 +46,9 @@ var r5 = 0 + a;
3046
var r6 = 0 /* a */ + 0;
3147
var r7 = 0 /* a */ + 1 /* b */;
3248
var r8 = 0 /* 'a' */ + 1 /* 'b' */;
49+
var r9 = 0 /* 'a' */ + 0 /* 'c' */;
50+
var r10 = a + c;
51+
var r11 = c + a;
52+
var r12 = b + c;
53+
var r13 = c + b;
54+
var r14 = c + c;

tests/baselines/reference/additionOperatorWithNumberAndEnum.types

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,23 @@ enum E { a, b }
44
>a : E
55
>b : E
66

7+
enum F { c, d }
8+
>F : F
9+
>c : F
10+
>d : F
11+
712
var a: number;
813
>a : number
914

1015
var b: E;
1116
>b : E
1217
>E : E
1318

19+
var c: E | F;
20+
>c : E | F
21+
>E : E
22+
>F : F
23+
1424
var r1 = a + a;
1525
>r1 : number
1626
>a + a : number
@@ -65,3 +75,41 @@ var r8 = E['a'] + E['b'];
6575
>E['b'] : E
6676
>E : typeof E
6777

78+
var r9 = E['a'] + F['c'];
79+
>r9 : number
80+
>E['a'] + F['c'] : number
81+
>E['a'] : E
82+
>E : typeof E
83+
>F['c'] : F
84+
>F : typeof F
85+
86+
var r10 = a + c;
87+
>r10 : number
88+
>a + c : number
89+
>a : number
90+
>c : E | F
91+
92+
var r11 = c + a;
93+
>r11 : number
94+
>c + a : number
95+
>c : E | F
96+
>a : number
97+
98+
var r12 = b + c;
99+
>r12 : number
100+
>b + c : number
101+
>b : E
102+
>c : E | F
103+
104+
var r13 = c + b;
105+
>r13 : number
106+
>c + b : number
107+
>c : E | F
108+
>b : E
109+
110+
var r14 = c + c;
111+
>r14 : number
112+
>c + c : number
113+
>c : E | F
114+
>c : E | F
115+

0 commit comments

Comments
 (0)