1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:14:46 -07:00

internal/lsp: add label completion candidates

Now we offer completion candidates for labels when completing "break",
"continue", and "goto" statements. We are reasonably smart about
filtering unusable labels, except we don't filter "goto" candidates
that jump across variable definitions.

Fixes golang/go#33987.

Change-Id: If296a7579845aba5d86c7050ab195c35d4b147ed
Reviewed-on: https://go-review.googlesource.com/c/tools/+/197417
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Muir Manders 2019-09-18 12:26:39 -07:00 committed by Rebecca Stambler
parent f6a1a6ff8e
commit 5bac78f585
5 changed files with 186 additions and 11 deletions

View File

@ -163,8 +163,9 @@ type completer struct {
// candidate to be. It will be the zero value if no information is available.
expectedType typeInference
// enclosingFunction is the function declaration enclosing the position.
enclosingFunction *types.Signature
// enclosingFunc contains information about the function enclosing
// the position.
enclosingFunc *funcInfo
// enclosingCompositeLiteral contains information about the composite literal
// enclosing the position.
@ -189,6 +190,15 @@ 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
@ -434,7 +444,6 @@ func Completion(ctx context.Context, view View, f File, pos protocol.Position, o
return nil, nil, nil
}
clInfo := enclosingCompositeLiteral(path, rng.Start, pkg.GetTypesInfo())
c := &completer{
pkg: pkg,
snapshot: snapshot,
@ -446,8 +455,8 @@ func Completion(ctx context.Context, view View, f File, pos protocol.Position, o
path: path,
pos: rng.Start,
seen: make(map[types.Object]bool),
enclosingFunction: enclosingFunction(path, rng.Start, pkg.GetTypesInfo()),
enclosingCompositeLiteral: clInfo,
enclosingFunc: enclosingFunction(path, rng.Start, pkg.GetTypesInfo()),
enclosingCompositeLiteral: enclosingCompositeLiteral(path, rng.Start, pkg.GetTypesInfo()),
opts: opts,
// default to a matcher that always matches
matcher: prefixMatcher(""),
@ -476,6 +485,11 @@ func Completion(ctx context.Context, view View, f File, pos protocol.Position, o
return c.items, c.getSurrounding(), nil
}
if lt := c.wantLabelCompletion(); lt != labelNone {
c.labels(lt)
return c.items, c.getSurrounding(), nil
}
switch n := path[0].(type) {
case *ast.Ident:
// Is this the Sel part of a selector?
@ -847,17 +861,24 @@ func enclosingCompositeLiteral(path []ast.Node, pos token.Pos, info *types.Info)
return nil
}
// enclosingFunction returns the signature of the function enclosing the given position.
func enclosingFunction(path []ast.Node, pos token.Pos, info *types.Info) *types.Signature {
// enclosingFunction returns the signature and body of the function
// enclosing the given position.
func enclosingFunction(path []ast.Node, pos token.Pos, info *types.Info) *funcInfo {
for _, node := range path {
switch t := node.(type) {
case *ast.FuncDecl:
if obj, ok := info.Defs[t.Name]; ok {
return obj.Type().(*types.Signature)
return &funcInfo{
sig: obj.Type().(*types.Signature),
body: t.Body,
}
}
case *ast.FuncLit:
if typ, ok := info.Types[t]; ok {
return typ.Type.(*types.Signature)
return &funcInfo{
sig: typ.Type.(*types.Signature),
body: t.Body,
}
}
}
}
@ -1017,7 +1038,8 @@ Nodes:
}
return typeInference{}
case *ast.ReturnStmt:
if sig := c.enclosingFunction; sig != nil {
if c.enclosingFunc != nil {
sig := c.enclosingFunc.sig
// Find signature result that corresponds to our return statement.
if resultIdx := indexExprAtPos(c.pos, node.Results); resultIdx < len(node.Results) {
if resultIdx < sig.Results().Len() {

View File

@ -83,6 +83,9 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
case *types.PkgName:
kind = protocol.ModuleCompletion
detail = fmt.Sprintf("%q", obj.Imported().Path())
case *types.Label:
kind = protocol.ConstantCompletion
detail = "label"
}
// If this candidate needs an additional import statement,

View File

@ -0,0 +1,111 @@
// 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 (
"go/ast"
"go/token"
)
type labelType int
const (
labelNone labelType = iota
labelBreak
labelContinue
labelGoto
)
// wantLabelCompletion returns true if we want (only) label
// completions at the position.
func (c *completer) wantLabelCompletion() labelType {
if _, ok := c.path[0].(*ast.Ident); ok && len(c.path) > 1 {
// We want a label if we are an *ast.Ident child of a statement
// that accepts a label, e.g. "break Lo<>".
return takesLabel(c.path[1])
}
return labelNone
}
// takesLabel returns the corresponding labelType if n is a statement
// that accepts a label, otherwise labelNone.
func takesLabel(n ast.Node) labelType {
if bs, ok := n.(*ast.BranchStmt); ok {
switch bs.Tok {
case token.BREAK:
return labelBreak
case token.CONTINUE:
return labelContinue
case token.GOTO:
return labelGoto
}
}
return labelNone
}
// labels adds completion items for labels defined in the enclosing
// function.
func (c *completer) labels(lt labelType) {
if c.enclosingFunc == nil {
return
}
addLabel := func(l *ast.LabeledStmt) {
labelObj := c.pkg.GetTypesInfo().ObjectOf(l.Label)
if labelObj != nil {
c.found(labelObj, highScore, nil)
}
}
switch lt {
case labelBreak, labelContinue:
// "break" and "continue" only accept labels from enclosing statements.
for _, p := range c.path {
switch p := p.(type) {
case *ast.FuncLit:
// Labels are function scoped, so don't continue out of functions.
return
case *ast.LabeledStmt:
switch p.Stmt.(type) {
case *ast.ForStmt:
// Loop labels can be used for "break" or "continue".
addLabel(p)
case *ast.SwitchStmt, *ast.SelectStmt:
// Switch and select labels can be used only for "break".
if lt == labelBreak {
addLabel(p)
}
}
}
}
case labelGoto:
// 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 {
if n == nil {
return false
}
switch n := n.(type) {
// Only search into block-like nodes enclosing our "goto".
// This prevents us from finding labels in nested blocks.
case *ast.BlockStmt, *ast.CommClause, *ast.CaseClause:
for _, p := range c.path {
if n == p {
return true
}
}
return false
case *ast.LabeledStmt:
addLabel(n)
}
return true
})
}
}

39
internal/lsp/testdata/labels/labels.go vendored Normal file
View File

@ -0,0 +1,39 @@
package labels
func _() {
goto F //@complete(" //", label1, label4)
Foo1: //@item(label1, "Foo1", "label", "const")
for {
Foo2: //@item(label2, "Foo2", "label", "const")
switch {
case true:
break F //@complete(" //", label2, label1)
continue F //@complete(" //", label1)
{
FooUnjumpable:
}
goto F //@complete(" //", label1, label2, label4)
func() {
goto F //@complete(" //", label3)
break F //@complete(" //")
continue F //@complete(" //")
Foo3: //@item(label3, "Foo3", "label", "const")
}()
}
}
break F //@complete(" //")
continue F //@complete(" //")
Foo4: //@item(label4, "Foo4", "label", "const")
return
}

View File

@ -1,5 +1,5 @@
-- summary --
CompletionsCount = 169
CompletionsCount = 178
CompletionSnippetCount = 36
UnimportedCompletionsCount = 1
DeepCompletionsCount = 5