Skip to content

Commit 95210ac

Browse files
authored
Merge pull request #10784 from Microsoft/enum-number-assignability-in-unions
Number and enum literal are assignable to enums, even inside unions
2 parents 9812ab5 + 5ed0653 commit 95210ac

14 files changed

+328
-29
lines changed

src/compiler/checker.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ namespace ts {
334334
const assignableRelation = createMap<RelationComparisonResult>();
335335
const comparableRelation = createMap<RelationComparisonResult>();
336336
const identityRelation = createMap<RelationComparisonResult>();
337+
const enumRelation = createMap<boolean>();
337338

338339
// This is for caching the result of getSymbolDisplayBuilder. Do not access directly.
339340
let _displayBuilder: SymbolDisplayBuilder;
@@ -6206,8 +6207,14 @@ namespace ts {
62066207
if (source === target) {
62076208
return true;
62086209
}
6209-
if (source.symbol.name !== target.symbol.name || !(source.symbol.flags & SymbolFlags.RegularEnum) || !(target.symbol.flags & SymbolFlags.RegularEnum)) {
6210-
return false;
6210+
const id = source.id + "," + target.id;
6211+
if (enumRelation[id] !== undefined) {
6212+
return enumRelation[id];
6213+
}
6214+
if (source.symbol.name !== target.symbol.name ||
6215+
!(source.symbol.flags & SymbolFlags.RegularEnum) || !(target.symbol.flags & SymbolFlags.RegularEnum) ||
6216+
(source.flags & TypeFlags.Union) !== (target.flags & TypeFlags.Union)) {
6217+
return enumRelation[id] = false;
62116218
}
62126219
const targetEnumType = getTypeOfSymbol(target.symbol);
62136220
for (const property of getPropertiesOfType(getTypeOfSymbol(source.symbol))) {
@@ -6218,11 +6225,11 @@ namespace ts {
62186225
errorReporter(Diagnostics.Property_0_is_missing_in_type_1, property.name,
62196226
typeToString(target, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType));
62206227
}
6221-
return false;
6228+
return enumRelation[id] = false;
62226229
}
62236230
}
62246231
}
6225-
return true;
6232+
return enumRelation[id] = true;
62266233
}
62276234

