Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions tsc/internal/checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/microsoft/TypeScript/tsc/internal/checker"
"github.com/microsoft/TypeScript/tsc/internal/compiler"
"github.com/microsoft/TypeScript/tsc/internal/core"
"github.com/microsoft/TypeScript/tsc/internal/nodebuilder"
"github.com/microsoft/TypeScript/tsc/internal/printer"
"github.com/microsoft/TypeScript/tsc/internal/repo"
"github.com/microsoft/TypeScript/tsc/internal/testutil/baseline"
"github.com/microsoft/TypeScript/tsc/internal/tsoptions"
Expand Down Expand Up @@ -189,6 +191,97 @@ func TestTypeToStringRecursivePrototype(t *testing.T) {
}
}

type typeAliasRecordingTracker struct {
*checker.SymbolTrackerImpl
scope *ast.Node
declarations []*ast.Node
cyclicErrors int
scopeQueries int
}

func (s *typeAliasRecordingTracker) TypeAliasScope() *ast.Node {
s.scopeQueries++
return s.scope
}

func (s *typeAliasRecordingTracker) AddTypeAliasDeclaration(declaration *ast.Node) {
s.declarations = append(s.declarations, declaration)
}

func (s *typeAliasRecordingTracker) ReportCyclicStructureError() {
s.cyclicErrors++
}

func TestNodeBuilderTypeAliasSynthesisFlag(t *testing.T) {
t.Parallel()
fs := bundled.WrapFS(vfstest.FromMap(map[string]string{
"/foo.ts": `export function make() {
return { next() { return this; } };
}
make();`,
"/tsconfig.json": `{"compilerOptions":{"strict":true},"files":["foo.ts"]}`,
}, tspath.CaseInsensitive))
host := compiler.NewCompilerHost(fs, bundled.LibPath(), nil, nil, nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, fs, nil)
assert.Equal(t, len(errors), 0)
p := compiler.NewProgram(compiler.ProgramOptions{Config: parsed, Host: host})
p.BindSourceFiles()
c, done := p.GetTypeChecker(t.Context())
defer done()
file := p.GetSourceFile("/foo.ts")
typ := c.GetTypeAtLocation(file.Statements.Nodes[1].Expression())
builder := checker.NewNodeBuilder(c, printer.NewEmitContext())
tracker := &typeAliasRecordingTracker{
SymbolTrackerImpl: checker.NewSymbolTrackerImpl(&checker.NodeBuilderContext{}, nil),
scope: file.AsNode(),
}
node := builder.TypeToTypeNode(typ, file.Statements.Nodes[0], nodebuilder.FlagsNoTruncation, nodebuilder.InternalFlagsNone, tracker)
assert.Assert(t, node == nil)
assert.Equal(t, tracker.cyclicErrors, 1)
assert.Equal(t, len(tracker.declarations), 0)
assert.Equal(t, tracker.scopeQueries, 0)

for _, synthesize := range []bool{false, true, false, true} {
tracker.declarations = nil
tracker.cyclicErrors, tracker.scopeQueries = 0, 0
internalFlags := nodebuilder.InternalFlagsNone
if synthesize {
internalFlags |= nodebuilder.InternalFlagsSynthesizeTypeAliases
}
node := builder.TypeToTypeNode(typ, file.Statements.Nodes[0], nodebuilder.FlagsNoTruncation|nodebuilder.FlagsAllowAnonymousIdentifier, internalFlags, tracker)
assert.Assert(t, node != nil)
assert.Equal(t, tracker.cyclicErrors, 0)
if synthesize {
assert.Equal(t, len(tracker.declarations), 1)
assert.Equal(t, tracker.declarations[0].Name().Text(), "make_recursive")
assert.Assert(t, tracker.scopeQueries > 0)
assert.Equal(t, node.Kind, ast.KindTypeReference)
assert.Equal(t, node.AsTypeReferenceNode().TypeName, tracker.declarations[0].Name())
} else {
assert.Equal(t, len(tracker.declarations), 0)
assert.Equal(t, tracker.scopeQueries, 0)
assert.Equal(t, node.Kind, ast.KindTypeLiteral)
}
}

for _, missingTracker := range []bool{false, true} {
func() {
tracker.scope = nil
var sink nodebuilder.SymbolTracker = tracker
if missingTracker {
sink = nil
}
defer func() {
message, ok := recover().(string)
assert.Assert(t, ok, "Expected a missing-scope assertion")
assert.Assert(t, strings.Contains(message, "Type alias synthesis requires a destination scope"))
}()
checker.NewNodeBuilder(c, printer.NewEmitContext()).TypeToTypeNode(typ, file.Statements.Nodes[0], nodebuilder.FlagsNoTruncation, nodebuilder.InternalFlagsSynthesizeTypeAliases, sink)
t.Fatal("Expected type alias synthesis without a destination scope to panic")
}()
}
}

