Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
160 changes: 160 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,160 @@
//===----------------------------------------------------------------------===//
//
// 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.
)
)
)
}

/// The result of looking up definition locations for a symbol.
package struct DefinitionLocationsResult {
/// The locations of the symbol's definition.
package let locations: [Location]
/// The occurrences from the index lookup, if any. Can be used by callers to avoid duplicate index lookups.
package let indexOccurrences: [SymbolOccurrence]

package init(locations: [Location], indexOccurrences: [SymbolOccurrence] = []) {
self.locations = locations
self.indexOccurrences = indexOccurrences
}
}

/// 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?,
languageService: any LanguageService
) async throws -> DefinitionLocationsResult {
// If this symbol is a module then generate a textual interface
if symbol.kind == .module {
// For module symbols, prefer using systemModule information if available
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 DefinitionLocationsResult(locations: [])
}

let location = try await definitionInInterface(
moduleName: moduleName,
groupName: groupName,
symbolUSR: nil,
originatorUri: originatorUri,
languageService: languageService
)
return DefinitionLocationsResult(locations: [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,
languageService: languageService
)
return DefinitionLocationsResult(locations: [location])
}

guard let index else {
if let bestLocalDeclaration = symbol.bestLocalDeclaration {
return DefinitionLocationsResult(locations: [bestLocalDeclaration])
}
return DefinitionLocationsResult(locations: [])
}

guard let usr = symbol.usr else { return DefinitionLocationsResult(locations: []) }
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 DefinitionLocationsResult(locations: [bestLocalDeclaration])
}
// Fallback: The symbol was not found in the index. This often happens with
// 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 definitionInInterface(
moduleName: systemModule.moduleName,
groupName: systemModule.groupName,
symbolUSR: symbol.usr,
originatorUri: originatorUri,
languageService: languageService
)
return DefinitionLocationsResult(locations: [location])
}
}
Comment thread
ahoppen marked this conversation as resolved.

return DefinitionLocationsResult(
locations: occurrences.compactMap { indexToLSPLocation($0.location) }.sorted(),
indexOccurrences: occurrences
)
}

/// 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,
languageService: any LanguageService
) async throws -> Location {
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)
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
Loading