Skip to content
Open
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
22 changes: 20 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2063,6 +2063,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var evolvingArrayTypes: EvolvingArrayType[] = [];
var undefinedProperties: SymbolTable = new Map();
var markerTypes = new Set<number>();
// Enum symbols whose member names are currently being late-bound, used to break the recursion
// that a computed enum member name referring back to the same enum would otherwise cause.
var enumsResolvingLateBoundNames = new Set<Symbol>();

var unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String);
var resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving);
Expand Down Expand Up @@ -13544,12 +13547,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function getDeclaredTypeOfEnum(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.declaredType) {
// A member with a dynamic (computed) name can refer back to a member of the same enum
// (e.g. `enum E { [object] = 1, object = 2 }`). Determining whether that name is
// late-bindable resolves the referenced member's type, which re-enters this function;
// while that resolution is in progress we must not try to late-bind such names again,
// otherwise we recurse until the stack overflows. Computed names are not permitted on
// enum members anyway (a separate error is reported), so treating them as non-bindable
// here only affects already-invalid code.
const resolvingLateBoundNames = enumsResolvingLateBoundNames.has(symbol);
if (!resolvingLateBoundNames) {
enumsResolvingLateBoundNames.add(symbol);
}
const memberTypeList: Type[] = [];
if (symbol.declarations) {
for (const declaration of symbol.declarations) {
if (declaration.kind === SyntaxKind.EnumDeclaration) {
for (const member of (declaration as EnumDeclaration).members) {
if (hasBindableName(member)) {
const bindable = resolvingLateBoundNames ? !hasDynamicName(member) : hasBindableName(member);
if (bindable) {
Comment on lines 13563 to +13567
const memberSymbol = getSymbolOfDeclaration(member);
const value = getEnumMemberValue(member).value;
const memberType = getFreshTypeOfLiteralType(
Expand All @@ -13564,14 +13579,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
}
if (!resolvingLateBoundNames) {
enumsResolvingLateBoundNames.delete(symbol);
}
const enumType = memberTypeList.length ?
getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined) :
createComputedEnumType(symbol);
if (enumType.flags & TypeFlags.Union) {
enumType.flags |= TypeFlags.EnumLiteral;
enumType.symbol = symbol;
}
links.declaredType = enumType;
links.declaredType ??= enumType;
}
return links.declaredType;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
enumComputedNameSelfReferenceCrash.ts(6,5): error TS1164: Computed property names are not allowed in enums.
enumComputedNameSelfReferenceCrash.ts(13,5): error TS1164: Computed property names are not allowed in enums.
enumComputedNameSelfReferenceCrash.ts(19,5): error TS1164: Computed property names are not allowed in enums.


==== enumComputedNameSelfReferenceCrash.ts (3 errors) ====
// Computed enum member names are not allowed, but a computed name that refers back to a member of
// the same enum must not send the checker into infinite recursion while resolving the enum type.
// https://github.com/microsoft/TypeScript/issues/63173

declare const enum E {
[object] = 1,
~~~~~~~~
!!! error TS1164: Computed property names are not allowed in enums.
A,
object = 10,
}
E.A.toString();

const enum F {
[F.A] = 1,
~~~~~
!!! error TS1164: Computed property names are not allowed in enums.
A = 2,
}
F.A.toString();

enum G {
[G.b] = 1,
~~~~~
!!! error TS1164: Computed property names are not allowed in enums.
b = 2,
}
G.b;

56 changes: 56 additions & 0 deletions tests/baselines/reference/enumComputedNameSelfReferenceCrash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//// [tests/cases/compiler/enumComputedNameSelfReferenceCrash.ts] ////

//// [enumComputedNameSelfReferenceCrash.ts]
// Computed enum member names are not allowed, but a computed name that refers back to a member of
// the same enum must not send the checker into infinite recursion while resolving the enum type.
// https://github.com/microsoft/TypeScript/issues/63173

declare const enum E {
[object] = 1,
A,
object = 10,
}
E.A.toString();

const enum F {
[F.A] = 1,
A = 2,
}
F.A.toString();

enum G {
[G.b] = 1,
b = 2,
}
G.b;


//// [enumComputedNameSelfReferenceCrash.js]
"use strict";
// Computed enum member names are not allowed, but a computed name that refers back to a member of
// the same enum must not send the checker into infinite recursion while resolving the enum type.
// https://github.com/microsoft/TypeScript/issues/63173
2 /* E.A */.toString();
2 /* F.A */.toString();
var G;
(function (G) {
G[G[G.b] = 1] = G.b;
G[G["b"] = 2] = "b";
})(G || (G = {}));
G.b;


//// [enumComputedNameSelfReferenceCrash.d.ts]
declare const enum E {
[object] = 1,
A = 2,
object = 10
}
declare const enum F {
[F.A] = 1,
A = 2
}
declare enum G {
[G.b] = 1,
b = 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//// [tests/cases/compiler/enumComputedNameSelfReferenceCrash.ts] ////

=== enumComputedNameSelfReferenceCrash.ts ===
// Computed enum member names are not allowed, but a computed name that refers back to a member of
// the same enum must not send the checker into infinite recursion while resolving the enum type.
// https://github.com/microsoft/TypeScript/issues/63173

declare const enum E {
>E : Symbol(E, Decl(enumComputedNameSelfReferenceCrash.ts, 0, 0))

[object] = 1,
>[object] : Symbol(E[object], Decl(enumComputedNameSelfReferenceCrash.ts, 4, 22))
>object : Symbol(E.object, Decl(enumComputedNameSelfReferenceCrash.ts, 6, 6))

A,
>A : Symbol(E.A, Decl(enumComputedNameSelfReferenceCrash.ts, 5, 17))

object = 10,
>object : Symbol(E.object, Decl(enumComputedNameSelfReferenceCrash.ts, 6, 6))
}
E.A.toString();
>E.A.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --))
>E.A : Symbol(E.A, Decl(enumComputedNameSelfReferenceCrash.ts, 5, 17))
>E : Symbol(E, Decl(enumComputedNameSelfReferenceCrash.ts, 0, 0))
>A : Symbol(E.A, Decl(enumComputedNameSelfReferenceCrash.ts, 5, 17))
>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --))

const enum F {
>F : Symbol(F, Decl(enumComputedNameSelfReferenceCrash.ts, 9, 15))

[F.A] = 1,
>[F.A] : Symbol(F[F.A], Decl(enumComputedNameSelfReferenceCrash.ts, 11, 14))
>F.A : Symbol(F.A, Decl(enumComputedNameSelfReferenceCrash.ts, 12, 14))
>F : Symbol(F, Decl(enumComputedNameSelfReferenceCrash.ts, 9, 15))
>A : Symbol(F.A, Decl(enumComputedNameSelfReferenceCrash.ts, 12, 14))

A = 2,
>A : Symbol(F.A, Decl(enumComputedNameSelfReferenceCrash.ts, 12, 14))
}
F.A.toString();
>F.A.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --))
>F.A : Symbol(F.A, Decl(enumComputedNameSelfReferenceCrash.ts, 12, 14))
>F : Symbol(F, Decl(enumComputedNameSelfReferenceCrash.ts, 9, 15))
>A : Symbol(F.A, Decl(enumComputedNameSelfReferenceCrash.ts, 12, 14))
>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --))

