Skip to content

Commit 37034a8

Browse files
committed
Fully qualify type names in call hierarchy, type hierarchy and workspace symbols
Previously, we didn’t take outer types into account or only took one level of container type into account. Fixes #1673 136078089
1 parent 6c5f5c9 commit 37034a8

File tree

3 files changed

+156
-114
lines changed

3 files changed

+156
-114
lines changed

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 113 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,13 +1444,24 @@ extension SourceKitLSPServer {
14441444
range: Range(symbolPosition)
14451445
)
14461446

1447+
let containerNames = index.containerNames(of: symbolOccurrence)
1448+
let containerName: String?
1449+
if containerNames.isEmpty {
1450+
containerName = nil
1451+
} else {
1452+
switch symbolOccurrence.symbol.language {
1453+
case .cxx, .c, .objc: containerName = containerNames.joined(separator: "::")
1454+
case .swift: containerName = containerNames.joined(separator: ".")
1455+
}
1456+
}
1457+
14471458
return WorkspaceSymbolItem.symbolInformation(
14481459
SymbolInformation(
14491460
name: symbolOccurrence.symbol.name,
14501461
kind: symbolOccurrence.symbol.kind.asLspSymbolKind(),
14511462
deprecated: nil,
14521463
location: symbolLocation,
1453-
containerName: index.containerName(of: symbolOccurrence)
1464+
containerName: containerName
14541465
)
14551466
)
14561467
}
@@ -1916,27 +1927,14 @@ extension SourceKitLSPServer {
19161927
}
19171928

