@@ -42,10 +42,13 @@ package golang
42
42
// - FileID-based de-duplication of edits to different URIs for the same file.
43
43
44
44
import (
45
+ "bytes"
45
46
"context"
46
47
"errors"
47
48
"fmt"
48
49
"go/ast"
50
+ "go/parser"
51
+ "go/printer"
49
52
"go/token"
50
53
"go/types"
51
54
"path"
@@ -64,8 +67,10 @@ import (
64
67
"golang.org/x/tools/gopls/internal/cache/parsego"
65
68
"golang.org/x/tools/gopls/internal/file"
66
69
"golang.org/x/tools/gopls/internal/protocol"
70
+ goplsastutil "golang.org/x/tools/gopls/internal/util/astutil"
67
71
"golang.org/x/tools/gopls/internal/util/bug"
68
72
"golang.org/x/tools/gopls/internal/util/safetoken"
73
+ internalastutil "golang.org/x/tools/internal/astutil"
69
74
"golang.org/x/tools/internal/diff"
70
75
"golang.org/x/tools/internal/event"
71
76
"golang.org/x/tools/internal/typesinternal"
@@ -126,6 +131,15 @@ func PrepareRename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle,
126
131
if err != nil {
127
132
return nil , nil , err
128
133
}
134
+
135
+ // Check if we're in a 'func' keyword. If so, we hijack the renaming to
136
+ // change the function signature.
137
+ if item , err := prepareRenameFuncSignature (pgf , pos ); err != nil {
138
+ return nil , nil , err
139
+ } else if item != nil {
140
+ return item , nil , nil
141
+ }
142
+
129
143
targets , node , err := objectsAt (pkg .TypesInfo (), pgf .File , pos )
130
144
if err != nil {
131
145
return nil , nil , err
@@ -193,6 +207,169 @@ func prepareRenamePackageName(ctx context.Context, snapshot *cache.Snapshot, pgf
193
207
}, nil
194
208
}
195
209
210
+ // prepareRenameFuncSignature prepares a change signature refactoring initiated
211
+ // through invoking a rename request at the 'func' keyword of a function
212
+ // declaration.
213
+ //
214
+ // The resulting text is the signature of the function, which may be edited to
215
+ // the new signature.
216
+ func prepareRenameFuncSignature (pgf * parsego.File , pos token.Pos ) (* PrepareItem , error ) {
217
+ fdecl := funcKeywordDecl (pgf , pos )
218
+ if fdecl == nil {
219
+ return nil , nil
220
+ }
221
+ ftyp := nameBlankParams (fdecl .Type )
222
+ var buf bytes.Buffer
223
+ if err := printer .Fprint (& buf , token .NewFileSet (), ftyp ); err != nil { // use a new fileset so that the signature is formatted on a single line
224
+ return nil , err
225
+ }
226
+ rng , err := pgf .PosRange (ftyp .Func , ftyp .Func + token .Pos (len ("func" )))
227
+ if err != nil {
228
+ return nil , err
229
+ }
230
+ text := buf .String ()
231
+ return & PrepareItem {
232
+ Range : rng ,
233
+ Text : text ,
234
+ }, nil
235
+ }
236
+
237
+ // nameBlankParams returns a copy of ftype with blank or unnamed params
238
+ // assigned a unique name.
239
+ func nameBlankParams (ftype * ast.FuncType ) * ast.FuncType {
240
+ ftype = internalastutil .CloneNode (ftype )
241
+
242
+ // First, collect existing names.
243
+ scope := make (map [string ]bool )
244
+ for name := range goplsastutil .FlatFields (ftype .Params ) {
245
+ if name != nil {
246
+ scope [name .Name ] = true
247
+ }
248
+ }
249
+ blanks := 0
250
+ for name , field := range goplsastutil .FlatFields (ftype .Params ) {
251
+ if name == nil {
252
+ name = ast .NewIdent ("_" )
253
+ field .Names = append (field .Names , name ) // ok to append
254
+ }
255
+ if name .Name == "" || name .Name == "_" {
256
+ for {
257
+ newName := fmt .Sprintf ("_%d" , blanks )
258
+ blanks ++
259
+ if ! scope [newName ] {
260
+ name .Name = newName
261
+ break
262
+ }
263
+ }
264
+ }
265
+ }
266
+ return ftype
267
+ }
268
+
269
+ // renameFuncSignature computes and applies the effective change signature
270
+ // operation resulting from a 'renamed' (=rewritten) signature.
271
+ func renameFuncSignature (ctx context.Context , snapshot * cache.Snapshot , f file.Handle , pp protocol.Position , newName string ) (map [protocol.DocumentURI ][]protocol.TextEdit , error ) {
272
+ // Find the renamed signature.
273
+ pkg , pgf , err := NarrowestPackageForFile (ctx , snapshot , f .URI ())
274
+ if err != nil {
275
+ return nil , err
276
+ }
277
+ pos , err := pgf .PositionPos (pp )
278
+ if err != nil {
279
+ return nil , err
280
+ }
281
+ fdecl := funcKeywordDecl (pgf , pos )
282
+ if fdecl == nil {
283
+ return nil , nil
284
+ }
285
+ ftyp := nameBlankParams (fdecl .Type )
286
+
287
+ // Parse the user's requested new signature.
288
+ parsed , err := parser .ParseExpr (newName )
289
+ if err != nil {
290
+ return nil , err
291
+ }
292
+ newType , _ := parsed .(* ast.FuncType )
293
+ if newType == nil {
294
+ return nil , fmt .Errorf ("parsed signature is %T, not a function type" , parsed )
295
+ }
296
+
297
+ // Check results, before we get into handling permutations of parameters.
298
+ if got , want := newType .Results .NumFields (), ftyp .Results .NumFields (); got != want {
299
+ return nil , fmt .Errorf ("changing results not yet supported (got %d results, want %d)" , got , want )
300
+ }
301
+ var resultTypes []string
302
+ for _ , field := range goplsastutil .FlatFields (ftyp .Results ) {
303
+ resultTypes = append (resultTypes , FormatNode (token .NewFileSet (), field .Type ))
304
+ }
305
+ resultIndex := 0
306
+ for _ , field := range goplsastutil .FlatFields (newType .Results ) {
307
+ if FormatNode (token .NewFileSet (), field .Type ) != resultTypes [resultIndex ] {
308
+ return nil , fmt .Errorf ("changing results not yet supported" )
309
+ }
310
+ resultIndex ++
311
+ }
312
+
313
+ type paramInfo struct {
314
+ idx int
315
+ typ string
316
+ }
317
+ oldParams := make (map [string ]paramInfo )
318
+ for name , field := range goplsastutil .FlatFields (ftyp .Params ) {
319
+ oldParams [name .Name ] = paramInfo {
320
+ idx : len (oldParams ),
321
+ typ : types .ExprString (field .Type ),
322
+ }
323
+ }
324
+
325
+ var newParams []int
326
+ for name , field := range goplsastutil .FlatFields (newType .Params ) {
327
+ if name == nil {
328
+ return nil , fmt .Errorf ("need named fields" )
329
+ }
330
+ info , ok := oldParams [name .Name ]
331
+ if ! ok {
332
+ return nil , fmt .Errorf ("couldn't find name %s: adding parameters not yet supported" , name )
333
+ }
334
+ if newType := types .ExprString (field .Type ); newType != info .typ {
335
+ return nil , fmt .Errorf ("changing types (%s to %s) not yet supported" , info .typ , newType )
336
+ }
337
+ newParams = append (newParams , info .idx )
338
+ }
339
+
340
+ rng , err := pgf .PosRange (ftyp .Func , ftyp .Func )
341
+ if err != nil {
342
+ return nil , err
343
+ }
344
+ changes , err := ChangeSignature (ctx , snapshot , pkg , pgf , rng , newParams )
345
+ if err != nil {
346
+ return nil , err
347
+ }
348
+ transposed := make (map [protocol.DocumentURI ][]protocol.TextEdit )
349
+ for _ , change := range changes {
350
+ transposed [change .TextDocumentEdit .TextDocument .URI ] = protocol .AsTextEdits (change .TextDocumentEdit .Edits )
351
+ }
352
+ return transposed , nil
353
+ }
354
+
355
+ // funcKeywordDecl returns the FuncDecl for which pos is in the 'func' keyword,
356
+ // if any.
357
+ func funcKeywordDecl (pgf * parsego.File , pos token.Pos ) * ast.FuncDecl {
358
+ path , _ := astutil .PathEnclosingInterval (pgf .File , pos , pos )
359
+ if len (path ) < 1 {
360
+ return nil
361
+ }
362
+ fdecl , _ := path [0 ].(* ast.FuncDecl )
363
+ if fdecl == nil {
364
+ return nil
365
+ }
366
+ ftyp := fdecl .Type
367
+ if pos < ftyp .Func || pos > ftyp .Func + token .Pos (len ("func" )) { // tolerate renaming immediately after 'func'
368
+ return nil
369
+ }
370
+ return fdecl
371
+ }
372
+
196
373
func checkRenamable (obj types.Object ) error {
197
374
switch obj := obj .(type ) {
198
375
case * types.Var :
@@ -219,6 +396,12 @@ func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp pro
219
396
ctx , done := event .Start (ctx , "golang.Rename" )
220
397
defer done ()
221
398
399
+ if edits , err := renameFuncSignature (ctx , snapshot , f , pp , newName ); err != nil {
400
+ return nil , false , err
401
+ } else if edits != nil {
402
+ return edits , false , nil
403
+ }
404
+
222
405
if ! isValidIdentifier (newName ) {
223
406
return nil , false , fmt .Errorf ("invalid identifier to rename: %q" , newName )
224
407
}
0 commit comments