Skip to content

Commit 790ffbe

Browse files
author
Fata Nugraha
committed
handle variable name collision better
1 parent ab1f0bd commit 790ffbe

2 files changed

Lines changed: 146 additions & 26 deletions

File tree

gopls/internal/golang/extract.go

Lines changed: 85 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,8 @@ func extractFunctionMethod(cpkg *cache.Package, pgf *parsego.File, start, end to
630630
allReturnsFinalErr = true // all ReturnStmts have final 'err' expression
631631
hasReturn = false // selection contains a ReturnStmt
632632
filter = []ast.Node{(*ast.ReturnStmt)(nil), (*ast.FuncLit)(nil)}
633+
634+
origRetStmts []*ast.ReturnStmt // return stmts in source order, for type lookups
633635
)
634636
curEnclosing.Inspect(filter, func(cur inspector.Cursor) (descend bool) {
635637
if funcLit, ok := cur.Node().(*ast.FuncLit); ok {
@@ -643,6 +645,8 @@ func extractFunctionMethod(cpkg *cache.Package, pgf *parsego.File, start, end to
643645
}
644646
hasReturn = true
645647

648+
origRetStmts = append(origRetStmts, ret)
649+
646650
if cur.Parent() == curStart.Parent() {
647651
hasNonNestedReturn = true
648652
}
@@ -1117,19 +1121,9 @@ func extractFunctionMethod(cpkg *cache.Package, pgf *parsego.File, start, end to
11171121
// Expand multi-value function calls in return statements.
11181122
// If a return contains a single CallExpr that is being augmented with new
11191123
// return values, the call return values must be expanded to maintain valid syntax.
1120-
ast.Inspect(extractedBlock, func(n ast.Node) bool {
1121-
switch n := n.(type) {
1122-
case *ast.BlockStmt:
1123-
n.List = expandFunctionCallReturnValues(n.List, info, newFuncResults, file, start)
1124-
case *ast.CaseClause:
1125-
n.Body = expandFunctionCallReturnValues(n.Body, info, newFuncResults, file, start)
1126-
case *ast.FuncLit:
1127-
// Don't descend into nested functions.
1128-
return false
1129-
}
1130-
1131-
return true
1132-
})
1124+
if err := expandMultiValueCallReturns(extractedBlock, info, newFuncResults, file, start, origRetStmts); err != nil {
1125+
return nil, nil, err
1126+
}
11331127

11341128
// Build the extracted function. We format the function declaration and body
11351129
// separately, so that comments are printed relative to the extracted
@@ -1231,8 +1225,59 @@ func extractFunctionMethod(cpkg *cache.Package, pgf *parsego.File, start, end to
12311225
}, nil
12321226
}
12331227

1234-
// expandFunctionCallReturnValues expands the return value of function calls when necessary.
1235-
func expandFunctionCallReturnValues(stmts []ast.Stmt, info *types.Info, newFuncResults *ast.FieldList, file *ast.File, start token.Pos) []ast.Stmt {
1228+
// expandMultiValueCallReturns expands multi-value function calls in return
1229+
// statements within the extracted block.
1230+
func expandMultiValueCallReturns(extractedBlock *ast.BlockStmt, info *types.Info, newFuncResults *ast.FieldList, file *ast.File, start token.Pos, origRetStmts []*ast.ReturnStmt) error {
1231+
// The re-parsed AST has no type information, so we pair its return stmts
1232+
// with the original (type-checked) ones to look up types for naming.
1233+
//
1234+
// The pairing is done as a separate pass because the second pass doesn't
1235+
// exactly visit the ReturnStmt in the same way as how the origRetStmts
1236+
// is collected (via ast.Inspect).
1237+
origRetMap := map[*ast.ReturnStmt]*ast.ReturnStmt{}
1238+
origIdx := 0
1239+
ast.Inspect(extractedBlock, func(n ast.Node) bool {
1240+
switch n := n.(type) {
1241+
case *ast.ReturnStmt:
1242+
if origIdx < len(origRetStmts) {
1243+
origRetMap[n] = origRetStmts[origIdx]
1244+
origIdx++
1245+
} else {
1246+
// The re-parsed AST may have injected returns appended
1247+
// at the end with no original counterpart but it is ok since
1248+
// we can guarantee it will not have CallExpr in it.
1249+
return false
1250+
}
1251+
case *ast.FuncLit:
1252+
return false // don't descend into closures.
1253+
}
1254+
1255+
return true
1256+
})
1257+
1258+
// Traverse the extracted block again and do the actual expansion.
1259+
var expandErr error
1260+
ast.Inspect(extractedBlock, func(n ast.Node) bool {
1261+
if expandErr != nil {
1262+
return false
1263+
}
1264+
switch n := n.(type) {
1265+
case *ast.BlockStmt:
1266+
n.List, expandErr = expandFunctionCallReturnValues(n.List, info, newFuncResults, file, start, origRetMap)
1267+
case *ast.CaseClause:
1268+
n.Body, expandErr = expandFunctionCallReturnValues(n.Body, info, newFuncResults, file, start, origRetMap)
1269+
case *ast.FuncLit:
1270+
return false // don't descend into closures.
1271+
}
1272+
1273+
return true
1274+
})
1275+
return expandErr
1276+
}
1277+
1278+
// expandFunctionCallReturnValues expands the return value of function calls
1279+
// in the given statement list when necessary.
1280+
func expandFunctionCallReturnValues(stmts []ast.Stmt, info *types.Info, newFuncResults *ast.FieldList, file *ast.File, start token.Pos, origRetMap map[*ast.ReturnStmt]*ast.ReturnStmt) ([]ast.Stmt, error) {
12361281
result := make([]ast.Stmt, 0, len(stmts))
12371282
for _, stmt := range stmts {
12381283
result = append(result, stmt)
@@ -1261,13 +1306,32 @@ func expandFunctionCallReturnValues(stmts []ast.Stmt, info *types.Info, newFuncR
12611306
// type information here. This should be correct assuming the original code
12621307
// is valid to begin with.
12631308
expandedVars := make([]ast.Expr, len(newFuncResults.List)-len(retStmt.Results)+1) // plus one to replace the CallExpr
1264-
prevIdx := 1
1309+
1310+
// Use type information from the original return statement to
1311+
// generate type-aware names and detect scope collisions.
1312+
origRet := origRetMap[retStmt]
1313+
if origRet == nil {
1314+
return nil, bug.Errorf("no original return statement for re-parsed return")
1315+
}
1316+
1317+
scopePos := origRet.Pos()
1318+
origCallExpr := origRet.Results[0].(*ast.CallExpr)
1319+
sig := info.TypeOf(origCallExpr.Fun).Underlying().(*types.Signature)
1320+
tup := sig.Results()
1321+
1322+
// Generate type-aware names for each expanded return values.
1323+
prevIdxByPrefix := map[string]int{}
12651324
for i := range expandedVars {
1266-
// ideally we want to generate a better name (e.g. `errX` for error values)
1267-
// but we don't have type info at this stage.
1268-
name, idx := freshName(info, file, start, "v", prevIdx)
1325+
prefix := "v"
1326+
if name, ok := varNameForType(tup.At(i).Type()); ok {
1327+
prefix = name
1328+
}
1329+
1330+
prev := prevIdxByPrefix[prefix]
1331+
name, next := freshName(info, file, scopePos, prefix, prev)
1332+
prevIdxByPrefix[prefix] = next
1333+
12691334
expandedVars[i] = ast.NewIdent(name)
1270-
prevIdx = idx
12711335
}
12721336

12731337
result[len(result)-1] = ast.Stmt(&ast.AssignStmt{
@@ -1282,7 +1346,7 @@ func expandFunctionCallReturnValues(stmts []ast.Stmt, info *types.Info, newFuncR
12821346
})
12831347
}
12841348

1285-
return result
1349+
return result, nil
12861350
}
12871351

12881352
// isSelector reports if e is the selector expr <x>, <sel>. It works for pointer and non-pointer selector expressions.

gopls/internal/test/marker/testdata/codeaction/functionextraction_issue77240.txt

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ func Fun(v2 int) (int, int, error) {
4343
func newFunction(v2 int) (int, int, error, bool) {
4444
switch v2 { //@codeaction("switch", "refactor.extract.function", end=end, result=ext)
4545
case 1:
46-
v1, // also a comment!
47-
v3, v4 := doOne()
48-
return v1, v3, v4, true // a comment!
46+
i, // also a comment!
47+
i1, err := doOne()
48+
return i, i1, err, true // a comment!
4949
case 2:
50-
v1, v3, v4 := doTwo()
51-
return v1, v3, v4, true
50+
i, i1, err := doTwo()
51+
return i, i1, err, true
5252
}
5353
return 0, 0, nil, false
5454
}
@@ -61,3 +61,59 @@ func doTwo() (int, int, error) {
6161
return 0, 2, nil
6262
}
6363

64+
-- p2/p.go --
65+
package extract
66+
67+
import "fmt"
68+
69+
func Fun(v2 int) (int, int, error) {
70+
switch v2 { //@codeaction("switch", "refactor.extract.function", end=end2, result=ext2)
71+
case 1:
72+
i := v2 + 1
73+
i1 := v2 + 2
74+
err := fmt.Errorf("foo")
75+
fmt.Println(i, i1, err)
76+
return doOne()
77+
case 2:
78+
return doTwo()
79+
} //@loc(end2, "}")
80+
81+
return 1, 3, nil
82+
}
83+
84+
func doOne() (int, int, error) { return 0, 1, nil }
85+
func doTwo() (int, int, error) { return 0, 2, nil }
86+
87+
-- @ext2/p2/p.go --
88+
package extract
89+
90+
import "fmt"
91+
92+
func Fun(v2 int) (int, int, error) {
93+
i, i1, err, shouldReturn := newFunction(v2)
94+
if shouldReturn {
95+
return i, i1, err
96+
} //@loc(end2, "}")
97+
98+
return 1, 3, nil
99+
}
100+
101+
func newFunction(v2 int) (int, int, error, bool) {
102+
switch v2 { //@codeaction("switch", "refactor.extract.function", end=end2, result=ext2)
103+
case 1:
104+
i := v2 + 1
105+
i1 := v2 + 2
106+
err := fmt.Errorf("foo")
107+
fmt.Println(i, i1, err)
108+
i2, i3, err1 := doOne()
109+
return i2, i3, err1, true
110+
case 2:
111+
i, i1, err := doTwo()
112+
return i, i1, err, true
113+
}
114+
return 0, 0, nil, false
115+
}
116+
117+
func doOne() (int, int, error) { return 0, 1, nil }
118+
func doTwo() (int, int, error) { return 0, 2, nil }
119+

0 commit comments

Comments
 (0)