blob: 25d4a99cafec8ffa23a71f39a5d8149976577af6 [file] [log] [blame]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"context"
"errors"
"fmt"
"go/ast"
"go/token"
"go/types"
"sort"
"strings"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/safetoken"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/bug"
"golang.org/x/tools/internal/event"
)
// ReferenceInfo holds information about reference to an identifier in Go source.
type ReferenceInfo struct {
Name string
MappedRange protocol.MappedRange
ident *ast.Ident
obj types.Object
pkg Package
isDeclaration bool
}
// referencesV1 returns a list of references for a given identifier within the packages
// containing pp. Declarations appear first in the result.
//
// Currently used by Server.{incomingCalls,rename}.
// TODO(adonovan): switch each over to referencesV2 in turn.
func referencesV1(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position, includeDeclaration bool) ([]*ReferenceInfo, error) {
ctx, done := event.Start(ctx, "source.References")
defer done()
// Is the cursor within the package name declaration?
pgf, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp)
if err != nil {
return nil, err
}
if inPackageName {
// TODO(rfindley): this is redundant with package renaming. Refactor to share logic.
metas, err := snapshot.MetadataForFile(ctx, f.URI())
if err != nil {
return nil, err
}
if len(metas) == 0 {
return nil, fmt.Errorf("found no package containing %s", f.URI())
}
targetPkg := metas[len(metas)-1] // widest package
// Find external direct references to the package (imports).
rdeps, err := snapshot.ReverseDependencies(ctx, targetPkg.ID, false)
if err != nil {
return nil, err
}
var refs []*ReferenceInfo
for _, rdep := range rdeps {
for _, uri := range rdep.CompiledGoFiles {
fh, err := snapshot.GetFile(ctx, uri)
if err != nil {
return nil, err
}
f, err := snapshot.ParseGo(ctx, fh, ParseHeader)
if err != nil {
return nil, err
}
for _, imp := range f.File.Imports {
if rdep.DepsByImpPath[UnquoteImportPath(imp)] == targetPkg.ID {
rng, err := f.PosMappedRange(imp.Pos(), imp.End())
if err != nil {
return nil, err
}
refs = append(refs, &ReferenceInfo{
Name: pgf.File.Name.Name,
MappedRange: rng,
})
}
}
}
}
// Find the package declaration of each file in the target package itself.
for _, uri := range targetPkg.CompiledGoFiles {
fh, err := snapshot.GetFile(ctx, uri)
if err != nil {
return nil, err
}
f, err := snapshot.ParseGo(ctx, fh, ParseHeader)
if err != nil {
return nil, err
}
rng, err := f.PosMappedRange(f.File.Name.Pos(), f.File.Name.End())
if err != nil {
return nil, err
}
refs = append(refs, &ReferenceInfo{
Name: pgf.File.Name.Name,
MappedRange: rng,
})
}
return refs, nil
}
qualifiedObjs, err := qualifiedObjsAtProtocolPos(ctx, snapshot, f.URI(), pp)
// Don't return references for builtin types.
if errors.Is(err, errBuiltin) {
return nil, nil
}
if err != nil {
return nil, err
}
refs, err := references(ctx, snapshot, qualifiedObjs, includeDeclaration, true, false)
if err != nil {
return nil, err
}
toSort := refs
if includeDeclaration {
toSort = refs[1:]
}
sort.Slice(toSort, func(i, j int) bool {
x, y := toSort[i], toSort[j]
if cmp := strings.Compare(string(x.MappedRange.URI()), string(y.MappedRange.URI())); cmp != 0 {
return cmp < 0
}
return x.ident.Pos() < y.ident.Pos()
})
return refs, nil
}
// parsePackageNameDecl is a convenience function that parses and
// returns the package name declaration of file fh, and reports
// whether the position ppos lies within it.
func parsePackageNameDecl(ctx context.Context, snapshot Snapshot, fh FileHandle, ppos protocol.Position) (*ParsedGoFile, bool, error) {
pgf, err := snapshot.ParseGo(ctx, fh, ParseHeader)
if err != nil {
return nil, false, err
}
// Careful: because we used ParseHeader,
// pgf.Pos(ppos) may be beyond EOF => (0, err).
pos, _ := pgf.PositionPos(ppos)
return pgf, pgf.File.Name.Pos() <= pos && pos <= pgf.File.Name.End(), nil
}
// references is a helper function to avoid recomputing qualifiedObjsAtProtocolPos.
// The first element of qos is considered to be the declaration;
// if isDeclaration, the first result is an extra item for it.
// Only the definition-related fields of qualifiedObject are used.
// (Arguably it should accept a smaller data type.)
//
// This implementation serves referencesV1 (the soon-to-be obsolete
// portion of Server.references) and Server.rename.
func references(ctx context.Context, snapshot Snapshot, qos []qualifiedObject, includeDeclaration, includeInterfaceRefs, includeEmbeddedRefs bool) ([]*ReferenceInfo, error) {
var (
references []*ReferenceInfo
seen = make(map[positionKey]bool)
)
pos := qos[0].obj.Pos()
if pos == token.NoPos {
return nil, fmt.Errorf("no position for %s", qos[0].obj) // e.g. error.Error
}
// Inv: qos[0].pkg != nil, since Pos is valid.
// Inv: qos[*].pkg != nil, since all qos are logically the same declaration.
filename := safetoken.StartPosition(qos[0].pkg.FileSet(), pos).Filename
pgf, err := qos[0].pkg.File(span.URIFromPath(filename))
if err != nil {
return nil, err
}
declIdent, err := findIdentifier(ctx, snapshot, qos[0].pkg, pgf, qos[0].obj.Pos())
if err != nil {
return nil, err
}
// Make sure declaration is the first item in the response.
if includeDeclaration {
references = append(references, &ReferenceInfo{
MappedRange: declIdent.MappedRange,
Name: qos[0].obj.Name(),
ident: declIdent.ident,
obj: qos[0].obj,
pkg: declIdent.pkg,
isDeclaration: true,
})
}
for _, qo := range qos {
var searchPkgs []Package
// Only search dependents if the object is exported.
if qo.obj.Exported() {
// If obj is a package-level object, we need only search
// among direct reverse dependencies.
// TODO(adonovan): opt: this will still spuriously search
// transitively for (e.g.) capitalized local variables.
// We could do better by checking for an objectpath.
transitive := qo.obj.Pkg().Scope().Lookup(qo.obj.Name()) != qo.obj
rdeps, err := snapshot.ReverseDependencies(ctx, qo.pkg.ID(), transitive)
if err != nil {
return nil, err
}
ids := make([]PackageID, 0, len(rdeps))
for _, rdep := range rdeps {
ids = append(ids, rdep.ID)
}
// TODO(adonovan): opt: build a search index
// that doesn't require type checking.
reverseDeps, err := snapshot.TypeCheck(ctx, TypecheckFull, ids...)
if err != nil {
return nil, err
}
searchPkgs = append(searchPkgs, reverseDeps...)
}
// Add the package in which the identifier is declared.
searchPkgs = append(searchPkgs, qo.pkg)
for _, pkg := range searchPkgs {
for ident, obj := range pkg.GetTypesInfo().Uses {
// For instantiated objects (as in methods or fields on instantiated
// types), we may not have pointer-identical objects but still want to
// consider them references.
if !equalOrigin(obj, qo.obj) {
// If ident is not a use of qo.obj, skip it, with one exception:
// uses of an embedded field can be considered references of the
// embedded type name
if !includeEmbeddedRefs {
continue
}
v, ok := obj.(*types.Var)
if !ok || !v.Embedded() {
continue
}
named, ok := v.Type().(*types.Named)
if !ok || named.Obj() != qo.obj {
continue
}
}
key, found := packagePositionKey(pkg, ident.Pos())
if !found {
bug.Reportf("ident %v (pos: %v) not found in package %v", ident.Name, ident.Pos(), pkg.Name())
continue
}
if seen[key] {
continue
}
seen[key] = true
rng, err := posToMappedRange(pkg, ident.Pos(), ident.End())
if err != nil {
return nil, err
}
references = append(references, &ReferenceInfo{
Name: ident.Name,
ident: ident,
pkg: pkg,
obj: obj,
MappedRange: rng,
})
}
}
}
// When searching on type name, don't include interface references -- they
// would be things like all references to Stringer for any type that
// happened to have a String method.
_, isType := declIdent.Declaration.obj.(*types.TypeName)
if includeInterfaceRefs && !isType {
// TODO(adonovan): opt: don't go back into the position domain:
// we have complete type information already.
declRange := declIdent.MappedRange.Range()
fh, err := snapshot.GetFile(ctx, declIdent.MappedRange.URI())
if err != nil {
return nil, err
}
interfaceRefs, err := interfaceReferences(ctx, snapshot, fh, declRange.Start)
if err != nil {
return nil, err
}
references = append(references, interfaceRefs...)
}
return references, nil
}
// equalOrigin reports whether obj1 and obj2 have equivalent origin object.
// This may be the case even if obj1 != obj2, if one or both of them is
// instantiated.
func equalOrigin(obj1, obj2 types.Object) bool {
return obj1.Pkg() == obj2.Pkg() && obj1.Pos() == obj2.Pos() && obj1.Name() == obj2.Name()
}
// interfaceReferences returns the references to the interfaces implemented by
// the type or method at the given position.
func interfaceReferences(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]*ReferenceInfo, error) {
implementations, err := implementations(ctx, s, f, pp)
if err != nil {
if errors.Is(err, ErrNotAType) {
return nil, nil
}
return nil, err
}
// Make a separate call to references() for each element
// since it treats the first qualifiedObject as a definition.
var refs []*ReferenceInfo
for _, impl := range implementations {
implRefs, err := references(ctx, s, []qualifiedObject{impl}, false, false, false)
if err != nil {
return nil, err
}
refs = append(refs, implRefs...)
}
return refs, nil
}