gopls/internal/lsp/source: fix implementations query on error type

Refactor so that implementations queries do not require local packages
for the queried object.

Fixes golang/go#43655

Change-Id: I1bf87c236cefbffff53fa2b2c1a11c2cfb96b763
Reviewed-on: https://go-review.googlesource.com/c/tools/+/518896
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/gopls/internal/lsp/source/implementation.go b/gopls/internal/lsp/source/implementation.go
index 25beccf..d9eb814 100644
--- a/gopls/internal/lsp/source/implementation.go
+++ b/gopls/internal/lsp/source/implementation.go
@@ -17,6 +17,7 @@
 	"sync"
 
 	"golang.org/x/sync/errgroup"
+	"golang.org/x/tools/gopls/internal/bug"
 	"golang.org/x/tools/gopls/internal/lsp/protocol"
 	"golang.org/x/tools/gopls/internal/lsp/safetoken"
 	"golang.org/x/tools/gopls/internal/lsp/source/methodsets"
@@ -70,60 +71,36 @@
 }
 
 func implementations(ctx context.Context, snapshot Snapshot, fh FileHandle, pp protocol.Position) ([]protocol.Location, error) {
-
-	// Type-check the query package, find the query identifier,
-	// and locate the type or method declaration it refers to.
-	declPosn, err := typeDeclPosition(ctx, snapshot, fh.URI(), pp)
+	obj, pkg, err := implementsObj(ctx, snapshot, fh.URI(), pp)
 	if err != nil {
 		return nil, err
 	}
 
-	// Type-check the declaring package (incl. variants) for use
-	// by the "local" search, which uses type information to
-	// enumerate all types within the package that satisfy the
-	// query type, even those defined local to a function.
-	declURI := span.URIFromPath(declPosn.Filename)
-	declMetas, err := snapshot.MetadataForFile(ctx, declURI)
-	if err != nil {
-		return nil, err
+	var localPkgs []Package
+	if obj.Pos().IsValid() { // no local package for error or error.Error
+		declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos())
+		// Type-check the declaring package (incl. variants) for use
+		// by the "local" search, which uses type information to
+		// enumerate all types within the package that satisfy the
+		// query type, even those defined local to a function.
+		declURI := span.URIFromPath(declPosn.Filename)
+		declMetas, err := snapshot.MetadataForFile(ctx, declURI)
+		if err != nil {
+			return nil, err
+		}
+		RemoveIntermediateTestVariants(&declMetas)
+		if len(declMetas) == 0 {
+			return nil, fmt.Errorf("no packages for file %s", declURI)
+		}
+		ids := make([]PackageID, len(declMetas))
+		for i, m := range declMetas {
+			ids[i] = m.ID
+		}
+		localPkgs, err = snapshot.TypeCheck(ctx, ids...)
+		if err != nil {
+			return nil, err
+		}
 	}
-	RemoveIntermediateTestVariants(&declMetas)
-	if len(declMetas) == 0 {
-		return nil, fmt.Errorf("no packages for file %s", declURI)
-	}
-	ids := make([]PackageID, len(declMetas))
-	for i, m := range declMetas {
-		ids[i] = m.ID
-	}
-	localPkgs, err := snapshot.TypeCheck(ctx, ids...)
-	if err != nil {
-		return nil, err
-	}
-	// The narrowest package will do, since the local search is based
-	// on position and the global search is based on fingerprint.
-	// (Neither is based on object identity.)
-	declPkg := localPkgs[0]
-	declFile, err := declPkg.File(declURI)
-	if err != nil {
-		return nil, err // "can't happen"
-	}
-
-	// Find declaration of corresponding object
-	// in this package based on (URI, offset).
-	pos, err := safetoken.Pos(declFile.Tok, declPosn.Offset)
-	if err != nil {
-		return nil, err
-	}
-	// TODO(adonovan): simplify: use objectsAt?
-	path := pathEnclosingObjNode(declFile.File, pos)
-	if path == nil {
-		return nil, ErrNoIdentFound // checked earlier
-	}
-	id, ok := path[0].(*ast.Ident)
-	if !ok {
-		return nil, ErrNoIdentFound // checked earlier
-	}
-	obj := declPkg.GetTypesInfo().ObjectOf(id) // may be nil
 
 	// Is the selected identifier a type name or method?
 	// (For methods, report the corresponding method names.)
@@ -140,7 +117,7 @@
 		}
 	}
 	if queryType == nil {
-		return nil, fmt.Errorf("%s is not a type or method", id.Name)
+		return nil, bug.Errorf("%s is not a type or method", obj.Name()) // should have been handled by implementsObj
 	}
 
 	// Compute the method-set fingerprint used as a key to the global search.
