// 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 ( "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(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.deepState.enqueue(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 }) } }