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:
parent
f6a1a6ff8e
commit
5bac78f585
@ -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() {
|
||||
|
@ -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,
|
||||
|
111
internal/lsp/source/completion_labels.go
Normal file
111
internal/lsp/source/completion_labels.go
Normal 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
39
internal/lsp/testdata/labels/labels.go
vendored
Normal 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
|
||||
}
|
2
internal/lsp/testdata/summary.txt.golden
vendored
2
internal/lsp/testdata/summary.txt.golden
vendored
@ -1,5 +1,5 @@
|
||||
-- summary --
|
||||
CompletionsCount = 169
|
||||
CompletionsCount = 178
|
||||
CompletionSnippetCount = 36
|
||||
UnimportedCompletionsCount = 1
|
||||
DeepCompletionsCount = 5
|
||||
|
Loading…
Reference in New Issue
Block a user