Skip to content

Commit

Permalink
Improve error message for computed enums (microsoft#37790)
Browse files Browse the repository at this point in the history
* Add error message for computed enums

* Add test case for computed enums

* Accept baselines

* Fix returned value when error
  • Loading branch information
okmttdhr committed Apr 15, 2020
1 parent 92cd3ae commit 06e05f2
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 54 deletions.
8 changes: 7 additions & 1 deletion src/compiler/checker.ts
Expand Up @@ -33676,7 +33676,13 @@ namespace ts {
}
else {
// Only here do we need to check that the initializer is assignable to the enum type.
checkTypeAssignableTo(checkExpression(initializer), getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined);
const source = checkExpression(initializer);
if (!isTypeAssignableToKind(source, TypeFlags.NumberLike)) {
error(initializer, Diagnostics.Only_numeric_enums_can_have_computed_members_but_this_expression_has_type_0_If_you_do_not_need_exhaustiveness_checks_consider_using_an_object_literal_instead, typeToString(source));
}
else {
checkTypeAssignableTo(source, getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined);
}
}
return value;

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Expand Up @@ -5737,5 +5737,9 @@
"The intersection '{0}' was reduced to 'never' because property '{1}' exists in multiple constituents and is private in some.": {
"category": "Error",
"code": 18032
},
"Only numeric enums can have computed members, but this expression has type '{0}'. If you do not need exhaustiveness checks, consider using an object literal instead.": {
"category": "Error",
"code": 18033
}
}
8 changes: 4 additions & 4 deletions tests/baselines/reference/arrowFunctionContexts.errors.txt
@@ -1,8 +1,8 @@
tests/cases/conformance/expressions/functions/arrowFunctionContexts.ts(2,1): error TS2410: The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.
tests/cases/conformance/expressions/functions/arrowFunctionContexts.ts(30,9): error TS2322: Type '() => number' is not assignable to type 'E'.
tests/cases/conformance/expressions/functions/arrowFunctionContexts.ts(30,9): error TS18033: Only numeric enums can have computed members, but this expression has type '() => number'. If you do not need exhaustiveness checks, consider using an object literal instead.
tests/cases/conformance/expressions/functions/arrowFunctionContexts.ts(31,16): error TS2332: 'this' cannot be referenced in current location.
tests/cases/conformance/expressions/functions/arrowFunctionContexts.ts(43,5): error TS2410: The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.
tests/cases/conformance/expressions/functions/arrowFunctionContexts.ts(71,13): error TS2322: Type '() => number' is not assignable to type 'E'.
tests/cases/conformance/expressions/functions/arrowFunctionContexts.ts(71,13): error TS18033: Only numeric enums can have computed members, but this expression has type '() => number'. If you do not need exhaustiveness checks, consider using an object literal instead.
tests/cases/conformance/expressions/functions/arrowFunctionContexts.ts(72,20): error TS2332: 'this' cannot be referenced in current location.


