package checker import ( "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/debug" "github.com/microsoft/typescript-go/internal/nodebuilder" ) func cloneNodeBuilderContext(context *NodeBuilderContext) func() { // Make type parameters created within this context not consume the name outside this context // The symbol serializer ends up creating many sibling scopes that all need "separate" contexts when // it comes to naming things - within a normal `typeToTypeNode` call, the node builder only ever descends // through the type tree, so the only cases where we could have used distinct sibling scopes was when there // were multiple generic overloads with similar generated type parameter names // The effect: // When we write out // export const x: (x: T) => T // export const y: (x: T) => T // we write it out like that, rather than as // export const x: (x: T) => T // export const y: (x: T_1) => T_1 restoreNames := context.typeParameterNames.EnterScope() restoreNamesByText := context.typeParameterNamesByText.EnterScope() restoreNamesByTextNextNameCount := context.typeParameterNamesByTextNextNameCount.EnterScope() restoreSymbolList := context.typeParameterSymbolList.EnterScope() return func() { restoreNames() restoreNamesByText() restoreNamesByTextNextNameCount() restoreSymbolList() } } type localsRecord struct { name string oldSymbol *ast.Symbol } func (b *NodeBuilderImpl) addSymbolTypeToContext(symbol *ast.Symbol, t *Type) func() { id := ast.GetSymbolId(symbol) oldType, oldTypeExists := b.ctx.enclosingSymbolTypes[id] b.ctx.enclosingSymbolTypes[id] = t return func() { if oldTypeExists { b.ctx.enclosingSymbolTypes[id] = oldType } else { delete(b.ctx.enclosingSymbolTypes, id) } } } func (b *NodeBuilderImpl) enterSignatureScope(signature *Signature) (expandedParams []*ast.Symbol, cleanup func()) { expandedParams = b.ch.getExpandedParameters(signature, true /*skipUnionExpanding*/)[0] cleanup = b.enterNewScope(signature.declaration, expandedParams, signature.typeParameters, signature.parameters, signature.mapper) return expandedParams, cleanup } func (b *NodeBuilderImpl) enterNewScope(declaration *ast.Node, expandedParams []*ast.Symbol, typeParameters []*Type, originalParameters []*ast.Symbol, mapper *TypeMapper) func() { cleanupContext := cloneNodeBuilderContext(b.ctx) // For regular function/method declarations, the enclosing declaration will already be signature.declaration, // so this is a no-op, but for arrow functions and function expressions, the enclosing declaration will be // the declaration that the arrow function / function expression is assigned to. // // If the parameters or return type include "typeof globalThis.paramName", using the wrong scope will lead // us to believe that we can emit "typeof paramName" instead, even though that would refer to the parameter, // not the global. Make sure we are in the right scope by changing the enclosingDeclaration to the function. // // We can't use the declaration directly; it may be in another file and so we may lose access to symbols // accessible to the current enclosing declaration, or gain access to symbols not accessible to the current // enclosing declaration. To keep this chain accurate, insert a fake scope into the chain which makes the // function's parameters visible. var cleanupParams func() var cleanupTypeParams func() oldEnclosingDecl := b.ctx.enclosingDeclaration oldMapper := b.ctx.mapper if mapper != nil { b.ctx.mapper = mapper } if b.ctx.enclosingDeclaration != nil && declaration != nil { // As a performance optimization, reuse the same fake scope within this chain. // This is especially needed when we are working on an excessively deep type; // if we don't do this, then we spend all of our time adding more and more // scopes that need to be searched in isSymbolAccessible later. Since all we // really want to do is to mark certain names as unavailable, we can just keep // all of the names we're introducing in one large table and push/pop from it as // needed; isSymbolAccessible will walk upward and find the closest "fake" scope, // which will conveniently report on any and all faked scopes in the chain. // // It'd likely be better to store this somewhere else for isSymbolAccessible, but // since that API _only_ uses the enclosing declaration (and its parents), this is // seems like the best way to inject names into that search process. // // Note that we only check the most immediate enclosingDeclaration; the only place we // could potentially add another fake scope into the chain is right here, so we don't // traverse all ancestors. pushFakeScope := func(kind string, addAll func(addSymbol func(name string, symbol *ast.Symbol))) func() { // We only ever need to look two declarations upward. debug.Assert(b.ctx.enclosingDeclaration != nil) var existingFakeScope *ast.Node if b.links.Has(b.ctx.enclosingDeclaration) { links := b.links.Get(b.ctx.enclosingDeclaration) if links.fakeScopeForSignatureDeclaration != nil && *links.fakeScopeForSignatureDeclaration == kind { existingFakeScope = b.ctx.enclosingDeclaration } } if existingFakeScope == nil && b.ctx.enclosingDeclaration.Parent != nil { if b.links.Has(b.ctx.enclosingDeclaration.Parent) { links := b.links.Get(b.ctx.enclosingDeclaration.Parent) if links.fakeScopeForSignatureDeclaration != nil && *links.fakeScopeForSignatureDeclaration == kind { existingFakeScope = b.ctx.enclosingDeclaration.Parent } } } debug.Assert(existingFakeScope == nil || ast.IsBlock(existingFakeScope)) var locals ast.SymbolTable if existingFakeScope != nil { locals = existingFakeScope.Locals() } if locals == nil { locals = make(ast.SymbolTable) } newLocals := []string{} oldLocals := []localsRecord{} addAll(func(name string, symbol *ast.Symbol) { // Add cleanup information only if we don't own the fake scope if existingFakeScope != nil { oldSymbol, ok := locals[name] if !ok || oldSymbol == nil { newLocals = append(newLocals, name) } else { oldLocals = append(oldLocals, localsRecord{name, oldSymbol}) } } locals[name] = symbol }) if existingFakeScope == nil { // Use a Block for this; the type of the node doesn't matter so long as it // has locals, and this is cheaper/easier than using a function-ish Node. fakeScope := b.f.NewBlock(b.f.NewNodeList([]*ast.Node{}), false) b.links.Get(fakeScope).fakeScopeForSignatureDeclaration = &kind data := fakeScope.LocalsContainerData() data.Locals = locals fakeScope.Parent = b.ctx.enclosingDeclaration b.ctx.enclosingDeclaration = fakeScope return nil } else { // We did not create the current scope, so we have to clean it up undo := func() { for _, s := range newLocals { delete(locals, s) } for _, s := range oldLocals { locals[s.name] = s.oldSymbol } } return undo } } if expandedParams == nil || !core.Some(expandedParams, func(p *ast.Symbol) bool { return p != nil }) { cleanupParams = nil } else { cleanupParams = pushFakeScope("params", func(add func(name string, symbol *ast.Symbol)) { if expandedParams == nil { return } for pIndex, param := range expandedParams { var originalParam *ast.Symbol if pIndex < len(originalParameters) { originalParam = originalParameters[pIndex] } if originalParameters != nil && originalParam != param { // Can't reference the expanded parameter name, just the original, unless we've expanded the param list for some reason if originalParam != nil { add(originalParam.Name, originalParam) } } else if !core.Some(param.Declarations, func(d *ast.Node) bool { var bindElement func(e *ast.BindingElement) var bindPattern func(e *ast.BindingPattern) bindPatternWorker := func(p *ast.BindingPattern) { for _, e := range p.Elements.Nodes { switch e.Kind { case ast.KindOmittedExpression: return case ast.KindBindingElement: bindElement(e.AsBindingElement()) return default: panic("Unhandled binding element kind") } } } bindElementWorker := func(e *ast.BindingElement) { if e.Name() != nil && ast.IsBindingPattern(e.Name()) { bindPattern(e.Name().AsBindingPattern()) return } symbol := b.ch.getSymbolOfDeclaration(e.AsNode()) if symbol != nil { // omitted expressions are now parsed as nameless binding patterns and also have no symbol add(symbol.Name, symbol) } } bindElement = bindElementWorker bindPattern = bindPatternWorker if ast.IsParameterDeclaration(d) && d.Name() != nil && ast.IsBindingPattern(d.Name()) { bindPattern(d.Name().AsBindingPattern()) return true } return false }) { add(param.Name, param) } } }) } if b.ctx.flags&nodebuilder.FlagsGenerateNamesForShadowedTypeParams != 0 && typeParameters != nil && core.Some(typeParameters, func(p *Type) bool { return p != nil }) { cleanupTypeParams = pushFakeScope("typeParams", func(add func(name string, symbol *ast.Symbol)) { if typeParameters == nil { return } for _, typeParam := range typeParameters { if typeParam == nil { continue } typeParamName := b.typeParameterToName(typeParam).Text add(typeParamName, typeParam.symbol) } }) } } return func() { if cleanupParams != nil { cleanupParams() } if cleanupTypeParams != nil { cleanupTypeParams() } cleanupContext() b.ctx.enclosingDeclaration = oldEnclosingDecl b.ctx.mapper = oldMapper } }