diff --git a/go.sum b/go.sum index c4cc4a6d32..da662da593 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= diff --git a/internal/lsp/cache/gofile.go b/internal/lsp/cache/gofile.go index 5eb62b8cb7..4111cd9071 100644 --- a/internal/lsp/cache/gofile.go +++ b/internal/lsp/cache/gofile.go @@ -162,6 +162,10 @@ func (f *goFile) GetCachedPackage(ctx context.Context) (source.Package, error) { } f.mu.Unlock() + if len(cphs) == 0 { + return nil, errors.Errorf("no CheckPackageHandles for %s", f.URI()) + } + cph, err := bestCheckPackageHandle(f.URI(), cphs) if err != nil { return nil, err @@ -202,6 +206,16 @@ func (f *goFile) wrongParseMode(ctx context.Context, mode source.ParseMode) bool return true } +func (f *goFile) Builtin() (*ast.File, bool) { + builtinPkg := f.View().BuiltinPackage() + for filename, file := range builtinPkg.Files { + if filename == f.URI().Filename() { + return file, true + } + } + return nil, 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(ctx context.Context) bool { diff --git a/internal/lsp/definition.go b/internal/lsp/definition.go index 07d5525051..c42cd420d7 100644 --- a/internal/lsp/definition.go +++ b/internal/lsp/definition.go @@ -19,39 +19,20 @@ func (s *Server) definition(ctx context.Context, params *protocol.TextDocumentPo if err != nil { return nil, err } - m, err := getMapper(ctx, f) + ident, err := source.Identifier(ctx, view, f, params.Position) if err != nil { return nil, err } - spn, err := m.PointSpan(params.Position) + decRange, err := ident.Declaration.Range() if err != nil { return nil, err } - rng, err := spn.Range(m.Converter) - if err != nil { - return nil, err - } - ident, err := source.Identifier(ctx, f, rng.Start) - if err != nil { - return nil, err - } - decSpan, err := ident.DeclarationRange().Span() - if err != nil { - return nil, err - } - decFile, err := getGoFile(ctx, view, decSpan.URI()) - if err != nil { - return nil, err - } - decM, err := getMapper(ctx, decFile) - if err != nil { - return nil, err - } - loc, err := decM.Location(decSpan) - if err != nil { - return nil, err - } - return []protocol.Location{loc}, nil + return []protocol.Location{ + { + URI: protocol.NewURI(ident.Declaration.URI()), + Range: decRange, + }, + }, nil } func (s *Server) typeDefinition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { @@ -61,37 +42,18 @@ func (s *Server) typeDefinition(ctx context.Context, params *protocol.TextDocume if err != nil { return nil, err } - m, err := getMapper(ctx, f) + ident, err := source.Identifier(ctx, view, f, params.Position) if err != nil { return nil, err } - spn, err := m.PointSpan(params.Position) + identRange, err := ident.Type.Range() if err != nil { return nil, err } - rng, err := spn.Range(m.Converter) - if err != nil { - return nil, err - } - ident, err := source.Identifier(ctx, f, rng.Start) - if err != nil { - return nil, err - } - identSpan, err := ident.Type.Range.Span() - if err != nil { - return nil, err - } - identFile, err := getGoFile(ctx, view, identSpan.URI()) - if err != nil { - return nil, err - } - identM, err := getMapper(ctx, identFile) - if err != nil { - return nil, err - } - loc, err := identM.Location(identSpan) - if err != nil { - return nil, err - } - return []protocol.Location{loc}, nil + return []protocol.Location{ + { + URI: protocol.NewURI(ident.Type.URI()), + Range: identRange, + }, + }, nil } diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go index da7dde4a66..333ce14477 100644 --- a/internal/lsp/hover.go +++ b/internal/lsp/hover.go @@ -38,19 +38,7 @@ func (s *Server) hover(ctx context.Context, params *protocol.TextDocumentPositio if err != nil { return nil, err } - m, err := getMapper(ctx, f) - if err != nil { - return nil, err - } - spn, err := m.PointSpan(params.Position) - if err != nil { - return nil, err - } - identRange, err := spn.Range(m.Converter) - if err != nil { - return nil, err - } - ident, err := source.Identifier(ctx, f, identRange.Start) + ident, err := source.Identifier(ctx, view, f, params.Position) if err != nil { return nil, nil } @@ -58,11 +46,7 @@ func (s *Server) hover(ctx context.Context, params *protocol.TextDocumentPositio if err != nil { return nil, err } - identSpan, err := ident.Range.Span() - if err != nil { - return nil, err - } - rng, err := m.Range(identSpan) + rng, err := ident.Range() if err != nil { return nil, err } diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index 59dbe0b34c..ec49f755d5 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -361,7 +361,7 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) { } else { locs, err = r.server.Definition(r.ctx, params) if err != nil { - t.Fatalf("failed for %v: %v", d.Src, err) + t.Fatalf("failed for %v: %+v", d.Src, err) } hover, err = r.server.Hover(r.ctx, params) } diff --git a/internal/lsp/references.go b/internal/lsp/references.go index 589450cf7a..28d6587169 100644 --- a/internal/lsp/references.go +++ b/internal/lsp/references.go @@ -21,44 +21,21 @@ func (s *Server) references(ctx context.Context, params *protocol.ReferenceParam if err != nil { return nil, err } - m, err := getMapper(ctx, f) - if err != nil { - return nil, err - } - spn, err := m.PointSpan(params.Position) - if err != nil { - return nil, err - } - rng, err := spn.Range(m.Converter) - if err != nil { - return nil, err - } // Find all references to the identifier at the position. - ident, err := source.Identifier(ctx, f, rng.Start) + ident, err := source.Identifier(ctx, view, f, params.Position) if err != nil { return nil, err } - references, err := ident.References(ctx) + references, err := ident.References(ctx, view) if err != nil { log.Error(ctx, "no references", err, tag.Of("Identifier", ident.Name)) } - if params.Context.IncludeDeclaration { - // The declaration of this identifier may not be in the - // scope that we search for references, so make sure - // it is added to the beginning of the list if IncludeDeclaration - // was specified. - references = append([]*source.ReferenceInfo{ - &source.ReferenceInfo{ - Range: ident.DeclarationRange(), - }, - }, references...) - } // Get the location of each reference to return as the result. locations := make([]protocol.Location, 0, len(references)) seen := make(map[span.Span]bool) for _, ref := range references { - refSpan, err := ref.Range.Span() + refSpan, err := ref.Span() if err != nil { return nil, err } @@ -66,20 +43,31 @@ func (s *Server) references(ctx context.Context, params *protocol.ReferenceParam continue // already added this location } seen[refSpan] = true + refRange, err := ref.Range() + if err != nil { + return nil, err + } + locations = append(locations, protocol.Location{ + URI: protocol.NewURI(ref.URI()), + Range: refRange, + }) + } + // The declaration of this identifier may not be in the + // scope that we search for references, so make sure + // it is added to the beginning of the list if IncludeDeclaration + // was specified. + if params.Context.IncludeDeclaration { + rng, err := ident.Declaration.Range() + if err != nil { + return nil, err + } + locations = append([]protocol.Location{ + { + URI: protocol.NewURI(ident.Declaration.URI()), + Range: rng, + }, + }, locations...) - refFile, err := getGoFile(ctx, view, refSpan.URI()) - if err != nil { - return nil, err - } - refM, err := getMapper(ctx, refFile) - if err != nil { - return nil, err - } - loc, err := refM.Location(refSpan) - if err != nil { - return nil, err - } - locations = append(locations, loc) } return locations, nil } diff --git a/internal/lsp/rename.go b/internal/lsp/rename.go index 0aca1e262b..40912413bb 100644 --- a/internal/lsp/rename.go +++ b/internal/lsp/rename.go @@ -19,23 +19,11 @@ func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*pr if err != nil { return nil, err } - m, err := getMapper(ctx, f) + ident, err := source.Identifier(ctx, view, f, params.Position) if err != nil { return nil, err } - spn, err := m.PointSpan(params.Position) - if err != nil { - return nil, err - } - rng, err := spn.Range(m.Converter) - if err != nil { - return nil, err - } - ident, err := source.Identifier(ctx, f, rng.Start) - if err != nil { - return nil, err - } - edits, err := ident.Rename(ctx, params.NewName) + edits, err := ident.Rename(ctx, view, params.NewName) if err != nil { return nil, err } diff --git a/internal/lsp/signature_help.go b/internal/lsp/signature_help.go index a608d99409..f846f94b30 100644 --- a/internal/lsp/signature_help.go +++ b/internal/lsp/signature_help.go @@ -33,7 +33,7 @@ func (s *Server) signatureHelp(ctx context.Context, params *protocol.TextDocumen if err != nil { return nil, err } - info, err := source.SignatureHelp(ctx, f, rng.Start) + info, err := source.SignatureHelp(ctx, view, f, params.Position) if err != nil { log.Print(ctx, "no signature help", tag.Of("At", rng), tag.Of("Failure", err)) return nil, nil diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go index 0ceaf5aee1..d17678ad3c 100644 --- a/internal/lsp/source/completion_format.go +++ b/internal/lsp/source/completion_format.go @@ -115,11 +115,11 @@ func (c *completer) item(cand candidate) (CompletionItem, error) { } // TODO(rstambler): Log errors when this feature is enabled. if c.opts.WantDocumentaton { - declRange, err := objToRange(c.ctx, c.view.Session().Cache().FileSet(), obj) + declRange, err := objToRange(c.ctx, c.view, obj) if err != nil { goto Return } - pos := declRange.FileSet.Position(declRange.Start) + pos := c.view.Session().Cache().FileSet().Position(declRange.spanRange.Start) if !pos.IsValid() { goto Return } @@ -136,16 +136,20 @@ func (c *completer) item(cand candidate) (CompletionItem, error) { if err != nil { goto Return } - var file *ast.File - for _, ph := range pkg.GetHandles() { - if ph.File().Identity().URI == gof.URI() { - file, _ = ph.Cached(c.ctx) + var ph ParseGoHandle + for _, h := range pkg.GetHandles() { + if h.File().Identity().URI == gof.URI() { + ph = h } } + if ph == nil { + goto Return + } + file, _ := ph.Cached(c.ctx) if file == nil { goto Return } - ident, err := findIdentifier(c.ctx, gof, pkg, file, declRange.Start) + ident, err := findIdentifier(c.ctx, c.view, gof, pkg, file, declRange.spanRange.Start) if err != nil { goto Return } diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go index 15da24643f..ea3e3f96e9 100644 --- a/internal/lsp/source/hover.go +++ b/internal/lsp/source/hover.go @@ -38,7 +38,7 @@ func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) { ctx, done := trace.StartSpan(ctx, "source.Hover") defer done() - h, err := i.decl.hover(ctx) + h, err := i.Declaration.hover(ctx) if err != nil { return nil, err } @@ -55,8 +55,8 @@ func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) { } // Set the documentation. - if i.decl.obj != nil { - h.SingleLine = types.ObjectString(i.decl.obj, i.qf) + if i.Declaration.obj != nil { + h.SingleLine = types.ObjectString(i.Declaration.obj, i.qf) } if h.comment != nil { h.FullDocumentation = h.comment.Text() @@ -65,7 +65,7 @@ func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) { return h, nil } -func (d declaration) hover(ctx context.Context) (*HoverInformation, error) { +func (d Declaration) hover(ctx context.Context) (*HoverInformation, error) { ctx, done := trace.StartSpan(ctx, "source.hover") defer done() obj := d.obj diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go index 2c5447bb19..a062dcc6e1 100644 --- a/internal/lsp/source/identifier.go +++ b/internal/lsp/source/identifier.go @@ -12,6 +12,7 @@ import ( "strconv" "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/telemetry/trace" errors "golang.org/x/xerrors" @@ -19,14 +20,16 @@ import ( // IdentifierInfo holds information about an identifier in Go source. type IdentifierInfo struct { - Name string - Range span.Range - File GoFile - Type struct { - Range span.Range + Name string + mappedRange + File GoFile + + Type struct { + mappedRange Object types.Object } - decl declaration + + Declaration Declaration pkg Package ident *ast.Ident @@ -34,44 +37,39 @@ type IdentifierInfo struct { qf types.Qualifier } -type declaration struct { - rng span.Range +type Declaration struct { + mappedRange node ast.Node obj types.Object wasImplicit bool } -func (i *IdentifierInfo) DeclarationRange() span.Range { - return i.decl.rng -} - // Identifier returns identifier information for a position // in a file, accounting for a potentially incomplete selector. -func Identifier(ctx context.Context, f GoFile, pos token.Pos) (*IdentifierInfo, error) { - pkg, err := f.GetPackage(ctx) +func Identifier(ctx context.Context, view View, f GoFile, pos protocol.Position) (*IdentifierInfo, error) { + file, pkg, m, err := fileToMapper(ctx, view, f.URI()) 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 { + spn, err := m.PointSpan(pos) + if err != nil { return nil, err } - return findIdentifier(ctx, f, pkg, file, pos) + rng, err := spn.Range(m.Converter) + if err != nil { + return nil, err + } + return findIdentifier(ctx, view, f, pkg, file, rng.Start) } -func findIdentifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) { - if result, err := identifier(ctx, f, pkg, file, pos); err != nil || result != nil { +func findIdentifier(ctx context.Context, view View, f GoFile, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) { + if result, err := identifier(ctx, view, f, pkg, file, pos); err != nil || result != nil { return result, err } // 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, f, pkg, file, pos-1) + 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)) } @@ -79,14 +77,14 @@ func findIdentifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, } // identifier checks a single position for a potential identifier. -func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) { +func identifier(ctx context.Context, view View, f GoFile, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) { ctx, done := trace.StartSpan(ctx, "source.identifier") defer done() var err error // Handle import specs separately, as there is no formal position for a package declaration. - if result, err := importSpec(ctx, f, file, pkg, pos); result != nil || err != nil { + if result, err := importSpec(ctx, view, f, file, pkg, pos); result != nil || err != nil { return result, err } path, _ := astutil.PathEnclosingInterval(file, pos, pos) @@ -115,9 +113,11 @@ func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos } } result.Name = result.ident.Name - result.Range = span.NewRange(f.FileSet(), result.ident.Pos(), result.ident.End()) - result.decl.obj = pkg.GetTypesInfo().ObjectOf(result.ident) - if result.decl.obj == nil { + if result.mappedRange, err = posToRange(ctx, view, result.ident.Pos(), result.ident.End()); err != nil { + return nil, err + } + result.Declaration.obj = pkg.GetTypesInfo().ObjectOf(result.ident) + if result.Declaration.obj == nil { // If there was no types.Object for the declaration, there might be an implicit local variable // declaration in a type switch. if objs := typeSwitchVar(pkg.GetTypesInfo(), path); len(objs) > 0 { @@ -125,8 +125,8 @@ func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos // but all of the types.Objects associated with the usages of this variable can be // used to connect it back to the declaration. // Preserve the first of these objects and treat it as if it were the declaring object. - result.decl.obj = objs[0] - result.decl.wasImplicit = true + result.Declaration.obj = objs[0] + result.Declaration.wasImplicit = true } else { // Probably a type error. return nil, errors.Errorf("no object for ident %v", result.Name) @@ -134,13 +134,13 @@ func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos } // Handle builtins separately. - if result.decl.obj.Parent() == types.Universe { + if result.Declaration.obj.Parent() == types.Universe { decl, ok := lookupBuiltinDecl(f.View(), result.Name).(ast.Node) if !ok { return nil, errors.Errorf("no declaration for %s", result.Name) } - result.decl.node = decl - if result.decl.rng, err = posToRange(ctx, f.FileSet(), result.Name, decl.Pos()); err != nil { + result.Declaration.node = decl + if result.Declaration.mappedRange, err = nameToRange(ctx, view, decl.Pos(), result.Name); err != nil { return nil, err } return result, nil @@ -149,26 +149,26 @@ func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos if result.wasEmbeddedField { // The original position was on the embedded field declaration, so we // try to dig out the type and jump to that instead. - if v, ok := result.decl.obj.(*types.Var); ok { + if v, ok := result.Declaration.obj.(*types.Var); ok { if typObj := typeToObject(v.Type()); typObj != nil { - result.decl.obj = typObj + result.Declaration.obj = typObj } } } for _, obj := range pkg.GetTypesInfo().Implicits { - if obj.Pos() == result.decl.obj.Pos() { + if obj.Pos() == result.Declaration.obj.Pos() { // Mark this declaration as implicit, since it will not // appear in a (*types.Info).Defs map. - result.decl.wasImplicit = true + result.Declaration.wasImplicit = true break } } - if result.decl.rng, err = objToRange(ctx, f.FileSet(), result.decl.obj); err != nil { + if result.Declaration.mappedRange, err = objToRange(ctx, view, result.Declaration.obj); err != nil { return nil, err } - if result.decl.node, err = objToNode(ctx, f.View(), pkg.GetTypes(), result.decl.obj, result.decl.rng); err != nil { + if result.Declaration.node, err = objToNode(ctx, f.View(), pkg.GetTypes(), result.Declaration.obj, result.Declaration.mappedRange.spanRange); err != nil { return nil, err } typ := pkg.GetTypesInfo().TypeOf(result.ident) @@ -182,7 +182,7 @@ func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos if hasErrorType(result.Type.Object) { return result, nil } - if result.Type.Range, err = objToRange(ctx, f.FileSet(), result.Type.Object); err != nil { + if result.Type.mappedRange, err = objToRange(ctx, view, result.Type.Object); err != nil { return nil, err } } @@ -204,7 +204,7 @@ func hasErrorType(obj types.Object) bool { return types.IsInterface(obj.Type()) && obj.Pkg() == nil && obj.Name() == "error" } -func objToRange(ctx context.Context, fset *token.FileSet, obj types.Object) (span.Range, error) { +func objToRange(ctx context.Context, view View, obj types.Object) (mappedRange, error) { if pkgName, ok := obj.(*types.PkgName); ok { // An imported Go package has a package-local, unqualified name. // When the name matches the imported package name, there is no @@ -217,17 +217,32 @@ func objToRange(ctx context.Context, fset *token.FileSet, obj types.Object) (spa // When the identifier does not appear in the source, have the range // of the object be the point at the beginning of the declaration. if pkgName.Imported().Name() == pkgName.Name() { - return posToRange(ctx, fset, "", obj.Pos()) + return nameToRange(ctx, view, obj.Pos(), "") } } - return posToRange(ctx, fset, obj.Name(), obj.Pos()) + return nameToRange(ctx, view, obj.Pos(), obj.Name()) } -func posToRange(ctx context.Context, fset *token.FileSet, name string, pos token.Pos) (span.Range, error) { +func nameToRange(ctx context.Context, view View, pos token.Pos, name string) (mappedRange, error) { + return posToRange(ctx, view, pos, pos+token.Pos(len(name))) +} + +func posToRange(ctx context.Context, view View, pos, end token.Pos) (mappedRange, error) { if !pos.IsValid() { - return span.Range{}, errors.Errorf("invalid position for %v", name) + return mappedRange{}, errors.Errorf("invalid position for %v", pos) } - return span.NewRange(fset, pos, pos+token.Pos(len(name))), nil + if !end.IsValid() { + return mappedRange{}, errors.Errorf("invalid position for %v", end) + } + posn := view.Session().Cache().FileSet().Position(pos) + _, m, err := cachedFileToMapper(ctx, view, span.FileURI(posn.Filename)) + if err != nil { + return mappedRange{}, err + } + return mappedRange{ + m: m, + spanRange: span.NewRange(view.Session().Cache().FileSet(), pos, end), + }, nil } func objToNode(ctx context.Context, view View, originPkg *types.Package, obj types.Object, rng span.Range) (ast.Decl, error) { @@ -279,7 +294,7 @@ func objToNode(ctx context.Context, view View, originPkg *types.Package, obj typ } // importSpec handles positions inside of an *ast.ImportSpec. -func importSpec(ctx context.Context, f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) { +func importSpec(ctx context.Context, view View, f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) { var imp *ast.ImportSpec for _, spec := range fAST.Imports { if spec.Path.Pos() <= pos && pos < spec.Path.End() { @@ -294,10 +309,12 @@ func importSpec(ctx context.Context, f GoFile, fAST *ast.File, pkg Package, pos return nil, errors.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err) } result := &IdentifierInfo{ - File: f, - Name: importPath, - Range: span.NewRange(f.FileSet(), imp.Pos(), imp.End()), - pkg: pkg, + File: f, + Name: importPath, + pkg: pkg, + } + if result.mappedRange, err = posToRange(ctx, view, imp.Pos(), imp.End()); err != nil { + return nil, err } // Consider the "declaration" of an import spec to be the imported package. importedPkg, err := pkg.GetImport(ctx, importPath) @@ -317,8 +334,10 @@ func importSpec(ctx context.Context, f GoFile, fAST *ast.File, pkg Package, pos if dest == nil { return nil, errors.Errorf("package %q has no files", importPath) } - result.decl.rng = span.NewRange(f.FileSet(), dest.Name.Pos(), dest.Name.End()) - result.decl.node = imp + if result.Declaration.mappedRange, err = posToRange(ctx, view, dest.Pos(), dest.End()); err != nil { + return nil, err + } + result.Declaration.node = imp return result, nil } diff --git a/internal/lsp/source/references.go b/internal/lsp/source/references.go index 2822e03464..a81a36c850 100644 --- a/internal/lsp/source/references.go +++ b/internal/lsp/source/references.go @@ -9,15 +9,14 @@ import ( "go/ast" "go/types" - "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/telemetry/trace" errors "golang.org/x/xerrors" ) // ReferenceInfo holds information about reference to an identifier in Go source. type ReferenceInfo struct { - Name string - Range span.Range + Name string + mappedRange ident *ast.Ident obj types.Object pkg Package @@ -26,13 +25,13 @@ 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) ([]*ReferenceInfo, error) { +func (i *IdentifierInfo) References(ctx context.Context, view View) ([]*ReferenceInfo, error) { ctx, done := trace.StartSpan(ctx, "source.References") defer done() var references []*ReferenceInfo // If the object declaration is nil, assume it is an import spec and do not look for references. - if i.decl.obj == nil { + if i.Declaration.obj == nil { return nil, errors.Errorf("no references for an import spec") } @@ -46,43 +45,49 @@ func (i *IdentifierInfo) References(ctx context.Context) ([]*ReferenceInfo, erro return nil, errors.Errorf("package %s has no types info", pkg.PkgPath()) } - if i.decl.wasImplicit { + if i.Declaration.wasImplicit { // The definition is implicit, so we must add it separately. // This occurs when the variable is declared in a type switch statement // or is an implicit package name. Both implicits are local to a file. references = append(references, &ReferenceInfo{ - Name: i.decl.obj.Name(), - Range: i.decl.rng, - obj: i.decl.obj, + Name: i.Declaration.obj.Name(), + mappedRange: i.Declaration.mappedRange, + obj: i.Declaration.obj, pkg: pkg, isDeclaration: true, }) } for ident, obj := range info.Defs { - if obj == nil || !sameObj(obj, i.decl.obj) { + if obj == nil || !sameObj(obj, i.Declaration.obj) { continue } - // Add the declarations at the beginning of the references list. - references = append([]*ReferenceInfo{&ReferenceInfo{ + reference := &ReferenceInfo{ Name: ident.Name, - Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()), ident: ident, obj: obj, pkg: pkg, isDeclaration: true, - }}, references...) + } + if reference.mappedRange, err = posToRange(ctx, view, ident.Pos(), ident.End()); err != nil { + return nil, err + } + // Add the declarations at the beginning of the references list. + references = append([]*ReferenceInfo{reference}, references...) } for ident, obj := range info.Uses { - if obj == nil || !sameObj(obj, i.decl.obj) { + if obj == nil || !sameObj(obj, i.Declaration.obj) { continue } - references = append(references, &ReferenceInfo{ + reference := &ReferenceInfo{ Name: ident.Name, - Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()), ident: ident, pkg: pkg, obj: obj, - }) + } + if reference.mappedRange, err = posToRange(ctx, view, ident.Pos(), ident.End()); err != nil { + return nil, err + } + references = append(references, reference) } } diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go index 00f26f585e..603fc84ea6 100644 --- a/internal/lsp/source/rename.go +++ b/internal/lsp/source/rename.go @@ -36,19 +36,19 @@ type renamer struct { } // Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package. -func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.URI][]diff.TextEdit, error) { +func (i *IdentifierInfo) Rename(ctx context.Context, view View, newName string) (map[span.URI][]diff.TextEdit, error) { ctx, done := trace.StartSpan(ctx, "source.Rename") defer done() // If the object declaration is nil, assume it is an import spec. - if i.decl.obj == nil { + if i.Declaration.obj == nil { // Find the corresponding package name for this import spec // and rename that instead. ident, err := i.getPkgName(ctx) if err != nil { return nil, err } - return ident.Rename(ctx, newName) + return ident.Rename(ctx, view, newName) } if i.Name == newName { return nil, errors.Errorf("old and new names are the same: %s", newName) @@ -57,18 +57,18 @@ func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.U return nil, errors.Errorf("invalid identifier to rename: %q", i.Name) } // Do not rename builtin identifiers. - if i.decl.obj.Parent() == types.Universe { + if i.Declaration.obj.Parent() == types.Universe { return nil, errors.Errorf("cannot rename builtin %q", i.Name) } if i.pkg == nil || i.pkg.IsIllTyped() { return nil, errors.Errorf("package for %s is ill typed", i.File.URI()) } // Do not rename identifiers declared in another package. - if i.pkg.GetTypes() != i.decl.obj.Pkg() { - return nil, errors.Errorf("failed to rename because %q is declared in package %q", i.Name, i.decl.obj.Pkg().Name()) + if i.pkg.GetTypes() != i.Declaration.obj.Pkg() { + 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) + refs, err := i.References(ctx, view) if err != nil { return nil, err } @@ -112,8 +112,8 @@ func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.U // 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 := i.File.FileSet().File(i.Range.Start) - pkgLine := file.Line(i.Range.Start) + file := i.File.FileSet().File(i.mappedRange.spanRange.Start) + pkgLine := file.Line(i.mappedRange.spanRange.Start) for _, obj := range i.pkg.GetTypesInfo().Defs { pkgName, ok := obj.(*types.PkgName) @@ -133,22 +133,22 @@ func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error // getPkgNameIdentifier returns an IdentifierInfo representing pkgName. // pkgName must be in the same package and file as ident. func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *types.PkgName) (*IdentifierInfo, error) { - decl := declaration{ + decl := Declaration{ obj: pkgName, wasImplicit: true, } var err error - if decl.rng, err = objToRange(ctx, ident.File.FileSet(), decl.obj); err != nil { + if decl.mappedRange, err = objToRange(ctx, ident.File.View(), decl.obj); err != nil { return nil, err } - if decl.node, err = objToNode(ctx, ident.File.View(), ident.pkg.GetTypes(), decl.obj, decl.rng); err != nil { + if decl.node, err = objToNode(ctx, ident.File.View(), ident.pkg.GetTypes(), decl.obj, decl.mappedRange.spanRange); err != nil { return nil, err } return &IdentifierInfo{ Name: pkgName.Name(), - Range: decl.rng, + mappedRange: decl.mappedRange, File: ident.File, - decl: decl, + Declaration: decl, pkg: ident.pkg, wasEmbeddedField: false, qf: ident.qf, @@ -165,7 +165,7 @@ func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) { return nil, err } for _, ref := range r.refs { - refSpan, err := ref.Range.Span() + refSpan, err := ref.spanRange.Span() if err != nil { return nil, err } diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go index 60cb1b1a0f..3d0b995416 100644 --- a/internal/lsp/source/signature_help.go +++ b/internal/lsp/source/signature_help.go @@ -12,6 +12,7 @@ import ( "go/types" "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/telemetry/trace" errors "golang.org/x/xerrors" ) @@ -26,21 +27,25 @@ type ParameterInformation struct { Label string } -func SignatureHelp(ctx context.Context, f GoFile, pos token.Pos) (*SignatureInformation, error) { +func SignatureHelp(ctx context.Context, view View, f GoFile, pos protocol.Position) (*SignatureInformation, error) { ctx, done := trace.StartSpan(ctx, "source.SignatureHelp") defer done() - file, err := f.GetAST(ctx, ParseFull) - if file == nil { + file, pkg, m, err := fileToMapper(ctx, view, f.URI()) + if err != nil { return nil, err } - pkg, err := f.GetPackage(ctx) + spn, err := m.PointSpan(pos) + if err != nil { + return nil, err + } + rng, err := spn.Range(m.Converter) if err != nil { return nil, err } // Find a call expression surrounding the query position. var callExpr *ast.CallExpr - path, _ := astutil.PathEnclosingInterval(file, pos, pos) + path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.Start) if path == nil { return nil, errors.Errorf("cannot find node enclosing position") } @@ -48,7 +53,7 @@ FindCall: for _, node := range path { switch node := node.(type) { case *ast.CallExpr: - if pos >= node.Lparen && pos <= node.Rparen { + if rng.Start >= node.Lparen && rng.Start <= node.Rparen { callExpr = node break FindCall } @@ -76,7 +81,7 @@ FindCall: // Handle builtin functions separately. if obj, ok := obj.(*types.Builtin); ok { - return builtinSignature(ctx, f.View(), callExpr, obj.Name(), pos) + return builtinSignature(ctx, f.View(), callExpr, obj.Name(), rng.Start) } // Get the type information for the function being called. @@ -93,25 +98,25 @@ FindCall: qf := qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()) params := formatParams(sig.Params(), sig.Variadic(), qf) results, writeResultParens := formatResults(sig.Results(), qf) - activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), pos) + activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), rng.Start) var ( name string comment *ast.CommentGroup ) if obj != nil { - rng, err := objToRange(ctx, f.FileSet(), obj) + rng, err := objToRange(ctx, view, obj) if err != nil { return nil, err } - node, err := objToNode(ctx, f.View(), pkg.GetTypes(), obj, rng) + node, err := objToNode(ctx, f.View(), pkg.GetTypes(), obj, rng.spanRange) if err != nil { return nil, err } - decl := &declaration{ - obj: obj, - rng: rng, - node: node, + decl := &Declaration{ + obj: obj, + mappedRange: rng, + node: node, } d, err := decl.hover(ctx) if err != nil { diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index 4b1ea4c1fd..14f1cb7d4d 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -346,12 +346,11 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) { if err != nil { t.Fatalf("failed for %v: %v", d.Src, err) } - tok, err := f.(source.GoFile).GetToken(ctx) + srcRng, err := spanToRange(r.data, d.Src) if err != nil { - t.Fatalf("failed to get token for %s: %v", d.Src.URI(), err) + t.Fatal(err) } - pos := tok.Pos(d.Src.Start().Offset()) - ident, err := source.Identifier(ctx, f.(source.GoFile), pos) + ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), srcRng.Start) if err != nil { t.Fatalf("failed for %v: %v", d.Src, err) } @@ -364,9 +363,15 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) { hover += h.Synopsis + "\n" } hover += h.Signature - rng := ident.DeclarationRange() + rng, err := ident.Range() + if err != nil { + t.Fatal(err) + } if d.IsType { - rng = ident.Type.Range + rng, err = ident.Type.Range() + if err != nil { + t.Fatal(err) + } hover = "" } if hover != "" { @@ -378,10 +383,10 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) { t.Errorf("for %v got %q want %q", d.Src, hover, expectHover) } } else if !d.OnlyHover { - if def, err := rng.Span(); err != nil { - t.Fatalf("failed for %v: %v", rng, err) - } else if def != d.Def { - t.Errorf("for %v got %v want %v", d.Src, def, d.Def) + if defRng, err := spanToRange(r.data, d.Def); err != nil { + t.Fatal(err) + } else if rng != defRng { + t.Errorf("for %v got %v want %v", d.Src, rng, d.Def) } } else { t.Errorf("no tests ran for %s", d.Src.URI()) @@ -424,12 +429,11 @@ func (r *runner) Reference(t *testing.T, data tests.References) { if err != nil { t.Fatalf("failed for %v: %v", src, err) } - tok, err := f.(source.GoFile).GetToken(ctx) + srcRng, err := spanToRange(r.data, src) if err != nil { - t.Fatalf("failed to get token for %s: %v", src.URI(), err) + t.Fatal(err) } - pos := tok.Pos(src.Start().Offset()) - ident, err := source.Identifier(ctx, f.(source.GoFile), pos) + ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), srcRng.Start) if err != nil { t.Fatalf("failed for %v: %v", src, err) } @@ -439,16 +443,16 @@ func (r *runner) Reference(t *testing.T, data tests.References) { want[pos] = true } - refs, err := ident.References(ctx) + refs, err := ident.References(ctx, r.view) if err != nil { t.Fatalf("failed for %v: %v", src, err) } got := make(map[span.Span]bool) for _, refInfo := range refs { - refSpan, err := refInfo.Range.Span() + refSpan, err := refInfo.Span() if err != nil { - t.Errorf("failed for %v item %v: %v", src, refInfo.Name, err) + t.Fatal(err) } got[refSpan] = true } @@ -474,17 +478,16 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) { if err != nil { t.Fatalf("failed for %v: %v", spn, err) } - tok, err := f.(source.GoFile).GetToken(ctx) + srcRng, err := spanToRange(r.data, spn) if err != nil { - t.Fatalf("failed to get token for %s: %v", spn.URI(), err) + t.Fatal(err) } - pos := tok.Pos(spn.Start().Offset()) - ident, err := source.Identifier(r.ctx, f.(source.GoFile), pos) + ident, err := source.Identifier(r.ctx, r.view, f.(source.GoFile), srcRng.Start) if err != nil { t.Error(err) continue } - changes, err := ident.Rename(r.ctx, newText) + changes, err := ident.Rename(r.ctx, r.view, newText) if err != nil { renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) { return []byte(err.Error()), nil @@ -619,12 +622,11 @@ func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) { if err != nil { t.Fatalf("failed for %v: %v", spn, err) } - tok, err := f.(source.GoFile).GetToken(ctx) + rng, err := spanToRange(r.data, spn) if err != nil { - t.Fatalf("failed to get token for %s: %v", spn.URI(), err) + t.Fatal(err) } - pos := tok.Pos(spn.Start().Offset()) - gotSignature, err := source.SignatureHelp(ctx, f.(source.GoFile), pos) + gotSignature, err := source.SignatureHelp(ctx, r.view, f.(source.GoFile), rng.Start) if err != nil { // Only fail if we got an error we did not expect. if expectedSignature != nil { @@ -667,3 +669,16 @@ func diffSignatures(spn span.Span, want *source.SignatureInformation, got *sourc func (r *runner) Link(t *testing.T, data tests.Links) { // This is a pure LSP feature, no source level functionality to be tested. } + +func spanToRange(data *tests.Data, span span.Span) (protocol.Range, error) { + contents, err := data.Exported.FileContents(span.URI().Filename()) + if err != nil { + return protocol.Range{}, err + } + m := protocol.NewColumnMapper(span.URI(), span.URI().Filename(), data.Exported.ExpectFileSet, nil, contents) + srcRng, err := m.Range(span) + if err != nil { + return protocol.Range{}, err + } + return srcRng, nil +} diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go index 6ca1a4e5bc..cc19699340 100644 --- a/internal/lsp/source/util.go +++ b/internal/lsp/source/util.go @@ -16,6 +16,7 @@ import ( "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/span" + errors "golang.org/x/xerrors" ) type mappedRange struct { @@ -42,10 +43,94 @@ func (s mappedRange) Range() (protocol.Range, error) { return *s.protocolRange, nil } +func (s mappedRange) Span() (span.Span, error) { + return s.spanRange.Span() +} + func (s mappedRange) URI() span.URI { return s.m.URI } +func fileToMapper(ctx context.Context, view View, uri span.URI) (*ast.File, Package, *protocol.ColumnMapper, error) { + f, err := view.GetFile(ctx, uri) + if err != nil { + return nil, nil, nil, err + } + gof, ok := f.(GoFile) + if !ok { + return nil, nil, nil, errors.Errorf("%s is not a Go file", f.URI()) + } + pkg, err := gof.GetPackage(ctx) + if err != nil { + return nil, nil, nil, err + } + file, m, err := pkgToMapper(ctx, view, pkg, uri) + if err != nil { + return nil, nil, nil, err + } + return file, pkg, m, nil +} + +func cachedFileToMapper(ctx context.Context, view View, uri span.URI) (*ast.File, *protocol.ColumnMapper, error) { + f, err := view.GetFile(ctx, uri) + if err != nil { + return nil, nil, err + } + gof, ok := f.(GoFile) + if !ok { + return nil, nil, errors.Errorf("%s is not a Go file", f.URI()) + } + if file, ok := gof.Builtin(); ok { + return builtinFileToMapper(ctx, view, gof, file) + } + pkg, err := gof.GetCachedPackage(ctx) + if err != nil { + return nil, nil, err + } + file, m, err := pkgToMapper(ctx, view, pkg, uri) + if err != nil { + return nil, nil, err + } + return file, m, nil +} + +func pkgToMapper(ctx context.Context, view View, pkg Package, uri span.URI) (*ast.File, *protocol.ColumnMapper, error) { + var ph ParseGoHandle + for _, h := range pkg.GetHandles() { + if h.File().Identity().URI == uri { + ph = h + } + } + file, err := ph.Cached(ctx) + if file == nil { + return nil, nil, err + } + data, _, err := ph.File().Read(ctx) + if err != nil { + return nil, nil, err + } + fset := view.Session().Cache().FileSet() + tok := fset.File(file.Pos()) + if tok == nil { + return nil, nil, errors.Errorf("no token.File for %s", uri) + } + return file, protocol.NewColumnMapper(uri, uri.Filename(), fset, tok, data), nil +} + +func builtinFileToMapper(ctx context.Context, view View, f GoFile, file *ast.File) (*ast.File, *protocol.ColumnMapper, error) { + fh := f.Handle(ctx) + data, _, err := fh.Read(ctx) + if err != nil { + return nil, nil, err + } + fset := view.Session().Cache().FileSet() + tok := fset.File(file.Pos()) + if tok == nil { + return nil, nil, errors.Errorf("no token.File for %s", f.URI()) + } + return nil, protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, tok, data), nil +} + func IsGenerated(ctx context.Context, view View, uri span.URI) bool { f, err := view.GetFile(ctx, uri) if err != nil { diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index b3a9288e26..d32662e4d3 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -258,6 +258,8 @@ type File interface { type GoFile interface { File + 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) diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index c0a5b31fae..8ea44ea8dd 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -108,10 +108,9 @@ type Tests interface { type Definition struct { Name string - Src span.Span IsType bool OnlyHover bool - Def span.Span + Src, Def span.Span } type CompletionSnippet struct {