Repository navigation
fix(resolution): a call a method hands on to another object no longer links back to it - #2925
Merged
Merged
Conversation
… links back to it matchMethodCall's strategy 3 guesses a method by its name when the receiver's type is unknown. With one method of that name in the project it was taken outright, so a wrapper handing its call on, like Play's `def acceptsURL(url) = proxied.acceptsURL(url)` or vben's `form.validate()` inside `FormApi.validate()`, linked to the method making the call, which then looked recursive in callers and impact. The single-candidate branch now gives no edge when that method is the caller itself and the receiver is another object (not this, self, Self, super or cls), as the word-overlap branch already did. On top of the PR: Go is left out. A Go method's receiver and struct fields are typed before this point, so a Go call that still reaches the guess is on a local the resolver could not type, mostly a range variable over the type's own children, which is real recursion: cobra's `sub.checkCommandGroups()` over `c.commands`, etcd's `child.Repr(...)`, prometheus's `subParameter.toLabels()`. Also a TypeScript wrapper test, a Go range-variable recursion test, and a CHANGELOG bullet. Validation, main (3a93f57) vs this change, 24 repos in 11 languages: no edge added; 63 self-loop edges removed, 56 of them wrong (delegations to a library or another object: playframework 19, vben 9, http4s 8, mihon 8, okhttp 5, ripgrep 3, flask 2, spring-modulith 2) and 7 real recursion through another instance of the caller's type, which now gets no edge (okhttp's Kotlin extension functions on File and Throwable 3, fastapi's _IncludedRouter 2, a serilog C# extension method 1, spring-modulith 1). As written the PR also removed the 8 Go recursion edges. Re-run on playframework after #2875 changed Play's resolver: 17 removed, the same less Java's two Multipart.* calls, which main now links to the Scala object itself. Index CPU on okhttp, median of 8 interleaved runs: 32.8 s on main, 32.3 s with this change. Tests: 2 of 4 fail on main; the PR as written fails the Go range-variable case. Original PR #1939 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danusha2345
pushed a commit
to danusha2345/codegraph
that referenced
this pull request
Oct 28, 2026
…mchenry#2972; 10 landed PRs yield to main; EXTRACTION_VERSION 56) Main landed ten more of our PRs as our own commits with the maintainer's additions (colbymchenry#2923 ← colbymchenry#1936, colbymchenry#2924 ← colbymchenry#1951, colbymchenry#2925 ← colbymchenry#1939, colbymchenry#2930 ← colbymchenry#2921, colbymchenry#2940 ← colbymchenry#1736, colbymchenry#2942 ← colbymchenry#1947, colbymchenry#2950 ← colbymchenry#1952, colbymchenry#2961 ← colbymchenry#1954, colbymchenry#2965 ← colbymchenry#1942, colbymchenry#2972 ← colbymchenry#1933) and the module-path half of colbymchenry#2308 (colbymchenry#2955). Where the integration carried our version, main's wins: the enclosing-scope member pick and its tests, the Go and Java receiver typers, the delegating-call self-loop rule, the duplicated-module copy confidence, the C #if collapse, the module-path resolution of Rust paths, the trail/path hardening in utils.ts. Kept beside main: colbymchenry#1875's Verilog hooks and status fields, colbymchenry#2839's implementations command, colbymchenry#1923, colbymchenry#2922, colbymchenry#2258, colbymchenry#1761, colbymchenry#1583, the bare-call half of colbymchenry#2308 (rust-use-bindings.ts). CHANGELOG is main's plus the open PRs' bullets. EXTRACTION_VERSION 56: the C #if group handling (colbymchenry#2940) and the kernel change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A method that hands its call on to another object's method of the same name, like a wrapper's
def acceptsURL(url) = proxied.acceptsURL(url), no longer links that call back to itself. Before, the wrapper looked recursive incallersand impact. Recursive calls throughthis/self, bare calls and calls in Go keep their link.Root cause
matchMethodCall's strategy 3 guesses a method by its name when it can't type the receiver. With only one method of that name in the project, the single-candidate branch took it outright. A delegating wrapper is often the only method of that name in the repository, because the method it delegates to belongs to a library, so the guess was the calling method itself. (The word-overlap branch already refuses the caller.)What came from the contributor, and what we changed
From @danusha2345 (#1939):
isForeignReceiverSelfLoopin the single-candidate branch (no edge when the one candidate is the caller and the receiver is notthis,self,Self,superorcls), the Scala delegating-wrapper test, and the recursion test (Javathis.depth()and baresize(), Go named receiver).Ours, on top:
sub.checkCommandGroups()overc.commandsandcmd.Traverse(…), etcd'schild.Repr(…),child.recoverAndclean()andtOp.isWrite()overop.thenOps, prometheus'ssubParameter.toLabels(). A Go method's own receiver and its struct fields are typed before strategy 3, so a Go call that still arrives there is on a local the resolver couldn't type, mostly a range variable over the type's own children. In every other language the rule is kept.Validation
Tests (
__tests__/resolution.test.ts, "Name-only method match never binds a delegating call to its own caller"): 2 of 4 fail on main (the Scala and TypeScript wrappers), 1 fails on the PR as written (Go range-variable recursion), all 4 pass here.A/B, main (3a93f57) vs this change, fresh indexes compared site by site, 24 repos in 11 languages (flask, click, fastapi, gin, cobra, etcd, prometheus, hugo, ripgrep, jq, Slim, serilog, eShopOnWeb, nowinandroid, okhttp, mihon, http4s, playframework, spring-modulith, express, zod, bulletproof-react, excalidraw, vben). Nothing is added. Every removed edge is a
callsself-loop that strategy 3 had guessed at 0.7:proxied.acceptsURL,underlying.withUrl, JDBCps.executeQuery, Seleniumelement.click(), Java calling a Scala object's same-named method), vben 9 (form.validate()and friends inFormApi), http4s 8 (pairs.drop(n)on a Vector,segments.splitAt), mihon 8 (ClassLoaderparent.loadClass,flow.collectAsState()), okhttp 5, ripgrep 3, flask 2 (jinjaloader.list_templates()), spring-modulith 2. Two of playframework's were sites that also had the right edge, which they keep.parentFile.isDescendentOf(…)andcause?.isNetworkUnusable()(3), fastapi'schild_branch.effective_low_priority_routes()on a just-built_IncludedRouter(2), a serilog C# extension method (1), spring-modulith'sit.containsTypeInAnyParent(…)(1). Nothing cheap tells them apart from the delegations: mihon's extension functionsStateFlow.collectAsLazyPagingItems()andPreference.collectAsState()are delegations with the same shape. This is the trade-off the PR describes, measured.Multipart.multipartParser/Multipart.randomBoundarycalls, which main itself now links to the ScalaMultipartobject (0.9). Nothing added.matchByExactNameandfindBestMatch) and fix(scala): resolve Outer.Inner(...) to the Scala type, never another language's constructor #1951 (ScalaOuter.Inner(...)inmatchMethodCall, just before strategy 3, plus a fall-through when a typed Scala match lands on another language's constructor). playframework indexed once more, main (b6379e6) vs this change on it: the same 17 self-loops removed, nothing else, and no other strategy-3 self-loop left (instance-method 0.7 self-loops 17 → 0). All 249 tests ofresolution.test.tspass, fix(scala): resolve Outer.Inner(...) to the Scala type, never another language's constructor #1951's Scala cases and the Scala wrapper case among them.Cost: okhttp index process CPU time, 8 interleaved rounds with the order alternated (other agents' jobs were running): median 32.8 s on main, 32.3 s with this change. The change adds one id comparison to a branch that already returns, so the difference is noise.
Suite on this commit (
CODEGRAPH_KERNEL_EXPECT=1 npx vitest run --maxWorkers=4 --minWorkers=1, Windows): 760 files passed, 6 skipped; 9,041 tests passed, 83 skipped, 0 failed.Lands #1939 by @danusha2345.
🤖 Generated with Claude Code