Expand Down Expand Up @@ -40,7 +40,7 @@ tests/cases/conformance/expressions/functions/arrowFunctionContexts.ts(72,20): e
enum E {
x = () => 4, // Error expected
~~~~~~~
!!! error TS2322: Type '() => number' is not assignable to type 'E'.
!!! error TS18033: Only numeric enums can have computed members, but this expression has type '() => number'. If you do not need exhaustiveness checks, consider using an object literal instead.
y = (() => this).length // error, can't use this in enum
~~~~
!!! error TS2332: 'this' cannot be referenced in current location.
Expand Down Expand Up @@ -87,7 +87,7 @@ tests/cases/conformance/expressions/functions/arrowFunctionContexts.ts(72,20): e
enum E {
x = () => 4, // Error expected
~~~~~~~
!!! error TS2322: Type '() => number' is not assignable to type 'E'.
!!! error TS18033: Only numeric enums can have computed members, but this expression has type '() => number'. If you do not need exhaustiveness checks, consider using an object literal instead.
y = (() => this).length
~~~~
!!! error TS2332: 'this' cannot be referenced in current location.
Expand Down
54 changes: 31 additions & 23 deletions tests/baselines/reference/enumErrors.errors.txt
Expand Up @@ -2,28 +2,30 @@ tests/cases/conformance/enums/enumErrors.ts(2,6): error TS2431: Enum name cannot
tests/cases/conformance/enums/enumErrors.ts(3,6): error TS2431: Enum name cannot be 'number'.
tests/cases/conformance/enums/enumErrors.ts(4,6): error TS2431: Enum name cannot be 'string'.
tests/cases/conformance/enums/enumErrors.ts(5,6): error TS2431: Enum name cannot be 'boolean'.
tests/cases/conformance/enums/enumErrors.ts(9,9): error TS2322: Type 'Number' is not assignable to type 'E5'.
tests/cases/conformance/enums/enumErrors.ts(26,9): error TS2322: Type 'true' is not assignable to type 'E11'.
tests/cases/conformance/enums/enumErrors.ts(27,9): error TS2322: Type 'Date' is not assignable to type 'E11'.
tests/cases/conformance/enums/enumErrors.ts(28,9): error TS2322: Type 'Window & typeof globalThis' is not assignable to type 'E11'.
tests/cases/conformance/enums/enumErrors.ts(29,9): error TS2322: Type '{}' is not assignable to type 'E11'.
tests/cases/conformance/enums/enumErrors.ts(35,9): error TS2553: Computed values are not permitted in an enum with string valued members.
tests/cases/conformance/enums/enumErrors.ts(9,9): error TS18033: Only numeric enums can have computed members, but this expression has type 'Number'. If you do not need exhaustiveness checks, consider using an object literal instead.
tests/cases/conformance/enums/enumErrors.ts(26,9): error TS18033: Only numeric enums can have computed members, but this expression has type 'true'. If you do not need exhaustiveness checks, consider using an object literal instead.
tests/cases/conformance/enums/enumErrors.ts(27,9): error TS18033: Only numeric enums can have computed members, but this expression has type 'Date'. If you do not need exhaustiveness checks, consider using an object literal instead.
tests/cases/conformance/enums/enumErrors.ts(28,9): error TS18033: Only numeric enums can have computed members, but this expression has type 'Window & typeof globalThis'. If you do not need exhaustiveness checks, consider using an object literal instead.
tests/cases/conformance/enums/enumErrors.ts(29,9): error TS18033: Only numeric enums can have computed members, but this expression has type '{}'. If you do not need exhaustiveness checks, consider using an object literal instead.
tests/cases/conformance/enums/enumErrors.ts(30,9): error TS18033: Only numeric enums can have computed members, but this expression has type 'string'. If you do not need exhaustiveness checks, consider using an object literal instead.
tests/cases/conformance/enums/enumErrors.ts(36,9): error TS2553: Computed values are not permitted in an enum with string valued members.
tests/cases/conformance/enums/enumErrors.ts(37,9): error TS2553: Computed values are not permitted in an enum with string valued members.
tests/cases/conformance/enums/enumErrors.ts(38,9): error TS2553: Computed values are not permitted in an enum with string valued members.
tests/cases/conformance/enums/enumErrors.ts(46,18): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(47,24): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(47,26): error TS2452: An enum member cannot have a numeric name.
tests/cases/conformance/enums/enumErrors.ts(48,28): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(48,30): error TS2452: An enum member cannot have a numeric name.
tests/cases/conformance/enums/enumErrors.ts(48,31): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(51,16): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(51,22): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(51,30): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(51,33): error TS2452: An enum member cannot have a numeric name.
tests/cases/conformance/enums/enumErrors.ts(39,9): error TS2553: Computed values are not permitted in an enum with string valued members.
tests/cases/conformance/enums/enumErrors.ts(40,9): error TS2553: Computed values are not permitted in an enum with string valued members.
tests/cases/conformance/enums/enumErrors.ts(48,18): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(49,24): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(49,26): error TS2452: An enum member cannot have a numeric name.
tests/cases/conformance/enums/enumErrors.ts(50,28): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(50,30): error TS2452: An enum member cannot have a numeric name.
tests/cases/conformance/enums/enumErrors.ts(50,31): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(53,16): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(53,22): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(53,30): error TS1357: An enum member name must be followed by a ',', '=', or '}'.
tests/cases/conformance/enums/enumErrors.ts(53,33): error TS2452: An enum member cannot have a numeric name.


==== tests/cases/conformance/enums/enumErrors.ts (23 errors) ====
==== tests/cases/conformance/enums/enumErrors.ts (25 errors) ====
// Enum named with PredefinedTypes
enum any { }
~~~
Expand All @@ -42,7 +44,7 @@ tests/cases/conformance/enums/enumErrors.ts(51,33): error TS2452: An enum member
enum E5 {
C = new Number(30)
~~~~~~~~~~~~~~
!!! error TS2322: Type 'Number' is not assignable to type 'E5'.
!!! error TS18033: Only numeric enums can have computed members, but this expression has type 'Number'. If you do not need exhaustiveness checks, consider using an object literal instead.
}

enum E9 {
Expand All @@ -61,16 +63,19 @@ tests/cases/conformance/enums/enumErrors.ts(51,33): error TS2452: An enum member
enum E11 {
A = true,
~~~~
!!! error TS2322: Type 'true' is not assignable to type 'E11'.
!!! error TS18033: Only numeric enums can have computed members, but this expression has type 'true'. If you do not need exhaustiveness checks, consider using an object literal instead.
B = new Date(),
~~~~~~~~~~
!!! error TS2322: Type 'Date' is not assignable to type 'E11'.
!!! error TS18033: Only numeric enums can have computed members, but this expression has type 'Date'. If you do not need exhaustiveness checks, consider using an object literal instead.
C = window,
~~~~~~
!!! error TS2322: Type 'Window & typeof globalThis' is not assignable to type 'E11'.
D = {}
!!! error TS18033: Only numeric enums can have computed members, but this expression has type 'Window & typeof globalThis'. If you do not need exhaustiveness checks, consider using an object literal instead.
D = {},
~~
!!! error TS2322: Type '{}' is not assignable to type 'E11'.
!!! error TS18033: Only numeric enums can have computed members, but this expression has type '{}'. If you do not need exhaustiveness checks, consider using an object literal instead.
E = (() => 'foo')(),
~~~~~~~~~~~~~~~
!!! error TS18033: Only numeric enums can have computed members, but this expression has type 'string'. If you do not need exhaustiveness checks, consider using an object literal instead.
}

// Enum with string valued member and computed member initializers
Expand All @@ -87,6 +92,9 @@ tests/cases/conformance/enums/enumErrors.ts(51,33): error TS2452: An enum member
!!! error TS2553: Computed values are not permitted in an enum with string valued members.
E = 1 + 1,
~~~~~
!!! error TS2553: Computed values are not permitted in an enum with string valued members.
F = (() => 'foo')(),
~~~~~~~~~~~~~~~
!!! error TS2553: Computed values are not permitted in an enum with string valued members.
}

Expand Down
6 changes: 5 additions & 1 deletion tests/baselines/reference/enumErrors.js
Expand Up @@ -27,7 +27,8 @@ enum E11 {
A = true,
B = new Date(),
C = window,
D = {}
D = {},
E = (() => 'foo')(),
}

// Enum with string valued member and computed member initializers
Expand All @@ -37,6 +38,7 @@ enum E12 {
C = window,
D = {},
E = 1 + 1,
F = (() => 'foo')(),
}

// Enum with incorrect syntax
Expand Down Expand Up @@ -90,6 +92,7 @@ var E11;
E11[E11["B"] = new Date()] = "B";
E11[E11["C"] = window] = "C";
E11[E11["D"] = {}] = "D";
E11[E11["E"] = (function () { return 'foo'; })()] = "E";
})(E11 || (E11 = {}));
// Enum with string valued member and computed member initializers
var E12;
Expand All @@ -99,6 +102,7 @@ var E12;
E12[E12["C"] = 0] = "C";
E12[E12["D"] = 0] = "D";
E12[E12["E"] = 0] = "E";
E12[E12["F"] = 0] = "F";
})(E12 || (E12 = {}));
// Enum with incorrect syntax
var E13;
Expand Down
52 changes: 29 additions & 23 deletions tests/baselines/reference/enumErrors.symbols
Expand Up @@ -65,62 +65,68 @@ enum E11 {
>C : Symbol(E11.C, Decl(enumErrors.ts, 26, 19))
>window : Symbol(window, Decl(lib.dom.d.ts, --, --))

D = {}
D = {},
>D : Symbol(E11.D, Decl(enumErrors.ts, 27, 15))

E = (() => 'foo')(),
>E : Symbol(E11.E, Decl(enumErrors.ts, 28, 11))
}