19181929
private func indexToLSPCallHierarchyItem(
1919-
symbol: Symbol,
1920-
containerName: String?,
1921-
location: Location
1922-
) -> CallHierarchyItem {
1923-
let name: String
1924-
if let containerName {
1925-
switch symbol.language {
1926-
case .objc where symbol.kind == .instanceMethod || symbol.kind == .instanceProperty:
1927-
name = "-[\(containerName) \(symbol.name)]"
1928-
case .objc where symbol.kind == .classMethod || symbol.kind == .classProperty:
1929-
name = "+[\(containerName) \(symbol.name)]"
1930-
case .cxx, .c, .objc:
1931-
// C shouldn't have container names for call hierarchy and Objective-C should be covered above.
1932-
// Fall back to using the C++ notation using `::`.
1933-
name = "\(containerName)::\(symbol.name)"
1934-
case .swift:
1935-
name = "\(containerName).\(symbol.name)"
1936-
}
1937-
} else {
1938-
name = symbol.name
1930+
definition: SymbolOccurrence,
1931+
index: CheckedIndex
1932+
) -> CallHierarchyItem? {
1933+
guard let location = indexToLSPLocation(definition.location) else {
1934+
return nil
19391935
}
1936+
let name = index.fullyQualifiedName(of: definition)
1937+
let symbol = definition.symbol
19401938
return CallHierarchyItem(
19411939
name: name,
19421940
kind: symbol.kind.asLspSymbolKind(),
@@ -1976,14 +1974,7 @@ extension SourceKitLSPServer {
19761974
guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
19771975
return nil
19781976
}
1979-
guard let location = indexToLSPLocation(definition.location) else {
1980-
return nil
1981-
}
1982-
return self.indexToLSPCallHierarchyItem(
1983-
symbol: definition.symbol,
1984-
containerName: index.containerName(of: definition),
1985-
location: location
1986-
)
1977+
return self.indexToLSPCallHierarchyItem(definition: definition, index: index)
19871978
}.sorted(by: { Location(uri: $0.uri, range: $0.range) < Location(uri: $1.uri, range: $1.range) })
19881979

19891980
// Ideally, we should show multiple symbols. But VS Code fails to display call hierarchies with multiple root items,
@@ -2045,38 +2036,27 @@ extension SourceKitLSPServer {
20452036

20462037
// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
20472038
func indexToLSPCallHierarchyItem2(
2048-
symbol: Symbol,
2049-
containerName: String?,
2050-
location: Location
2051-
) -> CallHierarchyItem {
2052-
return self.indexToLSPCallHierarchyItem(symbol: symbol, containerName: containerName, location: location)
2039+
definition: SymbolOccurrence,
2040+
index: CheckedIndex
2041+
) -> CallHierarchyItem? {
2042+
return self.indexToLSPCallHierarchyItem(definition: definition, index: index)
20532043
}
20542044

20552045
let calls = callersToCalls.compactMap { (caller: Symbol, calls: [SymbolOccurrence]) -> CallHierarchyIncomingCall? in
20562046
// Resolve the caller's definition to find its location
2057-
let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: caller.usr)
2058-
let definitionSymbolLocation = definition?.location
2059-
let definitionLocation = definitionSymbolLocation.flatMap(indexToLSPLocation2)
2060-
let containerName: String? =
2061-
if let definition {
2062-
index.containerName(of: definition)
2063-
} else {
2064-
nil
2065-
}
2047+
guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: caller.usr) else {
2048+
return nil
2049+
}
20662050

20672051
let locations = calls.compactMap { indexToLSPLocation2($0.location) }.sorted()
20682052
guard !locations.isEmpty else {
20692053
return nil
20702054
}
2055+
guard let item = indexToLSPCallHierarchyItem2(definition: definition, index: index) else {
2056+
return nil
2057+
}
20712058

2072-
return CallHierarchyIncomingCall(
2073-
from: indexToLSPCallHierarchyItem2(
2074-
symbol: caller,
2075-
containerName: containerName,
2076-
location: definitionLocation ?? locations.first!
2077-
),
2078-
fromRanges: locations.map(\.range)
2079-
)
2059+
return CallHierarchyIncomingCall(from: item, fromRanges: locations.map(\.range))
20802060
}
20812061
return calls.sorted(by: { $0.from.name < $1.from.name })
20822062
}
@@ -2095,11 +2075,10 @@ extension SourceKitLSPServer {
20952075

20962076
// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
20972077
func indexToLSPCallHierarchyItem2(
2098-
symbol: Symbol,
2099-
containerName: String?,
2100-
location: Location
2101-
) -> CallHierarchyItem {
2102-
return self.indexToLSPCallHierarchyItem(symbol: symbol, containerName: containerName, location: location)
2078+
definition: SymbolOccurrence,
2079+
index: CheckedIndex
2080+
) -> CallHierarchyItem? {
2081+
return self.indexToLSPCallHierarchyItem(definition: definition, index: index)
21032082
}
21042083

21052084
let callableUsrs = [data.usr] + index.occurrences(relatedToUSR: data.usr, roles: .accessorOf).map(\.symbol.usr)
@@ -2113,37 +2092,32 @@ extension SourceKitLSPServer {
21132092
}
21142093

21152094
// Resolve the callee's definition to find its location
2116-
let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: occurrence.symbol.usr)
2117-
let definitionSymbolLocation = definition?.location
2118-
let definitionLocation = definitionSymbolLocation.flatMap(indexToLSPLocation2)
2119-
let containerName: String? =
2120-
if let definition {
2121-
index.containerName(of: definition)
2122-
} else {
2123-
nil
2124-
}
2095+
guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: occurrence.symbol.usr) else {
2096+
return nil
2097+
}
21252098

2126-
return CallHierarchyOutgoingCall(
2127-
to: indexToLSPCallHierarchyItem2(
2128-
symbol: occurrence.symbol,
2129-
containerName: containerName,
2130-
location: definitionLocation ?? location // Use occurrence location as fallback
2131-
),
2132-
fromRanges: [location.range]
2133-
)
2099+
guard let item = indexToLSPCallHierarchyItem2(definition: definition, index: index) else {
2100+
return nil
2101+
}
2102+
2103+
return CallHierarchyOutgoingCall(to: item, fromRanges: [location.range])
21342104
}
21352105
return calls.sorted(by: { $0.to.name < $1.to.name })
21362106
}
21372107

