Skip to content

Conversation

@loveucifer
Copy link

This addresses issue #2276 by ensuring that all LSP requests that return source file locations map copied header files back to their original locations, not just jump-to-definition.

Previously, only the definition request applied this mapping. Now, the following requests also adjust locations for copied files:

  • textDocument/references
  • textDocument/implementation
  • workspace/symbol
  • callHierarchy/prepare
  • callHierarchy/incomingCalls
  • callHierarchy/outgoingCalls
  • typeHierarchy/prepare
  • typeHierarchy/supertypes
  • typeHierarchy/subtypes

This provides consistent navigation behavior, ensuring users are always taken to the original source files instead of build artifacts when possible.

@loveucifer loveucifer marked this pull request as draft December 9, 2025 07:19
This addresses issue swiftlang#2276 by ensuring that all LSP requests that return source file locations
map copied header files back to their original locations, not just jump-to-definition.

Previously, only the definition request applied this mapping. Now, the following requests
also adjust locations for copied files:
- textDocument/references
- textDocument/implementation
- workspace/symbol
- callHierarchy/prepare
- callHierarchy/incomingCalls
- callHierarchy/outgoingCalls
- typeHierarchy/prepare
- typeHierarchy/supertypes
- typeHierarchy/subtypes

This provides consistent navigation behavior, ensuring users are always taken to the original
source files instead of build artifacts when possible.
@loveucifer loveucifer force-pushed the feature/file-mapping-all-requests branch from 4fba69c to 70d900e Compare December 9, 2025 07:22
@loveucifer loveucifer marked this pull request as ready for review December 9, 2025 07:33
Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool 🤩 Excited to see this! I left a few comments inline.

@loveucifer loveucifer force-pushed the feature/file-mapping-all-requests branch from c4f0eb2 to 70d900e Compare December 10, 2025 16:32
- Remove async from workspaceEditAdjustedForCopiedFiles
- Refactor to use uriAdjustedForCopiedFiles helper
- Update dictionary update logic with +=
- Adjust LocationLink creation to use adjusted ranges
- Ensure selectionRange adjustment in prepareCallHierarchy
- Provide default WorkspaceEdit in ClangLanguageService
- Revert asyncMap to map and remove await in SourceKitLSPServer
- Chain workspace and index retrieval in incomingCalls
- Use indexToLSPCallHierarchyItem and shared helper for CallHierarchyItem
- Fix indentation and remove duplicated detail setting
- Use shared helper for TypeHierarchyItem
- Remove .sort() from expected array in tests
- Enable testFindImplementationInCopiedHeader
- Add await for actor-isolated BuildServerManager calls
@loveucifer loveucifer force-pushed the feature/file-mapping-all-requests branch from df65d94 to cae01c6 Compare December 10, 2025 18:17
- Refactor supertypes/subtypes to use indexToLSPTypeHierarchyItem helper
  instead of duplicating ~80 lines of TypeHierarchyItem creation code
- Remove unused workaround helper functions (indexToLSPLocation2,
  indexToLSPTypeHierarchyItem2)
- Fix test ordering: use deterministic sorted order instead of Set comparison
- Enable testFindImplementationInCopiedHeader test
- Add implementation request support for C/C++/ObjC functions with
  separate declaration and definition (finds definition when declarations
  exist without definitions at the same location)
- Fix whitespace/indentation issues
Copilot AI review requested due to automatic review settings December 16, 2025 02:41
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the remapping of copied header files to their original source locations across all LSP requests that return location information. Previously, only the textDocument/definition request applied this mapping; now all location-returning requests (references, implementation, workspace symbols, call hierarchy, and type hierarchy) consistently navigate users to original source files instead of build artifacts.

Key Changes:

  • Added location remapping to references, implementation, and workspace symbol requests
  • Implemented location remapping in call hierarchy (prepare, incoming, outgoing) operations
  • Implemented location remapping in type hierarchy (prepare, supertypes, subtypes) operations

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
Tests/SourceKitLSPTests/CopiedHeaderTests.swift New test file covering references, implementation, declaration, and workspace symbols in copied headers
Sources/SourceKitLSP/SourceKitLSPServer.swift Extended location remapping to all LSP location-returning requests and refactored call/type hierarchy handling
Sources/ClangLanguageService/ClangLanguageService.swift Added location remapping for declaration and rename requests
Sources/BuildServerIntegration/BuildServerManager.swift Added helper methods for remapping locations in workspace edits, location links, and type hierarchy items

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

