Skip to content

Commit a819c61

Browse files
committed
internal/refactor/inline: eliminate unnecessary binding decl
In calls from one method to another with a pointer receiver, the inliner was inserting an unnecessary binding decl for the receiver var, because it relied on Selection.Indirect, but no actual indirection through the pointer is going on, so the receiver is pure. Instead, clear the purity flag only if there's a load through an embedded field. Also, fix a latent bug in the BlockStmt brace eliding logic: we forgot to include function params/results in scope when the block is the body of a function. Plus tests for both. Change-Id: I7e149f55fb344e5f733cdd6811d060ef0dc42883 Reviewed-on: https://go-review.googlesource.com/c/tools/+/532177 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 102b64b commit a819c61

File tree

2 files changed

+53
-5
lines changed

2 files changed

+53
-5
lines changed

internal/refactor/inline/inline.go

+30-5
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
110110
// (see "statement theory").
111111
elideBraces := false
112112
if newBlock, ok := res.new.(*ast.BlockStmt); ok {
113-
parent := caller.path[nodeIndex(caller.path, res.old)+1]
113+
i := nodeIndex(caller.path, res.old)
114+
parent := caller.path[i+1]
114115
var body []ast.Stmt
115116
switch parent := parent.(type) {
116117
case *ast.BlockStmt:
@@ -121,11 +122,34 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
121122
body = parent.Body
122123
}
123124
if body != nil {
125+
callerNames := declares(body)
126+
127+
// If BlockStmt is a function body,
128+
// include its receiver, params, and results.
129+
addFieldNames := func(fields *ast.FieldList) {
130+
if fields != nil {
131+
for _, field := range fields.List {
132+
for _, id := range field.Names {
133+
callerNames[id.Name] = true
134+
}
135+
}
136+
}
137+
}
138+
switch f := caller.path[i+2].(type) {
139+
case *ast.FuncDecl:
140+
addFieldNames(f.Recv)
141+
addFieldNames(f.Type.Params)
142+
addFieldNames(f.Type.Results)
143+
case *ast.FuncLit:
144+
addFieldNames(f.Type.Params)
145+
addFieldNames(f.Type.Results)
146+
}
147+
124148
if len(callerLabels(caller.path)) > 0 {
125149
// TODO(adonovan): be more precise and reject
126150
// only forward gotos across the inlined block.
127151
logf("keeping block braces: caller uses control labels")
128-
} else if intersects(declares(newBlock.List), declares(body)) {
152+
} else if intersects(declares(newBlock.List), callerNames) {
129153
logf("keeping block braces: avoids name conflict")
130154
} else {
131155
elideBraces = true
@@ -1060,16 +1084,16 @@ func arguments(caller *Caller, calleeDecl *ast.FuncDecl, assign1 func(*types.Var
10601084
debugFormatNode(caller.Fset, caller.Call.Fun),
10611085
fld.Name())
10621086
}
1087+
if is[*types.Pointer](arg.typ.Underlying()) {
1088+
arg.pure = false // implicit *ptr operation => impure
1089+
}
10631090
arg.expr = &ast.SelectorExpr{
10641091
X: arg.expr,
10651092
Sel: makeIdent(fld.Name()),
10661093
}
10671094
arg.typ = fld.Type()
10681095
arg.duplicable = false
10691096
}
1070-
if seln.Indirect() {
1071-
arg.pure = false // one or more implicit *ptr operation => impure
1072-
}
10731097

10741098
// Make * or & explicit.
10751099
argIsPtr := arg.typ != deref(arg.typ)
@@ -1083,6 +1107,7 @@ func arguments(caller *Caller, calleeDecl *ast.FuncDecl, assign1 func(*types.Var
10831107
arg.expr = &ast.StarExpr{X: arg.expr}
10841108
arg.typ = deref(arg.typ)
10851109
arg.duplicable = false
1110+
arg.pure = false
10861111
}
10871112
}
10881113
}

internal/refactor/inline/inline_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,29 @@ func TestEmbeddedFields(t *testing.T) {
732732
`func _(v V) { (*V).f(&v) }`,
733733
`func _(v V) { print(&(&v).U.T) }`,
734734
},
735+
{
736+
// x is a single-assign var, and x.f does not load through a pointer
737+
// (despite types.Selection.Indirect=true), so x is pure.
738+
"No binding decl is required for recv in method-to-method calls.",
739+
`type T struct{}; func (x *T) f() { g(); print(*x) }; func g()`,
740+
`func (x *T) _() { x.f() }`,
741+
`func (x *T) _() {
742+
g()
743+
print(*x)
744+
}`,
745+
},
746+
{
747+
"Same, with implicit &recv.",
748+
`type T struct{}; func (x *T) f() { g(); print(*x) }; func g()`,
749+
`func (x T) _() { x.f() }`,
750+
`func (x T) _() {
751+
{
752+
var x *T = &x
753+
g()
754+
print(*x)
755+
}
756+
}`,
757+
},
735758
})
736759
}
737760

0 commit comments

Comments
 (0)