Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
4 changes: 4 additions & 0 deletions Sources/ClangLanguageService/ClangLanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,10 @@ extension ClangLanguageService {
return await workspace.buildServerManager.locationsOrLocationLinksAdjustedForCopiedFiles(result)
}

package func typeDefinition(_ req: TypeDefinitionRequest) async throws -> LocationsOrLocationLinksResponse? {
return try await forwardRequestToClangd(req)
}

package func completion(_ req: CompletionRequest) async throws -> CompletionList {
return try await forwardRequestToClangd(req)
}
Expand Down
1 change: 1 addition & 0 deletions Sources/SourceKitLSP/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_library(SourceKitLSP STATIC
DocumentManager.swift
DocumentSnapshot+FromFileContents.swift
DocumentSnapshot+PositionConversions.swift
DefinitionLocations.swift
GeneratedInterfaceDocumentURLData.swift
Hooks.swift
IndexProgressManager.swift
Expand Down
154 changes: 154 additions & 0 deletions Sources/SourceKitLSP/DefinitionLocations.swift
Comment thread
ahoppen marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

package import IndexStoreDB
@_spi(SourceKitLSP) package import LanguageServerProtocol
@_spi(SourceKitLSP) import SKLogging
package import SemanticIndex

/// Converts a location from the symbol index to an LSP location.
///
/// - Parameter location: The symbol index location
/// - Returns: The LSP location
package func indexToLSPLocation(_ location: SymbolLocation) -> Location? {
guard !location.path.isEmpty else { return nil }
return Location(
uri: location.documentUri,
range: Range(
Position(
// 1-based -> 0-based
// Note that we still use max(0, ...) as a fallback if the location is zero.
line: max(0, location.line - 1),
// Technically we would need to convert the UTF-8 column to a UTF-16 column. This would require reading the
// file. In practice they almost always coincide, so we accept the incorrectness here to avoid the file read.
utf16index: max(0, location.utf8Column - 1)
Comment thread
ahoppen marked this conversation as resolved.
)
)
)
}

/// Return the locations for jump to definition from the given `SymbolDetails`.
package func definitionLocations(
Comment thread
ahoppen marked this conversation as resolved.
for symbol: SymbolDetails,
originatorUri: DocumentURI,
index: CheckedIndex?,
openGeneratedInterface:
@escaping (
_ document: DocumentURI,
_ moduleName: String,
_ groupName: String?,
_ symbolUSR: String?
) async throws -> GeneratedInterfaceDetails?
Comment thread
ahoppen marked this conversation as resolved.
Outdated
) async throws -> [Location] {
// module symbols generate a textual interface
Comment thread
ahoppen marked this conversation as resolved.
Outdated
if symbol.kind == .module {
let moduleName: String
let groupName: String?

if let systemModule = symbol.systemModule {
moduleName = systemModule.moduleName
groupName = systemModule.groupName
} else if let name = symbol.name {
moduleName = name
groupName = nil
} else {
return []
}

let location = try await definitionInInterface(
moduleName: moduleName,
groupName: groupName,
symbolUSR: nil,
originatorUri: originatorUri,
openGeneratedInterface: openGeneratedInterface
)
return [location]
}

// system symbols use generated interface
if symbol.isSystem ?? false, let systemModule = symbol.systemModule {
let location = try await definitionInInterface(
moduleName: systemModule.moduleName,
groupName: systemModule.groupName,
symbolUSR: symbol.usr,
originatorUri: originatorUri,
openGeneratedInterface: openGeneratedInterface
)
return [location]
}

// try local declaration first
guard let index else {
if let bestLocalDeclaration = symbol.bestLocalDeclaration {
return [bestLocalDeclaration]
}
return []
}

guard let usr = symbol.usr else { return [] }
logger.info("Performing indexed jump-to-definition with USR \(usr)")

let occurrences = index.definitionOrDeclarationOccurrences(ofUSR: usr)

Comment thread
ahoppen marked this conversation as resolved.
if occurrences.isEmpty {
if let bestLocalDeclaration = symbol.bestLocalDeclaration {
return [bestLocalDeclaration]
}
// fallback to generated interface for SDK types without index data
if let systemModule = symbol.systemModule {
let location = try await definitionInInterface(
moduleName: systemModule.moduleName,
groupName: systemModule.groupName,
symbolUSR: symbol.usr,
originatorUri: originatorUri,
openGeneratedInterface: openGeneratedInterface
)
return [location]
}
}
Comment thread
ahoppen marked this conversation as resolved.

return occurrences.compactMap { indexToLSPLocation($0.location) }.sorted()
}

/// 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`.
///
/// `originatorUri` is the URI of the file from which the definition request is performed. It is used to determine the
/// compiler arguments to generate the generated interface.
package func definitionInInterface(
moduleName: String,
groupName: String?,
symbolUSR: String?,
originatorUri: DocumentURI,
openGeneratedInterface:
@escaping (
_ document: DocumentURI,
_ moduleName: String,
_ groupName: String?,
_ symbolUSR: String?
) async throws -> GeneratedInterfaceDetails?
) async throws -> Location {
let documentForBuildSettings = originatorUri.buildSettingsFile

guard
let interfaceDetails = try await openGeneratedInterface(
documentForBuildSettings,
moduleName,
groupName,
symbolUSR
)
else {
throw ResponseError.unknown("Could not generate Swift Interface for \(moduleName)")
}
let position = interfaceDetails.position ?? Position(line: 0, utf16index: 0)
return Location(uri: interfaceDetails.uri, range: Range(position))
}
5 changes: 5 additions & 0 deletions Sources/SourceKitLSP/LanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,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?
func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]?
func foldingRange(_ req: FoldingRangeRequest) async throws -> [FoldingRange]?
func documentSymbol(_ req: DocumentSymbolRequest) async throws -> DocumentSymbolResponse?
Expand Down Expand Up @@ -438,6 +439,10 @@ package extension LanguageService {
throw ResponseError.requestNotImplemented(DeclarationRequest.self)
}

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

