bug(swift): protocol extension calls resolve to unrelated private methods
Area
gitnexus (CLI / core / indexing / MCP server)
Summary
Unqualified calls inherited from a Swift protocol extension resolve to unrelated private methods with the same names, producing incorrect call edges and a missing downstream execution path.
Context
This standalone, dependency-free Swift program checks which helpers execute and compares that result with a fresh GitNexus index. Related issue #3262 concerns a nested constructor in an extension; this reproduction concerns inherited instance methods on a protocol conformer. The underlying implementation cause has not been established.
Expected behavior
The three unqualified calls in Scenario.run resolve to ScenarioSupport extension methods in Support.swift. store has type Store, and the subsequent call reaches Worker.execute.
The unrelated methods in AUnrelatedHelpers.swift are private to other types. In addition, OtherScenario.makeStore requires a currentValue: argument, and OtherItems.insertItem has no count: parameter. They cannot satisfy these call sites.
Actual behavior
context run returns all three unrelated private methods as outgoing calls and reports a process ending in OtherStore. trace run execute returns no_path, with OtherStore as the furthest reached symbol.
The executable compiles with warnings treated as errors and prints worker:9:item, confirming the intended extension helpers and downstream worker execute.
The context result reports epistemic: "lower-bound" and a boundary for an unresolved incoming run receiver in main.swift. That limitation is retained here; this report does not claim an exact result. The reported outgoing callees are nevertheless incompatible with the Swift call sites.
Steps to reproduce
- Create a directory with these four files.
Support.swift:
protocol ScenarioSupport {}
struct Worker {
func execute() -> String {
"worker"
}
}
struct Store {
let worker: Worker
}
extension ScenarioSupport {
func makeStore(observer: Int? = nil) -> Store {
Store(worker: Worker())
}
func makeValue(year: Int, month: Int, day: Int) -> Int {
year + month + day
}
func insertItem(into store: Store, count: Int = 1) -> String {
"item"
}
}
AUnrelatedHelpers.swift:
struct OtherStore {
let value: Int
}
struct OtherScenario {
private func makeStore(currentValue: Int) -> OtherStore {
OtherStore(value: currentValue)
}
}
enum OtherValue {
private static func makeValue(year: Int, month: Int, day: Int) -> Int {
-1
}
}
enum OtherItems {
private static func insertItem(into store: Store) -> String {
"decoy"
}
}
Scenario.swift:
struct Scenario: ScenarioSupport {
func run() -> String {
let store = makeStore()
let value = makeValue(year: 2, month: 3, day: 4)
let item = insertItem(into: store, count: 1)
return "\(store.worker.execute()):\(value):\(item)"
}
}
main.swift:
let output = Scenario().run()
precondition(output == "worker:9:item", output)
print(output)
- Initialize and commit the example, then compile and run it:
git init
git add Support.swift AUnrelatedHelpers.swift Scenario.swift main.swift
git commit -m "Add Swift protocol helper reproduction"
mkdir -p bin
swiftc -warnings-as-errors Support.swift AUnrelatedHelpers.swift Scenario.swift main.swift -o bin/reproducer
./bin/reproducer
The executable prints worker:9:item; its precondition passes.
- Create a fresh index and query from that directory:
gitnexus analyze --index-only .
gitnexus status --json
gitnexus context run --file Scenario.swift --content --repo "$PWD"
gitnexus trace run execute --from-file Scenario.swift --to-file Support.swift --depth 4 --repo "$PWD"
Environment
- GitNexus 1.6.11
- Node.js v26.8.2
- macOS 26.6.2, arm64
- Apple Swift 6.3.3
- Fresh index: 29 nodes, 50 edges; status reports current commit, current content for all four Swift files, and
up-to-date.
Logs / screenshots
Outgoing callees from the actual context run result:
{
"epistemic": "lower-bound",
"boundaries": [
"1 call site invoking `run` was dropped at index time because the receiver's type could not be established (e.g. an unresolved constructor, factory or chained expression). Those callers are absent from this result \u2014 actual impact may be higher."
],
"outgoing": {
"calls": [
{
"uid": "Function:AUnrelatedHelpers.swift:OtherItems.insertItem#1",
"name": "insertItem",
"filePath": "AUnrelatedHelpers.swift"
},
{
"uid": "Function:AUnrelatedHelpers.swift:OtherScenario.makeStore#1",
"name": "makeStore",
"filePath": "AUnrelatedHelpers.swift"
},
{
"uid": "Function:AUnrelatedHelpers.swift:OtherValue.makeValue#3",
"name": "makeValue",
"filePath": "AUnrelatedHelpers.swift"
}
]
}
}
Trace result:
{
"status": "no_path",
"from": {
"name": "run",
"filePath": "Scenario.swift",
"startLine": 1
},
"to": {
"name": "execute",
"filePath": "Support.swift",
"startLine": 3
},
"furthest": {
"name": "OtherStore",
"filePath": "AUnrelatedHelpers.swift",
"startLine": 0,
"depth": 2
}
}
Source: abhigyanpatwari/GitNexus