@@ -73,6 +73,31 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
73
73
assert (res .old != nil , "old is nil" )
74
74
assert (res .new != nil , "new is nil" )
75
75
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
+
76
101
// Don't call replaceNode(caller.File, res.old, res.new)
77
102
// as it mutates the caller's syntax tree.
78
103
// 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
727
752
if callee .NumResults == 1 {
728
753
logf ("strategy: reduce expr-context call to { return expr }" )
729
754
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.
751
755
res .old = caller .Call
752
- res .new = & ast. ParenExpr { X : results [0 ]}
756
+ res .new = results [0 ]
753
757
} else {
754
758
logf ("strategy: reduce spread-context call to { return expr }" )
755
759
@@ -2279,3 +2283,76 @@ func consistentOffsets(caller *Caller) bool {
2279
2283
}
2280
2284
return is [* ast.CallExpr ](expr )
2281
2285
}
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
+ }
0 commit comments