diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 5a5a155797c24..0de4889e268ca 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -16244,9 +16244,7 @@ func (c *Checker) resolveEntityName(name *ast.Node, meaning ast.SymbolFlags, ign c.markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), nil) } // We know a symbol with the given meaning exists along the alias chain, so resolve until we find it. - for symbol.Flags()&meaning == 0 && !dontResolveAlias && symbol.Flags()&ast.SymbolFlagsAlias != 0 { - symbol = c.resolveAlias(symbol) - } + symbol = c.resolveAliasRecursively(symbol, meaning, dontResolveAlias) } return symbol } @@ -16687,6 +16685,16 @@ func (c *Checker) ResolveAlias(symbol *ast.Symbol) (*ast.Symbol, bool) { return resolved, resolved != c.unknownSymbol } +// resolveAliasRecursively follows alias symbols until one with the requested meaning is found +// (or dontResolveAlias is set). Used by resolveEntityName and constant enum ElementAccess evaluation — +// the only places that fully walk the alias chain for a meaning. +func (c *Checker) resolveAliasRecursively(symbol *ast.Symbol, meaning ast.SymbolFlags, dontResolveAlias bool) *ast.Symbol { + for symbol.Flags()&meaning == 0 && !dontResolveAlias && symbol.Flags()&ast.SymbolFlagsAlias != 0 { + symbol = c.resolveAlias(symbol) + } + return symbol +} + // Resolve an alias symbol to the first target symbol in the resolution chain that includes some other // meaning. Pure aliases are eagerly resolved and any type-only markers are back-propagated to the original // symbol. The function panics if the argument is not a symbol with an alias meaning. @@ -24769,11 +24777,8 @@ func (c *Checker) evaluateEntity(expr *ast.Node, location *ast.Node) evaluator.R return evaluator.NewResult(jsnum.FromString(expr.Text()), false, false, false) } } - if symbol.Flags()&ast.SymbolFlagsEnumMember != 0 { - if location != nil { - return c.evaluateEnumMember(expr, symbol, location) - } - return c.getEnumMemberValue(symbol.ValueDeclaration()) + if result, ok := c.tryEvaluateEnumMemberSymbol(expr, symbol, location); ok { + return result } if c.isConstantVariable(symbol) { declaration := symbol.ValueDeclaration() @@ -24795,10 +24800,11 @@ func (c *Checker) evaluateEntity(expr *ast.Node, location *ast.Node) evaluator.R name := expr.AsElementAccessExpression().ArgumentExpression.Text() member := rootSymbol.Exports().Get(name) if member != nil { - if location != nil { - return c.evaluateEnumMember(expr, member, location) + // e.g. export import C = F.A — walk aliases until a Value meaning appears. + member = c.resolveAliasRecursively(member, ast.SymbolFlagsValue, false /*dontResolveAlias*/) + if result, ok := c.tryEvaluateEnumMemberSymbol(expr, member, location); ok { + return result } - return c.getEnumMemberValue(member.ValueDeclaration()) } } } @@ -24807,6 +24813,19 @@ func (c *Checker) evaluateEntity(expr *ast.Node, location *ast.Node) evaluator.R panic("Unhandled case in evaluateEntity") } +// tryEvaluateEnumMemberSymbol evaluates a symbol as a constant enum member when applicable. +// Shared by Identifier/PropertyAccess and ElementAccess paths in evaluateEntity. Merged +// namespace+enum symbols can export non-enum members (e.g. a class); those return ok=false. +func (c *Checker) tryEvaluateEnumMemberSymbol(expr *ast.Node, symbol *ast.Symbol, location *ast.Node) (evaluator.Result, bool) { + if symbol.Flags()&ast.SymbolFlagsEnumMember == 0 { + return evaluator.NewResult(nil, false, false, false), false + } + if location != nil { + return c.evaluateEnumMember(expr, symbol, location), true + } + return c.getEnumMemberValue(symbol.ValueDeclaration()), true +} + func (c *Checker) evaluateEnumMember(expr *ast.Node, symbol *ast.Symbol, location *ast.Node) evaluator.Result { declaration := symbol.ValueDeclaration() if declaration == nil || declaration == location { diff --git a/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.errors.txt b/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.errors.txt new file mode 100644 index 0000000000000..7818ffce72b99 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.errors.txt @@ -0,0 +1,28 @@ +enumInitializerNamespaceExportElementAccess.ts(8,9): error TS18033: Type 'typeof C' is not assignable to type 'number' as required for computed enum member values. + + +==== enumInitializerNamespaceExportElementAccess.ts (1 errors) ==== + // Namespace export merged with enum must not be treated as an enum member via E["C"]. + // Previously panicked: Unhandled case in Node.MemberList: KindModuleBlock + + namespace E { + export class C {} + } + enum E { + D = E["C"], + ~~~~~~ +!!! error TS18033: Type 'typeof C' is not assignable to type 'number' as required for computed enum member values. + } + + // Alias exports that resolve to enum members must still constant-evaluate (like E.C). + enum F { + A = 1, + } + namespace E2 { + export import C = F.A; + } + enum E2 { + D = E2["C"], + Next, + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.js b/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.js new file mode 100644 index 0000000000000..e5312dbec4852 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.js @@ -0,0 +1,52 @@ +//// [tests/cases/compiler/enumInitializerNamespaceExportElementAccess.ts] //// + +//// [enumInitializerNamespaceExportElementAccess.ts] +// Namespace export merged with enum must not be treated as an enum member via E["C"]. +// Previously panicked: Unhandled case in Node.MemberList: KindModuleBlock + +namespace E { + export class C {} +} +enum E { + D = E["C"], +} + +// Alias exports that resolve to enum members must still constant-evaluate (like E.C). +enum F { + A = 1, +} +namespace E2 { + export import C = F.A; +} +enum E2 { + D = E2["C"], + Next, +} + + +//// [enumInitializerNamespaceExportElementAccess.js] +"use strict"; +// Namespace export merged with enum must not be treated as an enum member via E["C"]. +// Previously panicked: Unhandled case in Node.MemberList: KindModuleBlock +var E; +(function (E) { + class C { + } + E.C = C; +})(E || (E = {})); +(function (E) { + E[E["D"] = E["C"]] = "D"; +})(E || (E = {})); +// Alias exports that resolve to enum members must still constant-evaluate (like E.C). +var F; +(function (F) { + F[F["A"] = 1] = "A"; +})(F || (F = {})); +var E2; +(function (E2) { + E2.C = F.A; +})(E2 || (E2 = {})); +(function (E2) { + E2[E2["D"] = 1] = "D"; + E2[E2["Next"] = 2] = "Next"; +})(E2 || (E2 = {})); diff --git a/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.symbols b/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.symbols new file mode 100644 index 0000000000000..f5e6db5dfc5fe --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.symbols @@ -0,0 +1,48 @@ +//// [tests/cases/compiler/enumInitializerNamespaceExportElementAccess.ts] //// + +=== enumInitializerNamespaceExportElementAccess.ts === +// Namespace export merged with enum must not be treated as an enum member via E["C"]. +// Previously panicked: Unhandled case in Node.MemberList: KindModuleBlock + +namespace E { +>E : Symbol(E, Decl(enumInitializerNamespaceExportElementAccess.ts, 0, 0), Decl(enumInitializerNamespaceExportElementAccess.ts, 5, 1)) + + export class C {} +>C : Symbol(C, Decl(enumInitializerNamespaceExportElementAccess.ts, 3, 13)) +} +enum E { +>E : Symbol(E, Decl(enumInitializerNamespaceExportElementAccess.ts, 0, 0), Decl(enumInitializerNamespaceExportElementAccess.ts, 5, 1)) + + D = E["C"], +>D : Symbol(E.D, Decl(enumInitializerNamespaceExportElementAccess.ts, 6, 8)) +>E : Symbol(E, Decl(enumInitializerNamespaceExportElementAccess.ts, 0, 0), Decl(enumInitializerNamespaceExportElementAccess.ts, 5, 1)) +>"C" : Symbol(E.C, Decl(enumInitializerNamespaceExportElementAccess.ts, 3, 13)) +} + +// Alias exports that resolve to enum members must still constant-evaluate (like E.C). +enum F { +>F : Symbol(F, Decl(enumInitializerNamespaceExportElementAccess.ts, 8, 1)) + + A = 1, +>A : Symbol(F.A, Decl(enumInitializerNamespaceExportElementAccess.ts, 11, 8)) +} +namespace E2 { +>E2 : Symbol(E2, Decl(enumInitializerNamespaceExportElementAccess.ts, 13, 1), Decl(enumInitializerNamespaceExportElementAccess.ts, 16, 1)) + + export import C = F.A; +>C : Symbol(C, Decl(enumInitializerNamespaceExportElementAccess.ts, 14, 14)) +>F : Symbol(F, Decl(enumInitializerNamespaceExportElementAccess.ts, 8, 1)) +>A : Symbol(C, Decl(enumInitializerNamespaceExportElementAccess.ts, 11, 8)) +} +enum E2 { +>E2 : Symbol(E2, Decl(enumInitializerNamespaceExportElementAccess.ts, 13, 1), Decl(enumInitializerNamespaceExportElementAccess.ts, 16, 1)) + + D = E2["C"], +>D : Symbol(E2.D, Decl(enumInitializerNamespaceExportElementAccess.ts, 17, 9)) +>E2 : Symbol(E2, Decl(enumInitializerNamespaceExportElementAccess.ts, 13, 1), Decl(enumInitializerNamespaceExportElementAccess.ts, 16, 1)) +>"C" : Symbol(E2.C, Decl(enumInitializerNamespaceExportElementAccess.ts, 14, 14)) + + Next, +>Next : Symbol(E2.Next, Decl(enumInitializerNamespaceExportElementAccess.ts, 18, 16)) +} + diff --git a/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.types b/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.types new file mode 100644 index 0000000000000..ade9d221407d6 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enumInitializerNamespaceExportElementAccess.types @@ -0,0 +1,51 @@ +//// [tests/cases/compiler/enumInitializerNamespaceExportElementAccess.ts] //// + +=== enumInitializerNamespaceExportElementAccess.ts === +// Namespace export merged with enum must not be treated as an enum member via E["C"]. +// Previously panicked: Unhandled case in Node.MemberList: KindModuleBlock + +namespace E { +>E : typeof E + + export class C {} +>C : C +} +enum E { +>E : E + + D = E["C"], +>D : E.D +>E["C"] : typeof E.C +>E : typeof E +>"C" : "C" +} + +// Alias exports that resolve to enum members must still constant-evaluate (like E.C). +enum F { +>F : F + + A = 1, +>A : F.A +>1 : 1 +} +namespace E2 { +>E2 : typeof E2 + + export import C = F.A; +>C : F.A +>F : F +>A : F.A +} +enum E2 { +>E2 : E2 + + D = E2["C"], +>D : E2.D +>E2["C"] : F +>E2 : typeof E2 +>"C" : "C" + + Next, +>Next : E2.Next +} + diff --git a/tsc/testdata/tests/cases/compiler/enumInitializerNamespaceExportElementAccess.ts b/tsc/testdata/tests/cases/compiler/enumInitializerNamespaceExportElementAccess.ts new file mode 100644 index 0000000000000..9a40c89e1baf4 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/enumInitializerNamespaceExportElementAccess.ts @@ -0,0 +1,21 @@ +// Namespace export merged with enum must not be treated as an enum member via E["C"]. +// Previously panicked: Unhandled case in Node.MemberList: KindModuleBlock + +namespace E { + export class C {} +} +enum E { + D = E["C"], +} + +// Alias exports that resolve to enum members must still constant-evaluate (like E.C). +enum F { + A = 1, +} +namespace E2 { + export import C = F.A; +} +enum E2 { + D = E2["C"], + Next, +}