mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
`IndexStoreDB` moves its index to the `saved` directory when it is deallocated. Because `IndexStoreDB` is primarily owned by `UncheckedIndex`, we rely on deallocating this object to save the index store. This is fairly brittle because various parts of the codebase may hold transient references to that object as reported in https://github.com/swiftlang/sourcekit-lsp/issues/2455#issuecomment-3873561003. Explicitly remove the reference from `UncheckedIndex` to `IndexStoreDB`. While this still isn’t perfect because other parts of the code base may hold references to `IndexStoreDB` but those should be a lot rarer, resulting in a more consistent closing of the index.
131 lines
5.2 KiB
Swift
131 lines
5.2 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2025 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 Foundation
|
|
import IndexStoreDB
|
|
@_spi(SourceKitLSP) import SKLogging
|
|
import SemanticIndex
|
|
@preconcurrency @_spi(LinkCompletion) import SwiftDocC
|
|
import SwiftExtensions
|
|
import SymbolKit
|
|
|
|
extension CheckedIndex {
|
|
/// Find a `SymbolOccurrence` that is considered the primary definition of the symbol with the given `DocCSymbolLink`.
|
|
///
|
|
/// If the `DocCSymbolLink` has an ambiguous definition, the most important role of this function is to deterministically return
|
|
/// the same result every time.
|
|
func primaryDefinitionOrDeclarationOccurrence(
|
|
ofDocCSymbolLink symbolLink: DocCSymbolLink,
|
|
fetchSymbolGraph: @Sendable (SymbolLocation) async throws -> String?
|
|
) async throws -> SymbolOccurrence? {
|
|
guard let topLevelSymbolName = symbolLink.components.last?.name else {
|
|
throw DocCCheckedIndexError.emptyDocCSymbolLink
|
|
}
|
|
// Find all occurrences of the symbol by name alone
|
|
var topLevelSymbolOccurrences: [SymbolOccurrence] = []
|
|
try forEachCanonicalSymbolOccurrence(byName: topLevelSymbolName) { symbolOccurrence in
|
|
topLevelSymbolOccurrences.append(symbolOccurrence)
|
|
return true // continue
|
|
}
|
|
// Determine which of the symbol occurrences actually matches the symbol link
|
|
var result: [SymbolOccurrence] = []
|
|
for occurrence in topLevelSymbolOccurrences {
|
|
let info = try await doccSymbolInformation(ofUSR: occurrence.symbol.usr, fetchSymbolGraph: fetchSymbolGraph)
|
|
if info.matches(symbolLink) {
|
|
result.append(occurrence)
|
|
}
|
|
}
|
|
// Ensure that this is deterministic by sorting the results
|
|
result.sort()
|
|
if result.count > 1 {
|
|
logger.debug("Multiple symbols found for DocC symbol link '\(symbolLink.linkString)'")
|
|
}
|
|
return result.first
|
|
}
|
|
|
|
/// Find the DocCSymbolLink for a given symbol USR.
|
|
///
|
|
/// - Parameters:
|
|
/// - usr: The symbol USR to find in the index.
|
|
/// - fetchSymbolGraph: Callback that returns a SymbolGraph for a given SymbolLocation
|
|
func doccSymbolInformation(
|
|
ofUSR usr: String,
|
|
fetchSymbolGraph: (SymbolLocation) async throws -> String?
|
|
) async throws -> DocCSymbolInformation {
|
|
guard let topLevelSymbolOccurrence = try primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
|
|
throw DocCCheckedIndexError.emptyDocCSymbolLink
|
|
}
|
|
let moduleName = topLevelSymbolOccurrence.location.moduleName
|
|
var symbols = [topLevelSymbolOccurrence]
|
|
// Find any parent symbols
|
|
var symbolOccurrence: SymbolOccurrence = topLevelSymbolOccurrence
|
|
while let parentSymbolOccurrence = try symbolOccurrence.parent(self) {
|
|
symbols.insert(parentSymbolOccurrence, at: 0)
|
|
symbolOccurrence = parentSymbolOccurrence
|
|
}
|
|
// Fetch symbol information from the symbol graph
|
|
var components = [DocCSymbolInformation.Component(fromModuleName: moduleName)]
|
|
for symbolOccurence in symbols {
|
|
guard let rawSymbolGraph = try await fetchSymbolGraph(symbolOccurence.location) else {
|
|
throw DocCCheckedIndexError.noSymbolGraph(symbolOccurence.symbol.usr)
|
|
}
|
|
let symbolGraph = try JSONDecoder().decode(SymbolGraph.self, from: Data(rawSymbolGraph.utf8))
|
|
guard let symbol = symbolGraph.symbols[symbolOccurence.symbol.usr] else {
|
|
throw DocCCheckedIndexError.symbolNotFound(symbolOccurence.symbol.usr)
|
|
}
|
|
components.append(DocCSymbolInformation.Component(fromSymbol: symbol))
|
|
}
|
|
return DocCSymbolInformation(components: components)
|
|
}
|
|
}
|
|
|
|
enum DocCCheckedIndexError: LocalizedError {
|
|
case emptyDocCSymbolLink
|
|
case noSymbolGraph(String)
|
|
case symbolNotFound(String)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .emptyDocCSymbolLink:
|
|
"The provided DocCSymbolLink was empty and could not be resolved"
|
|
case .noSymbolGraph(let usr):
|
|
"Unable to locate symbol graph for \(usr)"
|
|
case .symbolNotFound(let usr):
|
|
"Symbol \(usr) was not found in its symbol graph"
|
|
}
|
|
}
|
|
}
|
|
|
|
extension SymbolOccurrence {
|
|
func parent(_ index: CheckedIndex) throws -> SymbolOccurrence? {
|
|
let allParentRelations =
|
|
relations
|
|
.filter { $0.roles.contains(.childOf) }
|
|
.sorted()
|
|
if allParentRelations.count > 1 {
|
|
logger.debug("Symbol \(symbol.usr) has multiple parent symbols")
|
|
}
|
|
guard let parentRelation = allParentRelations.first else {
|
|
return nil
|
|
}
|
|
if parentRelation.symbol.kind == .extension {
|
|
let allSymbolOccurrences = try index.occurrences(relatedToUSR: parentRelation.symbol.usr, roles: .extendedBy)
|
|
.sorted()
|
|
if allSymbolOccurrences.count > 1 {
|
|
logger.debug("Extension \(parentRelation.symbol.usr) extends multiple symbols")
|
|
}
|
|
return allSymbolOccurrences.first
|
|
}
|
|
return try index.primaryDefinitionOrDeclarationOccurrence(ofUSR: parentRelation.symbol.usr)
|
|
}
|
|
}
|