Skip to content

Commit f76b112

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/golang: Rename all receiver variables
This CL causes the Rename operation, when applied to a method receiver variable, to rename all receiver variables of methods of the same named type, where possible. (Errors in secondary receivers, for example due to shadowing conflicts, are silently ignored.) + test, doc, relnote Fixes golang/go#41892 Change-Id: Ie5755508afc9ebdcfe0578cbd9cac71ee122fc38 Reviewed-on: https://go-review.googlesource.com/c/tools/+/665935 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]> Auto-Submit: Alan Donovan <[email protected]>
1 parent ee8f138 commit f76b112

File tree

4 files changed

+153
-4
lines changed

4 files changed

+153
-4
lines changed

gopls/doc/features/transformation.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -315,11 +315,18 @@ Similar problems may arise with packages that use reflection, such as
315315
`encoding/json` or `text/template`. There is no substitute for good
316316
judgment and testing.
317317

318+
Special cases:
319+
320+
- When renaming the receiver of a method, the tool also attempts to
321+
rename the receivers of all other methods associated with the same
322+
named type. Each other receiver that cannot be fully renamed is
323+
quietly skipped.
324+
325+
- Renaming a package declaration additionally causes the package's
326+
directory to be renamed.
327+
318328
Some tips for best results:
319329

