Skip to content

Commit

Permalink
internal/lsp: add support for implements to the LSP
Browse files Browse the repository at this point in the history
This adds support for the LSP implemention call, based
on the guru code for getting implementations. The guru code
did much more than we need, so some of the code has been
dropped, and other parts of it are ignored (for now).

Fixes golang/go#32973

Change-Id: I1a24450e17d5364f25c4b4120be5320b13ac822b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/203918
Run-TryBot: Michael Matloob <matloob@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
  • Loading branch information
matloob authored and stamblerre committed Oct 31, 2019
1 parent d9fd88a commit 02d0efc
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 25 deletions.
4 changes: 4 additions & 0 deletions internal/lsp/cmd/test/cmdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
//TODO: add command line link tests when it works
}

func (r *runner) Implementation(t *testing.T, spn span.Span, imp tests.Implementations) {
//TODO: add implements tests when it works
}

func CaptureStdOut(t testing.TB, f func()) string {
r, out, err := os.Pipe()
if err != nil {
Expand Down
24 changes: 24 additions & 0 deletions internal/lsp/implementation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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 lsp

import (
"context"

"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
)

func (s *Server) implementation(ctx context.Context, params *protocol.ImplementationParams) ([]protocol.Location, error) {
uri := span.NewURI(params.TextDocument.URI)
view := s.session.ViewOf(uri)
f, err := view.GetFile(ctx, uri)
if err != nil {
return nil, err
}

return source.Implementation(ctx, view, f, params.Position)
}
38 changes: 38 additions & 0 deletions internal/lsp/lsp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,44 @@ func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
}
}

func (r *runner) Implementation(t *testing.T, spn span.Span, m tests.Implementations) {
sm, err := r.data.Mapper(m.Src.URI())
if err != nil {
t.Fatal(err)
}
loc, err := sm.Location(m.Src)
if err != nil {
t.Fatalf("failed for %v: %v", m.Src, err)
}
tdpp := protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
Position: loc.Range.Start,
}
var locs []protocol.Location
params := &protocol.ImplementationParams{
TextDocumentPositionParams: tdpp,
}
locs, err = r.server.Implementation(r.ctx, params)
if err != nil {
t.Fatalf("failed for %v: %v", m.Src, err)
}
if len(locs) != len(m.Implementations) {
t.Fatalf("got %d locations for implementation, expected %d", len(locs), len(m.Implementations))
}
for i := range locs {
locURI := span.NewURI(locs[i].URI)
lm, err := r.data.Mapper(locURI)
if err != nil {
t.Fatal(err)
}
if imp, err := lm.Span(locs[i]); err != nil {
t.Fatalf("failed for %v: %v", locs[i], err)
} else if imp != m.Implementations[i] {
t.Errorf("for %dth implementation of %v got %v want %v", i, m.Src, imp, m.Implementations[i])
}
}
}

