Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pkg/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -30518,7 +30518,7 @@ func (c *Checker) getSymbolAtLocation(node *ast.Node, ignoreErrors bool) *ast.Sy
}
return nil
case ast.KindDefaultKeyword, ast.KindFunctionKeyword, ast.KindEqualsGreaterThanToken, ast.KindClassKeyword:
return c.getSymbolOfNode(node)
return c.getSymbolOfNode(node.Parent)
case ast.KindImportType:
if ast.IsLiteralImportTypeNode(node) {
return c.getSymbolAtLocation(node.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal, ignoreErrors)
Expand Down
3 changes: 3 additions & 0 deletions pkg/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,9 @@ func (b *NodeBuilderImpl) getNameOfSymbolAsWritten(symbol *ast.Symbol) string {
if len(name) > 0 {
return name
}
if symbol.Name == ast.InternalSymbolNameMissing {
return "__missing"
}
return symbol.Name
}

Expand Down
21 changes: 18 additions & 3 deletions pkg/compiler/checkerpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import (
)

type CheckerPool interface {
Count() int
GetChecker(ctx context.Context) (*checker.Checker, func())
GetCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func())
GetCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func())
GetAllCheckers(ctx context.Context) ([]*checker.Checker, func())
ForEachCheckerParallel(ctx context.Context, cb func(idx int, c *checker.Checker))
Files(checker *checker.Checker) iter.Seq[*ast.SourceFile]
}

Expand All @@ -42,6 +43,10 @@ func newCheckerPool(checkerCount int, program *Program) *checkerPool {
return pool
}

func (p *checkerPool) Count() int {
return p.checkerCount
}

func (p *checkerPool) GetCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
p.createCheckers()
checker := p.fileAssociations[file]
Expand Down Expand Up @@ -82,9 +87,19 @@ func (p *checkerPool) createCheckers() {
})
}