21382108
private func indexToLSPTypeHierarchyItem(
2139-
symbol: Symbol,
2109+
definition: SymbolOccurrence,
21402110
moduleName: String?,
2141-
location: Location,
21422111
index: CheckedIndex
2143-
) -> TypeHierarchyItem {
2112+
) -> TypeHierarchyItem? {
21442113
let name: String
21452114
let detail: String?
21462115

2116+
guard let location = indexToLSPLocation(definition.location) else {
2117+
return nil
2118+
}
2119+
2120+
let symbol = definition.symbol
21472121
switch symbol.kind {
21482122
case .extension:
21492123
// Query the conformance added by this extension
@@ -2164,7 +2138,7 @@ extension SourceKitLSPServer {
21642138
detail = "Extension"
21652139
}
21662140
default:
2167-
name = symbol.name
2141+
name = index.fullyQualifiedName(of: definition)
21682142
detail = moduleName
21692143
}
21702144

@@ -2213,16 +2187,12 @@ extension SourceKitLSPServer {
22132187
}
22142188
.compactMap(\.usr)
22152189
let typeHierarchyItems = usrs.compactMap { (usr) -> TypeHierarchyItem? in
2216-
guard
2217-
let info = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr),
2218-
let location = indexToLSPLocation(info.location)
2219-
else {
2190+
guard let info = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
22202191
return nil
22212192
}
22222193
return self.indexToLSPTypeHierarchyItem(
2223-
symbol: info.symbol,
2194+
definition: info,
22242195
moduleName: info.location.moduleName,
2225-
location: location,
22262196
index: index
22272197
)
22282198
}
@@ -2287,30 +2257,28 @@ extension SourceKitLSPServer {
22872257

22882258
// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
22892259
func indexToLSPTypeHierarchyItem2(
2290-
symbol: Symbol,
2260+
definition: SymbolOccurrence,
22912261
moduleName: String?,
2292-
location: Location,
22932262
index: CheckedIndex
2294-
) -> TypeHierarchyItem {
2295-
return self.indexToLSPTypeHierarchyItem(symbol: symbol, moduleName: moduleName, location: location, index: index)
2263+
) -> TypeHierarchyItem? {
2264+
return self.indexToLSPTypeHierarchyItem(
2265+
definition: definition,
2266+
moduleName: moduleName,
2267+
index: index
2268+
)
22962269
}
22972270

22982271
// Convert occurrences to type hierarchy items
22992272
let occurs = baseOccurs + retroactiveConformanceOccurs
23002273
let types = occurs.compactMap { occurrence -> TypeHierarchyItem? in
2301-
guard let location = indexToLSPLocation2(occurrence.location) else {
2274+
// Resolve the supertype's definition to find its location
2275+
guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: occurrence.symbol.usr) else {
23022276
return nil
23032277
}
23042278

2305-
// Resolve the supertype's definition to find its location
2306-
let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: occurrence.symbol.usr)
2307-
let definitionSymbolLocation = definition?.location
2308-
let definitionLocation = definitionSymbolLocation.flatMap(indexToLSPLocation2)
2309-
23102279
return indexToLSPTypeHierarchyItem2(
2311-
symbol: occurrence.symbol,
2312-
moduleName: definitionSymbolLocation?.moduleName,
2313-
location: definitionLocation ?? location, // Use occurrence location as fallback
2280+
definition: definition,
2281+
moduleName: definition.location.moduleName,
23142282
index: index
23152283
)
23162284
}
@@ -2334,12 +2302,15 @@ extension SourceKitLSPServer {
23342302

23352303
// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
23362304
func indexToLSPTypeHierarchyItem2(
2337-
symbol: Symbol,
2305+
definition: SymbolOccurrence,
23382306
moduleName: String?,
2339-
location: Location,
23402307
index: CheckedIndex
2341-
) -> TypeHierarchyItem {
2342-
return self.indexToLSPTypeHierarchyItem(symbol: symbol, moduleName: moduleName, location: location, index: index)
2308+
) -> TypeHierarchyItem? {
2309+
return self.indexToLSPTypeHierarchyItem(
2310+
definition: definition,
2311+
moduleName: moduleName,
2312+
index: index
2313+
)
23432314
}
23442315

23452316
// Convert occurrences to type hierarchy items
@@ -2350,20 +2321,18 @@ extension SourceKitLSPServer {
23502321
// to.
23512322
logger.fault("Expected at most extendedBy or baseOf relation but got \(occurrence.relations.count)")
23522323
}
2353-
guard let related = occurrence.relations.sorted().first, let location = indexToLSPLocation2(occurrence.location)
2354-
else {
2324+
guard let related = occurrence.relations.sorted().first else {
23552325
return nil
23562326
}
23572327

23582328
// Resolve the subtype's definition to find its location
2359-
let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: related.symbol.usr)
2360-
let definitionSymbolLocation = definition.map(\.location)
2361-
let definitionLocation = definitionSymbolLocation.flatMap(indexToLSPLocation2)
2329+
guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: related.symbol.usr) else {
2330+
return nil
2331+
}
23622332

