247 lines
10 KiB
Go
247 lines
10 KiB
Go
package checker
|
|
|
|
import (
|
|
"maps"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/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: <T>(x: T) => T
|
|
// export const y: <T>(x: T) => T
|
|
// we write it out like that, rather than as
|
|
// export const x: <T>(x: T) => T
|
|
// export const y: <T_1>(x: T_1) => T_1
|
|
oldMustCreateTypeParameterSymbolList := context.hasCreatedTypeParameterSymbolList
|
|
oldMustCreateTypeParametersNamesLookups := context.hasCreatedTypeParametersNamesLookups
|
|
oldTypeParameterNames := context.typeParameterNames
|
|
oldTypeParameterNamesByText := context.typeParameterNamesByText
|
|
oldTypeParameterNamesByTextNextNameCount := context.typeParameterNamesByTextNextNameCount
|
|
oldTypeParameterSymbolList := context.typeParameterSymbolList
|
|
context.hasCreatedTypeParameterSymbolList = oldTypeParameterSymbolList != nil
|
|
context.hasCreatedTypeParametersNamesLookups = oldTypeParameterNames != nil
|
|
context.typeParameterNames = maps.Clone(context.typeParameterNames)
|
|
context.typeParameterNamesByText = maps.Clone(context.typeParameterNamesByText)
|
|
context.typeParameterNamesByTextNextNameCount = maps.Clone(context.typeParameterNamesByTextNextNameCount)
|
|
context.typeParameterSymbolList = maps.Clone(context.typeParameterSymbolList)
|
|
return func() {
|
|
context.typeParameterNames = oldTypeParameterNames
|
|
context.typeParameterNamesByText = oldTypeParameterNamesByText
|
|
context.typeParameterNamesByTextNextNameCount = oldTypeParameterNamesByTextNextNameCount
|
|
context.typeParameterSymbolList = oldTypeParameterSymbolList
|
|
context.hasCreatedTypeParameterSymbolList = oldMustCreateTypeParameterSymbolList
|
|
context.hasCreatedTypeParametersNamesLookups = oldMustCreateTypeParametersNamesLookups
|
|
}
|
|
}
|
|
|
|
type localsRecord struct {
|
|
name string
|
|
oldSymbol *ast.Symbol
|
|
}
|
|
|
|
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.AssertIsDefined(b.ctx.enclosingDeclaration)
|
|
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.AssertOptionalNode(existingFakeScope, ast.IsBlock)
|
|
|
|
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 parameters that come from an expansion
|
|
add(param.Name, b.ch.unknownSymbol)
|
|
// Can't reference the original expanded parameter either
|
|
if originalParam != nil {
|
|
add(originalParam.Name, b.ch.unknownSymbol)
|
|
}
|
|
} 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.IsParameter(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
|
|
}
|
|
}
|