Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions Sources/SourceKitD/sourcekitd_uids.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

package import Csourcekitd

// swift-format-ignore: TypeNamesShouldBeCapitalized
// Matching C style types
package struct sourcekitd_api_keys {
/// `key.version_major`
package let versionMajor: sourcekitd_api_uid_t
Expand Down Expand Up @@ -793,8 +791,6 @@ package struct sourcekitd_api_keys {
}
}

// swift-format-ignore: TypeNamesShouldBeCapitalized
// Matching C style types
package struct sourcekitd_api_requests {
/// `source.request.protocol_version`
package let protocolVersion: sourcekitd_api_uid_t
Expand Down Expand Up @@ -969,8 +965,6 @@ package struct sourcekitd_api_requests {
}
}

// swift-format-ignore: TypeNamesShouldBeCapitalized
// Matching C style types
package struct sourcekitd_api_values {
/// `source.lang.swift.decl.function.free`
package let declFunctionFree: sourcekitd_api_uid_t
Expand Down Expand Up @@ -1024,6 +1018,14 @@ package struct sourcekitd_api_values {
package let declAccessorInit: sourcekitd_api_uid_t
/// `source.lang.swift.ref.function.accessor.init`
package let refAccessorInit: sourcekitd_api_uid_t
/// `source.lang.swift.decl.function.accessor.mutate`
package let declAccessorMutate: sourcekitd_api_uid_t
/// `source.lang.swift.ref.function.accessor.mutate`
package let refAccessorMutate: sourcekitd_api_uid_t
/// `source.lang.swift.decl.function.accessor.borrow`
package let declAccessorBorrow: sourcekitd_api_uid_t
/// `source.lang.swift.ref.function.accessor.borrow`
package let refAccessorBorrow: sourcekitd_api_uid_t
/// `source.lang.swift.decl.function.constructor`
package let declConstructor: sourcekitd_api_uid_t
/// `source.lang.swift.ref.function.constructor`
Expand Down Expand Up @@ -1248,6 +1250,8 @@ package struct sourcekitd_api_values {
package let diagWarning: sourcekitd_api_uid_t
/// `source.diagnostic.severity.error`
package let diagError: sourcekitd_api_uid_t
/// `source.diagnostic.severity.remark`
package let diagRemark: sourcekitd_api_uid_t
/// `source.diagnostic.category.deprecation`
package let diagDeprecation: sourcekitd_api_uid_t
/// `source.diagnostic.category.no_usage`
Expand Down Expand Up @@ -1352,8 +1356,6 @@ package struct sourcekitd_api_values {
package let semaEnabledNotification: sourcekitd_api_uid_t
/// `source.notification.editor.documentupdate`
package let documentUpdateNotification: sourcekitd_api_uid_t
/// `source.diagnostic.severity.remark`
package let diagRemark: sourcekitd_api_uid_t

package init(api: sourcekitd_api_functions_t) {
declFunctionFree = api.uid_get_from_cstr("source.lang.swift.decl.function.free")!
Expand Down Expand Up @@ -1382,6 +1384,10 @@ package struct sourcekitd_api_values {
refAccessorModify = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.modify")!
declAccessorInit = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.init")!
refAccessorInit = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.init")!
declAccessorMutate = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.mutate")!
refAccessorMutate = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.mutate")!
declAccessorBorrow = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.borrow")!
refAccessorBorrow = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.borrow")!
declConstructor = api.uid_get_from_cstr("source.lang.swift.decl.function.constructor")!
refConstructor = api.uid_get_from_cstr("source.lang.swift.ref.function.constructor")!
declDestructor = api.uid_get_from_cstr("source.lang.swift.decl.function.destructor")!
Expand Down Expand Up @@ -1494,6 +1500,7 @@ package struct sourcekitd_api_values {
diagNote = api.uid_get_from_cstr("source.diagnostic.severity.note")!
diagWarning = api.uid_get_from_cstr("source.diagnostic.severity.warning")!
diagError = api.uid_get_from_cstr("source.diagnostic.severity.error")!
diagRemark = api.uid_get_from_cstr("source.diagnostic.severity.remark")!
diagDeprecation = api.uid_get_from_cstr("source.diagnostic.category.deprecation")!
diagNoUsage = api.uid_get_from_cstr("source.diagnostic.category.no_usage")!
codeCompletionEverything = api.uid_get_from_cstr("source.codecompletion.everything")!
Expand Down Expand Up @@ -1546,6 +1553,5 @@ package struct sourcekitd_api_values {
semaDisabledNotification = api.uid_get_from_cstr("source.notification.sema_disabled")!
semaEnabledNotification = api.uid_get_from_cstr("source.notification.sema_enabled")!
documentUpdateNotification = api.uid_get_from_cstr("source.notification.editor.documentupdate")!
diagRemark = api.uid_get_from_cstr("source.diagnostic.severity.remark")!
}
}
3 changes: 0 additions & 3 deletions Sources/SourceKitD/sourcekitd_uids.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,6 @@
# Maintained from initializeService in Requests.cpp
KIND('SemaEnabledNotification', 'source.notification.sema_enabled'),
KIND('DocumentUpdateNotification', 'source.notification.editor.documentupdate'),

# Used exclusively within the SourceKit Plugin
KIND('DiagRemark', 'source.diagnostic.severity.remark'),
]

TYPES_AND_KEYS = [
Expand Down
10 changes: 10 additions & 0 deletions Sources/SourceKitLSP/LanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ package protocol LanguageService: AnyObject, Sendable {
func definition(_ request: DefinitionRequest) async throws -> LocationsOrLocationLinksResponse?

func declaration(_ request: DeclarationRequest) async throws -> LocationsOrLocationLinksResponse?
func typeDefinition(_ request: TypeDefinitionRequest) async throws -> LocationsOrLocationLinksResponse?
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think typeDefinition doesn’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.

func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]?
func foldingRange(_ req: FoldingRangeRequest) async throws -> [FoldingRange]?
func documentSymbol(_ req: DocumentSymbolRequest) async throws -> DocumentSymbolResponse?
Expand All @@ -248,6 +249,7 @@ package protocol LanguageService: AnyObject, Sendable {
func colorPresentation(_ req: ColorPresentationRequest) async throws -> [ColorPresentation]
func codeAction(_ req: CodeActionRequest) async throws -> CodeActionRequestResponse?
func inlayHint(_ req: InlayHintRequest) async throws -> [InlayHint]
func inlayHintResolve(_ req: InlayHintResolveRequest) async throws -> InlayHint
func codeLens(_ req: CodeLensRequest) async throws -> [CodeLens]
func documentDiagnostic(_ req: DocumentDiagnosticsRequest) async throws -> DocumentDiagnosticReport
func documentFormatting(_ req: DocumentFormattingRequest) async throws -> [TextEdit]?
Expand Down Expand Up @@ -427,6 +429,10 @@ package extension LanguageService {
throw ResponseError.requestNotImplemented(DeclarationRequest.self)
}

func typeDefinition(_ request: TypeDefinitionRequest) async throws -> LocationsOrLocationLinksResponse? {
throw ResponseError.requestNotImplemented(TypeDefinitionRequest.self)
}

func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]? {
throw ResponseError.requestNotImplemented(DocumentHighlightRequest.self)
}
Expand Down Expand Up @@ -471,6 +477,10 @@ package extension LanguageService {
throw ResponseError.requestNotImplemented(InlayHintRequest.self)
}

func inlayHintResolve(_ req: InlayHintResolveRequest) async throws -> InlayHint {
return req.inlayHint
}

func codeLens(_ req: CodeLensRequest) async throws -> [CodeLens] {
throw ResponseError.requestNotImplemented(CodeLensRequest.self)
}
Expand Down
31 changes: 30 additions & 1 deletion Sources/SourceKitLSP/SourceKitLSPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,8 @@ extension SourceKitLSPServer: QueueBasedMessageHandler {
await self.handleRequest(for: request, requestHandler: self.declaration)
case let request as RequestAndReply<DefinitionRequest>:
await self.handleRequest(for: request, requestHandler: self.definition)
case let request as RequestAndReply<TypeDefinitionRequest>:
await self.handleRequest(for: request, requestHandler: self.typeDefinition)
case let request as RequestAndReply<DoccDocumentationRequest>:
await self.handleRequest(for: request, requestHandler: self.doccDocumentation)
case let request as RequestAndReply<DocumentColorRequest>:
Expand Down Expand Up @@ -832,6 +834,8 @@ extension SourceKitLSPServer: QueueBasedMessageHandler {
initialized = true
case let request as RequestAndReply<InlayHintRequest>:
await self.handleRequest(for: request, requestHandler: self.inlayHint)
case let request as RequestAndReply<InlayHintResolveRequest>:
await request.reply { try await inlayHintResolve(request: request.params) }
case let request as RequestAndReply<IsIndexingRequest>:
await request.reply { try await self.isIndexing(request.params) }
case let request as RequestAndReply<OutputPathsRequest>:
Expand Down Expand Up @@ -1098,7 +1102,7 @@ extension SourceKitLSPServer {
let inlayHintOptions =
await registry.clientHasDynamicInlayHintRegistration
? nil
: ValueOrBool.value(InlayHintOptions(resolveProvider: false))
: ValueOrBool.value(InlayHintOptions(resolveProvider: true))

let semanticTokensOptions =
await registry.clientHasDynamicSemanticTokensRegistration
Expand Down Expand Up @@ -1144,6 +1148,7 @@ extension SourceKitLSPServer {
completionProvider: completionOptions,
signatureHelpProvider: signatureHelpOptions,
definitionProvider: .bool(true),
typeDefinitionProvider: .bool(true),
implementationProvider: .bool(true),
referencesProvider: .bool(true),
documentHighlightProvider: .bool(true),
Expand Down Expand Up @@ -1901,6 +1906,22 @@ extension SourceKitLSPServer {
return try await languageService.inlayHint(req)
}

func inlayHintResolve(
request: InlayHintResolveRequest
) async throws -> InlayHint {
guard case .dictionary(let dict) = request.inlayHint.data,
case .string(let uriString) = dict["uri"],
let uri = try? DocumentURI(string: uriString)
else {
return request.inlayHint
}
guard let workspace = await self.workspaceForDocument(uri: uri) else {
return request.inlayHint
}
let language = try documentManager.latestSnapshot(uri.buildSettingsFile).language
return try await primaryLanguageService(for: uri, language, in: workspace).inlayHintResolve(request)
}

func documentDiagnostic(
_ req: DocumentDiagnosticsRequest,
workspace: Workspace,
Expand Down Expand Up @@ -2150,6 +2171,14 @@ extension SourceKitLSPServer {
return .locations(remappedLocations)
}

func typeDefinition(
_ req: TypeDefinitionRequest,
workspace: Workspace,
languageService: any LanguageService
) async throws -> LocationsOrLocationLinksResponse? {
return try await languageService.typeDefinition(req)
}

/// Generate the generated interface for the given module, write it to disk and return the location to which to jump
/// to get to the definition of `symbolUSR`.
///
Expand Down
47 changes: 47 additions & 0 deletions Sources/SwiftLanguageService/CursorInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hmm but the tests fail without the D stripping.

Copy link
Copy Markdown
Contributor

@hamishknight hamishknight Jan 10, 2026

Choose a reason for hiding this comment

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

Oh interesting, I saw handling of Demangle::Node::Kind::Type in the code and assumed that was referring to D but it's actually a different kind. I guess this is okay for now but we really ought to fix this on the sourcekitd side, ideally at the same time as the $s -> s: change.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

okei

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 D as a temporary workaround for now as long as we try to adjust sourcekitd to work without this hack in the future.

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)
}
}
93 changes: 93 additions & 0 deletions Sources/SwiftLanguageService/InlayHintResolve.swift
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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,93 @@
//===----------------------------------------------------------------------===//
//
// 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.
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
}

if let typeInfo = try await cursorInfoFromTypeUSR(typeUsr, in: snapshot),
let location = typeInfo.symbolInfo.bestLocalDeclaration
{
return location
}

return nil
}
}
Loading