Skip to content

Commit b8e4490

Browse files
authored
Fixed CJS class expression exports emit anonymous constructor types (#3850)
1 parent f315ef9 commit b8e4490

44 files changed

Lines changed: 730 additions & 333 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

internal/checker/checker.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,7 @@ type Checker struct {
648648
moduleSymbols map[*ast.Node]*ast.Symbol
649649
globalThisSymbol *ast.Symbol
650650
symbolTableAliasCache map[symbolTableID][]*ast.Symbol
651+
classExpressionNameTables map[ast.NodeId]ast.SymbolTable
651652
resolveName func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol
652653
resolveNameForSymbolSuggestion func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol
653654
tupleTypes map[CacheHashKey]*Type

internal/checker/emitresolver.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,24 @@ func (r *EmitResolver) GetReferencedValueDeclarations(node *ast.IdentifierNode)
896896
return r.getReferenceResolver().GetReferencedValueDeclarations(node)
897897
}
898898

899+
// IsNameResolvedToDeclaration checks whether `name` resolved at `location`
900+
// resolves to a symbol that includes `declaration` among its declarations.
901+
// When `declaration` is nil, checks whether the name resolves to any symbol.
902+
// This is used by the declaration emitter to check for naming conflicts at file scope.
903+
func (r *EmitResolver) IsNameResolvedToDeclaration(location *ast.Node, name string, declaration *ast.Node) bool {
904+
r.checkerMu.Lock()
905+
defer r.checkerMu.Unlock()
906+
907+
symbol := r.checker.resolveName(location, name, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, nil /*nameNotFoundMessage*/, false /*isUse*/, false /*excludeGlobals*/)
908+
if symbol == nil {
909+
return false
910+
}
911+
if declaration == nil {
912+
return true
913+
}
914+
return slices.Contains(symbol.Declarations, declaration)
915+
}
916+
899917
func (r *EmitResolver) GetElementAccessExpressionName(expression *ast.ElementAccessExpression) string {
900918
if !ast.IsParseTreeNode(expression.AsNode()) {
901919
return ""

internal/checker/symbolaccessibility.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,12 +785,48 @@ func (c *Checker) someSymbolTableInScope(
785785
if table != nil && callback(table, symbolTableIDFromMembers(sym), false, false, location) {
786786
return true
787787
}
788+
// Class expression names (e.g., `B` in `class B {}`) are not stored in any
789+
// scope table — the binder uses bindAnonymousDeclaration. Expose the name
790+
// binding here so getAccessibleSymbolChain can resolve self-references.
791+
// This mirrors the special casing of class expression names in
792+
// (*NameResolver).Resolve; if class names are ever bound differently
793+
// (e.g., via class-local type aliases), both sites should be updated.
794+
if ast.IsClassExpression(location) && location.AsClassExpression().Name() != nil {
795+
nameTable := c.getClassExpressionNameTable(location)
796+
if nameTable != nil && callback(nameTable, symbolTableIDFromLocals(location.AsNode()), false, true, location) {
797+
return true
798+
}
799+
}
788800
}
789801
}
790802

791803
return callback(c.globals, symbolTableIDFromGlobals(), false, true, nil)
792804
}
793805

806+
// getClassExpressionNameTable returns a cached symbol table containing the class
807+
// expression's name binding. Class expression names are bound via
808+
// bindAnonymousDeclaration and aren't stored in any container's locals, so this
809+
// synthesized table lets someSymbolTableInScope expose them during accessibility checks.
810+
func (c *Checker) getClassExpressionNameTable(location *ast.Node) ast.SymbolTable {
811+
nodeId := ast.GetNodeId(location)
812+
if c.classExpressionNameTables != nil {
813+
if table, ok := c.classExpressionNameTables[nodeId]; ok {
814+
return table
815+
}
816+
}
817+
classSymbol := c.getSymbolOfDeclaration(location)
818+
nameText := location.AsClassExpression().Name().Text()
819+
if len(nameText) == 0 || classSymbol == nil {
820+
return nil
821+
}
822+
table := ast.SymbolTable{nameText: classSymbol}
823+
if c.classExpressionNameTables == nil {
824+
c.classExpressionNameTables = make(map[ast.NodeId]ast.SymbolTable)
825+
}
826+
c.classExpressionNameTables[nodeId] = table
827+
return table
828+
}
829+
794830
/**
795831
* Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested
796832
*

internal/printer/emitresolver.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ type EmitResolver interface {
104104
IsLiteralConstDeclaration(node *ast.Node) bool
105105
RequiresAddingImplicitUndefined(node *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool
106106
IsDeclarationVisible(node *ast.Node) bool
107+
IsNameResolvedToDeclaration(location *ast.Node, name string, declaration *ast.Node) bool
107108
IsImportRequiredByAugmentation(decl *ast.ImportDeclaration) bool
108109
IsDefinitelyReferenceToGlobalSymbolObject(node *ast.Node) bool
109110
IsImplementationOfOverload(node *ast.SignatureDeclaration) bool

internal/transformers/declarations/tracker.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ type SymbolTrackerImpl struct {
1515
host DeclarationEmitHost
1616
fallbackStack []*ast.Node
1717

18+
// For detecting class expression self-references during member serialization.
19+
// When set, TrackSymbol will record usage without reporting accessibility errors.
20+
watchedClassSymbol *ast.Symbol
21+
classSymbolTracked bool
22+
1823
getIsolatedDeclarationError func(node *ast.Node) *ast.Diagnostic
1924
}
2025

@@ -157,6 +162,13 @@ func (s *SymbolTrackerImpl) TrackSymbol(symbol *ast.Symbol, enclosingDeclaration
157162
if symbol.Flags&ast.SymbolFlagsTypeParameter != 0 {
158163
return false
159164
}
165+
// When watching for a class expression symbol, record its usage without
166+
// reporting accessibility errors — the caller will handle visibility by
167+
// wrapping the class in a namespace.
168+
if s.watchedClassSymbol != nil && symbol == s.watchedClassSymbol {
169+
s.classSymbolTracked = true
170+
return false
171+
}
160172
issuedDiagnostic := s.handleSymbolAccessibilityError(s.resolver.IsSymbolAccessible(symbol, enclosingDeclaration, meaning /*shouldComputeAliasToMarkVisible*/, true))
161173
return issuedDiagnostic
162174
}

0 commit comments

Comments
 (0)