golang.org/x/tools/gopls@v0.15.3/internal/golang/extract.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 golang 6 7 import ( 8 "bytes" 9 "fmt" 10 "go/ast" 11 "go/format" 12 "go/parser" 13 "go/token" 14 "go/types" 15 "sort" 16 "strings" 17 "text/scanner" 18 19 "golang.org/x/tools/go/analysis" 20 "golang.org/x/tools/go/ast/astutil" 21 "golang.org/x/tools/gopls/internal/util/bug" 22 "golang.org/x/tools/gopls/internal/util/safetoken" 23 "golang.org/x/tools/internal/analysisinternal" 24 ) 25 26 func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { 27 tokFile := fset.File(file.Pos()) 28 expr, path, ok, err := CanExtractVariable(start, end, file) 29 if !ok { 30 return nil, nil, fmt.Errorf("extractVariable: cannot extract %s: %v", safetoken.StartPosition(fset, start), err) 31 } 32 33 // Create new AST node for extracted code. 34 var lhsNames []string 35 switch expr := expr.(type) { 36 // TODO: stricter rules for selectorExpr. 37 case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.SliceExpr, 38 *ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr: 39 lhsName, _ := generateAvailableIdentifier(expr.Pos(), path, pkg, info, "x", 0) 40 lhsNames = append(lhsNames, lhsName) 41 case *ast.CallExpr: 42 tup, ok := info.TypeOf(expr).(*types.Tuple) 43 if !ok { 44 // If the call expression only has one return value, we can treat it the 45 // same as our standard extract variable case. 46 lhsName, _ := generateAvailableIdentifier(expr.Pos(), path, pkg, info, "x", 0) 47 lhsNames = append(lhsNames, lhsName) 48 break 49 } 50 idx := 0 51 for i := 0; i < tup.Len(); i++ { 52 // Generate a unique variable for each return value. 53 var lhsName string 54 lhsName, idx = generateAvailableIdentifier(expr.Pos(), path, pkg, info, "x", idx) 55 lhsNames = append(lhsNames, lhsName) 56 } 57 default: 58 return nil, nil, fmt.Errorf("cannot extract %T", expr) 59 } 60 61 insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path) 62 if insertBeforeStmt == nil { 63 return nil, nil, fmt.Errorf("cannot find location to insert extraction") 64 } 65 indent, err := calculateIndentation(src, tokFile, insertBeforeStmt) 66 if err != nil { 67 return nil, nil, err 68 } 69 newLineIndent := "\n" + indent 70 71 lhs := strings.Join(lhsNames, ", ") 72 assignStmt := &ast.AssignStmt{ 73 Lhs: []ast.Expr{ast.NewIdent(lhs)}, 74 Tok: token.DEFINE, 75 Rhs: []ast.Expr{expr}, 76 } 77 var buf bytes.Buffer 78 if err := format.Node(&buf, fset, assignStmt); err != nil { 79 return nil, nil, err 80 } 81 assignment := strings.ReplaceAll(buf.String(), "\n", newLineIndent) + newLineIndent 82 83 return fset, &analysis.SuggestedFix{ 84 TextEdits: []analysis.TextEdit{ 85 { 86 Pos: insertBeforeStmt.Pos(), 87 End: insertBeforeStmt.Pos(), 88 NewText: []byte(assignment), 89 }, 90 { 91 Pos: start, 92 End: end, 93 NewText: []byte(lhs), 94 }, 95 }, 96 }, nil 97 } 98 99 // CanExtractVariable reports whether the code in the given range can be 100 // extracted to a variable. 101 func CanExtractVariable(start, end token.Pos, file *ast.File) (ast.Expr, []ast.Node, bool, error) { 102 if start == end { 103 return nil, nil, false, fmt.Errorf("start and end are equal") 104 } 105 path, _ := astutil.PathEnclosingInterval(file, start, end) 106 if len(path) == 0 { 107 return nil, nil, false, fmt.Errorf("no path enclosing interval") 108 } 109 for _, n := range path { 110 if _, ok := n.(*ast.ImportSpec); ok { 111 return nil, nil, false, fmt.Errorf("cannot extract variable in an import block") 112 } 113 } 114 node := path[0] 115 if start != node.Pos() || end != node.End() { 116 return nil, nil, false, fmt.Errorf("range does not map to an AST node") 117 } 118 expr, ok := node.(ast.Expr) 119 if !ok { 120 return nil, nil, false, fmt.Errorf("node is not an expression") 121 } 122 switch expr.(type) { 123 case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.CallExpr, 124 *ast.SliceExpr, *ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr: 125 return expr, path, true, nil 126 } 127 return nil, nil, false, fmt.Errorf("cannot extract an %T to a variable", expr) 128 } 129 130 // Calculate indentation for insertion. 131 // When inserting lines of code, we must ensure that the lines have consistent 132 // formatting (i.e. the proper indentation). To do so, we observe the indentation on the 133 // line of code on which the insertion occurs. 134 func calculateIndentation(content []byte, tok *token.File, insertBeforeStmt ast.Node) (string, error) { 135 line := safetoken.Line(tok, insertBeforeStmt.Pos()) 136 lineOffset, stmtOffset, err := safetoken.Offsets(tok, tok.LineStart(line), insertBeforeStmt.Pos()) 137 if err != nil { 138 return "", err 139 } 140 return string(content[lineOffset:stmtOffset]), nil 141 } 142 143 // generateAvailableIdentifier adjusts the new function name until there are no collisions in scope. 144 // Possible collisions include other function and variable names. Returns the next index to check for prefix. 145 func generateAvailableIdentifier(pos token.Pos, path []ast.Node, pkg *types.Package, info *types.Info, prefix string, idx int) (string, int) { 146 scopes := CollectScopes(info, path, pos) 147 scopes = append(scopes, pkg.Scope()) 148 return generateIdentifier(idx, prefix, func(name string) bool { 149 for _, scope := range scopes { 150 if scope != nil && scope.Lookup(name) != nil { 151 return true 152 } 153 } 154 return false 155 }) 156 } 157 158 func generateIdentifier(idx int, prefix string, hasCollision func(string) bool) (string, int) { 159 name := prefix 160 if idx != 0 { 161 name += fmt.Sprintf("%d", idx) 162 } 163 for hasCollision(name) { 164 idx++ 165 name = fmt.Sprintf("%v%d", prefix, idx) 166 } 167 return name, idx + 1 168 } 169 170 // returnVariable keeps track of the information we need to properly introduce a new variable 171 // that we will return in the extracted function. 172 type returnVariable struct { 173 // name is the identifier that is used on the left-hand side of the call to 174 // the extracted function. 175 name ast.Expr 176 // decl is the declaration of the variable. It is used in the type signature of the 177 // extracted function and for variable declarations. 178 decl *ast.Field 179 // zeroVal is the "zero value" of the type of the variable. It is used in a return 180 // statement in the extracted function. 181 zeroVal ast.Expr 182 } 183 184 // extractMethod refactors the selected block of code into a new method. 185 func extractMethod(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { 186 return extractFunctionMethod(fset, start, end, src, file, pkg, info, true) 187 } 188 189 // extractFunction refactors the selected block of code into a new function. 190 func extractFunction(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { 191 return extractFunctionMethod(fset, start, end, src, file, pkg, info, false) 192 } 193 194 // extractFunctionMethod refactors the selected block of code into a new function/method. 195 // It also replaces the selected block of code with a call to the extracted 196 // function. First, we manually adjust the selection range. We remove trailing 197 // and leading whitespace characters to ensure the range is precisely bounded 198 // by AST nodes. Next, we determine the variables that will be the parameters 199 // and return values of the extracted function/method. Lastly, we construct the call 200 // of the function/method and insert this call as well as the extracted function/method into 201 // their proper locations. 202 func extractFunctionMethod(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info, isMethod bool) (*token.FileSet, *analysis.SuggestedFix, error) { 203 errorPrefix := "extractFunction" 204 if isMethod { 205 errorPrefix = "extractMethod" 206 } 207 208 tok := fset.File(file.Pos()) 209 if tok == nil { 210 return nil, nil, bug.Errorf("no file for position") 211 } 212 p, ok, methodOk, err := CanExtractFunction(tok, start, end, src, file) 213 if (!ok && !isMethod) || (!methodOk && isMethod) { 214 return nil, nil, fmt.Errorf("%s: cannot extract %s: %v", errorPrefix, 215 safetoken.StartPosition(fset, start), err) 216 } 217 tok, path, start, end, outer, node := p.tok, p.path, p.start, p.end, p.outer, p.node 218 fileScope := info.Scopes[file] 219 if fileScope == nil { 220 return nil, nil, fmt.Errorf("%s: file scope is empty", errorPrefix) 221 } 222 pkgScope := fileScope.Parent() 223 if pkgScope == nil { 224 return nil, nil, fmt.Errorf("%s: package scope is empty", errorPrefix) 225 } 226 227 // A return statement is non-nested if its parent node is equal to the parent node 228 // of the first node in the selection. These cases must be handled separately because 229 // non-nested return statements are guaranteed to execute. 230 var retStmts []*ast.ReturnStmt 231 var hasNonNestedReturn bool 232 startParent := findParent(outer, node) 233 ast.Inspect(outer, func(n ast.Node) bool { 234 if n == nil { 235 return false 236 } 237 if n.Pos() < start || n.End() > end { 238 return n.Pos() <= end 239 } 240 ret, ok := n.(*ast.ReturnStmt) 241 if !ok { 242 return true 243 } 244 if findParent(outer, n) == startParent { 245 hasNonNestedReturn = true 246 } 247 retStmts = append(retStmts, ret) 248 return false 249 }) 250 containsReturnStatement := len(retStmts) > 0 251 252 // Now that we have determined the correct range for the selection block, 253 // we must determine the signature of the extracted function. We will then replace 254 // the block with an assignment statement that calls the extracted function with 255 // the appropriate parameters and return values. 256 variables, err := collectFreeVars(info, file, fileScope, pkgScope, start, end, path[0]) 257 if err != nil { 258 return nil, nil, err 259 } 260 261 var ( 262 receiverUsed bool 263 receiver *ast.Field 264 receiverName string 265 receiverObj types.Object 266 ) 267 if isMethod { 268 if outer == nil || outer.Recv == nil || len(outer.Recv.List) == 0 { 269 return nil, nil, fmt.Errorf("%s: cannot extract need method receiver", errorPrefix) 270 } 271 receiver = outer.Recv.List[0] 272 if len(receiver.Names) == 0 || receiver.Names[0] == nil { 273 return nil, nil, fmt.Errorf("%s: cannot extract need method receiver name", errorPrefix) 274 } 275 recvName := receiver.Names[0] 276 receiverName = recvName.Name 277 receiverObj = info.ObjectOf(recvName) 278 } 279 280 var ( 281 params, returns []ast.Expr // used when calling the extracted function 282 paramTypes, returnTypes []*ast.Field // used in the signature of the extracted function 283 uninitialized []types.Object // vars we will need to initialize before the call 284 ) 285 286 // Avoid duplicates while traversing vars and uninitialized. 287 seenVars := make(map[types.Object]ast.Expr) 288 seenUninitialized := make(map[types.Object]struct{}) 289 290 // Some variables on the left-hand side of our assignment statement may be free. If our 291 // selection begins in the same scope in which the free variable is defined, we can 292 // redefine it in our assignment statement. See the following example, where 'b' and 293 // 'err' (both free variables) can be redefined in the second funcCall() while maintaining 294 // correctness. 295 // 296 // 297 // Not Redefined: 298 // 299 // a, err := funcCall() 300 // var b int 301 // b, err = funcCall() 302 // 303 // Redefined: 304 // 305 // a, err := funcCall() 306 // b, err := funcCall() 307 // 308 // We track the number of free variables that can be redefined to maintain our preference 309 // of using "x, y, z := fn()" style assignment statements. 310 var canRedefineCount int 311 312 // Each identifier in the selected block must become (1) a parameter to the 313 // extracted function, (2) a return value of the extracted function, or (3) a local 314 // variable in the extracted function. Determine the outcome(s) for each variable 315 // based on whether it is free, altered within the selected block, and used outside 316 // of the selected block. 317 for _, v := range variables { 318 if _, ok := seenVars[v.obj]; ok { 319 continue 320 } 321 if v.obj.Name() == "_" { 322 // The blank identifier is always a local variable 323 continue 324 } 325 typ := analysisinternal.TypeExpr(file, pkg, v.obj.Type()) 326 if typ == nil { 327 return nil, nil, fmt.Errorf("nil AST expression for type: %v", v.obj.Name()) 328 } 329 seenVars[v.obj] = typ 330 identifier := ast.NewIdent(v.obj.Name()) 331 // An identifier must meet three conditions to become a return value of the 332 // extracted function. (1) its value must be defined or reassigned within 333 // the selection (isAssigned), (2) it must be used at least once after the 334 // selection (isUsed), and (3) its first use after the selection 335 // cannot be its own reassignment or redefinition (objOverriden). 336 vscope := v.obj.Parent() 337 if vscope == nil { 338 return nil, nil, fmt.Errorf("parent nil") 339 } 340 isUsed, firstUseAfter := objUsed(info, end, vscope.End(), v.obj) 341 if v.assigned && isUsed && !varOverridden(info, firstUseAfter, v.obj, v.free, outer) { 342 returnTypes = append(returnTypes, &ast.Field{Type: typ}) 343 returns = append(returns, identifier) 344 if !v.free { 345 uninitialized = append(uninitialized, v.obj) 346 347 } else { 348 // In go1.22, Scope.Pos for function scopes changed (#60752): 349 // it used to start at the body ('{'), now it starts at "func". 350 // 351 // The second condition below handles the case when 352 // v's block is the FuncDecl.Body itself. 353 if vscope.Pos() == startParent.Pos() || 354 startParent == outer.Body && vscope == info.Scopes[outer.Type] { 355 canRedefineCount++ 356 } 357 } 358 } 359 // An identifier must meet two conditions to become a parameter of the 360 // extracted function. (1) it must be free (isFree), and (2) its first 361 // use within the selection cannot be its own definition (isDefined). 362 if v.free && !v.defined { 363 // Skip the selector for a method. 364 if isMethod && v.obj == receiverObj { 365 receiverUsed = true 366 continue 367 } 368 params = append(params, identifier) 369 paramTypes = append(paramTypes, &ast.Field{ 370 Names: []*ast.Ident{identifier}, 371 Type: typ, 372 }) 373 } 374 } 375 376 reorderParams(params, paramTypes) 377 378 // Find the function literal that encloses the selection. The enclosing function literal 379 // may not be the enclosing function declaration (i.e. 'outer'). For example, in the 380 // following block: 381 // 382 // func main() { 383 // ast.Inspect(node, func(n ast.Node) bool { 384 // v := 1 // this line extracted 385 // return true 386 // }) 387 // } 388 // 389 // 'outer' is main(). However, the extracted selection most directly belongs to 390 // the anonymous function literal, the second argument of ast.Inspect(). We use the 391 // enclosing function literal to determine the proper return types for return statements 392 // within the selection. We still need the enclosing function declaration because this is 393 // the top-level declaration. We inspect the top-level declaration to look for variables 394 // as well as for code replacement. 395 enclosing := outer.Type 396 for _, p := range path { 397 if p == enclosing { 398 break 399 } 400 if fl, ok := p.(*ast.FuncLit); ok { 401 enclosing = fl.Type 402 break 403 } 404 } 405 406 // We put the selection in a constructed file. We can then traverse and edit 407 // the extracted selection without modifying the original AST. 408 startOffset, endOffset, err := safetoken.Offsets(tok, start, end) 409 if err != nil { 410 return nil, nil, err 411 } 412 selection := src[startOffset:endOffset] 413 extractedBlock, err := parseBlockStmt(fset, selection) 414 if err != nil { 415 return nil, nil, err 416 } 417 418 // We need to account for return statements in the selected block, as they will complicate 419 // the logical flow of the extracted function. See the following example, where ** denotes 420 // the range to be extracted. 421 // 422 // Before: 423 // 424 // func _() int { 425 // a := 1 426 // b := 2 427 // **if a == b { 428 // return a 429 // }** 430 // ... 431 // } 432 // 433 // After: 434 // 435 // func _() int { 436 // a := 1 437 // b := 2 438 // cond0, ret0 := x0(a, b) 439 // if cond0 { 440 // return ret0 441 // } 442 // ... 443 // } 444 // 445 // func x0(a int, b int) (bool, int) { 446 // if a == b { 447 // return true, a 448 // } 449 // return false, 0 450 // } 451 // 452 // We handle returns by adding an additional boolean return value to the extracted function. 453 // This bool reports whether the original function would have returned. Because the 454 // extracted selection contains a return statement, we must also add the types in the 455 // return signature of the enclosing function to the return signature of the 456 // extracted function. We then add an extra if statement checking this boolean value 457 // in the original function. If the condition is met, the original function should 458 // return a value, mimicking the functionality of the original return statement(s) 459 // in the selection. 460 // 461 // If there is a return that is guaranteed to execute (hasNonNestedReturns=true), then 462 // we don't need to include this additional condition check and can simply return. 463 // 464 // Before: 465 // 466 // func _() int { 467 // a := 1 468 // b := 2 469 // **if a == b { 470 // return a 471 // } 472 // return b** 473 // } 474 // 475 // After: 476 // 477 // func _() int { 478 // a := 1 479 // b := 2 480 // return x0(a, b) 481 // } 482 // 483 // func x0(a int, b int) int { 484 // if a == b { 485 // return a 486 // } 487 // return b 488 // } 489 490 var retVars []*returnVariable 491 var ifReturn *ast.IfStmt 492 if containsReturnStatement { 493 if !hasNonNestedReturn { 494 // The selected block contained return statements, so we have to modify the 495 // signature of the extracted function as described above. Adjust all of 496 // the return statements in the extracted function to reflect this change in 497 // signature. 498 if err := adjustReturnStatements(returnTypes, seenVars, file, pkg, extractedBlock); err != nil { 499 return nil, nil, err 500 } 501 } 502 // Collect the additional return values and types needed to accommodate return 503 // statements in the selection. Update the type signature of the extracted 504 // function and construct the if statement that will be inserted in the enclosing 505 // function. 506 retVars, ifReturn, err = generateReturnInfo(enclosing, pkg, path, file, info, start, hasNonNestedReturn) 507 if err != nil { 508 return nil, nil, err 509 } 510 } 511 512 // Add a return statement to the end of the new function. This return statement must include 513 // the values for the types of the original extracted function signature and (if a return 514 // statement is present in the selection) enclosing function signature. 515 // This only needs to be done if the selections does not have a non-nested return, otherwise 516 // it already terminates with a return statement. 517 hasReturnValues := len(returns)+len(retVars) > 0 518 if hasReturnValues && !hasNonNestedReturn { 519 extractedBlock.List = append(extractedBlock.List, &ast.ReturnStmt{ 520 Results: append(returns, getZeroVals(retVars)...), 521 }) 522 } 523 524 // Construct the appropriate call to the extracted function. 525 // We must meet two conditions to use ":=" instead of '='. (1) there must be at least 526 // one variable on the lhs that is uninitialized (non-free) prior to the assignment. 527 // (2) all of the initialized (free) variables on the lhs must be able to be redefined. 528 sym := token.ASSIGN 529 canDefineCount := len(uninitialized) + canRedefineCount 530 canDefine := len(uninitialized)+len(retVars) > 0 && canDefineCount == len(returns) 531 if canDefine { 532 sym = token.DEFINE 533 } 534 var name, funName string 535 if isMethod { 536 name = "newMethod" 537 // TODO(suzmue): generate a name that does not conflict for "newMethod". 538 funName = name 539 } else { 540 name = "newFunction" 541 funName, _ = generateAvailableIdentifier(start, path, pkg, info, name, 0) 542 } 543 extractedFunCall := generateFuncCall(hasNonNestedReturn, hasReturnValues, params, 544 append(returns, getNames(retVars)...), funName, sym, receiverName) 545 546 // Build the extracted function. 547 newFunc := &ast.FuncDecl{ 548 Name: ast.NewIdent(funName), 549 Type: &ast.FuncType{ 550 Params: &ast.FieldList{List: paramTypes}, 551 Results: &ast.FieldList{List: append(returnTypes, getDecls(retVars)...)}, 552 }, 553 Body: extractedBlock, 554 } 555 if isMethod { 556 var names []*ast.Ident 557 if receiverUsed { 558 names = append(names, ast.NewIdent(receiverName)) 559 } 560 newFunc.Recv = &ast.FieldList{ 561 List: []*ast.Field{{ 562 Names: names, 563 Type: receiver.Type, 564 }}, 565 } 566 } 567 568 // Create variable declarations for any identifiers that need to be initialized prior to 569 // calling the extracted function. We do not manually initialize variables if every return 570 // value is uninitialized. We can use := to initialize the variables in this situation. 571 var declarations []ast.Stmt 572 if canDefineCount != len(returns) { 573 declarations = initializeVars(uninitialized, retVars, seenUninitialized, seenVars) 574 } 575 576 var declBuf, replaceBuf, newFuncBuf, ifBuf, commentBuf bytes.Buffer 577 if err := format.Node(&declBuf, fset, declarations); err != nil { 578 return nil, nil, err 579 } 580 if err := format.Node(&replaceBuf, fset, extractedFunCall); err != nil { 581 return nil, nil, err 582 } 583 if ifReturn != nil { 584 if err := format.Node(&ifBuf, fset, ifReturn); err != nil { 585 return nil, nil, err 586 } 587 } 588 if err := format.Node(&newFuncBuf, fset, newFunc); err != nil { 589 return nil, nil, err 590 } 591 // Find all the comments within the range and print them to be put somewhere. 592 // TODO(suzmue): print these in the extracted function at the correct place. 593 for _, cg := range file.Comments { 594 if cg.Pos().IsValid() && cg.Pos() < end && cg.Pos() >= start { 595 for _, c := range cg.List { 596 fmt.Fprintln(&commentBuf, c.Text) 597 } 598 } 599 } 600 601 // We're going to replace the whole enclosing function, 602 // so preserve the text before and after the selected block. 603 outerStart, outerEnd, err := safetoken.Offsets(tok, outer.Pos(), outer.End()) 604 if err != nil { 605 return nil, nil, err 606 } 607 before := src[outerStart:startOffset] 608 after := src[endOffset:outerEnd] 609 indent, err := calculateIndentation(src, tok, node) 610 if err != nil { 611 return nil, nil, err 612 } 613 newLineIndent := "\n" + indent 614 615 var fullReplacement strings.Builder 616 fullReplacement.Write(before) 617 if commentBuf.Len() > 0 { 618 comments := strings.ReplaceAll(commentBuf.String(), "\n", newLineIndent) 619 fullReplacement.WriteString(comments) 620 } 621 if declBuf.Len() > 0 { // add any initializations, if needed 622 initializations := strings.ReplaceAll(declBuf.String(), "\n", newLineIndent) + 623 newLineIndent 624 fullReplacement.WriteString(initializations) 625 } 626 fullReplacement.Write(replaceBuf.Bytes()) // call the extracted function 627 if ifBuf.Len() > 0 { // add the if statement below the function call, if needed 628 ifstatement := newLineIndent + 629 strings.ReplaceAll(ifBuf.String(), "\n", newLineIndent) 630 fullReplacement.WriteString(ifstatement) 631 } 632 fullReplacement.Write(after) 633 fullReplacement.WriteString("\n\n") // add newlines after the enclosing function 634 fullReplacement.Write(newFuncBuf.Bytes()) // insert the extracted function 635 636 return fset, &analysis.SuggestedFix{ 637 TextEdits: []analysis.TextEdit{{ 638 Pos: outer.Pos(), 639 End: outer.End(), 640 NewText: []byte(fullReplacement.String()), 641 }}, 642 }, nil 643 } 644 645 // isSelector reports if e is the selector expr <x>, <sel>. 646 func isSelector(e ast.Expr, x, sel string) bool { 647 selectorExpr, ok := e.(*ast.SelectorExpr) 648 if !ok { 649 return false 650 } 651 ident, ok := selectorExpr.X.(*ast.Ident) 652 if !ok { 653 return false 654 } 655 return ident.Name == x && selectorExpr.Sel.Name == sel 656 } 657 658 // reorderParams reorders the given parameters in-place to follow common Go conventions. 659 func reorderParams(params []ast.Expr, paramTypes []*ast.Field) { 660 // Move Context parameter (if any) to front. 661 for i, t := range paramTypes { 662 if isSelector(t.Type, "context", "Context") { 663 p, t := params[i], paramTypes[i] 664 copy(params[1:], params[:i]) 665 copy(paramTypes[1:], paramTypes[:i]) 666 params[0], paramTypes[0] = p, t 667 break 668 } 669 } 670 } 671 672 // adjustRangeForCommentsAndWhiteSpace adjusts the given range to exclude unnecessary leading or 673 // trailing whitespace characters from selection as well as leading or trailing comments. 674 // In the following example, each line of the if statement is indented once. There are also two 675 // extra spaces after the sclosing bracket before the line break and a comment. 676 // 677 // \tif (true) { 678 // \t _ = 1 679 // \t} // hello \n 680 // 681 // By default, a valid range begins at 'if' and ends at the first whitespace character 682 // after the '}'. But, users are likely to highlight full lines rather than adjusting 683 // their cursors for whitespace. To support this use case, we must manually adjust the 684 // ranges to match the correct AST node. In this particular example, we would adjust 685 // rng.Start forward to the start of 'if' and rng.End backward to after '}'. 686 func adjustRangeForCommentsAndWhiteSpace(tok *token.File, start, end token.Pos, content []byte, file *ast.File) (token.Pos, token.Pos, error) { 687 // Adjust the end of the range to after leading whitespace and comments. 688 prevStart := token.NoPos 689 startComment := sort.Search(len(file.Comments), func(i int) bool { 690 // Find the index for the first comment that ends after range start. 691 return file.Comments[i].End() > start 692 }) 693 for prevStart != start { 694 prevStart = start 695 // If start is within a comment, move start to the end 696 // of the comment group. 697 if startComment < len(file.Comments) && file.Comments[startComment].Pos() <= start && start < file.Comments[startComment].End() { 698 start = file.Comments[startComment].End() 699 startComment++ 700 } 701 // Move forwards to find a non-whitespace character. 702 offset, err := safetoken.Offset(tok, start) 703 if err != nil { 704 return 0, 0, err 705 } 706 for offset < len(content) && isGoWhiteSpace(content[offset]) { 707 offset++ 708 } 709 start = tok.Pos(offset) 710 } 711 712 // Adjust the end of the range to before trailing whitespace and comments. 713 prevEnd := token.NoPos 714 endComment := sort.Search(len(file.Comments), func(i int) bool { 715 // Find the index for the first comment that ends after the range end. 716 return file.Comments[i].End() >= end 717 }) 718 // Search will return n if not found, so we need to adjust if there are no 719 // comments that would match. 720 if endComment == len(file.Comments) { 721 endComment = -1 722 } 723 for prevEnd != end { 724 prevEnd = end 725 // If end is within a comment, move end to the start 726 // of the comment group. 727 if endComment >= 0 && file.Comments[endComment].Pos() < end && end <= file.Comments[endComment].End() { 728 end = file.Comments[endComment].Pos() 729 endComment-- 730 } 731 // Move backwards to find a non-whitespace character. 732 offset, err := safetoken.Offset(tok, end) 733 if err != nil { 734 return 0, 0, err 735 } 736 for offset > 0 && isGoWhiteSpace(content[offset-1]) { 737 offset-- 738 } 739 end = tok.Pos(offset) 740 } 741 742 return start, end, nil 743 } 744 745 // isGoWhiteSpace returns true if b is a considered white space in 746 // Go as defined by scanner.GoWhitespace. 747 func isGoWhiteSpace(b byte) bool { 748 return uint64(scanner.GoWhitespace)&(1<<uint(b)) != 0 749 } 750 751 // findParent finds the parent AST node of the given target node, if the target is a 752 // descendant of the starting node. 753 func findParent(start ast.Node, target ast.Node) ast.Node { 754 var parent ast.Node 755 analysisinternal.WalkASTWithParent(start, func(n, p ast.Node) bool { 756 if n == target { 757 parent = p 758 return false 759 } 760 return true 761 }) 762 return parent 763 } 764 765 // variable describes the status of a variable within a selection. 766 type variable struct { 767 obj types.Object 768 769 // free reports whether the variable is a free variable, meaning it should 770 // be a parameter to the extracted function. 771 free bool 772 773 // assigned reports whether the variable is assigned to in the selection. 774 assigned bool 775 776 // defined reports whether the variable is defined in the selection. 777 defined bool 778 } 779 780 // collectFreeVars maps each identifier in the given range to whether it is "free." 781 // Given a range, a variable in that range is defined as "free" if it is declared 782 // outside of the range and neither at the file scope nor package scope. These free 783 // variables will be used as arguments in the extracted function. It also returns a 784 // list of identifiers that may need to be returned by the extracted function. 785 // Some of the code in this function has been adapted from tools/cmd/guru/freevars.go. 786 func collectFreeVars(info *types.Info, file *ast.File, fileScope, pkgScope *types.Scope, start, end token.Pos, node ast.Node) ([]*variable, error) { 787 // id returns non-nil if n denotes an object that is referenced by the span 788 // and defined either within the span or in the lexical environment. The bool 789 // return value acts as an indicator for where it was defined. 790 id := func(n *ast.Ident) (types.Object, bool) { 791 obj := info.Uses[n] 792 if obj == nil { 793 return info.Defs[n], false 794 } 795 if obj.Name() == "_" { 796 return nil, false // exclude objects denoting '_' 797 } 798 if _, ok := obj.(*types.PkgName); ok { 799 return nil, false // imported package 800 } 801 if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) { 802 return nil, false // not defined in this file 803 } 804 scope := obj.Parent() 805 if scope == nil { 806 return nil, false // e.g. interface method, struct field 807 } 808 if scope == fileScope || scope == pkgScope { 809 return nil, false // defined at file or package scope 810 } 811 if start <= obj.Pos() && obj.Pos() <= end { 812 return obj, false // defined within selection => not free 813 } 814 return obj, true 815 } 816 // sel returns non-nil if n denotes a selection o.x.y that is referenced by the 817 // span and defined either within the span or in the lexical environment. The bool 818 // return value acts as an indicator for where it was defined. 819 var sel func(n *ast.SelectorExpr) (types.Object, bool) 820 sel = func(n *ast.SelectorExpr) (types.Object, bool) { 821 switch x := astutil.Unparen(n.X).(type) { 822 case *ast.SelectorExpr: 823 return sel(x) 824 case *ast.Ident: 825 return id(x) 826 } 827 return nil, false 828 } 829 seen := make(map[types.Object]*variable) 830 firstUseIn := make(map[types.Object]token.Pos) 831 var vars []types.Object 832 ast.Inspect(node, func(n ast.Node) bool { 833 if n == nil { 834 return false 835 } 836 if start <= n.Pos() && n.End() <= end { 837 var obj types.Object 838 var isFree, prune bool 839 switch n := n.(type) { 840 case *ast.Ident: 841 obj, isFree = id(n) 842 case *ast.SelectorExpr: 843 obj, isFree = sel(n) 844 prune = true 845 } 846 if obj != nil { 847 seen[obj] = &variable{ 848 obj: obj, 849 free: isFree, 850 } 851 vars = append(vars, obj) 852 // Find the first time that the object is used in the selection. 853 first, ok := firstUseIn[obj] 854 if !ok || n.Pos() < first { 855 firstUseIn[obj] = n.Pos() 856 } 857 if prune { 858 return false 859 } 860 } 861 } 862 return n.Pos() <= end 863 }) 864 865 // Find identifiers that are initialized or whose values are altered at some 866 // point in the selected block. For example, in a selected block from lines 2-4, 867 // variables x, y, and z are included in assigned. However, in a selected block 868 // from lines 3-4, only variables y and z are included in assigned. 869 // 870 // 1: var a int 871 // 2: var x int 872 // 3: y := 3 873 // 4: z := x + a 874 // 875 ast.Inspect(node, func(n ast.Node) bool { 876 if n == nil { 877 return false 878 } 879 if n.Pos() < start || n.End() > end { 880 return n.Pos() <= end 881 } 882 switch n := n.(type) { 883 case *ast.AssignStmt: 884 for _, assignment := range n.Lhs { 885 lhs, ok := assignment.(*ast.Ident) 886 if !ok { 887 continue 888 } 889 obj, _ := id(lhs) 890 if obj == nil { 891 continue 892 } 893 if _, ok := seen[obj]; !ok { 894 continue 895 } 896 seen[obj].assigned = true 897 if n.Tok != token.DEFINE { 898 continue 899 } 900 // Find identifiers that are defined prior to being used 901 // elsewhere in the selection. 902 // TODO: Include identifiers that are assigned prior to being 903 // used elsewhere in the selection. Then, change the assignment 904 // to a definition in the extracted function. 905 if firstUseIn[obj] != lhs.Pos() { 906 continue 907 } 908 // Ensure that the object is not used in its own re-definition. 909 // For example: 910 // var f float64 911 // f, e := math.Frexp(f) 912 for _, expr := range n.Rhs { 913 if referencesObj(info, expr, obj) { 914 continue 915 } 916 if _, ok := seen[obj]; !ok { 917 continue 918 } 919 seen[obj].defined = true 920 break 921 } 922 } 923 return false 924 case *ast.DeclStmt: 925 gen, ok := n.Decl.(*ast.GenDecl) 926 if !ok { 927 return false 928 } 929 for _, spec := range gen.Specs { 930 vSpecs, ok := spec.(*ast.ValueSpec) 931 if !ok { 932 continue 933 } 934 for _, vSpec := range vSpecs.Names { 935 obj, _ := id(vSpec) 936 if obj == nil { 937 continue 938 } 939 if _, ok := seen[obj]; !ok { 940 continue 941 } 942 seen[obj].assigned = true 943 } 944 } 945 return false 946 case *ast.IncDecStmt: 947 if ident, ok := n.X.(*ast.Ident); !ok { 948 return false 949 } else if obj, _ := id(ident); obj == nil { 950 return false 951 } else { 952 if _, ok := seen[obj]; !ok { 953 return false 954 } 955 seen[obj].assigned = true 956 } 957 } 958 return true 959 }) 960 var variables []*variable 961 for _, obj := range vars { 962 v, ok := seen[obj] 963 if !ok { 964 return nil, fmt.Errorf("no seen types.Object for %v", obj) 965 } 966 variables = append(variables, v) 967 } 968 return variables, nil 969 } 970 971 // referencesObj checks whether the given object appears in the given expression. 972 func referencesObj(info *types.Info, expr ast.Expr, obj types.Object) bool { 973 var hasObj bool 974 ast.Inspect(expr, func(n ast.Node) bool { 975 if n == nil { 976 return false 977 } 978 ident, ok := n.(*ast.Ident) 979 if !ok { 980 return true 981 } 982 objUse := info.Uses[ident] 983 if obj == objUse { 984 hasObj = true 985 return false 986 } 987 return false 988 }) 989 return hasObj 990 } 991 992 type fnExtractParams struct { 993 tok *token.File 994 start, end token.Pos 995 path []ast.Node 996 outer *ast.FuncDecl 997 node ast.Node 998 } 999 1000 // CanExtractFunction reports whether the code in the given range can be 1001 // extracted to a function. 1002 func CanExtractFunction(tok *token.File, start, end token.Pos, src []byte, file *ast.File) (*fnExtractParams, bool, bool, error) { 1003 if start == end { 1004 return nil, false, false, fmt.Errorf("start and end are equal") 1005 } 1006 var err error 1007 start, end, err = adjustRangeForCommentsAndWhiteSpace(tok, start, end, src, file) 1008 if err != nil { 1009 return nil, false, false, err 1010 } 1011 path, _ := astutil.PathEnclosingInterval(file, start, end) 1012 if len(path) == 0 { 1013 return nil, false, false, fmt.Errorf("no path enclosing interval") 1014 } 1015 // Node that encloses the selection must be a statement. 1016 // TODO: Support function extraction for an expression. 1017 _, ok := path[0].(ast.Stmt) 1018 if !ok { 1019 return nil, false, false, fmt.Errorf("node is not a statement") 1020 } 1021 1022 // Find the function declaration that encloses the selection. 1023 var outer *ast.FuncDecl 1024 for _, p := range path { 1025 if p, ok := p.(*ast.FuncDecl); ok { 1026 outer = p 1027 break 1028 } 1029 } 1030 if outer == nil { 1031 return nil, false, false, fmt.Errorf("no enclosing function") 1032 } 1033 1034 // Find the nodes at the start and end of the selection. 1035 var startNode, endNode ast.Node 1036 ast.Inspect(outer, func(n ast.Node) bool { 1037 if n == nil { 1038 return false 1039 } 1040 // Do not override 'start' with a node that begins at the same location 1041 // but is nested further from 'outer'. 1042 if startNode == nil && n.Pos() == start && n.End() <= end { 1043 startNode = n 1044 } 1045 if endNode == nil && n.End() == end && n.Pos() >= start { 1046 endNode = n 1047 } 1048 return n.Pos() <= end 1049 }) 1050 if startNode == nil || endNode == nil { 1051 return nil, false, false, fmt.Errorf("range does not map to AST nodes") 1052 } 1053 // If the region is a blockStmt, use the first and last nodes in the block 1054 // statement. 1055 // <rng.start>{ ... }<rng.end> => { <rng.start>...<rng.end> } 1056 if blockStmt, ok := startNode.(*ast.BlockStmt); ok { 1057 if len(blockStmt.List) == 0 { 1058 return nil, false, false, fmt.Errorf("range maps to empty block statement") 1059 } 1060 startNode, endNode = blockStmt.List[0], blockStmt.List[len(blockStmt.List)-1] 1061 start, end = startNode.Pos(), endNode.End() 1062 } 1063 return &fnExtractParams{ 1064 tok: tok, 1065 start: start, 1066 end: end, 1067 path: path, 1068 outer: outer, 1069 node: startNode, 1070 }, true, outer.Recv != nil, nil 1071 } 1072 1073 // objUsed checks if the object is used within the range. It returns the first 1074 // occurrence of the object in the range, if it exists. 1075 func objUsed(info *types.Info, start, end token.Pos, obj types.Object) (bool, *ast.Ident) { 1076 var firstUse *ast.Ident 1077 for id, objUse := range info.Uses { 1078 if obj != objUse { 1079 continue 1080 } 1081 if id.Pos() < start || id.End() > end { 1082 continue 1083 } 1084 if firstUse == nil || id.Pos() < firstUse.Pos() { 1085 firstUse = id 1086 } 1087 } 1088 return firstUse != nil, firstUse 1089 } 1090 1091 // varOverridden traverses the given AST node until we find the given identifier. Then, we 1092 // examine the occurrence of the given identifier and check for (1) whether the identifier 1093 // is being redefined. If the identifier is free, we also check for (2) whether the identifier 1094 // is being reassigned. We will not include an identifier in the return statement of the 1095 // extracted function if it meets one of the above conditions. 1096 func varOverridden(info *types.Info, firstUse *ast.Ident, obj types.Object, isFree bool, node ast.Node) bool { 1097 var isOverriden bool 1098 ast.Inspect(node, func(n ast.Node) bool { 1099 if n == nil { 1100 return false 1101 } 1102 assignment, ok := n.(*ast.AssignStmt) 1103 if !ok { 1104 return true 1105 } 1106 // A free variable is initialized prior to the selection. We can always reassign 1107 // this variable after the selection because it has already been defined. 1108 // Conversely, a non-free variable is initialized within the selection. Thus, we 1109 // cannot reassign this variable after the selection unless it is initialized and 1110 // returned by the extracted function. 1111 if !isFree && assignment.Tok == token.ASSIGN { 1112 return false 1113 } 1114 for _, assigned := range assignment.Lhs { 1115 ident, ok := assigned.(*ast.Ident) 1116 // Check if we found the first use of the identifier. 1117 if !ok || ident != firstUse { 1118 continue 1119 } 1120 objUse := info.Uses[ident] 1121 if objUse == nil || objUse != obj { 1122 continue 1123 } 1124 // Ensure that the object is not used in its own definition. 1125 // For example: 1126 // var f float64 1127 // f, e := math.Frexp(f) 1128 for _, expr := range assignment.Rhs { 1129 if referencesObj(info, expr, obj) { 1130 return false 1131 } 1132 } 1133 isOverriden = true 1134 return false 1135 } 1136 return false 1137 }) 1138 return isOverriden 1139 } 1140 1141 // parseBlockStmt generates an AST file from the given text. We then return the portion of the 1142 // file that represents the text. 1143 func parseBlockStmt(fset *token.FileSet, src []byte) (*ast.BlockStmt, error) { 1144 text := "package main\nfunc _() { " + string(src) + " }" 1145 extract, err := parser.ParseFile(fset, "", text, 0) 1146 if err != nil { 1147 return nil, err 1148 } 1149 if len(extract.Decls) == 0 { 1150 return nil, fmt.Errorf("parsed file does not contain any declarations") 1151 } 1152 decl, ok := extract.Decls[0].(*ast.FuncDecl) 1153 if !ok { 1154 return nil, fmt.Errorf("parsed file does not contain expected function declaration") 1155 } 1156 if decl.Body == nil { 1157 return nil, fmt.Errorf("extracted function has no body") 1158 } 1159 return decl.Body, nil 1160 } 1161 1162 // generateReturnInfo generates the information we need to adjust the return statements and 1163 // signature of the extracted function. We prepare names, signatures, and "zero values" that 1164 // represent the new variables. We also use this information to construct the if statement that 1165 // is inserted below the call to the extracted function. 1166 func generateReturnInfo(enclosing *ast.FuncType, pkg *types.Package, path []ast.Node, file *ast.File, info *types.Info, pos token.Pos, hasNonNestedReturns bool) ([]*returnVariable, *ast.IfStmt, error) { 1167 var retVars []*returnVariable 1168 var cond *ast.Ident 1169 if !hasNonNestedReturns { 1170 // Generate information for the added bool value. 1171 name, _ := generateAvailableIdentifier(pos, path, pkg, info, "shouldReturn", 0) 1172 cond = &ast.Ident{Name: name} 1173 retVars = append(retVars, &returnVariable{ 1174 name: cond, 1175 decl: &ast.Field{Type: ast.NewIdent("bool")}, 1176 zeroVal: ast.NewIdent("false"), 1177 }) 1178 } 1179 // Generate information for the values in the return signature of the enclosing function. 1180 if enclosing.Results != nil { 1181 idx := 0 1182 for _, field := range enclosing.Results.List { 1183 typ := info.TypeOf(field.Type) 1184 if typ == nil { 1185 return nil, nil, fmt.Errorf( 1186 "failed type conversion, AST expression: %T", field.Type) 1187 } 1188 expr := analysisinternal.TypeExpr(file, pkg, typ) 1189 if expr == nil { 1190 return nil, nil, fmt.Errorf("nil AST expression") 1191 } 1192 var name string 1193 name, idx = generateAvailableIdentifier(pos, path, pkg, info, "returnValue", idx) 1194 retVars = append(retVars, &returnVariable{ 1195 name: ast.NewIdent(name), 1196 decl: &ast.Field{Type: expr}, 1197 zeroVal: analysisinternal.ZeroValue(file, pkg, typ), 1198 }) 1199 } 1200 } 1201 var ifReturn *ast.IfStmt 1202 if !hasNonNestedReturns { 1203 // Create the return statement for the enclosing function. We must exclude the variable 1204 // for the condition of the if statement (cond) from the return statement. 1205 ifReturn = &ast.IfStmt{ 1206 Cond: cond, 1207 Body: &ast.BlockStmt{ 1208 List: []ast.Stmt{&ast.ReturnStmt{Results: getNames(retVars)[1:]}}, 1209 }, 1210 } 1211 } 1212 return retVars, ifReturn, nil 1213 } 1214 1215 // adjustReturnStatements adds "zero values" of the given types to each return statement 1216 // in the given AST node. 1217 func adjustReturnStatements(returnTypes []*ast.Field, seenVars map[types.Object]ast.Expr, file *ast.File, pkg *types.Package, extractedBlock *ast.BlockStmt) error { 1218 var zeroVals []ast.Expr 1219 // Create "zero values" for each type. 1220 for _, returnType := range returnTypes { 1221 var val ast.Expr 1222 for obj, typ := range seenVars { 1223 if typ != returnType.Type { 1224 continue 1225 } 1226 val = analysisinternal.ZeroValue(file, pkg, obj.Type()) 1227 break 1228 } 1229 if val == nil { 1230 return fmt.Errorf( 1231 "could not find matching AST expression for %T", returnType.Type) 1232 } 1233 zeroVals = append(zeroVals, val) 1234 } 1235 // Add "zero values" to each return statement. 1236 // The bool reports whether the enclosing function should return after calling the 1237 // extracted function. We set the bool to 'true' because, if these return statements 1238 // execute, the extracted function terminates early, and the enclosing function must 1239 // return as well. 1240 zeroVals = append(zeroVals, ast.NewIdent("true")) 1241 ast.Inspect(extractedBlock, func(n ast.Node) bool { 1242 if n == nil { 1243 return false 1244 } 1245 if n, ok := n.(*ast.ReturnStmt); ok { 1246 n.Results = append(zeroVals, n.Results...) 1247 return false 1248 } 1249 return true 1250 }) 1251 return nil 1252 } 1253 1254 // generateFuncCall constructs a call expression for the extracted function, described by the 1255 // given parameters and return variables. 1256 func generateFuncCall(hasNonNestedReturn, hasReturnVals bool, params, returns []ast.Expr, name string, token token.Token, selector string) ast.Node { 1257 var replace ast.Node 1258 callExpr := &ast.CallExpr{ 1259 Fun: ast.NewIdent(name), 1260 Args: params, 1261 } 1262 if selector != "" { 1263 callExpr = &ast.CallExpr{ 1264 Fun: &ast.SelectorExpr{ 1265 X: ast.NewIdent(selector), 1266 Sel: ast.NewIdent(name), 1267 }, 1268 Args: params, 1269 } 1270 } 1271 if hasReturnVals { 1272 if hasNonNestedReturn { 1273 // Create a return statement that returns the result of the function call. 1274 replace = &ast.ReturnStmt{ 1275 Return: 0, 1276 Results: []ast.Expr{callExpr}, 1277 } 1278 } else { 1279 // Assign the result of the function call. 1280 replace = &ast.AssignStmt{ 1281 Lhs: returns, 1282 Tok: token, 1283 Rhs: []ast.Expr{callExpr}, 1284 } 1285 } 1286 } else { 1287 replace = callExpr 1288 } 1289 return replace 1290 } 1291 1292 // initializeVars creates variable declarations, if needed. 1293 // Our preference is to replace the selected block with an "x, y, z := fn()" style 1294 // assignment statement. We can use this style when all of the variables in the 1295 // extracted function's return statement are either not defined prior to the extracted block 1296 // or can be safely redefined. However, for example, if z is already defined 1297 // in a different scope, we replace the selected block with: 1298 // 1299 // var x int 1300 // var y string 1301 // x, y, z = fn() 1302 func initializeVars(uninitialized []types.Object, retVars []*returnVariable, seenUninitialized map[types.Object]struct{}, seenVars map[types.Object]ast.Expr) []ast.Stmt { 1303 var declarations []ast.Stmt 1304 for _, obj := range uninitialized { 1305 if _, ok := seenUninitialized[obj]; ok { 1306 continue 1307 } 1308 seenUninitialized[obj] = struct{}{} 1309 valSpec := &ast.ValueSpec{ 1310 Names: []*ast.Ident{ast.NewIdent(obj.Name())}, 1311 Type: seenVars[obj], 1312 } 1313 genDecl := &ast.GenDecl{ 1314 Tok: token.VAR, 1315 Specs: []ast.Spec{valSpec}, 1316 } 1317 declarations = append(declarations, &ast.DeclStmt{Decl: genDecl}) 1318 } 1319 // Each variable added from a return statement in the selection 1320 // must be initialized. 1321 for i, retVar := range retVars { 1322 n := retVar.name.(*ast.Ident) 1323 valSpec := &ast.ValueSpec{ 1324 Names: []*ast.Ident{n}, 1325 Type: retVars[i].decl.Type, 1326 } 1327 genDecl := &ast.GenDecl{ 1328 Tok: token.VAR, 1329 Specs: []ast.Spec{valSpec}, 1330 } 1331 declarations = append(declarations, &ast.DeclStmt{Decl: genDecl}) 1332 } 1333 return declarations 1334 } 1335 1336 // getNames returns the names from the given list of returnVariable. 1337 func getNames(retVars []*returnVariable) []ast.Expr { 1338 var names []ast.Expr 1339 for _, retVar := range retVars { 1340 names = append(names, retVar.name) 1341 } 1342 return names 1343 } 1344 1345 // getZeroVals returns the "zero values" from the given list of returnVariable. 1346 func getZeroVals(retVars []*returnVariable) []ast.Expr { 1347 var zvs []ast.Expr 1348 for _, retVar := range retVars { 1349 zvs = append(zvs, retVar.zeroVal) 1350 } 1351 return zvs 1352 } 1353 1354 // getDecls returns the declarations from the given list of returnVariable. 1355 func getDecls(retVars []*returnVariable) []*ast.Field { 1356 var decls []*ast.Field 1357 for _, retVar := range retVars { 1358 decls = append(decls, retVar.decl) 1359 } 1360 return decls 1361 }