func BenchmarkNewChecker(b *testing.B) {
fs := bundled.WrapFS(osvfs.FS())
rootPath := tspath.RootedDirectoryPathFromAbsolute(filepath.Join(repo.TestDataPath(), "fixtures/compiler"))
Expand Down
54 changes: 38 additions & 16 deletions tsc/internal/checker/nodebuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package checker
import (
"github.com/microsoft/TypeScript/tsc/internal/ast"
"github.com/microsoft/TypeScript/tsc/internal/core"
"github.com/microsoft/TypeScript/tsc/internal/debug"
"github.com/microsoft/TypeScript/tsc/internal/nodebuilder"
"github.com/microsoft/TypeScript/tsc/internal/printer"
)
Expand Down Expand Up @@ -39,23 +40,34 @@ func (b *NodeBuilder) enterContext(enclosingDeclaration *ast.Node, flags nodebui
}
b.ctxStack = append(b.ctxStack, b.impl.ctx)
b.impl.ctx = &NodeBuilderContext{
host: b.host,
tracker: tracker,
flags: flags,
internalFlags: internalFlags,
maxExpansionDepth: verbosityLevel,
maxTruncationLength: maxTruncationLength,
enclosingDeclaration: enclosingDeclaration,
enclosingFile: ast.GetSourceFileOfNode(enclosingDeclaration),
inferTypeParameters: make([]*Type, 0),
symbolDepth: make(map[CompositeSymbolIdentity]int),
trackedSymbols: make([]*TrackedSymbolArgs, 0),
reverseMappedStack: make([]*ast.Symbol, 0),
enclosingSymbolTypes: make(map[ast.SymbolId]*Type),
remappedSymbolReferences: make(map[ast.SymbolId]*ast.Symbol),
host: b.host,
tracker: tracker,
flags: flags,
internalFlags: internalFlags,
maxExpansionDepth: verbosityLevel,
maxTruncationLength: maxTruncationLength,
enclosingDeclaration: enclosingDeclaration,
typeAliasNamingDeclaration: enclosingDeclaration,
enclosingFile: ast.GetSourceFileOfNode(enclosingDeclaration),
inferTypeParameters: make([]*Type, 0),
symbolDepth: make(map[CompositeSymbolIdentity]int),
trackedSymbols: make([]*TrackedSymbolArgs, 0),
reverseMappedStack: make([]*ast.Symbol, 0),
enclosingSymbolTypes: make(map[ast.SymbolId]*Type),
remappedSymbolReferences: make(map[ast.SymbolId]*ast.Symbol),
}
for node := enclosingDeclaration; node != nil; node = node.Parent {
if ast.IsClassLike(node) || ast.IsInterfaceDeclaration(node) {
if symbol := b.impl.ch.getSymbolOfDeclaration(node); symbol != nil {
parameters := b.impl.ch.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)
b.impl.ctx.typeParameters = append(parameters, b.impl.ctx.typeParameters...)
}
}
}
b.impl.ctx.tracker = NewSymbolTrackerImpl(b.impl.ctx, tracker)
if internalFlags&nodebuilder.InternalFlagsSynthesizeTypeAliases != 0 {
debug.Assert(b.impl.ctx.tracker.TypeAliasScope() != nil, "Type alias synthesis requires a destination scope")
}
tracker = NewSymbolTrackerImpl(b.impl.ctx, tracker)
b.impl.ctx.tracker = tracker
}

