Skip to main content

Recover qualified Swift call edges (Type.method / Type.shared.method) that Graphify's AST pass drops, reconnecting cross-service call pathways in the knowledge graph.

Project description

graphify-swift-edges

Recover qualified Swift call edges that Graphify's tree-sitter pass records but never links — reconnecting cross-type call pathways in the knowledge graph.

The problem

Graphify builds a code knowledge graph with tree-sitter. For Swift it correctly records each type's methods (Type --method--> .foo()), but it does not link qualified call sites back to the callee's method node:

// Caller.swift
try PaymentStore.reconcile(id: row.id)          // static, fully resolvable
let rows = PaymentStore.shared.fetch(query: q)   // singleton, fully resolvable

These are fully-resolvable calls, yet in the resulting graph the callee type has zero incoming calls edges. Types that talk to each other through static or singleton methods become disconnected islands, so graphify path and graphify affected cannot trace the pathway between them — even though the call is right there in the source.

What this tool does

It re-parses the source with the same tree-sitter grammar, then resolves qualified calls deterministically against the graph's own symbol table and appends the missing calls edges:

  1. Build {TypeLabel → typeNode} and {(typeNode, method) → methodNode} from the graph's existing method edges.
  2. Walk every call_expression; keep navigation_expression callees of the form Type.method(…) or Type.shared.method(…) (PascalCase receiver).
  3. Resolve receiver → type node, attribute the caller by enclosing-type AST ancestry (not a line heuristic), and emit caller --calls--> methodNode.

Precision guards (why it doesn't invent edges)

An edge is emitted only when all hold — otherwise the call is skipped, never guessed:

  • the receiver label maps to exactly one type node (ambiguous labels are skipped);
  • that type actually owns a method of that name;
  • the receiver is type/singleton-qualified (unqualified foo() is skipped — see Scope).

Validation — GRDB.swift (479 files)

A large, real-world, open-source Swift codebase (different authors, heavy use of extensions, generics, and protocols), checkout including its test suite:

result
calls edges added 70
path PlayerListModelAppDatabase "No path found" → 2-hop call edge
precision (independent type-def check) 69 / 70 (98.6%)

Reproduce:

graphify .                                              # build graphify-out/graph.json on GRDB
graphify-swift-edges graphify-out/graph.json . -o graphify-out/graph.json
graphify path "PlayerListModel" "AppDatabase"          # now a real 2-hop call edge

Two honest findings this surfaced:

  • Recall is codebase-dependent, and the guard is deliberately conservative. GRDB triggered 3,614 ambiguous-receiver skips because extension/generic-heavy types fragment into many same-label nodes (Database → 15 nodes; TableRecord/FetchRequest → 5 each) and the test suite defines many same-named fixtures (Player, Pet, Item). The tool skips all of these rather than guess — fewer edges, never wrong ones. Recovery scales with how many qualified calls have an unambiguous receiver, which varies a lot by codebase.
  • The single false positive is the documented residual mode. Pet.setup() resolved even though GRDB defines struct Pet in several test files: the upstream graph had collapsed them into one node, so the ambiguity guard couldn't see the collision. 1-in-70, confined to duplicated test fixtures — exactly what the IndexStoreDB route (Scope) would eliminate.

Precision was measured independently of the parser, via type-definition uniqueness in the raw source: a receiver whose name has exactly one class/struct/enum/actor/protocol definition cannot be a same-name collision.

Usage

uv tool install graphify-swift-edges      # or: pip install graphify-swift-edges
# after `graphify` has built graphify-out/graph.json:
graphify-swift-edges graphify-out/graph.json /path/to/swift/src -o graphify-out/graph.json

Then query the reconnected graph with graphify path (verified working):

graphify path "SomeCaller" "SomeCallee" --graph graphify-out/graph.json
# Shortest path (2 hops): SomeCaller --calls [RESOLVED_QUALIFIED]--> .method() <--method-- SomeCallee

affected caveat (measured): recovered edges point at method nodes. graphify affected on the owning type returns nothing (it reverse-traverses calls but not method), and on a method label it errors with No unique node match (labels like .fetch() are ambiguous across types). So use path for cross-type tracing, not affected, until Graphify's affected learns to hop method edges.

Scope (honest boundaries)

  • Recovers: type-qualified (Foo.bar()) and singleton-qualified (Foo.shared.bar()) calls.
  • Does NOT recover: instance-variable calls (let s = Foo(); s.bar()) — these need real type inference. The correct general fix is a SourceKit / IndexStoreDB extractor that reads Swift's precise semantic index (protocol dispatch, generics, inferred types included). That is the documented sequel, not this tool.
  • The residual false-positive mode (a same-name type whose duplicate definitions the upstream graph collapsed into one node) is rare and, in testing, confined to duplicated test fixtures.
  • Validated on one large open-source codebase; the resolution is language-general in principle (Kotlin, TS have the same qualified-call shape) but only Swift is implemented and measured here.

Tests

uv run --with pytest --with tree-sitter --with tree-sitter-swift python -m pytest

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

graphify_swift_edges-0.1.0.tar.gz (22.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

graphify_swift_edges-0.1.0-py3-none-any.whl (8.8 kB view details)

Uploaded Python 3

File details

Details for the file graphify_swift_edges-0.1.0.tar.gz.

File metadata

  • Download URL: graphify_swift_edges-0.1.0.tar.gz
  • Upload date:
  • Size: 22.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for graphify_swift_edges-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5be5b925820dee0691234f0cee6afc076da49661d7011441088949f38d2b9666
MD5 ce76a162fe8385b3f5656df949afd5d4
BLAKE2b-256 1e69fd57e1cddab1cd23355a0311f341a2c0ed72f7ba92da9a59f17b7162e2aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for graphify_swift_edges-0.1.0.tar.gz:

Publisher: publish.yml on novicetrader11-arch/graphify-swift-edges

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file graphify_swift_edges-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for graphify_swift_edges-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 18bf85e0ba9d2bf3054b9c42e405261dfbf5210e8e6a4f1f876d5ab64ee0a06a
MD5 dd5a66b35dd11a6c05a3b2967c3dd5b4
BLAKE2b-256 027733a66a38ed64026b2a44e5e0e93bde1fd19175d94a501df95b6d793037f6

See more details on using hashes here.

Provenance

The following attestation bundles were made for graphify_swift_edges-0.1.0-py3-none-any.whl:

Publisher: publish.yml on novicetrader11-arch/graphify-swift-edges

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page