Skip to content

Commit

Permalink
gopls,internal/lsp: Implement method stubbing via CodeAction
Browse files Browse the repository at this point in the history
This CL adds a quickfix CodeAction that detects "missing method"
compiler errors and suggests adding method stubs to the concrete
type that would implement the interface. There are many ways that
a user might indicate a concrete type is meant to be used as an interface.
This PR detects two types of those errors: variable declaration and function returns.
For variable declarations, things like the following should be detected:
1. var _ SomeInterface = SomeType{}
2. var _ = SomeInterface(SomeType{})
3. var _ SomeInterface = (*SomeType)(nil)
For function returns, the following example is the primary detection:
func newIface() SomeInterface {
	return &SomeType{}
}
More detections can be added in the future of course.

Fixes golang/go#37537

Change-Id: Ibb7784622184c9885eff2ccc786767682876b4d3
  • Loading branch information
marwan-at-work committed Dec 1, 2020
1 parent fd09bd9 commit c3bd206
Show file tree
Hide file tree
Showing 22 changed files with 1,464 additions and 39 deletions.
5 changes: 5 additions & 0 deletions internal/lsp/code_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
return nil, nil
}
diagnostics := params.Context.Diagnostics
implActions, err := source.MethodStubActions(ctx, diagnostics, snapshot, fh)
if err != nil {
return nil, fmt.Errorf("implActions: %w", err)
}
codeActions = append(codeActions, implActions...)

// First, process any missing imports and pair them with the
// diagnostics they fix.
Expand Down
39 changes: 3 additions & 36 deletions internal/lsp/source/completion/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ type completer struct {

// enclosingFunc contains information about the function enclosing
// the position.
enclosingFunc *funcInfo
enclosingFunc *source.FuncInfo

// enclosingCompositeLiteral contains information about the composite literal
// enclosing the position.
Expand All @@ -219,15 +219,6 @@ type completer struct {
startTime time.Time
}

// funcInfo holds info about a function object.
type funcInfo struct {
// sig is the function declaration enclosing the position.
sig *types.Signature

// body is the function's body.
body *ast.BlockStmt
}

type compLitInfo struct {
// cl is the *ast.CompositeLit enclosing the position.
cl *ast.CompositeLit
Expand Down Expand Up @@ -506,7 +497,7 @@ func Completion(ctx context.Context, snapshot source.Snapshot, fh source.FileHan
path: path,
pos: pos,
seen: make(map[types.Object]bool),
enclosingFunc: enclosingFunction(path, pkg.GetTypesInfo()),
enclosingFunc: source.EnclosingFunction(path, pkg.GetTypesInfo()),
enclosingCompositeLiteral: enclosingCompositeLiteral(path, rng.Start, pkg.GetTypesInfo()),
deepState: deepCompletionState{
enabled: opts.DeepCompletion,
Expand Down Expand Up @@ -1646,30 +1637,6 @@ func enclosingCompositeLiteral(path []ast.Node, pos token.Pos, info *types.Info)
return nil
}

// enclosingFunction returns the signature and body of the function
// enclosing the given position.
func enclosingFunction(path []ast.Node, info *types.Info) *funcInfo {
for _, node := range path {
switch t := node.(type) {
case *ast.FuncDecl:
if obj, ok := info.Defs[t.Name]; ok {
return &funcInfo{
sig: obj.Type().(*types.Signature),
body: t.Body,
}
}
case *ast.FuncLit:
if typ, ok := info.Types[t]; ok {
return &funcInfo{
sig: typ.Type.(*types.Signature),
body: t.Body,
}
}
}
}
return nil
}

func (c *completer) expectedCompositeLiteralType() types.Type {
clInfo := c.enclosingCompositeLiteral
switch t := clInfo.clType.(type) {
Expand Down Expand Up @@ -1989,7 +1956,7 @@ Nodes:
}
case *ast.ReturnStmt:
if c.enclosingFunc != nil {
sig := c.enclosingFunc.sig
sig := c.enclosingFunc.Sig
// Find signature result that corresponds to our return statement.
if resultIdx := exprAtPos(c.pos, node.Results); resultIdx < len(node.Results) {
if resultIdx < sig.Results().Len() {
Expand Down
2 changes: 1 addition & 1 deletion internal/lsp/source/completion/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (c *completer) labels(lt labelType) {
// Goto accepts any label in the same function not in a nested
// block. It also doesn't take labels that would jump across
// variable definitions, but ignore that case for now.
ast.Inspect(c.enclosingFunc.body, func(n ast.Node) bool {
ast.Inspect(c.enclosingFunc.Body, func(n ast.Node) bool {
if n == nil {
return false
}
Expand Down
2 changes: 1 addition & 1 deletion internal/lsp/source/completion/statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func (c *completer) addErrCheckAndReturn() {

var (
errorType = types.Universe.Lookup("error").Type()
result = c.enclosingFunc.sig.Results()
result = c.enclosingFunc.Sig.Results()
)
// Make sure our enclosing function returns an error.
if result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType) {
Expand Down
Loading

0 comments on commit c3bd206

Please sign in to comment.