Skip to content

Commit f3fccee

Browse files
committed
gopls/internal/golang: support removing unused parameters from methods
Make a minimal change to the removeparam algorithm to support removing unused parameters of methods. This doesn't address the problem of expanding the set of signatures or calls based on interface satisfaction constraints: that is really a separate concern, which also affects method renaming (golang/go#58461). Were we to have a more general solution to that problem, it would be relatively straightforward to also expand the change signature algorithm. For golang/go#38028 Change-Id: I296cb1f828f07d841c83b1fd33593ccd2fee3539 Reviewed-on: https://go-review.googlesource.com/c/tools/+/572296 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 2d517d5 commit f3fccee

File tree

3 files changed

+228
-4
lines changed

3 files changed

+228
-4
lines changed

gopls/internal/golang/change_signature.go

+43-4
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ func RemoveUnusedParameter(ctx context.Context, fh file.Handle, rng protocol.Ran
5959
if err != nil {
6060
return nil, err // e.g. invalid range
6161
}
62-
if info.Decl.Recv != nil {
63-
return nil, fmt.Errorf("can't change signature of methods (yet)")
64-
}
6562
if info.Field == nil {
6663
return nil, fmt.Errorf("failed to find field")
6764
}
@@ -373,8 +370,50 @@ func rewriteCalls(ctx context.Context, rw signatureRewrite) (map[protocol.Docume
373370

374371
wrapper := internalastutil.CloneNode(rw.origDecl)
375372
wrapper.Type.Params = rw.params
373+
374+
// Get the receiver name, creating it if necessary.
375+
var recv string // nonempty => call is a method call with receiver recv
376+
if wrapper.Recv.NumFields() > 0 {
377+
if len(wrapper.Recv.List[0].Names) > 0 {
378+
recv = wrapper.Recv.List[0].Names[0].Name
379+
} else {
380+
// Create unique name for the temporary receiver, which will be inlined away.
381+
//
382+
// We use the lexical scope of the original function to avoid conflicts
383+
// with (e.g.) named result variables. However, since the parameter syntax
384+
// may have been modified/renamed from the original function, we must
385+
// reject those names too.
386+
usedParams := make(map[string]bool)
387+
for _, fld := range wrapper.Type.Params.List {
388+
for _, name := range fld.Names {
389+
usedParams[name.Name] = true
390+
}
391+
}
392+
scope := rw.pkg.TypesInfo().Scopes[rw.origDecl.Type]
393+
if scope == nil {
394+
return nil, bug.Errorf("missing function scope for %v", rw.origDecl.Name.Name)
395+
}
396+
for i := 0; ; i++ {
397+
recv = fmt.Sprintf("r%d", i)
398+
_, obj := scope.LookupParent(recv, token.NoPos)
399+
if obj == nil && !usedParams[recv] {
400+
break
401+
}
402+
}
403+
wrapper.Recv.List[0].Names = []*ast.Ident{{Name: recv}}
404+
}
405+
}
406+
407+
name := &ast.Ident{Name: delegate.Name.Name}
408+
var fun ast.Expr = name
409+
if recv != "" {
410+
fun = &ast.SelectorExpr{
411+
X: &ast.Ident{Name: recv},
412+
Sel: name,
413+
}
414+
}
376415
call := &ast.CallExpr{
377-
Fun: &ast.Ident{Name: delegate.Name.Name},
416+
Fun: fun,
378417
Args: rw.callArgs,
379418
}
380419
if rw.variadic {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
This test verifies that gopls can remove unused parameters from methods.
2+
3+
Specifically, check
4+
1. basic removal of unused parameters, when the receiver is named, locally and
5+
accross package boundaries
6+
2. handling of unnamed receivers
7+
8+
-- go.mod --
9+
module example.com/rm
10+
11+
go 1.20
12+
13+
-- basic.go --
14+
package rm
15+
16+
type Basic int
17+
18+
func (t Basic) Foo(x int) { //@codeaction("x", "x", "refactor.rewrite", basic)
19+
}
20+
21+
func _(b Basic) {
22+
b.Foo(1)
23+
// TODO(rfindley): methodexprs should not get rewritten as methods.
24+
Basic.Foo(1, 2)
25+
}
26+
27+
-- basicuse/p.go --
28+
package basicuse
29+
30+
import "example.com/rm"
31+
32+
func _() {
33+
x := new(rm.Basic)
34+
x.Foo(sideEffects())
35+
rm.Basic.Foo(1,2)
36+
}
37+
38+
func sideEffects() int
39+
40+
-- @basic/basic.go --
41+
package rm
42+
43+
type Basic int
44+
45+
func (t Basic) Foo() { //@codeaction("x", "x", "refactor.rewrite", basic)
46+
}
47+
48+
func _(b Basic) {
49+
b.Foo()
50+
// TODO(rfindley): methodexprs should not get rewritten as methods.
51+
Basic(1).Foo()
52+
}
53+
-- @basic/basicuse/p.go --
54+
package basicuse
55+
56+
import "example.com/rm"
57+
58+
func _() {
59+
x := new(rm.Basic)
60+
var (
61+
t rm.Basic = *x
62+
_ int = sideEffects()
63+
)
64+
t.Foo()
65+
rm.Basic(1).Foo()
66+
}
67+
68+
func sideEffects() int
69+
-- missingrecv.go --
70+
package rm
71+
72+
type Missing struct{}
73+
74+
var r2 int
75+
76+
func (Missing) M(a, b, c, r0 int) (r1 int) { //@codeaction("b", "b", "refactor.rewrite", missingrecv)
77+
return a + c
78+
}
79+
80+
func _() {
81+
m := &Missing{}
82+
_ = m.M(1, 2, 3, 4)
83+
}
84+
85+
-- missingrecvuse/p.go --
86+
package missingrecvuse
87+
88+
import "example.com/rm"
89+
90+
func _() {
91+
x := rm.Missing{}
92+
x.M(1, sideEffects(), 3, 4)
93+
}
94+
95+
func sideEffects() int
96+
97+
-- @missingrecv/missingrecv.go --
98+
package rm
99+
100+
type Missing struct{}
101+
102+
var r2 int
103+
104+
func (Missing) M(a, c, r0 int) (r1 int) { //@codeaction("b", "b", "refactor.rewrite", missingrecv)
105+
return a + c
106+
}
107+
108+
func _() {
109+
m := &Missing{}
110+
_ = (*m).M(1, 3, 4)
111+
}
112+
-- @missingrecv/missingrecvuse/p.go --
113+
package missingrecvuse
114+
115+
import "example.com/rm"
116+
117+
func _() {
118+
x := rm.Missing{}
119+
var _ int = sideEffects()
120+
x.M(1, 3, 4)
121+
}
122+
123+
func sideEffects() int
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
This test verifies that gopls can remove unused parameters from methods,
2+
when that method satisfies an interface.
3+
4+
For now, we just update static calls. In the future, we should compute the set
5+
of dynamic calls that must change (and therefore, the set of concrete functions
6+
that must be modified), in order to produce the desired outcome for our users.
7+
8+
Doing so would be more complicated, so for now this test simply records the
9+
current behavior.
10+
11+
-- go.mod --
12+
module example.com/rm
13+
14+
go 1.20
15+
16+
-- p.go --
17+
package rm
18+
19+
type T int
20+
21+
func (t T) Foo(x int) { //@codeaction("x", "x", "refactor.rewrite", basic)
22+
}
23+
24+
-- use/use.go --
25+
package use
26+
27+
import "example.com/rm"
28+
29+
type Fooer interface{
30+
Foo(int)
31+
}
32+
33+
var _ Fooer = rm.T(0)
34+
35+
func _() {
36+
var x rm.T
37+
x.Foo(1)
38+
}
39+
-- @basic/p.go --
40+
package rm
41+
42+
type T int
43+
44+
func (t T) Foo() { //@codeaction("x", "x", "refactor.rewrite", basic)
45+
}
46+
47+
-- @basic/use/use.go --
48+
package use
49+
50+
import "example.com/rm"
51+
52+
type Fooer interface {
53+
Foo(int)
54+
}
55+
56+
var _ Fooer = rm.T(0)
57+
58+
func _() {
59+
var x rm.T
60+
var t rm.T = x
61+
t.Foo()
62+
}

0 commit comments

Comments
 (0)