@@ -42,10 +42,13 @@ package golang
4242// - FileID-based de-duplication of edits to different URIs for the same file.
4343
4444import (
45+ "bytes"
4546 "context"
4647 "errors"
4748 "fmt"
4849 "go/ast"
50+ "go/parser"
51+ "go/printer"
4952 "go/token"
5053 "go/types"
5154 "path"
@@ -64,8 +67,10 @@ import (
6467 "golang.org/x/tools/gopls/internal/cache/parsego"
6568 "golang.org/x/tools/gopls/internal/file"
6669 "golang.org/x/tools/gopls/internal/protocol"
70+ goplsastutil "golang.org/x/tools/gopls/internal/util/astutil"
6771 "golang.org/x/tools/gopls/internal/util/bug"
6872 "golang.org/x/tools/gopls/internal/util/safetoken"
73+ internalastutil "golang.org/x/tools/internal/astutil"
6974 "golang.org/x/tools/internal/diff"
7075 "golang.org/x/tools/internal/event"
7176 "golang.org/x/tools/internal/typesinternal"
@@ -126,6 +131,15 @@ func PrepareRename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle,
126131 if err != nil {
127132 return nil , nil , err
128133 }
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+
129143 targets , node , err := objectsAt (pkg .TypesInfo (), pgf .File , pos )
130144 if err != nil {
131145 return nil , nil , err
@@ -193,6 +207,169 @@ func prepareRenamePackageName(ctx context.Context, snapshot *cache.Snapshot, pgf
193207 }, nil
194208}
195209
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+
196373func checkRenamable (obj types.Object ) error {
197374 switch obj := obj .(type ) {
198375 case * types.Var :
@@ -219,6 +396,12 @@ func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp pro
219396 ctx , done := event .Start (ctx , "golang.Rename" )
220397 defer done ()
221398
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+
222405 if ! isValidIdentifier (newName ) {
223406 return nil , false , fmt .Errorf ("invalid identifier to rename: %q" , newName )
224407 }
0 commit comments