@@ -312,12 +312,18 @@ func (s *symbol) Rename(newName string) (*protocol.WorkspaceEdit, error) {
312312 var edits protocol.WorkspaceEdit
313313 switch s .kind .(type ) {
314314 case * referenceable :
315+ if err := checkRenameConflicts (s , newName ); err != nil {
316+ return nil , err
317+ }
315318 changes , err := renameChangesForReferenceableSymbol (s , newName )
316319 if err != nil {
317320 return nil , err
318321 }
319322 edits .Changes = changes
320323 case * static :
324+ if err := checkRenameConflicts (s , newName ); err != nil {
325+ return nil , err
326+ }
321327 edits .Changes = map [protocol.DocumentURI ][]protocol.TextEdit {
322328 s .file .uri : {{
323329 Range : reportSpanToProtocolRange (s .span ),
@@ -328,6 +334,9 @@ func (s *symbol) Rename(newName string) (*protocol.WorkspaceEdit, error) {
328334 // For references, we attempt to rename the definition symbol, if resolved. This would
329335 // include this reference symbol.
330336 if s .def != nil {
337+ if err := checkRenameConflicts (s .def , newName ); err != nil {
338+ return nil , err
339+ }
331340 changes , err := renameChangesForReferenceableSymbol (s .def , newName )
332341 if err != nil {
333342 return nil , err
@@ -362,6 +371,46 @@ func renameChangesForReferenceableSymbol(s *symbol, newName string) (map[protoco
362371 return changes , nil
363372}
364373
374+ // checkRenameConflicts takes the symbol and desired new name and checks if this conflicts
375+ // with an existing symbol in the same scope and returns an error if a conflict is found.
376+ func checkRenameConflicts (target * symbol , newName string ) error {
377+ parent := target .ir .FullName ().Parent ()
378+ if parent != "" {
379+ var existing source.Span
380+ newFullName := parent .Append (newName )
381+ containsFunc := func (s * symbol ) bool {
382+ existing = s .span
383+ return s .ir .FullName () == newFullName
384+ }
385+ // We check all files in the workspace for a conflict, since a package can span an arbitrary
386+ // number of files.
387+ // We first check the current symbol's file.
388+ if slices .ContainsFunc (target .file .symbols , containsFunc ) {
389+ return fmt .Errorf (
390+ "Renaming %q to %q would conflict with existing symbol at %s:%d:%d" ,
391+ target .ir .FullName ().Name (),
392+ newName ,
393+ target .file .ir .Path (),
394+ existing .StartLoc ().Line ,
395+ existing .StartLoc ().Column ,
396+ )
397+ }
398+ for _ , file := range target .file .workspace .PathToFile () {
399+ if slices .ContainsFunc (file .symbols , containsFunc ) {
400+ return fmt .Errorf (
401+ "Renaming %q to %q would conflict with existing symbol at %s:%d:%d" ,
402+ target .ir .FullName ().Name (),
403+ newName ,
404+ file .ir .Path (),
405+ existing .StartLoc ().Line ,
406+ existing .StartLoc ().Column ,
407+ )
408+ }
409+ }
410+ }
411+ return nil
412+ }
413+
365414func protowireTypeForPredeclared (name predeclared.Name ) protowire.Type {
366415 switch name {
367416 case predeclared .Bool , predeclared .Int32 , predeclared .Int64 , predeclared .UInt32 ,
0 commit comments