Skip to content

Commit 9f230b5

Browse files
committed
internal/lsp: fix extract bug choosing available identifiers
When choosing variable names, extract makes sure that the chosen name does not conflict with any existing variables. By avoiding these conflicts, we may actually have a conflict with the other names we are choosing. This change removes this conflict by sending the next index to use as the suffix of the function name. Change-Id: Icd81b67db29db2503e214d24ec19ca1065cda090 Reviewed-on: https://go-review.googlesource.com/c/tools/+/326111 Trust: Suzy Mueller <suzmue@golang.org> Run-TryBot: Suzy Mueller <suzmue@golang.org> gopls-CI: kokoro <noreply+kokoro@google.com> Reviewed-by: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Go Bot <gobot@golang.org>
1 parent 4e58f8f commit 9f230b5

File tree

3 files changed

+36
-17
lines changed

3 files changed

+36
-17
lines changed

internal/lsp/source/extract.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,23 @@ func extractVariable(fset *token.FileSet, rng span.Range, src []byte, file *ast.
3333
// TODO: stricter rules for selectorExpr.
3434
case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.SliceExpr,
3535
*ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr:
36-
lhsNames = append(lhsNames, generateAvailableIdentifier(expr.Pos(), file, path, info, "x", 0))
36+
lhsName, _ := generateAvailableIdentifier(expr.Pos(), file, path, info, "x", 0)
37+
lhsNames = append(lhsNames, lhsName)
3738
case *ast.CallExpr:
3839
tup, ok := info.TypeOf(expr).(*types.Tuple)
3940
if !ok {
4041
// If the call expression only has one return value, we can treat it the
4142
// same as our standard extract variable case.
42-
lhsNames = append(lhsNames,
43-
generateAvailableIdentifier(expr.Pos(), file, path, info, "x", 0))
43+
lhsName, _ := generateAvailableIdentifier(expr.Pos(), file, path, info, "x", 0)
44+
lhsNames = append(lhsNames, lhsName)
4445
break
4546
}
47+
idx := 0
4648
for i := 0; i < tup.Len(); i++ {
4749
// Generate a unique variable for each return value.
48-
lhsNames = append(lhsNames,
49-
generateAvailableIdentifier(expr.Pos(), file, path, info, "x", i))
50+
var lhsName string
51+
lhsName, idx = generateAvailableIdentifier(expr.Pos(), file, path, info, "x", idx)
52+
lhsNames = append(lhsNames, lhsName)
5053
}
5154
default:
5255
return nil, fmt.Errorf("cannot extract %T", expr)
@@ -133,15 +136,15 @@ func calculateIndentation(content []byte, tok *token.File, insertBeforeStmt ast.
133136
}
134137

135138
// generateAvailableIdentifier adjusts the new function name until there are no collisons in scope.
136-
// Possible collisions include other function and variable names.
137-
func generateAvailableIdentifier(pos token.Pos, file *ast.File, path []ast.Node, info *types.Info, prefix string, idx int) string {
139+
// Possible collisions include other function and variable names. Returns the next index to check for prefix.
140+
func generateAvailableIdentifier(pos token.Pos, file *ast.File, path []ast.Node, info *types.Info, prefix string, idx int) (string, int) {
138141
scopes := CollectScopes(info, path, pos)
139142
name := prefix + fmt.Sprintf("%d", idx)
140143
for file.Scope.Lookup(name) != nil || !isValidName(name, scopes) {
141144
idx++
142145
name = fmt.Sprintf("%v%d", prefix, idx)
143146
}
144-
return name
147+
return name, idx + 1
145148
}
146149

147150
// isValidName checks for variable collision in scope.
@@ -465,7 +468,7 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
465468
if canDefine {
466469
sym = token.DEFINE
467470
}
468-
funName := generateAvailableIdentifier(rng.Start, file, path, info, "fn", 0)
471+
funName, _ := generateAvailableIdentifier(rng.Start, file, path, info, "fn", 0)
469472
extractedFunCall := generateFuncCall(hasNonNestedReturn, hasReturnValues, params,
470473
append(returns, getNames(retVars)...), funName, sym)
471474

@@ -996,7 +999,8 @@ func generateReturnInfo(enclosing *ast.FuncType, pkg *types.Package, path []ast.
996999
var cond *ast.Ident
9971000
if !hasNonNestedReturns {
9981001
// Generate information for the added bool value.
999-
cond = &ast.Ident{Name: generateAvailableIdentifier(pos, file, path, info, "cond", 0)}
1002+
name, _ := generateAvailableIdentifier(pos, file, path, info, "cond", 0)
1003+
cond = &ast.Ident{Name: name}
10001004
retVars = append(retVars, &returnVariable{
10011005
name: cond,
10021006
decl: &ast.Field{Type: ast.NewIdent("bool")},
@@ -1005,7 +1009,8 @@ func generateReturnInfo(enclosing *ast.FuncType, pkg *types.Package, path []ast.
10051009
}
10061010
// Generate information for the values in the return signature of the enclosing function.
10071011
if enclosing.Results != nil {
1008-
for i, field := range enclosing.Results.List {
1012+
idx := 0
1013+
for _, field := range enclosing.Results.List {
10091014
typ := info.TypeOf(field.Type)
10101015
if typ == nil {
10111016
return nil, nil, fmt.Errorf(
@@ -1015,9 +1020,11 @@ func generateReturnInfo(enclosing *ast.FuncType, pkg *types.Package, path []ast.
10151020
if expr == nil {
10161021
return nil, nil, fmt.Errorf("nil AST expression")
10171022
}
1023+
var name string
1024+
name, idx = generateAvailableIdentifier(pos, file,
1025+
path, info, "ret", idx)
10181026
retVars = append(retVars, &returnVariable{
1019-
name: ast.NewIdent(generateAvailableIdentifier(pos, file,
1020-
path, info, "ret", i)),
1027+
name: ast.NewIdent(name),
10211028
decl: &ast.Field{Type: expr},
10221029
zeroVal: analysisinternal.ZeroValue(
10231030
fset, file, pkg, typ),

internal/lsp/testdata/extract/extract_variable/extract_func_call.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package extract
33
import "strconv"
44

55
func _() {
6-
a := append([]int{}, 1) //@suggestedfix("append([]int{}, 1)", "refactor.extract")
6+
x0 := append([]int{}, 1) //@suggestedfix("append([]int{}, 1)", "refactor.extract")
77
str := "1"
88
b, err := strconv.Atoi(str) //@suggestedfix("strconv.Atoi(str)", "refactor.extract")
99
}

internal/lsp/testdata/extract/extract_variable/extract_func_call.go.golden

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,27 @@ func _() {
1010
b, err := strconv.Atoi(str) //@suggestedfix("strconv.Atoi(str)", "refactor.extract")
1111
}
1212

13+
-- suggestedfix_extract_func_call_6_8 --
14+
package extract
15+
16+
import "strconv"
17+
18+
func _() {
19+
x1 := append([]int{}, 1)
20+
x0 := x1 //@suggestedfix("append([]int{}, 1)", "refactor.extract")
21+
str := "1"
22+
b, err := strconv.Atoi(str) //@suggestedfix("strconv.Atoi(str)", "refactor.extract")
23+
}
24+
1325
-- suggestedfix_extract_func_call_8_12 --
1426
package extract
1527

1628
import "strconv"
1729

1830
func _() {
19-
a := append([]int{}, 1) //@suggestedfix("append([]int{}, 1)", "refactor.extract")
31+
x0 := append([]int{}, 1) //@suggestedfix("append([]int{}, 1)", "refactor.extract")
2032
str := "1"
21-
x0, x1 := strconv.Atoi(str)
22-
b, err := x0, x1 //@suggestedfix("strconv.Atoi(str)", "refactor.extract")
33+
x1, x2 := strconv.Atoi(str)
34+
b, err := x1, x2 //@suggestedfix("strconv.Atoi(str)", "refactor.extract")
2335
}
2436

0 commit comments

Comments
 (0)