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
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
143 changes: 143 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,143 @@
//===----------------------------------------------------------------------===//
//
// 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 IndexStoreDB
@_spi(SourceKitLSP) package import LanguageServerProtocol
@_spi(SourceKitLSP) import SKLogging
package import SemanticIndex

/// converts a symbol index location to an LSP location
Comment thread
ahoppen marked this conversation as resolved.
Outdated
func indexToLSPLocation(_ location: SymbolLocation) -> Location? {
guard !location.path.isEmpty else { return nil }
return Location(
uri: location.documentUri,
range: Range(
Position(
line: max(0, location.line - 1),
utf16index: max(0, location.utf8Column - 1)
Comment thread
ahoppen marked this conversation as resolved.
)
)
)
}

/// returns the definition location for a symbol, handling generated interfaces for SDK types
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()
}

/// generates a swift interface and returns location for the given symbol
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
11 changes: 11 additions & 0 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 @@ -2003,6 +2006,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 Down
38 changes: 14 additions & 24 deletions Sources/SwiftLanguageService/InlayHintResolve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@ extension SwiftLanguageService {
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.
/// looks up the definition location for the type at the given position
Comment thread
ahoppen marked this conversation as resolved.
Outdated
func lookupTypeDefinitionLocation(
snapshot: DocumentSnapshot,
position: Position
Expand All @@ -87,27 +84,20 @@ extension SwiftLanguageService {
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))
let locations = try await SourceKitLSP.definitionLocations(
for: typeInfo.symbolInfo,
originatorUri: snapshot.uri,
index: nil,
Comment thread
ahoppen marked this conversation as resolved.
Outdated
openGeneratedInterface: { document, moduleName, groupName, symbolUSR in
try await self.openGeneratedInterface(
document: document,
moduleName: moduleName,
groupName: groupName,
symbolUSR: symbolUSR
)
}
}
)

return nil
return locations.first
Comment thread
ahoppen marked this conversation as resolved.
Outdated
}
}
1 change: 1 addition & 0 deletions Sources/SwiftLanguageService/SwiftLanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ extension SwiftLanguageService {
retriggerCharacters: [",", ":"]
),
definitionProvider: nil,
typeDefinitionProvider: .bool(true),
implementationProvider: .bool(true),
referencesProvider: nil,
documentHighlightProvider: .bool(true),
Expand Down
83 changes: 83 additions & 0 deletions Sources/SwiftLanguageService/TypeDefinition.swift
Comment thread
loveucifer marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//===----------------------------------------------------------------------===//
//
// 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 IndexStoreDB
@_spi(SourceKitLSP) package import LanguageServerProtocol
import SemanticIndex
import SourceKitD
import SourceKitLSP

extension SwiftLanguageService {
/// handles the textDocument/typeDefinition request
package func typeDefinition(_ request: TypeDefinitionRequest) async throws -> LocationsOrLocationLinksResponse? {
let uri = request.textDocument.uri
let position = request.position

let snapshot = try await self.latestSnapshot(for: uri)
let compileCommand = await self.compileCommand(for: 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)
let documentManager = try self.documentManager

// if cursor is on a type symbol itself, use its USR directly
var symbol: SymbolDetails?
if let cursorInfo = CursorInfo(dict, snapshot: snapshot, documentManager: documentManager, sourcekitd: sourcekitd) {
switch cursorInfo.symbolInfo.kind {
case .class, .struct, .enum, .interface, .typeParameter:
symbol = cursorInfo.symbolInfo
default:
break
}
}

// otherwise get the type of the symbol at this position
if symbol == nil {
guard let typeUsr: String = dict[keys.typeUsr] else {
return nil
}
let typeInfo = try await cursorInfoFromTypeUSR(typeUsr, in: snapshot)
symbol = typeInfo?.symbolInfo
Comment thread
ahoppen marked this conversation as resolved.
Outdated
}

guard let symbol else {
return nil
}
Comment thread
ahoppen marked this conversation as resolved.
Outdated

let locations = try await SourceKitLSP.definitionLocations(
for: symbol,
originatorUri: uri,
index: nil,
Comment thread
ahoppen marked this conversation as resolved.
Outdated
openGeneratedInterface: { document, moduleName, groupName, symbolUSR in
try await self.openGeneratedInterface(
document: document,
moduleName: moduleName,
groupName: groupName,
symbolUSR: symbolUSR
)
}
)

if locations.isEmpty {
return nil
}
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.

Similar to my comments in the inlay type hints PR, this doesn’t handle jumping to generated interfaces. Instead of doing another ad-hoc fix like you did in the inlay hints PR, I’d really like to see this unified with the logic in indexBasedJumpToDefinition. Could you look into how we can best re-use code between both that function, inlay hints resolve and the type definition request?


return .locations(locations)
}
}
Loading