loveucifer and others added 2 commits December 16, 2025 08:15
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@loveucifer
Copy link
Author

hey @ahoppen can i get another review :)

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I left two more comments inline but I think a few of my last comments are still outstanding (see the ones that I didn’t mark as resolved). In particular, I don’t think that SourceKitLSPServer.swift should contain any large-scale edits for this change, it should just call to adjust methods in BuildServerManager to take copied files into account.

@loveucifer
Copy link
Author

Thanks, I left two more comments inline but I think a few of my last comments are still outstanding (see the ones that I didn’t mark as resolved). In particular, I don’t think that SourceKitLSPServer.swift should contain any large-scale edits for this change, it should just call to adjust methods in BuildServerManager to take copied files into account.

got it thanks !

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. The PR looks pretty good now, I left a few more stylistic-ish comments inline.

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few more comments in the PR to simplify the diff a little further, but otherwise looks good to me.

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks great to me. Thanks for addressing this issue 😍

@ahoppen
Copy link
Member

ahoppen commented Dec 19, 2025

@swift-ci Please test

@ahoppen
Copy link
Member

ahoppen commented Dec 19, 2025

@swift-ci Please test Windows

@loveucifer
Copy link
Author

Everything looks great to me. Thanks for addressing this issue 😍

Thanks! This was my first contribution to Swift, and I loved the whole PR process

@rintaro
Copy link
Member

rintaro commented Dec 19, 2025

(I'm asking around about the compiler crashes)

@rintaro
Copy link
Member

rintaro commented Dec 19, 2025

I preserved the current state in https://github.com/rintaro/sourcekit-lsp/tree/PR-2385-crasher so compiler folks can look into it.
Feel free to modify the PR and try workaround if there's any.

@ahoppen
Copy link
Member

ahoppen commented Dec 21, 2025

I filed swiftlang/swift#86170 for the compiler crash. A workaround seems to be to refer to for … in instead of asyncCompactMap in prepareTypeHierarchy, eg. using the following diff. @loveucifer could you apply that and I’ll trigger CI again?

diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift
index 80055d84..c92b7c68 100644
--- a/Sources/SourceKitLSP/SourceKitLSPServer.swift
+++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift
@@ -2546,7 +2546,8 @@ extension SourceKitLSPServer {
       )
     }
 
-    let typeHierarchyItems = await usrs.asyncCompactMap { (usr) -> TypeHierarchyItem? in
+    var typeHierarchyItems: [TypeHierarchyItem] = []
+    for usr in usrs {
       guard let info = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
         return nil
       }
@@ -2570,8 +2571,9 @@ extension SourceKitLSPServer {
       guard let item = indexToLSPTypeHierarchyItem2(definition: info, moduleName: moduleName, index: index) else {
         return nil
       }
-      return await workspace.buildServerManager.typeHierarchyItemAdjustedForCopiedFiles(item)
-    }.sorted(by: { $0.name < $1.name })
+      typeHierarchyItems.append(await workspace.buildServerManager.typeHierarchyItemAdjustedForCopiedFiles(item))
+    }
+    typeHierarchyItems.sort(by: { $0.name < $1.name })
 
     if typeHierarchyItems.isEmpty {
       // When returning an empty array, VS Code fails with the following two errors. Returning `nil` works around those

@loveucifer
Copy link
Author

I filed swiftlang/swift#86170 for the compiler crash. A workaround seems to be to refer to for … in instead of asyncCompactMap in prepareTypeHierarchy, eg. using the following diff. @loveucifer could you apply that and I’ll trigger CI again?

diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift
index 80055d84..c92b7c68 100644
--- a/Sources/SourceKitLSP/SourceKitLSPServer.swift
+++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift
@@ -2546,7 +2546,8 @@ extension SourceKitLSPServer {
       )
     }
 
