-
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 8 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,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) | ||
|
ahoppen marked this conversation as resolved.
|
||
| ) | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| /// Return the locations for jump to definition from the given `SymbolDetails`. | ||
| 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() | ||
| } | ||
|
|
||
| /// 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)) | ||
| } | ||
| 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), | ||
|
|
@@ -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, | ||
|
|
@@ -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, | ||
|
|
@@ -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] | ||
| } | ||
|
|
@@ -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] | ||
| } | ||
|
|
@@ -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, | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.