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.

return locations.map { locationAdjustedForCopiedFiles($0) }
}

package func workspaceEditAdjustedForCopiedFiles(_ workspaceEdit: WorkspaceEdit?) async -> WorkspaceEdit? {
Copy link
Member

Choose a reason for hiding this comment

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

I don’t think any of these functions need to be async.

var newChanges: [DocumentURI: [TextEdit]] = [:]
for (uri, edits) in changes {
let newUri = self.locationAdjustedForCopiedFiles(
Location(uri: uri, range: Position(line: 0, utf16index: 0)..<Position(line: 0, utf16index: 0))
Copy link
Member

Choose a reason for hiding this comment

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

Instead of synthesizing a Location with an irrelevant position, I think we should just have a function that adjusts a DocumentURI for copied files.

let newUri = self.locationAdjustedForCopiedFiles(
Location(uri: uri, range: Position(line: 0, utf16index: 0)..<Position(line: 0, utf16index: 0))
).uri
newChanges[newUri, default: []].append(contentsOf: edits)
Copy link
Member

Choose a reason for hiding this comment

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

Just personal preference 😉

Suggested change
newChanges[newUri, default: []].append(contentsOf: edits)
newChanges[newUri, default: []] += edits

remappedLinks.append(LocationLink(
originSelectionRange: link.originSelectionRange,
targetUri: newUri,
targetRange: link.targetRange,
Copy link
Member

Choose a reason for hiding this comment

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

I think we should use the target range of the adjusted location here. Just in case the location adjustment in the future also modifies the range. Same for the selection range.

detail: item.detail,
uri: adjustedLocation.uri,
range: adjustedLocation.range,
selectionRange: item.selectionRange,
Copy link
Member

Choose a reason for hiding this comment

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

It might seem redundant now but I’d like to run the selection range through locationAdjustedForCopiedFiles as well, just so we have a centralized place that might also adjust the range eg. if it discovers that copying the file removed the first line and thus all line number need to be shifted by 1 or something like that (not proposing that we do this now but it might be something we could consider doing in the future).

} else {
name = "\(symbol.name): \(conformances.map(\.symbol.name).sorted().joined(separator: ", "))"
}
switch symbol.kind {
Copy link
Member

Choose a reason for hiding this comment

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

This looks like incorrect indentation to me. Could you run swift-format on your change?

Comment on lines 2518 to 2527
// Add the file name and line to the detail string
if let url = remappedLocation.uri.fileURL,
let basename = (try? AbsolutePath(validating: url.filePath))?.basename
{
detail = "Extension at \(basename):\(remappedLocation.range.lowerBound.line + 1)"
} else if !definition.location.moduleName.isEmpty {
detail = "Extension in \(definition.location.moduleName)"
} else {
detail = "Extension"
}
Copy link
Member

Choose a reason for hiding this comment

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

Did you unintentionally duplicate this code?

detail = info.location.moduleName
}

let item = TypeHierarchyItem(
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn’t we be able to just all into the adjust method for TypeHierarchyItem here and not have these 40-ish lines of adjustment code? Same for the other TypeHierarchyItem returning functions.

Comment on lines 123 to 129
response.sort()
var expected = [
try project.location(from: "1️⃣", to: "1️⃣", in: "Test.h"),
try project.location(from: "2️⃣", to: "2️⃣", in: "Test.c"),
]
expected.sort()
XCTAssertEqual(response, expected)
Copy link
Member

Choose a reason for hiding this comment

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

Ordering should be deterministic, so I don’t think that .sort() should be needed.

XCTAssertEqual(response, expected)
}

func DISABLED_testFindImplementationInCopiedHeader() async throws {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this disabled? Artifact of local development?

@loveucifer
Copy link
Author

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

oh cool I will try to fix , this was my first time contributing to swift :)

@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
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.

2 participants