320-
- There is currently no special support for renaming all receivers of
321-
a family of methods at once, so you will need to rename one receiver
322-
one at a time (golang/go#41892).
323330
- The safety checks performed by the Rename algorithm require type
324331
information. If the program is grossly malformed, there may be
325332
insufficient information for it to run (golang/go#41870),

gopls/doc/release/v0.19.0.md

+7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77

88
# New features
99

10+
## "Rename" of method receivers
11+
12+
The Rename operation, when applied to the receiver of a method, now
13+
also attempts to rename the receivers of all other methods associated
14+
with the same named type. Each other receiver that cannot be fully
15+
renamed is quietly skipped.
16+
1017
## "Implementations" supports signature types
1118

1219
The Implementations query reports the correspondence between abstract

gopls/internal/golang/rename.go

+68-1
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,10 @@ import (
6969
"golang.org/x/tools/gopls/internal/protocol"
7070
goplsastutil "golang.org/x/tools/gopls/internal/util/astutil"
7171
"golang.org/x/tools/gopls/internal/util/bug"
72+
"golang.org/x/tools/gopls/internal/util/moreiters"
7273
"golang.org/x/tools/gopls/internal/util/safetoken"
7374
internalastutil "golang.org/x/tools/internal/astutil"
75+
"golang.org/x/tools/internal/astutil/cursor"
7476
"golang.org/x/tools/internal/diff"
7577
"golang.org/x/tools/internal/event"
7678
"golang.org/x/tools/internal/typesinternal"
@@ -482,6 +484,7 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
482484
// computes the union across all variants.)
483485
var targets map[types.Object]ast.Node
484486
var pkg *cache.Package
487+
var cur cursor.Cursor // of selected Ident or ImportSpec
485488
{
486489
mps, err := snapshot.MetadataForFile(ctx, f.URI())
487490
if err != nil {
@@ -505,6 +508,11 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
505508
if err != nil {
506509
return nil, err
507510
}
511+
var ok bool
512+
cur, ok = pgf.Cursor.FindPos(pos, pos)
513+
if !ok {
514+
return nil, fmt.Errorf("can't find cursor for selection")
515+
}
508516
objects, _, err := objectsAt(pkg.TypesInfo(), pgf.File, pos)
509517
if err != nil {
510518
return nil, err
@@ -571,8 +579,34 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
571579
for obj := range targets {
572580
objects = append(objects, obj)
573581
}
582+
574583
editMap, _, err := renameObjects(newName, pkg, objects...)
575-
return editMap, err
584+
if err != nil {
585+
return nil, err
586+
}
587+
588+
// If target is a receiver, also rename receivers of
589+
// other methods of the same type that don't already
590+
// have the target name. Quietly discard edits from
591+
// any that can't be renamed.
592+
//
593+
// TODO(adonovan): UX question: require that the
594+
// selection be the declaration of the receiver before
595+
// we broaden the renaming?
596+
if curDecl, ok := moreiters.First(cur.Enclosing((*ast.FuncDecl)(nil))); ok {
597+
decl := curDecl.Node().(*ast.FuncDecl) // enclosing func
598+
if decl.Recv != nil &&
599+
len(decl.Recv.List) > 0 &&
600+
len(decl.Recv.List[0].Names) > 0 {
601+
recv := pkg.TypesInfo().Defs[decl.Recv.List[0].Names[0]]
602+
if recv == obj {
603+
// TODO(adonovan): simplify the above 7 lines to
604+
// to "if obj.(*Var).Kind==Recv" in go1.25.
605+
renameReceivers(pkg, recv.(*types.Var), newName, editMap)
606+
}
607+
}
608+
}
609+
return editMap, nil
576610
}
577611

578612
// Exported: search globally.
@@ -632,6 +666,39 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
632666
return renameExported(pkgs, declPkgPath, declObjPath, newName)
633667
}
634668

669+
// renameReceivers renames all receivers of methods of the same named
670+
// type as recv. The edits of each successful renaming are added to
671+
// editMap; the failed ones are quietly discarded.
672+
func renameReceivers(pkg *cache.Package, recv *types.Var, newName string, editMap map[protocol.DocumentURI][]diff.Edit) {
673+
_, named := typesinternal.ReceiverNamed(recv)
674+
if named == nil {
675+
return
676+
}
677+
678+
// Find receivers of other methods of the same named type.
679+
for m := range named.Origin().Methods() {
680+
recv2 := m.Signature().Recv()
681+
if recv2 == recv {
682+
continue // don't re-rename original receiver
683+
}
684+
if recv2.Name() == newName {
685+
continue // no renaming needed
686+
}
687+
editMap2, _, err := renameObjects(newName, pkg, recv2)
688+
if err != nil {
689+
continue // ignore secondary failures
690+
}
691+
692+
// Since all methods (and their comments)
693+
// are disjoint, and don't affect imports,
694+
// we can safely assume that all edits are
695+
// nonconflicting and disjoint.
696+
for uri, edits := range editMap2 {
697+
editMap[uri] = append(editMap[uri], edits...)
698+
}
699+
}
700+
}
701+
635702
// typeCheckReverseDependencies returns the type-checked packages for
636703
// the reverse dependencies of all packages variants containing
637704
// file declURI. The packages are in some topological order.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
This test exercises renaming of method receivers (golang/go#41892).
2+
3+
Notes:
4+
- x to print fails for A.J because it would shadow the built-in print;
5+
that renaming is quietly skipped.
6+
- various combinations of named, aliases, and pointers are tested.
7+
- package b exercises generics.
8+
9+
-- a/a.go --
10+
package a
11+
12+
type T int
13+
type A = T
14+
15+
func (T) F() {}
16+
func (t T) G() {} //@rename("t", "x", tx)
17+
func (U T) H() {} //@rename("U", "v", Uv)
18+
func (_ T) I() {}
19+
func (v A) J() { print(v) }
20+
func (w *T) K() {}
21+
func (x *A) L() {} //@rename("x", "print", xprint)
22+
23+
-- @tx/a/a.go --
24+
@@ -7,2 +7,2 @@
25+
-func (t T) G() {} //@rename("t", "x", tx)
26+
-func (U T) H() {} //@rename("U", "v", Uv)
27+
+func (x T) G() {} //@rename("t", "x", tx)
28+
+func (x T) H() {} //@rename("U", "v", Uv)
29+
@@ -10,2 +10,2 @@
30+
-func (v A) J() { print(v) }
31+
-func (w *T) K() {}
32+
+func (x A) J() { print(x) }
33+
+func (x *T) K() {}
34+
-- @Uv/a/a.go --
35+
@@ -7,2 +7,2 @@
36+
-func (t T) G() {} //@rename("t", "x", tx)
37+
-func (U T) H() {} //@rename("U", "v", Uv)
38+
+func (v T) G() {} //@rename("t", "x", tx)
39+
+func (v T) H() {} //@rename("U", "v", Uv)
40+
@@ -11,2 +11,2 @@
41+
-func (w *T) K() {}
42+
-func (x *A) L() {} //@rename("x", "print", xprint)
43+
+func (v *T) K() {}
44+
+func (v *A) L() {} //@rename("x", "print", xprint)
45+
-- @xprint/a/a.go --
46+
@@ -7,2 +7,2 @@
47+
-func (t T) G() {} //@rename("t", "x", tx)
48+
-func (U T) H() {} //@rename("U", "v", Uv)
49+
+func (print T) G() {} //@rename("t", "x", tx)
50+
+func (print T) H() {} //@rename("U", "v", Uv)
51+
@@ -11,2 +11,2 @@
52+
-func (w *T) K() {}
53+
-func (x *A) L() {} //@rename("x", "print", xprint)
54+
+func (print *T) K() {}
55+
+func (print *A) L() {} //@rename("x", "print", xprint)
56+
-- b/b.go --
57+
package b
58+
59+
type C[T any] int
60+
func (r C[T]) F() {} //@rename("r", "c", rc)
61+
func (r C[T]) G() {}
62+
63+
-- @rc/b/b.go --
64+
@@ -4,2 +4,2 @@
65+
-func (r C[T]) F() {} //@rename("r", "c", rc)
66+
-func (r C[T]) G() {}
67+
+func (c C[T]) F() {} //@rename("r", "c", rc)
68+
+func (c C[T]) G() {}

0 commit comments

Comments
 (0)