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
41 changes: 30 additions & 11 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
Expand All @@ -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())
}
}
}
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
}

Original file line number Diff line number Diff line change
@@ -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 = {}));
Original file line number Diff line number Diff line change
@@ -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))
}

Original file line number Diff line number Diff line change
@@ -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
}

Original file line number Diff line number Diff line change
@@ -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,
}