@@ -166,8 +143,13 @@
 	}
 	RemoveIntermediateTestVariants(&globalMetas)
 	globalIDs := make([]PackageID, 0, len(globalMetas))
+
+	var pkgPath PackagePath
+	if obj.Pkg() != nil { // nil for error
+		pkgPath = PackagePath(obj.Pkg().Path())
+	}
 	for _, m := range globalMetas {
-		if m.PkgPath == declPkg.Metadata().PkgPath {
+		if m.PkgPath == pkgPath {
 			continue // declaring package is handled by local implementation
 		}
 		globalIDs = append(globalIDs, m.ID)
@@ -241,18 +223,19 @@
 	return m.OffsetLocation(start, end)
 }
 
-// typeDeclPosition returns the position of the declaration of the
-// type (or one of its methods) referred to at (uri, ppos).
-func typeDeclPosition(ctx context.Context, snapshot Snapshot, uri span.URI, ppos protocol.Position) (token.Position, error) {
-	var noPosn token.Position
-
+// implementsObj returns the object to query for implementations, which is a
+// type name or method.
+//
+// The returned Package is the narrowest package containing ppos, which is the
+// package using the resulting obj but not necessarily the declaring package.
+func implementsObj(ctx context.Context, snapshot Snapshot, uri span.URI, ppos protocol.Position) (types.Object, Package, error) {
 	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, uri)
 	if err != nil {
-		return noPosn, err
+		return nil, nil, err
 	}
 	pos, err := pgf.PositionPos(ppos)
 	if err != nil {
-		return noPosn, err
+		return nil, nil, err
 	}
 
 	// This function inherits the limitation of its predecessor in
@@ -267,11 +250,11 @@
 	// TODO(adonovan): simplify: use objectsAt?
 	path := pathEnclosingObjNode(pgf.File, pos)
 	if path == nil {
-		return noPosn, ErrNoIdentFound
+		return nil, nil, ErrNoIdentFound
 	}
 	id, ok := path[0].(*ast.Ident)
 	if !ok {
-		return noPosn, ErrNoIdentFound
+		return nil, nil, ErrNoIdentFound
 	}
 
 	// Is the object a type or method? Reject other kinds.
@@ -287,18 +270,17 @@
 		// ok
 	case *types.Func:
 		if obj.Type().(*types.Signature).Recv() == nil {
-			return noPosn, fmt.Errorf("%s is a function, not a method", id.Name)
+			return nil, nil, fmt.Errorf("%s is a function, not a method", id.Name)
 		}
 	case nil:
-		return noPosn, fmt.Errorf("%s denotes unknown object", id.Name)
+		return nil, nil, fmt.Errorf("%s denotes unknown object", id.Name)
 	default:
 		// e.g. *types.Var -> "var".
 		kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
-		return noPosn, fmt.Errorf("%s is a %s, not a type", id.Name, kind)
+		return nil, nil, fmt.Errorf("%s is a %s, not a type", id.Name, kind)
 	}
 
-	declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos())
-	return declPosn, nil
+	return obj, pkg, nil
 }
 
 // localImplementations searches within pkg for declarations of all
diff --git a/gopls/internal/regtest/marker/testdata/implementation/issue43655.txt b/gopls/internal/regtest/marker/testdata/implementation/issue43655.txt
new file mode 100644
index 0000000..a7f1d57
--- /dev/null
+++ b/gopls/internal/regtest/marker/testdata/implementation/issue43655.txt
@@ -0,0 +1,22 @@
+This test verifies that we fine implementations of the built-in error interface.
+
+-- go.mod --
+module example.com
+go 1.12
+
+-- p.go --
+package p
+
+type errA struct{ error } //@loc(errA, "errA")
+
+type errB struct{} //@loc(errB, "errB")
+func (errB) Error() string{ return "" } //@loc(errBError, "Error")
+
+type notAnError struct{}
+func (notAnError) Error() int { return 0 }
+
+func _() {
+	var _ error //@implementation("error", errA, errB)
+	var a errA
+	_ = a.Error //@implementation("Error", errBError)
+}