Comment on lines +442 to +445
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.

Should we also forward the request to clangd in the ClangLanguageService? Not sure if clangd supports the request right now but if it does in the future, we’ll pick it up that way.

func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]? {
throw ResponseError.requestNotImplemented(DocumentHighlightRequest.self)
}
Expand Down
101 changes: 41 additions & 60 deletions 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)
Comment on lines +793 to +794
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.

This list is ordered alphabetically? Could insert this at t?

case let request as RequestAndReply<DoccDocumentationRequest>:
await self.handleRequest(for: request, requestHandler: self.doccDocumentation)
case let request as RequestAndReply<DocumentColorRequest>:
Expand Down Expand Up @@ -1146,6 +1148,7 @@ extension SourceKitLSPServer {
completionProvider: completionOptions,
signatureHelpProvider: signatureHelpOptions,
definitionProvider: .bool(true),
typeDefinitionProvider: .bool(true),
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.

Can you also advertise the typeDefinitionProvider here?

implementationProvider: .bool(true),
referencesProvider: .bool(true),
documentHighlightProvider: .bool(true),
Expand Down Expand Up @@ -1974,27 +1977,6 @@ extension SourceKitLSPServer {
return try await languageService.documentDiagnostic(req)
}

/// Converts a location from the symbol index to an LSP location.
///
/// - Parameter location: The symbol index location
/// - Returns: The LSP location
private nonisolated func indexToLSPLocation(_ location: SymbolLocation) -> Location? {
guard !location.path.isEmpty else { return nil }
return Location(
uri: location.documentUri,
range: Range(
Position(
// 1-based -> 0-based
// Note that we still use max(0, ...) as a fallback if the location is zero.
line: max(0, location.line - 1),
// Technically we would need to convert the UTF-8 column to a UTF-16 column. This would require reading the
// file. In practice they almost always coincide, so we accept the incorrectness here to avoid the file read.
utf16index: max(0, location.utf8Column - 1)
)
)
)
}

func declaration(
_ req: DeclarationRequest,
workspace: Workspace,
Expand All @@ -2003,6 +1985,14 @@ extension SourceKitLSPServer {
return try await languageService.declaration(req)
}

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

/// Return the locations for jump to definition from the given `SymbolDetails`.
private func definitionLocations(
for symbol: SymbolDetails,
Expand All @@ -2025,23 +2015,37 @@ extension SourceKitLSPServer {
return []
}

let interfaceLocation = try await self.definitionInInterface(
let interfaceLocation = try await definitionInInterface(
moduleName: moduleName,
groupName: groupName,
symbolUSR: nil,
originatorUri: uri,
languageService: languageService
openGeneratedInterface: { document, moduleName, groupName, symbolUSR in
try await languageService.openGeneratedInterface(
document: document,
moduleName: moduleName,
groupName: groupName,
symbolUSR: symbolUSR
)
}
)
return [interfaceLocation]
}

if symbol.isSystem ?? false, let systemModule = symbol.systemModule {
let location = try await self.definitionInInterface(
let location = try await definitionInInterface(
moduleName: systemModule.moduleName,
groupName: systemModule.groupName,
symbolUSR: symbol.usr,
originatorUri: uri,
languageService: languageService
openGeneratedInterface: { document, moduleName, groupName, symbolUSR in
try await languageService.openGeneratedInterface(
document: document,
moduleName: moduleName,
groupName: groupName,
symbolUSR: symbolUSR
)
}
)
return [location]
}
Expand Down Expand Up @@ -2095,12 +2099,19 @@ extension SourceKitLSPServer {
// third-party binary frameworks or libraries where indexing data is missing.
// If module info is available, fallback to generating the textual interface.
if let systemModule = symbol.systemModule {
let location = try await self.definitionInInterface(
let location = try await definitionInInterface(
moduleName: systemModule.moduleName,
groupName: systemModule.groupName,
symbolUSR: symbol.usr,
originatorUri: uri,
languageService: languageService
openGeneratedInterface: { document, moduleName, groupName, symbolUSR in
try await languageService.openGeneratedInterface(
document: document,
moduleName: moduleName,
groupName: groupName,
symbolUSR: symbolUSR
)
}
)
return [location]
}
Expand Down Expand Up @@ -2230,36 +2241,6 @@ extension SourceKitLSPServer {
return .locations(remappedLocations)
}

/// 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`.
///
/// `originatorUri` is the URI of the file from which the definition request is performed. It is used to determine the
/// compiler arguments to generate the generated interface.
func definitionInInterface(
moduleName: String,
groupName: String?,
symbolUSR: String?,
originatorUri: DocumentURI,
languageService: any LanguageService
) async throws -> Location {
// Let openGeneratedInterface handle all the logic, including checking if we're already in the right interface
let documentForBuildSettings = originatorUri.buildSettingsFile

guard
let interfaceDetails = try await languageService.openGeneratedInterface(
document: documentForBuildSettings,
moduleName: moduleName,
groupName: groupName,
symbolUSR: symbolUSR
)
else {
throw ResponseError.unknown("Could not generate Swift Interface for \(moduleName)")
}
let position = interfaceDetails.position ?? Position(line: 0, utf16index: 0)
let loc = Location(uri: interfaceDetails.uri, range: Range(position))
return loc
}

func implementation(
_ req: ImplementationRequest,
workspace: Workspace,
Expand Down Expand Up @@ -2434,7 +2415,7 @@ extension SourceKitLSPServer {

// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
func indexToLSPLocation2(_ location: SymbolLocation) -> Location? {
return self.indexToLSPLocation(location)
return indexToLSPLocation(location)
}

// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
Expand Down Expand Up @@ -2478,7 +2459,7 @@ extension SourceKitLSPServer {

// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
func indexToLSPLocation2(_ location: SymbolLocation) -> Location? {
return self.indexToLSPLocation(location)
return indexToLSPLocation(location)
}

// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
Expand Down Expand Up @@ -2600,7 +2581,7 @@ extension SourceKitLSPServer {

// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
func indexToLSPLocation2(_ location: SymbolLocation) -> Location? {
return self.indexToLSPLocation(location)
return indexToLSPLocation(location)
}

// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
Expand Down
Loading