blob: 3c544129e3fd54b5a187643bc174d31daaf8713e [file] [log] [blame]
// 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 completion
import (
"context"
"go/ast"
"go/token"
"math"
)
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(ctx context.Context, lt labelType) {
if c.enclosingFunc == nil {
return
}
addLabel := func(score float64, l *ast.LabeledStmt) {
labelObj := c.pkg.GetTypesInfo().ObjectOf(l.Label)
if labelObj != nil {
c.found(ctx, candidate{obj: labelObj, score: score})
}
}
switch lt {
case labelBreak, labelContinue:
// "break" and "continue" only accept labels from enclosing statements.
for i, 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, *ast.RangeStmt:
// Loop labels can be used for "break" or "continue".
addLabel(highScore*math.Pow(.99, float64(i)), p)
case *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
// Switch and select labels can be used only for "break".
if lt == labelBreak {
addLabel(highScore*math.Pow(.99, float64(i)), 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(highScore, n)
}
return true
})
}
}