Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / internal / lsp / source / completion / statements.go
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.
4
5 package completion
6
7 import (
8         "fmt"
9         "go/ast"
10         "go/token"
11         "go/types"
12
13         "golang.org/x/tools/internal/lsp/protocol"
14         "golang.org/x/tools/internal/lsp/snippet"
15         "golang.org/x/tools/internal/lsp/source"
16 )
17
18 // addStatementCandidates adds full statement completion candidates
19 // appropriate for the current context.
20 func (c *completer) addStatementCandidates() {
21         c.addErrCheckAndReturn()
22         c.addAssignAppend()
23 }
24
25 // addAssignAppend offers a completion candidate of the form:
26 //
27 //     someSlice = append(someSlice, )
28 //
29 // It will offer the "append" completion in two situations:
30 //
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, )".
34 //
35 // Or
36 //
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() {
41         if len(c.path) < 3 {
42                 return
43         }
44
45         ident, _ := c.path[0].(*ast.Ident)
46         if ident == nil {
47                 return
48         }
49
50         var (
51                 // sliceText is the full name of our slice object, e.g. "s.abc" in
52                 // "s.abc = app<>".
53                 sliceText string
54                 // needsLHS is true if we need to prepend the LHS slice name and
55                 // "=" to our candidate.
56                 needsLHS = false
57                 fset     = c.snapshot.FileSet()
58         )
59
60         switch n := c.path[1].(type) {
61         case *ast.AssignStmt:
62                 // We are already in an assignment. Make sure our prefix matches "append".
63                 if c.matcher.Score("append") <= 0 {
64                         return
65                 }
66
67                 exprIdx := exprAtPos(c.pos, n.Rhs)
68                 if exprIdx == len(n.Rhs) || exprIdx > len(n.Lhs)-1 {
69                         return
70                 }
71
72                 lhsType := c.pkg.GetTypesInfo().TypeOf(n.Lhs[exprIdx])
73                 if lhsType == nil {
74                         return
75                 }
76
77                 // Make sure our corresponding LHS object is a slice.
78                 if _, isSlice := lhsType.Underlying().(*types.Slice); !isSlice {
79                         return
80                 }
81
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 {
87                         return
88                 }
89
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) + "."
94                 needsLHS = true
95         case *ast.ExprStmt:
96                 needsLHS = true
97         default:
98                 return
99         }
100
101         var (
102                 label string
103                 snip  snippet.Builder
104                 score = highScore
105         )
106
107         if needsLHS {
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 {
112                         return
113                 }
114
115                 if _, isSlice := bestItem.obj.Type().Underlying().(*types.Slice); !isSlice {
116                         return
117                 }
118
119                 // Don't rank the full form assign + append candidate above the
120                 // slice itself.
121                 score = bestItem.Score - 0.01
122
123                 // Fill in rest of sliceText now that we have the object name.
124                 sliceText += bestItem.Label
125
126                 // Fill in the candidate's LHS bits.
127                 label = fmt.Sprintf("%s = ", bestItem.Label)
128                 snip.WriteText(label)
129         }
130
131         snip.WriteText(fmt.Sprintf("append(%s, ", sliceText))
132         snip.WritePlaceholder(nil)
133         snip.WriteText(")")
134
135         c.items = append(c.items, CompletionItem{
136                 Label:   label + fmt.Sprintf("append(%s, )", sliceText),
137                 Kind:    protocol.FunctionCompletion,
138                 Score:   score,
139                 snippet: &snip,
140         })
141 }
142
143 // topCandidate returns the strictly highest scoring candidate
144 // collected so far. If the top two candidates have the same score,
145 // nil is returned.
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]
153                 }
154         }
155
156         // If secondBestItem has the same score, bestItem isn't
157         // the strict best.
158         if secondBestItem != nil && secondBestItem.Score == bestItem.Score {
159                 return nil
160         }
161
162         return bestItem
163 }
164
165 // addErrCheckAndReturn offers a completion candidate of the form:
166 //
167 //     if err != nil {
168 //       return nil, err
169 //     }
170 //
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 {
177                 return
178         }
179
180         var (
181                 errorType = types.Universe.Lookup("error").Type()
182                 result    = c.enclosingFunc.sig.Results()
183         )
184         // Make sure our enclosing function returns an error.
185         if result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType) {
186                 return
187         }
188
189         prevLine := prevStmt(c.pos, c.path)
190         if prevLine == nil {
191                 return
192         }
193
194         // Make sure our preceding statement was as assignment.
195         assign, _ := prevLine.(*ast.AssignStmt)
196         if assign == nil || len(assign.Lhs) == 0 {
197                 return
198         }
199
200         lastAssignee := assign.Lhs[len(assign.Lhs)-1]
201
202         // Make sure the final assignee is an error.
203         if !types.Identical(c.pkg.GetTypesInfo().TypeOf(lastAssignee), errorType) {
204                 return
205         }
206
207         var (
208                 // errText is e.g. "err" in "foo, err := bar()".
209                 errText = source.FormatNode(c.snapshot.FileSet(), lastAssignee)
210
211                 // Whether we need to include the "if" keyword in our candidate.
212                 needsIf = true
213         )
214
215         // "_" isn't a real object.
216         if errText == "_" {
217                 return
218         }
219
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
222         // statement.
223         switch n := c.path[0].(type) {
224         case *ast.Ident:
225                 switch c.path[1].(type) {
226                 case *ast.ExprStmt:
227                         // This handles:
228                         //
229                         //     f, err := os.Open("foo")
230                         //     i<>
231
232                         // Make sure they are typing "if".
233                         if c.matcher.Score("if") <= 0 {
234                                 return
235                         }
236                 case *ast.IfStmt:
237                         // This handles:
238                         //
239                         //     f, err := os.Open("foo")
240                         //     if er<>
241
242                         // Make sure they are typing the error's name.
243                         if c.matcher.Score(errText) <= 0 {
244                                 return
245                         }
246
247                         needsIf = false
248                 default:
249                         return
250                 }
251         case *ast.IfStmt:
252                 // This handles:
253                 //
254                 //     f, err := os.Open("foo")
255                 //     if <>
256
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 {
261                         return
262                 }
263
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"))
267         }
268
269         // Build up a snippet that looks like:
270         //
271         //     if err != nil {
272         //       return <zero value>, ..., ${1:err}
273         //     }
274         //
275         // We make the error a placeholder so it is easy to alter the error.
276         var snip snippet.Builder
277         if needsIf {
278                 snip.WriteText("if ")
279         }
280         snip.WriteText(fmt.Sprintf("%s != nil {\n\treturn ", errText))
281
282         for i := 0; i < result.Len()-1; i++ {
283                 snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf))
284                 snip.WriteText(", ")
285         }
286
287         snip.WritePlaceholder(func(b *snippet.Builder) {
288                 b.WriteText(errText)
289         })
290
291         snip.WriteText("\n}")
292
293         label := fmt.Sprintf("%[1]s != nil { return %[1]s }", errText)
294         if needsIf {
295                 label = "if " + label
296         }
297
298         c.items = append(c.items, CompletionItem{
299                 Label: label,
300                 // There doesn't seem to be a more appropriate kind.
301                 Kind:    protocol.KeywordCompletion,
302                 Score:   highScore,
303                 snippet: &snip,
304         })
305 }