func (r *runner) Highlight(t *testing.T, name string, locations []span.Span) {
m, err := r.data.Mapper(locations[0].URI())
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ func (s *Server) TypeDefinition(ctx context.Context, params *protocol.TypeDefini
return s.typeDefinition(ctx, params)
}

func (s *Server) Implementation(context.Context, *protocol.ImplementationParams) ([]protocol.Location, error) {
return nil, notImplemented("Implementation")
func (s *Server) Implementation(ctx context.Context, params *protocol.ImplementationParams) ([]protocol.Location, error) {
return s.implementation(ctx, params)
}

func (s *Server) References(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) {
Expand Down
154 changes: 154 additions & 0 deletions internal/lsp/source/implementation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// 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.

// The code in this file is based largely on the code in
// cmd/guru/implements.go. The guru implementation supports
// looking up "implementers" of methods also, but that
// code has been cut out here for now for simplicity.

package source

import (
"context"
"go/types"
"sort"

"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/lsp/protocol"
)

func Implementation(ctx context.Context, view View, f File, position protocol.Position) ([]protocol.Location, error) {
// Find all references to the identifier at the position.
ident, err := Identifier(ctx, view, f, position)
if err != nil {
return nil, err
}

res, err := ident.implementations(ctx)
if err != nil {
return nil, err
}

var locations []protocol.Location
for _, t := range res.to {
// We'll provide implementations that are named types and pointers to named types.
if p, ok := t.(*types.Pointer); ok {
t = p.Elem()
}
if n, ok := t.(*types.Named); ok {
ph, pkg, err := view.FindFileInPackage(ctx, f.URI(), ident.pkg)
if err != nil {
return nil, err
}
f, _, _, err := ph.Cached()
if err != nil {
return nil, err
}
ident, err := findIdentifier(ctx, view.Snapshot(), pkg, f, n.Obj().Pos())
if err != nil {
return nil, err
}
decRange, err := ident.Declaration.Range()
if err != nil {
return nil, err
}
locations = append(locations, protocol.Location{
URI: protocol.NewURI(ident.Declaration.URI()),
Range: decRange,
})
}
}

return locations, nil
}

func (i *IdentifierInfo) implementations(ctx context.Context) (implementsResult, error) {
T := i.Type.Object.Type()

// Find all named types, even local types (which can have
// methods due to promotion) and the built-in "error".
// We ignore aliases 'type M = N' to avoid duplicate
// reporting of the Named type N.
var allNamed []*types.Named
info := i.pkg.GetTypesInfo()
for _, obj := range info.Defs {
if obj, ok := obj.(*types.TypeName); ok && !obj.IsAlias() {
if named, ok := obj.Type().(*types.Named); ok {
allNamed = append(allNamed, named)
}
}
}

allNamed = append(allNamed, types.Universe.Lookup("error").Type().(*types.Named))

var msets typeutil.MethodSetCache

// TODO(matloob): We only use the to result for now. Figure out if we want to
// surface the from and fromPtr results to users.
// Test each named type.
var to, from, fromPtr []types.Type
for _, U := range allNamed {
if isInterface(T) {
if msets.MethodSet(T).Len() == 0 {
continue // empty interface
}
if isInterface(U) {
if msets.MethodSet(U).Len() == 0 {
continue // empty interface
}

// T interface, U interface
if !types.Identical(T, U) {
if types.AssignableTo(U, T) {
to = append(to, U)
}
if types.AssignableTo(T, U) {
from = append(from, U)
}
}
} else {
// T interface, U concrete
if types.AssignableTo(U, T) {
to = append(to, U)
} else if pU := types.NewPointer(U); types.AssignableTo(pU, T) {
to = append(to, pU)
}
}
} else if isInterface(U) {
if msets.MethodSet(U).Len() == 0 {
continue // empty interface
}

// T concrete, U interface
if types.AssignableTo(T, U) {
from = append(from, U)
} else if pT := types.NewPointer(T); types.AssignableTo(pT, U) {
fromPtr = append(fromPtr, U)
}
}
}

// Sort types (arbitrarily) to ensure test determinism.
sort.Sort(typesByString(to))
sort.Sort(typesByString(from))
sort.Sort(typesByString(fromPtr))

// TODO(matloob): Perhaps support calling implements on methods instead of just interface types,
// as guru does.

return implementsResult{to, from, fromPtr}, nil
}

// implementsResult contains the results of an implements query.
type implementsResult struct {
to []types.Type // named or ptr-to-named types assignable to interface T
from []types.Type // named interfaces assignable from T
fromPtr []types.Type // named interfaces assignable only from *T
}

type typesByString []types.Type

func (p typesByString) Len() int { return len(p) }
func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
36 changes: 36 additions & 0 deletions internal/lsp/source/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,42 @@ func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
}
}

func (r *runner) Implementation(t *testing.T, spn span.Span, m tests.Implementations) {
ctx := r.ctx
f, err := r.view.GetFile(ctx, m.Src.URI())
if err != nil {
t.Fatalf("failed for %v: %v", m.Src, err)
}
sm, err := r.data.Mapper(m.Src.URI())
if err != nil {
t.Fatal(err)
}
loc, err := sm.Location(m.Src)
if err != nil {
t.Fatalf("failed for %v: %v", m.Src, err)
}
var locs []protocol.Location
locs, err = source.Implementation(r.ctx, r.view, f, loc.Range.Start)
if err != nil {
t.Fatalf("failed for %v: %v", m.Src, err)
}
if len(locs) != len(m.Implementations) {
t.Fatalf("got %d locations for implementation, expected %d", len(locs), len(m.Implementations))
}
for i := range locs {
locURI := span.NewURI(locs[i].URI)
lm, err := r.data.Mapper(locURI)
if err != nil {
t.Fatal(err)
}
if imp, err := lm.Span(locs[i]); err != nil {
t.Fatalf("failed for %v: %v", locs[i], err)
} else if imp != m.Implementations[i] {
t.Errorf("for %dth implementation of %v got %v want %v", i, m.Src, imp, m.Implementations[i])
}
}
}

func (r *runner) Highlight(t *testing.T, name string, locations []span.Span) {
ctx := r.ctx
src := locations[0]
Expand Down
21 changes: 21 additions & 0 deletions internal/lsp/testdata/implementation/implementation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package implementation

type ImpP struct{} //@ImpP

func (*ImpP) Laugh() {

}

type ImpS struct{} //@ImpS

func (ImpS) Laugh() {

}

type ImpI interface { //@ImpI
Laugh()
}

type Laugher interface { //@implementations("augher", ImpP),implementations("augher", ImpI),implementations("augher", ImpS),
Laugh()
}
Loading

0 comments on commit 02d0efc

Please sign in to comment.