func (p *checkerPool) GetAllCheckers(ctx context.Context) ([]*checker.Checker, func()) {
// Runs `cb` for each checker in the pool concurrently, locking and unlocking checker mutexes as it goes,
// making it safe to call `ForEachCheckerParallel` from many threads simultaneously.
func (p *checkerPool) ForEachCheckerParallel(ctx context.Context, cb func(idx int, c *checker.Checker)) {
p.createCheckers()
return p.checkers, noop
wg := core.NewWorkGroup(p.program.SingleThreaded())
for idx, checker := range p.checkers {
wg.Queue(func() {
p.locks[idx].Lock()
defer p.locks[idx].Unlock()
cb(idx, checker)
})
}
wg.RunAndWait()
}

func (p *checkerPool) Files(checker *checker.Checker) iter.Seq[*ast.SourceFile] {
Expand Down
98 changes: 43 additions & 55 deletions pkg/compiler/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"slices"
"strings"
"sync"
"sync/atomic"

"github.com/go-json-experiment/json"
"github.com/buke/typescript-go-internal/pkg/ast"
Expand Down Expand Up @@ -351,28 +352,22 @@ func (p *Program) BindSourceFiles() {
}

func (p *Program) CheckSourceFiles(ctx context.Context, files []*ast.SourceFile) {
wg := core.NewWorkGroup(p.SingleThreaded())
checkers, done := p.checkerPool.GetAllCheckers(ctx)
defer done()
for _, checker := range checkers {
wg.Queue(func() {
for file := range p.checkerPool.Files(checker) {
if files == nil || slices.Contains(files, file) {
checker.CheckSourceFile(ctx, file)
}
p.checkerPool.ForEachCheckerParallel(ctx, func(_ int, checker *checker.Checker) {
for file := range p.checkerPool.Files(checker) {
if files == nil || slices.Contains(files, file) {
checker.CheckSourceFile(ctx, file)
}
})
}
wg.RunAndWait()
}
})
}

// Return the type checker associated with the program.
func (p *Program) GetTypeChecker(ctx context.Context) (*checker.Checker, func()) {
return p.checkerPool.GetChecker(ctx)
}

func (p *Program) GetTypeCheckers(ctx context.Context) ([]*checker.Checker, func()) {
return p.checkerPool.GetAllCheckers(ctx)
func (p *Program) ForEachCheckerParallel(ctx context.Context, cb func(idx int, c *checker.Checker)) {
p.checkerPool.ForEachCheckerParallel(ctx, cb)
}

// Return a checker for the given file. We may have multiple checkers in concurrent scenarios and this
Expand Down Expand Up @@ -965,14 +960,12 @@ func (p *Program) GetGlobalDiagnostics(ctx context.Context) []*ast.Diagnostic {
return nil
}

var globalDiagnostics []*ast.Diagnostic
checkers, done := p.checkerPool.GetAllCheckers(ctx)
defer done()
for _, checker := range checkers {
globalDiagnostics = append(globalDiagnostics, checker.GetGlobalDiagnostics()...)
}
globalDiagnostics := make([][]*ast.Diagnostic, p.checkerPool.Count())
p.checkerPool.ForEachCheckerParallel(ctx, func(idx int, checker *checker.Checker) {
globalDiagnostics[idx] = checker.GetGlobalDiagnostics()
})

return SortAndDeduplicateDiagnostics(globalDiagnostics)
return SortAndDeduplicateDiagnostics(slices.Concat(globalDiagnostics...))
}

func (p *Program) GetDeclarationDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
Expand Down Expand Up @@ -1033,22 +1026,23 @@ func (p *Program) getSemanticDiagnosticsForFileNotFilter(ctx context.Context, so
defer done()
}
diags := slices.Clip(sourceFile.BindDiagnostics())
checkers, closeCheckers := p.checkerPool.GetAllCheckers(ctx)
defer closeCheckers()

// Ask for diags from all checkers; checking one file may add diagnostics to other files.
// These are deduplicated later.
for _, checker := range checkers {
checkerDiags := make([][]*ast.Diagnostic, p.checkerPool.Count())
p.checkerPool.ForEachCheckerParallel(ctx, func(idx int, checker *checker.Checker) {
if sourceFile == nil || checker == fileChecker {
diags = append(diags, checker.GetDiagnostics(ctx, sourceFile)...)
checkerDiags[idx] = checker.GetDiagnostics(ctx, sourceFile)
} else {
diags = append(diags, checker.GetDiagnosticsWithoutCheck(sourceFile)...)
checkerDiags[idx] = checker.GetDiagnosticsWithoutCheck(sourceFile)
}
}
})
if ctx.Err() != nil {
return nil
}

diags = append(diags, slices.Concat(checkerDiags...)...)

// !!! This should be rewritten to work like getBindAndCheckDiagnosticsForFileNoCache.

isPlainJS := ast.IsPlainJSFile(sourceFile, compilerOptions.CheckJs)
Expand Down Expand Up @@ -1140,22 +1134,20 @@ func (p *Program) getSuggestionDiagnosticsForFile(ctx context.Context, sourceFil

diags := slices.Clip(sourceFile.BindSuggestionDiagnostics)

checkers, closeCheckers := p.checkerPool.GetAllCheckers(ctx)
defer closeCheckers()

// Ask for diags from all checkers; checking one file may add diagnostics to other files.
// These are deduplicated later.
for _, checker := range checkers {
checkerDiags := make([][]*ast.Diagnostic, p.checkerPool.Count())
p.checkerPool.ForEachCheckerParallel(ctx, func(idx int, checker *checker.Checker) {
if sourceFile == nil || checker == fileChecker {
diags = append(diags, checker.GetSuggestionDiagnostics(ctx, sourceFile)...)
checkerDiags[idx] = checker.GetSuggestionDiagnostics(ctx, sourceFile)
} else {
// !!! is there any case where suggestion diagnostics are produced in other checkers?
}
}
})
if ctx.Err() != nil {
return nil
}

diags = append(diags, slices.Concat(checkerDiags...)...)

return diags
}

Expand Down Expand Up @@ -1251,32 +1243,28 @@ func (p *Program) SymbolCount() int {
for _, file := range p.files {
count += file.SymbolCount
}
checkers, done := p.checkerPool.GetAllCheckers(context.Background())
defer done()
for _, checker := range checkers {
count += int(checker.SymbolCount)
}
return count
var val atomic.Uint32
val.Store(uint32(count))
p.checkerPool.ForEachCheckerParallel(context.Background(), func(idx int, c *checker.Checker) {
val.Add(c.SymbolCount)
})
return int(val.Load())
}

func (p *Program) TypeCount() int {
var count int
checkers, done := p.checkerPool.GetAllCheckers(context.Background())
defer done()
for _, checker := range checkers {
count += int(checker.TypeCount)
}
return count
var val atomic.Uint32
p.checkerPool.ForEachCheckerParallel(context.Background(), func(idx int, c *checker.Checker) {
val.Add(c.TypeCount)
})
return int(val.Load())
}

func (p *Program) InstantiationCount() int {
var count int
checkers, done := p.checkerPool.GetAllCheckers(context.Background())
defer done()
for _, checker := range checkers {
count += int(checker.TotalInstantiationCount)
}
return count
var val atomic.Uint32
p.checkerPool.ForEachCheckerParallel(context.Background(), func(idx int, c *checker.Checker) {
val.Add(c.TotalInstantiationCount)
})
return int(val.Load())
}

func (p *Program) Program() *Program {
Expand Down
3 changes: 0 additions & 3 deletions pkg/fourslash/_scripts/failingTests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,6 @@ TestQuickInfoForTypeParameterInTypeAlias1
TestQuickInfoForTypeParameterInTypeAlias2
TestQuickInfoForTypeofParameter
TestQuickInfoForUMDModuleAlias
TestQuickInfoFromContextualType
TestQuickInfoFunctionKeyword
TestQuickInfoGenerics
TestQuickInfoGetterSetter
Expand All @@ -471,7 +470,6 @@ TestQuickInfoJsDocNonDiscriminatedUnionSharedProp
TestQuickInfoJsdocTypedefMissingType
TestQuickInfoMappedSpreadTypes
TestQuickInfoMappedType
TestQuickInfoMappedTypeMethods
TestQuickInfoMappedTypeRecursiveInference
TestQuickInfoModuleVariables
TestQuickInfoNarrowedTypeOfAliasSymbol
Expand Down Expand Up @@ -526,7 +524,6 @@ TestQuickInfoTypeError
TestQuickInfoTypeOfThisInStatics
TestQuickInfoTypeOnlyNamespaceAndClass
TestQuickInfoUnionOfNamespaces
TestQuickInfoUnion_discriminated
TestQuickInfoWidenedTypes
TestQuickInfo_notInsideComment
TestQuickInforForSucessiveInferencesIsNotAny
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestQuickInfoFromContextualType(t *testing.T) {
t.Parallel()
t.Skip()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: quickInfoExportAssignmentOfGenericInterface_0.ts
interface I {
Expand Down
2 changes: 1 addition & 1 deletion pkg/fourslash/tests/gen/quickInfoMappedTypeMethods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestQuickInfoMappedTypeMethods(t *testing.T) {
t.Parallel()
t.Skip()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `type M = { [K in 'one']: any };
const x: M = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestQuickInfoUnion_discriminated(t *testing.T) {
t.Parallel()
t.Skip()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: quickInfoJsDocTags.ts
type U = A | B;
Expand Down
17 changes: 17 additions & 0 deletions pkg/fourslash/tests/quickInfoFunction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package fourslash_test

import (
"testing"

"github.com/buke/typescript-go-internal/pkg/fourslash"
"github.com/buke/typescript-go-internal/pkg/testutil"
)

func TestQuickInfoFunction(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `/**/function foo() { return "hi"; }`

f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
f.VerifyQuickInfoAt(t, "", "function foo(): string", "")
}
14 changes: 13 additions & 1 deletion pkg/ls/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ func (l *LanguageService) ProvideHover(ctx context.Context, documentURI lsproto.
c, done := program.GetTypeCheckerForFile(ctx, file)
defer done()
rangeNode := getNodeForQuickInfo(node)
quickInfo, documentation := l.getQuickInfoAndDocumentationForSymbol(c, c.GetSymbolAtLocation(node), rangeNode, contentFormat)
symbol := getSymbolAtLocationForQuickInfo(c, node)
quickInfo, documentation := l.getQuickInfoAndDocumentationForSymbol(c, symbol, rangeNode, contentFormat)
if quickInfo == "" {
return lsproto.HoverOrNull{}, nil
}
Expand Down Expand Up @@ -282,6 +283,17 @@ func getNodeForQuickInfo(node *ast.Node) *ast.Node {
return node
}

func getSymbolAtLocationForQuickInfo(c *checker.Checker, node *ast.Node) *ast.Symbol {
if objectElement := getContainingObjectLiteralElement(node); objectElement != nil {
if contextualType := c.GetContextualType(objectElement.Parent, checker.ContextFlagsNone); contextualType != nil {
if properties := c.GetPropertySymbolsFromContextualType(objectElement, contextualType, false /*unionSymbolOk*/); len(properties) == 1 {
return properties[0]
}
}
}
return c.GetSymbolAtLocation(node)
}

func inConstructorContext(node *ast.Node) bool {
if node.Kind == ast.KindConstructorKeyword {
return true
Expand Down
18 changes: 14 additions & 4 deletions pkg/project/checkerpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,32 @@ func (p *CheckerPool) Files(checker *checker.Checker) iter.Seq[*ast.SourceFile]
panic("unimplemented")
}

func (p *CheckerPool) GetAllCheckers(ctx context.Context) ([]*checker.Checker, func()) {
func (p *CheckerPool) Count() int {
return p.maxCheckers
}

func (p *CheckerPool) ForEachCheckerParallel(ctx context.Context, cb func(idx int, c *checker.Checker)) {
p.mu.Lock()
defer p.mu.Unlock()

requestID := core.GetRequestID(ctx)
if requestID == "" {
panic("cannot call GetAllCheckers on a project.checkerPool without a request ID")
panic("cannot call ForEachCheckerParallel on a project.checkerPool without a request ID")
}

// A request can only access one checker
if c, release := p.getRequestCheckerLocked(requestID); c != nil {
return []*checker.Checker{c}, release
defer release()
cb(0, c)
return
}

// TODO: Does this ever work without deadlocking? `p.GetChecker` also tries to lock this mutex.
// Should this just be a panic?
c, release := p.GetChecker(ctx)
return []*checker.Checker{c}, release
defer release()
cb(0, c)
return
}

func (p *CheckerPool) getCheckerLocked(requestID string) (*checker.Checker, int) {
Expand Down
6 changes: 2 additions & 4 deletions pkg/testrunner/compiler_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,9 +529,7 @@ func createHarnessTestFile(unit *testUnit, currentDirectory string) *harnessutil
func (c *compilerTest) verifyUnionOrdering(t *testing.T) {
t.Run("union ordering", func(t *testing.T) {
p := c.result.Program.Program()
checkers, done := p.GetTypeCheckers(t.Context())
defer done()
for _, c := range checkers {
p.ForEachCheckerParallel(t.Context(), func(_ int, c *checker.Checker) {
for union := range c.UnionTypes() {
types := union.Types()

Expand All @@ -549,7 +547,7 @@ func (c *compilerTest) verifyUnionOrdering(t *testing.T) {
assert.Assert(t, slices.Equal(shuffled, types), "compareTypes does not sort union types consistently")
}
}
}
})
})
}

Expand Down
Loading