// Enum with string valued member and computed member initializers
enum E12 {
>E12 : Symbol(E12, Decl(enumErrors.ts, 29, 1))
>E12 : Symbol(E12, Decl(enumErrors.ts, 30, 1))

A = '',
>A : Symbol(E12.A, Decl(enumErrors.ts, 32, 10))
>A : Symbol(E12.A, Decl(enumErrors.ts, 33, 10))

B = new Date(),
>B : Symbol(E12.B, Decl(enumErrors.ts, 33, 11))
>B : Symbol(E12.B, Decl(enumErrors.ts, 34, 11))
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))

C = window,
>C : Symbol(E12.C, Decl(enumErrors.ts, 34, 19))
>C : Symbol(E12.C, Decl(enumErrors.ts, 35, 19))
>window : Symbol(window, Decl(lib.dom.d.ts, --, --))

D = {},
>D : Symbol(E12.D, Decl(enumErrors.ts, 35, 15))
>D : Symbol(E12.D, Decl(enumErrors.ts, 36, 15))

E = 1 + 1,
>E : Symbol(E12.E, Decl(enumErrors.ts, 36, 11))
>E : Symbol(E12.E, Decl(enumErrors.ts, 37, 11))

F = (() => 'foo')(),
>F : Symbol(E12.F, Decl(enumErrors.ts, 38, 14))
}

