Skip to content

feat(graph): full-fidelity Swift extraction - #219

Merged
shhdwi merged 6 commits into
trailhq:mainfrom
mattstrayer:feat/swift-full-fidelity
Aug 27, 2026
Merged

shhdwi merged 6 commits into
trailhq:mainfrom
mattstrayer:feat/swift-full-fidelity

Conversation

@mattstrayer

@mattstrayer mattstrayer commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

What

Promotes Swift (.swift) from the generic breadth tier to a hand-written depth-tier extractor, following the Kotlin promotion (#130). Today the breadth tier's swift.scm has no @reference.call captures at all, so a Swift repo gets symbols and zero wiring — no callers, no blast radius, no coupling-ranked retrieval.

How

Extractor (src/graph/extract.ts, mirroring describeKotlin):

  • One class_declaration node covers class / struct / enum / actor / extension, told apart by their own keyword token → class, struct, enum, class, and the extension case below. protocol → interface, typealias → type, top-level let/var → variable.
  • Extensions: extension Point takes the extended type's name, so its members mint as Point methods and member calls on a Point receiver resolve to them. The extension node itself is kind module, not a second class named Point — that duplicate made every Point() initializer call and : Point heritage target ambiguous (resolve never guesses), and a test pins the regression.
  • init → a method named after its type (like Java's constructor_declaration); func → method inside any type, function elsewhere.
  • Calls via call_expression with navigation_expression / self / super receivers; : inheritance clause → extends edges (bare names — Swift can't say syntactically which specifier is the superclass); import → module-path import edges.
  • Visibility: only private / fileprivate hide a symbol. Swift's default internal is module-wide, and a repo is typically one module, so it counts as API surface.

Resolution (src/graph/resolve.ts, two existing precedents reused):

  • A bare lowercase call inside a type body is ONE edge carrying both readings in Swift's inner-scope-first order: the member reading first (owner-qualified index + in-repo ancestor chain, so clearLogs() in a test subclass finds the base class's method), then the free-function reading only when no member exists on the chain — a name defined as both yields the member edge alone, as Swift dispatches it.
  • super.method() resolves against the declaration's own superclass (the first : entry — Swift's grammar puts the superclass before any protocol), climbing past the current class's override.
  • Overloads disambiguate by declared arity vs call-site argument count, Java's exact mechanism (defaults/variadics make the arity a minimum); a set arity can't split (save(Int) vs save(String)) drops rather than taking the same-file first-declaration tiebreak.
  • Animal(legs: 4) is an ordinary call node with no new; it falls back to class/struct/enum targets once functions find nothing — Python's constructor-fallback shape.

Dependency: tree-sitter-swift@^0.7.1 — the first release whose install compiles the shipped parser.c instead of regenerating it via tree-sitter-cli (0.6.0's gyp action breaks under npm hoisting: No rule to make target node_modules/tree-sitter-cli). Its peer wants tree-sitter@^0.22; a root overrides entry pins it to the repo's 0.21 runtime for dev installs, and I verified a transitive (end-user) install resolves cleanly without any override, with the compiled grammar loading and parsing fine on the 0.21 runtime (ABI 14).

Receiver-type bindings

A second commit adds the Swift collectBindings pass, so untyped receivers now resolve too. Bound clues, all syntax-local: typed parameters (argument labels handled), typed properties (one optional level unwrapped), initializer-call assignments (let vet = Vet() — an UpperCamelCase callee names the constructed type, the same convention trust as Go's NewX), and fields bound both bare and self.-prefixed like Java's. self.repo.save() binds one hop off self like TS's this.x, and Animal.census() resolves via an uppercase-receiver rule mirroring PHP's scoped calls. A receiver with no local clue (a chained call's result) stays unresolved rather than guessed.

Dogfooded on two real Swift codebases

Alamofire (108 files, ~43k lines) and swift-composable-architecture (796 files, ~79k lines): both build 100% of files with zero parse failures, graft check reports OK (deterministic) on both, and the graphs come out at 3,067 nodes / 6,530 edges and 6,127 nodes / 12,433 edges respectively.

Sampling cross-file inferred method-target call edges caught two false-positive classes in the first implementation of the implicit-self rule (then a bare-name kinds widening): Text("hi") — SwiftUI's own init — bound to an unrelated in-repo extension Text { init(template:) }, and contains(element) inside extension Set — a stdlib call — bound to an unrelated type's only in-repo contains. The third commit replaces the widening with the ancestor-chain intent described above; after it, both classes are gone, while 280 inherited stored(...) calls in Alamofire's tests and 144 clearLogs/assertLogs calls in TCA's still resolve to their base-class methods through the chain.

Testing

  • New test/graph-swift.test.ts: 20 tests — kind mapping for all five class_declaration keywords, extension member ownership + the ambiguity regression, initializer methods, call shapes (bare / member / self / super / trailing closure), heritage, imports, visibility incl. private(set), and resolution-level pins through buildGraph (implicit-self, constructor edges, ambiguous-drops, receiver-typed member calls incl. extension methods).
  • test/graph-languages.test.ts / test/supported-extensions.test.ts updated for the tier move.
  • Full suite: 935/935 pass.
  • End-to-end on a demo Swift package: extends Animal -> Greeter, run -> walk (extension → class), walk -> greet (implicit self), makeAnimal -> Animal (initializer, inferred), cross-file bare calls — all resolving.

README's supported-languages section is also brought current (Kotlin and PHP were still listed as breadth-tier; Nix was missing).

Move Swift (.swift) from the generic breadth tier — whose tags query had no
call captures, so Swift repos indexed symbols with zero wiring — to a
hand-written tree-sitter extractor, following Kotlin's promotion (trailhq#130).

tree-sitter-swift folds class/struct/enum/actor/extension into one
class_declaration node type, told apart by their own keyword; describeSwift
rekinds them (actor → class), protocols become interfaces, typealias a type,
and top-level let/var variables. An extension takes the extended type's name
so its members mint as that type's methods, but the node itself is kind
"module" — a second same-named class would make every initializer call and
heritage target ambiguous and drop. init becomes a method named after its
type, like a Java constructor.

Calls resolve via call_expression with navigation/self/super receivers. A
bare call inside a type body may be an implicit-`self` member call, so it may
also match a method (R Phase 4's widening; unique matches only). An
initializer call — an ordinary call node, no `new` — falls back to
class/struct/enum targets once functions find nothing (Python's
constructor-fallback shape). The `:` inheritance clause yields extends edges,
import declarations yield module-path import edges, and only
private/fileprivate hide a symbol: Swift's default `internal` is module-wide,
which for a one-module repo is the API surface.

Dependency: tree-sitter-swift ^0.7.1 — the first release whose install
compiles the shipped parser instead of regenerating it via tree-sitter-cli,
which broke under npm's hoisting. Its tree-sitter peer wants ^0.22, so a root
`overrides` entry pins it to the repo's 0.21 runtime for dev installs;
transitive (end-user) installs resolve cleanly without it (verified).

Known gap, shared with Kotlin: untyped receivers (`a.run()`) have no bindings
collector yet, so those member calls stay unresolved rather than guessed.
github-actions Bot added a commit that referenced this pull request Aug 24, 2026
Close the untyped-receiver gap the Swift promotion shipped with: a
collectBindings pass for Swift, from the confident, syntax-local clues the
other languages already trust. Typed parameters (`func feed(animal: Animal)`,
the last simple_identifier before the `:` is the local name — the first may
be an external argument label), typed properties (`var d: Doctor`, one level
of optional unwrapped), and initializer-call assignments (`let vet = Vet()` —
an UpperCamelCase callee names the constructed type, the same convention
trust as Go's `NewX`). A property directly inside a type body is a field,
bound at the type's scope both bare and `self.`-prefixed, like Java's fields.

calleeName gains the `self.repo.save()` shape (one hop off self binds like
TS's `this.x`) and now emits member calls with unknowable receivers instead
of dropping them at extract time — recvType stays unset and resolve drops
them, the same contract as Java's and TS's unknown receivers. A type-member
call (`Animal.census()`) resolves via an uppercase-receiver fallback in
resolveRecvType, mirroring PHP's scoped-call rule; a shadowing local binding
is tried first.

swiftDefName mirrors describeSwift's scope segments (extensions named after
the extended type, inits after their enclosing type) so the two scope stacks
stay in lockstep, per bindings.ts's existing no-value-import contract.
github-actions Bot added a commit that referenced this pull request Aug 24, 2026
…lobal name uniqueness

Dogfooding the Swift extractor on Alamofire (108 files) and
swift-composable-architecture (796 files) caught two false-positive classes
in the bare-call kind widening:

- An UpperCamelCase bare call is an initializer, and matching it against
  methods bound every `Text("hi")` (SwiftUI's own init) to an unrelated
  in-repo `extension Text { init(template:) }` that happened to be the
  repo's only method named Text.
- A stdlib call inside an extension (`contains(element)` in `extension
  Set`) bound to an unrelated type's only in-repo `contains`.

Replace the widening: a bare lowercase call in a type body now emits a
second intent — viaMember, typed to the enclosing class — which resolves
only through the owner-qualified method index and the class's in-repo
ancestor chain, and drops otherwise. The plain intent still resolves
genuine free-function calls, and initializer calls take the class/struct/
enum fallback, which extension nodes (kind "module") can never false-match.

On the two corpora after the fix: both false-positive classes are gone,
280 inherited `stored(...)` calls in Alamofire's tests and 144
`clearLogs`/`assertLogs` calls in TCA's resolve to their base-class
methods via the chain, both graphs build 100% of files, and `graft check`
reports OK (deterministic) on both.
@mattstrayer

Copy link
Copy Markdown
Contributor Author

Tested against two substantial real-world Swift codebases before review:

Results on both: 100% of files parsed with zero failures, graft check reports OK (deterministic rebuild), no malformed nodes. Graphs came out at 3,067 nodes / 6,530 edges (Alamofire) and 6,127 nodes / 12,433 edges (TCA).

Sampling cross-file inferred method-target call edges on these corpora caught two false-positive classes in the first implicit-self implementation (Text("hi") binding to an unrelated in-repo extension Text { init(template:) }; contains(element) inside extension Set binding to an unrelated type's only contains). Commit 7c9f129 replaces that mechanism with the ancestor-chain intent described in the PR body — after it, both classes are gone while the correct inherited-method edges remain (280 stored(...) calls in Alamofire's tests, 144 clearLogs/assertLogs calls in TCA's, all resolving to their base-class methods through the chain).

…resolution

Address three adversarial-review findings against the Swift promotion:

1. Bare-call double edges. A lowercase bare call in a type body emitted two
   independent intents (member + free function), and when BOTH targets
   existed the graph kept both edges — Swift resolves inner-scope-first, to
   the member alone. Now it is ONE edge: the member reading (viaMember +
   recvType = enclosing type), with a new RawEdge.implicitSelf flag letting
   resolve.ts take the free-function reading only when the whole owner chain
   has no such member. An ambiguous member set still drops without falling
   through — a member exists, so the function reading is wrong.

2. Overloads resolved to the first declaration. With no arity data, several
   same-owner candidates hit the same-file tiebreak, which picks whichever
   overload appears first and stamps it `extracted`. Swift callables now
   carry arity (required-parameter count; defaults/variadics mark it a
   minimum) and Swift call sites carry argCount (value_arguments + trailing
   closure) — Java's exact mechanism — so distinct-arity overloads resolve
   precisely, and a set arity can't split (`save(Int)` vs `save(String)`)
   drops rather than guessing by source order.

3. super.method() never resolved. resolveRecvType maps `super` through
   ctx.rSuperClass, which only R populated. Swift class declarations now set
   it from their own `:` clause's first entry (the grammar puts a superclass
   before any protocol), so `super.ping()` in an override resolves to the
   PARENT's method, climbing past the current class's own.

Four new tests pin member-vs-global precedence (inside and outside the
type), equal-arity drop, distinct-arity resolution, and super dispatch.
Re-dogfooded on Alamofire and swift-composable-architecture: both graphs
build 100% of files, `graft check` OK, duplicate-reading and first-overload
edges gone.
@mattstrayer

Copy link
Copy Markdown
Contributor Author

An adversarial review pass (Codex) against this branch surfaced three dispatch-precision findings; commit c3e4c21 addresses all three:

  1. Bare-call double edges — a lowercase bare call in a type body emitted both a member intent and a free-function intent, and when both targets existed (func helper() at top level AND A.helper) the graph kept both. Now it is one edge resolved in Swift's inner-scope-first order: member reading first, free-function reading only when the whole owner chain has no such member.
  2. Overloads picked the first declaration — with no arity data, save("x") against save(Int)/save(String) resolved to whichever overload appeared first in the file, stamped extracted. Swift callables now carry declared arity (defaults/variadics mark it a minimum) and call sites carry argument count (trailing closure included), reusing Java's existing narrowByArity; a set arity can't split drops instead of guessing by source order.
  3. super.method() never resolved — resolveRecvType mapped super through a field only R populated. Swift class declarations now populate it from their own : clause's first entry, so a super.ping() in an override resolves to the parent's method, past the current class's own.

Four new tests pin member-vs-global precedence, equal-arity drop, distinct-arity resolution, and super dispatch (Swift suite now 20 tests; full suite 935/935). Re-dogfooded on Alamofire and swift-composable-architecture: 100% parse, graft check OK, duplicate-reading and first-overload edges gone.

github-actions Bot added a commit that referenced this pull request Aug 24, 2026
@github-actions

github-actions Bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

🌱 graft blast radius

1 area changed → 5 areas can be affected. 16 dependent symbols, depth 2.
Tests: resolveEdges has tests the diff did not touch.

flowchart TB
  A0(("build.ts<br/>9 symbols"))
  A1(("cli.ts<br/>3 symbols"))
  A2(("review.ts<br/>2 symbols"))
  A3(("enrich.ts<br/>1 symbol"))
  A4(("tools.ts<br/>1 symbol"))
  classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
  class A0,A1,A2,A3,A4 reached;
Loading
Can be affected Symbols Nearest hop Reached from
build.ts 9 src/graph/build.ts:L1-L404 build.ts — imports, depth 1 resolveEdges
cli.ts 3 src/cli.ts:L1-L1033 cli.ts — imports, depth 2 resolveEdges
review.ts 2 src/app/review.ts:L1-L159 review.ts — imports, depth 2 resolveEdges
enrich.ts 1 src/graph/lsp/enrich.ts:L1-L141 enrich.ts — imports, depth 1 resolveEdges
tools.ts 1 src/mcp/tools.ts:L1-L339 tools.ts — imports, depth 2 resolveEdges
All 16 dependent symbols, grouped by area

build.ts — 9 symbols in 7 files

  • src/graph/build.ts:L1-L404 — build.ts (imports, depth 1)
  • src/graph/build.ts:L147-L403 — buildGraph (calls, depth 1)
  • src/graph/check.ts:L1-L190 — check.ts (imports, depth 1)
  • src/graph/container.ts:L1-L209 — container.ts (imports, depth 1)
  • src/graph/source-files.ts:L1-L94 — source-files.ts (imports, depth 1)
  • src/graph/fingerprint.ts:L1-L185 — fingerprint.ts (imports, depth 2)
  • src/graph/refresh.ts:L1-L270 — refresh.ts (imports, depth 2)
  • src/graph/refresh.ts:L150-L223 — ensureFreshGraph (calls, depth 2)
  • src/graph/workspace.ts:L1-L509 — workspace.ts (imports, depth 2)

cli.ts — 3 symbols in 2 files

  • src/cli.ts:L1-L1033 — cli.ts (imports, depth 2)
  • src/engine.ts:L1-L155 — engine.ts (imports, depth 2)
  • src/engine.ts:L86-L95 — graph (calls, depth 2)

review.ts — 2 symbols in 1 file

  • src/app/review.ts:L1-L159 — review.ts (imports, depth 2)
  • src/app/review.ts:L45-L84 — reviewPullRequest (calls, depth 2)

enrich.ts — 1 symbol in 1 file

  • src/graph/lsp/enrich.ts:L1-L141 — enrich.ts (imports, depth 1)

tools.ts — 1 symbol in 1 file

  • src/mcp/tools.ts:L1-L339 — tools.ts (imports, depth 2)
Test signal per changed area — 1 ⚠

Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.

  • ⚠ resolveEdges — 1 of 23 reached · 3 test files reach it, none changed here
    • not reached: defName, swiftDefName, resolveRecvType, isClassNode, visit, handleSwift, swiftCtorTypeName, swiftTypeName, …14 more
32 test suites also reference this code

40 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.

  • test/ask-index.test.ts
  • test/ask.test.ts
  • test/container-extract.test.ts
  • test/covers.test.ts
  • test/generic-extract.test.ts
  • test/graph-cross-language.test.ts
  • test/graph-enrich-pending.test.ts
  • test/graph-go.test.ts
  • test/graph-incremental.test.ts
  • test/graph-invariants.test.ts
  • test/graph-java.test.ts
  • test/graph-php.test.ts
  • test/graph-posix-paths.test.ts
  • test/graph-python.test.ts
  • test/graph-r-classes.test.ts
  • test/graph-r-phase3.test.ts
  • test/graph-r-phase4.test.ts
  • test/graph-r-phase5.test.ts
  • test/graph-r.test.ts
  • test/graph-references.test.ts
  • …12 more

⚠️ 4 changed files not in the graph (CHANGELOG.md, README.md, package-lock.json, package.json) — no parser claims the extension, or the index predates the file.

graft blast · refs/graft/base...HEAD · depth 2 · 11 changed files

Open the interactive graph → — click an area to see the code that changed, and the line that reaches it.

github-actions Bot added a commit that referenced this pull request Aug 25, 2026
github-actions Bot added a commit that referenced this pull request Aug 25, 2026
github-actions Bot added a commit that referenced this pull request Aug 25, 2026
# Conflicts:
#	CHANGELOG.md
#	package.json
@shhdwi

shhdwi commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Verified locally on top of current main. Rebased two trivial conflicts (CHANGELOG keep-both; package.json — kept the renamed @davisvaughan/tree-sitter-r key from #233 alongside the new swift allowScripts entry + overrides). extract.ts/resolve.ts auto-merged.

Depth-tier Swift (native tree-sitter-swift@0.7.1, no committed binaries). Behavioral on a class+struct+protocol+cross-type fixture: 12 nodes, 16 edges, all resolved — extends (inheritance + protocol conformance), implicit-self calls up the chain, and receiver-type resolution (Zoo.openGates → Dog.feed via a keeper: Dog binding); ambiguous same-name overrides dropped, not guessed. Full suite 1040/1040 incl. 8 graph-swift cases. Merging.

@shhdwi
shhdwi merged commit a09ff53 into trailhq:main Aug 27, 2026
1 of 2 checks passed
github-actions Bot added a commit that referenced this pull request Aug 27, 2026
FrancescoMerenda pushed a commit to FrancescoMerenda/Graft that referenced this pull request Sep 7, 2026
* feat(graph): full-fidelity Swift extraction

Move Swift (.swift) from the generic breadth tier — whose tags query had no
call captures, so Swift repos indexed symbols with zero wiring — to a
hand-written tree-sitter extractor, following Kotlin's promotion (trailhq#130).

tree-sitter-swift folds class/struct/enum/actor/extension into one
class_declaration node type, told apart by their own keyword; describeSwift
rekinds them (actor → class), protocols become interfaces, typealias a type,
and top-level let/var variables. An extension takes the extended type's name
so its members mint as that type's methods, but the node itself is kind
"module" — a second same-named class would make every initializer call and
heritage target ambiguous and drop. init becomes a method named after its
type, like a Java constructor.

Calls resolve via call_expression with navigation/self/super receivers. A
bare call inside a type body may be an implicit-`self` member call, so it may
also match a method (R Phase 4's widening; unique matches only). An
initializer call — an ordinary call node, no `new` — falls back to
class/struct/enum targets once functions find nothing (Python's
constructor-fallback shape). The `:` inheritance clause yields extends edges,
import declarations yield module-path import edges, and only
private/fileprivate hide a symbol: Swift's default `internal` is module-wide,
which for a one-module repo is the API surface.

Dependency: tree-sitter-swift ^0.7.1 — the first release whose install
compiles the shipped parser instead of regenerating it via tree-sitter-cli,
which broke under npm's hoisting. Its tree-sitter peer wants ^0.22, so a root
`overrides` entry pins it to the repo's 0.21 runtime for dev installs;
transitive (end-user) installs resolve cleanly without it (verified).

Known gap, shared with Kotlin: untyped receivers (`a.run()`) have no bindings
collector yet, so those member calls stay unresolved rather than guessed.

* feat(graph): Swift receiver-type bindings

Close the untyped-receiver gap the Swift promotion shipped with: a
collectBindings pass for Swift, from the confident, syntax-local clues the
other languages already trust. Typed parameters (`func feed(animal: Animal)`,
the last simple_identifier before the `:` is the local name — the first may
be an external argument label), typed properties (`var d: Doctor`, one level
of optional unwrapped), and initializer-call assignments (`let vet = Vet()` —
an UpperCamelCase callee names the constructed type, the same convention
trust as Go's `NewX`). A property directly inside a type body is a field,
bound at the type's scope both bare and `self.`-prefixed, like Java's fields.

calleeName gains the `self.repo.save()` shape (one hop off self binds like
TS's `this.x`) and now emits member calls with unknowable receivers instead
of dropping them at extract time — recvType stays unset and resolve drops
them, the same contract as Java's and TS's unknown receivers. A type-member
call (`Animal.census()`) resolves via an uppercase-receiver fallback in
resolveRecvType, mirroring PHP's scoped-call rule; a shadowing local binding
is tried first.

swiftDefName mirrors describeSwift's scope segments (extensions named after
the extended type, inits after their enclosing type) so the two scope stacks
stay in lockstep, per bindings.ts's existing no-value-import contract.

* fix(swift): resolve implicit-self calls via the ancestor chain, not global name uniqueness

Dogfooding the Swift extractor on Alamofire (108 files) and
swift-composable-architecture (796 files) caught two false-positive classes
in the bare-call kind widening:

- An UpperCamelCase bare call is an initializer, and matching it against
  methods bound every `Text("hi")` (SwiftUI's own init) to an unrelated
  in-repo `extension Text { init(template:) }` that happened to be the
  repo's only method named Text.
- A stdlib call inside an extension (`contains(element)` in `extension
  Set`) bound to an unrelated type's only in-repo `contains`.

Replace the widening: a bare lowercase call in a type body now emits a
second intent — viaMember, typed to the enclosing class — which resolves
only through the owner-qualified method index and the class's in-repo
ancestor chain, and drops otherwise. The plain intent still resolves
genuine free-function calls, and initializer calls take the class/struct/
enum fallback, which extension nodes (kind "module") can never false-match.

On the two corpora after the fix: both false-positive classes are gone,
280 inherited `stored(...)` calls in Alamofire's tests and 144
`clearLogs`/`assertLogs` calls in TCA's resolve to their base-class
methods via the chain, both graphs build 100% of files, and `graft check`
reports OK (deterministic) on both.

* fix(swift): member-first bare-call precedence, overload arity, super resolution

Address three adversarial-review findings against the Swift promotion:

1. Bare-call double edges. A lowercase bare call in a type body emitted two
   independent intents (member + free function), and when BOTH targets
   existed the graph kept both edges — Swift resolves inner-scope-first, to
   the member alone. Now it is ONE edge: the member reading (viaMember +
   recvType = enclosing type), with a new RawEdge.implicitSelf flag letting
   resolve.ts take the free-function reading only when the whole owner chain
   has no such member. An ambiguous member set still drops without falling
   through — a member exists, so the function reading is wrong.

2. Overloads resolved to the first declaration. With no arity data, several
   same-owner candidates hit the same-file tiebreak, which picks whichever
   overload appears first and stamps it `extracted`. Swift callables now
   carry arity (required-parameter count; defaults/variadics mark it a
   minimum) and Swift call sites carry argCount (value_arguments + trailing
   closure) — Java's exact mechanism — so distinct-arity overloads resolve
   precisely, and a set arity can't split (`save(Int)` vs `save(String)`)
   drops rather than guessing by source order.

3. super.method() never resolved. resolveRecvType maps `super` through
   ctx.rSuperClass, which only R populated. Swift class declarations now set
   it from their own `:` clause's first entry (the grammar puts a superclass
   before any protocol), so `super.ping()` in an override resolves to the
   PARENT's method, climbing past the current class's own.

Four new tests pin member-vs-global precedence (inside and outside the
type), equal-arity drop, distinct-arity resolution, and super dispatch.
Re-dogfooded on Alamofire and swift-composable-architecture: both graphs
build 100% of files, `graft check` OK, duplicate-reading and first-overload
edges gone.

---------

Co-authored-by: Shrish Dwivedi <shrish@nanonets.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants