Skip to content

Swift: overloaded methods collapse into one node, producing fake callers in trace_path #2061

Description

@jaganio

Version

0.10.8

Platform

macOS (Apple Silicon), Darwin 25.6.0

Summary

Two Swift methods on the same type that differ only in their parameter labels end up as a single node in the graph. The node that survives keeps one overload's line range, but it ends up holding the CALLS edges from both of them.

The practical fallout is that trace_path reports callers that don't exist. A function that only ever calls one overload shows up as a caller of whatever the other overload calls.

Overloading by argument label is pretty common in Swift (work(flag:) vs work(name:)), so this comes up a lot in real code.

Minimal reproduction

Three files, no dependencies, nothing to build.

Sources/Sink.swift

class Sink {
	func target() {}
}

Sources/Service.swift

class Service {
	let sink = Sink()

	// Overload A: DOES call target()
	func work(flag: Bool) {
		self.sink.target()
	}

	// Overload B: does NOT call target()
	func work(name: String) {
		print(name)
	}
}

Sources/Caller.swift

class Caller {
	let service = Service()

	// Calls ONLY overload B, which never reaches target()
	func onlyCallsOverloadB() {
		self.service.work(name: "x")
	}
}
codebase-memory-mcp cli index_repository --repo_path <repro> --name overload-repro --mode fast
codebase-memory-mcp cli search_graph --project overload-repro --name_pattern '^work$'
codebase-memory-mcp cli trace_path  --project overload-repro --function_name target --direction inbound --depth 3

Actual

search_graph finds one node where the source has two methods:

total: 1
  work Method 10-12 1 2

Lines 10-12 are overload B, work(name:). Overload A at lines 5-7 gets no node at all. But the surviving node reports out 2, and the body of work(name:) is just print(name), so it has picked up the call to target() from the overload that no longer exists.

trace_path then reports a caller that isn't real:

function: target
direction: inbound
callers_total: 2
  onlyCallsOverloadB 2      <- should not be here
  work 1

onlyCallsOverloadB calls work(name:), and that body is a single print, so it never reaches target() at any depth.

Expected

Two separate nodes, one per overload, with target() reachable only from work(flag:).

Notes

parse_partial_count is 0 for this repo, so nothing is failing to parse and this isn't a grammar issue. It looks like the qualified name doesn't include the parameter labels that tell Swift overloads apart, so the second declaration just overwrites the first.

This might belong to the same family as the fabricated CALLS edges filed for Rust (#2053) and Go (#1906, #1909, #1927), but the mechanism looks different. There's no stdlib receiver involved here, and both symbols are project-local methods on the same type.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions