golang.org/x/tools/gopls@v0.15.3/internal/golang/completion/statements.go (about) 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 "strings" 13 14 "golang.org/x/tools/gopls/internal/cache" 15 "golang.org/x/tools/gopls/internal/golang" 16 "golang.org/x/tools/gopls/internal/golang/completion/snippet" 17 "golang.org/x/tools/gopls/internal/protocol" 18 ) 19 20 // addStatementCandidates adds full statement completion candidates 21 // appropriate for the current context. 22 func (c *completer) addStatementCandidates() { 23 c.addErrCheck() 24 c.addAssignAppend() 25 c.addReturnZeroValues() 26 } 27 28 // addAssignAppend offers a completion candidate of the form: 29 // 30 // someSlice = append(someSlice, ) 31 // 32 // It will offer the "append" completion in either of two situations: 33 // 34 // 1. Position is in RHS of assign, prefix matches "append", and 35 // corresponding LHS object is a slice. For example, 36 // "foo = ap<>" completes to "foo = append(foo, )". 37 // 38 // 2. Prefix is an ident or selector in an *ast.ExprStmt (i.e. 39 // beginning of statement), and our best matching candidate is a 40 // slice. For example: "foo.ba" completes to "foo.bar = append(foo.bar, )". 41 func (c *completer) addAssignAppend() { 42 if len(c.path) < 3 { 43 return 44 } 45 46 ident, _ := c.path[0].(*ast.Ident) 47 if ident == nil { 48 return 49 } 50 51 var ( 52 // sliceText is the full name of our slice object, e.g. "s.abc" in 53 // "s.abc = app<>". 54 sliceText string 55 // needsLHS is true if we need to prepend the LHS slice name and 56 // "=" to our candidate. 57 needsLHS = false 58 fset = c.pkg.FileSet() 59 ) 60 61 switch n := c.path[1].(type) { 62 case *ast.AssignStmt: 63 // We are already in an assignment. Make sure our prefix matches "append". 64 if c.matcher.Score("append") <= 0 { 65 return 66 } 67 68 exprIdx := exprAtPos(c.pos, n.Rhs) 69 if exprIdx == len(n.Rhs) || exprIdx > len(n.Lhs)-1 { 70 return 71 } 72 73 lhsType := c.pkg.GetTypesInfo().TypeOf(n.Lhs[exprIdx]) 74 if lhsType == nil { 75 return 76 } 77 78 // Make sure our corresponding LHS object is a slice. 79 if _, isSlice := lhsType.Underlying().(*types.Slice); !isSlice { 80 return 81 } 82 83 // The name or our slice is whatever's in the LHS expression. 84 sliceText = golang.FormatNode(fset, n.Lhs[exprIdx]) 85 case *ast.SelectorExpr: 86 // Make sure we are a selector at the beginning of a statement. 87 if _, parentIsExprtStmt := c.path[2].(*ast.ExprStmt); !parentIsExprtStmt { 88 return 89 } 90 91 // So far we only know the first part of our slice name. For 92 // example in "s.a<>" we only know our slice begins with "s." 93 // since the user could still be typing. 94 sliceText = golang.FormatNode(fset, n.X) + "." 95 needsLHS = true 96 case *ast.ExprStmt: 97 needsLHS = true 98 default: 99 return 100 } 101 102 var ( 103 label string 104 snip snippet.Builder 105 score = highScore 106 ) 107 108 if needsLHS { 109 // Offer the long form assign + append candidate if our best 110 // candidate is a slice. 111 bestItem := c.topCandidate() 112 if bestItem == nil || !bestItem.isSlice { 113 return 114 } 115 116 // Don't rank the full form assign + append candidate above the 117 // slice itself. 118 score = bestItem.Score - 0.01 119 120 // Fill in rest of sliceText now that we have the object name. 121 sliceText += bestItem.Label 122 123 // Fill in the candidate's LHS bits. 124 label = fmt.Sprintf("%s = ", bestItem.Label) 125 snip.WriteText(label) 126 } 127 128 snip.WriteText(fmt.Sprintf("append(%s, ", sliceText)) 129 snip.WritePlaceholder(nil) 130 snip.WriteText(")") 131 132 c.items = append(c.items, CompletionItem{ 133 Label: label + fmt.Sprintf("append(%s, )", sliceText), 134 Kind: protocol.FunctionCompletion, 135 Score: score, 136 snippet: &snip, 137 }) 138 } 139 140 // topCandidate returns the strictly highest scoring candidate 141 // collected so far. If the top two candidates have the same score, 142 // nil is returned. 143 func (c *completer) topCandidate() *CompletionItem { 144 var bestItem, secondBestItem *CompletionItem 145 for i := range c.items { 146 if bestItem == nil || c.items[i].Score > bestItem.Score { 147 bestItem = &c.items[i] 148 } else if secondBestItem == nil || c.items[i].Score > secondBestItem.Score { 149 secondBestItem = &c.items[i] 150 } 151 } 152 153 // If secondBestItem has the same score, bestItem isn't 154 // the strict best. 155 if secondBestItem != nil && secondBestItem.Score == bestItem.Score { 156 return nil 157 } 158 159 return bestItem 160 } 161 162 // addErrCheck offers a completion candidate of the form: 163 // 164 // if err != nil { 165 // return nil, err 166 // } 167 // 168 // In the case of test functions, it offers a completion candidate of the form: 169 // 170 // if err != nil { 171 // t.Fatal(err) 172 // } 173 // 174 // The position must be in a function that returns an error, and the 175 // statement preceding the position must be an assignment where the 176 // final LHS object is an error. addErrCheck will synthesize 177 // zero values as necessary to make the return statement valid. 178 func (c *completer) addErrCheck() { 179 if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders { 180 return 181 } 182 183 var ( 184 errorType = types.Universe.Lookup("error").Type() 185 result = c.enclosingFunc.sig.Results() 186 testVar = getTestVar(c.enclosingFunc, c.pkg) 187 isTest = testVar != "" 188 doesNotReturnErr = result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType) 189 ) 190 // Make sure our enclosing function is a Test func or returns an error. 191 if !isTest && doesNotReturnErr { 192 return 193 } 194 195 prevLine := prevStmt(c.pos, c.path) 196 if prevLine == nil { 197 return 198 } 199 200 // Make sure our preceding statement was as assignment. 201 assign, _ := prevLine.(*ast.AssignStmt) 202 if assign == nil || len(assign.Lhs) == 0 { 203 return 204 } 205 206 lastAssignee := assign.Lhs[len(assign.Lhs)-1] 207 208 // Make sure the final assignee is an error. 209 if !types.Identical(c.pkg.GetTypesInfo().TypeOf(lastAssignee), errorType) { 210 return 211 } 212 213 var ( 214 // errVar is e.g. "err" in "foo, err := bar()". 215 errVar = golang.FormatNode(c.pkg.FileSet(), lastAssignee) 216 217 // Whether we need to include the "if" keyword in our candidate. 218 needsIf = true 219 ) 220 221 // If the returned error from the previous statement is "_", it is not a real object. 222 // If we don't have an error, and the function signature takes a testing.TB that is either ignored 223 // or an "_", then we also can't call t.Fatal(err). 224 if errVar == "_" { 225 return 226 } 227 228 // Below we try to detect if the user has already started typing "if 229 // err" so we can replace what they've typed with our complete 230 // statement. 231 switch n := c.path[0].(type) { 232 case *ast.Ident: 233 switch c.path[1].(type) { 234 case *ast.ExprStmt: 235 // This handles: 236 // 237 // f, err := os.Open("foo") 238 // i<> 239 240 // Make sure they are typing "if". 241 if c.matcher.Score("if") <= 0 { 242 return 243 } 244 case *ast.IfStmt: 245 // This handles: 246 // 247 // f, err := os.Open("foo") 248 // if er<> 249 250 // Make sure they are typing the error's name. 251 if c.matcher.Score(errVar) <= 0 { 252 return 253 } 254 255 needsIf = false 256 default: 257 return 258 } 259 case *ast.IfStmt: 260 // This handles: 261 // 262 // f, err := os.Open("foo") 263 // if <> 264 265 // Avoid false positives by ensuring the if's cond is a bad 266 // expression. For example, don't offer the completion in cases 267 // like "if <> somethingElse". 268 if _, bad := n.Cond.(*ast.BadExpr); !bad { 269 return 270 } 271 272 // If "if" is our direct prefix, we need to include it in our 273 // candidate since the existing "if" will be overwritten. 274 needsIf = c.pos == n.Pos()+token.Pos(len("if")) 275 } 276 277 // Build up a snippet that looks like: 278 // 279 // if err != nil { 280 // return <zero value>, ..., ${1:err} 281 // } 282 // 283 // We make the error a placeholder so it is easy to alter the error. 284 var snip snippet.Builder 285 if needsIf { 286 snip.WriteText("if ") 287 } 288 snip.WriteText(fmt.Sprintf("%s != nil {\n\t", errVar)) 289 290 var label string 291 if isTest { 292 snip.WriteText(fmt.Sprintf("%s.Fatal(%s)", testVar, errVar)) 293 label = fmt.Sprintf("%[1]s != nil { %[2]s.Fatal(%[1]s) }", errVar, testVar) 294 } else { 295 snip.WriteText("return ") 296 for i := 0; i < result.Len()-1; i++ { 297 snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf)) 298 snip.WriteText(", ") 299 } 300 snip.WritePlaceholder(func(b *snippet.Builder) { 301 b.WriteText(errVar) 302 }) 303 label = fmt.Sprintf("%[1]s != nil { return %[1]s }", errVar) 304 } 305 306 snip.WriteText("\n}") 307 308 if needsIf { 309 label = "if " + label 310 } 311 312 c.items = append(c.items, CompletionItem{ 313 Label: label, 314 Kind: protocol.SnippetCompletion, 315 Score: highScore, 316 snippet: &snip, 317 }) 318 } 319 320 // getTestVar checks the function signature's input parameters and returns 321 // the name of the first parameter that implements "testing.TB". For example, 322 // func someFunc(t *testing.T) returns the string "t", func someFunc(b *testing.B) 323 // returns "b" etc. An empty string indicates that the function signature 324 // does not take a testing.TB parameter or does so but is ignored such 325 // as func someFunc(*testing.T). 326 func getTestVar(enclosingFunc *funcInfo, pkg *cache.Package) string { 327 if enclosingFunc == nil || enclosingFunc.sig == nil { 328 return "" 329 } 330 331 var testingPkg *types.Package 332 for _, p := range pkg.GetTypes().Imports() { 333 if p.Path() == "testing" { 334 testingPkg = p 335 break 336 } 337 } 338 if testingPkg == nil { 339 return "" 340 } 341 tbObj := testingPkg.Scope().Lookup("TB") 342 if tbObj == nil { 343 return "" 344 } 345 iface, ok := tbObj.Type().Underlying().(*types.Interface) 346 if !ok { 347 return "" 348 } 349 350 sig := enclosingFunc.sig 351 for i := 0; i < sig.Params().Len(); i++ { 352 param := sig.Params().At(i) 353 if param.Name() == "_" { 354 continue 355 } 356 if !types.Implements(param.Type(), iface) { 357 continue 358 } 359 return param.Name() 360 } 361 362 return "" 363 } 364 365 // addReturnZeroValues offers a snippet candidate on the form: 366 // 367 // return 0, "", nil 368 // 369 // Requires a partially or fully written return keyword at position. 370 // Requires current position to be in a function with more than 371 // zero return parameters. 372 func (c *completer) addReturnZeroValues() { 373 if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders { 374 return 375 } 376 result := c.enclosingFunc.sig.Results() 377 if result.Len() == 0 { 378 return 379 } 380 381 // Offer just less than we expect from return as a keyword. 382 var score = stdScore - 0.01 383 switch c.path[0].(type) { 384 case *ast.ReturnStmt, *ast.Ident: 385 f := c.matcher.Score("return") 386 if f <= 0 { 387 return 388 } 389 score *= float64(f) 390 default: 391 return 392 } 393 394 // The snippet will have a placeholder over each return value. 395 // The label will not. 396 var snip snippet.Builder 397 var label strings.Builder 398 snip.WriteText("return ") 399 fmt.Fprintf(&label, "return ") 400 401 for i := 0; i < result.Len(); i++ { 402 if i > 0 { 403 snip.WriteText(", ") 404 fmt.Fprintf(&label, ", ") 405 } 406 407 zero := formatZeroValue(result.At(i).Type(), c.qf) 408 snip.WritePlaceholder(func(b *snippet.Builder) { 409 b.WriteText(zero) 410 }) 411 fmt.Fprintf(&label, zero) 412 } 413 414 c.items = append(c.items, CompletionItem{ 415 Label: label.String(), 416 Kind: protocol.SnippetCompletion, 417 Score: score, 418 snippet: &snip, 419 }) 420 }