-
Notifications
You must be signed in to change notification settings - Fork 373
add go-to-definition for inlay hints (#2318) #2436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
8e300ee
f46a31b
27cf04e
564a45b
3630ab9
9e12af1
81610b3
22e7ac0
0695d3c
ceaf87b
0b02672
45b1598
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -207,4 +207,51 @@ extension SwiftLanguageService { | |
| additionalParameters: appendAdditionalParameters | ||
| ) | ||
| } | ||
|
|
||
| /// Because of https://github.com/swiftlang/swift/issues/86432 sourcekitd returns a mangled name instead of a USR | ||
| /// as the type USR. Work around this by replacing mangled names (starting with `$s`) to a USR, starting with `s:`. | ||
| /// We also strip the trailing `D` suffix which represents a type mangling - this may not work correctly for generic | ||
| /// types with type arguments. | ||
| // TODO: Remove once https://github.com/swiftlang/swift/issues/86432 is fixed | ||
| private func convertMangledTypeToUSR(_ mangledType: String) -> String { | ||
| var result = mangledType | ||
| if result.hasPrefix("$s") { | ||
| result = "s:" + result.dropFirst(2) | ||
| } | ||
| // Strip trailing 'D' (type mangling suffix) to get declaration USR | ||
| if result.hasSuffix("D") { | ||
| result = String(result.dropLast()) | ||
| } | ||
|
Comment on lines
+221
to
+224
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You shouldn't need to do this, cursor info should be able to handle a type USR with it
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm but the tests fail without the D stripping.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh interesting, I saw handling of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. okei
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to add my 2cts, while I’m not a huge fan, I’m fine with having the stripping of |
||
| return result | ||
| } | ||
|
|
||
| /// Get cursor info for a type by looking up its USR. | ||
| /// | ||
| /// - Parameters: | ||
| /// - mangledType: The mangled name of the type | ||
| /// - snapshot: Document snapshot for context (used to get compile command) | ||
| /// - Returns: CursorInfo for the type declaration, or `nil` if not found | ||
| func cursorInfoFromTypeUSR( | ||
| _ mangledType: String, | ||
| in snapshot: DocumentSnapshot | ||
| ) async throws -> CursorInfo? { | ||
| let usr = convertMangledTypeToUSR(mangledType) | ||
|
|
||
| let compileCommand = await self.compileCommand(for: snapshot.uri, fallbackAfterTimeout: true) | ||
| let documentManager = try self.documentManager | ||
|
|
||
| let keys = self.keys | ||
|
|
||
| let skreq = sourcekitd.dictionary([ | ||
| keys.cancelOnSubsequentRequest: 0, | ||
| keys.usr: usr, | ||
| keys.sourceFile: snapshot.uri.sourcekitdSourceFile, | ||
| keys.primaryFile: snapshot.uri.primaryFile?.pseudoPath, | ||
| keys.compilerArgs: compileCommand?.compilerArgs as [any SKDRequestValue]?, | ||
| ]) | ||
|
|
||
| let dict = try await send(sourcekitdRequest: \.cursorInfo, skreq, snapshot: snapshot) | ||
|
|
||
| return CursorInfo(dict, snapshot: snapshot, documentManager: documentManager, sourcekitd: sourcekitd) | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add this file to CMakeLists.txt to make the tests pass on Windows? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the Swift.org open source project | ||
| // | ||
| // Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors | ||
| // Licensed under Apache License v2.0 with Runtime Library Exception | ||
| // | ||
| // See https://swift.org/LICENSE.txt for license information | ||
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| import Foundation | ||
| import IndexStoreDB | ||
| @_spi(SourceKitLSP) package import LanguageServerProtocol | ||
| import SemanticIndex | ||
| import SourceKitD | ||
| import SourceKitLSP | ||
|
|
||
| extension SwiftLanguageService { | ||
| /// Resolves an inlay hint by looking up the type definition location. | ||
| package func inlayHintResolve(_ req: InlayHintResolveRequest) async throws -> InlayHint { | ||
| let hint = req.inlayHint | ||
|
|
||
| guard hint.kind == .type, | ||
| let resolveData = InlayHintResolveData(fromLSPAny: hint.data) | ||
| else { | ||
| return hint | ||
| } | ||
|
|
||
| // Fail if document version has changed since the hint was created | ||
| let currentSnapshot = try await self.latestSnapshot(for: resolveData.uri) | ||
| guard currentSnapshot.version == resolveData.version else { | ||
| return hint | ||
| } | ||
|
|
||
| let typeLocation = try await lookupTypeDefinitionLocation( | ||
| snapshot: currentSnapshot, | ||
| position: resolveData.position | ||
| ) | ||
|
|
||
| guard let typeLocation else { | ||
| return hint | ||
| } | ||
|
|
||
| if case .string(let labelText) = hint.label { | ||
| return InlayHint( | ||
| position: hint.position, | ||
| label: .parts([InlayHintLabelPart(value: labelText, location: typeLocation)]), | ||
| kind: hint.kind, | ||
| textEdits: hint.textEdits, | ||
| tooltip: hint.tooltip, | ||
| paddingLeft: hint.paddingLeft, | ||
| paddingRight: hint.paddingRight, | ||
| data: hint.data | ||
| ) | ||
| } | ||
|
|
||
| return hint | ||
| } | ||
|
|
||
| /// Looks up the definition location for the type at the given position. | ||
| /// | ||
| /// This is used by inlay hint resolution to enable go-to-definition on type hints. | ||
| /// For SDK types, this returns a location in the generated interface. | ||
| func lookupTypeDefinitionLocation( | ||
| snapshot: DocumentSnapshot, | ||
| position: Position | ||
| ) async throws -> Location? { | ||
| let compileCommand = await self.compileCommand(for: snapshot.uri, fallbackAfterTimeout: false) | ||
|
|
||
| let skreq = sourcekitd.dictionary([ | ||
| keys.cancelOnSubsequentRequest: 0, | ||
| keys.offset: snapshot.utf8Offset(of: position), | ||
| keys.sourceFile: snapshot.uri.sourcekitdSourceFile, | ||
| keys.primaryFile: snapshot.uri.primaryFile?.pseudoPath, | ||
| keys.compilerArgs: compileCommand?.compilerArgs as [any SKDRequestValue]?, | ||
| ]) | ||
|
|
||
| let dict = try await send(sourcekitdRequest: \.cursorInfo, skreq, snapshot: snapshot) | ||
|
|
||
| guard let typeUsr: String = dict[keys.typeUsr] else { | ||
| return nil | ||
| } | ||
|
|
||
| guard let typeInfo = try await cursorInfoFromTypeUSR(typeUsr, in: snapshot) else { | ||
| return nil | ||
| } | ||
|
|
||
| // For local types, return the local declaration | ||
| if let location = typeInfo.symbolInfo.bestLocalDeclaration { | ||
| return location | ||
| } | ||
|
|
||
| // For SDK types, fall back to generated interface | ||
| if typeInfo.symbolInfo.isSystem ?? false, | ||
| let systemModule = typeInfo.symbolInfo.systemModule | ||
| { | ||
| let interfaceDetails = try await self.openGeneratedInterface( | ||
| document: snapshot.uri, | ||
| moduleName: systemModule.moduleName, | ||
| groupName: systemModule.groupName, | ||
| symbolUSR: typeInfo.symbolInfo.usr | ||
| ) | ||
| if let details = interfaceDetails { | ||
| let position = details.position ?? Position(line: 0, utf16index: 0) | ||
| return Location(uri: details.uri, range: Range(position)) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
typeDefinitiondoesn’t have any tests at the moment yet. I would suggest we move its implementation to a follow-up PR and focus this one on the inlay hints.