-    let typeHierarchyItems = await usrs.asyncCompactMap { (usr) -> TypeHierarchyItem? in
+    var typeHierarchyItems: [TypeHierarchyItem] = []
+    for usr in usrs {
       guard let info = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
         return nil
       }
@@ -2570,8 +2571,9 @@ extension SourceKitLSPServer {
       guard let item = indexToLSPTypeHierarchyItem2(definition: info, moduleName: moduleName, index: index) else {
         return nil
       }
-      return await workspace.buildServerManager.typeHierarchyItemAdjustedForCopiedFiles(item)
-    }.sorted(by: { $0.name < $1.name })
+      typeHierarchyItems.append(await workspace.buildServerManager.typeHierarchyItemAdjustedForCopiedFiles(item))
+    }
+    typeHierarchyItems.sort(by: { $0.name < $1.name })
 
     if typeHierarchyItems.isEmpty {
       // When returning an empty array, VS Code fails with the following two errors. Returning `nil` works around those

got it doing it rn

@loveucifer
Copy link
Author

hey @ahoppen sorry if this is the wrong place to ask is there a server or communication channel for this repo

@ahoppen
Copy link
Member

ahoppen commented Dec 22, 2025

@swift-ci Please test

@ahoppen
Copy link
Member

ahoppen commented Dec 22, 2025

sorry if this is the wrong place to ask is there a server or communication channel for this repo

For anything that’s related to a specific issue or a code change, GitHub is really the best communication channel. For anything more broad, you can also post in the SourceKit-LSP category on the Swift Forums.

@loveucifer
Copy link
Author

seems like its still failing :(

@ahoppen
Copy link
Member

ahoppen commented Dec 22, 2025

Looks like we need to apply the same trick in a couple new places. It builds with the following diff applied using swift-DEVELOPMENT-SNAPSHOT-2025-11-26-a. Could you apply it to your PR. And sorry for this churn.

Diff
diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift
index dbf4896f..3542baf1 100644
--- a/Sources/SourceKitLSP/SourceKitLSPServer.swift
+++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift
@@ -2281,15 +2281,17 @@ extension SourceKitLSPServer {
 
     // Only return a single call hierarchy item. Returning multiple doesn't make sense because they will all have the
     // same USR (because we query them by USR) and will thus expand to the exact same call hierarchy.
-    let callHierarchyItems = await usrs.asyncCompactMap { (usr) -> CallHierarchyItem? in
+    var callHierarchyItems: [CallHierarchyItem] = []
+    for usr in usrs {
       guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
-        return nil
+        continue
       }
       guard let item = indexToLSPCallHierarchyItem2(definition: definition, index: index) else {
-        return nil
+        continue
       }
-      return await workspace.buildServerManager.callHierarchyItemAdjustedForCopiedFiles(item)
-    }.sorted(by: { Location(uri: $0.uri, range: $0.range) < Location(uri: $1.uri, range: $1.range) })
+      callHierarchyItems.append(await workspace.buildServerManager.callHierarchyItemAdjustedForCopiedFiles(item))
+    }
+    callHierarchyItems.sort(by: { Location(uri: $0.uri, range: $0.range) < Location(uri: $1.uri, range: $1.range) })
 
     // Ideally, we should show multiple symbols. But VS Code fails to display call hierarchies with multiple root items,
     // failing with `Cannot read properties of undefined (reading 'map')`. Pick the first one.
@@ -2358,24 +2360,25 @@ extension SourceKitLSPServer {
       return self.indexToLSPCallHierarchyItem(definition: definition, index: index)
     }
 
-    let calls = await callersToCalls.asyncCompactMap { (caller, callsList) -> CallHierarchyIncomingCall? in
+    var calls: [CallHierarchyIncomingCall] = []
+    for (caller, callsList) in callersToCalls {
       // Resolve the caller's definition to find its location
       guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: caller.usr) else {
-        return nil
+        continue
       }
 
       let locations = callsList.compactMap { indexToLSPLocation2($0.location) }.sorted()
       let remappedLocations = await workspace.buildServerManager.locationsAdjustedForCopiedFiles(locations)
       guard !remappedLocations.isEmpty else {
-        return nil
+        continue
       }
 
       guard let item = indexToLSPCallHierarchyItem2(definition: definition, index: index) else {
-        return nil
+        continue
       }
       let remappedItem = await workspace.buildServerManager.callHierarchyItemAdjustedForCopiedFiles(item)
 
-      return CallHierarchyIncomingCall(from: remappedItem, fromRanges: remappedLocations.map(\.range))
+      calls.append(CallHierarchyIncomingCall(from: remappedItem, fromRanges: remappedLocations.map(\.range)))
     }
     return calls.sorted(by: { $0.from.name < $1.from.name })
   }
@@ -2404,26 +2407,27 @@ extension SourceKitLSPServer {
     let callableUsrs = [data.usr] + index.occurrences(relatedToUSR: data.usr, roles: .accessorOf).map(\.symbol.usr)
     let callOccurrences = callableUsrs.flatMap { index.occurrences(relatedToUSR: $0, roles: .containedBy) }
       .filter(\.shouldShowInCallHierarchy)
-    let calls = await callOccurrences.asyncCompactMap { occurrence -> CallHierarchyOutgoingCall? in
+    var calls: [CallHierarchyOutgoingCall] = []
+    for occurrence in callOccurrences {
       guard occurrence.symbol.kind.isCallable else {
-        return nil
+        continue
       }
       guard let location = indexToLSPLocation2(occurrence.location) else {
-        return nil
+        continue
       }
       let remappedLocation = await workspace.buildServerManager.locationAdjustedForCopiedFiles(location)
 
       // Resolve the callee's definition to find its location
       guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: occurrence.symbol.usr) else {
-        return nil
+        continue
       }
 
       guard let item = indexToLSPCallHierarchyItem2(definition: definition, index: index) else {
-        return nil
+        continue
       }
       let remappedItem = await workspace.buildServerManager.callHierarchyItemAdjustedForCopiedFiles(item)
 
-      return CallHierarchyOutgoingCall(to: remappedItem, fromRanges: [remappedLocation.range])
+      calls.append(CallHierarchyOutgoingCall(to: remappedItem, fromRanges: [remappedLocation.range]))
     }
     return calls.sorted(by: { $0.to.name < $1.to.name })
   }
@@ -2624,17 +2628,18 @@ extension SourceKitLSPServer {
 
     // Convert occurrences to type hierarchy items
     let occurs = baseOccurs + retroactiveConformanceOccurs
-    let types = await occurs.asyncCompactMap { occurrence -> TypeHierarchyItem? in
+    var types: [TypeHierarchyItem] = []
+    for occurrence in occurs {
       // Resolve the supertype's definition to find its location
       guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: occurrence.symbol.usr) else {
-        return nil
+        continue
       }
 
       let moduleName = definition.location.moduleName
       guard let item = indexToLSPTypeHierarchyItem2(definition: definition, moduleName: moduleName, index: index) else {
-        return nil
+        continue
       }
-      return await workspace.buildServerManager.typeHierarchyItemAdjustedForCopiedFiles(item)
+      types.append(await workspace.buildServerManager.typeHierarchyItemAdjustedForCopiedFiles(item))
     }
     return types.sorted(by: { $0.name < $1.name })
   }
@@ -2664,7 +2669,8 @@ extension SourceKitLSPServer {
     }
 
     // Convert occurrences to type hierarchy items
-    let types = await occurs.asyncCompactMap { occurrence -> TypeHierarchyItem? in
+    var types: [TypeHierarchyItem] = []
+    for occurrence in occurs {
       if occurrence.relations.count > 1 {
         // An occurrence with a `baseOf` or `extendedBy` relation is an occurrence inside an inheritance clause.
         // Such an occurrence can only be the source of a single type, namely the one that the inheritance clause belongs
@@ -2672,19 +2678,19 @@ extension SourceKitLSPServer {
         logger.fault("Expected at most extendedBy or baseOf relation but got \(occurrence.relations.count)")
       }
       guard let related = occurrence.relations.sorted().first else {
-        return nil
+        continue
       }
 
       // Resolve the subtype's definition to find its location
       guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: related.symbol.usr) else {
-        return nil
+        continue
       }
 
       let moduleName = definition.location.moduleName
       guard let item = indexToLSPTypeHierarchyItem2(definition: definition, moduleName: moduleName, index: index) else {
-        return nil
+        continue
       }
-      return await workspace.buildServerManager.typeHierarchyItemAdjustedForCopiedFiles(item)
+      types.append(await workspace.buildServerManager.typeHierarchyItemAdjustedForCopiedFiles(item))
     }
     return types.sorted { $0.name < $1.name }
   }

@loveucifer
Copy link
Author

@ahoppen hope this fixes it :3

@rintaro
Copy link
Member

rintaro commented Dec 22, 2025

@swift-ci Please test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants