-
Notifications
You must be signed in to change notification settings - Fork 373
Implement textDocument/typeDefinition request #2445
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 6 commits
d8b8d71
94ad50d
2a360c0
b7e76ff
82a7726
0e52fed
cf56bf2
5ee63b7
cccdde2
d73aba2
6028898
e786a14
f1f46f5
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 |
|---|---|---|
| @@ -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 | ||
|
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) | ||
|
ahoppen marked this conversation as resolved.
|
||
| ) | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| /// returns the definition location for a symbol, handling generated interfaces for SDK types | ||
| package func definitionLocations( | ||
|
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? | ||
|
ahoppen marked this conversation as resolved.
Outdated
|
||
| ) async throws -> [Location] { | ||
| // module symbols generate a textual interface | ||
|
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) | ||
|
|
||
|
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] | ||
| } | ||
| } | ||
|
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)) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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? | ||
|
|
@@ -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
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. Should we also forward the request to clangd in the |
||
| func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]? { | ||
| throw ResponseError.requestNotImplemented(DocumentHighlightRequest.self) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
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. This list is ordered alphabetically? Could insert this at |
||
| case let request as RequestAndReply<DoccDocumentationRequest>: | ||
| await self.handleRequest(for: request, requestHandler: self.doccDocumentation) | ||
| case let request as RequestAndReply<DocumentColorRequest>: | ||
|
|
@@ -1146,6 +1148,7 @@ extension SourceKitLSPServer { | |
| completionProvider: completionOptions, | ||
| signatureHelpProvider: signatureHelpOptions, | ||
| definitionProvider: .bool(true), | ||
| typeDefinitionProvider: .bool(true), | ||
|
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. Can you also advertise the |
||
| implementationProvider: .bool(true), | ||
| referencesProvider: .bool(true), | ||
| documentHighlightProvider: .bool(true), | ||
|
|
@@ -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, | ||
|
|
||
|
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 | ||
|
ahoppen marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| guard let symbol else { | ||
| return nil | ||
| } | ||
|
ahoppen marked this conversation as resolved.
Outdated
|
||
|
|
||
| let locations = try await SourceKitLSP.definitionLocations( | ||
| for: symbol, | ||
| originatorUri: uri, | ||
| index: nil, | ||
|
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 | ||
| } | ||
|
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. 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 |
||
|
|
||
| return .locations(locations) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.