Skip to content

Commit 9dfaa30

Browse files
committed
Merge pull request microsoft#6947 from Microsoft/operatorsAndIntersections
Allow operators and indexing with intersections involving primtive types
2 parents cceeffa + 788ba3c commit 9dfaa30

5 files changed

Lines changed: 360 additions & 33 deletions

File tree

src/compiler/checker.ts

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5591,7 +5591,7 @@ namespace ts {
55915591
}
55925592

55935593
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
5594-
if (!(target.flags & TypeFlags.ObjectLiteralPatternWithComputedProperties) && someConstituentTypeHasKind(target, TypeFlags.ObjectType)) {
5594+
if (!(target.flags & TypeFlags.ObjectLiteralPatternWithComputedProperties) && maybeTypeOfKind(target, TypeFlags.ObjectType)) {
55955595
for (const prop of getPropertiesOfObjectType(source)) {
55965596
if (!isKnownProperty(target, prop.name)) {
55975597
if (reportErrors) {
@@ -8178,7 +8178,7 @@ namespace ts {
81788178
}
81798179

81808180
function isTypeAnyOrAllConstituentTypesHaveKind(type: Type, kind: TypeFlags): boolean {
8181-
return isTypeAny(type) || allConstituentTypesHaveKind(type, kind);
8181+
return isTypeAny(type) || isTypeOfKind(type, kind);
81828182
}
81838183

81848184
function isNumericLiteralName(name: string) {
@@ -9684,7 +9684,7 @@ namespace ts {
96849684

96859685
case SyntaxKind.ComputedPropertyName:
96869686
const nameType = checkComputedPropertyName(<ComputedPropertyName>element.name);
9687-
if (allConstituentTypesHaveKind(nameType, TypeFlags.ESSymbol)) {
9687+
if (isTypeOfKind(nameType, TypeFlags.ESSymbol)) {
96889688
return nameType;
96899689
}
96909690
else {
@@ -10337,9 +10337,8 @@ namespace ts {
1033710337
const widenedType = getWidenedType(exprType);
1033810338

1033910339
// Permit 'number[] | "foo"' to be asserted to 'string'.
10340-
const bothAreStringLike =
10341-
someConstituentTypeHasKind(targetType, TypeFlags.StringLike) &&
10342-
someConstituentTypeHasKind(widenedType, TypeFlags.StringLike);
10340+
const bothAreStringLike = maybeTypeOfKind(targetType, TypeFlags.StringLike) &&
10341+
maybeTypeOfKind(widenedType, TypeFlags.StringLike);
1034310342
if (!bothAreStringLike && !(isTypeAssignableTo(targetType, widenedType))) {
1034410343
checkTypeAssignableTo(exprType, targetType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other);
1034510344
}
@@ -10594,7 +10593,7 @@ namespace ts {
1059410593
}
1059510594

1059610595
// Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions.
10597-
if (returnType === voidType || isTypeAny(returnType) || (returnType && (returnType.flags & TypeFlags.Union) && someConstituentTypeHasKind(returnType, TypeFlags.Any | TypeFlags.Void))) {
10596+
if (returnType && maybeTypeOfKind(returnType, TypeFlags.Any | TypeFlags.Void)) {
1059810597
return;
1059910598
}
1060010599

@@ -10850,7 +10849,7 @@ namespace ts {
1085010849
case SyntaxKind.PlusToken:
1085110850
case SyntaxKind.MinusToken:
1085210851
case SyntaxKind.TildeToken:
10853-
if (someConstituentTypeHasKind(operandType, TypeFlags.ESSymbol)) {
10852+
if (maybeTypeOfKind(operandType, TypeFlags.ESSymbol)) {
1085410853
error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator));
1085510854
}
1085610855
return numberType;
@@ -10882,38 +10881,47 @@ namespace ts {
1088210881
return numberType;
1088310882
}
1088410883

10885-
// Just like isTypeOfKind below, except that it returns true if *any* constituent
10886-
// has this kind.
10887-
function someConstituentTypeHasKind(type: Type, kind: TypeFlags): boolean {
10884+
// Return true if type might be of the given kind. A union or intersection type might be of a given
10885+
// kind if at least one constituent type is of the given kind.
10886+
function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean {
1088810887
if (type.flags & kind) {
1088910888
return true;
1089010889
}
1089110890
if (type.flags & TypeFlags.UnionOrIntersection) {
1089210891
const types = (<UnionOrIntersectionType>type).types;
10893-
for (const current of types) {
10894-
if (current.flags & kind) {
10892+
for (const t of types) {
10893+
if (maybeTypeOfKind(t, kind)) {
1089510894
return true;
1089610895
}
1089710896
}
10898-
return false;
1089910897
}
1090010898
return false;
1090110899
}
1090210900

10903-
// Return true if type has the given flags, or is a union or intersection type composed of types that all have those flags.
10904-
function allConstituentTypesHaveKind(type: Type, kind: TypeFlags): boolean {
10901+
// Return true if type is of the given kind. A union type is of a given kind if all constituent types
10902+
// are of the given kind. An intersection type is of a given kind if at least one constituent type is
10903+
// of the given kind.
10904+
function isTypeOfKind(type: Type, kind: TypeFlags): boolean {
1090510905
if (type.flags & kind) {
1090610906
return true;
1090710907
}
10908-
if (type.flags & TypeFlags.UnionOrIntersection) {
10908+
if (type.flags & TypeFlags.Union) {
1090910909
const types = (<UnionOrIntersectionType>type).types;
10910-
for (const current of types) {
10911-
if (!(current.flags & kind)) {
10910+
for (const t of types) {
10911+
if (!isTypeOfKind(t, kind)) {
1091210912
return false;
1091310913
}
1091410914
}
1091510915
return true;
1091610916
}
10917+
if (type.flags & TypeFlags.Intersection) {
10918+
const types = (<UnionOrIntersectionType>type).types;
10919+
for (const t of types) {
10920+
if (isTypeOfKind(t, kind)) {
10921+
return true;
10922+
}
10923+
}
10924+
}
1091710925
return false;
1091810926
}
1091910927

@@ -10931,7 +10939,7 @@ namespace ts {
1093110939
// and the right operand to be of type Any or a subtype of the 'Function' interface type.
1093210940
// The result is always of the Boolean primitive type.
1093310941
// NOTE: do not raise error if leftType is unknown as related error was already reported
10934-
if (allConstituentTypesHaveKind(leftType, TypeFlags.Primitive)) {
10942+
if (isTypeOfKind(leftType, TypeFlags.Primitive)) {
1093510943
error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
1093610944
}
1093710945
// NOTE: do not raise error if right is unknown as related error was already reported
@@ -11146,13 +11154,13 @@ namespace ts {
1114611154
if (rightType.flags & (TypeFlags.Undefined | TypeFlags.Null)) rightType = leftType;
1114711155

1114811156
let resultType: Type;
11149-
if (allConstituentTypesHaveKind(leftType, TypeFlags.NumberLike) && allConstituentTypesHaveKind(rightType, TypeFlags.NumberLike)) {
11157+
if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) {
1115011158
// Operands of an enum type are treated as having the primitive type Number.
1115111159
// If both operands are of the Number primitive type, the result is of the Number primitive type.
1115211160
resultType = numberType;
1115311161
}
1115411162
else {
11155-
if (allConstituentTypesHaveKind(leftType, TypeFlags.StringLike) || allConstituentTypesHaveKind(rightType, TypeFlags.StringLike)) {
11163+
if (isTypeOfKind(leftType, TypeFlags.StringLike) || isTypeOfKind(rightType, TypeFlags.StringLike)) {
1115611164
// If one or both operands are of the String primitive type, the result is of the String primitive type.
1115711165
resultType = stringType;
1115811166
}
@@ -11190,7 +11198,7 @@ namespace ts {
1119011198
case SyntaxKind.EqualsEqualsEqualsToken:
1119111199
case SyntaxKind.ExclamationEqualsEqualsToken:
1119211200
// Permit 'number[] | "foo"' to be asserted to 'string'.
11193-
if (someConstituentTypeHasKind(leftType, TypeFlags.StringLike) && someConstituentTypeHasKind(rightType, TypeFlags.StringLike)) {
11201+
if (maybeTypeOfKind(leftType, TypeFlags.StringLike) && maybeTypeOfKind(rightType, TypeFlags.StringLike)) {
1119411202
return booleanType;
1119511203
}
1119611204
if (!isTypeAssignableTo(leftType, rightType) && !isTypeAssignableTo(rightType, leftType)) {
@@ -11215,8 +11223,8 @@ namespace ts {
1121511223
// Return true if there was no error, false if there was an error.
1121611224
function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean {
1121711225
const offendingSymbolOperand =
11218-
someConstituentTypeHasKind(leftType, TypeFlags.ESSymbol) ? left :
11219-
someConstituentTypeHasKind(rightType, TypeFlags.ESSymbol) ? right :
11226+
maybeTypeOfKind(leftType, TypeFlags.ESSymbol) ? left :
11227+
maybeTypeOfKind(rightType, TypeFlags.ESSymbol) ? right :
1122011228
undefined;
1122111229
if (offendingSymbolOperand) {
1122211230
error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator));
@@ -13700,7 +13708,7 @@ namespace ts {
1370013708
let hasDuplicateDefaultClause = false;
1370113709

1370213710
const expressionType = checkExpression(node.expression);
13703-
const expressionTypeIsStringLike = someConstituentTypeHasKind(expressionType, TypeFlags.StringLike);
13711+
const expressionTypeIsStringLike = maybeTypeOfKind(expressionType, TypeFlags.StringLike);
1370413712
forEach(node.caseBlock.clauses, clause => {
1370513713
// Grammar check for duplicate default clauses, skip if we already report duplicate default clause
1370613714
if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) {
@@ -13724,7 +13732,7 @@ namespace ts {
1372413732

1372513733
const expressionTypeIsAssignableToCaseType =
1372613734
// Permit 'number[] | "foo"' to be asserted to 'string'.
13727-
(expressionTypeIsStringLike && someConstituentTypeHasKind(caseType, TypeFlags.StringLike)) ||
13735+
(expressionTypeIsStringLike && maybeTypeOfKind(caseType, TypeFlags.StringLike)) ||
1372813736
isTypeAssignableTo(expressionType, caseType);
1372913737

1373013738
if (!expressionTypeIsAssignableToCaseType) {
@@ -15930,22 +15938,22 @@ namespace ts {
1593015938
else if (type.flags & TypeFlags.Any) {
1593115939
return TypeReferenceSerializationKind.ObjectType;
1593215940
}
15933-
else if (allConstituentTypesHaveKind(type, TypeFlags.Void)) {
15941+
else if (isTypeOfKind(type, TypeFlags.Void)) {
1593415942
return TypeReferenceSerializationKind.VoidType;
1593515943
}
15936-
else if (allConstituentTypesHaveKind(type, TypeFlags.Boolean)) {
15944+
else if (isTypeOfKind(type, TypeFlags.Boolean)) {
1593715945
return TypeReferenceSerializationKind.BooleanType;
1593815946
}
15939-
else if (allConstituentTypesHaveKind(type, TypeFlags.NumberLike)) {
15947+
else if (isTypeOfKind(type, TypeFlags.NumberLike)) {
1594015948
return TypeReferenceSerializationKind.NumberLikeType;
1594115949
}
15942-
else if (allConstituentTypesHaveKind(type, TypeFlags.StringLike)) {
15950+
else if (isTypeOfKind(type, TypeFlags.StringLike)) {
1594315951
return TypeReferenceSerializationKind.StringLikeType;
1594415952
}
15945-
else if (allConstituentTypesHaveKind(type, TypeFlags.Tuple)) {
15953+
else if (isTypeOfKind(type, TypeFlags.Tuple)) {
1594615954
return TypeReferenceSerializationKind.ArrayLikeType;
1594715955
}
15948-
else if (allConstituentTypesHaveKind(type, TypeFlags.ESSymbol)) {
15956+
else if (isTypeOfKind(type, TypeFlags.ESSymbol)) {
1594915957
return TypeReferenceSerializationKind.ESSymbolType;
1595015958
}
1595115959
else if (isFunctionType(type)) {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//// [operatorsAndIntersectionTypes.ts]
2+
type Guid = string & { $Guid }; // Tagged string type
3+
type SerialNo = number & { $SerialNo }; // Tagged number type
4+
5+
function createGuid() {
6+
return "21EC2020-3AEA-4069-A2DD-08002B30309D" as Guid;
7+
}
8+
9+
function createSerialNo() {
10+
return 12345 as SerialNo;
11+
}
12+
13+
let map1: { [x: string]: number } = {};
14+
let guid = createGuid();
15+
map1[guid] = 123; // Can with tagged string
16+
17+
let map2: { [x: number]: string } = {};
18+
let serialNo = createSerialNo();
19+
map2[serialNo] = "hello"; // Can index with tagged number
20+
21+
const s1 = "{" + guid + "}";
22+
const s2 = guid.toLowerCase();
23+
const s3 = guid + guid;
24+
const s4 = guid + serialNo;
25+
const s5 = serialNo.toPrecision(0);
26+
const n1 = serialNo * 3;
27+
const n2 = serialNo + serialNo;
28+
const b1 = guid === "";
29+
const b2 = guid === guid;
30+
const b3 = serialNo === 0;
31+
const b4 = serialNo === serialNo;
32+
33+
34+
//// [operatorsAndIntersectionTypes.js]
35+
function createGuid() {
36+
return "21EC2020-3AEA-4069-A2DD-08002B30309D";
37+
}
38+
function createSerialNo() {
39+
return 12345;
40+
}
41+
var map1 = {};
42+
var guid = createGuid();
43+
map1[guid] = 123; // Can with tagged string
44+
var map2 = {};
45+
var serialNo = createSerialNo();
46+
map2[serialNo] = "hello"; // Can index with tagged number
47+
var s1 = "{" + guid + "}";
48+
var s2 = guid.toLowerCase();
49+
var s3 = guid + guid;
50+
var s4 = guid + serialNo;
51+
var s5 = serialNo.toPrecision(0);
52+
var n1 = serialNo * 3;
53+
var n2 = serialNo + serialNo;
54+
var b1 = guid === "";
55+
var b2 = guid === guid;
56+
var b3 = serialNo === 0;
57+
var b4 = serialNo === serialNo;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
=== tests/cases/conformance/types/intersection/operatorsAndIntersectionTypes.ts ===
2+
type Guid = string & { $Guid }; // Tagged string type
3+
>Guid : Symbol(Guid, Decl(operatorsAndIntersectionTypes.ts, 0, 0))
4+
>$Guid : Symbol($Guid, Decl(operatorsAndIntersectionTypes.ts, 0, 22))
5+
6+
type SerialNo = number & { $SerialNo }; // Tagged number type
7+
>SerialNo : Symbol(SerialNo, Decl(operatorsAndIntersectionTypes.ts, 0, 31))
8+
>$SerialNo : Symbol($SerialNo, Decl(operatorsAndIntersectionTypes.ts, 1, 26))
9+
10+
function createGuid() {
11+
>createGuid : Symbol(createGuid, Decl(operatorsAndIntersectionTypes.ts, 1, 39))
12+
13+
return "21EC2020-3AEA-4069-A2DD-08002B30309D" as Guid;
14+
>Guid : Symbol(Guid, Decl(operatorsAndIntersectionTypes.ts, 0, 0))
15+
}
16+
17+
function createSerialNo() {
18+
>createSerialNo : Symbol(createSerialNo, Decl(operatorsAndIntersectionTypes.ts, 5, 1))
19+
20+
return 12345 as SerialNo;
21+
>SerialNo : Symbol(SerialNo, Decl(operatorsAndIntersectionTypes.ts, 0, 31))
22+
}
23+
24+
let map1: { [x: string]: number } = {};
25+
>map1 : Symbol(map1, Decl(operatorsAndIntersectionTypes.ts, 11, 3))
26+
>x : Symbol(x, Decl(operatorsAndIntersectionTypes.ts, 11, 13))
27+
28+
let guid = createGuid();
29+
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
30+
>createGuid : Symbol(createGuid, Decl(operatorsAndIntersectionTypes.ts, 1, 39))
31+
32+
map1[guid] = 123; // Can with tagged string
33+
>map1 : Symbol(map1, Decl(operatorsAndIntersectionTypes.ts, 11, 3))
34+
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
35+
36+
let map2: { [x: number]: string } = {};
37+
>map2 : Symbol(map2, Decl(operatorsAndIntersectionTypes.ts, 15, 3))
38+
>x : Symbol(x, Decl(operatorsAndIntersectionTypes.ts, 15, 13))
39+
40+
let serialNo = createSerialNo();
41+
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
42+
>createSerialNo : Symbol(createSerialNo, Decl(operatorsAndIntersectionTypes.ts, 5, 1))
43+
44+
map2[serialNo] = "hello"; // Can index with tagged number
45+
>map2 : Symbol(map2, Decl(operatorsAndIntersectionTypes.ts, 15, 3))
46+
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
47+
48+
const s1 = "{" + guid + "}";
49+
>s1 : Symbol(s1, Decl(operatorsAndIntersectionTypes.ts, 19, 5))
50+
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
51+
52+
const s2 = guid.toLowerCase();
53+
>s2 : Symbol(s2, Decl(operatorsAndIntersectionTypes.ts, 20, 5))
54+
>guid.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
55+
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
56+
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
57+
58+
const s3 = guid + guid;
59+
>s3 : Symbol(s3, Decl(operatorsAndIntersectionTypes.ts, 21, 5))
60+
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
61+
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
62+
63+
const s4 = guid + serialNo;
64+
>s4 : Symbol(s4, Decl(operatorsAndIntersectionTypes.ts, 22, 5))
65+
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
66+
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
67+
68+
const s5 = serialNo.toPrecision(0);
69+
>s5 : Symbol(s5, Decl(operatorsAndIntersectionTypes.ts, 23, 5))
70+
>serialNo.toPrecision : Symbol(Number.toPrecision, Decl(lib.d.ts, --, --))
71+
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
72+
>toPrecision : Symbol(Number.toPrecision, Decl(lib.d.ts, --, --))
73+
74+
const n1 = serialNo * 3;
75+
>n1 : Symbol(n1, Decl(operatorsAndIntersectionTypes.ts, 24, 5))
76+
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
77+
78+
const n2 = serialNo + serialNo;
79+
>n2 : Symbol(n2, Decl(operatorsAndIntersectionTypes.ts, 25, 5))
80+
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
81+
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
82+
83+
const b1 = guid === "";
84+
>b1 : Symbol(b1, Decl(operatorsAndIntersectionTypes.ts, 26, 5))
85+
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
86+
87+
const b2 = guid === guid;
88+
>b2 : Symbol(b2, Decl(operatorsAndIntersectionTypes.ts, 27, 5))
89+
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
90+
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
91+
92+
const b3 = serialNo === 0;
93+
>b3 : Symbol(b3, Decl(operatorsAndIntersectionTypes.ts, 28, 5))
94+
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
95+
96+
const b4 = serialNo === serialNo;
97+
>b4 : Symbol(b4, Decl(operatorsAndIntersectionTypes.ts, 29, 5))
98+
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
99+
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
100+

0 commit comments

Comments
 (0)