Skip to content

Commit 1058109

Browse files
adonovangopherbot
authored andcommitted
internal/refactor/inline: don't insert unnecessary parens
This change causes parens to be inserted only if needed around the new node in the replacement. Parens are needed if the child is a unary or binary operation and either (a) the parent is a unary or binary of higher precedence or (b) the child is the operand of a postfix operator. Also, tests. Updates golang/go#63259 Change-Id: I12ee95ad79b4921755d9bc87952f6404cb166e2b Reviewed-on: https://go-review.googlesource.com/c/tools/+/532098 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]> Auto-Submit: Alan Donovan <[email protected]>
1 parent d8e94f2 commit 1058109

File tree

7 files changed

+152
-30
lines changed

7 files changed

+152
-30
lines changed

gopls/internal/regtest/marker/testdata/codeaction/inline.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func add(x, y int) int { return x + y }
1717
package a
1818

1919
func _() {
20-
println((1 + 2)) //@codeaction("refactor.inline", "add", ")", inline)
20+
println(1 + 2) //@codeaction("refactor.inline", "add", ")", inline)
2121
}
2222

2323
func add(x, y int) int { return x + y }

internal/refactor/inline/inline.go

+99-22
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,31 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
7373
assert(res.old != nil, "old is nil")
7474
assert(res.new != nil, "new is nil")
7575

76+
// A single return operand inlined to a unary
77+
// expression context may need parens. Otherwise:
78+
// func two() int { return 1+1 }
79+
// print(-two()) => print(-1+1) // oops!
80+
//
81+
// Usually it is not necessary to insert ParenExprs
82+
// as the formatter is smart enough to insert them as
83+
// needed by the context. But the res.{old,new}
84+
// substitution is done by formatting res.new in isolation
85+
// and then splicing its text over res.old, so the
86+
// formatter doesn't see the parent node and cannot do
87+
// the right thing. (One solution would be to always
88+
// format the enclosing node of old, but that requires
89+
// non-lossy comment handling, #20744.)
90+
//
91+
// So, we must analyze the call's context
92+
// to see whether ambiguity is possible.
93+
// For example, if the context is x[y:z], then
94+
// the x subtree is subject to precedence ambiguity
95+
// (replacing x by p+q would give p+q[y:z] which is wrong)
96+
// but the y and z subtrees are safe.
97+
if needsParens(caller.path, res.old, res.new) {
98+
res.new = &ast.ParenExpr{X: res.new.(ast.Expr)}
99+
}
100+
76101
// Don't call replaceNode(caller.File, res.old, res.new)
77102
// as it mutates the caller's syntax tree.
78103
// Instead, splice the file, replacing the extent of the "old"
@@ -727,29 +752,8 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
727752
if callee.NumResults == 1 {
728753
logf("strategy: reduce expr-context call to { return expr }")
729754

730-
// A single return operand inlined to a unary
731-
// expression context may need parens. Otherwise:
732-
// func two() int { return 1+1 }
733-
// print(-two()) => print(-1+1) // oops!
734-
//
735-
// Usually it is not necessary to insert ParenExprs
736-
// as the formatter is smart enough to insert them as
737-
// needed by the context. But the res.{old,new}
738-
// substitution is done by formatting res.new in isolation
739-
// and then splicing its text over res.old, so the
740-
// formatter doesn't see the parent node and cannot do
741-
// the right thing. (One solution would be to always
742-
// format the enclosing node of old, but that requires
743-
// non-lossy comment handling, #20744.)
744-
//
745-
// TODO(adonovan): do better by analyzing 'context'
746-
// to see whether ambiguity is possible.
747-
// For example, if the context is x[y:z], then
748-
// the x subtree is subject to precedence ambiguity
749-
// (replacing x by p+q would give p+q[y:z] which is wrong)
750-
// but the y and z subtrees are safe.
751755
res.old = caller.Call
752-
res.new = &ast.ParenExpr{X: results[0]}
756+
res.new = results[0]
753757
} else {
754758
logf("strategy: reduce spread-context call to { return expr }")
755759

@@ -2279,3 +2283,76 @@ func consistentOffsets(caller *Caller) bool {
22792283
}
22802284
return is[*ast.CallExpr](expr)
22812285
}
2286+
2287+
// needsParens reports whether parens are required to avoid ambiguity
2288+
// around the new node replacing the specified old node (which is some
2289+
// ancestor of the CallExpr identified by its PathEnclosingInterval).
2290+
func needsParens(callPath []ast.Node, old, new ast.Node) bool {
2291+
// Find enclosing old node and its parent.
2292+
// TODO(adonovan): Use index[ast.Node]() in go1.20.
2293+
i := -1
2294+
for i = range callPath {
2295+
if callPath[i] == old {
2296+
break
2297+
}
2298+
}
2299+
if i == -1 {
2300+
panic("not found")
2301+
}
2302+
2303+
// There is no precedence ambiguity when replacing
2304+
// (e.g.) a statement enclosing the call.
2305+
if !is[ast.Expr](old) {
2306+
return false
2307+
}
2308+
2309+
// An expression beneath a non-expression
2310+
// has no precedence ambiguity.
2311+
parent, ok := callPath[i+1].(ast.Expr)
2312+
if !ok {
2313+
return false
2314+
}
2315+
2316+
precedence := func(n ast.Node) int {
2317+
switch n := n.(type) {
2318+
case *ast.UnaryExpr, *ast.StarExpr:
2319+
return token.UnaryPrec
2320+
case *ast.BinaryExpr:
2321+
return n.Op.Precedence()
2322+
}
2323+
return -1
2324+
}
2325+
2326+
// Parens are not required if the new node
2327+
// is not unary or binary.
2328+
newprec := precedence(new)
2329+
if newprec < 0 {
2330+
return false
2331+
}
2332+
2333+
// Parens are required if parent and child are both
2334+
// unary or binary and the parent has higher precedence.
2335+
if precedence(parent) > newprec {
2336+
return true
2337+
}
2338+
2339+
// Was the old node the operand of a postfix operator?
2340+
// f().sel
2341+
// f()[i:j]
2342+
// f()[i]
2343+
// f().(T)
2344+
// f()(x)
2345+
switch parent := parent.(type) {
2346+
case *ast.SelectorExpr:
2347+
return parent.X == old
2348+
case *ast.IndexExpr:
2349+
return parent.X == old
2350+
case *ast.SliceExpr:
2351+
return parent.X == old
2352+
case *ast.TypeAssertExpr:
2353+
return parent.X == old
2354+
case *ast.CallExpr:
2355+
return parent.Fun == old
2356+
}
2357+
return false
2358+
}