62286235
function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map<RelationComparisonResult>, errorReporter?: ErrorReporter) {
@@ -6237,8 +6244,18 @@ namespace ts {
62376244
if (source.flags & TypeFlags.Null && (!strictNullChecks || target.flags & TypeFlags.Null)) return true;
62386245
if (relation === assignableRelation || relation === comparableRelation) {
62396246
if (source.flags & TypeFlags.Any) return true;
6240-
if (source.flags & (TypeFlags.Number | TypeFlags.NumberLiteral) && target.flags & TypeFlags.Enum) return true;
6241-
if (source.flags & TypeFlags.NumberLiteral && target.flags & TypeFlags.EnumLiteral && (<LiteralType>source).text === (<LiteralType>target).text) return true;
6247+
if ((source.flags & TypeFlags.Number | source.flags & TypeFlags.NumberLiteral) && target.flags & TypeFlags.EnumLike) return true;
6248+
if (source.flags & TypeFlags.EnumLiteral &&
6249+
target.flags & TypeFlags.EnumLiteral &&
6250+
(<LiteralType>source).text === (<LiteralType>target).text &&
6251+
isEnumTypeRelatedTo((<EnumLiteralType>source).baseType, (<EnumLiteralType>target).baseType, errorReporter)) {
6252+
return true;
6253+
}
6254+
if (source.flags & TypeFlags.EnumLiteral &&
6255+
target.flags & TypeFlags.Enum &&
6256+
isEnumTypeRelatedTo(<EnumType>target, (<EnumLiteralType>source).baseType, errorReporter)) {
6257+
return true;
6258+
}
62426259
}
62436260
return false;
62446261
}

tests/baselines/reference/enumAssignmentCompat.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
tests/cases/compiler/enumAssignmentCompat.ts(26,5): error TS2322: Type 'typeof W' is not assignable to type 'number'.
22
tests/cases/compiler/enumAssignmentCompat.ts(28,5): error TS2322: Type 'W.a' is not assignable to type 'typeof W'.
33
tests/cases/compiler/enumAssignmentCompat.ts(30,5): error TS2322: Type '3' is not assignable to type 'typeof W'.
4-
tests/cases/compiler/enumAssignmentCompat.ts(31,5): error TS2322: Type '4' is not assignable to type 'W.a'.
54
tests/cases/compiler/enumAssignmentCompat.ts(32,5): error TS2322: Type 'W.a' is not assignable to type 'WStatic'.
65
tests/cases/compiler/enumAssignmentCompat.ts(33,5): error TS2322: Type '5' is not assignable to type 'WStatic'.
76

87

9-
==== tests/cases/compiler/enumAssignmentCompat.ts (6 errors) ====
8+
==== tests/cases/compiler/enumAssignmentCompat.ts (5 errors) ====
109
module W {
1110
export class D { }
1211
}
@@ -44,8 +43,6 @@ tests/cases/compiler/enumAssignmentCompat.ts(33,5): error TS2322: Type '5' is no
4443
~
4544
!!! error TS2322: Type '3' is not assignable to type 'typeof W'.
4645
var e: typeof W.a = 4;
47-
~
48-
!!! error TS2322: Type '4' is not assignable to type 'W.a'.
4946
var f: WStatic = W.a; // error
5047
~
5148
!!! error TS2322: Type 'W.a' is not assignable to type 'WStatic'.

tests/baselines/reference/enumAssignmentCompat2.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
tests/cases/compiler/enumAssignmentCompat2.ts(25,5): error TS2322: Type 'typeof W' is not assignable to type 'number'.
22
tests/cases/compiler/enumAssignmentCompat2.ts(27,5): error TS2322: Type 'W.a' is not assignable to type 'typeof W'.
33
tests/cases/compiler/enumAssignmentCompat2.ts(29,5): error TS2322: Type '3' is not assignable to type 'typeof W'.
4-
tests/cases/compiler/enumAssignmentCompat2.ts(30,5): error TS2322: Type '4' is not assignable to type 'W.a'.
54
tests/cases/compiler/enumAssignmentCompat2.ts(31,5): error TS2322: Type 'W.a' is not assignable to type 'WStatic'.
65
tests/cases/compiler/enumAssignmentCompat2.ts(32,5): error TS2322: Type '5' is not assignable to type 'WStatic'.
76

87

9-
==== tests/cases/compiler/enumAssignmentCompat2.ts (6 errors) ====
8+
==== tests/cases/compiler/enumAssignmentCompat2.ts (5 errors) ====
109
enum W {
1110

1211
a, b, c,
@@ -43,8 +42,6 @@ tests/cases/compiler/enumAssignmentCompat2.ts(32,5): error TS2322: Type '5' is n
4342
~
4443
!!! error TS2322: Type '3' is not assignable to type 'typeof W'.
4544
var e: typeof W.a = 4;
46-
~
47-
!!! error TS2322: Type '4' is not assignable to type 'W.a'.
4845
var f: WStatic = W.a; // error
4946
~
5047
!!! error TS2322: Type 'W.a' is not assignable to type 'WStatic'.

tests/baselines/reference/enumAssignmentCompat3.errors.txt

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
tests/cases/compiler/enumAssignmentCompat3.ts(68,1): error TS2322: Type 'Abcd.E' is not assignable to type 'First.E'.
2-
Property 'd' is missing in type 'First.E'.
1+
tests/cases/compiler/enumAssignmentCompat3.ts(68,1): error TS2324: Property 'd' is missing in type 'First.E'.
32
tests/cases/compiler/enumAssignmentCompat3.ts(70,1): error TS2322: Type 'Cd.E' is not assignable to type 'First.E'.
43
Property 'd' is missing in type 'First.E'.
54
tests/cases/compiler/enumAssignmentCompat3.ts(71,1): error TS2322: Type 'Nope' is not assignable to type 'E'.
6-
tests/cases/compiler/enumAssignmentCompat3.ts(75,1): error TS2322: Type 'First.E' is not assignable to type 'Ab.E'.
7-
Property 'c' is missing in type 'Ab.E'.
5+
tests/cases/compiler/enumAssignmentCompat3.ts(72,1): error TS2322: Type 'Decl.E' is not assignable to type 'First.E'.
6+
tests/cases/compiler/enumAssignmentCompat3.ts(75,1): error TS2324: Property 'c' is missing in type 'Ab.E'.
87
tests/cases/compiler/enumAssignmentCompat3.ts(76,1): error TS2322: Type 'First.E' is not assignable to type 'Cd.E'.
9-
Property 'a' is missing in type 'Cd.E'.
108
tests/cases/compiler/enumAssignmentCompat3.ts(77,1): error TS2322: Type 'E' is not assignable to type 'Nope'.
9+
tests/cases/compiler/enumAssignmentCompat3.ts(78,1): error TS2322: Type 'First.E' is not assignable to type 'Decl.E'.
1110
tests/cases/compiler/enumAssignmentCompat3.ts(82,1): error TS2322: Type 'Const.E' is not assignable to type 'First.E'.
1211
tests/cases/compiler/enumAssignmentCompat3.ts(83,1): error TS2322: Type 'First.E' is not assignable to type 'Const.E'.
13-
tests/cases/compiler/enumAssignmentCompat3.ts(86,1): error TS2322: Type 'Merged.E' is not assignable to type 'First.E'.
14-
Property 'd' is missing in type 'First.E'.
12+
tests/cases/compiler/enumAssignmentCompat3.ts(86,1): error TS2324: Property 'd' is missing in type 'First.E'.
1513

1614

17-
==== tests/cases/compiler/enumAssignmentCompat3.ts (9 errors) ====
15+
==== tests/cases/compiler/enumAssignmentCompat3.ts (11 errors) ====
1816
namespace First {
1917
export enum E {
2018
a, b, c,
@@ -84,8 +82,7 @@ tests/cases/compiler/enumAssignmentCompat3.ts(86,1): error TS2322: Type 'Merged.
8482
abc = secondAbc; // ok
8583
abc = secondAbcd; // missing 'd'
8684
~~~
87-
!!! error TS2322: Type 'Abcd.E' is not assignable to type 'First.E'.
88-
!!! error TS2322: Property 'd' is missing in type 'First.E'.
85+
!!! error TS2324: Property 'd' is missing in type 'First.E'.
8986
abc = secondAb; // ok
9087
abc = secondCd; // missing 'd'
9188
~~~
@@ -95,20 +92,22 @@ tests/cases/compiler/enumAssignmentCompat3.ts(86,1): error TS2322: Type 'Merged.
9592
~~~
9693
!!! error TS2322: Type 'Nope' is not assignable to type 'E'.
9794
abc = decl; // ok
95+
~~~
96+
!!! error TS2322: Type 'Decl.E' is not assignable to type 'First.E'.
9897
secondAbc = abc; // ok
9998
secondAbcd = abc; // ok
10099
secondAb = abc; // missing 'c'
101100
~~~~~~~~
102-
!!! error TS2322: Type 'First.E' is not assignable to type 'Ab.E'.
103-
!!! error TS2322: Property 'c' is missing in type 'Ab.E'.
101+
!!! error TS2324: Property 'c' is missing in type 'Ab.E'.
104102
secondCd = abc; // missing 'a' and 'b'
105103
~~~~~~~~
106104
!!! error TS2322: Type 'First.E' is not assignable to type 'Cd.E'.
107-
!!! error TS2322: Property 'a' is missing in type 'Cd.E'.
108105
nope = abc; // nope!
109106
~~~~
110107
!!! error TS2322: Type 'E' is not assignable to type 'Nope'.
111108
decl = abc; // ok
109+
~~~~
110+
!!! error TS2322: Type 'First.E' is not assignable to type 'Decl.E'.
112111

113112
// const is only assignable to itself
114113
k = k;
@@ -122,8 +121,7 @@ tests/cases/compiler/enumAssignmentCompat3.ts(86,1): error TS2322: Type 'Merged.
122121
// merged enums compare all their members
123122
abc = merged; // missing 'd'
124123
~~~
125-
!!! error TS2322: Type 'Merged.E' is not assignable to type 'First.E'.
126-
!!! error TS2322: Property 'd' is missing in type 'First.E'.
124+
!!! error TS2324: Property 'd' is missing in type 'First.E'.
127125
merged = abc; // ok
128126
abc = merged2; // ok
129127
merged2 = abc; // ok
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
tests/cases/compiler/enumAssignmentCompat5.ts(20,9): error TS2535: Enum type 'Computed' has members with initializers that are not literals.
2+
3+
4+
==== tests/cases/compiler/enumAssignmentCompat5.ts (1 errors) ====
5+
enum E {
6+
A, B, C
7+
}
8+
enum Computed {
9+
A = 1 << 1,
10+
B = 1 << 2,
11+
C = 1 << 3,
12+
}
13+
let n: number;
14+
let e: E = n; // ok because it's too inconvenient otherwise
15+
e = 0; // ok, in range
16+
e = 4; // ok, out of range, but allowed computed enums don't have all members
17+
let a: E.A = 0; // ok, A === 0
18+
a = 2; // error, 2 !== 0
19+
a = n; // ok
20+
21+
let c: Computed = n; // ok
22+
c = n; // ok
23+
c = 4; // ok
24+
let ca: Computed.A = 1; // error, Computed.A isn't a literal type because Computed has no enum literals
25+
~~~~~~~~~~
26+
!!! error TS2535: Enum type 'Computed' has members with initializers that are not literals.
27+
28+
29+
30+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//// [enumAssignmentCompat5.ts]
2+
enum E {
3+
A, B, C
4+
}
5+
enum Computed {
6+
A = 1 << 1,
7+
B = 1 << 2,
8+
C = 1 << 3,
9+
}
10+
let n: number;
11+
let e: E = n; // ok because it's too inconvenient otherwise
12+
e = 0; // ok, in range
13+
e = 4; // ok, out of range, but allowed computed enums don't have all members
14+
let a: E.A = 0; // ok, A === 0
15+
a = 2; // error, 2 !== 0
16+
a = n; // ok
17+
18+
let c: Computed = n; // ok
19+
c = n; // ok
20+
c = 4; // ok
21+
let ca: Computed.A = 1; // error, Computed.A isn't a literal type because Computed has no enum literals
22+
23+
24+
25+
26+
27+
//// [enumAssignmentCompat5.js]
28+
var E;
29+
(function (E) {
30+
E[E["A"] = 0] = "A";
31+
E[E["B"] = 1] = "B";
32+
E[E["C"] = 2] = "C";
33+
})(E || (E = {}));
34+
var Computed;
35+
(function (Computed) {
36+
Computed[Computed["A"] = 2] = "A";
37+
Computed[Computed["B"] = 4] = "B";
38+
Computed[Computed["C"] = 8] = "C";
39+
})(Computed || (Computed = {}));
40+
var n;
41+
var e = n; // ok because it's too inconvenient otherwise
42+
e = 0; // ok, in range
43+
e = 4; // ok, out of range, but allowed computed enums don't have all members
44+
var a = 0; // ok, A === 0
45+
a = 2; // error, 2 !== 0
46+
a = n; // ok
47+
var c = n; // ok
48+
c = n; // ok
49+
c = 4; // ok
50+
var ca = 1; // error, Computed.A isn't a literal type because Computed has no enum literals
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
tests/cases/compiler/enumLiteralAssignableToEnumInsideUnion.ts(24,7): error TS2322: Type 'Foo' is not assignable to type 'boolean | Foo'.
2+
tests/cases/compiler/enumLiteralAssignableToEnumInsideUnion.ts(25,7): error TS2322: Type 'Foo' is not assignable to type 'boolean | Foo'.
3+
tests/cases/compiler/enumLiteralAssignableToEnumInsideUnion.ts(26,7): error TS2322: Type 'Foo' is not assignable to type 'boolean | Foo.B'.
4+
tests/cases/compiler/enumLiteralAssignableToEnumInsideUnion.ts(27,7): error TS2322: Type 'Foo' is not assignable to type 'boolean | Foo.A'.
5+
6+
7+
==== tests/cases/compiler/enumLiteralAssignableToEnumInsideUnion.ts (4 errors) ====
8+
module X {
9+
export enum Foo {
10+
A, B
11+
}
12+
}
13+
module Y {
14+
export enum Foo {
15+
A, B
16+
}
17+
}
18+
module Z {
19+
export enum Foo {
20+
A = 1 << 1,
21+
B = 1 << 2,
22+
}
23+
}
24+
module Ka {
25+
export enum Foo {
26+
A = 1 << 10,
27+
B = 1 << 11,
28+
}
29+
}
30+
const e0: X.Foo | boolean = Y.Foo.A; // ok
31+
const e1: X.Foo | boolean = Z.Foo.A; // not legal, Z is computed
32+
~~
33+
!!! error TS2322: Type 'Foo' is not assignable to type 'boolean | Foo'.
34+
const e2: X.Foo.A | X.Foo.B | boolean = Z.Foo.A; // still not legal
35+
~~
36+
!!! error TS2322: Type 'Foo' is not assignable to type 'boolean | Foo'.
37+
const e3: X.Foo.B | boolean = Z.Foo.A; // not legal
38+
~~
39+
!!! error TS2322: Type 'Foo' is not assignable to type 'boolean | Foo.B'.
40+
const e4: X.Foo.A | boolean = Z.Foo.A; // not legal either because Z.Foo is computed and Z.Foo.A is not necessarily assignable to X.Foo.A
41+
~~
42+
!!! error TS2322: Type 'Foo' is not assignable to type 'boolean | Foo.A'.
43+
const e5: Ka.Foo | boolean = Z.Foo.A; // ok
44+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//// [enumLiteralAssignableToEnumInsideUnion.ts]
2+
module X {
3+
export enum Foo {
4+
A, B
5+
}
6+
}
7+
module Y {
8+
export enum Foo {
9+
A, B
10+
}
11+
}
12+
module Z {
13+
export enum Foo {
14+
A = 1 << 1,
15+
B = 1 << 2,
16+
}
17+
}
18+
module Ka {
19+
export enum Foo {
20+
A = 1 << 10,
21+
B = 1 << 11,
22+
}
23+
}
24+
const e0: X.Foo | boolean = Y.Foo.A; // ok
25+
const e1: X.Foo | boolean = Z.Foo.A; // not legal, Z is computed
26+
const e2: X.Foo.A | X.Foo.B | boolean = Z.Foo.A; // still not legal
27+
const e3: X.Foo.B | boolean = Z.Foo.A; // not legal
28+
const e4: X.Foo.A | boolean = Z.Foo.A; // not legal either because Z.Foo is computed and Z.Foo.A is not necessarily assignable to X.Foo.A
29+
const e5: Ka.Foo | boolean = Z.Foo.A; // ok
30+
31+
32+
//// [enumLiteralAssignableToEnumInsideUnion.js]
33+
var X;
34+
(function (X) {
35+
(function (Foo) {
36+
Foo[Foo["A"] = 0] = "A";
37+
Foo[Foo["B"] = 1] = "B";
38+
})(X.Foo || (X.Foo = {}));
39+
var Foo = X.Foo;
40+
})(X || (X = {}));
41+
var Y;
42+
(function (Y) {
43+
(function (Foo) {
44+
Foo[Foo["A"] = 0] = "A";
45+
Foo[Foo["B"] = 1] = "B";
46+
})(Y.Foo || (Y.Foo = {}));
47+
var Foo = Y.Foo;
48+
})(Y || (Y = {}));
49+
var Z;
50+
(function (Z) {
51+
(function (Foo) {
52+
Foo[Foo["A"] = 2] = "A";
53+
Foo[Foo["B"] = 4] = "B";
54+
})(Z.Foo || (Z.Foo = {}));
55+
var Foo = Z.Foo;
56+
})(Z || (Z = {}));
57+
var Ka;
58+
(function (Ka) {
59+
(function (Foo) {
60+
Foo[Foo["A"] = 1024] = "A";
61+
Foo[Foo["B"] = 2048] = "B";
62+
})(Ka.Foo || (Ka.Foo = {}));
63+
var Foo = Ka.Foo;
64+
})(Ka || (Ka = {}));
65+
var e0 = Y.Foo.A; // ok
66+
var e1 = Z.Foo.A; // not legal, Z is computed
67+
var e2 = Z.Foo.A; // still not legal
68+
var e3 = Z.Foo.A; // not legal
69+
var e4 = Z.Foo.A; // not legal either because Z.Foo is computed and Z.Foo.A is not necessarily assignable to X.Foo.A
70+
var e5 = Z.Foo.A; // ok
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//// [numberAssignableToEnumInsideUnion.ts]
2+
enum E { A, B }
3+
let n: number;
4+
let z: E | boolean = n;
5+
6+
7+
//// [numberAssignableToEnumInsideUnion.js]
8+
var E;
9+
(function (E) {
10+
E[E["A"] = 0] = "A";
11+
E[E["B"] = 1] = "B";
12+
})(E || (E = {}));
13+
var n;
14+
var z = n;

0 commit comments

Comments
 (0)