1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
13 "golang.org/x/tools/internal/lsp/protocol"
14 "golang.org/x/tools/internal/lsp/snippet"
15 "golang.org/x/tools/internal/lsp/source"
18 // addStatementCandidates adds full statement completion candidates
19 // appropriate for the current context.
20 func (c *completer) addStatementCandidates() {
21 c.addErrCheckAndReturn()
25 // addAssignAppend offers a completion candidate of the form:
27 // someSlice = append(someSlice, )
29 // It will offer the "append" completion in two situations:
31 // 1. Position is in RHS of assign, prefix matches "append", and
32 // corresponding LHS object is a slice. For example,
33 // "foo = ap<>" completes to "foo = append(foo, )".
37 // 2. Prefix is an ident or selector in an *ast.ExprStmt (i.e.
38 // beginning of statement), and our best matching candidate is a
39 // slice. For example: "foo.ba" completes to "foo.bar = append(foo.bar, )".
40 func (c *completer) addAssignAppend() {
45 ident, _ := c.path[0].(*ast.Ident)
51 // sliceText is the full name of our slice object, e.g. "s.abc" in
54 // needsLHS is true if we need to prepend the LHS slice name and
55 // "=" to our candidate.
57 fset = c.snapshot.FileSet()
60 switch n := c.path[1].(type) {
62 // We are already in an assignment. Make sure our prefix matches "append".
63 if c.matcher.Score("append") <= 0 {
67 exprIdx := exprAtPos(c.pos, n.Rhs)
68 if exprIdx == len(n.Rhs) || exprIdx > len(n.Lhs)-1 {
72 lhsType := c.pkg.GetTypesInfo().TypeOf(n.Lhs[exprIdx])
77 // Make sure our corresponding LHS object is a slice.
78 if _, isSlice := lhsType.Underlying().(*types.Slice); !isSlice {
82 // The name or our slice is whatever's in the LHS expression.
83 sliceText = source.FormatNode(fset, n.Lhs[exprIdx])
84 case *ast.SelectorExpr:
85 // Make sure we are a selector at the beginning of a statement.
86 if _, parentIsExprtStmt := c.path[2].(*ast.ExprStmt); !parentIsExprtStmt {
90 // So far we only know the first part of our slice name. For
91 // example in "s.a<>" we only know our slice begins with "s."
92 // since the user could still be typing.
93 sliceText = source.FormatNode(fset, n.X) + "."
108 // Offer the long form assign + append candidate if our best
109 // candidate is a slice.
110 bestItem := c.topCandidate()
111 if bestItem == nil || bestItem.obj == nil || bestItem.obj.Type() == nil {
115 if _, isSlice := bestItem.obj.Type().Underlying().(*types.Slice); !isSlice {
119 // Don't rank the full form assign + append candidate above the
121 score = bestItem.Score - 0.01
123 // Fill in rest of sliceText now that we have the object name.
124 sliceText += bestItem.Label
126 // Fill in the candidate's LHS bits.
127 label = fmt.Sprintf("%s = ", bestItem.Label)
128 snip.WriteText(label)
131 snip.WriteText(fmt.Sprintf("append(%s, ", sliceText))
132 snip.WritePlaceholder(nil)
135 c.items = append(c.items, CompletionItem{
136 Label: label + fmt.Sprintf("append(%s, )", sliceText),
137 Kind: protocol.FunctionCompletion,
143 // topCandidate returns the strictly highest scoring candidate
144 // collected so far. If the top two candidates have the same score,
146 func (c *completer) topCandidate() *CompletionItem {
147 var bestItem, secondBestItem *CompletionItem
148 for i := range c.items {
149 if bestItem == nil || c.items[i].Score > bestItem.Score {
150 bestItem = &c.items[i]
151 } else if secondBestItem == nil || c.items[i].Score > secondBestItem.Score {
152 secondBestItem = &c.items[i]
156 // If secondBestItem has the same score, bestItem isn't
158 if secondBestItem != nil && secondBestItem.Score == bestItem.Score {
165 // addErrCheckAndReturn offers a completion candidate of the form:
171 // The position must be in a function that returns an error, and the
172 // statement preceding the position must be an assignment where the
173 // final LHS object is an error. addErrCheckAndReturn will synthesize
174 // zero values as necessary to make the return statement valid.
175 func (c *completer) addErrCheckAndReturn() {
176 if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders {
181 errorType = types.Universe.Lookup("error").Type()
182 result = c.enclosingFunc.sig.Results()
184 // Make sure our enclosing function returns an error.
185 if result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType) {
189 prevLine := prevStmt(c.pos, c.path)
194 // Make sure our preceding statement was as assignment.
195 assign, _ := prevLine.(*ast.AssignStmt)
196 if assign == nil || len(assign.Lhs) == 0 {
200 lastAssignee := assign.Lhs[len(assign.Lhs)-1]
202 // Make sure the final assignee is an error.
203 if !types.Identical(c.pkg.GetTypesInfo().TypeOf(lastAssignee), errorType) {
208 // errText is e.g. "err" in "foo, err := bar()".
209 errText = source.FormatNode(c.snapshot.FileSet(), lastAssignee)
211 // Whether we need to include the "if" keyword in our candidate.
215 // "_" isn't a real object.
220 // Below we try to detect if the user has already started typing "if
221 // err" so we can replace what they've typed with our complete
223 switch n := c.path[0].(type) {
225 switch c.path[1].(type) {
229 // f, err := os.Open("foo")
232 // Make sure they are typing "if".
233 if c.matcher.Score("if") <= 0 {
239 // f, err := os.Open("foo")
242 // Make sure they are typing the error's name.
243 if c.matcher.Score(errText) <= 0 {
254 // f, err := os.Open("foo")
257 // Avoid false positives by ensuring the if's cond is a bad
258 // expression. For example, don't offer the completion in cases
259 // like "if <> somethingElse".
260 if _, bad := n.Cond.(*ast.BadExpr); !bad {
264 // If "if" is our direct prefix, we need to include it in our
265 // candidate since the existing "if" will be overwritten.
266 needsIf = c.pos == n.Pos()+token.Pos(len("if"))
269 // Build up a snippet that looks like:
272 // return <zero value>, ..., ${1:err}
275 // We make the error a placeholder so it is easy to alter the error.
276 var snip snippet.Builder
278 snip.WriteText("if ")
280 snip.WriteText(fmt.Sprintf("%s != nil {\n\treturn ", errText))
282 for i := 0; i < result.Len()-1; i++ {
283 snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf))
287 snip.WritePlaceholder(func(b *snippet.Builder) {
291 snip.WriteText("\n}")
293 label := fmt.Sprintf("%[1]s != nil { return %[1]s }", errText)
295 label = "if " + label
298 c.items = append(c.items, CompletionItem{
300 // There doesn't seem to be a more appropriate kind.
301 Kind: protocol.KeywordCompletion,