Skip to content

Commit 53e637b

Browse files
committed
internal/refactor/inline: improve check for last uses of free vars
This showed up when I used the inliner for removing parameters: we should be more precise about detecting the last use of a local var. A TODO is left to make this perfect, which will be done in a subsequent CL. For golang/go#63534 Change-Id: Id5c753f3e7ae51e07db1d29a59e82e51c6d5952c Reviewed-on: https://go-review.googlesource.com/c/tools/+/535335 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 61bb3e9 commit 53e637b

File tree

2 files changed

+63
-5
lines changed

2 files changed

+63
-5
lines changed

internal/refactor/inline/inline.go

+33-5
Original file line numberDiff line numberDiff line change
@@ -1258,11 +1258,11 @@ next:
12581258
// remove the last reference to a caller local var.
12591259
if caller.enclosingFunc != nil {
12601260
for free := range arg.freevars {
1261-
if v, ok := caller.lookup(free).(*types.Var); ok && within(v.Pos(), caller.enclosingFunc.Body) {
1262-
// TODO(adonovan): be more precise and check that v
1263-
// is indeed referenced only by call arguments.
1264-
// Better: proceed, but blank out its declaration as needed.
1265-
logf("keeping param %q: arg contains perhaps the last reference to possible caller local %v @ %v",
1261+
// TODO(rfindley): we can get this 100% right by looking for
1262+
// references among other arguments which have non-zero references
1263+
// within the callee.
1264+
if v, ok := caller.lookup(free).(*types.Var); ok && within(v.Pos(), caller.enclosingFunc.Body) && !isUsedOutsideCall(caller, v) {
1265+
logf("keeping param %q: arg contains perhaps the last reference to caller local %v @ %v",
12661266
param.info.Name, v, caller.Fset.PositionFor(v.Pos(), false))
12671267
continue next
12681268
}
@@ -1332,6 +1332,34 @@ next:
13321332
}
13331333
}
13341334

1335+
// isUsedOutsideCall reports whether v is used outside of caller.Call, within
1336+
// the body of caller.enclosingFunc.
1337+
func isUsedOutsideCall(caller *Caller, v *types.Var) bool {
1338+
used := false
1339+
ast.Inspect(caller.enclosingFunc.Body, func(n ast.Node) bool {
1340+
if n == caller.Call {
1341+
return false
1342+
}
1343+
switch n := n.(type) {
1344+
case *ast.Ident:
1345+
if use := caller.Info.Uses[n]; use == v {
1346+
used = true
1347+
}
1348+
case *ast.FuncType:
1349+
// All params are used.
1350+
for _, fld := range n.Params.List {
1351+
for _, n := range fld.Names {
1352+
if def := caller.Info.Defs[n]; def == v {
1353+
used = true
1354+
}
1355+
}
1356+
}
1357+
}
1358+
return !used // keep going until we find a use
1359+
})
1360+
return used
1361+
}
1362+
13351363
// checkFalconConstraints checks whether constant arguments
13361364
// are safe to substitute (e.g. s[i] -> ""[0] is not safe.)
13371365
//

internal/refactor/inline/inline_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,36 @@ func TestSubstitution(t *testing.T) {
594594
`func _() { var local int; f(local) }`,
595595
`func _() { var local int; _ = local }`,
596596
},
597+
{
598+
"Arguments that are used are detected",
599+
`func f(int) {}`,
600+
`func _() { var local int; _ = local; f(local) }`,
601+
`func _() { var local int; _ = local }`,
602+
},
603+
{
604+
"Arguments that are used are detected",
605+
`func f(x, y int) { print(x) }`,
606+
`func _() { var z int; f(z, z) }`,
607+
`func _() {
608+
var z int
609+
var _ int = z
610+
print(z)
611+
}`,
612+
},
613+
{
614+
"Function parameters are always used",
615+
`func f(int) {}`,
616+
`func _() {
617+
func(local int) {
618+
f(local)
619+
}(1)
620+
}`,
621+
`func _() {
622+
func(local int) {
623+
624+
}(1)
625+
}`,
626+
},
597627
{
598628
"Regression test for detection of shadowing in nested functions.",
599629
`func f(x int) { _ = func() { y := 1; print(y); print(x) } }`,

0 commit comments

Comments
 (0)