@@ -623,6 +623,37 @@ extension SourceKitServer {
623
623
}
624
624
}
625
625
626
+ /// Starting from the given USR, compute the transitive closure of all declarations that are overridden or override
627
+ /// the symbol, including the USR itself.
628
+ ///
629
+ /// This includes symbols that need to traverse the inheritance hierarchy up and down. For example, it includes all
630
+ /// occurrences of `foo` in the following when started from `Inherited.foo`.
631
+ ///
632
+ /// ```swift
633
+ /// class Base { func foo() {} }
634
+ /// class Inherited: Base { override func foo() {} }
635
+ /// class OtherInherited: Base { override func foo() {} }
636
+ /// ```
637
+ private func overridingAndOverriddenUsrs( of usr: String , index: IndexStoreDB ) -> [ String ] {
638
+ var workList = [ usr]
639
+ var usrs : [ String ] = [ ]
640
+ while let usr = workList. popLast ( ) {
641
+ usrs. append ( usr)
642
+ var relatedUsrs = index. occurrences ( relatedToUSR: usr, roles: . overrideOf) . map ( \. symbol. usr)
643
+ relatedUsrs += index. occurrences ( ofUSR: usr, roles: . overrideOf) . flatMap { occurrence in
644
+ occurrence. relations. filter { $0. roles. contains ( . overrideOf) } . map ( \. symbol. usr)
645
+ }
646
+ for overriddenUsr in relatedUsrs {
647
+ if usrs. contains ( overriddenUsr) || workList. contains ( overriddenUsr) {
648
+ // Already handling this USR. Nothing to do.
649
+ continue
650
+ }
651
+ workList. append ( overriddenUsr)
652
+ }
653
+ }
654
+ return usrs
655
+ }
656
+
626
657
func rename( _ request: RenameRequest ) async throws -> WorkspaceEdit ? {
627
658
let uri = request. textDocument. uri
628
659
let snapshot = try documentManager. latestSnapshot ( uri)
@@ -669,8 +700,46 @@ extension SourceKitServer {
669
700
// If we have a USR + old name, perform an index lookup to find workspace-wide symbols to rename.
670
701
// First, group all occurrences of that USR by the files they occur in.
671
702
var locationsByFile : [ URL : [ RenameLocation ] ] = [ : ]
672
- for occurrence in index. occurrences ( ofUSR: usr, roles: renameRoles) {
703
+
704
+ var languageServerTypesCache : [ URL : LanguageServerType ? ] = [ : ]
705
+ func languageServerType( of url: URL ) -> LanguageServerType ? {
706
+ if let cachedValue = languageServerTypesCache [ url] {
707
+ return cachedValue
708
+ }
709
+ let serverType = LanguageServerType ( symbolProvider: index. symbolProvider ( for: url. path) )
710
+ languageServerTypesCache [ url] = serverType
711
+ return serverType
712
+ }
713
+
714
+ let usrsToRename = overridingAndOverriddenUsrs ( of: usr, index: index)
715
+ let occurrencesToRename = usrsToRename. flatMap { index. occurrences ( ofUSR: $0, roles: renameRoles) }
716
+ for occurrence in occurrencesToRename {
673
717
let url = URL ( fileURLWithPath: occurrence. location. path)
718
+
719
+ // Determine whether we should add the location produced by the index to those that will be renamed, or if it has
720
+ // already been handled by the set provided by the AST.
721
+ if changes [ DocumentURI ( url) ] != nil {
722
+ if occurrence. symbol. usr == usr {
723
+ // If the language server's rename function already produced AST-based locations for this symbol, no need to
724
+ // perform an indexed rename for it.
725
+ continue
726
+ }
727
+ switch languageServerType ( of: url) {
728
+ case . swift:
729
+ // sourcekitd only produces AST-based results for the direct calls to this USR. This is because the Swift
730
+ // AST only has upwards references to superclasses and overridden methods, not the other way round. It is
731
+ // thus not possible to (easily) compute an up-down closure like described in `overridingAndOverriddenUsrs`.
732
+ // We thus need to perform an indexed rename for other, related USRs.
733
+ break
734
+ case . clangd:
735
+ // clangd produces AST-based results for the entire class hierarchy, so nothing to do.
736
+ continue
737
+ case nil :
738
+ // Unknown symbol provider
739
+ continue
740
+ }
741
+ }
742
+
674
743
let renameLocation = RenameLocation (
675
744
line: occurrence. location. line,
676
745
utf8Column: occurrence. location. utf8Column,
@@ -683,12 +752,11 @@ extension SourceKitServer {
683
752
// edits.
684
753
let urisAndEdits =
685
754
await locationsByFile
686
- . filter { changes [ DocumentURI ( $0. key) ] == nil }
687
755
. concurrentMap { ( url: URL , renameLocations: [ RenameLocation ] ) -> ( DocumentURI , [ TextEdit ] ) ? in
688
756
let uri = DocumentURI ( url)
689
757
let language : Language
690
- switch index . symbolProvider ( for : url. path ) {
691
- case . clang :
758
+ switch languageServerType ( of : url) {
759
+ case . clangd :
692
760
// Technically, we still don't know the language of the source file but defaulting to C is sufficient to
693
761
// ensure we get the clang toolchain language server, which is all we care about.
694
762
language = . c
@@ -728,12 +796,8 @@ extension SourceKitServer {
728
796
return ( uri, edits)
729
797
} . compactMap { $0 }
730
798
for (uri, editsForUri) in urisAndEdits {
731
- precondition (
732
- changes [ uri] == nil ,
733
- " We should have only computed edits for URIs that didn't have edits from the initial rename request "
734
- )
735
799
if !editsForUri. isEmpty {
736
- changes [ uri] = editsForUri
800
+ changes [ uri, default : [ ] ] + = editsForUri
737
801
}
738
802
}
739
803
var edits = renameResult. edits
@@ -754,15 +818,19 @@ extension SourceKitServer {
754
818
guard
755
819
let index = workspace. index,
756
820
let usr = languageServicePrepareRename. usr,
757
- let oldName = try await self . getCrossLanguageName ( forUsr: usr, workspace: workspace, index: index)
821
+ let oldName = try await self . getCrossLanguageName ( forUsr: usr, workspace: workspace, index: index) ,
822
+ var definitionName = oldName. definitionName
758
823
else {
759
824
return prepareRenameResult
760
825
}
826
+ if oldName. definitionLanguage == . swift, definitionName. hasSuffix ( " () " ) {
827
+ definitionName = String ( definitionName. dropLast ( 2 ) )
828
+ }
761
829
762
830
// Get the name of the symbol's definition, if possible.
763
831
// This is necessary for cross-language rename. Eg. when renaming an Objective-C method from Swift,
764
832
// the user still needs to enter the new Objective-C name.
765
- prepareRenameResult. placeholder = oldName . definitionName
833
+ prepareRenameResult. placeholder = definitionName
766
834
return prepareRenameResult
767
835
}
768
836
0 commit comments