-
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
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
d8b8d71
Implement textDocument/typeDefinition request
loveucifer 94ad50d
some improvements
loveucifer 2a360c0
address comments
loveucifer b7e76ff
unify typeDefinition
loveucifer 82a7726
fix type annotation
loveucifer 0e52fed
unify definition locations
loveucifer cf56bf2
Address PR review feedback
loveucifer 5ee63b7
Deduplicate helper functions between DefinitionLocations and SourceKi…
loveucifer cccdde2
Use any LanguageService instead of closures
loveucifer d73aba2
Remove duplicate definitionLocations and call shared function
loveucifer 6028898
Optimize index usage for typeDefinition and inlay hint resolve requests.
loveucifer e786a14
swift format
loveucifer f1f46f5
Add TypeDefinition.swift to CMakeLists.txt
loveucifer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
|
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( | ||
|
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) | ||
|
|
||
|
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]) | ||
| } | ||
| } | ||
|
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)) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
| } | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.