diff --git a/internal/lsp/cache/gofile.go b/internal/lsp/cache/gofile.go index 62f6b472f0..8a21de56e6 100644 --- a/internal/lsp/cache/gofile.go +++ b/internal/lsp/cache/gofile.go @@ -7,7 +7,6 @@ package cache import ( "context" "go/ast" - "go/token" "sync" "golang.org/x/tools/internal/lsp/source" @@ -49,36 +48,6 @@ func (f *goFile) metadata() []*metadata { return result } -func (f *goFile) GetToken(ctx context.Context) (*token.File, error) { - file, err := f.GetAST(ctx, source.ParseFull) - if file == nil { - return nil, err - } - tok := f.view.session.cache.fset.File(file.Pos()) - if tok == nil { - return nil, errors.Errorf("no token.File for %s", f.URI()) - } - return tok, nil -} - -func (f *goFile) GetAST(ctx context.Context, mode source.ParseMode) (*ast.File, error) { - ctx = telemetry.File.With(ctx, f.URI()) - fh := f.Handle(ctx) - - if f.isDirty(ctx, fh) || f.wrongParseMode(ctx, fh, mode) { - if err := f.view.loadParseTypecheck(ctx, f, fh); err != nil { - return nil, err - } - } - // Check for a cached AST first, in case getting a trimmed version would actually cause a re-parse. - cached, err := f.view.session.cache.cachedAST(fh, mode) - if cached != nil || err != nil { - return cached, err - } - ph := f.view.session.cache.ParseGoHandle(fh, mode) - return ph.Parse(ctx) -} - func (cache *cache) cachedAST(fh source.FileHandle, mode source.ParseMode) (*ast.File, error) { for _, m := range []source.ParseMode{ source.ParseHeader, @@ -175,6 +144,24 @@ func (f *goFile) GetCachedPackage(ctx context.Context) (source.Package, error) { return cph.Cached(ctx) } +func (f *goFile) GetCachedPackages(ctx context.Context) ([]source.Package, error) { + f.mu.Lock() + defer f.mu.Unlock() + + var pkgs []source.Package + for _, cph := range f.pkgs { + pkg, err := cph.Cached(ctx) + if err != nil { + return nil, err + } + pkgs = append(pkgs, pkg) + } + if len(pkgs) == 0 { + return nil, errors.Errorf("no CheckPackageHandles for %s", f.URI()) + } + return pkgs, nil +} + // bestCheckPackageHandle picks the "narrowest" package for a given file. // // By "narrowest" package, we mean the package with the fewest number of files diff --git a/internal/lsp/link.go b/internal/lsp/link.go index 954aea2db4..5c42f14ca0 100644 --- a/internal/lsp/link.go +++ b/internal/lsp/link.go @@ -27,16 +27,17 @@ func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLink if err != nil { return nil, err } - data, _, err := f.Handle(ctx).Read(ctx) + fh := f.Handle(ctx) + data, _, err := fh.Read(ctx) if err != nil { return nil, err } - file, err := f.GetAST(ctx, source.ParseFull) + file, err := view.Session().Cache().ParseGoHandle(fh, source.ParseFull).Parse(ctx) if file == nil { return nil, err } tok := view.Session().Cache().FileSet().File(file.Pos()) - m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), f.FileSet(), tok, data) + m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), view.Session().Cache().FileSet(), tok, data) var links []protocol.DocumentLink ast.Inspect(file, func(node ast.Node) bool { diff --git a/internal/lsp/references.go b/internal/lsp/references.go index ea238d4a34..25977774ca 100644 --- a/internal/lsp/references.go +++ b/internal/lsp/references.go @@ -26,7 +26,7 @@ func (s *Server) references(ctx context.Context, params *protocol.ReferenceParam if err != nil { return nil, err } - references, err := ident.References(ctx, view) + references, err := ident.References(ctx) if err != nil { log.Error(ctx, "no references", err, tag.Of("Identifier", ident.Name)) } diff --git a/internal/lsp/source/folding_range.go b/internal/lsp/source/folding_range.go index 4ffd612c87..8a367098d0 100644 --- a/internal/lsp/source/folding_range.go +++ b/internal/lsp/source/folding_range.go @@ -19,15 +19,16 @@ type FoldingRangeInfo struct { func FoldingRange(ctx context.Context, view View, f GoFile, lineFoldingOnly bool) (ranges []*FoldingRangeInfo, err error) { // TODO(suzmue): consider limiting the number of folding ranges returned, and // implement a way to prioritize folding ranges in that case. - fset := f.FileSet() - file, err := f.GetAST(ctx, ParseFull) + fh := f.Handle(ctx) + file, err := view.Session().Cache().ParseGoHandle(fh, ParseFull).Parse(ctx) if err != nil { return nil, err } - data, _, err := f.Handle(ctx).Read(ctx) + data, _, err := fh.Read(ctx) if err != nil { return nil, err } + fset := view.Session().Cache().FileSet() m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, fset.File(file.Pos()), data) // Get folding ranges for comments separately as they are not walked by ast.Inspect. diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go index ec3c6caf5b..033e546705 100644 --- a/internal/lsp/source/format.go +++ b/internal/lsp/source/format.go @@ -8,6 +8,7 @@ package source import ( "bytes" "context" + "go/ast" "go/format" "go/token" @@ -29,14 +30,19 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error) if !ok { return nil, errors.Errorf("formatting is not supported for non-Go files") } - file, err := gof.GetAST(ctx, ParseFull) - if file == nil { - return nil, err - } pkg, err := gof.GetPackage(ctx) if err != nil { return nil, err } + var file *ast.File + for _, ph := range pkg.GetHandles() { + if ph.File().Identity().URI == f.URI() { + file, err = ph.Cached(ctx) + } + } + if file == nil { + return nil, err + } if hasListErrors(pkg.GetErrors()) || hasParseErrors(pkg, f.URI()) { // Even if this package has list or parse errors, this file may not // have any parse errors and can still be formatted. Using format.Node @@ -49,7 +55,7 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error) return computeTextEdits(ctx, view.Session().Cache().FileSet(), f, string(formatted)) } - fset := f.FileSet() + fset := view.Session().Cache().FileSet() buf := &bytes.Buffer{} // format.Node changes slightly from one release to another, so the version diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go index ea3e3f96e9..17fc7d192a 100644 --- a/internal/lsp/source/hover.go +++ b/internal/lsp/source/hover.go @@ -46,7 +46,7 @@ func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) { switch x := h.source.(type) { case ast.Node: var b strings.Builder - if err := format.Node(&b, i.File.FileSet(), x); err != nil { + if err := format.Node(&b, i.View.Session().Cache().FileSet(), x); err != nil { return nil, err } h.Signature = b.String() diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go index 373d347c2f..c74e0e697c 100644 --- a/internal/lsp/source/identifier.go +++ b/internal/lsp/source/identifier.go @@ -21,8 +21,9 @@ import ( // IdentifierInfo holds information about an identifier in Go source. type IdentifierInfo struct { Name string - mappedRange + View View File GoFile + mappedRange Type struct { mappedRange @@ -69,11 +70,11 @@ func findIdentifier(ctx context.Context, view View, f GoFile, pkg Package, file // If the position is not an identifier but immediately follows // an identifier or selector period (as is common when // requesting a completion), use the path to the preceding node. - result, err := identifier(ctx, view, f, pkg, file, pos-1) - if result == nil && err == nil { - err = errors.Errorf("no identifier found for %s", f.FileSet().Position(pos)) + ident, err := identifier(ctx, view, f, pkg, file, pos-1) + if ident == nil && err == nil { + err = errors.New("no identifier found") } - return result, err + return ident, err } // identifier checks a single position for a potential identifier. @@ -92,6 +93,7 @@ func identifier(ctx context.Context, view View, f GoFile, pkg Package, file *ast return nil, errors.Errorf("can't find node enclosing position") } result := &IdentifierInfo{ + View: view, File: f, qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()), pkg: pkg, @@ -168,7 +170,7 @@ func identifier(ctx context.Context, view View, f GoFile, pkg Package, file *ast if result.Declaration.mappedRange, err = objToMappedRange(ctx, view, result.Declaration.obj); err != nil { return nil, err } - if result.Declaration.node, err = objToNode(ctx, f.View(), pkg.GetTypes(), result.Declaration.obj, result.Declaration.mappedRange.spanRange); err != nil { + if result.Declaration.node, err = objToNode(ctx, view, pkg.GetTypes(), result.Declaration.obj, result.Declaration.mappedRange.spanRange); err != nil { return nil, err } typ := pkg.GetTypesInfo().TypeOf(result.ident) @@ -268,6 +270,7 @@ func importSpec(ctx context.Context, view View, f GoFile, fAST *ast.File, pkg Pa return nil, errors.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err) } result := &IdentifierInfo{ + View: view, File: f, Name: importPath, pkg: pkg, diff --git a/internal/lsp/source/references.go b/internal/lsp/source/references.go index bafd02ac4f..defb826ba6 100644 --- a/internal/lsp/source/references.go +++ b/internal/lsp/source/references.go @@ -25,7 +25,7 @@ type ReferenceInfo struct { // References returns a list of references for a given identifier within the packages // containing i.File. Declarations appear first in the result. -func (i *IdentifierInfo) References(ctx context.Context, view View) ([]*ReferenceInfo, error) { +func (i *IdentifierInfo) References(ctx context.Context) ([]*ReferenceInfo, error) { ctx, done := trace.StartSpan(ctx, "source.References") defer done() var references []*ReferenceInfo @@ -34,8 +34,7 @@ func (i *IdentifierInfo) References(ctx context.Context, view View) ([]*Referenc if i.Declaration.obj == nil { return nil, errors.Errorf("no references for an import spec") } - - pkgs, err := i.File.GetPackages(ctx) + pkgs, err := i.File.GetCachedPackages(ctx) if err != nil { return nil, err } @@ -61,7 +60,7 @@ func (i *IdentifierInfo) References(ctx context.Context, view View) ([]*Referenc if obj == nil || !sameObj(obj, i.Declaration.obj) { continue } - rng, err := posToRange(ctx, view, ident.Pos(), ident.End()) + rng, err := posToRange(ctx, i.View, ident.Pos(), ident.End()) if err != nil { return nil, err } @@ -79,7 +78,7 @@ func (i *IdentifierInfo) References(ctx context.Context, view View) ([]*Referenc if obj == nil || !sameObj(obj, i.Declaration.obj) { continue } - rng, err := posToRange(ctx, view, ident.Pos(), ident.End()) + rng, err := posToRange(ctx, i.View, ident.Pos(), ident.End()) if err != nil { return nil, err } diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go index 5bd040d29d..d833acc485 100644 --- a/internal/lsp/source/rename.go +++ b/internal/lsp/source/rename.go @@ -110,14 +110,14 @@ func (i *IdentifierInfo) Rename(ctx context.Context, view View, newName string) return nil, errors.Errorf("failed to rename because %q is declared in package %q", i.Name, i.Declaration.obj.Pkg().Name()) } - refs, err := i.References(ctx, view) + refs, err := i.References(ctx) if err != nil { return nil, err } r := renamer{ ctx: ctx, - fset: i.File.FileSet(), + fset: view.Session().Cache().FileSet(), refs: refs, objsToUpdate: make(map[types.Object]bool), from: i.Name, @@ -164,8 +164,16 @@ func (i *IdentifierInfo) Rename(ctx context.Context, view View, newName string) // getPkgName gets the pkg name associated with an identifer representing // the import path in an import spec. func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error) { - file, err := i.File.GetAST(ctx, ParseHeader) - if err != nil { + var ( + file *ast.File + err error + ) + for _, ph := range i.pkg.GetHandles() { + if ph.File().Identity().URI == i.File.URI() { + file, err = ph.Cached(ctx) + } + } + if file == nil { return nil, err } var namePos token.Pos @@ -211,6 +219,7 @@ func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *t } return &IdentifierInfo{ Name: pkgName.Name(), + View: ident.View, mappedRange: decl.mappedRange, File: ident.File, Declaration: decl, diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index f19f6c91e1..14cc98b994 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -461,11 +461,12 @@ func (r *runner) Import(t *testing.T, data tests.Imports) { if err != nil { t.Fatalf("failed for %v: %v", spn, err) } - tok, err := f.(source.GoFile).GetToken(ctx) + fh := f.Handle(ctx) + tok, err := r.view.Session().Cache().TokenHandle(fh).Token(ctx) if err != nil { - t.Fatalf("failed to get token for %s: %v", spn.URI(), err) + t.Fatal(err) } - rng, err := spn.Range(span.NewTokenConverter(f.FileSet(), tok)) + rng, err := spn.Range(span.NewTokenConverter(r.data.Exported.ExpectFileSet, tok)) if err != nil { t.Fatalf("failed for %v: %v", spn, err) } @@ -476,7 +477,7 @@ func (r *runner) Import(t *testing.T, data tests.Imports) { } continue } - data, _, err := f.Handle(ctx).Read(ctx) + data, _, err := fh.Read(ctx) if err != nil { t.Error(err) continue @@ -596,7 +597,7 @@ func (r *runner) Reference(t *testing.T, data tests.References) { want[pos] = true } - refs, err := ident.References(ctx, r.view) + refs, err := ident.References(ctx) if err != nil { t.Fatalf("failed for %v: %v", src, err) } diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go index 7f2eb91403..e0f2f32d65 100644 --- a/internal/lsp/source/symbols.go +++ b/internal/lsp/source/symbols.go @@ -18,14 +18,20 @@ func DocumentSymbols(ctx context.Context, view View, f GoFile) ([]protocol.Docum ctx, done := trace.StartSpan(ctx, "source.DocumentSymbols") defer done() - file, err := f.GetAST(ctx, ParseFull) - if file == nil { - return nil, err - } pkg, err := f.GetPackage(ctx) if err != nil { return nil, err } + var file *ast.File + for _, ph := range pkg.GetHandles() { + if ph.File().Identity().URI == f.URI() { + file, err = ph.Cached(ctx) + } + } + if file == nil { + return nil, err + } + info := pkg.GetTypesInfo() q := qualifier(file, pkg.GetTypes(), info) diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index 205a8de7ae..0f1a7b494c 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -259,8 +259,6 @@ type File interface { URI() span.URI View() View Handle(ctx context.Context) FileHandle - FileSet() *token.FileSet - GetToken(ctx context.Context) (*token.File, error) } // GoFile represents a Go source file that has been type-checked. @@ -269,12 +267,12 @@ type GoFile interface { Builtin() (*ast.File, bool) - // GetAST returns the AST for the file, at or above the given mode. - GetAST(ctx context.Context, mode ParseMode) (*ast.File, error) - // GetCachedPackage returns the cached package for the file, if any. GetCachedPackage(ctx context.Context) (Package, error) + // GetCachedPackage returns the cached package for the file, if any. + GetCachedPackages(ctx context.Context) ([]Package, error) + // GetPackage returns the CheckPackageHandle for the package that this file belongs to. GetCheckPackageHandle(ctx context.Context) (CheckPackageHandle, error)