Skip to content

Commit 6f94d86

Browse files
authored
Merge pull request #1103 from ahoppen/ahoppen/call-hierarchy-look-through-protocols
Show calls to satisfied protocol requirements in call hierarchy
2 parents db633b5 + 8fa839c commit 6f94d86

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2086,7 +2086,14 @@ extension SourceKitServer {
20862086
else {
20872087
return []
20882088
}
2089-
let callableUsrs = [data.usr] + index.occurrences(relatedToUSR: data.usr, roles: .accessorOf).map(\.symbol.usr)
2089+
var callableUsrs = [data.usr]
2090+
// Calls to the accessors of a property count as calls to the property
2091+
callableUsrs += index.occurrences(relatedToUSR: data.usr, roles: .accessorOf).map(\.symbol.usr)
2092+
// Also show calls to the functions that this method overrides. This includes overridden class methods and
2093+
// satisfied protocol requirements.
2094+
callableUsrs += index.occurrences(ofUSR: data.usr, roles: .overrideOf).flatMap { occurrence in
2095+
occurrence.relations.filter { $0.roles.contains(.overrideOf) }.map(\.symbol.usr)
2096+
}
20902097
let callOccurrences = callableUsrs.flatMap { index.occurrences(ofUSR: $0, roles: .calledBy) }
20912098
let calls = callOccurrences.flatMap { occurrence -> [CallHierarchyIncomingCall] in
20922099
guard let location = indexToLSPLocation(occurrence.location) else {

Tests/SourceKitLSPTests/CallHierarchyTests.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,4 +447,102 @@ final class CallHierarchyTests: XCTestCase {
447447
]
448448
)
449449
}
450+
451+
func testIncomingCallHierarchyLooksThroughProtocols() async throws {
452+
let ws = try await IndexedSingleSwiftFileWorkspace(
453+
"""
454+
protocol MyProtocol {
455+
func foo()
456+
}
457+
struct MyStruct: MyProtocol {
458+
func 1️⃣foo() {}
459+
}
460+
struct Unrelated: MyProtocol {
461+
func foo() {}
462+
}
463+
func 2️⃣test(proto: MyProtocol) {
464+
proto.3️⃣foo()
465+
Unrelated().foo() // should not be considered a call to MyStruct.foo
466+
}
467+
"""
468+
)
469+
let prepare = try await ws.testClient.send(
470+
CallHierarchyPrepareRequest(
471+
textDocument: TextDocumentIdentifier(ws.fileURI),
472+
position: ws.positions["1️⃣"]
473+
)
474+
)
475+
let initialItem = try XCTUnwrap(prepare?.only)
476+
let calls = try await ws.testClient.send(CallHierarchyIncomingCallsRequest(item: initialItem))
477+
XCTAssertEqual(
478+
calls,
479+
[
480+
CallHierarchyIncomingCall(
481+
from: CallHierarchyItem(
482+
name: "test(proto:)",
483+
kind: .function,
484+
tags: nil,
485+
detail: "test", // test is the module name because the file is called test.swift
486+
uri: ws.fileURI,
487+
range: Range(ws.positions["2️⃣"]),
488+
selectionRange: Range(ws.positions["2️⃣"]),
489+
data: .dictionary([
490+
"usr": .string("s:4testAA5protoyAA10MyProtocol_p_tF"),
491+
"uri": .string(ws.fileURI.stringValue),
492+
])
493+
),
494+
fromRanges: [Range(ws.positions["3️⃣"])]
495+
)
496+
]
497+
)
498+
}
499+
500+
func testIncomingCallHierarchyLooksThroughSuperclasses() async throws {
501+
let ws = try await IndexedSingleSwiftFileWorkspace(
502+
"""
503+
class Base {
504+
func foo() {}
505+
}
506+
class Inherited: Base {
507+
override func 1️⃣foo() {}
508+
}
509+
class Unrelated: Base {
510+
override func foo() {}
511+
}
512+
func 2️⃣test(base: Base) {
513+
base.3️⃣foo()
514+
Unrelated().foo() // should not be considered a call to MyStruct.foo
515+
}
516+
"""
517+
)
518+
let prepare = try await ws.testClient.send(
519+
CallHierarchyPrepareRequest(
520+
textDocument: TextDocumentIdentifier(ws.fileURI),
521+
position: ws.positions["1️⃣"]
522+
)
523+
)
524+
let initialItem = try XCTUnwrap(prepare?.only)
525+
let calls = try await ws.testClient.send(CallHierarchyIncomingCallsRequest(item: initialItem))
526+
XCTAssertEqual(
527+
calls,
528+
[
529+
CallHierarchyIncomingCall(
530+
from: CallHierarchyItem(
531+
name: "test(base:)",
532+
kind: .function,
533+
tags: nil,
534+
detail: "test", // test is the module name because the file is called test.swift
535+
uri: ws.fileURI,
536+
range: Range(ws.positions["2️⃣"]),
537+
selectionRange: Range(ws.positions["2️⃣"]),
538+
data: .dictionary([
539+
"usr": .string("s:4testAA4baseyAA4BaseC_tF"),
540+
"uri": .string(ws.fileURI.stringValue),
541+
])
542+
),
543+
fromRanges: [Range(ws.positions["3️⃣"])]
544+
)
545+
]
546+
)
547+
}
450548
}

0 commit comments

Comments
 (0)