internal/lsp: cache the *ast.File and *token.File on the package
This change removes the need for the ast and token fields on the *goFile
object. We switch to using source.ParseGoHandles on the package, which
means that we can easily access both the AST and token via the package,
which is already cached.
Change-Id: I5f78bbe09362f4d95eb15556617bdbd809a7a55d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/185878
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index 9b78773..8cafc03 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -17,6 +17,7 @@
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/telemetry/trace"
+ "golang.org/x/tools/internal/lsp/xlog"
"golang.org/x/tools/internal/span"
)
@@ -121,42 +122,42 @@
mode = source.ParseExported
}
var (
- files []*astFile
- phs []source.ParseGoHandle
- wg sync.WaitGroup
+ files = make([]*ast.File, len(meta.files))
+ errors = make([]error, len(meta.files))
+ wg sync.WaitGroup
)
for _, filename := range meta.files {
uri := span.FileURI(filename)
f, err := imp.view.getFile(ctx, uri)
if err != nil {
+ xlog.Errorf(ctx, "unable to get file for %s: %v", f.URI(), err)
continue
}
- ph := imp.view.session.cache.ParseGoHandle(f.Handle(ctx), mode)
- phs = append(phs, ph)
- files = append(files, &astFile{
- uri: ph.File().Identity().URI,
- isTrimmed: mode == source.ParseExported,
- ph: ph,
- })
+ pkg.files = append(pkg.files, imp.view.session.cache.ParseGoHandle(f.Handle(ctx), mode))
}
- for i, ph := range phs {
+ for i, ph := range pkg.files {
wg.Add(1)
go func(i int, ph source.ParseGoHandle) {
defer wg.Done()
- files[i].file, files[i].err = ph.Parse(ctx)
+ files[i], errors[i] = ph.Parse(ctx)
}(i, ph)
}
wg.Wait()
+ var i int
for _, f := range files {
- pkg.files = append(pkg.files, f)
-
- if f.err != nil {
- if f.err == context.Canceled {
- return nil, f.err
- }
- imp.view.session.cache.appendPkgError(pkg, f.err)
+ if f != nil {
+ files[i] = f
+ i++
+ }
+ }
+ for _, err := range errors {
+ if err == context.Canceled {
+ return nil, err
+ }
+ if err != nil {
+ imp.view.session.cache.appendPkgError(pkg, err)
}
}
@@ -192,7 +193,7 @@
check := types.NewChecker(cfg, imp.fset, pkg.types, pkg.typesInfo)
// Ignore type-checking errors.
- check.Files(pkg.GetSyntax())
+ check.Files(files)
// Add every file in this package to our cache.
if err := imp.cachePackage(ctx, pkg, meta, mode); err != nil {
@@ -203,16 +204,17 @@
}
func (imp *importer) cachePackage(ctx context.Context, pkg *pkg, meta *metadata, mode source.ParseMode) error {
- for _, file := range pkg.files {
- f, err := imp.view.getFile(ctx, file.uri)
+ for _, ph := range pkg.files {
+ uri := ph.File().Identity().URI
+ f, err := imp.view.getFile(ctx, uri)
if err != nil {
- return fmt.Errorf("no such file %s: %v", file.uri, err)
+ return fmt.Errorf("no such file %s: %v", uri, err)
}
gof, ok := f.(*goFile)
if !ok {
- return fmt.Errorf("non Go file %s", file.uri)
+ return fmt.Errorf("non Go file %s", uri)
}
- if err := imp.cachePerFile(gof, file, pkg); err != nil {
+ if err := imp.cachePerFile(gof, ph, pkg); err != nil {
return fmt.Errorf("failed to cache file %s: %v", gof.URI(), err)
}
}
@@ -231,7 +233,7 @@
return nil
}
-func (imp *importer) cachePerFile(gof *goFile, file *astFile, p *pkg) error {
+func (imp *importer) cachePerFile(gof *goFile, ph source.ParseGoHandle, p *pkg) error {
gof.mu.Lock()
defer gof.mu.Unlock()
@@ -241,25 +243,11 @@
}
gof.pkgs[p.id] = p
- // Get the AST for the file.
- gof.ast = file
- if gof.ast == nil {
- return fmt.Errorf("no AST information for %s", file.uri)
+ file, err := ph.Parse(imp.ctx)
+ if file == nil {
+ return fmt.Errorf("no AST for %s: %v", ph.File().Identity().URI, err)
}
- if gof.ast.file == nil {
- return fmt.Errorf("no AST for %s", file.uri)
- }
- // Get the *token.File directly from the AST.
- pos := gof.ast.file.Pos()
- if !pos.IsValid() {
- return fmt.Errorf("AST for %s has an invalid position", file.uri)
- }
- tok := imp.view.session.cache.FileSet().File(pos)
- if tok == nil {
- return fmt.Errorf("no *token.File for %s", file.uri)
- }
- gof.token = tok
- gof.imports = gof.ast.file.Imports
+ gof.imports = file.Imports
return nil
}
diff --git a/internal/lsp/cache/file.go b/internal/lsp/cache/file.go
index e160c25..d237de5 100644
--- a/internal/lsp/cache/file.go
+++ b/internal/lsp/cache/file.go
@@ -34,8 +34,6 @@
handleMu sync.Mutex
handle source.FileHandle
-
- token *token.File
}
func basename(filename string) string {
diff --git a/internal/lsp/cache/gofile.go b/internal/lsp/cache/gofile.go
index 03750ba..e77c1e4 100644
--- a/internal/lsp/cache/gofile.go
+++ b/internal/lsp/cache/gofile.go
@@ -6,6 +6,7 @@
import (
"context"
+ "fmt"
"go/ast"
"go/token"
"sync"
@@ -34,7 +35,6 @@
imports []*ast.ImportSpec
- ast *astFile
pkgs map[packageID]*pkg
meta map[packageID]*metadata
}
@@ -47,69 +47,49 @@
isTrimmed bool
}
-func (f *goFile) GetToken(ctx context.Context) *token.File {
- f.view.mu.Lock()
- defer f.view.mu.Unlock()
-
- if f.isDirty() || f.astIsTrimmed() {
- if _, err := f.view.loadParseTypecheck(ctx, f); err != nil {
- xlog.Errorf(ctx, "unable to check package for %s: %v", f.URI(), err)
- return nil
- }
+func (f *goFile) GetToken(ctx context.Context) (*token.File, error) {
+ file, err := f.GetAST(ctx, source.ParseFull)
+ if file == nil {
+ return nil, err
}
- f.mu.Lock()
- defer f.mu.Unlock()
-
- if unexpectedAST(ctx, f) {
- return nil
- }
- return f.token
+ return f.view.session.cache.fset.File(file.Pos()), nil
}
-func (f *goFile) GetAnyAST(ctx context.Context) *ast.File {
+func (f *goFile) GetAST(ctx context.Context, mode source.ParseMode) (*ast.File, error) {
f.view.mu.Lock()
defer f.view.mu.Unlock()
- if f.isDirty() {
+ if f.isDirty(ctx) || f.wrongParseMode(ctx, mode) {
if _, err := f.view.loadParseTypecheck(ctx, f); err != nil {
- xlog.Errorf(ctx, "unable to check package for %s: %v", f.URI(), err)
- return nil
+ return nil, fmt.Errorf("GetAST: unable to check package for %s: %v", f.URI(), err)
}
}
-
- f.mu.Lock()
- defer f.mu.Unlock()
-
- if f.ast == nil {
- return nil
- }
- return f.ast.file
-}
-
-func (f *goFile) GetAST(ctx context.Context) *ast.File {
- f.view.mu.Lock()
- defer f.view.mu.Unlock()
-
- if f.isDirty() || f.astIsTrimmed() {
- if _, err := f.view.loadParseTypecheck(ctx, f); err != nil {
- xlog.Errorf(ctx, "unable to check package for %s: %v", f.URI(), err)
- return nil
+ fh := f.Handle(ctx)
+ // Check for a cached AST first, in case getting a trimmed version would actually cause a re-parse.
+ for _, m := range []source.ParseMode{
+ source.ParseHeader,
+ source.ParseExported,
+ source.ParseFull,
+ } {
+ if m < mode {
+ continue
+ }
+ if v, ok := f.view.session.cache.store.Cached(parseKey{
+ file: fh.Identity(),
+ mode: m,
+ }).(*parseGoData); ok {
+ return v.ast, v.err
}
}
- f.mu.Lock()
- defer f.mu.Unlock()
-
- if unexpectedAST(ctx, f) {
- return nil
- }
- return f.ast.file
+ ph := f.view.session.cache.ParseGoHandle(fh, mode)
+ return ph.Parse(ctx)
}
func (f *goFile) GetPackages(ctx context.Context) []source.Package {
f.view.mu.Lock()
defer f.view.mu.Unlock()
- if f.isDirty() || f.astIsTrimmed() {
+ if f.isDirty(ctx) || f.wrongParseMode(ctx, source.ParseFull) {
if errs, err := f.view.loadParseTypecheck(ctx, f); err != nil {
xlog.Errorf(ctx, "unable to check package for %s: %v", f.URI(), err)
@@ -124,9 +104,6 @@
f.mu.Lock()
defer f.mu.Unlock()
- if unexpectedAST(ctx, f) {
- return nil
- }
var pkgs []source.Package
for _, pkg := range f.pkgs {
pkgs = append(pkgs, pkg)
@@ -149,23 +126,24 @@
return result
}
-func unexpectedAST(ctx context.Context, f *goFile) bool {
- // If the AST comes back nil, something has gone wrong.
- if f.ast == nil {
- xlog.Errorf(ctx, "expected full AST for %s, returned nil", f.URI())
- return true
- }
- // If the AST comes back trimmed, something has gone wrong.
- if f.ast.isTrimmed {
- xlog.Errorf(ctx, "expected full AST for %s, returned trimmed", f.URI())
- return true
+func (f *goFile) wrongParseMode(ctx context.Context, mode source.ParseMode) bool {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ fh := f.Handle(ctx)
+ for _, pkg := range f.pkgs {
+ for _, ph := range pkg.files {
+ if fh.Identity() == ph.File().Identity() {
+ return ph.Mode() < mode
+ }
+ }
}
return false
}
// isDirty is true if the file needs to be type-checked.
// It assumes that the file's view's mutex is held by the caller.
-func (f *goFile) isDirty() bool {
+func (f *goFile) isDirty(ctx context.Context) bool {
f.mu.Lock()
defer f.mu.Unlock()
@@ -185,14 +163,16 @@
if len(f.missingImports) > 0 {
return true
}
- return f.token == nil || f.ast == nil
-}
-
-func (f *goFile) astIsTrimmed() bool {
- f.mu.Lock()
- defer f.mu.Unlock()
-
- return f.ast != nil && f.ast.isTrimmed
+ fh := f.Handle(ctx)
+ for _, pkg := range f.pkgs {
+ for _, file := range pkg.files {
+ // There is a type-checked package for the current file handle.
+ if file.File().Identity() == fh.Identity() {
+ return false
+ }
+ }
+ }
+ return true
}
func (f *goFile) GetActiveReverseDeps(ctx context.Context) []source.GoFile {
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index d0c2b1d..fd5f532 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -20,7 +20,7 @@
// If the AST for this file is trimmed, and we are explicitly type-checking it,
// don't ignore function bodies.
- if f.astIsTrimmed() {
+ if f.wrongParseMode(ctx, source.ParseFull) {
v.pcache.mu.Lock()
f.invalidateAST(ctx)
v.pcache.mu.Unlock()
diff --git a/internal/lsp/cache/modfile.go b/internal/lsp/cache/modfile.go
index dd27db1..a86a3da 100644
--- a/internal/lsp/cache/modfile.go
+++ b/internal/lsp/cache/modfile.go
@@ -6,6 +6,7 @@
import (
"context"
+ "fmt"
"go/token"
)
@@ -14,7 +15,10 @@
fileBase
}
-func (*modFile) GetToken(context.Context) *token.File { return nil }
-func (*modFile) setContent(content []byte) {}
-func (*modFile) filename() string { return "" }
-func (*modFile) isActive() bool { return false }
+func (*modFile) GetToken(context.Context) (*token.File, error) {
+ return nil, fmt.Errorf("GetToken: not implemented")
+}
+
+func (*modFile) setContent(content []byte) {}
+func (*modFile) filename() string { return "" }
+func (*modFile) isActive() bool { return false }
diff --git a/internal/lsp/cache/pkg.go b/internal/lsp/cache/pkg.go
index 73d5e8a..c261079 100644
--- a/internal/lsp/cache/pkg.go
+++ b/internal/lsp/cache/pkg.go
@@ -22,7 +22,7 @@
id packageID
pkgPath packagePath
- files []*astFile
+ files []source.ParseGoHandle
errors []packages.Error
imports map[packagePath]*pkg
types *types.Package
@@ -149,17 +149,18 @@
func (pkg *pkg) GetFilenames() []string {
filenames := make([]string, 0, len(pkg.files))
- for _, f := range pkg.files {
- filenames = append(filenames, f.uri.Filename())
+ for _, ph := range pkg.files {
+ filenames = append(filenames, ph.File().Identity().URI.Filename())
}
return filenames
}
-func (pkg *pkg) GetSyntax() []*ast.File {
+func (pkg *pkg) GetSyntax(ctx context.Context) []*ast.File {
var syntax []*ast.File
- for _, f := range pkg.files {
- if f.file != nil {
- syntax = append(syntax, f.file)
+ for _, ph := range pkg.files {
+ file, _ := ph.Parse(ctx)
+ if file != nil {
+ syntax = append(syntax, file)
}
}
return syntax
diff --git a/internal/lsp/cache/sumfile.go b/internal/lsp/cache/sumfile.go
index cb7b81c9..4dd7822 100644
--- a/internal/lsp/cache/sumfile.go
+++ b/internal/lsp/cache/sumfile.go
@@ -6,6 +6,7 @@
import (
"context"
+ "fmt"
"go/token"
)
@@ -14,7 +15,10 @@
fileBase
}
-func (*sumFile) GetToken(context.Context) *token.File { return nil }
-func (*sumFile) setContent(content []byte) {}
-func (*sumFile) filename() string { return "" }
-func (*sumFile) isActive() bool { return false }
+func (*sumFile) GetToken(context.Context) (*token.File, error) {
+ return nil, fmt.Errorf("GetToken: not implemented")
+}
+
+func (*sumFile) setContent(content []byte) {}
+func (*sumFile) filename() string { return "" }
+func (*sumFile) isActive() bool { return false }
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index f4922ce..e0547b9 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -245,8 +245,6 @@
// including any position and type information that depends on it.
func (f *goFile) invalidateAST(ctx context.Context) {
f.mu.Lock()
- f.ast = nil
- f.token = nil
pkgs := f.pkgs
f.mu.Unlock()
@@ -287,6 +285,16 @@
continue
}
gof.mu.Lock()
+ if pkg, ok := gof.pkgs[id]; ok {
+ // TODO: Ultimately, we shouldn't need this.
+ // Preemptively delete all of the cached keys if we are invalidating a package.
+ for _, ph := range pkg.files {
+ v.session.cache.store.Delete(parseKey{
+ file: ph.File().Identity(),
+ mode: ph.Mode(),
+ })
+ }
+ }
delete(gof.pkgs, id)
gof.mu.Unlock()
}
diff --git a/internal/lsp/format.go b/internal/lsp/format.go
index cf29a5d..9bbcc19 100644
--- a/internal/lsp/format.go
+++ b/internal/lsp/format.go
@@ -6,7 +6,6 @@
import (
"context"
- "fmt"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
@@ -39,9 +38,9 @@
}
if rng.Start == rng.End {
// If we have a single point, assume we want the whole file.
- tok := f.GetToken(ctx)
- if tok == nil {
- return nil, nil, span.Range{}, fmt.Errorf("no file information for %s", f.URI())
+ tok, err := f.GetToken(ctx)
+ if err != nil {
+ return nil, nil, span.Range{}, err
}
rng.End = tok.Pos(tok.Size())
}
diff --git a/internal/lsp/link.go b/internal/lsp/link.go
index 121ff5ca..6ca0d87 100644
--- a/internal/lsp/link.go
+++ b/internal/lsp/link.go
@@ -26,13 +26,12 @@
if err != nil {
return nil, err
}
- file := f.GetAST(ctx)
+ file, err := f.GetAST(ctx, source.ParseFull)
if file == nil {
- return nil, fmt.Errorf("no AST for %v", uri)
+ return nil, err
}
var links []protocol.DocumentLink
-
ast.Inspect(file, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.ImportSpec:
@@ -78,6 +77,27 @@
return links, nil
}
+func findLinksInString(src string, pos token.Pos, view source.View, mapper *protocol.ColumnMapper) ([]protocol.DocumentLink, error) {
+ var links []protocol.DocumentLink
+ re, err := getURLRegexp()
+ if err != nil {
+ return nil, fmt.Errorf("cannot create regexp for links: %s", err.Error())
+ }
+ for _, urlIndex := range re.FindAllIndex([]byte(src), -1) {
+ start := urlIndex[0]
+ end := urlIndex[1]
+ startPos := token.Pos(int(pos) + start)
+ endPos := token.Pos(int(pos) + end)
+ target := src[start:end]
+ l, err := toProtocolLink(view, mapper, target, startPos, endPos)
+ if err != nil {
+ return nil, err
+ }
+ links = append(links, l)
+ }
+ return links, nil
+}
+
const urlRegexpString = "(http|ftp|https)://([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?"
var (
@@ -108,24 +128,3 @@
}
return l, nil
}
-
-func findLinksInString(src string, pos token.Pos, view source.View, mapper *protocol.ColumnMapper) ([]protocol.DocumentLink, error) {
- var links []protocol.DocumentLink
- re, err := getURLRegexp()
- if err != nil {
- return nil, fmt.Errorf("cannot create regexp for links: %s", err.Error())
- }
- for _, urlIndex := range re.FindAllIndex([]byte(src), -1) {
- start := urlIndex[0]
- end := urlIndex[1]
- startPos := token.Pos(int(pos) + start)
- endPos := token.Pos(int(pos) + end)
- target := src[start:end]
- l, err := toProtocolLink(view, mapper, target, startPos, endPos)
- if err != nil {
- return nil, err
- }
- links = append(links, l)
- }
- return links, nil
-}
diff --git a/internal/lsp/source/analysis.go b/internal/lsp/source/analysis.go
index 915bd25..27dfb8e 100644
--- a/internal/lsp/source/analysis.go
+++ b/internal/lsp/source/analysis.go
@@ -148,7 +148,7 @@
pass := &analysis.Pass{
Analyzer: act.Analyzer,
Fset: fset,
- Files: act.Pkg.GetSyntax(),
+ Files: act.Pkg.GetSyntax(ctx),
Pkg: act.Pkg.GetTypes(),
TypesInfo: act.Pkg.GetTypesInfo(),
TypesSizes: act.Pkg.GetTypesSizes(),
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index d2bad6b..f7a7b99 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -280,11 +280,11 @@
func Completion(ctx context.Context, view View, f GoFile, pos token.Pos, opts CompletionOptions) ([]CompletionItem, *Selection, error) {
ctx, done := trace.StartSpan(ctx, "source.Completion")
defer done()
- file := f.GetAST(ctx)
- if file == nil {
- return nil, nil, fmt.Errorf("no AST for %s", f.URI())
- }
+ file, err := f.GetAST(ctx, ParseFull)
+ if file == nil {
+ return nil, nil, err
+ }
pkg := f.GetPackage(ctx)
if pkg == nil || pkg.IsIllTyped() {
return nil, nil, fmt.Errorf("package for %s is ill typed", f.URI())
@@ -509,6 +509,7 @@
if scope == types.Universe {
score *= 0.1
}
+
// If we haven't already added a candidate for an object with this name.
if _, ok := seen[obj.Name()]; !ok {
seen[obj.Name()] = struct{}{}
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
index de1f4f4..3adc69f 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion_format.go
@@ -98,31 +98,38 @@
if c.opts.WantDocumentaton {
declRange, err := objToRange(c.ctx, c.view.Session().Cache().FileSet(), obj)
if err != nil {
- return CompletionItem{}, err
+ xlog.Errorf(c.ctx, "failed to get declaration range for object %s: %v", obj.Name(), err)
+ goto Return
}
pos := declRange.FileSet.Position(declRange.Start)
if !pos.IsValid() {
- return CompletionItem{}, fmt.Errorf("invalid declaration position for %v", item.Label)
+ xlog.Errorf(c.ctx, "invalid declaration position for %v: %v", item.Label, err)
+ goto Return
}
uri := span.FileURI(pos.Filename)
f, err := c.view.GetFile(c.ctx, uri)
if err != nil {
- return CompletionItem{}, err
+ xlog.Errorf(c.ctx, "unable to get file for %s: %v", uri, err)
+ goto Return
}
gof, ok := f.(GoFile)
if !ok {
- return CompletionItem{}, fmt.Errorf("declaration for %s not in a Go file: %s", item.Label, uri)
+ xlog.Errorf(c.ctx, "declaration for %s not in a Go file: %s", item.Label, uri)
+ goto Return
}
ident, err := Identifier(c.ctx, c.view, gof, declRange.Start)
if err != nil {
- return CompletionItem{}, err
+ xlog.Errorf(c.ctx, "no identifier for %s: %v", obj.Name(), err)
+ goto Return
}
documentation, err := ident.Documentation(c.ctx, SynopsisDocumentation)
if err != nil {
- return CompletionItem{}, err
+ xlog.Errorf(c.ctx, "no documentation for %s: %v", obj.Name(), err)
+ goto Return
}
item.Documentation = documentation
}
+Return:
return item, nil
}
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 268b99f..03751df 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -239,9 +239,9 @@
xlog.Errorf(ctx, "%s is not a Go file", spn.URI())
return spn
}
- tok := diagFile.GetToken(ctx)
- if tok == nil {
- xlog.Errorf(ctx, "could not find token.File for diagnostic: %v", spn.URI())
+ tok, err := diagFile.GetToken(ctx)
+ if err != nil {
+ xlog.Errorf(ctx, "could not find token.File for %s: %v", spn.URI(), err)
return spn
}
data, _, err := diagFile.Handle(ctx).Read(ctx)
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index 72f7ddb..e8884e6 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -25,9 +25,10 @@
func Format(ctx context.Context, f GoFile, rng span.Range) ([]TextEdit, error) {
ctx, done := trace.StartSpan(ctx, "source.Format")
defer done()
- file := f.GetAST(ctx)
+
+ file, err := f.GetAST(ctx, ParseFull)
if file == nil {
- return nil, fmt.Errorf("no AST for %s", f.URI())
+ return nil, err
}
pkg := f.GetPackage(ctx)
if hasListErrors(pkg.GetErrors()) || hasParseErrors(pkg.GetErrors()) {
diff --git a/internal/lsp/source/highlight.go b/internal/lsp/source/highlight.go
index 7f4b71a..a5a3358 100644
--- a/internal/lsp/source/highlight.go
+++ b/internal/lsp/source/highlight.go
@@ -18,9 +18,10 @@
func Highlight(ctx context.Context, f GoFile, pos token.Pos) ([]span.Span, error) {
ctx, done := trace.StartSpan(ctx, "source.Highlight")
defer done()
- file := f.GetAST(ctx)
+
+ file, err := f.GetAST(ctx, ParseFull)
if file == nil {
- return nil, fmt.Errorf("no AST for %s", f.URI())
+ return nil, err
}
fset := f.FileSet()
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index bc9c565..65724e6 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -65,16 +65,17 @@
func identifier(ctx context.Context, view View, f GoFile, pos token.Pos) (*IdentifierInfo, error) {
ctx, done := trace.StartSpan(ctx, "source.identifier")
defer done()
- file := f.GetAST(ctx)
+
+ file, err := f.GetAST(ctx, ParseFull)
if file == nil {
- return nil, fmt.Errorf("no AST for %s", f.URI())
+ return nil, err
}
pkg := f.GetPackage(ctx)
if pkg == nil || pkg.IsIllTyped() {
return nil, fmt.Errorf("pkg for %s is ill-typed", f.URI())
}
// Handle import specs separately, as there is no formal position for a package declaration.
- if result, err := importSpec(f, file, pkg, pos); result != nil || err != nil {
+ if result, err := importSpec(ctx, f, file, pkg, pos); result != nil || err != nil {
return result, err
}
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
@@ -121,8 +122,6 @@
}
}
- var err error
-
// Handle builtins separately.
if result.decl.obj.Parent() == types.Universe {
decl, ok := lookupBuiltinDecl(f.View(), result.Name).(ast.Node)
@@ -235,14 +234,13 @@
}
// If the object is exported from a different package,
// we don't need its full AST to find the definition.
- var declAST *ast.File
+ mode := ParseFull
if obj.Exported() && obj.Pkg() != originPkg {
- declAST = declFile.GetAnyAST(ctx)
- } else {
- declAST = declFile.GetAST(ctx)
+ mode = ParseExported
}
+ declAST, err := declFile.GetAST(ctx, mode)
if declAST == nil {
- return nil, fmt.Errorf("no AST for %s", f.URI())
+ return nil, err
}
path, _ := astutil.PathEnclosingInterval(declAST, rng.Start, rng.End)
if path == nil {
@@ -267,7 +265,7 @@
}
// importSpec handles positions inside of an *ast.ImportSpec.
-func importSpec(f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
+func importSpec(ctx context.Context, f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
var imp *ast.ImportSpec
for _, spec := range fAST.Imports {
if spec.Pos() <= pos && pos < spec.End() {
@@ -292,12 +290,12 @@
if importedPkg == nil {
return nil, fmt.Errorf("no import for %q", importPath)
}
- if importedPkg.GetSyntax() == nil {
+ if importedPkg.GetSyntax(ctx) == nil {
return nil, fmt.Errorf("no syntax for for %q", importPath)
}
// Heuristic: Jump to the longest (most "interesting") file of the package.
var dest *ast.File
- for _, f := range importedPkg.GetSyntax() {
+ for _, f := range importedPkg.GetSyntax(ctx) {
if dest == nil || f.End()-f.Pos() > dest.End()-dest.Pos() {
dest = f
}
diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go
index d8eb3a3..3964fa4 100644
--- a/internal/lsp/source/rename.go
+++ b/internal/lsp/source/rename.go
@@ -39,6 +39,7 @@
func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.URI][]TextEdit, error) {
ctx, done := trace.StartSpan(ctx, "source.Rename")
defer done()
+
if i.Name == newName {
return nil, fmt.Errorf("old and new names are the same: %s", newName)
}
diff --git a/internal/lsp/source/rename_check.go b/internal/lsp/source/rename_check.go
index 8d2ac92..9d95b63 100644
--- a/internal/lsp/source/rename_check.go
+++ b/internal/lsp/source/rename_check.go
@@ -113,7 +113,7 @@
}
// Check for conflicts between package block and all file blocks.
- for _, f := range pkg.GetSyntax() {
+ for _, f := range pkg.GetSyntax(r.ctx) {
fileScope := pkg.GetTypesInfo().Scopes[f]
b, prev := fileScope.LookupParent(r.to, token.NoPos)
if b == fileScope {
@@ -328,7 +328,7 @@
return true
}
- for _, f := range pkg.GetSyntax() {
+ for _, f := range pkg.GetSyntax(ctx) {
ast.Inspect(f, visit)
if len(stack) != 0 {
panic(stack)
@@ -802,7 +802,7 @@
r.from, r.to, pkg.PkgPath())
return nil
}
- f.Find(pkg.GetTypesInfo(), pkg.GetSyntax())
+ f.Find(pkg.GetTypesInfo(), pkg.GetSyntax(r.ctx))
}
r.satisfyConstraints = f.Result
}
@@ -835,7 +835,7 @@
//
func pathEnclosingInterval(ctx context.Context, fset *token.FileSet, pkg Package, start, end token.Pos) (resPkg Package, path []ast.Node, exact bool) {
var pkgs = []Package{pkg}
- for _, f := range pkg.GetSyntax() {
+ for _, f := range pkg.GetSyntax(ctx) {
for _, imp := range f.Imports {
if imp == nil {
continue
@@ -848,7 +848,7 @@
}
}
for _, p := range pkgs {
- for _, f := range p.GetSyntax() {
+ for _, f := range p.GetSyntax(ctx) {
if f.Pos() == token.NoPos {
// This can happen if the parser saw
// too many errors and bailed out.
diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
index e34d685..69786a4 100644
--- a/internal/lsp/source/signature_help.go
+++ b/internal/lsp/source/signature_help.go
@@ -28,9 +28,10 @@
func SignatureHelp(ctx context.Context, f GoFile, pos token.Pos) (*SignatureInformation, error) {
ctx, done := trace.StartSpan(ctx, "source.SignatureHelp")
defer done()
- file := f.GetAST(ctx)
+
+ file, err := f.GetAST(ctx, ParseFull)
if file == nil {
- return nil, fmt.Errorf("no AST for %s", f.URI())
+ return nil, err
}
pkg := f.GetPackage(ctx)
if pkg == nil || pkg.IsIllTyped() {
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 1adfcd8..081009c 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -145,9 +145,9 @@
if err != nil {
t.Fatalf("failed for %v: %v", src, err)
}
- tok := f.(source.GoFile).GetToken(ctx)
- if tok == nil {
- t.Fatalf("failed to get token for %v", src)
+ tok, err := f.(source.GoFile).GetToken(ctx)
+ if err != nil {
+ t.Fatalf("failed to get token for %s: %v", src.URI(), err)
}
pos := tok.Pos(src.Start().Offset())
list, surrounding, err := source.Completion(ctx, r.view, f.(source.GoFile), pos, source.CompletionOptions{
@@ -183,7 +183,10 @@
if err != nil {
t.Fatalf("failed for %v: %v", src, err)
}
- tok := f.GetToken(ctx)
+ tok, err := f.(source.GoFile).GetToken(ctx)
+ if err != nil {
+ t.Fatalf("failed to get token for %s: %v", src.URI(), err)
+ }
pos := tok.Pos(src.Start().Offset())
list, _, err := source.Completion(ctx, r.view, f.(source.GoFile), pos, source.CompletionOptions{
DeepComplete: strings.Contains(string(src.URI()), "deepcomplete"),
@@ -305,7 +308,11 @@
if err != nil {
t.Fatalf("failed for %v: %v", spn, err)
}
- rng, err := spn.Range(span.NewTokenConverter(f.FileSet(), f.GetToken(ctx)))
+ tok, err := f.(source.GoFile).GetToken(ctx)
+ if err != nil {
+ t.Fatalf("failed to get token for %s: %v", spn.URI(), err)
+ }
+ rng, err := spn.Range(span.NewTokenConverter(f.FileSet(), tok))
if err != nil {
t.Fatalf("failed for %v: %v", spn, err)
}
@@ -343,7 +350,11 @@
if err != nil {
t.Fatalf("failed for %v: %v", spn, err)
}
- rng, err := spn.Range(span.NewTokenConverter(f.FileSet(), f.GetToken(ctx)))
+ tok, err := f.(source.GoFile).GetToken(ctx)
+ if err != nil {
+ t.Fatalf("failed to get token for %s: %v", spn.URI(), err)
+ }
+ rng, err := spn.Range(span.NewTokenConverter(f.FileSet(), tok))
if err != nil {
t.Fatalf("failed for %v: %v", spn, err)
}
@@ -374,7 +385,10 @@
if err != nil {
t.Fatalf("failed for %v: %v", d.Src, err)
}
- tok := f.GetToken(ctx)
+ tok, err := f.(source.GoFile).GetToken(ctx)
+ if err != nil {
+ t.Fatalf("failed to get token for %s: %v", d.Src.URI(), err)
+ }
pos := tok.Pos(d.Src.Start().Offset())
ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), pos)
if err != nil {
@@ -417,7 +431,10 @@
if err != nil {
t.Fatalf("failed for %v: %v", src, err)
}
- tok := f.GetToken(ctx)
+ tok, err := f.(source.GoFile).GetToken(ctx)
+ if err != nil {
+ t.Fatalf("failed to get token for %s: %v", src.URI(), err)
+ }
pos := tok.Pos(src.Start().Offset())
highlights, err := source.Highlight(ctx, f.(source.GoFile), pos)
if err != nil {
@@ -441,8 +458,10 @@
if err != nil {
t.Fatalf("failed for %v: %v", src, err)
}
-
- tok := f.GetToken(ctx)
+ tok, err := f.(source.GoFile).GetToken(ctx)
+ if err != nil {
+ t.Fatalf("failed to get token for %s: %v", src.URI(), err)
+ }
pos := tok.Pos(src.Start().Offset())
ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), pos)
if err != nil {
@@ -489,7 +508,10 @@
if err != nil {
t.Fatalf("failed for %v: %v", spn, err)
}
- tok := f.GetToken(ctx)
+ tok, err := f.(source.GoFile).GetToken(ctx)
+ if err != nil {
+ t.Fatalf("failed to get token for %s: %v", spn.URI(), err)
+ }
pos := tok.Pos(spn.Start().Offset())
ident, err := source.Identifier(r.ctx, r.view, f.(source.GoFile), pos)
@@ -632,7 +654,10 @@
if err != nil {
t.Fatalf("failed for %v: %v", spn, err)
}
- tok := f.GetToken(ctx)
+ tok, err := f.(source.GoFile).GetToken(ctx)
+ if err != nil {
+ t.Fatalf("failed to get token for %s: %v", spn.URI(), err)
+ }
pos := tok.Pos(spn.Start().Offset())
gotSignature, err := source.SignatureHelp(ctx, f.(source.GoFile), pos)
if err != nil {
diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go
index ab70ee1..84358f1 100644
--- a/internal/lsp/source/symbols.go
+++ b/internal/lsp/source/symbols.go
@@ -44,10 +44,11 @@
func DocumentSymbols(ctx context.Context, f GoFile) ([]Symbol, error) {
ctx, done := trace.StartSpan(ctx, "source.DocumentSymbols")
defer done()
+
fset := f.FileSet()
- file := f.GetAST(ctx)
+ file, err := f.GetAST(ctx, ParseFull)
if file == nil {
- return nil, fmt.Errorf("no AST for %s", f.URI())
+ return nil, err
}
pkg := f.GetPackage(ctx)
if pkg == nil || pkg.IsIllTyped() {
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 4f7e782..d0db264 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -216,19 +216,15 @@
View() View
Handle(ctx context.Context) FileHandle
FileSet() *token.FileSet
- GetToken(ctx context.Context) *token.File
+ GetToken(ctx context.Context) (*token.File, error)
}
// GoFile represents a Go source file that has been type-checked.
type GoFile interface {
File
- // GetAnyAST returns an AST that may or may not contain function bodies.
- // It should be used in scenarios where function bodies are not necessary.
- GetAnyAST(ctx context.Context) *ast.File
-
// GetAST returns the full AST for the file.
- GetAST(ctx context.Context) *ast.File
+ GetAST(ctx context.Context, mode ParseMode) (*ast.File, error)
// GetPackage returns the package that this file belongs to.
GetPackage(ctx context.Context) Package
@@ -255,7 +251,7 @@
ID() string
PkgPath() string
GetFilenames() []string
- GetSyntax() []*ast.File
+ GetSyntax(context.Context) []*ast.File
GetErrors() []packages.Error
GetTypes() *types.Package
GetTypesInfo() *types.Info
diff --git a/internal/lsp/util.go b/internal/lsp/util.go
index 3984ed8..d3b375b 100644
--- a/internal/lsp/util.go
+++ b/internal/lsp/util.go
@@ -22,7 +22,11 @@
if err != nil {
return nil, nil, err
}
- m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), f.FileSet(), f.GetToken(ctx), data)
+ tok, err := f.GetToken(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+ m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), f.FileSet(), tok, data)
return f, m, nil
}