enum G {
>G : Symbol(G, Decl(enumComputedNameSelfReferenceCrash.ts, 15, 15))

[G.b] = 1,
>[G.b] : Symbol(G[G.b], Decl(enumComputedNameSelfReferenceCrash.ts, 17, 8))
>G.b : Symbol(G.b, Decl(enumComputedNameSelfReferenceCrash.ts, 18, 14))
>G : Symbol(G, Decl(enumComputedNameSelfReferenceCrash.ts, 15, 15))
>b : Symbol(G.b, Decl(enumComputedNameSelfReferenceCrash.ts, 18, 14))

b = 2,
>b : Symbol(G.b, Decl(enumComputedNameSelfReferenceCrash.ts, 18, 14))
}
G.b;
>G.b : Symbol(G.b, Decl(enumComputedNameSelfReferenceCrash.ts, 18, 14))
>G : Symbol(G, Decl(enumComputedNameSelfReferenceCrash.ts, 15, 15))
>b : Symbol(G.b, Decl(enumComputedNameSelfReferenceCrash.ts, 18, 14))

109 changes: 109 additions & 0 deletions tests/baselines/reference/enumComputedNameSelfReferenceCrash.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//// [tests/cases/compiler/enumComputedNameSelfReferenceCrash.ts] ////

=== enumComputedNameSelfReferenceCrash.ts ===
// Computed enum member names are not allowed, but a computed name that refers back to a member of
// the same enum must not send the checker into infinite recursion while resolving the enum type.
// https://github.com/microsoft/TypeScript/issues/63173

declare const enum E {
>E : E
> : ^

[object] = 1,
>[object] : E.__computed
> : ^^^^^^^^^^^^
>object : E.object
> : ^^^^^^^^
>1 : 1
> : ^

A,
>A : E.A
> : ^^^

object = 10,
>object : E.object
> : ^^^^^^^^
>10 : 10
> : ^^
}
E.A.toString();
>E.A.toString() : string
> : ^^^^^^
>E.A.toString : (radix?: number) => string
> : ^ ^^^ ^^^^^
>E.A : E.A
> : ^^^
>E : typeof E
> : ^^^^^^^^
>A : E.A
> : ^^^
>toString : (radix?: number) => string
> : ^ ^^^ ^^^^^

const enum F {
>F : F
> : ^

[F.A] = 1,
>[F.A] : F.__computed
> : ^^^^^^^^^^^^
>F.A : F
> : ^
>F : typeof F
> : ^^^^^^^^
>A : F
> : ^
>1 : 1
> : ^

A = 2,
>A : F.A
> : ^^^
>2 : 2
> : ^
}
F.A.toString();
>F.A.toString() : string
> : ^^^^^^
>F.A.toString : (radix?: number) => string
> : ^ ^^^ ^^^^^
>F.A : F
> : ^
>F : typeof F
> : ^^^^^^^^
>A : F
> : ^
>toString : (radix?: number) => string
> : ^ ^^^ ^^^^^

enum G {
>G : G
> : ^

[G.b] = 1,
>[G.b] : G.__computed
> : ^^^^^^^^^^^^
>G.b : G
> : ^
>G : typeof G
> : ^^^^^^^^
>b : G
> : ^
>1 : 1
> : ^

b = 2,
>b : G.b
> : ^^^
>2 : 2
> : ^
}
G.b;
>G.b : G
> : ^
>G : typeof G
> : ^^^^^^^^
>b : G
> : ^

25 changes: 25 additions & 0 deletions tests/cases/compiler/enumComputedNameSelfReferenceCrash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// @target: es2015
// @declaration: true

// Computed enum member names are not allowed, but a computed name that refers back to a member of
// the same enum must not send the checker into infinite recursion while resolving the enum type.
// https://github.com/microsoft/TypeScript/issues/63173

declare const enum E {
[object] = 1,
A,
object = 10,
}
E.A.toString();

const enum F {
[F.A] = 1,
A = 2,
}
F.A.toString();

enum G {
[G.b] = 1,
b = 2,
}
G.b;