Skip to content

Commit 30a3bd9

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/golang: refactor.extract.variable: allow all exprs
This CL changes "Extract variable" to allow its use on all legitimate expressions. Previously it had a short allowlist of obviously valid forms based on syntax alone, which excluded anything that might be a type (not a term). It also used to trigger spuriously on (e.g.) an ExprStmt that calls a nullary function, or a two-valued comma,ok expression. Now, it consults the type checker for the definitive answer. Also, it uses PathEnclosingInterval's "exact" flag so that extraneous whitespace is permitted. + test Fixes golang/go#70561 Change-Id: I45471ca99e11b04db02671fbd1702dec2547b258 Reviewed-on: https://go-review.googlesource.com/c/tools/+/631777 Reviewed-by: Robert Findley <rfindley@google.com> Auto-Submit: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 0edd1ab commit 30a3bd9

File tree

4 files changed

+57
-26
lines changed

4 files changed

+57
-26
lines changed

gopls/internal/golang/codeaction.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ var codeActionProducers = [...]codeActionProducer{
236236
{kind: settings.RefactorExtractFunction, fn: refactorExtractFunction},
237237
{kind: settings.RefactorExtractMethod, fn: refactorExtractMethod},
238238
{kind: settings.RefactorExtractToNewFile, fn: refactorExtractToNewFile},
239-
{kind: settings.RefactorExtractVariable, fn: refactorExtractVariable},
239+
{kind: settings.RefactorExtractVariable, fn: refactorExtractVariable, needPkg: true},
240240
{kind: settings.RefactorInlineCall, fn: refactorInlineCall, needPkg: true},
241241
{kind: settings.RefactorRewriteChangeQuote, fn: refactorRewriteChangeQuote},
242242
{kind: settings.RefactorRewriteFillStruct, fn: refactorRewriteFillStruct, needPkg: true},
@@ -465,7 +465,7 @@ func refactorExtractMethod(ctx context.Context, req *codeActionsRequest) error {
465465
// refactorExtractVariable produces "Extract variable" code actions.
466466
// See [extractVariable] for command implementation.
467467
func refactorExtractVariable(ctx context.Context, req *codeActionsRequest) error {
468-
if _, _, ok, _ := canExtractVariable(req.start, req.end, req.pgf.File); ok {
468+
if _, _, ok, _ := canExtractVariable(req.pkg.TypesInfo(), req.pgf.File, req.start, req.end); ok {
469469
req.addApplyFixAction("Extract variable", fixExtractVariable, req.loc)
470470
}
471471
return nil

gopls/internal/golang/extract.go

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,14 @@ import (
2828

2929
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) {
3030
tokFile := fset.File(file.FileStart)
31-
expr, path, ok, err := canExtractVariable(start, end, file)
31+
expr, path, ok, err := canExtractVariable(info, file, start, end)
3232
if !ok {
3333
return nil, nil, fmt.Errorf("extractVariable: cannot extract %s: %v", safetoken.StartPosition(fset, start), err)
3434
}
3535

36-
// Create new AST node for extracted code.
36+
// Create new AST node for extracted expression.
3737
var lhsNames []string
3838
switch expr := expr.(type) {
39-
// TODO: stricter rules for selectorExpr.
40-
case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.SliceExpr,
41-
*ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr:
42-
lhsName, _ := generateAvailableName(expr.Pos(), path, pkg, info, "x", 0)
43-
lhsNames = append(lhsNames, lhsName)
4439
case *ast.CallExpr:
4540
tup, ok := info.TypeOf(expr).(*types.Tuple)
4641
if !ok {
@@ -57,8 +52,11 @@ func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file
5752
lhsName, idx = generateAvailableName(expr.Pos(), path, pkg, info, "x", idx)
5853
lhsNames = append(lhsNames, lhsName)
5954
}
55+
6056
default:
61-
return nil, nil, fmt.Errorf("cannot extract %T", expr)
57+
// TODO: stricter rules for selectorExpr.
58+
lhsName, _ := generateAvailableName(expr.Pos(), path, pkg, info, "x", 0)
59+
lhsNames = append(lhsNames, lhsName)
6260
}
6361

6462
// TODO: There is a bug here: for a variable declared in a labeled
@@ -110,33 +108,31 @@ func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file
110108

111109
// canExtractVariable reports whether the code in the given range can be
112110
// extracted to a variable.
113-
func canExtractVariable(start, end token.Pos, file *ast.File) (ast.Expr, []ast.Node, bool, error) {
111+
func canExtractVariable(info *types.Info, file *ast.File, start, end token.Pos) (ast.Expr, []ast.Node, bool, error) {
114112
if start == end {
115-
return nil, nil, false, fmt.Errorf("start and end are equal")
113+
return nil, nil, false, fmt.Errorf("empty selection")
114+
}
115+
path, exact := astutil.PathEnclosingInterval(file, start, end)
116+
if !exact {
117+
return nil, nil, false, fmt.Errorf("selection is not an expression")
116118
}
117-
path, _ := astutil.PathEnclosingInterval(file, start, end)
118119
if len(path) == 0 {
119-
return nil, nil, false, fmt.Errorf("no path enclosing interval")
120+
return nil, nil, false, bug.Errorf("no path enclosing interval")
120121
}
121122
for _, n := range path {
122123
if _, ok := n.(*ast.ImportSpec); ok {
123124
return nil, nil, false, fmt.Errorf("cannot extract variable in an import block")
124125
}
125126
}
126-
node := path[0]
127-
if start != node.Pos() || end != node.End() {
128-
return nil, nil, false, fmt.Errorf("range does not map to an AST node")
129-
}
130-
expr, ok := node.(ast.Expr)
127+
expr, ok := path[0].(ast.Expr)
131128
if !ok {
132-
return nil, nil, false, fmt.Errorf("node is not an expression")
129+
return nil, nil, false, fmt.Errorf("selection is not an expression") // e.g. statement
133130
}
134-
switch expr.(type) {
135-
case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.CallExpr,
136-
*ast.SliceExpr, *ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr:
137-
return expr, path, true, nil
131+
if tv, ok := info.Types[expr]; !ok || !tv.IsValue() || tv.Type == nil || tv.HasOk() {
132+
// e.g. type, builtin, x.(type), 2-valued m[k], or ill-typed
133+
return nil, nil, false, fmt.Errorf("selection is not a single-valued expression")
138134
}
139-
return nil, nil, false, fmt.Errorf("cannot extract an %T to a variable", expr)
135+
return expr, path, true, nil
140136
}
141137

142138
// Calculate indentation for insertion.

gopls/internal/test/integration/misc/codeactions_test.go

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
This test checks that extract variable permits:
2+
- extraneous whitespace in the selection
3+
- function literals
4+
- pointer dereference expressions
5+
- parenthesized expressions
6+
7+
-- a.go --
8+
package a
9+
10+
func _(ptr *int) {
11+
var _ = 1 + 2 + 3 //@codeaction("1 + 2 ", "refactor.extract.variable", edit=spaces)
12+
var _ = func() {} //@codeaction("func() {}", "refactor.extract.variable", edit=funclit)
13+
var _ = *ptr //@codeaction("*ptr", "refactor.extract.variable", edit=ptr)
14+
var _ = (ptr) //@codeaction("(ptr)", "refactor.extract.variable", edit=paren)
15+
}
16+
17+
-- @spaces/a.go --
18+
@@ -4 +4,2 @@
19+
- var _ = 1 + 2 + 3 //@codeaction("1 + 2 ", "refactor.extract.variable", edit=spaces)
20+
+ x := 1 + 2
21+
+ var _ = x+ 3 //@codeaction("1 + 2 ", "refactor.extract.variable", edit=spaces)
22+
-- @funclit/a.go --
23+
@@ -5 +5,2 @@
24+
- var _ = func() {} //@codeaction("func() {}", "refactor.extract.variable", edit=funclit)
25+
+ x := func() {}
26+
+ var _ = x //@codeaction("func() {}", "refactor.extract.variable", edit=funclit)
27+
-- @ptr/a.go --
28+
@@ -6 +6,2 @@
29+
- var _ = *ptr //@codeaction("*ptr", "refactor.extract.variable", edit=ptr)
30+
+ x := *ptr
31+
+ var _ = x //@codeaction("*ptr", "refactor.extract.variable", edit=ptr)
32+
-- @paren/a.go --
33+
@@ -7 +7,2 @@
34+
- var _ = (ptr) //@codeaction("(ptr)", "refactor.extract.variable", edit=paren)
35+
+ x := (ptr)
36+
+ var _ = x //@codeaction("(ptr)", "refactor.extract.variable", edit=paren)

0 commit comments

Comments
 (0)