// Enum with incorrect syntax
enum E13 {
>E13 : Symbol(E13, Decl(enumErrors.ts, 38, 1))
>E13 : Symbol(E13, Decl(enumErrors.ts, 40, 1))

postComma,
>postComma : Symbol(E13.postComma, Decl(enumErrors.ts, 41, 10))
>postComma : Symbol(E13.postComma, Decl(enumErrors.ts, 43, 10))

postValueComma = 1,
>postValueComma : Symbol(E13.postValueComma, Decl(enumErrors.ts, 42, 14))
>postValueComma : Symbol(E13.postValueComma, Decl(enumErrors.ts, 44, 14))

postSemicolon;
>postSemicolon : Symbol(E13.postSemicolon, Decl(enumErrors.ts, 43, 23))
>postSemicolon : Symbol(E13.postSemicolon, Decl(enumErrors.ts, 45, 23))

postColonValueComma: 2,
>postColonValueComma : Symbol(E13.postColonValueComma, Decl(enumErrors.ts, 45, 18))
>2 : Symbol(E13[2], Decl(enumErrors.ts, 46, 24))
>postColonValueComma : Symbol(E13.postColonValueComma, Decl(enumErrors.ts, 47, 18))
>2 : Symbol(E13[2], Decl(enumErrors.ts, 48, 24))

postColonValueSemicolon: 3;
>postColonValueSemicolon : Symbol(E13.postColonValueSemicolon, Decl(enumErrors.ts, 46, 27))
>3 : Symbol(E13[3], Decl(enumErrors.ts, 47, 28))
>postColonValueSemicolon : Symbol(E13.postColonValueSemicolon, Decl(enumErrors.ts, 48, 27))
>3 : Symbol(E13[3], Decl(enumErrors.ts, 49, 28))

};

enum E14 { a, b: any "hello" += 1, c, d}
>E14 : Symbol(E14, Decl(enumErrors.ts, 48, 2))
>a : Symbol(E14.a, Decl(enumErrors.ts, 50, 10))
>b : Symbol(E14.b, Decl(enumErrors.ts, 50, 13))
>any : Symbol(E14.any, Decl(enumErrors.ts, 50, 16))
>"hello" : Symbol(E14["hello"], Decl(enumErrors.ts, 50, 20))
>1 : Symbol(E14[1], Decl(enumErrors.ts, 50, 31))
>c : Symbol(E14.c, Decl(enumErrors.ts, 50, 34))
>d : Symbol(E14.d, Decl(enumErrors.ts, 50, 37))
>E14 : Symbol(E14, Decl(enumErrors.ts, 50, 2))
>a : Symbol(E14.a, Decl(enumErrors.ts, 52, 10))
>b : Symbol(E14.b, Decl(enumErrors.ts, 52, 13))
>any : Symbol(E14.any, Decl(enumErrors.ts, 52, 16))
>"hello" : Symbol(E14["hello"], Decl(enumErrors.ts, 52, 20))
>1 : Symbol(E14[1], Decl(enumErrors.ts, 52, 31))
>c : Symbol(E14.c, Decl(enumErrors.ts, 52, 34))
>d : Symbol(E14.d, Decl(enumErrors.ts, 52, 37))

16 changes: 15 additions & 1 deletion tests/baselines/reference/enumErrors.types
Expand Up @@ -69,9 +69,16 @@ enum E11 {
>C : E11
>window : Window & typeof globalThis

D = {}
D = {},
>D : E11
>{} : {}

E = (() => 'foo')(),
>E : E11
>(() => 'foo')() : string
>(() => 'foo') : () => string
>() => 'foo' : () => string
>'foo' : "foo"
}

// Enum with string valued member and computed member initializers
Expand Down Expand Up @@ -100,6 +107,13 @@ enum E12 {
>1 + 1 : number
>1 : 1
>1 : 1

F = (() => 'foo')(),
>F : E12.B
>(() => 'foo')() : string
>(() => 'foo') : () => string
>() => 'foo' : () => string
>'foo' : "foo"
}

// Enum with incorrect syntax
Expand Down
4 changes: 3 additions & 1 deletion tests/cases/conformance/enums/enumErrors.ts
Expand Up @@ -26,7 +26,8 @@ enum E11 {
A = true,
B = new Date(),
C = window,
D = {}
D = {},
E = (() => 'foo')(),
}

// Enum with string valued member and computed member initializers
Expand All @@ -36,6 +37,7 @@ enum E12 {
C = window,
D = {},
E = 1 + 1,
F = (() => 'foo')(),
}

// Enum with incorrect syntax
Expand Down

0 comments on commit 06e05f2

Please sign in to comment.