internal/refactor/inline/inline_test.go

+47-2
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ func TestBasics(t *testing.T) {
359359
"Basic",
360360
`func f(x int) int { return x }`,
361361
`var _ = f(0)`,
362-
`var _ = (0)`,
362+
`var _ = 0`,
363363
},
364364
{
365365
"Empty body, no arg effects.",
@@ -387,6 +387,51 @@ func TestBasics(t *testing.T) {
387387
})
388388
}
389389

390+
func TestPrecedenceParens(t *testing.T) {
391+
// Ensure that parens are inserted when (and only when) necessary
392+
// around the replacement for the call expression. (This is a special
393+
// case in the way the inliner uses a combination of AST formatting
394+
// for the call and text splicing for the rest of the file.)
395+
runTests(t, []testcase{
396+
{
397+
"Multiplication in addition context (no parens).",
398+
`func f(x, y int) int { return x * y }`,
399+
`func _() { _ = 1 + f(2, 3) }`,
400+
`func _() { _ = 1 + 2*3 }`,
401+
},
402+
{
403+
"Addition in multiplication context (parens).",
404+
`func f(x, y int) int { return x + y }`,
405+
`func _() { _ = 1 * f(2, 3) }`,
406+
`func _() { _ = 1 * (2 + 3) }`,
407+
},
408+
{
409+
"Addition in negation context (parens).",
410+
`func f(x, y int) int { return x + y }`,
411+
`func _() { _ = -f(1, 2) }`,
412+
`func _() { _ = -(1 + 2) }`,
413+
},
414+
{
415+
"Addition in call context (no parens).",
416+
`func f(x, y int) int { return x + y }`,
417+
`func _() { println(f(1, 2)) }`,
418+
`func _() { println(1 + 2) }`,
419+
},
420+
{
421+
"Addition in slice operand context (parens).",
422+
`func f(x, y string) string { return x + y }`,
423+
`func _() { _ = f("x", "y")[1:2] }`,
424+
`func _() { _ = ("x" + "y")[1:2] }`,
425+
},
426+
{
427+
"String literal in slice operand context (no parens).",
428+
`func f(x string) string { return x }`,
429+
`func _() { _ = f("xy")[1:2] }`,
430+
`func _() { _ = "xy"[1:2] }`,
431+
},
432+
})
433+
}
434+
390435
func TestSubstitution(t *testing.T) {
391436
runTests(t, []testcase{
392437
{
@@ -421,7 +466,7 @@ func TestTailCallStrategy(t *testing.T) {
421466
"Tail call.",
422467
`func f() int { return 1 }`,
423468
`func _() int { return f() }`,
424-
`func _() int { return (1) }`,
469+
`func _() int { return 1 }`,
425470
},
426471
{
427472
"Void tail call.",

internal/refactor/inline/testdata/basic-err.txtar

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ package a
1919

2020
import "io"
2121

22-
var _ = (io.EOF.Error()) //@ inline(re"getError", getError)
22+
var _ = io.EOF.Error() //@ inline(re"getError", getError)
2323

2424
func getError(err error) string { return err.Error() }

internal/refactor/inline/testdata/basic-reduce.txtar

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func zero() int { return 0 }
1414
-- zero --
1515
package a
1616

17-
var _ = (0) //@ inline(re"zero", zero)
17+
var _ = 0 //@ inline(re"zero", zero)
1818

1919
func zero() int { return 0 }
2020

@@ -46,5 +46,5 @@ var _ = add(len(""), 2) //@ inline(re"add", add2)
4646
-- add2 --
4747
package a
4848

49-
var _ = (len("") + 2) //@ inline(re"add", add2)
49+
var _ = len("") + 2 //@ inline(re"add", add2)
5050

internal/refactor/inline/testdata/comments.txtar

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func g() int { return 1 /*hello*/ + /*there*/ 1 }
5151
package a
5252

5353
func _() {
54-
println((1 + 1)) //@ inline(re"g", g)
54+
println(1 + 1) //@ inline(re"g", g)
5555
}
5656

5757
func g() int { return 1 /*hello*/ + /*there*/ 1 }

internal/refactor/inline/testdata/param-subst.txtar

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ func add(x, y int) int { return x + 2*y }
1414
-- add --
1515
package a
1616

17-
var _ = (2 + 2*(1+1)) //@ inline(re"add", add)
17+
var _ = 2 + 2*(1+1) //@ inline(re"add", add)
1818

1919
func add(x, y int) int { return x + 2*y }

0 commit comments

Comments
 (0)