// propagateVerbosityOut copies expansion signals from the context to the VerbosityContext output.
Expand Down Expand Up @@ -102,6 +114,9 @@ func (b *NodeBuilder) exitContextSlice(result []*ast.Node) []*ast.Node {
}

func (b *NodeBuilder) exitContextCheck() {
if !b.impl.ctx.encounteredError {
b.impl.emitSerializationTypeAliases()
}
if b.impl.ctx.truncating && b.impl.ctx.flags&nodebuilder.FlagsNoTruncation != 0 {
b.impl.ctx.tracker.ReportTruncationError()
}
Expand All @@ -116,6 +131,7 @@ func (b *NodeBuilder) IndexInfoToIndexSignatureDeclaration(info *IndexInfo, encl
// SerializeReturnTypeForSignature implements NodeBuilderInterface.
func (b *NodeBuilder) SerializeReturnTypeForSignature(signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
b.impl.ctx.typeAliasNamingDeclaration = signatureDeclaration
signature := b.impl.ch.getSignatureFromDeclaration(signatureDeclaration)
_, cleanup := b.impl.enterSignatureScope(signature)
result := b.impl.serializeReturnTypeForSignature(signature, true)
Expand All @@ -133,6 +149,7 @@ func (b *NodeBuilder) SerializeTypeParametersForSignature(signatureDeclaration *
// SerializeTypeForDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) SerializeTypeForDeclaration(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
b.impl.ctx.typeAliasNamingDeclaration = declaration
return b.exitContext(b.impl.serializeTypeForDeclaration(declaration, nil, symbol, true))
}

Expand Down Expand Up @@ -236,24 +253,28 @@ func (b *NodeBuilder) SymbolToExpression(symbol *ast.Symbol, meaning ast.SymbolF
// SymbolToNode implements NodeBuilderInterface.
func (b *NodeBuilder) SymbolToNode(symbol *ast.Symbol, meaning ast.SymbolFlags, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
b.impl.ctx.typeAliasNamingDeclaration = core.Coalesce(core.FirstOrNil(symbol.Declarations()), enclosingDeclaration)
return b.exitContext(b.impl.symbolToNode(symbol, meaning))
}

// SymbolToParameterDeclaration implements NodeBuilderInterface.
func (b NodeBuilder) SymbolToParameterDeclaration(symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
b.impl.ctx.typeAliasNamingDeclaration = core.Coalesce(getEffectiveParameterDeclaration(symbol), enclosingDeclaration)
return b.exitContext(b.impl.symbolToParameterDeclaration(symbol, false))
}

// SymbolToTypeParameterDeclarations implements NodeBuilderInterface.
func (b *NodeBuilder) SymbolToTypeParameterDeclarations(symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
b.impl.ctx.typeAliasNamingDeclaration = core.Coalesce(core.FirstOrNil(symbol.Declarations()), enclosingDeclaration)
return b.exitContextSlice(b.impl.symbolToTypeParameterDeclarations(symbol))
}

// TypeParameterToDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) TypeParameterToDeclaration(parameter *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
b.impl.ctx.typeAliasNamingDeclaration = core.Coalesce(core.FirstOrNil(parameter.symbol.Declarations()), enclosingDeclaration)
return b.exitContext(b.impl.typeParameterToDeclaration(parameter))
}

Expand All @@ -271,6 +292,7 @@ func (b *NodeBuilder) TypeToTypeNode(typ *Type, enclosingDeclaration *ast.Node,

func (b *NodeBuilder) TryJSTypeNodeToTypeNode(node *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
b.impl.ctx.typeAliasNamingDeclaration = node
return b.exitContext(b.impl.tryJSTypeNodeToTypeNode(node))
}

Expand Down
Loading
Loading