Skip to content

Commit bfcbc1b

Browse files
committed
internal/refactor/inline: avoid unnecessary desugaring in selectors
When substituting into the receiver of a selector expression, we do not need to desugar the receiver argument, because the selector will still be valid. Add call site analysis to detect this case and undo the desugaring. Fixes golang/go#69442 Change-Id: I66c5e37b7b12e003f4e4f2e5c41eff909507097f Reviewed-on: https://go-review.googlesource.com/c/tools/+/630855 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent b93a72a commit bfcbc1b

File tree

5 files changed

+116
-25
lines changed

5 files changed

+116
-25
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func (Missing) M(a, c, r0 int) (r1 int) { //@codeaction("b", "refactor.rewrite.r
128128

129129
func _() {
130130
m := &Missing{}
131-
_ = (*m).M(1, 3, 4)
131+
_ = m.M(1, 3, 4)
132132
}
133133
-- @missingrecv/missingrecvuse/p.go --
134134
package missingrecvuse

internal/refactor/inline/callee.go

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,11 @@ type paramInfo struct {
395395
type refInfo struct {
396396
Offset int // FuncDecl-relative byte offset of parameter ref within body
397397
NeedType bool // type of substituted arg must be identical to type of param
398+
// IsSelectionOperand indicates whether the parameter reference is the
399+
// operand of a selection (param.f). If so, and param's argument is itself
400+
// a receiver parameter (a common case), we don't need to desugar (&v or *ptr)
401+
// the selection: if param.Method is a valid selection, then so is param.fieldOrMethod.
402+
IsSelectionOperand bool
398403
}
399404

400405
// analyzeParams computes information about parameters of function fn,
@@ -476,8 +481,9 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
476481
// the substituted expression is an untyped constant or unnamed type.
477482
ifaceParam := isNonTypeParamInterface(v.Type())
478483
ref := refInfo{
479-
Offset: int(n.Pos() - decl.Pos()),
480-
NeedType: !ifaceParam || !isAssignableOperand(info, stack),
484+
Offset: int(n.Pos() - decl.Pos()),
485+
NeedType: !ifaceParam || !isAssignableOperand(info, stack),
486+
IsSelectionOperand: isSelectionOperand(stack),
481487
}
482488
pinfo.Refs = append(pinfo.Refs, ref)
483489
pinfo.Shadow = addShadows(pinfo.Shadow, info, pinfo.Name, stack)
@@ -510,21 +516,7 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
510516
// then passing a different (yet assignable) type may affect type inference,
511517
// and so isAssignableOperand reports false.
512518
func isAssignableOperand(info *types.Info, stack []ast.Node) bool {
513-
var (
514-
parent ast.Node // the parent node surrounding expr
515-
expr, _ = stack[len(stack)-1].(ast.Expr) // the relevant expr, after stripping parentheses
516-
)
517-
if expr == nil {
518-
return false
519-
}
520-
for i := len(stack) - 2; i >= 0; i-- {
521-
if pexpr, ok := stack[i].(*ast.ParenExpr); ok {
522-
expr = pexpr
523-
} else {
524-
parent = stack[i]
525-
break
526-
}
527-
}
519+
parent, expr := exprContext(stack)
528520
if parent == nil {
529521
return false
530522
}
@@ -600,6 +592,40 @@ func isAssignableOperand(info *types.Info, stack []ast.Node) bool {
600592
return false // In all other cases, preserve the parameter type.
601593
}
602594

595+
// exprContext returns the innermost parent->child expression nodes for the
596+
// given outer-to-inner stack, after stripping parentheses.
597+
//
598+
// If no such context exists, returns (nil, nil).
599+
func exprContext(stack []ast.Node) (parent ast.Node, expr ast.Expr) {
600+
expr, _ = stack[len(stack)-1].(ast.Expr)
601+
if expr == nil {
602+
return nil, nil
603+
}
604+
for i := len(stack) - 2; i >= 0; i-- {
605+
if pexpr, ok := stack[i].(*ast.ParenExpr); ok {
606+
expr = pexpr
607+
} else {
608+
parent = stack[i]
609+
break
610+
}
611+
}
612+
if parent == nil {
613+
return nil, nil
614+
}
615+
return parent, expr
616+
}
617+
618+
// isSelectionOperand reports whether the innermost node of stack is operand
619+
// (x) of a selection x.f.
620+
func isSelectionOperand(stack []ast.Node) bool {
621+
parent, expr := exprContext(stack)
622+
if parent == nil {
623+
return false
624+
}
625+
sel, ok := parent.(*ast.SelectorExpr)
626+
return ok && sel.X == expr
627+
}
628+
603629
// addShadows returns the shadows set augmented by the set of names
604630
// locally shadowed at the location of the reference in the callee
605631
// (identified by the stack). The name of the reference itself is

internal/refactor/inline/inline.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ func (st *state) inline() (*Result, error) {
224224
f, err = parser.ParseFile(fset, "callee.go", content, mode)
225225
if err != nil {
226226
// Something has gone very wrong.
227-
logf("failed to reparse <<%s>>", string(content)) // debugging
227+
logf("failed to reparse <<%s>>: %v", string(content), err) // debugging
228228
return err
229229
}
230230
return nil
@@ -1395,6 +1395,7 @@ type argument struct {
13951395
freevars map[string]bool // free names of expr
13961396
substitutable bool // is candidate for substitution
13971397
variadic bool // is explicit []T{...} for eliminated variadic
1398+
desugaredRecv bool // is *recv or &recv, where operator was elided
13981399
}
13991400

14001401
// arguments returns the effective arguments of the call.
@@ -1488,12 +1489,14 @@ func (st *state) arguments(caller *Caller, calleeDecl *ast.FuncDecl, assign1 fun
14881489
// &recv
14891490
arg.expr = &ast.UnaryExpr{Op: token.AND, X: arg.expr}
14901491
arg.typ = types.NewPointer(arg.typ)
1492+
arg.desugaredRecv = true
14911493
} else if argIsPtr && !paramIsPtr {
14921494
// *recv
14931495
arg.expr = &ast.StarExpr{X: arg.expr}
14941496
arg.typ = typeparams.Deref(arg.typ)
14951497
arg.duplicable = false
14961498
arg.pure = false
1499+
arg.desugaredRecv = true
14971500
}
14981501
}
14991502
}
@@ -1691,6 +1694,23 @@ next:
16911694
logf("replacing parameter %q by argument %q",
16921695
param.info.Name, debugFormatNode(caller.Fset, arg.expr))
16931696
for _, ref := range param.info.Refs {
1697+
// Apply any transformations necessary for this reference.
1698+
argExpr := arg.expr
1699+
1700+
// If the reference itself is being selected, and we applied desugaring
1701+
// (an explicit &x or *x), we can undo that desugaring here as it is
1702+
// not necessary for a selector. We don't need to check addressability
1703+
// here because if we desugared, the receiver must have been
1704+
// addressable.
1705+
if ref.IsSelectionOperand && arg.desugaredRecv {
1706+
switch e := argExpr.(type) {
1707+
case *ast.UnaryExpr:
1708+
argExpr = e.X
1709+
case *ast.StarExpr:
1710+
argExpr = e.X
1711+
}
1712+
}
1713+
16941714
// If the reference requires exact type agreement (as reported by
16951715
// param.info.NeedType), wrap the argument in an explicit conversion
16961716
// if substitution might materially change its type. (We already did
@@ -1699,7 +1719,7 @@ next:
16991719
// This is only needed for substituted arguments. All other arguments
17001720
// are given explicit types in either a binding decl or when using the
17011721
// literalization strategy.
1702-
1722+
//
17031723
// If the types are identical, we can eliminate
17041724
// redundant type conversions such as this:
17051725
//
@@ -1716,7 +1736,6 @@ next:
17161736
// its (possibly "untyped") inherent type, so
17171737
// the conversion from untyped 1 to int32 is non-trivial even
17181738
// though both arg and param have identical types (int32).
1719-
argExpr := arg.expr
17201739
if ref.NeedType &&
17211740
!types.Identical(arg.typ, param.obj.Type()) &&
17221741
!trivialConversion(arg.constant, arg.typ, param.obj.Type()) {

internal/refactor/inline/inline_test.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,10 +1487,22 @@ func TestSubstitutionPreservesParameterType(t *testing.T) {
14871487
`func _() { T(1).g() }`,
14881488
},
14891489
{
1490-
"Implicit dereference is made explicit", // TODO(rfindley): fix unnecessary literalization.
1491-
`type T struct{ field int }; func (x T) f(y T) bool { return x == y && x.field == x.g() }; func (x T) g() int { return 0 }`,
1492-
`func _() { var t *T; var y T; _ = t.f(y) }`,
1493-
`func _() { var t *T; var y T; _ = func() bool { var x T = *t; return x == y && x.field == x.g() }() }`,
1490+
"Implicit reference is made explicit outside of selector",
1491+
`type T int; func (x *T) f() bool { return x == x.id() }; func (x *T) id() *T { return x }`,
1492+
`func _() { var t T; _ = t.f() }`,
1493+
`func _() { var t T; _ = &t == t.id() }`,
1494+
},
1495+
{
1496+
"Implicit parenthesized reference is not made explicit in selector",
1497+
`type T int; func (x *T) f() bool { return x == (x).id() }; func (x *T) id() *T { return x }`,
1498+
`func _() { var t T; _ = t.f() }`,
1499+
`func _() { var t T; _ = &t == (t).id() }`,
1500+
},
1501+
{
1502+
"Implicit dereference is made explicit outside of selector", // TODO(rfindley): avoid unnecessary literalization here
1503+
`type T int; func (x T) f() bool { return x == x.id() }; func (x T) id() T { return x }`,
1504+
`func _() { var t *T; _ = t.f() }`,
1505+
`func _() { var t *T; _ = func() bool { var x T = *t; return x == x.id() }() }`,
14941506
},
14951507
{
14961508
"Check for shadowing error on type used in the conversion.",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
This test checks that we don't introduce unnecessary (&v) or (*ptr) operations
2+
when calling a method on an addressable receiver.
3+
4+
-- go.mod --
5+
module testdata
6+
7+
go 1.20
8+
9+
-- main.go --
10+
package foo
11+
type T int
12+
13+
func (*T) F() {}
14+
15+
func (t *T) G() { t.F() }
16+
17+
func main() {
18+
var t T
19+
t.G() //@ inline(re"G", inline)
20+
}
21+
22+
-- inline --
23+
package foo
24+
25+
type T int
26+
27+
func (*T) F() {}
28+
29+
func (t *T) G() { t.F() }
30+
31+
func main() {
32+
var t T
33+
t.F() //@ inline(re"G", inline)
34+
}

0 commit comments

Comments
 (0)