23632333
return indexToLSPTypeHierarchyItem2(
2364-
symbol: related.symbol,
2365-
moduleName: definitionSymbolLocation?.moduleName,
2366-
location: definitionLocation ?? location, // Use occurrence location as fallback
2334+
definition: definition,
2335+
moduleName: definition.location.moduleName,
23672336
index: index
23682337
)
23692338
}
@@ -2408,24 +2377,23 @@ private let maxWorkspaceSymbolResults = 4096
24082377
package typealias Diagnostic = LanguageServerProtocol.Diagnostic
24092378

24102379
fileprivate extension CheckedIndex {
2411-
/// Get the name of the symbol that is a parent of this symbol, if one exists
2412-
func containerName(of symbol: SymbolOccurrence) -> String? {
2380+
func containerNames(of symbol: SymbolOccurrence) -> [String] {
24132381
// The container name of accessors is the container of the surrounding variable.
24142382
let accessorOf = symbol.relations.filter { $0.roles.contains(.accessorOf) }
24152383
if let primaryVariable = accessorOf.sorted().first {
24162384
if accessorOf.count > 1 {
24172385
logger.fault("Expected an occurrence to an accessor of at most one symbol, not multiple")
24182386
}
24192387
if let primaryVariable = primaryDefinitionOrDeclarationOccurrence(ofUSR: primaryVariable.symbol.usr) {
2420-
return containerName(of: primaryVariable)
2388+
return containerNames(of: primaryVariable)
24212389
}
24222390
}
24232391

24242392
let containers = symbol.relations.filter { $0.roles.contains(.childOf) }
24252393
if containers.count > 1 {
24262394
logger.fault("Expected an occurrence to a child of at most one symbol, not multiple")
24272395
}
2428-
return containers.filter {
2396+
let container = containers.filter {
24292397
switch $0.symbol.kind {
24302398
case .module, .namespace, .enum, .struct, .class, .protocol, .extension, .union:
24312399
return true
@@ -2434,7 +2402,39 @@ fileprivate extension CheckedIndex {
24342402
.destructor, .conversionFunction, .parameter, .using, .concept, .commentTag:
24352403
return false
24362404
}
2437-
}.sorted().first?.symbol.name
2405+
}.sorted().first
2406+
2407+
if let container {
2408+
if let containerDefinition = primaryDefinitionOrDeclarationOccurrence(ofUSR: container.symbol.usr) {
2409+
return self.containerNames(of: containerDefinition) + [container.symbol.name]
2410+
}
2411+
return [container.symbol.name]
2412+
} else {
2413+
return []
2414+
}
2415+
}
2416+
2417+
/// Take the name of containers into account to form a fully-qualified name for the given symbol.
2418+
/// This means that we will form names of nested types and type-qualify methods.
2419+
func fullyQualifiedName(of symbolOccurrence: SymbolOccurrence) -> String {
2420+
let symbol = symbolOccurrence.symbol
2421+
let containerNames = containerNames(of: symbolOccurrence)
2422+
guard let containerName = containerNames.last else {
2423+
// No containers, so nothing to do.
2424+
return symbol.name
2425+
}
2426+
switch symbol.language {
2427+
case .objc where symbol.kind == .instanceMethod || symbol.kind == .instanceProperty:
2428+
return "-[\(containerName) \(symbol.name)]"
2429+
case .objc where symbol.kind == .classMethod || symbol.kind == .classProperty:
2430+
return "+[\(containerName) \(symbol.name)]"
2431+
case .cxx, .c, .objc:
2432+
// C shouldn't have container names for call hierarchy and Objective-C should be covered above.
2433+
// Fall back to using the C++ notation using `::`.
2434+
return (containerNames + [symbol.name]).joined(separator: "::")
2435+
case .swift:
2436+
return (containerNames + [symbol.name]).joined(separator: ".")
2437+
}
24382438
}
24392439
}
24402440

Tests/SourceKitLSPTests/CallHierarchyTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ final class CallHierarchyTests: XCTestCase {
833833
[
834834
CallHierarchyIncomingCall(
835835
from: CallHierarchyItem(
836-
name: "Bar.init()",
836+
name: "Outer.Bar.init()",
837837
kind: .constructor,
838838
tags: nil,
839839
uri: project.fileURI,

0 commit comments

Comments
 (0)