From 610e7362ded5cc43315cbb1ef1841525ccac7cf0 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Oct 2026 11:37:14 -0700 Subject: [PATCH 01/10] Cache one nodebuilder per emit resolver, make emit resolver emit context scoped --- tsc/internal/checker/checker.go | 10 +- tsc/internal/checker/emitresolver.go | 130 ++++++++++-------- tsc/internal/checker/exports.go | 2 +- tsc/internal/checker/nodebuilderimpl.go | 4 +- tsc/internal/checker/symbolaccessibility.go | 2 +- tsc/internal/compiler/emitHost.go | 18 ++- tsc/internal/compiler/emitter.go | 2 +- tsc/internal/ls/findallreferences.go | 2 +- tsc/internal/printer/emithost.go | 2 +- tsc/internal/printer/emitresolver.go | 14 +- .../transformers/declarations/transform.go | 24 ++-- .../tstransforms/importelision_test.go | 5 +- 12 files changed, 119 insertions(+), 96 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 9b52a55d92310..c7ccc9f83f0b8 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -23,6 +23,7 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/jsnum" "github.com/microsoft/TypeScript/tsc/internal/module" "github.com/microsoft/TypeScript/tsc/internal/modulespecifiers" + "github.com/microsoft/TypeScript/tsc/internal/printer" "github.com/microsoft/TypeScript/tsc/internal/scanner" "github.com/microsoft/TypeScript/tsc/internal/stringutil" "github.com/microsoft/TypeScript/tsc/internal/tracing" @@ -890,6 +891,7 @@ type Checker struct { compareTypesAssignable TypeComparer emitResolver *EmitResolver emitResolverOnce sync.Once + emitResolverLinks EmitResolverLinks _jsxNamespace string _jsxFactoryEntity *ast.Node skipDirectInferenceNodes collections.Set[*ast.Node] @@ -32687,14 +32689,18 @@ func (c *Checker) GetTypeAtLocation(node *ast.Node) *Type { return c.getTypeOfNode(ast.GetReparsedNodeForNode(node)) } -func (c *Checker) GetEmitResolver() *EmitResolver { +func (c *Checker) getDiagnosticsEmitResolver() *EmitResolver { c.emitResolverOnce.Do(func() { - c.emitResolver = newEmitResolver(c) + c.emitResolver = newEmitResolver(c, printer.NewEmitContext()) }) return c.emitResolver } +func (c *Checker) GetEmitResolver(emitContext *printer.EmitContext) *EmitResolver { + return newEmitResolver(c, emitContext) +} + func (c *Checker) GetAliasedSymbol(symbol *ast.Symbol) *ast.Symbol { return c.resolveAlias(symbol) } diff --git a/tsc/internal/checker/emitresolver.go b/tsc/internal/checker/emitresolver.go index 9bc590fd179bf..a2ad3f743345c 100644 --- a/tsc/internal/checker/emitresolver.go +++ b/tsc/internal/checker/emitresolver.go @@ -31,25 +31,41 @@ type DeclarationFileLinks struct { aliasesMarked bool // if file has had alias visibility marked } +type EmitResolverLinks struct { + jsxLinks core.LinkStore[*ast.Node, JSXLinks] + declarationLinks core.LinkStore[*ast.Node, DeclarationLinks] + declarationFileLinks core.LinkStore[*ast.Node, DeclarationFileLinks] +} + type EmitResolver struct { checker *Checker checkerMu *sync.Mutex + emitContext *printer.EmitContext + requestNodeBuilder *NodeBuilder + links *EmitResolverLinks isValueAliasDeclaration func(node *ast.Node) bool aliasMarkingVisitor func(node *ast.Node) bool referenceResolver binder.ReferenceResolver - jsxLinks core.LinkStore[*ast.Node, JSXLinks] - declarationLinks core.LinkStore[*ast.Node, DeclarationLinks] - declarationFileLinks core.LinkStore[*ast.Node, DeclarationFileLinks] } -func newEmitResolver(checker *Checker) *EmitResolver { - e := &EmitResolver{checker: checker} +func newEmitResolver(checker *Checker, emitContext *printer.EmitContext) *EmitResolver { + if emitContext == nil { + panic("EmitResolver requires an EmitContext") + } + e := &EmitResolver{checker: checker, emitContext: emitContext, links: &checker.emitResolverLinks} e.isValueAliasDeclaration = e.isValueAliasDeclarationWorker e.aliasMarkingVisitor = e.aliasMarkingVisitorWorker e.checkerMu = &checker.mu return e } +func (r *EmitResolver) nodeBuilder() *NodeBuilder { + if r.requestNodeBuilder == nil { + r.requestNodeBuilder = NewNodeBuilder(r.checker, r.emitContext) + } + return r.requestNodeBuilder +} + func (r *EmitResolver) GetJsxFactoryEntity(location *ast.Node) *ast.Node { r.checkerMu.Lock() defer r.checkerMu.Unlock() @@ -117,7 +133,7 @@ func (r *EmitResolver) isDeclarationVisible(node *ast.Node) bool { return false } - links := r.declarationLinks.Get(node) + links := r.links.declarationLinks.Get(node) if links.isVisible == core.TSUnknown { if r.determineIfDeclarationIsVisible(node) { links.isVisible = core.TSTrue @@ -236,10 +252,10 @@ func (r *EmitResolver) determineIfDeclarationIsVisible(node *ast.Node) bool { func (r *EmitResolver) PrecalculateDeclarationEmitVisibility(file *ast.SourceFile) { r.checkerMu.Lock() defer r.checkerMu.Unlock() - if r.declarationFileLinks.Get(file.AsNode()).aliasesMarked { + if r.links.declarationFileLinks.Get(file.AsNode()).aliasesMarked { return } - r.declarationFileLinks.Get(file.AsNode()).aliasesMarked = true + r.links.declarationFileLinks.Get(file.AsNode()).aliasesMarked = true // TODO: Does this even *have* to be an upfront walk? If it's not possible for a // import a = a.b.c statement to chain into exposing a statement in a sibling scope, // it could at least be pushed into scope entry - then it wouldn't need to be recursive. @@ -293,7 +309,7 @@ func (r *EmitResolver) markLinkedAliases(node *ast.Node) { var nextSymbol *ast.Symbol for _, declaration := range exportSymbol.Declarations { - r.declarationLinks.Get(declaration).isVisible = core.TSTrue + r.links.declarationLinks.Get(declaration).isVisible = core.TSTrue if ast.IsInternalModuleImportEqualsDeclaration(declaration) { // Add the referenced top container visible @@ -387,7 +403,7 @@ func (r *EmitResolver) hasVisibleDeclarations(symbol *ast.Symbol, shouldComputeA var addVisibleAlias func(declaration *ast.Node, aliasingStatement *ast.Node) if shouldComputeAliasToMakeVisible { addVisibleAlias = func(declaration *ast.Node, aliasingStatement *ast.Node) { - r.declarationLinks.Get(declaration).isVisible = core.TSTrue + r.links.declarationLinks.Get(declaration).isVisible = core.TSTrue if aliasesToMakeVisibleSet == nil { aliasesToMakeVisibleSet = make(map[ast.NodeId]*ast.Node) } @@ -869,14 +885,14 @@ func (r *EmitResolver) GetReferencedExportContainer(node *ast.IdentifierNode, pr func (r *EmitResolver) SetReferencedImportDeclaration(node *ast.IdentifierNode, ref *ast.Declaration) { r.checkerMu.Lock() defer r.checkerMu.Unlock() - r.jsxLinks.Get(node).importRef = ref + r.links.jsxLinks.Get(node).importRef = ref } func (r *EmitResolver) GetReferencedImportDeclaration(node *ast.IdentifierNode) *ast.Declaration { r.checkerMu.Lock() defer r.checkerMu.Unlock() if !ast.IsParseTreeNode(node) { - return r.jsxLinks.Get(node).importRef + return r.links.jsxLinks.Get(node).importRef } symbol := r.checker.getReferencedValueOrAliasSymbol(node) @@ -947,46 +963,43 @@ func (r *EmitResolver) GetReferencedMemberValueDeclaration(node *ast.Node) *ast. // and requires giving it access to a lot of context it's otherwise not required to have, which also further complicates the API // and likely reduces performance. There's probably some refactoring that could be done here to simplify this. -func (r *EmitResolver) CreateReturnTypeOfSignatureDeclaration(emitContext *printer.EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { - original := emitContext.ParseNode(signatureDeclaration) +func (r *EmitResolver) CreateReturnTypeOfSignatureDeclaration(signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + original := r.emitContext.ParseNode(signatureDeclaration) if original == nil { - return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) + return r.emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) } r.checkerMu.Lock() defer r.checkerMu.Unlock() - requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context - return requestNodeBuilder.SerializeReturnTypeForSignature(original, enclosingDeclaration, flags, internalFlags, tracker) + return r.nodeBuilder().SerializeReturnTypeForSignature(original, enclosingDeclaration, flags, internalFlags, tracker) } -func (r *EmitResolver) CreateTypeParametersOfSignatureDeclaration(emitContext *printer.EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node { - original := emitContext.ParseNode(signatureDeclaration) +func (r *EmitResolver) CreateTypeParametersOfSignatureDeclaration(signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node { + original := r.emitContext.ParseNode(signatureDeclaration) if original == nil { return nil } r.checkerMu.Lock() defer r.checkerMu.Unlock() - requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context - return requestNodeBuilder.SerializeTypeParametersForSignature(original, enclosingDeclaration, flags, internalFlags, tracker) + return r.nodeBuilder().SerializeTypeParametersForSignature(original, enclosingDeclaration, flags, internalFlags, tracker) } -func (r *EmitResolver) CreateTypeOfDeclaration(emitContext *printer.EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { - original := emitContext.ParseNode(declaration) +func (r *EmitResolver) CreateTypeOfDeclaration(declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + original := r.emitContext.ParseNode(declaration) if original == nil { - return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) + return r.emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) } r.checkerMu.Lock() defer r.checkerMu.Unlock() - requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context // // Get type of the symbol if this is the valid symbol otherwise get type at location symbol := r.checker.getSymbolOfDeclaration(declaration) - return requestNodeBuilder.SerializeTypeForDeclaration(declaration, symbol, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) + return r.nodeBuilder().SerializeTypeForDeclaration(declaration, symbol, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) } -func (r *EmitResolver) CreateLiteralConstValue(emitContext *printer.EmitContext, node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node { - node = emitContext.ParseNode(node) +func (r *EmitResolver) CreateLiteralConstValue(node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node { + node = r.emitContext.ParseNode(node) r.checkerMu.Lock() t := r.checker.getTypeOfSymbol(r.checker.getSymbolOfDeclaration(node)) r.checkerMu.Unlock() @@ -998,14 +1011,13 @@ func (r *EmitResolver) CreateLiteralConstValue(emitContext *printer.EmitContext, if t.flags&TypeFlagsEnumLike != 0 { r.checkerMu.Lock() defer r.checkerMu.Unlock() - requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context - enumResult = requestNodeBuilder.SymbolToExpression(t.symbol, ast.SymbolFlagsValue, node, nodebuilder.FlagsNone, nodebuilder.InternalFlagsNone, tracker) + enumResult = r.nodeBuilder().SymbolToExpression(t.symbol, ast.SymbolFlagsValue, node, nodebuilder.FlagsNone, nodebuilder.InternalFlagsNone, tracker) // What about regularTrueType/regularFalseType - since those aren't fresh, we never make initializers from them // TODO: handle those if this function is ever used for more than initializers in declaration emit } else if t == r.checker.trueType { - enumResult = emitContext.Factory.NewKeywordExpression(ast.KindTrueKeyword) + enumResult = r.emitContext.Factory.NewKeywordExpression(ast.KindTrueKeyword) } else if t == r.checker.falseType { - enumResult = emitContext.Factory.NewKeywordExpression(ast.KindFalseKeyword) + enumResult = r.emitContext.Factory.NewKeywordExpression(ast.KindFalseKeyword) } if enumResult != nil { return enumResult @@ -1015,51 +1027,50 @@ func (r *EmitResolver) CreateLiteralConstValue(emitContext *printer.EmitContext, } switch value := t.AsLiteralType().value.(type) { case string: - return emitContext.Factory.NewStringLiteral(value, ast.TokenFlagsNone) + return r.emitContext.Factory.NewStringLiteral(value, ast.TokenFlagsNone) case jsnum.Number: if value.IsInf() { if value > 0 { - return emitContext.Factory.NewIdentifier("Infinity") + return r.emitContext.Factory.NewIdentifier("Infinity") } - return emitContext.Factory.NewPrefixUnaryExpression(ast.KindMinusToken, emitContext.Factory.NewIdentifier("Infinity")) + return r.emitContext.Factory.NewPrefixUnaryExpression(ast.KindMinusToken, r.emitContext.Factory.NewIdentifier("Infinity")) } if value.IsNaN() { - return emitContext.Factory.NewIdentifier("NaN") + return r.emitContext.Factory.NewIdentifier("NaN") } if value.Abs() != value { // negative - return emitContext.Factory.NewPrefixUnaryExpression( + return r.emitContext.Factory.NewPrefixUnaryExpression( ast.KindMinusToken, - emitContext.Factory.NewNumericLiteral(value.String()[1:], ast.TokenFlagsNone), + r.emitContext.Factory.NewNumericLiteral(value.String()[1:], ast.TokenFlagsNone), ) } - return emitContext.Factory.NewNumericLiteral(value.String(), ast.TokenFlagsNone) + return r.emitContext.Factory.NewNumericLiteral(value.String(), ast.TokenFlagsNone) case jsnum.PseudoBigInt: - return emitContext.Factory.NewBigIntLiteral(pseudoBigIntToString(value)+"n", ast.TokenFlagsNone) + return r.emitContext.Factory.NewBigIntLiteral(pseudoBigIntToString(value)+"n", ast.TokenFlagsNone) case bool: kind := ast.KindFalseKeyword if value { kind = ast.KindTrueKeyword } - return emitContext.Factory.NewKeywordExpression(kind) + return r.emitContext.Factory.NewKeywordExpression(kind) } panic("unhandled literal const value kind") } -func (r *EmitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { - expression = emitContext.ParseNode(expression) +func (r *EmitResolver) CreateTypeOfExpression(expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + expression = r.emitContext.ParseNode(expression) if expression == nil { - return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) + return r.emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) } r.checkerMu.Lock() defer r.checkerMu.Unlock() - requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context - return requestNodeBuilder.SerializeTypeForExpression(expression, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) + return r.nodeBuilder().SerializeTypeForExpression(expression, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) } -func (r *EmitResolver) CreateLateBoundIndexSignatures(emitContext *printer.EmitContext, container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node { - container = emitContext.ParseNode(container) +func (r *EmitResolver) CreateLateBoundIndexSignatures(container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node { + container = r.emitContext.ParseNode(container) r.checkerMu.Lock() defer r.checkerMu.Unlock() @@ -1072,7 +1083,7 @@ func (r *EmitResolver) CreateLateBoundIndexSignatures(emitContext *printer.EmitC instanceInfos = r.checker.getIndexInfosOfIndexSymbol(instanceIndexSymbol, siblingSymbols) } - requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context + requestNodeBuilder := r.nodeBuilder() var result []*ast.Node for i, infoList := range [][]*IndexInfo{staticInfos, instanceInfos} { @@ -1111,13 +1122,13 @@ func (r *EmitResolver) CreateLateBoundIndexSignatures(emitContext *printer.EmitC tracker.TrackSymbol(name, enclosingDeclaration, ast.SymbolFlagsValue) } - mods := core.IfElse(isStatic, []*ast.Node{emitContext.Factory.NewModifier(ast.KindStaticKeyword)}, nil) + mods := core.IfElse(isStatic, []*ast.Node{r.emitContext.Factory.NewModifier(ast.KindStaticKeyword)}, nil) if info.isReadonly { - mods = append(mods, emitContext.Factory.NewModifier(ast.KindReadonlyKeyword)) + mods = append(mods, r.emitContext.Factory.NewModifier(ast.KindReadonlyKeyword)) } - decl := emitContext.Factory.NewPropertyDeclaration( - core.IfElse(mods != nil, emitContext.Factory.NewModifierList(mods), nil), + decl := r.emitContext.Factory.NewPropertyDeclaration( + core.IfElse(mods != nil, r.emitContext.Factory.NewModifierList(mods), nil), c.Name(), c.QuestionToken(), requestNodeBuilder.TypeToTypeNode(r.checker.getTypeOfSymbol(c.Symbol()), enclosingDeclaration, flags, internalFlags, tracker), @@ -1130,10 +1141,10 @@ func (r *EmitResolver) CreateLateBoundIndexSignatures(emitContext *printer.EmitC } node := requestNodeBuilder.IndexInfoToIndexSignatureDeclaration(info, enclosingDeclaration, flags, internalFlags, tracker) if node != nil && isStatic { - modNodes := []*ast.Node{emitContext.Factory.NewModifier(ast.KindStaticKeyword)} + modNodes := []*ast.Node{r.emitContext.Factory.NewModifier(ast.KindStaticKeyword)} modNodes = append(modNodes, node.ModifierNodes()...) - mods := emitContext.Factory.NewModifierList(modNodes) - node = emitContext.Factory.UpdateIndexSignatureDeclaration( + mods := r.emitContext.Factory.NewModifierList(modNodes) + node = r.emitContext.Factory.UpdateIndexSignatureDeclaration( node.AsIndexSignatureDeclaration(), mods, node.ParameterList(), @@ -1269,13 +1280,12 @@ func (r *EmitResolver) GetPropertiesOfContainerFunction(node *ast.Node) []*ast.S return r.checker.getPropertiesOfType(r.checker.getTypeOfSymbol(s)) } -func (r *EmitResolver) TryJSTypeNodeToTypeNode(emitContext *printer.EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { - typeNode = emitContext.ParseNode(typeNode) +func (r *EmitResolver) TryJSTypeNodeToTypeNode(typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + typeNode = r.emitContext.ParseNode(typeNode) r.checkerMu.Lock() defer r.checkerMu.Unlock() - requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context - return requestNodeBuilder.TryJSTypeNodeToTypeNode(typeNode, enclosingDeclaration, flags, internalFlags, tracker) + return r.nodeBuilder().TryJSTypeNodeToTypeNode(typeNode, enclosingDeclaration, flags, internalFlags, tracker) } // IsThisPropertyAssignmentDeclarationRedundant reports whether a JS `this. = ...` expando diff --git a/tsc/internal/checker/exports.go b/tsc/internal/checker/exports.go index e38a55be571ff..84105c950b8de 100644 --- a/tsc/internal/checker/exports.go +++ b/tsc/internal/checker/exports.go @@ -392,7 +392,7 @@ func (c *Checker) RequiresAddingImplicitUndefined(node *ast.Node) bool { if symbol == nil { return false } - return c.GetEmitResolver().RequiresAddingImplicitUndefined(node, symbol, enclosingDeclaration) + return c.getDiagnosticsEmitResolver().RequiresAddingImplicitUndefined(node, symbol, enclosingDeclaration) } func (c *Checker) RemoveMissingOrUndefinedType(t *Type) *Type { diff --git a/tsc/internal/checker/nodebuilderimpl.go b/tsc/internal/checker/nodebuilderimpl.go index fac1f1414c58a..6d6da08761cd5 100644 --- a/tsc/internal/checker/nodebuilderimpl.go +++ b/tsc/internal/checker/nodebuilderimpl.go @@ -2154,7 +2154,7 @@ func (b *NodeBuilderImpl) isTriviallySerializableComputedName(e *ast.Node) bool return false } // TODO: going through emit resolver here is weird. Relayer these APIs. - return b.ch.GetEmitResolver().isEntityNameVisible(e.Name().Expression(), b.ctx.enclosingDeclaration, false).Accessibility == printer.SymbolAccessibilityAccessible + return b.ch.getDiagnosticsEmitResolver().isEntityNameVisible(e.Name().Expression(), b.ctx.enclosingDeclaration, false).Accessibility == printer.SymbolAccessibilityAccessible } func (b *NodeBuilderImpl) indexInfoToObjectComputedNamesOrSignatureDeclaration(indexInfo *IndexInfo, typeNode *ast.TypeNode) []*ast.Node { @@ -2286,7 +2286,7 @@ func (b *NodeBuilderImpl) serializeTypeForDeclaration(declaration *ast.Declarati } // !!! TODO: JSDoc, getEmitResolver call is unfortunate layering for the helper - hoist it into checker - requiresAddingUndefined := declaration != nil && (ast.IsParameterDeclaration(declaration) || ast.IsPropertySignatureDeclaration(declaration) || ast.IsPropertyDeclaration(declaration)) && b.ch.GetEmitResolver().requiresAddingImplicitUndefined(declaration, symbol, b.ctx.enclosingDeclaration) + requiresAddingUndefined := declaration != nil && (ast.IsParameterDeclaration(declaration) || ast.IsPropertySignatureDeclaration(declaration) || ast.IsPropertyDeclaration(declaration)) && b.ch.getDiagnosticsEmitResolver().requiresAddingImplicitUndefined(declaration, symbol, b.ctx.enclosingDeclaration) addUndefinedForParameter := requiresAddingUndefined && (ast.IsParameterDeclaration(declaration) /*|| ast.IsJSDocParameterTag(declaration)*/) if addUndefinedForParameter { t = b.ch.getOptionalType(t, false) diff --git a/tsc/internal/checker/symbolaccessibility.go b/tsc/internal/checker/symbolaccessibility.go index a066af7159de5..db28c162d11d6 100644 --- a/tsc/internal/checker/symbolaccessibility.go +++ b/tsc/internal/checker/symbolaccessibility.go @@ -37,7 +37,7 @@ func (c *Checker) IsAnySymbolAccessible(symbols []*ast.Symbol, enclosingDeclarat if len(accessibleSymbolChain) > 0 { hadAccessibleChain = symbol // TODO: going through emit resolver here is weird. Relayer these APIs. - hasAccessibleDeclarations := c.GetEmitResolver().hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible) + hasAccessibleDeclarations := c.getDiagnosticsEmitResolver().hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible) if hasAccessibleDeclarations != nil { return hasAccessibleDeclarations } diff --git a/tsc/internal/compiler/emitHost.go b/tsc/internal/compiler/emitHost.go index 1f199f0c9d7a0..de2b6d8751fb9 100644 --- a/tsc/internal/compiler/emitHost.go +++ b/tsc/internal/compiler/emitHost.go @@ -31,15 +31,19 @@ var _ EmitHost = (*emitHost)(nil) // NOTE: emitHost operations must be thread-safe type emitHost struct { - program *Program - emitResolver printer.EmitResolver + program *Program + getEmitResolver func(*printer.EmitContext) printer.EmitResolver + getEffectiveDeclarationFlags func(*ast.Node, ast.ModifierFlags) ast.ModifierFlags } func newEmitHost(ctx context.Context, program *Program, file *ast.SourceFile) (*emitHost, func()) { checker, done := program.GetTypeCheckerForFile(ctx, file) return &emitHost{ - program: program, - emitResolver: checker.GetEmitResolver(), + program: program, + getEmitResolver: func(emitContext *printer.EmitContext) printer.EmitResolver { + return checker.GetEmitResolver(emitContext) + }, + getEffectiveDeclarationFlags: checker.GetEffectiveDeclarationFlags, }, done } @@ -88,7 +92,7 @@ func (host *emitHost) GetRedirectTargets(path tspath.PathKey) []tspath.RootedFil } func (host *emitHost) GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags { - return host.GetEmitResolver().GetEffectiveDeclarationFlags(node, flags) + return host.getEffectiveDeclarationFlags(node, flags) } func (host *emitHost) GetOutputPathsFor(file *ast.SourceFile, forceDtsPaths bool) declarations.OutputPaths { @@ -130,8 +134,8 @@ func (host *emitHost) WriteFile(fileName tspath.RootedFilePath, text string) err return host.program.Host().FS().WriteFile(fileName, text) } -func (host *emitHost) GetEmitResolver() printer.EmitResolver { - return host.emitResolver +func (host *emitHost) GetEmitResolver(emitContext *printer.EmitContext) printer.EmitResolver { + return host.getEmitResolver(emitContext) } func (host *emitHost) IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool { diff --git a/tsc/internal/compiler/emitter.go b/tsc/internal/compiler/emitter.go index 4e3b697a1b17e..b1fcc71220a7d 100644 --- a/tsc/internal/compiler/emitter.go +++ b/tsc/internal/compiler/emitter.go @@ -117,7 +117,7 @@ func getScriptTransformers(emitContext *printer.EmitContext, host printer.EmitHo importElisionEnabled := !options.VerbatimModuleSyntax.IsTrue() && !ast.IsInJSFile(sourceFile.AsNode()) jsxTransformEnabled := options.GetJSXTransformEnabled() && sourceFile.LanguageVariant == core.LanguageVariantJSX - emitResolver := host.GetEmitResolver() + emitResolver := host.GetEmitResolver(emitContext) var referenceResolver binder.ReferenceResolver if importElisionEnabled || jsxTransformEnabled || !options.GetIsolatedModules() || options.EmitDecoratorMetadata.IsTrue() { diff --git a/tsc/internal/ls/findallreferences.go b/tsc/internal/ls/findallreferences.go index 80b53f8f48e1f..9cefa911b6d7c 100644 --- a/tsc/internal/ls/findallreferences.go +++ b/tsc/internal/ls/findallreferences.go @@ -509,7 +509,7 @@ func (l *LanguageService) getNonLocalDefinition(ctx context.Context, entry *Symb program := l.GetProgram() checker, done := program.GetTypeChecker(ctx) defer done() - emitResolver := checker.GetEmitResolver() + emitResolver := checker.GetEmitResolver(printer.NewEmitContext()) for _, d := range entry.definition.symbol.Declarations { if isDefinitionVisible(emitResolver, d) { file, startPos := getFileAndStartPosFromDeclaration(d) diff --git a/tsc/internal/printer/emithost.go b/tsc/internal/printer/emithost.go index 993e02ee12f76..6a046a56f69aa 100644 --- a/tsc/internal/printer/emithost.go +++ b/tsc/internal/printer/emithost.go @@ -16,7 +16,7 @@ type EmitHost interface { IsEmitBlocked(file tspath.RootedFilePath) bool WriteFile(fileName tspath.RootedFilePath, text string) error GetEmitModuleFormatOfFile(file ast.HasFileName) core.ModuleKind - GetEmitResolver() EmitResolver + GetEmitResolver(emitContext *EmitContext) EmitResolver GetProjectReferenceFromSource(path tspath.PathKey) *tsoptions.SourceOutputAndProjectReference IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool } diff --git a/tsc/internal/printer/emitresolver.go b/tsc/internal/printer/emitresolver.go index f9b98dba994ce..59f1ed44ed4dc 100644 --- a/tsc/internal/printer/emitresolver.go +++ b/tsc/internal/printer/emitresolver.go @@ -117,11 +117,11 @@ type EmitResolver interface { GetReferencedValueDeclarationUnsafe(node *ast.IdentifierNode) *ast.Declaration // Node construction for declaration emit - CreateTypeOfDeclaration(emitContext *EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node - CreateReturnTypeOfSignatureDeclaration(emitContext *EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node - CreateTypeParametersOfSignatureDeclaration(emitContext *EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node - CreateLiteralConstValue(emitContext *EmitContext, node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node - CreateTypeOfExpression(emitContext *EmitContext, expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node - CreateLateBoundIndexSignatures(emitContext *EmitContext, container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node - TryJSTypeNodeToTypeNode(emitContext *EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeOfDeclaration(declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateReturnTypeOfSignatureDeclaration(signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeParametersOfSignatureDeclaration(signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node + CreateLiteralConstValue(node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeOfExpression(expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateLateBoundIndexSignatures(container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node + TryJSTypeNodeToTypeNode(typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node } diff --git a/tsc/internal/transformers/declarations/transform.go b/tsc/internal/transformers/declarations/transform.go index e8e6c0b22c5f1..1ce3561a25d1c 100644 --- a/tsc/internal/transformers/declarations/transform.go +++ b/tsc/internal/transformers/declarations/transform.go @@ -39,7 +39,7 @@ type DeclarationEmitHost interface { GetOutputPathsFor(file *ast.SourceFile, forceDtsPaths bool) OutputPaths SourceFileMayBeEmitted(file *ast.SourceFile, forceDtsEmit bool) bool GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags - GetEmitResolver() printer.EmitResolver + GetEmitResolver(emitContext *printer.EmitContext) printer.EmitResolver } type thisPropertyAssignmentKey struct { @@ -99,7 +99,10 @@ type DeclarationTransformer struct { // TODO: Convert to transformers.TransformerFactory signature to allow more automatic composition with other transforms func NewDeclarationTransformer(host DeclarationEmitHost, context *printer.EmitContext, compilerOptions *core.CompilerOptions, declarationFilePath tspath.RootedFilePath) *DeclarationTransformer { - resolver := host.GetEmitResolver() + if context == nil { + context = printer.NewEmitContext() + } + resolver := host.GetEmitResolver(context) state := &SymbolTrackerSharedState{isolatedDeclarations: compilerOptions.IsolatedDeclarations.IsTrue(), stripInternal: compilerOptions.StripInternal.IsTrue(), resolver: resolver} tracker := NewSymbolTracker(host, resolver, state) // TODO: Use new host GetOutputPathsFor method instead of passing in entrypoint paths (which will also better support bundled emit) @@ -1274,7 +1277,7 @@ func (tx *DeclarationTransformer) transformExportAssignment(input *ast.Node, ass tx.cjsExportAssignmentName = newId var type_, initializer *ast.Node if ast.IsPrimitiveLiteralValue(unwrapParenthesizedExpression(expression), true) { - initializer = tx.resolver.CreateLiteralConstValue(tx.EmitContext(), tx.EmitContext().ParseNode(assignment), tx.tracker) + initializer = tx.resolver.CreateLiteralConstValue(tx.EmitContext().ParseNode(assignment), tx.tracker) } if initializer == nil { type_ = tx.ensureType(assignment, false) @@ -1490,7 +1493,7 @@ func (tx *DeclarationTransformer) transformCommonJSExportWorker(input *ast.Node, tx.preserveJsDoc(statement, input) tx.removeAllComments(assignment) return tx.Factory().NewSyntaxList([]*ast.Node{statement, assignment}) - } else if tx.host.GetEmitResolver().GetReferencedValueDeclaration(name) == input || tx.host.GetEmitResolver().GetReferencedValueDeclaration(name) == nil { + } else if tx.resolver.GetReferencedValueDeclaration(name) == input || tx.resolver.GetReferencedValueDeclaration(name) == nil { // only inline to a export var if the `name` lookup points at this assignment or nothing - if it points at something else, we must use a temp name // export var name: Type tx.tracker.PushErrorFallbackNode(input) @@ -1655,7 +1658,7 @@ func (tx *DeclarationTransformer) ensureType(node *ast.Node, ignorePrivate bool) if tx.inClassExpressionDeclaration { jsFlags &^= nodebuilder.FlagsWriteClassExpressionAsTypeLiteral } - res := tx.resolver.TryJSTypeNodeToTypeNode(tx.EmitContext(), node.Type(), tx.enclosingDeclaration, jsFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) + res := tx.resolver.TryJSTypeNodeToTypeNode(node.Type(), tx.enclosingDeclaration, jsFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) if res != nil { return res } @@ -1681,9 +1684,9 @@ func (tx *DeclarationTransformer) ensureType(node *ast.Node, ignorePrivate bool) flags &^= nodebuilder.FlagsWriteClassExpressionAsTypeLiteral } if ast.HasInferredType(node) { - typeNode = tx.resolver.CreateTypeOfDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, flags, declarationEmitInternalNodeBuilderFlags, tx.tracker) + typeNode = tx.resolver.CreateTypeOfDeclaration(node, tx.enclosingDeclaration, flags, declarationEmitInternalNodeBuilderFlags, tx.tracker) } else if ast.IsFunctionLike(node) { - typeNode = tx.resolver.CreateReturnTypeOfSignatureDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, flags, declarationEmitInternalNodeBuilderFlags, tx.tracker) + typeNode = tx.resolver.CreateReturnTypeOfSignatureDeclaration(node, tx.enclosingDeclaration, flags, declarationEmitInternalNodeBuilderFlags, tx.tracker) } else { debug.AssertNever(node) } @@ -1965,7 +1968,6 @@ func (tx *DeclarationTransformer) buildClassMembers(classNode *ast.Node, extraMe } lateIndexes := tx.resolver.CreateLateBoundIndexSignatures( - tx.EmitContext(), classNode, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, @@ -2027,7 +2029,7 @@ func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDecl varDecl := tx.Factory().NewVariableDeclaration( newId, nil, - tx.resolver.CreateTypeOfExpression(tx.EmitContext(), extendsClause.Expression(), input.AsNode(), declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker), + tx.resolver.CreateTypeOfExpression(extendsClause.Expression(), input.AsNode(), declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker), nil, ) var mods *ast.ModifierList @@ -2374,7 +2376,7 @@ func (tx *DeclarationTransformer) ensureTypeParams(node *ast.Node, params *ast.T } if data := node.FunctionLikeData(); data != nil && data.FullSignature != nil { - if nodes := tx.resolver.CreateTypeParametersOfSignatureDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker); nodes != nil { + if nodes := tx.resolver.CreateTypeParametersOfSignatureDeclaration(node, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker); nodes != nil { typeParameters = &ast.TypeParameterList{ Loc: node.Loc, Nodes: nodes, @@ -2432,7 +2434,7 @@ func (tx *DeclarationTransformer) ensureNoInitializer(node *ast.Node) *ast.Node if !ast.IsPrimitiveLiteralValue(unwrappedInitializer, true) { tx.tracker.ReportInferenceFallback(node) } - return tx.resolver.CreateLiteralConstValue(tx.EmitContext(), tx.EmitContext().ParseNode(node), tx.tracker) + return tx.resolver.CreateLiteralConstValue(tx.EmitContext().ParseNode(node), tx.tracker) } return nil } diff --git a/tsc/internal/transformers/tstransforms/importelision_test.go b/tsc/internal/transformers/tstransforms/importelision_test.go index 259db833bcec4..4da8073f6a139 100644 --- a/tsc/internal/transformers/tstransforms/importelision_test.go +++ b/tsc/internal/transformers/tstransforms/importelision_test.go @@ -263,9 +263,10 @@ func TestImportElision(t *testing.T) { }, }, nil) - emitResolver := c.GetEmitResolver() + emitContext := printer.NewEmitContext() + emitResolver := c.GetEmitResolver(emitContext) - opts := &transformers.TransformOptions{CompilerOptions: compilerOptions, Context: printer.NewEmitContext(), EmitResolver: emitResolver, Resolver: emitResolver} + opts := &transformers.TransformOptions{CompilerOptions: compilerOptions, Context: emitContext, EmitResolver: emitResolver, Resolver: emitResolver} file = tstransforms.NewTypeEraserTransformer(opts).TransformSourceFile(file) file = tstransforms.NewImportElisionTransformer(opts).TransformSourceFile(file) emittestutil.CheckEmit(t, nil, file, rec.output) From b5f8011114f90448460db23998495d77dc8942ae Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Oct 2026 12:36:48 -0700 Subject: [PATCH 02/10] Use links from checker, monitor locks better --- tsc/internal/checker/emitresolver.go | 21 +++++++++------------ tsc/internal/checker/exports.go | 6 ++++++ tsc/internal/compiler/emitHost.go | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/tsc/internal/checker/emitresolver.go b/tsc/internal/checker/emitresolver.go index a2ad3f743345c..40b60580b9120 100644 --- a/tsc/internal/checker/emitresolver.go +++ b/tsc/internal/checker/emitresolver.go @@ -42,7 +42,6 @@ type EmitResolver struct { checkerMu *sync.Mutex emitContext *printer.EmitContext requestNodeBuilder *NodeBuilder - links *EmitResolverLinks isValueAliasDeclaration func(node *ast.Node) bool aliasMarkingVisitor func(node *ast.Node) bool referenceResolver binder.ReferenceResolver @@ -52,7 +51,7 @@ func newEmitResolver(checker *Checker, emitContext *printer.EmitContext) *EmitRe if emitContext == nil { panic("EmitResolver requires an EmitContext") } - e := &EmitResolver{checker: checker, emitContext: emitContext, links: &checker.emitResolverLinks} + e := &EmitResolver{checker: checker, emitContext: emitContext} e.isValueAliasDeclaration = e.isValueAliasDeclarationWorker e.aliasMarkingVisitor = e.aliasMarkingVisitorWorker e.checkerMu = &checker.mu @@ -133,7 +132,7 @@ func (r *EmitResolver) isDeclarationVisible(node *ast.Node) bool { return false } - links := r.links.declarationLinks.Get(node) + links := r.checker.emitResolverLinks.declarationLinks.Get(node) if links.isVisible == core.TSUnknown { if r.determineIfDeclarationIsVisible(node) { links.isVisible = core.TSTrue @@ -252,10 +251,10 @@ func (r *EmitResolver) determineIfDeclarationIsVisible(node *ast.Node) bool { func (r *EmitResolver) PrecalculateDeclarationEmitVisibility(file *ast.SourceFile) { r.checkerMu.Lock() defer r.checkerMu.Unlock() - if r.links.declarationFileLinks.Get(file.AsNode()).aliasesMarked { + if r.checker.emitResolverLinks.declarationFileLinks.Get(file.AsNode()).aliasesMarked { return } - r.links.declarationFileLinks.Get(file.AsNode()).aliasesMarked = true + r.checker.emitResolverLinks.declarationFileLinks.Get(file.AsNode()).aliasesMarked = true // TODO: Does this even *have* to be an upfront walk? If it's not possible for a // import a = a.b.c statement to chain into exposing a statement in a sibling scope, // it could at least be pushed into scope entry - then it wouldn't need to be recursive. @@ -309,7 +308,7 @@ func (r *EmitResolver) markLinkedAliases(node *ast.Node) { var nextSymbol *ast.Symbol for _, declaration := range exportSymbol.Declarations { - r.links.declarationLinks.Get(declaration).isVisible = core.TSTrue + r.checker.emitResolverLinks.declarationLinks.Get(declaration).isVisible = core.TSTrue if ast.IsInternalModuleImportEqualsDeclaration(declaration) { // Add the referenced top container visible @@ -403,7 +402,7 @@ func (r *EmitResolver) hasVisibleDeclarations(symbol *ast.Symbol, shouldComputeA var addVisibleAlias func(declaration *ast.Node, aliasingStatement *ast.Node) if shouldComputeAliasToMakeVisible { addVisibleAlias = func(declaration *ast.Node, aliasingStatement *ast.Node) { - r.links.declarationLinks.Get(declaration).isVisible = core.TSTrue + r.checker.emitResolverLinks.declarationLinks.Get(declaration).isVisible = core.TSTrue if aliasesToMakeVisibleSet == nil { aliasesToMakeVisibleSet = make(map[ast.NodeId]*ast.Node) } @@ -885,14 +884,14 @@ func (r *EmitResolver) GetReferencedExportContainer(node *ast.IdentifierNode, pr func (r *EmitResolver) SetReferencedImportDeclaration(node *ast.IdentifierNode, ref *ast.Declaration) { r.checkerMu.Lock() defer r.checkerMu.Unlock() - r.links.jsxLinks.Get(node).importRef = ref + r.checker.emitResolverLinks.jsxLinks.Get(node).importRef = ref } func (r *EmitResolver) GetReferencedImportDeclaration(node *ast.IdentifierNode) *ast.Declaration { r.checkerMu.Lock() defer r.checkerMu.Unlock() if !ast.IsParseTreeNode(node) { - return r.links.jsxLinks.Get(node).importRef + return r.checker.emitResolverLinks.jsxLinks.Get(node).importRef } symbol := r.checker.getReferencedValueOrAliasSymbol(node) @@ -1161,9 +1160,7 @@ func (r *EmitResolver) CreateLateBoundIndexSignatures(container *ast.Node, enclo func (r *EmitResolver) GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags { // node = emitContext.ParseNode(node) - r.checkerMu.Lock() - defer r.checkerMu.Unlock() - return r.checker.GetEffectiveDeclarationFlags(node, flags) + return r.checker.GetEffectiveDeclarationFlagsForEmit(node, flags) } func (r *EmitResolver) GetConstantValue(node *ast.Node) any { diff --git a/tsc/internal/checker/exports.go b/tsc/internal/checker/exports.go index 84105c950b8de..9bacb8a8d65a4 100644 --- a/tsc/internal/checker/exports.go +++ b/tsc/internal/checker/exports.go @@ -220,6 +220,12 @@ func (c *Checker) GetEffectiveDeclarationFlags(n *ast.Node, flagsToCheck ast.Mod return c.getEffectiveDeclarationFlags(n, flagsToCheck) } +func (c *Checker) GetEffectiveDeclarationFlagsForEmit(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags { + c.mu.Lock() + defer c.mu.Unlock() + return c.getEffectiveDeclarationFlags(node, flags) +} + func (c *Checker) GetBaseConstraintOfType(t *Type) *Type { return c.getBaseConstraintOfType(t) } diff --git a/tsc/internal/compiler/emitHost.go b/tsc/internal/compiler/emitHost.go index de2b6d8751fb9..95d846e885198 100644 --- a/tsc/internal/compiler/emitHost.go +++ b/tsc/internal/compiler/emitHost.go @@ -43,7 +43,7 @@ func newEmitHost(ctx context.Context, program *Program, file *ast.SourceFile) (* getEmitResolver: func(emitContext *printer.EmitContext) printer.EmitResolver { return checker.GetEmitResolver(emitContext) }, - getEffectiveDeclarationFlags: checker.GetEffectiveDeclarationFlags, + getEffectiveDeclarationFlags: checker.GetEffectiveDeclarationFlagsForEmit, }, done } From 60e9ff826362cc1b0d06d8256c435e435bdea187 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Oct 2026 12:50:11 -0700 Subject: [PATCH 03/10] Simplify --- tsc/internal/checker/emitresolver.go | 4 +++- tsc/internal/checker/exports.go | 6 ------ tsc/internal/compiler/emitHost.go | 10 ++------- .../transformers/declarations/transform.go | 21 +++++++++---------- .../transformers/declarations/util.go | 8 +++---- 5 files changed, 19 insertions(+), 30 deletions(-) diff --git a/tsc/internal/checker/emitresolver.go b/tsc/internal/checker/emitresolver.go index 40b60580b9120..cda15c21b6d34 100644 --- a/tsc/internal/checker/emitresolver.go +++ b/tsc/internal/checker/emitresolver.go @@ -1160,7 +1160,9 @@ func (r *EmitResolver) CreateLateBoundIndexSignatures(container *ast.Node, enclo func (r *EmitResolver) GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags { // node = emitContext.ParseNode(node) - return r.checker.GetEffectiveDeclarationFlagsForEmit(node, flags) + r.checkerMu.Lock() + defer r.checkerMu.Unlock() + return r.checker.GetEffectiveDeclarationFlags(node, flags) } func (r *EmitResolver) GetConstantValue(node *ast.Node) any { diff --git a/tsc/internal/checker/exports.go b/tsc/internal/checker/exports.go index 9bacb8a8d65a4..84105c950b8de 100644 --- a/tsc/internal/checker/exports.go +++ b/tsc/internal/checker/exports.go @@ -220,12 +220,6 @@ func (c *Checker) GetEffectiveDeclarationFlags(n *ast.Node, flagsToCheck ast.Mod return c.getEffectiveDeclarationFlags(n, flagsToCheck) } -func (c *Checker) GetEffectiveDeclarationFlagsForEmit(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags { - c.mu.Lock() - defer c.mu.Unlock() - return c.getEffectiveDeclarationFlags(node, flags) -} - func (c *Checker) GetBaseConstraintOfType(t *Type) *Type { return c.getBaseConstraintOfType(t) } diff --git a/tsc/internal/compiler/emitHost.go b/tsc/internal/compiler/emitHost.go index 95d846e885198..ceff0f5d58387 100644 --- a/tsc/internal/compiler/emitHost.go +++ b/tsc/internal/compiler/emitHost.go @@ -31,9 +31,8 @@ var _ EmitHost = (*emitHost)(nil) // NOTE: emitHost operations must be thread-safe type emitHost struct { - program *Program - getEmitResolver func(*printer.EmitContext) printer.EmitResolver - getEffectiveDeclarationFlags func(*ast.Node, ast.ModifierFlags) ast.ModifierFlags + program *Program + getEmitResolver func(*printer.EmitContext) printer.EmitResolver } func newEmitHost(ctx context.Context, program *Program, file *ast.SourceFile) (*emitHost, func()) { @@ -43,7 +42,6 @@ func newEmitHost(ctx context.Context, program *Program, file *ast.SourceFile) (* getEmitResolver: func(emitContext *printer.EmitContext) printer.EmitResolver { return checker.GetEmitResolver(emitContext) }, - getEffectiveDeclarationFlags: checker.GetEffectiveDeclarationFlagsForEmit, }, done } @@ -91,10 +89,6 @@ func (host *emitHost) GetRedirectTargets(path tspath.PathKey) []tspath.RootedFil return host.program.GetRedirectTargets(path) } -func (host *emitHost) GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags { - return host.getEffectiveDeclarationFlags(node, flags) -} - func (host *emitHost) GetOutputPathsFor(file *ast.SourceFile, forceDtsPaths bool) declarations.OutputPaths { // TODO: cache return outputpaths.GetOutputPathsFor(file, host.Options(), host, outputpaths.ForceEmitPaths{Dts: forceDtsPaths}) diff --git a/tsc/internal/transformers/declarations/transform.go b/tsc/internal/transformers/declarations/transform.go index 1ce3561a25d1c..deea2ae9a1819 100644 --- a/tsc/internal/transformers/declarations/transform.go +++ b/tsc/internal/transformers/declarations/transform.go @@ -38,7 +38,6 @@ type DeclarationEmitHost interface { GetOutputPathsFor(file *ast.SourceFile, forceDtsPaths bool) OutputPaths SourceFileMayBeEmitted(file *ast.SourceFile, forceDtsEmit bool) bool - GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags GetEmitResolver(emitContext *printer.EmitContext) printer.EmitResolver } @@ -821,7 +820,7 @@ func (tx *DeclarationTransformer) transformExpressionWithTypeArguments(input *as } func (tx *DeclarationTransformer) transformTypeParameterDeclaration(input *ast.TypeParameterDeclaration) *ast.Node { - if isPrivateMethodTypeParameter(tx.host, input) && (input.DefaultType != nil || input.Constraint != nil) { + if isPrivateMethodTypeParameter(tx.resolver, input) && (input.DefaultType != nil || input.Constraint != nil) { return tx.Factory().UpdateTypeParameterDeclaration( input, input.Modifiers(), @@ -1007,7 +1006,7 @@ func (tx *DeclarationTransformer) transformSetAccessorDeclaration(input *ast.Set tx.ensureModifiers(input.AsNode()), input.Name(), nil, // accessors shouldn't have type params - tx.updateAccessorParamList(input.AsNode(), tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0), + tx.updateAccessorParamList(input.AsNode(), tx.resolver.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0), nil, nil, nil, @@ -1023,7 +1022,7 @@ func (tx *DeclarationTransformer) transformGetAccesorDeclaration(input *ast.GetA tx.ensureModifiers(input.AsNode()), input.Name(), nil, // accessors shouldn't have type params - tx.updateAccessorParamList(input.AsNode(), tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0), + tx.updateAccessorParamList(input.AsNode(), tx.resolver.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0), tx.ensureType(input.AsNode(), false), nil, nil, @@ -1116,7 +1115,7 @@ func (tx *DeclarationTransformer) omitPrivateMethodType(input *ast.Node) *ast.No } func (tx *DeclarationTransformer) transformMethodSignatureDeclaration(input *ast.MethodSignatureDeclaration) *ast.Node { - if tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0 { + if tx.resolver.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0 { return tx.omitPrivateMethodType(input.AsNode()) } else if ast.IsPrivateIdentifier(input.Name()) { return nil @@ -1134,7 +1133,7 @@ func (tx *DeclarationTransformer) transformMethodSignatureDeclaration(input *ast } func (tx *DeclarationTransformer) transformMethodDeclaration(input *ast.MethodDeclaration) *ast.Node { - if tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0 { + if tx.resolver.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0 { return tx.omitPrivateMethodType(input.AsNode()) } else if ast.IsPrivateIdentifier(input.Name()) { return nil @@ -1638,7 +1637,7 @@ func (tx *DeclarationTransformer) removeAllComments(node *ast.Node) { } func (tx *DeclarationTransformer) ensureType(node *ast.Node, ignorePrivate bool) *ast.Node { - if !ignorePrivate && tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(node), ast.ModifierFlagsPrivate) != 0 { + if !ignorePrivate && tx.resolver.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(node), ast.ModifierFlagsPrivate) != 0 { // Private nodes emit no types (except private parameter properties, whose parameter types are actually visible) return nil } @@ -1702,7 +1701,7 @@ func (tx *DeclarationTransformer) ensureType(node *ast.Node, ignorePrivate bool) } func (tx *DeclarationTransformer) shouldPrintWithInitializer(node *ast.Node) bool { - return canHaveLiteralInitializer(tx.host, node) && node.Initializer() != nil && tx.resolver.IsLiteralConstDeclaration(tx.EmitContext().MostOriginal(node)) + return canHaveLiteralInitializer(tx.resolver, node) && node.Initializer() != nil && tx.resolver.IsLiteralConstDeclaration(tx.EmitContext().MostOriginal(node)) } func (tx *DeclarationTransformer) checkEntityNameVisibility(entityName *ast.Node, enclosingDeclaration *ast.Node) { @@ -1912,7 +1911,7 @@ func (tx *DeclarationTransformer) stripExportModifiers(statement *ast.Node) *ast return nil } parseNode := tx.EmitContext().ParseNode(statement) - if ast.IsImportEqualsDeclaration(statement) || (parseNode != nil && tx.host.GetEffectiveDeclarationFlags(parseNode, ast.ModifierFlagsDefault) != 0) || !ast.CanHaveModifiers(statement) { + if ast.IsImportEqualsDeclaration(statement) || (parseNode != nil && tx.resolver.GetEffectiveDeclarationFlags(parseNode, ast.ModifierFlagsDefault) != 0) || !ast.CanHaveModifiers(statement) { // `export import` statements should remain as-is, as imports are _not_ implicitly exported in an ambient namespace // Likewise, `export default` classes and the like and just be `default`, so we preserve their `export` modifiers, too return statement @@ -2358,7 +2357,7 @@ func (tx *DeclarationTransformer) ensureModifierFlags(node *ast.Node) ast.Modifi } func (tx *DeclarationTransformer) ensureTypeParams(node *ast.Node, params *ast.TypeParameterList) *ast.TypeParameterList { - if tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(node), ast.ModifierFlagsPrivate) != 0 { + if tx.resolver.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(node), ast.ModifierFlagsPrivate) != 0 { return nil } var typeParameters *ast.TypeParameterList @@ -2392,7 +2391,7 @@ func (tx *DeclarationTransformer) ensureTypeParams(node *ast.Node, params *ast.T } func (tx *DeclarationTransformer) updateParamList(node *ast.Node, params *ast.ParameterList) *ast.ParameterList { - if tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(node), ast.ModifierFlagsPrivate) != 0 || len(params.Nodes) == 0 { + if tx.resolver.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(node), ast.ModifierFlagsPrivate) != 0 || len(params.Nodes) == 0 { return tx.Factory().NewNodeList([]*ast.Node{}) } results := make([]*ast.Node, len(params.Nodes)) diff --git a/tsc/internal/transformers/declarations/util.go b/tsc/internal/transformers/declarations/util.go index cfe66b26ba76c..422a62134808f 100644 --- a/tsc/internal/transformers/declarations/util.go +++ b/tsc/internal/transformers/declarations/util.go @@ -10,11 +10,11 @@ func needsScopeMarker(result *ast.Node) bool { return !ast.IsAnyImportOrReExport(result) && !ast.IsExportAssignment(result) && !ast.HasSyntacticModifier(result, ast.ModifierFlagsExport) && !ast.IsAmbientModule(result) } -func canHaveLiteralInitializer(host DeclarationEmitHost, node *ast.Node) bool { +func canHaveLiteralInitializer(resolver printer.EmitResolver, node *ast.Node) bool { switch node.Kind { case ast.KindPropertyDeclaration, ast.KindPropertySignature: - return host.GetEffectiveDeclarationFlags(node, ast.ModifierFlagsPrivate) == 0 + return resolver.GetEffectiveDeclarationFlags(node, ast.ModifierFlagsPrivate) == 0 case ast.KindParameter, ast.KindVariableDeclaration: return true @@ -146,8 +146,8 @@ func unwrapParenthesizedExpression(o *ast.Node) *ast.Node { return o } -func isPrivateMethodTypeParameter(host DeclarationEmitHost, node *ast.TypeParameterDeclaration) bool { - return node.AsNode().Parent.Kind == ast.KindMethodDeclaration && host.GetEffectiveDeclarationFlags(node.AsNode().Parent, ast.ModifierFlagsPrivate) != 0 +func isPrivateMethodTypeParameter(resolver printer.EmitResolver, node *ast.TypeParameterDeclaration) bool { + return node.AsNode().Parent.Kind == ast.KindMethodDeclaration && resolver.GetEffectiveDeclarationFlags(node.AsNode().Parent, ast.ModifierFlagsPrivate) != 0 } // Returns true if expando properties should be emitted for this function. From 4399701e2302cd6c9525692ab206e132c0489c87 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Oct 2026 17:21:51 -0700 Subject: [PATCH 04/10] Rename get->new where apprpriate, cache in the emitHost, and unpool emit contexts so they have finite lifetimes --- tsc/internal/checker/checker.go | 2 +- tsc/internal/compiler/emitHost.go | 40 +++++++++++--- .../compiler/emitHost_resolver_test.go | 53 +++++++++++++++++++ tsc/internal/compiler/emitter.go | 14 ++--- tsc/internal/ls/findallreferences.go | 2 +- tsc/internal/printer/emitcontext.go | 21 -------- .../tsbaseline/type_symbol_baseline.go | 4 +- .../tstransforms/importelision_test.go | 2 +- 8 files changed, 96 insertions(+), 42 deletions(-) create mode 100644 tsc/internal/compiler/emitHost_resolver_test.go diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index c7ccc9f83f0b8..f589c1bfb5bd8 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -32697,7 +32697,7 @@ func (c *Checker) getDiagnosticsEmitResolver() *EmitResolver { return c.emitResolver } -func (c *Checker) GetEmitResolver(emitContext *printer.EmitContext) *EmitResolver { +func (c *Checker) NewEmitResolver(emitContext *printer.EmitContext) *EmitResolver { return newEmitResolver(c, emitContext) } diff --git a/tsc/internal/compiler/emitHost.go b/tsc/internal/compiler/emitHost.go index ceff0f5d58387..0b3daa3fa0e63 100644 --- a/tsc/internal/compiler/emitHost.go +++ b/tsc/internal/compiler/emitHost.go @@ -2,8 +2,11 @@ package compiler import ( "context" + "sync" + "weak" "github.com/microsoft/TypeScript/tsc/internal/ast" + "github.com/microsoft/TypeScript/tsc/internal/checker" "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/module" "github.com/microsoft/TypeScript/tsc/internal/outputpaths" @@ -32,16 +35,15 @@ var _ EmitHost = (*emitHost)(nil) // NOTE: emitHost operations must be thread-safe type emitHost struct { program *Program - getEmitResolver func(*printer.EmitContext) printer.EmitResolver + newEmitResolver func(*printer.EmitContext) *checker.EmitResolver + emitResolvers sync.Map } func newEmitHost(ctx context.Context, program *Program, file *ast.SourceFile) (*emitHost, func()) { checker, done := program.GetTypeCheckerForFile(ctx, file) return &emitHost{ - program: program, - getEmitResolver: func(emitContext *printer.EmitContext) printer.EmitResolver { - return checker.GetEmitResolver(emitContext) - }, + program: program, + newEmitResolver: checker.NewEmitResolver, }, done } @@ -129,7 +131,33 @@ func (host *emitHost) WriteFile(fileName tspath.RootedFilePath, text string) err } func (host *emitHost) GetEmitResolver(emitContext *printer.EmitContext) printer.EmitResolver { - return host.getEmitResolver(emitContext) + key := weak.Make(emitContext) + if cached, ok := host.emitResolvers.Load(key); ok { + if resolver := cached.(weak.Pointer[checker.EmitResolver]).Value(); resolver != nil { + return resolver + } + } + host.emitResolvers.Range(func(cachedKey, cachedValue any) bool { + entryKey := cachedKey.(weak.Pointer[printer.EmitContext]) + if entryKey.Value() == nil || cachedValue.(weak.Pointer[checker.EmitResolver]).Value() == nil { + host.emitResolvers.CompareAndDelete(cachedKey, cachedValue) + } + return true + }) + resolver := host.newEmitResolver(emitContext) + resolverRef := weak.Make(resolver) + for { + cached, loaded := host.emitResolvers.LoadOrStore(key, resolverRef) + if !loaded { + return resolver + } + if existing := cached.(weak.Pointer[checker.EmitResolver]).Value(); existing != nil { + return existing + } + if host.emitResolvers.CompareAndSwap(key, cached, resolverRef) { + return resolver + } + } } func (host *emitHost) IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool { diff --git a/tsc/internal/compiler/emitHost_resolver_test.go b/tsc/internal/compiler/emitHost_resolver_test.go new file mode 100644 index 0000000000000..c93efde72e608 --- /dev/null +++ b/tsc/internal/compiler/emitHost_resolver_test.go @@ -0,0 +1,53 @@ +package compiler + +import ( + "runtime" + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/checker" + "github.com/microsoft/TypeScript/tsc/internal/printer" +) + +func TestEmitHostResolverCache(t *testing.T) { + t.Parallel() + created := 0 + host := &emitHost{newEmitResolver: func(*printer.EmitContext) *checker.EmitResolver { + created++ + return &checker.EmitResolver{} + }} + emitContext := printer.NewEmitContext() + first := host.GetEmitResolver(emitContext) + if host.GetEmitResolver(emitContext) != first || created != 1 { + t.Fatal("expected repeated requests to reuse the resolver") + } + otherContext := printer.NewEmitContext() + if host.GetEmitResolver(otherContext) == first || created != 2 { + t.Fatal("expected distinct contexts to have distinct resolvers") + } + runtime.KeepAlive(first) +} + +func TestEmitHostResolverCacheConcurrent(t *testing.T) { + t.Parallel() + host := &emitHost{newEmitResolver: func(*printer.EmitContext) *checker.EmitResolver { + return &checker.EmitResolver{} + }} + emitContext := printer.NewEmitContext() + const callers = 16 + start := make(chan struct{}) + results := make(chan printer.EmitResolver, callers) + for range callers { + go func() { + <-start + results <- host.GetEmitResolver(emitContext) + }() + } + close(start) + first := <-results + for range callers - 1 { + if result := <-results; result != first { + t.Error("expected concurrent requests to reuse the same live resolver") + } + } + runtime.KeepAlive(first) +} diff --git a/tsc/internal/compiler/emitter.go b/tsc/internal/compiler/emitter.go index b1fcc71220a7d..75da6b0b89110 100644 --- a/tsc/internal/compiler/emitter.go +++ b/tsc/internal/compiler/emitter.go @@ -47,8 +47,9 @@ func (e *emitter) emit() { if e.tr != nil { defer e.tr.Push(tracing.PhaseEmit, "emit", map[string]any{"path": string(e.sourceFile.PathKey())}, true)() } - e.emitJSFile(e.sourceFile, e.paths.JsFilePath(), e.paths.SourceMapFilePath()) - e.emitDeclarationFile(e.sourceFile, e.paths.DeclarationFilePath(), e.paths.DeclarationMapPath()) + emitContext := printer.NewEmitContext() + e.emitJSFile(emitContext, e.sourceFile, e.paths.JsFilePath(), e.paths.SourceMapFilePath()) + e.emitDeclarationFile(emitContext, e.sourceFile, e.paths.DeclarationFilePath(), e.paths.DeclarationMapPath()) e.emitResult.Diagnostics = e.emitterDiagnostics.GetDiagnostics() } @@ -178,7 +179,7 @@ func getScriptTransformers(emitContext *printer.EmitContext, host printer.EmitHo return tx } -func (e *emitter) emitJSFile(sourceFile *ast.SourceFile, jsFilePath tspath.RootedFilePath, sourceMapFilePath tspath.RootedFilePath) { +func (e *emitter) emitJSFile(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, jsFilePath tspath.RootedFilePath, sourceMapFilePath tspath.RootedFilePath) { options := e.host.Options() if sourceFile == nil || e.emitOnly != EmitAll && e.emitOnly != EmitOnlyJs || jsFilePath == "" { @@ -194,9 +195,6 @@ func (e *emitter) emitJSFile(sourceFile *ast.SourceFile, jsFilePath tspath.Roote defer e.tr.Push(tracing.PhaseEmit, "emitJsFileOrBundle", map[string]any{"jsFilePath": jsFilePath}, true)() } - emitContext, putEmitContext := printer.GetEmitContext() - defer putEmitContext() - sourceFile = e.runScriptTransformers(emitContext, sourceFile) printerOptions := printer.PrinterOptions{ @@ -218,7 +216,7 @@ func (e *emitter) emitJSFile(sourceFile *ast.SourceFile, jsFilePath tspath.Roote e.printSourceFile(jsFilePath, sourceMapFilePath, sourceFile, printer, options, shouldEmitSourceMaps(options, sourceFile)) } -func (e *emitter) emitDeclarationFile(sourceFile *ast.SourceFile, declarationFilePath tspath.RootedFilePath, declarationMapPath tspath.RootedFilePath) { +func (e *emitter) emitDeclarationFile(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, declarationFilePath tspath.RootedFilePath, declarationMapPath tspath.RootedFilePath) { options := e.host.Options() if sourceFile == nil || e.emitOnly == EmitOnlyJs || declarationFilePath == "" { @@ -231,8 +229,6 @@ func (e *emitter) emitDeclarationFile(sourceFile *ast.SourceFile, declarationFil defer e.tr.Push(tracing.PhaseEmit, "emitDeclarationFileOrBundle", map[string]any{"declarationFilePath": declarationFilePath}, true)() } - emitContext, putEmitContext := printer.GetEmitContext() - defer putEmitContext() sourceFile, diags := e.runDeclarationTransformers(emitContext, sourceFile, declarationFilePath, declarationMapPath) for _, elem := range diags { diff --git a/tsc/internal/ls/findallreferences.go b/tsc/internal/ls/findallreferences.go index 9cefa911b6d7c..ea287f75d7638 100644 --- a/tsc/internal/ls/findallreferences.go +++ b/tsc/internal/ls/findallreferences.go @@ -509,7 +509,7 @@ func (l *LanguageService) getNonLocalDefinition(ctx context.Context, entry *Symb program := l.GetProgram() checker, done := program.GetTypeChecker(ctx) defer done() - emitResolver := checker.GetEmitResolver(printer.NewEmitContext()) + emitResolver := checker.NewEmitResolver(printer.NewEmitContext()) for _, d := range entry.definition.symbol.Declarations { if isDefinitionVisible(emitResolver, d) { file, startPos := getFileAndStartPosFromDeclaration(d) diff --git a/tsc/internal/printer/emitcontext.go b/tsc/internal/printer/emitcontext.go index ee483873f0d58..7953212bd40e7 100644 --- a/tsc/internal/printer/emitcontext.go +++ b/tsc/internal/printer/emitcontext.go @@ -3,7 +3,6 @@ package printer import ( "maps" "slices" - "sync" "sync/atomic" "github.com/microsoft/TypeScript/tsc/internal/ast" @@ -48,26 +47,6 @@ func NewEmitContext() *EmitContext { return c } -var emitContextPool = sync.Pool{ - New: func() any { - return NewEmitContext() - }, -} - -func GetEmitContext() (*EmitContext, func()) { - c := emitContextPool.Get().(*EmitContext) - return c, func() { - c.Reset() - emitContextPool.Put(c) - } -} - -func (c *EmitContext) Reset() { - *c = EmitContext{ - Factory: c.Factory, - } -} - func (c *EmitContext) onCreate(node *ast.Node) { node.Flags |= ast.NodeFlagsSynthesized } diff --git a/tsc/internal/testutil/tsbaseline/type_symbol_baseline.go b/tsc/internal/testutil/tsbaseline/type_symbol_baseline.go index 6a04466983cc6..343a85a74e973 100644 --- a/tsc/internal/testutil/tsbaseline/type_symbol_baseline.go +++ b/tsc/internal/testutil/tsbaseline/type_symbol_baseline.go @@ -350,8 +350,7 @@ func (walker *typeWriterWalker) writeTypeOrSymbol(node *ast.Node, isSymbolWalk b fileChecker, done := walker.getTypeCheckerForCurrentFile() defer done() - ctx, putCtx := printer.GetEmitContext() - defer putCtx() + ctx := printer.NewEmitContext() if !isSymbolWalk { // Don't try to get the type of something that's already a type. @@ -390,7 +389,6 @@ func (walker *typeWriterWalker) writeTypeOrSymbol(node *ast.Node, isSymbolWalk b !isIntrinsicJsxTag(node, walker.currentSourceFile) { typeString = t.AsIntrinsicType().IntrinsicName() } else { - ctx.Reset() builder := checker.NewNodeBuilder(fileChecker, ctx) typeFormatFlags := checker.TypeFormatFlagsNoTruncation | checker.TypeFormatFlagsAllowUniqueESSymbolType | checker.TypeFormatFlagsGenerateNamesForShadowedTypeParams typeNode := builder.TypeToTypeNode(t, node.Parent, nodebuilder.Flags(typeFormatFlags&checker.TypeFormatFlagsNodeBuilderFlagsMask)|nodebuilder.FlagsIgnoreErrors, nodebuilder.InternalFlagsAllowUnresolvedNames, nil) diff --git a/tsc/internal/transformers/tstransforms/importelision_test.go b/tsc/internal/transformers/tstransforms/importelision_test.go index 4da8073f6a139..b6654955a7c5d 100644 --- a/tsc/internal/transformers/tstransforms/importelision_test.go +++ b/tsc/internal/transformers/tstransforms/importelision_test.go @@ -264,7 +264,7 @@ func TestImportElision(t *testing.T) { }, nil) emitContext := printer.NewEmitContext() - emitResolver := c.GetEmitResolver(emitContext) + emitResolver := c.NewEmitResolver(emitContext) opts := &transformers.TransformOptions{CompilerOptions: compilerOptions, Context: emitContext, EmitResolver: emitResolver, Resolver: emitResolver} file = tstransforms.NewTypeEraserTransformer(opts).TransformSourceFile(file) From 38f28de97a9a2ef0f3f39845e22774f00838a6b1 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Oct 2026 17:36:13 -0700 Subject: [PATCH 05/10] Quick NewEmitContext usage audit now that its not pooled - move creation up to operation bounds (or remove entirely) --- tsc/internal/checker/services.go | 6 ++--- tsc/internal/ls/completions.go | 24 +++++++++++++++--- tsc/internal/ls/hover.go | 13 +++++++--- tsc/internal/ls/signaturehelp.go | 43 +++++++++++++++++--------------- 4 files changed, 56 insertions(+), 30 deletions(-) diff --git a/tsc/internal/checker/services.go b/tsc/internal/checker/services.go index 35fc2cf7e6dd7..73d70a79293c7 100644 --- a/tsc/internal/checker/services.go +++ b/tsc/internal/checker/services.go @@ -10,7 +10,6 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/collections" "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/debug" - "github.com/microsoft/TypeScript/tsc/internal/printer" "github.com/microsoft/TypeScript/tsc/internal/scanner" ) @@ -897,12 +896,11 @@ func (c *Checker) GetConstantValue(node *ast.Node) any { } func (c *Checker) getResolvedSignatureWorker(node *ast.Node, checkMode CheckMode, argumentCount int) (*Signature, []*Signature) { - parsedNode := printer.NewEmitContext().ParseNode(node) c.apparentArgumentCount = &argumentCount candidatesOutArray := &[]*Signature{} var res *Signature - if parsedNode != nil { - res = c.getResolvedSignature(parsedNode, candidatesOutArray, checkMode) + if node != nil && ast.IsParseTreeNode(node) { + res = c.getResolvedSignature(node, candidatesOutArray, checkMode) } c.apparentArgumentCount = nil return res, *candidatesOutArray diff --git a/tsc/internal/ls/completions.go b/tsc/internal/ls/completions.go index be8e2d0904d28..ff0f180d5b84d 100644 --- a/tsc/internal/ls/completions.go +++ b/tsc/internal/ls/completions.go @@ -6166,6 +6166,7 @@ func getJSDocParameterCompletions( } } paramIndex := -1 + var emitContext *printer.EmitContext return core.MapNonNil(fun.Parameters(), func(param *ast.ParameterDeclarationNode) *CompletionItem { paramIndex++ if paramIndex < paramTagCount { @@ -6176,6 +6177,7 @@ func getJSDocParameterCompletions( tabstopCounter := 1 paramName := param.Name().Text() displayText := getJSDocParamAnnotation( + &emitContext, paramName, param.Initializer(), param.AsParameterDeclaration().DotDotDotToken, @@ -6190,6 +6192,7 @@ func getJSDocParameterCompletions( var snippetText string if isSnippet { snippetText = getJSDocParamAnnotation( + &emitContext, paramName, param.Initializer(), param.AsParameterDeclaration().DotDotDotToken, @@ -6222,6 +6225,7 @@ func getJSDocParameterCompletions( // Destructuring parameter; do it positionally paramPath := fmt.Sprintf("param%d", paramIndex) displayTextResult := generateJSDocParamTagsForDestructuring( + &emitContext, paramPath, param.Name(), param.Initializer(), @@ -6235,6 +6239,7 @@ func getJSDocParameterCompletions( var snippetText string if isSnippet { snippetTextResult := generateJSDocParamTagsForDestructuring( + &emitContext, paramPath, param.Name(), param.Initializer(), @@ -6267,6 +6272,7 @@ func getJSDocParameterCompletions( } func getJSDocParamAnnotation( + emitContext **printer.EmitContext, paramName string, initializer *ast.Expression, dotDotDotToken *ast.TokenNode, @@ -6310,7 +6316,9 @@ func getJSDocParamAnnotation( nil, /*idToSymbol*/ ) if typeNode != nil { - emitContext := printer.NewEmitContext() + if *emitContext == nil { + *emitContext = printer.NewEmitContext() + } // !!! snippet p p := printer.NewPrinter(printer.PrinterOptions{ RemoveComments: true, @@ -6318,8 +6326,8 @@ func getJSDocParamAnnotation( // Module: options.Module, // ModuleResolution: options.ModuleResolution, // Target: options.Target, - }, printer.PrintHandlers{}, emitContext) - emitContext.SetEmitFlags(typeNode, printer.EFSingleLine) + }, printer.PrintHandlers{}, *emitContext) + (*emitContext).SetEmitFlags(typeNode, printer.EFSingleLine) t = p.Emit(typeNode, file) } } @@ -6358,6 +6366,7 @@ func getJSDocParamNameWithInitializer(paramName string, initializer *ast.Express } func generateJSDocParamTagsForDestructuring( + emitContext **printer.EmitContext, path string, pattern *ast.BindingPatternNode, initializer *ast.Expression, @@ -6371,6 +6380,7 @@ func generateJSDocParamTagsForDestructuring( tabstopCounter := 1 if !isJS { return []string{getJSDocParamAnnotation( + emitContext, path, initializer, dotDotDotToken, @@ -6384,6 +6394,7 @@ func generateJSDocParamTagsForDestructuring( )} } return jsDocParamPatternWorker( + emitContext, path, pattern, initializer, @@ -6398,6 +6409,7 @@ func generateJSDocParamTagsForDestructuring( } func jsDocParamPatternWorker( + emitContext **printer.EmitContext, path string, pattern *ast.BindingPatternNode, initializer *ast.Expression, @@ -6412,6 +6424,7 @@ func jsDocParamPatternWorker( if ast.IsObjectBindingPattern(pattern) && dotDotDotToken == nil { childCounter := *counter rootParam := getJSDocParamAnnotation( + emitContext, path, initializer, dotDotDotToken, @@ -6426,6 +6439,7 @@ func jsDocParamPatternWorker( var childTags []string for _, element := range pattern.Elements() { elementTags := jsDocParamElementWorker( + emitContext, path, element, initializer, @@ -6450,6 +6464,7 @@ func jsDocParamPatternWorker( } return []string{ getJSDocParamAnnotation( + emitContext, path, initializer, dotDotDotToken, @@ -6467,6 +6482,7 @@ func jsDocParamPatternWorker( // Assumes binding element is inside object binding pattern. // We can't deeply annotate an array binding pattern. func jsDocParamElementWorker( + emitContext **printer.EmitContext, path string, element *ast.BindingElementNode, initializer *ast.Expression, @@ -6491,6 +6507,7 @@ func jsDocParamElementWorker( paramName := fmt.Sprintf("%s.%s", path, propertyName) return []string{ getJSDocParamAnnotation( + emitContext, paramName, element.Initializer(), element.AsBindingElement().DotDotDotToken, @@ -6509,6 +6526,7 @@ func jsDocParamElementWorker( return nil } return jsDocParamPatternWorker( + emitContext, fmt.Sprintf("%s.%s", path, propertyName), element.Name(), element.Initializer(), diff --git a/tsc/internal/ls/hover.go b/tsc/internal/ls/hover.go index c86032ce1f196..ffe0b63856a66 100644 --- a/tsc/internal/ls/hover.go +++ b/tsc/internal/ls/hover.go @@ -438,6 +438,13 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol // nodeBuilderFlags for classified output (same as signatureHelpNodeBuilderFlags) const classifiedNodeBuilderFlags = nodebuilder.FlagsIgnoreErrors | nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope | nodebuilder.FlagsWriteTypeParametersInQualifiedName + var emitContext *printer.EmitContext + getEmitContext := func() *printer.EmitContext { + if emitContext == nil { + emitContext = printer.NewEmitContext() + } + return emitContext + } // writeTypeClassified writes a type to dpw with proper classification (punctuation, symbols, keywords). // Falls back to flat text when vsCapability is false or when TypeToTypeNode fails. @@ -447,7 +454,7 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol dpw.Write(c.TypeToStringEx(t, enclosing, flags, vc)) return } - emitContext := printer.NewEmitContext() + emitContext := getEmitContext() idToSymbol := make(map[*ast.IdentifierNode]*ast.Symbol) nb := checker.NewNodeBuilderEx(c, emitContext, idToSymbol) combinedFlags := nodebuilder.Flags(flags&checker.TypeFormatFlagsNodeBuilderFlagsMask) | classifiedNodeBuilderFlags @@ -485,7 +492,7 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol sigOutput = ast.KindCallSignature } } - emitContext := printer.NewEmitContext() + emitContext := getEmitContext() idToSymbol := make(map[*ast.IdentifierNode]*ast.Symbol) nb := checker.NewNodeBuilderEx(c, emitContext, idToSymbol) combinedFlags := nodebuilder.Flags(flags&checker.TypeFormatFlagsNodeBuilderFlagsMask) | classifiedNodeBuilderFlags @@ -519,7 +526,7 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol return } attributes := declaration.AsModuleDeclaration().Attributes - emitContext := printer.NewEmitContext() + emitContext := getEmitContext() emitContext.SetEmitFlags(attributes, printer.EFSingleLine) p := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, emitContext) tempDpw := newDisplayPartsWriter(vsCapability) diff --git a/tsc/internal/ls/signaturehelp.go b/tsc/internal/ls/signaturehelp.go index 78aa78490d4b8..65161c1418f60 100644 --- a/tsc/internal/ls/signaturehelp.go +++ b/tsc/internal/ls/signaturehelp.go @@ -209,11 +209,13 @@ func createTypeHelpItems(ctx context.Context, symbol *ast.Symbol, argumentInfo * } func getTypeHelpItem(symbol *ast.Symbol, typeParameter []*checker.Type, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, c *checker.Checker) signatureInformation { - printer := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, nil) + emitContext := printer.NewEmitContext() + builder := checker.NewNodeBuilder(c, emitContext) + printer := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, emitContext) parameters := make([]signatureHelpParameter, len(typeParameter)) for i, typeParam := range typeParameter { - parameters[i] = createSignatureHelpParameterForTypeParameter(typeParam, sourceFile, enclosingDeclaration, c, printer) + parameters[i] = createSignatureHelpParameterForTypeParameter(typeParam, sourceFile, enclosingDeclaration, builder, printer) } // Creating display label @@ -447,14 +449,15 @@ func (l *LanguageService) computeActiveParameter(sig signatureInformation, argum } func (l *LanguageService) getSignatureHelpItem(candidate *checker.Signature, isTypeParameterList bool, callTargetSymbol string, callTargetSym *ast.Symbol, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, c *checker.Checker, docFormat lsproto.MarkupKind, vsCapability bool) []signatureInformation { + emitContext := printer.NewEmitContext() var infos []*signatureHelpItemInfo if isTypeParameterList { - infos = l.itemInfoForTypeParameters(candidate, c, enclosingDeclaration, sourceFile, docFormat, vsCapability) + infos = l.itemInfoForTypeParameters(candidate, c, enclosingDeclaration, sourceFile, docFormat, vsCapability, emitContext) } else { - infos = l.itemInfoForParameters(candidate, c, enclosingDeclaration, sourceFile, docFormat, vsCapability) + infos = l.itemInfoForParameters(candidate, c, enclosingDeclaration, sourceFile, docFormat, vsCapability, emitContext) } - suffixDpw := returnTypeToDisplayParts(candidate, c, enclosingDeclaration, sourceFile, vsCapability) + suffixDpw := returnTypeToDisplayParts(candidate, c, enclosingDeclaration, sourceFile, vsCapability, emitContext) // Generate documentation from the signature's declaration var documentation *string @@ -485,7 +488,7 @@ func (l *LanguageService) getSignatureHelpItem(candidate *checker.Signature, isT return result } -func returnTypeToDisplayParts(candidateSignature *checker.Signature, c *checker.Checker, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, vsCapability bool) *displayPartsWriter { +func returnTypeToDisplayParts(candidateSignature *checker.Signature, c *checker.Checker, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, vsCapability bool, emitContext *printer.EmitContext) *displayPartsWriter { dpw := newDisplayPartsWriter(vsCapability) // Add ": " prefix @@ -498,7 +501,7 @@ func returnTypeToDisplayParts(candidateSignature *checker.Signature, c *checker. returnType := c.GetReturnTypeOfSignature(candidateSignature) typeNode := c.TypeToTypeNode(returnType, enclosingDeclaration, signatureHelpNodeBuilderFlags, nil) if typeNode != nil { - p := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, printer.NewEmitContext()) + p := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, emitContext) // Use a temporary writer for p.Write since the printer calls Clear() on its writer tempDpw := newDisplayPartsWriter(vsCapability) p.Write(typeNode, sourceFile, tempDpw, nil) @@ -510,8 +513,8 @@ func returnTypeToDisplayParts(candidateSignature *checker.Signature, c *checker. return dpw } -func (l *LanguageService) itemInfoForTypeParameters(candidateSignature *checker.Signature, c *checker.Checker, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, docFormat lsproto.MarkupKind, vsCapability bool) []*signatureHelpItemInfo { - emitContext := printer.NewEmitContext() +func (l *LanguageService) itemInfoForTypeParameters(candidateSignature *checker.Signature, c *checker.Checker, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, docFormat lsproto.MarkupKind, vsCapability bool, emitContext *printer.EmitContext) []*signatureHelpItemInfo { + builder := checker.NewNodeBuilder(c, emitContext) p := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, emitContext) var typeParameters []*checker.Type @@ -522,12 +525,12 @@ func (l *LanguageService) itemInfoForTypeParameters(candidateSignature *checker. } signatureHelpTypeParameters := make([]signatureHelpParameter, len(typeParameters)) for i, typeParameter := range typeParameters { - signatureHelpTypeParameters[i] = createSignatureHelpParameterForTypeParameter(typeParameter, sourceFile, enclosingDeclaration, c, p) + signatureHelpTypeParameters[i] = createSignatureHelpParameterForTypeParameter(typeParameter, sourceFile, enclosingDeclaration, builder, p) } thisParameter := []signatureHelpParameter{} if candidateSignature.ThisParameter() != nil { - thisParameter = []signatureHelpParameter{l.createSignatureHelpParameterForParameter(candidateSignature.ThisParameter(), enclosingDeclaration, p, sourceFile, c, docFormat)} + thisParameter = []signatureHelpParameter{l.createSignatureHelpParameterForParameter(candidateSignature.ThisParameter(), enclosingDeclaration, builder, p, sourceFile, c, docFormat)} } // Creating type parameter display label @@ -559,7 +562,7 @@ func (l *LanguageService) itemInfoForTypeParameters(candidateSignature *checker. parameters := thisParameter for j, param := range parameterList { - paramNode := checker.NewNodeBuilder(c, emitContext).SymbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil) + paramNode := builder.SymbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil) if j > 0 { paramDpw.WritePunctuation(", ") @@ -585,14 +588,14 @@ func (l *LanguageService) itemInfoForTypeParameters(candidateSignature *checker. return result } -func (l *LanguageService) itemInfoForParameters(candidateSignature *checker.Signature, c *checker.Checker, enclosingDeclaratipn *ast.Node, sourceFile *ast.SourceFile, docFormat lsproto.MarkupKind, vsCapability bool) []*signatureHelpItemInfo { - emitContext := printer.NewEmitContext() +func (l *LanguageService) itemInfoForParameters(candidateSignature *checker.Signature, c *checker.Checker, enclosingDeclaratipn *ast.Node, sourceFile *ast.SourceFile, docFormat lsproto.MarkupKind, vsCapability bool, emitContext *printer.EmitContext) []*signatureHelpItemInfo { + builder := checker.NewNodeBuilder(c, emitContext) p := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, emitContext) signatureHelpTypeParameters := make([]signatureHelpParameter, len(candidateSignature.TypeParameters())) if len(candidateSignature.TypeParameters()) != 0 { for i, typeParameter := range candidateSignature.TypeParameters() { - signatureHelpTypeParameters[i] = createSignatureHelpParameterForTypeParameter(typeParameter, sourceFile, enclosingDeclaratipn, c, p) + signatureHelpTypeParameters[i] = createSignatureHelpParameterForTypeParameter(typeParameter, sourceFile, enclosingDeclaratipn, builder, p) } } @@ -637,7 +640,7 @@ func (l *LanguageService) itemInfoForParameters(candidateSignature *checker.Sign paramDpw.WriteFrom(dpw) for j, param := range parameterList { - paramNode := checker.NewNodeBuilder(c, emitContext).SymbolToParameterDeclaration(param, enclosingDeclaratipn, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil) + paramNode := builder.SymbolToParameterDeclaration(param, enclosingDeclaratipn, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil) if j > 0 { paramDpw.WritePunctuation(", ") @@ -691,13 +694,13 @@ func (l *LanguageService) createSignatureHelpParameterFromLabel(parameter *ast.S } } -func (l *LanguageService) createSignatureHelpParameterForParameter(parameter *ast.Symbol, enclosingDeclaratipn *ast.Node, p *printer.Printer, sourceFile *ast.SourceFile, c *checker.Checker, docFormat lsproto.MarkupKind) signatureHelpParameter { - display := p.Emit(checker.NewNodeBuilder(c, printer.NewEmitContext()).SymbolToParameterDeclaration(parameter, enclosingDeclaratipn, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil), sourceFile) +func (l *LanguageService) createSignatureHelpParameterForParameter(parameter *ast.Symbol, enclosingDeclaratipn *ast.Node, builder *checker.NodeBuilder, p *printer.Printer, sourceFile *ast.SourceFile, c *checker.Checker, docFormat lsproto.MarkupKind) signatureHelpParameter { + display := p.Emit(builder.SymbolToParameterDeclaration(parameter, enclosingDeclaratipn, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil), sourceFile) return l.createSignatureHelpParameterFromLabel(parameter, display, c, docFormat) } -func createSignatureHelpParameterForTypeParameter(t *checker.Type, sourceFile *ast.SourceFile, enclosingDeclaration *ast.Node, c *checker.Checker, p *printer.Printer) signatureHelpParameter { - display := p.Emit(checker.NewNodeBuilder(c, printer.NewEmitContext()).TypeParameterToDeclaration(t, enclosingDeclaration, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil), sourceFile) +func createSignatureHelpParameterForTypeParameter(t *checker.Type, sourceFile *ast.SourceFile, enclosingDeclaration *ast.Node, builder *checker.NodeBuilder, p *printer.Printer) signatureHelpParameter { + display := p.Emit(builder.TypeParameterToDeclaration(t, enclosingDeclaration, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil), sourceFile) return signatureHelpParameter{ parameterInfo: &lsproto.ParameterInformation{ Label: lsproto.StringOrTuple{String: &display}, From 1c48062c1eb39dc685152fced46982881e164794 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Oct 2026 17:53:02 -0700 Subject: [PATCH 06/10] And toss in some releases for good measure --- tsc/internal/ls/hover.go | 2 ++ tsc/internal/ls/signaturehelp.go | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/tsc/internal/ls/hover.go b/tsc/internal/ls/hover.go index ffe0b63856a66..b39c53ab7334e 100644 --- a/tsc/internal/ls/hover.go +++ b/tsc/internal/ls/hover.go @@ -455,6 +455,7 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol return } emitContext := getEmitContext() + defer emitContext.Factory.ReleaseArenas() idToSymbol := make(map[*ast.IdentifierNode]*ast.Symbol) nb := checker.NewNodeBuilderEx(c, emitContext, idToSymbol) combinedFlags := nodebuilder.Flags(flags&checker.TypeFormatFlagsNodeBuilderFlagsMask) | classifiedNodeBuilderFlags @@ -493,6 +494,7 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol } } emitContext := getEmitContext() + defer emitContext.Factory.ReleaseArenas() idToSymbol := make(map[*ast.IdentifierNode]*ast.Symbol) nb := checker.NewNodeBuilderEx(c, emitContext, idToSymbol) combinedFlags := nodebuilder.Flags(flags&checker.TypeFormatFlagsNodeBuilderFlagsMask) | classifiedNodeBuilderFlags diff --git a/tsc/internal/ls/signaturehelp.go b/tsc/internal/ls/signaturehelp.go index 65161c1418f60..6e1fce510d96c 100644 --- a/tsc/internal/ls/signaturehelp.go +++ b/tsc/internal/ls/signaturehelp.go @@ -570,6 +570,7 @@ func (l *LanguageService) itemInfoForTypeParameters(candidateSignature *checker. // Use a temporary writer for p.Write since the printer calls Clear() on its writer tempDpw := newDisplayPartsWriter(vsCapability) p.Write(paramNode, sourceFile, tempDpw, nil) + emitContext.Factory.ReleaseArenas() paramLabel := tempDpw.String() paramDpw.WriteFrom(tempDpw) @@ -648,6 +649,7 @@ func (l *LanguageService) itemInfoForParameters(candidateSignature *checker.Sign // Use a temporary writer for p.Write since the printer calls Clear() on its writer tempDpw := newDisplayPartsWriter(vsCapability) p.Write(paramNode, sourceFile, tempDpw, nil) + emitContext.Factory.ReleaseArenas() paramLabel := tempDpw.String() paramDpw.WriteFrom(tempDpw) @@ -695,11 +697,13 @@ func (l *LanguageService) createSignatureHelpParameterFromLabel(parameter *ast.S } func (l *LanguageService) createSignatureHelpParameterForParameter(parameter *ast.Symbol, enclosingDeclaratipn *ast.Node, builder *checker.NodeBuilder, p *printer.Printer, sourceFile *ast.SourceFile, c *checker.Checker, docFormat lsproto.MarkupKind) signatureHelpParameter { + defer builder.EmitContext().Factory.ReleaseArenas() display := p.Emit(builder.SymbolToParameterDeclaration(parameter, enclosingDeclaratipn, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil), sourceFile) return l.createSignatureHelpParameterFromLabel(parameter, display, c, docFormat) } func createSignatureHelpParameterForTypeParameter(t *checker.Type, sourceFile *ast.SourceFile, enclosingDeclaration *ast.Node, builder *checker.NodeBuilder, p *printer.Printer) signatureHelpParameter { + defer builder.EmitContext().Factory.ReleaseArenas() display := p.Emit(builder.TypeParameterToDeclaration(t, enclosingDeclaration, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil), sourceFile) return signatureHelpParameter{ parameterInfo: &lsproto.ParameterInformation{ From 1d31075efc0ba7dfe4577a503b7fc8ac5228ee26 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Oct 2026 18:03:52 -0700 Subject: [PATCH 07/10] Alright, lets just remove the cache and see if we have perf issues, even... --- tsc/internal/compiler/emitHost.go | 33 +----------- .../compiler/emitHost_resolver_test.go | 53 ------------------- tsc/internal/compiler/emitter.go | 3 +- tsc/internal/printer/emithost.go | 2 +- .../transformers/declarations/transform.go | 4 +- 5 files changed, 7 insertions(+), 88 deletions(-) delete mode 100644 tsc/internal/compiler/emitHost_resolver_test.go diff --git a/tsc/internal/compiler/emitHost.go b/tsc/internal/compiler/emitHost.go index 0b3daa3fa0e63..46c160faa67fa 100644 --- a/tsc/internal/compiler/emitHost.go +++ b/tsc/internal/compiler/emitHost.go @@ -2,8 +2,6 @@ package compiler import ( "context" - "sync" - "weak" "github.com/microsoft/TypeScript/tsc/internal/ast" "github.com/microsoft/TypeScript/tsc/internal/checker" @@ -36,7 +34,6 @@ var _ EmitHost = (*emitHost)(nil) type emitHost struct { program *Program newEmitResolver func(*printer.EmitContext) *checker.EmitResolver - emitResolvers sync.Map } func newEmitHost(ctx context.Context, program *Program, file *ast.SourceFile) (*emitHost, func()) { @@ -130,34 +127,8 @@ func (host *emitHost) WriteFile(fileName tspath.RootedFilePath, text string) err return host.program.Host().FS().WriteFile(fileName, text) } -func (host *emitHost) GetEmitResolver(emitContext *printer.EmitContext) printer.EmitResolver { - key := weak.Make(emitContext) - if cached, ok := host.emitResolvers.Load(key); ok { - if resolver := cached.(weak.Pointer[checker.EmitResolver]).Value(); resolver != nil { - return resolver - } - } - host.emitResolvers.Range(func(cachedKey, cachedValue any) bool { - entryKey := cachedKey.(weak.Pointer[printer.EmitContext]) - if entryKey.Value() == nil || cachedValue.(weak.Pointer[checker.EmitResolver]).Value() == nil { - host.emitResolvers.CompareAndDelete(cachedKey, cachedValue) - } - return true - }) - resolver := host.newEmitResolver(emitContext) - resolverRef := weak.Make(resolver) - for { - cached, loaded := host.emitResolvers.LoadOrStore(key, resolverRef) - if !loaded { - return resolver - } - if existing := cached.(weak.Pointer[checker.EmitResolver]).Value(); existing != nil { - return existing - } - if host.emitResolvers.CompareAndSwap(key, cached, resolverRef) { - return resolver - } - } +func (host *emitHost) NewEmitResolver(emitContext *printer.EmitContext) printer.EmitResolver { + return host.newEmitResolver(emitContext) } func (host *emitHost) IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool { diff --git a/tsc/internal/compiler/emitHost_resolver_test.go b/tsc/internal/compiler/emitHost_resolver_test.go deleted file mode 100644 index c93efde72e608..0000000000000 --- a/tsc/internal/compiler/emitHost_resolver_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package compiler - -import ( - "runtime" - "testing" - - "github.com/microsoft/TypeScript/tsc/internal/checker" - "github.com/microsoft/TypeScript/tsc/internal/printer" -) - -func TestEmitHostResolverCache(t *testing.T) { - t.Parallel() - created := 0 - host := &emitHost{newEmitResolver: func(*printer.EmitContext) *checker.EmitResolver { - created++ - return &checker.EmitResolver{} - }} - emitContext := printer.NewEmitContext() - first := host.GetEmitResolver(emitContext) - if host.GetEmitResolver(emitContext) != first || created != 1 { - t.Fatal("expected repeated requests to reuse the resolver") - } - otherContext := printer.NewEmitContext() - if host.GetEmitResolver(otherContext) == first || created != 2 { - t.Fatal("expected distinct contexts to have distinct resolvers") - } - runtime.KeepAlive(first) -} - -func TestEmitHostResolverCacheConcurrent(t *testing.T) { - t.Parallel() - host := &emitHost{newEmitResolver: func(*printer.EmitContext) *checker.EmitResolver { - return &checker.EmitResolver{} - }} - emitContext := printer.NewEmitContext() - const callers = 16 - start := make(chan struct{}) - results := make(chan printer.EmitResolver, callers) - for range callers { - go func() { - <-start - results <- host.GetEmitResolver(emitContext) - }() - } - close(start) - first := <-results - for range callers - 1 { - if result := <-results; result != first { - t.Error("expected concurrent requests to reuse the same live resolver") - } - } - runtime.KeepAlive(first) -} diff --git a/tsc/internal/compiler/emitter.go b/tsc/internal/compiler/emitter.go index 75da6b0b89110..251aa15cf7d52 100644 --- a/tsc/internal/compiler/emitter.go +++ b/tsc/internal/compiler/emitter.go @@ -118,7 +118,7 @@ func getScriptTransformers(emitContext *printer.EmitContext, host printer.EmitHo importElisionEnabled := !options.VerbatimModuleSyntax.IsTrue() && !ast.IsInJSFile(sourceFile.AsNode()) jsxTransformEnabled := options.GetJSXTransformEnabled() && sourceFile.LanguageVariant == core.LanguageVariantJSX - emitResolver := host.GetEmitResolver(emitContext) + emitResolver := host.NewEmitResolver(emitContext) var referenceResolver binder.ReferenceResolver if importElisionEnabled || jsxTransformEnabled || !options.GetIsolatedModules() || options.EmitDecoratorMetadata.IsTrue() { @@ -214,6 +214,7 @@ func (e *emitter) emitJSFile(emitContext *printer.EmitContext, sourceFile *ast.S }, emitContext) e.printSourceFile(jsFilePath, sourceMapFilePath, sourceFile, printer, options, shouldEmitSourceMaps(options, sourceFile)) + emitContext.Factory.ReleaseArenas() } func (e *emitter) emitDeclarationFile(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, declarationFilePath tspath.RootedFilePath, declarationMapPath tspath.RootedFilePath) { diff --git a/tsc/internal/printer/emithost.go b/tsc/internal/printer/emithost.go index 6a046a56f69aa..fb08bd83799c5 100644 --- a/tsc/internal/printer/emithost.go +++ b/tsc/internal/printer/emithost.go @@ -16,7 +16,7 @@ type EmitHost interface { IsEmitBlocked(file tspath.RootedFilePath) bool WriteFile(fileName tspath.RootedFilePath, text string) error GetEmitModuleFormatOfFile(file ast.HasFileName) core.ModuleKind - GetEmitResolver(emitContext *EmitContext) EmitResolver + NewEmitResolver(emitContext *EmitContext) EmitResolver GetProjectReferenceFromSource(path tspath.PathKey) *tsoptions.SourceOutputAndProjectReference IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool } diff --git a/tsc/internal/transformers/declarations/transform.go b/tsc/internal/transformers/declarations/transform.go index deea2ae9a1819..1fbeab569d69f 100644 --- a/tsc/internal/transformers/declarations/transform.go +++ b/tsc/internal/transformers/declarations/transform.go @@ -38,7 +38,7 @@ type DeclarationEmitHost interface { GetOutputPathsFor(file *ast.SourceFile, forceDtsPaths bool) OutputPaths SourceFileMayBeEmitted(file *ast.SourceFile, forceDtsEmit bool) bool - GetEmitResolver(emitContext *printer.EmitContext) printer.EmitResolver + NewEmitResolver(emitContext *printer.EmitContext) printer.EmitResolver } type thisPropertyAssignmentKey struct { @@ -101,7 +101,7 @@ func NewDeclarationTransformer(host DeclarationEmitHost, context *printer.EmitCo if context == nil { context = printer.NewEmitContext() } - resolver := host.GetEmitResolver(context) + resolver := host.NewEmitResolver(context) state := &SymbolTrackerSharedState{isolatedDeclarations: compilerOptions.IsolatedDeclarations.IsTrue(), stripInternal: compilerOptions.StripInternal.IsTrue(), resolver: resolver} tracker := NewSymbolTracker(host, resolver, state) // TODO: Use new host GetOutputPathsFor method instead of passing in entrypoint paths (which will also better support bundled emit) From 1bc388e67d534491c1a099a44841bd52dc2f6251 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Oct 2026 18:28:24 -0700 Subject: [PATCH 08/10] To-done another related TODO and actually relayer all the awkward stuff --- tsc/internal/checker/checker.go | 10 - tsc/internal/checker/emitresolver.go | 339 +------------------- tsc/internal/checker/emitsupport.go | 336 +++++++++++++++++++ tsc/internal/checker/exports.go | 2 +- tsc/internal/checker/nodebuilderimpl.go | 6 +- tsc/internal/checker/symbolaccessibility.go | 3 +- tsc/internal/ls/hover.go | 8 +- 7 files changed, 349 insertions(+), 355 deletions(-) create mode 100644 tsc/internal/checker/emitsupport.go diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index f589c1bfb5bd8..9983614324115 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -889,8 +889,6 @@ type Checker struct { isStringIndexSignatureOnlyType func(*Type) bool markNodeAssignments func(*ast.Node) bool compareTypesAssignable TypeComparer - emitResolver *EmitResolver - emitResolverOnce sync.Once emitResolverLinks EmitResolverLinks _jsxNamespace string _jsxFactoryEntity *ast.Node @@ -32689,14 +32687,6 @@ func (c *Checker) GetTypeAtLocation(node *ast.Node) *Type { return c.getTypeOfNode(ast.GetReparsedNodeForNode(node)) } -func (c *Checker) getDiagnosticsEmitResolver() *EmitResolver { - c.emitResolverOnce.Do(func() { - c.emitResolver = newEmitResolver(c, printer.NewEmitContext()) - }) - - return c.emitResolver -} - func (c *Checker) NewEmitResolver(emitContext *printer.EmitContext) *EmitResolver { return newEmitResolver(c, emitContext) } diff --git a/tsc/internal/checker/emitresolver.go b/tsc/internal/checker/emitresolver.go index cda15c21b6d34..b39d2364185fd 100644 --- a/tsc/internal/checker/emitresolver.go +++ b/tsc/internal/checker/emitresolver.go @@ -120,132 +120,7 @@ func (r *EmitResolver) IsDeclarationVisible(node *ast.Node) bool { // Only lock on external API func to prevent deadlocks r.checkerMu.Lock() defer r.checkerMu.Unlock() - return r.isDeclarationVisible(node) -} - -func (r *EmitResolver) isDeclarationVisible(node *ast.Node) bool { - // node = r.emitContext.ParseNode(node) - if !ast.IsParseTreeNode(node) { - return false - } - if node == nil { - return false - } - - links := r.checker.emitResolverLinks.declarationLinks.Get(node) - if links.isVisible == core.TSUnknown { - if r.determineIfDeclarationIsVisible(node) { - links.isVisible = core.TSTrue - } else { - links.isVisible = core.TSFalse - } - } - return links.isVisible == core.TSTrue -} - -func (r *EmitResolver) determineIfDeclarationIsVisible(node *ast.Node) bool { - switch node.Kind { - case ast.KindJSDocCallbackTag, - // ast.KindJSDocEnumTag, // !!! TODO: JSDoc @enum support? - ast.KindJSDocTypedefTag: - // Top-level jsdoc type aliases are considered exported - // First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file - return node.Parent != nil && node.Parent.Parent != nil && node.Parent.Parent.Parent != nil && ast.IsSourceFile(node.Parent.Parent.Parent) - case ast.KindBindingElement: - return r.isDeclarationVisible(node.Parent.Parent) - case ast.KindVariableDeclaration, - ast.KindModuleDeclaration, - ast.KindClassDeclaration, - ast.KindInterfaceDeclaration, - ast.KindTypeAliasDeclaration, - ast.KindJSTypeAliasDeclaration, - ast.KindFunctionDeclaration, - ast.KindEnumDeclaration, - ast.KindImportEqualsDeclaration: - if ast.IsVariableDeclaration(node) { - if ast.IsBindingPattern(node.Name()) && - len(node.Name().Elements()) == 0 { - // If the binding pattern is empty, this variable declaration is not visible - return false - } - // falls through - } - // External module augmentation is always visible - // A @typedef at top-level in an external module is always visible - if ast.IsExternalModuleAugmentation(node) || ast.IsImplicitlyExportedJSDocDeclaration(node) { - return true - } - parent := ast.GetDeclarationContainer(node) - // If the node is not exported or it is not ambient module element (except import declaration) - if r.checker.getCombinedModifierFlagsCached(node)&ast.ModifierFlagsExport == 0 && - !(node.Kind != ast.KindImportEqualsDeclaration && parent.Kind != ast.KindSourceFile && parent.Flags&ast.NodeFlagsAmbient != 0) { - return ast.IsGlobalSourceFile(parent) - } - // Exported members/ambient module elements (exception import declaration) are visible if parent is visible - return r.isDeclarationVisible(parent) - - case ast.KindPropertyDeclaration, - ast.KindPropertySignature, - ast.KindGetAccessor, - ast.KindSetAccessor, - ast.KindMethodDeclaration, - ast.KindMethodSignature: - if r.checker.GetEffectiveDeclarationFlags(node, ast.ModifierFlagsPrivate|ast.ModifierFlagsProtected) != 0 { - // Private/protected properties/methods are not visible - return false - } - // Public properties/methods are visible if its parents are visible, so: - return r.isDeclarationVisible(node.Parent) - - case ast.KindConstructor, - ast.KindConstructSignature, - ast.KindCallSignature, - ast.KindIndexSignature, - ast.KindParameter, - ast.KindModuleBlock, - ast.KindFunctionType, - ast.KindConstructorType, - ast.KindTypeLiteral, - ast.KindTypeReference, - ast.KindArrayType, - ast.KindTupleType, - ast.KindUnionType, - ast.KindIntersectionType, - ast.KindParenthesizedType, - ast.KindNamedTupleMember: - return r.isDeclarationVisible(node.Parent) - - // Default binding, import specifier and namespace import is visible - // only on demand so by default it is not visible - case ast.KindImportClause, - ast.KindNamespaceImport, - ast.KindImportSpecifier: - return false - - // Type parameters are always visible - case ast.KindTypeParameter: - return true - // Source file and namespace export are always visible - case ast.KindSourceFile, - ast.KindNamespaceExportDeclaration: - return true - - // Export assignments do not create name bindings outside the module - case ast.KindExportAssignment: - return false - - // An `export {X}` (without a module specifier) is itself a visible re-export of - // the named binding; it contributes to the symbol's external visibility. - case ast.KindExportSpecifier: - exportDecl := node.Parent.Parent - if ast.IsExportDeclaration(exportDecl) && exportDecl.AsExportDeclaration().ModuleSpecifier == nil { - return r.isDeclarationVisible(exportDecl.Parent) - } - return false - - default: - return false - } + return r.checker.isDeclarationVisible(node) } func (r *EmitResolver) PrecalculateDeclarationEmitVisibility(file *ast.SourceFile) { @@ -323,160 +198,10 @@ func (r *EmitResolver) markLinkedAliases(node *ast.Node) { } } -func getMeaningOfEntityNameReference(entityName *ast.Node) ast.SymbolFlags { - // get symbol of the first identifier of the entityName - if entityName.Parent.Kind == ast.KindTypeQuery || - entityName.Parent.Kind == ast.KindExpressionWithTypeArguments && !ast.IsPartOfTypeNode(entityName.Parent) || - entityName.Parent.Kind == ast.KindComputedPropertyName || - entityName.Parent.Kind == ast.KindTypePredicate && entityName.Parent.AsTypePredicateNode().ParameterName == entityName || - entityName.Parent.Kind == ast.KindBinaryExpression { - // Typeof value - return ast.SymbolFlagsValue | ast.SymbolFlagsExportValue - } - if entityName.Kind == ast.KindQualifiedName || entityName.Kind == ast.KindPropertyAccessExpression || - entityName.Parent.Kind == ast.KindImportEqualsDeclaration || - (entityName.Parent.Kind == ast.KindQualifiedName && entityName.Parent.AsQualifiedName().Left == entityName) || - (entityName.Parent.Kind == ast.KindPropertyAccessExpression && entityName.Parent.Expression() == entityName) || - (entityName.Parent.Kind == ast.KindElementAccessExpression && entityName.Parent.Expression() == entityName) { - // Left identifier from type reference or TypeAlias - // Entity name of the import declaration - return ast.SymbolFlagsNamespace - } - // Type Reference or TypeAlias entity = Identifier - return ast.SymbolFlagsType -} - func (r *EmitResolver) IsEntityNameVisible(entityName *ast.Node, enclosingDeclaration *ast.Node) printer.SymbolAccessibilityResult { r.checkerMu.Lock() defer r.checkerMu.Unlock() - return r.isEntityNameVisible(entityName, enclosingDeclaration, true) -} - -func (r *EmitResolver) isEntityNameVisible(entityName *ast.Node, enclosingDeclaration *ast.Node, shouldComputeAliasToMakeVisible bool) printer.SymbolAccessibilityResult { - // node = r.emitContext.ParseNode(entityName) - if !ast.IsParseTreeNode(entityName) { - return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityNotAccessible} - } - - meaning := getMeaningOfEntityNameReference(entityName) - firstIdentifier := ast.GetFirstIdentifier(entityName) - - symbol := r.checker.resolveName(enclosingDeclaration, firstIdentifier.Text(), meaning, nil, false, false) - - if symbol != nil && symbol.Flags&ast.SymbolFlagsTypeParameter != 0 && meaning&ast.SymbolFlagsType != 0 { - return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityAccessible} - } - - if symbol == nil && ast.IsThisIdentifier(firstIdentifier) { - sym := r.checker.getSymbolOfDeclaration(r.checker.getThisContainer(firstIdentifier, false, false)) - if r.isSymbolAccessible(sym, enclosingDeclaration, meaning, false).Accessibility == printer.SymbolAccessibilityAccessible { - return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityAccessible} - } - } - - if symbol == nil { - return printer.SymbolAccessibilityResult{ - Accessibility: printer.SymbolAccessibilityNotResolved, - ErrorSymbolName: firstIdentifier.Text(), - ErrorNode: firstIdentifier, - } - } - - visible := r.hasVisibleDeclarations(symbol, shouldComputeAliasToMakeVisible) - if visible != nil { - return *visible - } - - return printer.SymbolAccessibilityResult{ - Accessibility: printer.SymbolAccessibilityNotAccessible, - ErrorSymbolName: firstIdentifier.Text(), - ErrorNode: firstIdentifier, - } -} - -func noopAddVisibleAlias(declaration *ast.Node, aliasingStatement *ast.Node) {} - -func (r *EmitResolver) hasVisibleDeclarations(symbol *ast.Symbol, shouldComputeAliasToMakeVisible bool) *printer.SymbolAccessibilityResult { - var aliasesToMakeVisibleSet map[ast.NodeId]*ast.Node - - var addVisibleAlias func(declaration *ast.Node, aliasingStatement *ast.Node) - if shouldComputeAliasToMakeVisible { - addVisibleAlias = func(declaration *ast.Node, aliasingStatement *ast.Node) { - r.checker.emitResolverLinks.declarationLinks.Get(declaration).isVisible = core.TSTrue - if aliasesToMakeVisibleSet == nil { - aliasesToMakeVisibleSet = make(map[ast.NodeId]*ast.Node) - } - aliasesToMakeVisibleSet[ast.GetNodeId(declaration)] = aliasingStatement - } - } else { - addVisibleAlias = noopAddVisibleAlias - } - - for _, declaration := range symbol.Declarations { - if ast.IsIdentifier(declaration) { - continue - } - if !r.isDeclarationVisible(declaration) { - // Mark the unexported alias as visible if its parent is visible - // because these kind of aliases can be used to name types in declaration file - anyImportSyntax := getAnyImportSyntax(declaration) - if anyImportSyntax != nil && - !ast.HasSyntacticModifier(anyImportSyntax, ast.ModifierFlagsExport) && // import clause without export - r.isDeclarationVisible(anyImportSyntax.Parent) { - addVisibleAlias(declaration, anyImportSyntax) - continue - } - if ast.IsVariableDeclaration(declaration) && ast.IsVariableStatement(declaration.Parent.Parent) && - !ast.HasSyntacticModifier(declaration.Parent.Parent, ast.ModifierFlagsExport) && // unexported variable statement - r.isDeclarationVisible(declaration.Parent.Parent.Parent) { - addVisibleAlias(declaration, declaration.Parent.Parent) - continue - } - if ast.IsLateVisibilityPaintedStatement(declaration) && // unexported top-level statement - !ast.HasSyntacticModifier(declaration, ast.ModifierFlagsExport) && - r.isDeclarationVisible(declaration.Parent) { - addVisibleAlias(declaration, declaration) - continue - } - if ast.IsBindingElement(declaration) { - if symbol.Flags&ast.SymbolFlagsAlias != 0 && ast.IsInJSFile(declaration) && declaration.Parent != nil && declaration.Parent.Parent != nil && // exported import-like top-level JS require statement - ast.IsVariableDeclaration(declaration.Parent.Parent) && - declaration.Parent.Parent.Parent.Parent != nil && ast.IsVariableStatement(declaration.Parent.Parent.Parent.Parent) && - !ast.HasSyntacticModifier(declaration.Parent.Parent.Parent.Parent, ast.ModifierFlagsExport) && - declaration.Parent.Parent.Parent.Parent.Parent != nil && // check if the thing containing the variable statement is visible (ie, the file) - r.isDeclarationVisible(declaration.Parent.Parent.Parent.Parent.Parent) { - addVisibleAlias(declaration, declaration.Parent.Parent.Parent.Parent) - continue - } - if symbol.Flags&ast.SymbolFlagsBlockScopedVariable != 0 { - rootDeclaration := ast.WalkUpBindingElementsAndPatterns(declaration) - if ast.IsParameterDeclaration(rootDeclaration) { - return nil - } - variableStatement := rootDeclaration.Parent.Parent - if !ast.IsVariableStatement(variableStatement) { - return nil - } - if ast.HasSyntacticModifier(variableStatement, ast.ModifierFlagsExport) { - continue // no alias to add, already exported - } - if !r.isDeclarationVisible(variableStatement.Parent) { - return nil // not visible - } - addVisibleAlias(declaration, variableStatement) - continue - } - } - - // Declaration is not visible - return nil - } - } - - return &printer.SymbolAccessibilityResult{ - Accessibility: printer.SymbolAccessibilityAccessible, - AliasesToMakeVisible: slices.Collect(maps.Values(aliasesToMakeVisibleSet)), - } + return r.checker.isEntityNameVisible(entityName, enclosingDeclaration, true) } func (r *EmitResolver) IsImplementationOfOverload(node *ast.SignatureDeclaration) bool { @@ -586,7 +311,7 @@ func (r *EmitResolver) RequiresAddingImplicitUndefined(declaration *ast.Node, sy } r.checkerMu.Lock() defer r.checkerMu.Unlock() - return r.requiresAddingImplicitUndefined(declaration, symbol, enclosingDeclaration) + return r.checker.requiresAddingImplicitUndefined(declaration, symbol, enclosingDeclaration) } func (r *EmitResolver) RequiresAddingImplicitUndefinedUnsafe(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool { @@ -594,61 +319,7 @@ func (r *EmitResolver) RequiresAddingImplicitUndefinedUnsafe(declaration *ast.No return false } // NO LOCKING - only should be called in contexts that already have a checker lock - return r.requiresAddingImplicitUndefined(declaration, symbol, enclosingDeclaration) -} - -func (r *EmitResolver) requiresAddingImplicitUndefined(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool { - // node = r.emitContext.ParseNode(node) - if !ast.IsParseTreeNode(declaration) { - return false - } - switch declaration.Kind { - case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindJSDocPropertyTag: - if symbol == nil { - symbol = r.checker.getSymbolOfDeclaration(declaration) - } - t := r.checker.getTypeOfSymbol(symbol) - r.checker.mappedSymbolLinks.Has(symbol) - return (symbol.Flags&ast.SymbolFlagsProperty != 0) && (symbol.Flags&ast.SymbolFlagsOptional != 0) && isOptionalDeclaration(declaration) && r.checker.ReverseMappedSymbolLinks.Has(symbol) && r.checker.ReverseMappedSymbolLinks.Get(symbol).mappedType != nil && containsNonMissingUndefinedType(r.checker, t) - case ast.KindParameter, ast.KindJSDocParameterTag: - return r.requiresAddingImplicitUndefinedWorker(declaration, enclosingDeclaration) - default: - panic("Node cannot possibly require adding undefined") - } -} - -func (r *EmitResolver) requiresAddingImplicitUndefinedWorker(parameter *ast.Node, enclosingDeclaration *ast.Node) bool { - return (r.isRequiredInitializedParameter(parameter, enclosingDeclaration) || r.isOptionalUninitializedParameterProperty(parameter)) && !r.declaredParameterTypeContainsUndefined(parameter) -} - -func (r *EmitResolver) declaredParameterTypeContainsUndefined(parameter *ast.Node) bool { - // typeNode := getNonlocalEffectiveTypeAnnotationNode(parameter); // !!! JSDoc Support - typeNode := parameter.Type() - if typeNode == nil { - return false - } - t := r.checker.getTypeFromTypeNode(typeNode) - // allow error type here to avoid confusing errors that the annotation has to contain undefined when it does in cases like this: - // - // export function fn(x?: Unresolved | undefined): void {} - return r.checker.isErrorType(t) || r.checker.containsUndefinedType(t) -} - -func (r *EmitResolver) isOptionalUninitializedParameterProperty(parameter *ast.Node) bool { - return r.checker.strictNullChecks && - r.isOptionalParameter(parameter) && - ( /*isJSDocParameterTag(parameter) ||*/ parameter.Initializer() == nil) && // !!! TODO: JSDoc support - ast.HasSyntacticModifier(parameter, ast.ModifierFlagsParameterPropertyModifier) -} - -func (r *EmitResolver) isRequiredInitializedParameter(parameter *ast.Node, enclosingDeclaration *ast.Node) bool { - if !r.checker.strictNullChecks || r.isOptionalParameter(parameter) || /*isJSDocParameterTag(parameter) ||*/ parameter.Initializer() == nil { // !!! TODO: JSDoc Support - return false - } - if ast.HasSyntacticModifier(parameter, ast.ModifierFlagsParameterPropertyModifier) { - return enclosingDeclaration != nil && ast.IsFunctionLikeDeclaration(enclosingDeclaration) - } - return true + return r.checker.requiresAddingImplicitUndefined(declaration, symbol, enclosingDeclaration) } func (r *EmitResolver) isOptionalParameter(node *ast.Node) bool { @@ -1106,7 +777,7 @@ func (r *EmitResolver) CreateLateBoundIndexSignatures(container *ast.Node, enclo return c.Name() != nil && ast.IsComputedPropertyName(c.Name()) && ast.IsEntityNameExpression(c.Name().Expression()) && - r.isEntityNameVisible(c.Name().Expression(), enclosingDeclaration, false).Accessibility == printer.SymbolAccessibilityAccessible + r.checker.isEntityNameVisible(c.Name().Expression(), enclosingDeclaration, false).Accessibility == printer.SymbolAccessibilityAccessible }) if allComponentComputedNamesSerializable { for _, c := range info.components { diff --git a/tsc/internal/checker/emitsupport.go b/tsc/internal/checker/emitsupport.go new file mode 100644 index 0000000000000..37e08a0052b46 --- /dev/null +++ b/tsc/internal/checker/emitsupport.go @@ -0,0 +1,336 @@ +package checker + +import ( + "maps" + "slices" + + "github.com/microsoft/TypeScript/tsc/internal/ast" + "github.com/microsoft/TypeScript/tsc/internal/core" + "github.com/microsoft/TypeScript/tsc/internal/printer" +) + +func (c *Checker) isDeclarationVisible(node *ast.Node) bool { + if !ast.IsParseTreeNode(node) { + return false + } + if node == nil { + return false + } + + links := c.emitResolverLinks.declarationLinks.Get(node) + if links.isVisible == core.TSUnknown { + if c.determineIfDeclarationIsVisible(node) { + links.isVisible = core.TSTrue + } else { + links.isVisible = core.TSFalse + } + } + return links.isVisible == core.TSTrue +} + +func (c *Checker) determineIfDeclarationIsVisible(node *ast.Node) bool { + switch node.Kind { + case ast.KindJSDocCallbackTag, + // ast.KindJSDocEnumTag, // !!! TODO: JSDoc @enum support? + ast.KindJSDocTypedefTag: + // Top-level jsdoc type aliases are considered exported + // First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file + return node.Parent != nil && node.Parent.Parent != nil && node.Parent.Parent.Parent != nil && ast.IsSourceFile(node.Parent.Parent.Parent) + case ast.KindBindingElement: + return c.isDeclarationVisible(node.Parent.Parent) + case ast.KindVariableDeclaration, + ast.KindModuleDeclaration, + ast.KindClassDeclaration, + ast.KindInterfaceDeclaration, + ast.KindTypeAliasDeclaration, + ast.KindJSTypeAliasDeclaration, + ast.KindFunctionDeclaration, + ast.KindEnumDeclaration, + ast.KindImportEqualsDeclaration: + if ast.IsVariableDeclaration(node) { + if ast.IsBindingPattern(node.Name()) && + len(node.Name().Elements()) == 0 { + // If the binding pattern is empty, this variable declaration is not visible + return false + } + // falls through + } + // External module augmentation is always visible + // A @typedef at top-level in an external module is always visible + if ast.IsExternalModuleAugmentation(node) || ast.IsImplicitlyExportedJSDocDeclaration(node) { + return true + } + parent := ast.GetDeclarationContainer(node) + // If the node is not exported or it is not ambient module element (except import declaration) + if c.getCombinedModifierFlagsCached(node)&ast.ModifierFlagsExport == 0 && + !(node.Kind != ast.KindImportEqualsDeclaration && parent.Kind != ast.KindSourceFile && parent.Flags&ast.NodeFlagsAmbient != 0) { + return ast.IsGlobalSourceFile(parent) + } + // Exported members/ambient module elements (exception import declaration) are visible if parent is visible + return c.isDeclarationVisible(parent) + + case ast.KindPropertyDeclaration, + ast.KindPropertySignature, + ast.KindGetAccessor, + ast.KindSetAccessor, + ast.KindMethodDeclaration, + ast.KindMethodSignature: + if c.GetEffectiveDeclarationFlags(node, ast.ModifierFlagsPrivate|ast.ModifierFlagsProtected) != 0 { + // Private/protected properties/methods are not visible + return false + } + // Public properties/methods are visible if its parents are visible, so: + return c.isDeclarationVisible(node.Parent) + + case ast.KindConstructor, + ast.KindConstructSignature, + ast.KindCallSignature, + ast.KindIndexSignature, + ast.KindParameter, + ast.KindModuleBlock, + ast.KindFunctionType, + ast.KindConstructorType, + ast.KindTypeLiteral, + ast.KindTypeReference, + ast.KindArrayType, + ast.KindTupleType, + ast.KindUnionType, + ast.KindIntersectionType, + ast.KindParenthesizedType, + ast.KindNamedTupleMember: + return c.isDeclarationVisible(node.Parent) + + // Default binding, import specifier and namespace import is visible + // only on demand so by default it is not visible + case ast.KindImportClause, + ast.KindNamespaceImport, + ast.KindImportSpecifier: + return false + + // Type parameters are always visible + case ast.KindTypeParameter: + return true + // Source file and namespace export are always visible + case ast.KindSourceFile, + ast.KindNamespaceExportDeclaration: + return true + + // Export assignments do not create name bindings outside the module + case ast.KindExportAssignment: + return false + + // An `export {X}` (without a module specifier) is itself a visible re-export of + // the named binding; it contributes to the symbol's external visibility. + case ast.KindExportSpecifier: + exportDecl := node.Parent.Parent + if ast.IsExportDeclaration(exportDecl) && exportDecl.AsExportDeclaration().ModuleSpecifier == nil { + return c.isDeclarationVisible(exportDecl.Parent) + } + return false + + default: + return false + } +} + +func getMeaningOfEntityNameReference(entityName *ast.Node) ast.SymbolFlags { + // get symbol of the first identifier of the entityName + if entityName.Parent.Kind == ast.KindTypeQuery || + entityName.Parent.Kind == ast.KindExpressionWithTypeArguments && !ast.IsPartOfTypeNode(entityName.Parent) || + entityName.Parent.Kind == ast.KindComputedPropertyName || + entityName.Parent.Kind == ast.KindTypePredicate && entityName.Parent.AsTypePredicateNode().ParameterName == entityName || + entityName.Parent.Kind == ast.KindBinaryExpression { + // Typeof value + return ast.SymbolFlagsValue | ast.SymbolFlagsExportValue + } + if entityName.Kind == ast.KindQualifiedName || entityName.Kind == ast.KindPropertyAccessExpression || + entityName.Parent.Kind == ast.KindImportEqualsDeclaration || + (entityName.Parent.Kind == ast.KindQualifiedName && entityName.Parent.AsQualifiedName().Left == entityName) || + (entityName.Parent.Kind == ast.KindPropertyAccessExpression && entityName.Parent.Expression() == entityName) || + (entityName.Parent.Kind == ast.KindElementAccessExpression && entityName.Parent.Expression() == entityName) { + // Left identifier from type reference or TypeAlias + // Entity name of the import declaration + return ast.SymbolFlagsNamespace + } + // Type Reference or TypeAlias entity = Identifier + return ast.SymbolFlagsType +} + +func (c *Checker) isEntityNameVisible(entityName *ast.Node, enclosingDeclaration *ast.Node, shouldComputeAliasToMakeVisible bool) printer.SymbolAccessibilityResult { + if !ast.IsParseTreeNode(entityName) { + return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityNotAccessible} + } + + meaning := getMeaningOfEntityNameReference(entityName) + firstIdentifier := ast.GetFirstIdentifier(entityName) + + symbol := c.resolveName(enclosingDeclaration, firstIdentifier.Text(), meaning, nil, false, false) + + if symbol != nil && symbol.Flags&ast.SymbolFlagsTypeParameter != 0 && meaning&ast.SymbolFlagsType != 0 { + return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityAccessible} + } + + if symbol == nil && ast.IsThisIdentifier(firstIdentifier) { + sym := c.getSymbolOfDeclaration(c.getThisContainer(firstIdentifier, false, false)) + if c.IsSymbolAccessible(sym, enclosingDeclaration, meaning, false).Accessibility == printer.SymbolAccessibilityAccessible { + return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityAccessible} + } + } + + if symbol == nil { + return printer.SymbolAccessibilityResult{ + Accessibility: printer.SymbolAccessibilityNotResolved, + ErrorSymbolName: firstIdentifier.Text(), + ErrorNode: firstIdentifier, + } + } + + visible := c.hasVisibleDeclarations(symbol, shouldComputeAliasToMakeVisible) + if visible != nil { + return *visible + } + + return printer.SymbolAccessibilityResult{ + Accessibility: printer.SymbolAccessibilityNotAccessible, + ErrorSymbolName: firstIdentifier.Text(), + ErrorNode: firstIdentifier, + } +} + +func noopAddVisibleAlias(declaration *ast.Node, aliasingStatement *ast.Node) {} + +func (c *Checker) hasVisibleDeclarations(symbol *ast.Symbol, shouldComputeAliasToMakeVisible bool) *printer.SymbolAccessibilityResult { + var aliasesToMakeVisibleSet map[ast.NodeId]*ast.Node + + var addVisibleAlias func(declaration *ast.Node, aliasingStatement *ast.Node) + if shouldComputeAliasToMakeVisible { + addVisibleAlias = func(declaration *ast.Node, aliasingStatement *ast.Node) { + c.emitResolverLinks.declarationLinks.Get(declaration).isVisible = core.TSTrue + if aliasesToMakeVisibleSet == nil { + aliasesToMakeVisibleSet = make(map[ast.NodeId]*ast.Node) + } + aliasesToMakeVisibleSet[ast.GetNodeId(declaration)] = aliasingStatement + } + } else { + addVisibleAlias = noopAddVisibleAlias + } + + for _, declaration := range symbol.Declarations { + if ast.IsIdentifier(declaration) { + continue + } + if !c.isDeclarationVisible(declaration) { + // Mark the unexported alias as visible if its parent is visible + // because these kind of aliases can be used to name types in declaration file + anyImportSyntax := getAnyImportSyntax(declaration) + if anyImportSyntax != nil && + !ast.HasSyntacticModifier(anyImportSyntax, ast.ModifierFlagsExport) && // import clause without export + c.isDeclarationVisible(anyImportSyntax.Parent) { + addVisibleAlias(declaration, anyImportSyntax) + continue + } + if ast.IsVariableDeclaration(declaration) && ast.IsVariableStatement(declaration.Parent.Parent) && + !ast.HasSyntacticModifier(declaration.Parent.Parent, ast.ModifierFlagsExport) && // unexported variable statement + c.isDeclarationVisible(declaration.Parent.Parent.Parent) { + addVisibleAlias(declaration, declaration.Parent.Parent) + continue + } + if ast.IsLateVisibilityPaintedStatement(declaration) && // unexported top-level statement + !ast.HasSyntacticModifier(declaration, ast.ModifierFlagsExport) && + c.isDeclarationVisible(declaration.Parent) { + addVisibleAlias(declaration, declaration) + continue + } + if ast.IsBindingElement(declaration) { + if symbol.Flags&ast.SymbolFlagsAlias != 0 && ast.IsInJSFile(declaration) && declaration.Parent != nil && declaration.Parent.Parent != nil && // exported import-like top-level JS require statement + ast.IsVariableDeclaration(declaration.Parent.Parent) && + declaration.Parent.Parent.Parent.Parent != nil && ast.IsVariableStatement(declaration.Parent.Parent.Parent.Parent) && + !ast.HasSyntacticModifier(declaration.Parent.Parent.Parent.Parent, ast.ModifierFlagsExport) && + declaration.Parent.Parent.Parent.Parent.Parent != nil && // check if the thing containing the variable statement is visible (ie, the file) + c.isDeclarationVisible(declaration.Parent.Parent.Parent.Parent.Parent) { + addVisibleAlias(declaration, declaration.Parent.Parent.Parent.Parent) + continue + } + if symbol.Flags&ast.SymbolFlagsBlockScopedVariable != 0 { + rootDeclaration := ast.WalkUpBindingElementsAndPatterns(declaration) + if ast.IsParameterDeclaration(rootDeclaration) { + return nil + } + variableStatement := rootDeclaration.Parent.Parent + if !ast.IsVariableStatement(variableStatement) { + return nil + } + if ast.HasSyntacticModifier(variableStatement, ast.ModifierFlagsExport) { + continue // no alias to add, already exported + } + if !c.isDeclarationVisible(variableStatement.Parent) { + return nil // not visible + } + addVisibleAlias(declaration, variableStatement) + continue + } + } + + // Declaration is not visible + return nil + } + } + + return &printer.SymbolAccessibilityResult{ + Accessibility: printer.SymbolAccessibilityAccessible, + AliasesToMakeVisible: slices.Collect(maps.Values(aliasesToMakeVisibleSet)), + } +} + +func (c *Checker) requiresAddingImplicitUndefined(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool { + if !ast.IsParseTreeNode(declaration) { + return false + } + switch declaration.Kind { + case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindJSDocPropertyTag: + if symbol == nil { + symbol = c.getSymbolOfDeclaration(declaration) + } + t := c.getTypeOfSymbol(symbol) + c.mappedSymbolLinks.Has(symbol) + return (symbol.Flags&ast.SymbolFlagsProperty != 0) && (symbol.Flags&ast.SymbolFlagsOptional != 0) && isOptionalDeclaration(declaration) && c.ReverseMappedSymbolLinks.Has(symbol) && c.ReverseMappedSymbolLinks.Get(symbol).mappedType != nil && containsNonMissingUndefinedType(c, t) + case ast.KindParameter, ast.KindJSDocParameterTag: + return c.requiresAddingImplicitUndefinedWorker(declaration, enclosingDeclaration) + default: + panic("Node cannot possibly require adding undefined") + } +} + +func (c *Checker) requiresAddingImplicitUndefinedWorker(parameter *ast.Node, enclosingDeclaration *ast.Node) bool { + return (c.isRequiredInitializedParameter(parameter, enclosingDeclaration) || c.isOptionalUninitializedParameterProperty(parameter)) && !c.declaredParameterTypeContainsUndefined(parameter) +} + +func (c *Checker) declaredParameterTypeContainsUndefined(parameter *ast.Node) bool { + // typeNode := getNonlocalEffectiveTypeAnnotationNode(parameter); // !!! JSDoc Support + typeNode := parameter.Type() + if typeNode == nil { + return false + } + t := c.getTypeFromTypeNode(typeNode) + // allow error type here to avoid confusing errors that the annotation has to contain undefined when it does in cases like this: + // + // export function fn(x?: Unresolved | undefined): void {} + return c.isErrorType(t) || c.containsUndefinedType(t) +} + +func (c *Checker) isOptionalUninitializedParameterProperty(parameter *ast.Node) bool { + return c.strictNullChecks && + c.isOptionalParameter(parameter) && + ( /*isJSDocParameterTag(parameter) ||*/ parameter.Initializer() == nil) && // !!! TODO: JSDoc support + ast.HasSyntacticModifier(parameter, ast.ModifierFlagsParameterPropertyModifier) +} + +func (c *Checker) isRequiredInitializedParameter(parameter *ast.Node, enclosingDeclaration *ast.Node) bool { + if !c.strictNullChecks || c.isOptionalParameter(parameter) || /*isJSDocParameterTag(parameter) ||*/ parameter.Initializer() == nil { // !!! TODO: JSDoc Support + return false + } + if ast.HasSyntacticModifier(parameter, ast.ModifierFlagsParameterPropertyModifier) { + return enclosingDeclaration != nil && ast.IsFunctionLikeDeclaration(enclosingDeclaration) + } + return true +} diff --git a/tsc/internal/checker/exports.go b/tsc/internal/checker/exports.go index 84105c950b8de..f77488e783142 100644 --- a/tsc/internal/checker/exports.go +++ b/tsc/internal/checker/exports.go @@ -392,7 +392,7 @@ func (c *Checker) RequiresAddingImplicitUndefined(node *ast.Node) bool { if symbol == nil { return false } - return c.getDiagnosticsEmitResolver().RequiresAddingImplicitUndefined(node, symbol, enclosingDeclaration) + return c.requiresAddingImplicitUndefined(node, symbol, enclosingDeclaration) } func (c *Checker) RemoveMissingOrUndefinedType(t *Type) *Type { diff --git a/tsc/internal/checker/nodebuilderimpl.go b/tsc/internal/checker/nodebuilderimpl.go index 6d6da08761cd5..4592b3c656c43 100644 --- a/tsc/internal/checker/nodebuilderimpl.go +++ b/tsc/internal/checker/nodebuilderimpl.go @@ -2153,8 +2153,7 @@ func (b *NodeBuilderImpl) isTriviallySerializableComputedName(e *ast.Node) bool if !shapeGood { return false } - // TODO: going through emit resolver here is weird. Relayer these APIs. - return b.ch.getDiagnosticsEmitResolver().isEntityNameVisible(e.Name().Expression(), b.ctx.enclosingDeclaration, false).Accessibility == printer.SymbolAccessibilityAccessible + return b.ch.isEntityNameVisible(e.Name().Expression(), b.ctx.enclosingDeclaration, false).Accessibility == printer.SymbolAccessibilityAccessible } func (b *NodeBuilderImpl) indexInfoToObjectComputedNamesOrSignatureDeclaration(indexInfo *IndexInfo, typeNode *ast.TypeNode) []*ast.Node { @@ -2285,8 +2284,7 @@ func (b *NodeBuilderImpl) serializeTypeForDeclaration(declaration *ast.Declarati } } - // !!! TODO: JSDoc, getEmitResolver call is unfortunate layering for the helper - hoist it into checker - requiresAddingUndefined := declaration != nil && (ast.IsParameterDeclaration(declaration) || ast.IsPropertySignatureDeclaration(declaration) || ast.IsPropertyDeclaration(declaration)) && b.ch.getDiagnosticsEmitResolver().requiresAddingImplicitUndefined(declaration, symbol, b.ctx.enclosingDeclaration) + requiresAddingUndefined := declaration != nil && (ast.IsParameterDeclaration(declaration) || ast.IsPropertySignatureDeclaration(declaration) || ast.IsPropertyDeclaration(declaration)) && b.ch.requiresAddingImplicitUndefined(declaration, symbol, b.ctx.enclosingDeclaration) addUndefinedForParameter := requiresAddingUndefined && (ast.IsParameterDeclaration(declaration) /*|| ast.IsJSDocParameterTag(declaration)*/) if addUndefinedForParameter { t = b.ch.getOptionalType(t, false) diff --git a/tsc/internal/checker/symbolaccessibility.go b/tsc/internal/checker/symbolaccessibility.go index db28c162d11d6..6115adea5d373 100644 --- a/tsc/internal/checker/symbolaccessibility.go +++ b/tsc/internal/checker/symbolaccessibility.go @@ -36,8 +36,7 @@ func (c *Checker) IsAnySymbolAccessible(symbols []*ast.Symbol, enclosingDeclarat accessibleSymbolChain := c.getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning /*useOnlyExternalAliasing*/, false) if len(accessibleSymbolChain) > 0 { hadAccessibleChain = symbol - // TODO: going through emit resolver here is weird. Relayer these APIs. - hasAccessibleDeclarations := c.getDiagnosticsEmitResolver().hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible) + hasAccessibleDeclarations := c.hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible) if hasAccessibleDeclarations != nil { return hasAccessibleDeclarations } diff --git a/tsc/internal/ls/hover.go b/tsc/internal/ls/hover.go index b39c53ab7334e..249960e8021dd 100644 --- a/tsc/internal/ls/hover.go +++ b/tsc/internal/ls/hover.go @@ -438,12 +438,12 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol // nodeBuilderFlags for classified output (same as signatureHelpNodeBuilderFlags) const classifiedNodeBuilderFlags = nodebuilder.FlagsIgnoreErrors | nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope | nodebuilder.FlagsWriteTypeParametersInQualifiedName - var emitContext *printer.EmitContext + var displayEmitContext *printer.EmitContext getEmitContext := func() *printer.EmitContext { - if emitContext == nil { - emitContext = printer.NewEmitContext() + if displayEmitContext == nil { + displayEmitContext = printer.NewEmitContext() } - return emitContext + return displayEmitContext } // writeTypeClassified writes a type to dpw with proper classification (punctuation, symbols, keywords). From a5efea3836478a439a54ad323ecaca2e52bee0d7 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Oct 2026 18:40:38 -0700 Subject: [PATCH 09/10] And hoist construction of emit resolvers up a bit and borrow an emit context from the resolver now that it owns one --- tsc/internal/checker/emitresolver.go | 4 ++ tsc/internal/compiler/emitter.go | 38 +++++++++++-------- tsc/internal/printer/emitresolver.go | 1 + .../transformers/declarations/transform.go | 9 ++--- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/tsc/internal/checker/emitresolver.go b/tsc/internal/checker/emitresolver.go index b39d2364185fd..4953359de14da 100644 --- a/tsc/internal/checker/emitresolver.go +++ b/tsc/internal/checker/emitresolver.go @@ -58,6 +58,10 @@ func newEmitResolver(checker *Checker, emitContext *printer.EmitContext) *EmitRe return e } +func (r *EmitResolver) EmitContext() *printer.EmitContext { + return r.emitContext +} + func (r *EmitResolver) nodeBuilder() *NodeBuilder { if r.requestNodeBuilder == nil { r.requestNodeBuilder = NewNodeBuilder(r.checker, r.emitContext) diff --git a/tsc/internal/compiler/emitter.go b/tsc/internal/compiler/emitter.go index 251aa15cf7d52..a6ede1f299240 100644 --- a/tsc/internal/compiler/emitter.go +++ b/tsc/internal/compiler/emitter.go @@ -48,8 +48,9 @@ func (e *emitter) emit() { defer e.tr.Push(tracing.PhaseEmit, "emit", map[string]any{"path": string(e.sourceFile.PathKey())}, true)() } emitContext := printer.NewEmitContext() - e.emitJSFile(emitContext, e.sourceFile, e.paths.JsFilePath(), e.paths.SourceMapFilePath()) - e.emitDeclarationFile(emitContext, e.sourceFile, e.paths.DeclarationFilePath(), e.paths.DeclarationMapPath()) + emitResolver := e.host.NewEmitResolver(emitContext) + e.emitJSFile(emitResolver, e.sourceFile, e.paths.JsFilePath(), e.paths.SourceMapFilePath()) + e.emitDeclarationFile(emitResolver, e.sourceFile, e.paths.DeclarationFilePath(), e.paths.DeclarationMapPath()) e.emitResult.Diagnostics = e.emitterDiagnostics.GetDiagnostics() } @@ -58,30 +59,30 @@ type declarationTransformer interface { GetDiagnostics() []*ast.Diagnostic } -func (e *emitter) getDeclarationTransformers(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, declarationFilePath tspath.RootedFilePath) []declarationTransformer { +func (e *emitter) getDeclarationTransformers(emitResolver printer.EmitResolver, sourceFile *ast.SourceFile, declarationFilePath tspath.RootedFilePath) []declarationTransformer { forceDtsEmit := e.emitOnly == EmitOnlyBuilderSignature || e.forceEmit && e.emitOnly == EmitOnlyDts return []declarationTransformer{ - declarations.NewDeclarationTransformer(e.host, emitContext, e.host.Options(), declarationFilePath), + declarations.NewDeclarationTransformer(e.host, emitResolver, e.host.Options(), declarationFilePath), declarations.NewSupplementalReferencesTransformer(e.host, sourceFile, declarationFilePath, forceDtsEmit), } } -func (e *emitter) runScriptTransformers(emitContext *printer.EmitContext, sourceFile *ast.SourceFile) *ast.SourceFile { +func (e *emitter) runScriptTransformers(emitResolver printer.EmitResolver, sourceFile *ast.SourceFile) *ast.SourceFile { if e.tr != nil { defer e.tr.Push(tracing.PhaseEmit, "transformNodes", map[string]any{"path": string(sourceFile.PathKey())}, false)() } - for _, transformer := range getScriptTransformers(emitContext, e.host, sourceFile) { + for _, transformer := range getScriptTransformers(emitResolver, e.host, sourceFile) { sourceFile = transformer.TransformSourceFile(sourceFile) } return sourceFile } -func (e *emitter) runDeclarationTransformers(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, declarationFilePath, declarationMapPath tspath.RootedFilePath) (*ast.SourceFile, []*ast.Diagnostic) { +func (e *emitter) runDeclarationTransformers(emitResolver printer.EmitResolver, sourceFile *ast.SourceFile, declarationFilePath, declarationMapPath tspath.RootedFilePath) (*ast.SourceFile, []*ast.Diagnostic) { if e.tr != nil { defer e.tr.Push(tracing.PhaseEmit, "transformNodes", map[string]any{"path": string(sourceFile.PathKey())}, false)() } var diags []*ast.Diagnostic - for _, transformer := range e.getDeclarationTransformers(emitContext, sourceFile, declarationFilePath) { + for _, transformer := range e.getDeclarationTransformers(emitResolver, sourceFile, declarationFilePath) { sourceFile = transformer.TransformSourceFile(sourceFile) diags = append(diags, transformer.GetDiagnostics()...) } @@ -110,7 +111,11 @@ func getModuleTransformer(opts *transformers.TransformOptions) *transformers.Tra } } -func getScriptTransformers(emitContext *printer.EmitContext, host printer.EmitHost, sourceFile *ast.SourceFile) []*transformers.Transformer { +func getScriptTransformers(emitResolver printer.EmitResolver, host printer.EmitHost, sourceFile *ast.SourceFile) []*transformers.Transformer { + if emitResolver == nil || emitResolver.EmitContext() == nil { + panic("Script transformers require an EmitResolver with an EmitContext") + } + emitContext := emitResolver.EmitContext() var tx []*transformers.Transformer options := host.Options() @@ -118,8 +123,6 @@ func getScriptTransformers(emitContext *printer.EmitContext, host printer.EmitHo importElisionEnabled := !options.VerbatimModuleSyntax.IsTrue() && !ast.IsInJSFile(sourceFile.AsNode()) jsxTransformEnabled := options.GetJSXTransformEnabled() && sourceFile.LanguageVariant == core.LanguageVariantJSX - emitResolver := host.NewEmitResolver(emitContext) - var referenceResolver binder.ReferenceResolver if importElisionEnabled || jsxTransformEnabled || !options.GetIsolatedModules() || options.EmitDecoratorMetadata.IsTrue() { referenceResolver = emitResolver @@ -179,7 +182,8 @@ func getScriptTransformers(emitContext *printer.EmitContext, host printer.EmitHo return tx } -func (e *emitter) emitJSFile(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, jsFilePath tspath.RootedFilePath, sourceMapFilePath tspath.RootedFilePath) { +func (e *emitter) emitJSFile(emitResolver printer.EmitResolver, sourceFile *ast.SourceFile, jsFilePath tspath.RootedFilePath, sourceMapFilePath tspath.RootedFilePath) { + emitContext := emitResolver.EmitContext() options := e.host.Options() if sourceFile == nil || e.emitOnly != EmitAll && e.emitOnly != EmitOnlyJs || jsFilePath == "" { @@ -195,7 +199,7 @@ func (e *emitter) emitJSFile(emitContext *printer.EmitContext, sourceFile *ast.S defer e.tr.Push(tracing.PhaseEmit, "emitJsFileOrBundle", map[string]any{"jsFilePath": jsFilePath}, true)() } - sourceFile = e.runScriptTransformers(emitContext, sourceFile) + sourceFile = e.runScriptTransformers(emitResolver, sourceFile) printerOptions := printer.PrinterOptions{ RemoveComments: options.RemoveComments.IsTrue(), @@ -217,7 +221,8 @@ func (e *emitter) emitJSFile(emitContext *printer.EmitContext, sourceFile *ast.S emitContext.Factory.ReleaseArenas() } -func (e *emitter) emitDeclarationFile(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, declarationFilePath tspath.RootedFilePath, declarationMapPath tspath.RootedFilePath) { +func (e *emitter) emitDeclarationFile(emitResolver printer.EmitResolver, sourceFile *ast.SourceFile, declarationFilePath tspath.RootedFilePath, declarationMapPath tspath.RootedFilePath) { + emitContext := emitResolver.EmitContext() options := e.host.Options() if sourceFile == nil || e.emitOnly == EmitOnlyJs || declarationFilePath == "" { @@ -230,7 +235,7 @@ func (e *emitter) emitDeclarationFile(emitContext *printer.EmitContext, sourceFi defer e.tr.Push(tracing.PhaseEmit, "emitDeclarationFileOrBundle", map[string]any{"declarationFilePath": declarationFilePath}, true)() } - sourceFile, diags := e.runDeclarationTransformers(emitContext, sourceFile, declarationFilePath, declarationMapPath) + sourceFile, diags := e.runDeclarationTransformers(emitResolver, sourceFile, declarationFilePath, declarationMapPath) for _, elem := range diags { // Add declaration transform diagnostics to emit diagnostics @@ -550,7 +555,8 @@ func getDeclarationDiagnostics(host EmitHost, file *ast.SourceFile) []*ast.Diagn return []*ast.Diagnostic{} } options := host.Options() - transform := declarations.NewDeclarationTransformer(host, nil, options, "") + emitResolver := host.NewEmitResolver(printer.NewEmitContext()) + transform := declarations.NewDeclarationTransformer(host, emitResolver, options, "") transform.TransformSourceFile(file) return transform.GetDiagnostics() } diff --git a/tsc/internal/printer/emitresolver.go b/tsc/internal/printer/emitresolver.go index 59f1ed44ed4dc..d86a0ca378ecc 100644 --- a/tsc/internal/printer/emitresolver.go +++ b/tsc/internal/printer/emitresolver.go @@ -75,6 +75,7 @@ const ( type EmitResolver interface { binder.ReferenceResolver + EmitContext() *EmitContext IsReferencedAliasDeclaration(node *ast.Node) bool IsValueAliasDeclaration(node *ast.Node) bool IsTopLevelValueImportEqualsWithEntityName(node *ast.Node) bool diff --git a/tsc/internal/transformers/declarations/transform.go b/tsc/internal/transformers/declarations/transform.go index 1fbeab569d69f..0daf681bfb59e 100644 --- a/tsc/internal/transformers/declarations/transform.go +++ b/tsc/internal/transformers/declarations/transform.go @@ -38,7 +38,6 @@ type DeclarationEmitHost interface { GetOutputPathsFor(file *ast.SourceFile, forceDtsPaths bool) OutputPaths SourceFileMayBeEmitted(file *ast.SourceFile, forceDtsEmit bool) bool - NewEmitResolver(emitContext *printer.EmitContext) printer.EmitResolver } type thisPropertyAssignmentKey struct { @@ -97,11 +96,11 @@ type DeclarationTransformer struct { } // TODO: Convert to transformers.TransformerFactory signature to allow more automatic composition with other transforms -func NewDeclarationTransformer(host DeclarationEmitHost, context *printer.EmitContext, compilerOptions *core.CompilerOptions, declarationFilePath tspath.RootedFilePath) *DeclarationTransformer { - if context == nil { - context = printer.NewEmitContext() +func NewDeclarationTransformer(host DeclarationEmitHost, resolver printer.EmitResolver, compilerOptions *core.CompilerOptions, declarationFilePath tspath.RootedFilePath) *DeclarationTransformer { + if resolver == nil || resolver.EmitContext() == nil { + panic("DeclarationTransformer requires an EmitResolver with an EmitContext") } - resolver := host.NewEmitResolver(context) + context := resolver.EmitContext() state := &SymbolTrackerSharedState{isolatedDeclarations: compilerOptions.IsolatedDeclarations.IsTrue(), stripInternal: compilerOptions.StripInternal.IsTrue(), resolver: resolver} tracker := NewSymbolTracker(host, resolver, state) // TODO: Use new host GetOutputPathsFor method instead of passing in entrypoint paths (which will also better support bundled emit) From 234fc0917300ce45ffd9ca5e42ea6c28c9533581 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Oct 2026 18:49:40 -0700 Subject: [PATCH 10/10] Move emit arena free a smidge so it applies to declaration emit, too --- tsc/internal/compiler/emitter.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tsc/internal/compiler/emitter.go b/tsc/internal/compiler/emitter.go index a6ede1f299240..d2a70b1d2a4ae 100644 --- a/tsc/internal/compiler/emitter.go +++ b/tsc/internal/compiler/emitter.go @@ -217,8 +217,7 @@ func (e *emitter) emitJSFile(emitResolver printer.EmitResolver, sourceFile *ast. // !!! }, emitContext) - e.printSourceFile(jsFilePath, sourceMapFilePath, sourceFile, printer, options, shouldEmitSourceMaps(options, sourceFile)) - emitContext.Factory.ReleaseArenas() + e.printSourceFile(emitContext, jsFilePath, sourceMapFilePath, sourceFile, printer, options, shouldEmitSourceMaps(options, sourceFile)) } func (e *emitter) emitDeclarationFile(emitResolver printer.EmitResolver, sourceFile *ast.SourceFile, declarationFilePath tspath.RootedFilePath, declarationMapPath tspath.RootedFilePath) { @@ -291,7 +290,7 @@ func (e *emitter) emitDeclarationFile(emitResolver printer.EmitResolver, sourceF MapRoot: options.MapRoot, // Explicitly do not pass through either inline option. } - e.printSourceFile(declarationFilePath, declarationMapPath, sourceFile, printer, declarationMapOptions, shouldEmitSourceMaps(declarationMapOptions, sourceFile)) + e.printSourceFile(emitContext, declarationFilePath, declarationMapPath, sourceFile, printer, declarationMapOptions, shouldEmitSourceMaps(declarationMapOptions, sourceFile)) } type declarationMapSource struct { @@ -313,7 +312,7 @@ func (s *declarationMapSource) FileName() tspath.RootedFilePath { return s.fileN func (s *declarationMapSource) Text() string { return s.text } func (s *declarationMapSource) ECMALineMap() []core.TextPos { return s.lineMap } -func (e *emitter) printSourceFile(jsFilePath tspath.RootedFilePath, sourceMapFilePath tspath.RootedFilePath, sourceFile *ast.SourceFile, printer_ *printer.Printer, mapOptions *core.CompilerOptions, shouldEmitSourceMaps bool) { +func (e *emitter) printSourceFile(emitContext *printer.EmitContext, jsFilePath tspath.RootedFilePath, sourceMapFilePath tspath.RootedFilePath, sourceFile *ast.SourceFile, printer_ *printer.Printer, mapOptions *core.CompilerOptions, shouldEmitSourceMaps bool) { // !!! sourceMapGenerator options := e.host.Options() var sourceMapGenerator *sourcemap.Generator @@ -327,6 +326,7 @@ func (e *emitter) printSourceFile(jsFilePath tspath.RootedFilePath, sourceMapFil } printer_.Write(sourceFile.AsNode(), sourceFile, e.writer, sourceMapGenerator) + emitContext.Factory.ReleaseArenas() sourceMapUrlPos := -1 if sourceMapGenerator != nil {