Skip to content

Commit c2bea79

Browse files
committed
internal/lsp/source: make it an error to rename embedded fields
Field embedding links two objects (a TypeName and a Var) by name, requiring special handling during renaming. In CL 282932, renaming of types was made to propagate to uses of their embeddings. However, no such propagation in the reverse direction was added, meaning that renaming an embedded field would not rename the corresponding type, and code could still be left in a non-compiling state. It should be an invariant that renaming does not change program behavior. To enforce with field embeddings this we'd need to also rename the corresponding type, but this seems problematic. If I'm hovering over the field selector x.T, and rename T, it is surprising that this would end up renaming a type. For lack of a better solution, make it an error to rename embedded fields, but try to provide a helpful error message. Also handle the blank identifier, for which renaming was giving a message to "please file a bug". Marker tests are added for the new errors in rename, but not for prepareRename. The prepareRename tests were not set up for asserting on errors -- perhaps that would be a good project for a later CL where we clean up errors. Fixes golang/go#43616 Change-Id: I66c2dd5e531dd102431d1edd443d553687d9ca7e Reviewed-on: https://go-review.googlesource.com/c/tools/+/284312 Run-TryBot: Robert Findley <rfindley@google.com> Trust: Robert Findley <rfindley@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
1 parent 514964b commit c2bea79

File tree

7 files changed

+46
-19
lines changed

7 files changed

+46
-19
lines changed

internal/lsp/rename.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ func (s *Server) prepareRename(ctx context.Context, params *protocol.PrepareRena
4343
}
4444
// Do not return errors here, as it adds clutter.
4545
// Returning a nil result means there is not a valid rename.
46-
item, err := source.PrepareRename(ctx, snapshot, fh, params.Position)
46+
item, usererr, err := source.PrepareRename(ctx, snapshot, fh, params.Position)
4747
if err != nil {
48-
return nil, nil // ignore errors
48+
// Return usererr here rather than err, to avoid cluttering the UI with
49+
// internal error details.
50+
return nil, usererr
4951
}
5052
// TODO(suzmue): return ident.Name as the placeholder text.
5153
return &item.Range, nil

internal/lsp/source/rename.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,30 @@ type PrepareItem struct {
4242
Text string
4343
}
4444

45-
func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position) (*PrepareItem, error) {
45+
// PrepareRename searches for a valid renaming at position pp.
46+
//
47+
// The returned usererr is intended to be displayed to the user to explain why
48+
// the prepare fails. Probably we could eliminate the redundancy in returning
49+
// two errors, but for now this is done defensively.
50+
func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position) (_ *PrepareItem, usererr, err error) {
4651
ctx, done := event.Start(ctx, "source.PrepareRename")
4752
defer done()
4853

4954
qos, err := qualifiedObjsAtProtocolPos(ctx, snapshot, f, pp)
5055
if err != nil {
51-
return nil, err
56+
return nil, nil, err
5257
}
5358
node, obj, pkg := qos[0].node, qos[0].obj, qos[0].sourcePkg
59+
if err := checkRenamable(obj); err != nil {
60+
return nil, err, err
61+
}
5462
mr, err := posToMappedRange(snapshot, pkg, node.Pos(), node.End())
5563
if err != nil {
56-
return nil, err
64+
return nil, nil, err
5765
}
5866
rng, err := mr.Range()
5967
if err != nil {
60-
return nil, err
68+
return nil, nil, err
6169
}
6270
if _, isImport := node.(*ast.ImportSpec); isImport {
6371
// We're not really renaming the import path.
@@ -66,10 +74,22 @@ func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp prot
6674
return &PrepareItem{
6775
Range: rng,
6876
Text: obj.Name(),
69-
}, nil
77+
}, nil, nil
78+
}
79+
80+
// checkRenamable verifies if an obj may be renamed.
81+
func checkRenamable(obj types.Object) error {
82+
if v, ok := obj.(*types.Var); ok && v.Embedded() {
83+
return errors.New("can't rename embedded fields: rename the type directly or name the field")
84+
}
85+
if obj.Name() == "_" {
86+
return errors.New("can't rename \"_\"")
87+
}
88+
return nil
7089
}
7190

72-
// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package.
91+
// Rename returns a map of TextEdits for each file modified when renaming a
92+
// given identifier within a package.
7393
func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, newName string) (map[span.URI][]protocol.TextEdit, error) {
7494
ctx, done := event.Start(ctx, "source.Rename")
7595
defer done()
@@ -79,9 +99,11 @@ func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position,
7999
return nil, err
80100
}
81101

82-
obj := qos[0].obj
83-
pkg := qos[0].pkg
102+
obj, pkg := qos[0].obj, qos[0].pkg
84103

104+
if err := checkRenamable(obj); err != nil {
105+
return nil, err
106+
}
85107
if obj.Name() == newName {
86108
return nil, errors.Errorf("old and new names are the same: %s", newName)
87109
}

internal/lsp/source/source_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -820,7 +820,7 @@ func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.Prepare
820820
if err != nil {
821821
t.Fatal(err)
822822
}
823-
item, err := source.PrepareRename(r.ctx, r.snapshot, fh, srcRng.Start)
823+
item, _, err := source.PrepareRename(r.ctx, r.snapshot, fh, srcRng.Start)
824824
if err != nil {
825825
if want.Text != "" { // expected an ident.
826826
t.Errorf("prepare rename failed for %v: got error: %v", src, err)

internal/lsp/testdata/good/good1.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package good //@diag("package", "no_diagnostics", "", "error")
22

33
import (
4-
_ "go/ast" //@prepare("go/ast", "_", "_")
54
"golang.org/x/tools/internal/lsp/types" //@item(types_import, "types", "\"golang.org/x/tools/internal/lsp/types\"", "package")
65
)
76

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
-- bar-rename --
22
package issue43616
33

4-
type bar int //@rename("foo","bar")
4+
type bar int //@rename("foo","bar"),prepare("oo","foo","foo")
55

6-
var x struct{ bar }
6+
var x struct{ bar } //@rename("foo","baz")
77

8-
var _ = x.bar
8+
var _ = x.bar //@rename("foo","quux")
99

10+
-- baz-rename --
11+
can't rename embedded fields: rename the type directly or name the field
12+
-- quux-rename --
13+
can't rename embedded fields: rename the type directly or name the field
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package issue43616
22

3-
type foo int //@rename("foo","bar")
3+
type foo int //@rename("foo","bar"),prepare("oo","foo","foo")
44

5-
var x struct{ foo }
5+
var x struct{ foo } //@rename("foo","baz")
66

7-
var _ = x.foo
7+
var _ = x.foo //@rename("foo","quux")

internal/lsp/testdata/summary.txt.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ DefinitionsCount = 64
1919
TypeDefinitionsCount = 2
2020
HighlightsCount = 69
2121
ReferencesCount = 25
22-
RenamesCount = 31
22+
RenamesCount = 33
2323
PrepareRenamesCount = 7
2424
SymbolsCount = 5
2525
WorkspaceSymbolsCount = 20

0 commit comments

Comments
 (0)