Repository navigation
fix(resolution): a call on the caller's own class reaches that class's member, or what it inherits - #2950
Merged
Conversation
…s member, or what it inherits Root cause: a bare call in Java or Scala, and a `self.`/`this.` call in Python, TypeScript and JavaScript, reaches matchByExactName as a bare name. With several same-named methods in reach, findBestMatch ranked them by file and line distance, so a sibling class's method declared nearer the call won over the caller's own class's or the one it inherits; above the 500 candidate ceiling the call was dropped. Fix (contributor): matchEnclosingScopeMember runs before the ranking. It reads the receiver back from the call line (bare, self/this, super) and walks the caller's scopes innermost-out: the own class, then what it inherits, then, for a bare call, the classes it is nested in. A member found there is the answer (0.9), and its overloads are ranked among themselves. A super call in a JS class expression is left to js-super-calls (PR 2497). Ours: - Inherited members first come from the declaration's own resolved supertypes (getSupertypeNodes), in the order its header lists them, a class further up before an interface that only declares the member; the PR's walk by name stays as the fallback when those edges reach nothing (an `extends` the index resolved to a namesake, as Minestom's Player to a nested EntityStatuses.LivingEntity). By name alone, Django's spatialite, oracle and mysql GIS editors reached the postgresql backend's DatabaseSchemaEditor.execute and _delete_index_sql (0.9), a class with two bases took either one's member (BaseDeleteView(DeletionMixin, FormMixin) got FormMixin.get_success_url), and SystemUI's RotationContextButton got RotationButton's declaration instead of ButtonDispatcher.getCurrentView. - Only `super`, `this`/`self` and Python's `cls` name the receiver. The PR also read `parent.`/`base.` as super and `cls.`/`static.` as self, but Java's extractor drops a receiver variable of those names too: protobuf's `parent.getFullName()` on an EnumDescriptor went to GenericDescriptor. - C++ is left to matchCppImplicitThisCall (PR 2479), which already looks members up in the class, its bases and its enclosing classes. The PR's C++ branch added the caller's own file's namespace functions, which beat the overload the arguments fit: fmt's 3-argument `copy<Char>(b, e, out)` calls went to format.h's 2-argument copy (0.9). - Tests for each, and the CHANGELOG bullet in the house format. Validation: enclosing-class-member-call.test.ts has 12 cases: 6 fail against main's resolver, and all 5 of ours fail against the PR as written. Kernel A/B against main 7cceba5 on 15 repos (leveldb, fmt, gin and serilog byte-identical). Every changed site was judged by a hierarchy oracle (own class, then bases depth-first in header order, classes before interfaces, enclosing classes for a bare call) or by hand. About 7,400 links moved from a wrong method to the right one (protobuf 3,208, vscode base+editor 1,383, Play 806, django 766, netbox 316, SystemUI 314, spring-ai 312), and about 9,100 right links rose from 0.7 to 0.9. One site got worse (http4s `object RespPrelude { case class RespPrelude }`), and no C++ site changed. CPU, median of 3 interleaved runs: kestra 154.9 s on main vs 152.8 s, vscode base+editor 79.9 s vs 82.6 s (within the rounds' spread). On a vendored-theme stand-in of 1,500 classes, the walk links the 4,500 calls main drops at the 500-candidate ceiling, for +2.2 s (JS) and +1.8 s (Python). Full suite on 3df1cc64 (main 31ec908): 768 files passed, 6 skipped; 9,160 tests passed, 0 failed, 85 skipped. Original PR #1952 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
pushed a commit
that referenced
this pull request
Oct 28, 2026
…rary type gets no guess
Java typed a call receiver from a local declaration or a plain field of
the enclosing class only. A field of an outer class read inside an inner
or anonymous class (mHandler.post(...) inside new Runnable() {...}),
Outer.this.f, a chain of fields (this.mOwner.mRepo.save()) and a local
whose type has type arguments fell through to the name-only strategies,
and so did every receiver whose declared type is a library class:
parcel.readInt() on an android.os.Parcel went to a project
VersionedParcel.readInt, list.size() to any project size().
The declared type is now read for each of those shapes, a chain one hop
at a time with each hop's own imports, keeping a written package or
outer class (Map.Entry, play.api.mvc.BodyParser). A receiver whose
declared type the project does not declare, or that an import or a
written package pins outside the project, gets no edge. When no import
decides between same-named types, a Java call prefers the Java
declaration over a Scala or Kotlin one.
Also, from review: a field the class around the call declares is read
as written before the member reader, which keeps only the last name
(PauseTest.Suite, a field written with its package); a local or loop
variable keeps the package its declaration writes; a field typed by a
type parameter goes to its bound, and a class the class head names as
a type argument (Comparable<ApplicationModule>) is not taken for one; a
nested type a superclass declares, or one an on-demand import of its
class names (import play.mvc.Http.*), counts as named; and a typed miss
on a type no project class or interface is named (a URI, an enum's
name()) gets no guess, as on main.
Validation:
- java-receiver-type.test.ts: 19 tests, the PR's 5 red on main; the
other 14 pass on main, and our 8 each fail without their change.
- A/B against main 2577601 (with #2942 and #2950; the same sites
change against 7cceba5), sites added/removed/retargeted: jextract
+2/-1/106, spring-modulith +1/-3/2, Minestom +52/-91/71, SystemUI
+53/-319/96, spring-ai +69/-659/532, kestra-java +5/-504/162,
playframework +0/-196/599; click and gin identical. Of the removed
edges main had at 0.9, 1,004 were wrong (an import oracle plus 16
read by hand) and 4 right (a lambda parameter, two nested types of a
Scala object, a field read under a static import of a same-named
member). Retargets land on the declared type or the supertype
declaring the method, except about 16 build()/clone() calls on
spring-ai's nested option builders, which move from one wrong target
to another.
- Cost (3 interleaved rounds): spring-ai wall 27.9 s on main vs
25.9 s, CPU 90.2 s vs 88.1 s. SystemUI read 71.3 vs 79.4 s wall
under other jobs' load; CPU profiles of both arms put the new code at
about 0.6 s of 115 s.
Original PR #1942 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
Lands #1952 by @danusha2345. These calls now link to the method of the class they're written in, or to the one that class inherits:
selforthis.Before, they could link to a same-named method of another class that is declared closer to the call. In Java and Scala, a call inside a nested class also reaches the enclosing class's method, and a
supercall in Java or Python reaches the parent's method.Root cause
The extractor records
render(),this.render(),self.render()andsuper.render()all under the bare namerender, so they reachmatchByExactName.findBestMatchranks them by file and line distance, so a sibling class'srenderdeclared next to the call beat the caller's own class's, or the one it inherits.From the contributor, and what we changed
matchEnclosingScopeMember, which runs before the ranking.self/this,super) and walks the caller's scopes innermost-out: the own class, then what it inherits, then, for a bare call, the enclosing classes.supercall in a JS class expression to js-super-calls (JavaScript: aliased constructor receivers and overridden super calls remain unresolved #2497).__tests__/enclosing-class-member-call.test.ts.extends/implementsedges (getSupertypeNodes), in the order its header names them, and takes a class further up over an interface that only declares the member.extendsthe index resolved to a namesake: Minestom'sPlayer extends LivingEntitypoints at a nestedEntityStatuses.LivingEntity, and Play's doc controllers point at a doc-localBaseController.DatabaseSchemaEditor.executeand_delete_index_sqlat 0.9. Django has oneDatabaseSchemaEditorper backend, and each of these editors inherits both methods fromBaseDatabaseSchemaEditorthrough its own backend's class.BaseDeleteView(DeletionMixin, FormMixin)gotFormMixin.get_success_url, andAdminPasswordChangeForm(SetUnusablePasswordMixin, SetPasswordMixin, …)gotSetPasswordMixin's methods.RotationContextButton extends ContextualButton implements RotationButtongotRotationButton's declaration ofgetCurrentViewinstead ofButtonDispatcher's implementation.super,this/selfand Python'sclsname the receiver. The PR also readparent./base.assuperandcls./static./Self./$thisasself. Java's extractor drops a receiver variable of those names too, so protobuf'sparent.getFullName()on anEnumDescriptorwent toGenericDescriptorat 0.9.matchCppImplicitThisCall(fix(cpp): a call a method makes with no receiver links to its class's own or inherited method #2479). That function already finds members in the class, its bases and its enclosing classes. The PR's C++ branch added the caller's own file's free functions at namespace scope, and those beat the overload the arguments fit: fmt's 3-argumentcopy<Char>(begin, end, out)calls went to format.h's 2-argumentcopy(string_view, out)at 0.9. It also turned constructor calls intoinstantiatesedges. Some of its namespace picks were right, so C++ namespace lookup with overload fitting would make a good follow-up.Our part of the change is 86 of the 290 lines of the new walk and its helpers, about 30%. We replaced 15 of the PR's lines, all of them C++ handling, the receiver words, the base order and comments.
Validation
enclosing-class-member-call.test.tshas 12 cases. Against main's resolver (31ec908), 6 of them fail: the PR's Scala, Pythonself., TypeScriptthis., Javasuper.and Java nestedBuildercases, plus the first-base case.parentvariable, and C++ namespace overload.js-class-expressions,js-super-callsandsuper-call-self-edgepass unchanged. The current head doesn't touch thejs-class-expressionsexpectations, so there is no 0.4 → 0.9 change to justify.CODEGRAPH_KERNEL_EXPECT=1: 768 files passed, 6 skipped; 9,160 tests passed, 0 failed, 85 skipped. Rebased onto 6162d87, which changes onlysrc/sync/watcher.ts(fix(watcher): a deleted link target no longer pins a CPU core on Windows #2938). The patch-id is unchanged, and the PR's test passes there.containsandextends/implementsedges: the caller's class, then its bases depth-first in header order, classes before interfaces, then the enclosing classes for a bare call.cls.calls, rightcls.right, 4 lookups through a class defined in a test method, plausiblebase+editorAction, rightsuper.calls)src(AOSP)About 7,400 links now go to the right method, and about 9,100 right links went from 0.7 to 0.9.
super.calls, which main left unresolved: SystemUI 299, kestra 59, spring-ai 51, protobuf 48, Play 35.self.settings()reachingSimpleTestCase.settingsinstead of a mail handler's;this._register()/this.dispose()reachingDisposable's;self()reachingDefaultChatOptionsBuilder.self;object RespPrelude { case class RespPrelude … RespPrelude(…) }now reaches the object rather than the case class. A bare name equal to its scope is read as a constructor.Against the PR as written (on 0174f80), the branch keeps every Java, Scala, Python and TypeScript improvement. It also fixes:
parent.sites.The PR's C++ changes now stay as main has them. That is fmt's 33 retargets and 5
calls→instantiatessites, including the wrongcopy/writeoverloads, and protobuf's 101 C++ retargets and 39calls→instantiatessites. Leaving C++ out also gives up that branch's right picks: fmt's 6 calls through a local namedself, and protobuf pyext's namespace-scoped_GetDescriptorcalls.The name fallback keeps the PR's right answers where the index resolved an
extendsto a namesake (Minestom's 15super.calls, Play's 26 docActioncalls). Both of thoseextendsresolutions are bugs of their own, worth a separate fix.base+editor(TypeScript)The walk runs before the 500-candidate ceiling, so a call on a ubiquitous name pays one pass over its candidates. The stress fixtures are a stand-in for a vendored theme like Metronic (#999): 1,500 widget classes that share
init/update/render/destroyand call each other throughthis./self./a bare call. There, the walk links the 4,500 calls main drops at the ceiling, all to the caller's own class.Lands #1952 by @danusha2345.
🤖 Generated with Claude Code