Repository navigation
fix(rust): a call on what as_mut() hands back reaches the receiver's declared type - #2710
Merged
Merged
Conversation
…declared type A Rust call written x.as_mut().m(..) or x.as_ref().m(..) reaches the resolver by its bare name, so it was matched by name alone. tokio's self.delay.as_mut().reset(now) on a Pin<Box<Sleep>> field linked to the calling Interval::reset, its this.timer.as_mut().as_pin_mut() on an Option to a stream's as_pin_mut, reader.as_mut().poll_read(..) on a generic R to BufReader::poll_read, and dioxus's self.child.as_mut().map(..) and self.server.as_mut().filter(..) on Options to its own GenerationalRefMut::map and Store::filter. Such a call is now read from the call's own chain and resolved on the receiver's declared type as as_mut passes it on: a Pin<P> re-pins what it points at (Rust's lookup through the Pin from the previous change), a Box, NonNull, Rc or Arc lends what it holds, an Option stays an Option, whose methods are std's, unless ? takes the value back out (self.client.as_mut()?.next()), and a type parameter or another std or dependency type gives no project method. A field is read from its struct's declaration, also through pin-project's let me = self.project() (a #[pin] field projects to Pin<&mut T>), even when a pin_project! macro wraps the struct; a local or parameter from its own declaration, which now also reads let x: T = .. annotations. A receiver whose type isn't written where it can be read, a pin! local or a call's result, is left to the name strategies as before. A/B on tokio, axum, clap, serde, ripgrep, gleam and dioxus: 33 wrong links removed, 7 moved to the right method, 13 right links added, none lost, and 28 same-target links now resolved from the declared type instead of by name. axum, clap, serde and the controls express, gin, flask and leveldb are byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 20, 2026
Merged
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.
Follow-up to #2701: Rust calls chained on
.as_mut()/.as_ref().What was wrong
A call written
x.as_mut().m(…)reaches the resolver by its bare name (m), so it was matched by name alone — #2701'sPinlookup never saw it. On the A/B repos that linked, among others:self.delay.as_mut().reset(now)×3delay: Pin<Box<Sleep>>Interval::resetthis.timer.as_mut().as_pin_mut()×4Option<…>LinesStream::as_pin_mutreader.as_mut().poll_read(…),f.as_mut().poll(cx)×6Pin<&mut R>, a genericFBufReader::poll_read,BlockingTask::pollentry.as_ref().might_be_registered()×2NonNull<TimerShared>StateCell::might_be_registeredself.child.as_mut().map(…),self.server.as_mut().filter(…),x.as_ref().context(…)×12Option<…>GenerationalRefMut::map,Store::filter,DiffState::context, …The change (
src/resolution/name-matcher.ts)matchRustAsMutChainCall, an exclusive hook beside the C++ chain hooks inmatchReferenceInner, reads the chain from the call's own column (self.delay.as_mut().reset(, with an optional?afteras_mut()) and resolves the method on whatas_mut/as_refhands back for the receiver's declared type (rustLentCall):Pin<P>re-pins what it points at → fix(rust): a call through a Pin, Rc or Box self reaches the method Rust picks, inherent first #2701's lookup throughPin(rustPinnedCallOn);Box,NonNull,Rc,ArcorCowlends what it holds → that type's method (rustCallOnType);Option/Resultstays one — std's methods, no edge — unless?takes the value back out (self.client.as_mut()?.next());as_mut(return type not read here), or a receiver whose type isn't written where it can be read (apin!local, a call's result) → undefined, the name strategies decide as before.The receiver's type comes from:
self(throughself: Pin<&mut Self>,self.as_mut()is that samePin); a field's declaration in its struct (rustFieldDeclaration), also when apin_project!macro wraps the struct and through pin-project'slet me = self.project(), where a#[pin]field isPin<&mut T>; a local's or parameter's declaration (rustDeclaredTypeText, which now also readslet x: T = …— #2701's reader stopped at theletand missed the annotation). A cheap line check (.as_mut()/.as_ref()on the call's line) keeps the hook off every other bare call.Validation
Fresh full indexes,
mainf2f5dee vs this branch, keyed by call site (re-run after rebasing onto 90e2456, which brought #2707's Rust extractor change: the same changed sites, site for site; the branch now sits on a31bf2f, whose one newer commit is C-only):Every changed site was read:
Option/Resultmethods (map,filter,cloned,is_none_or,as_pin_mut, anyhow'scontext) guessed onto project types; generic readers, writers, sinks and futures (R,W,S,F);Pin::get_ref→AsyncFd::get_ref; an externalStream/dyn Future/web_sys::Nodemethod; ripgrep'spath.as_ref().metadata()and gleam'spath.as_ref().replace(…)onP: AsRef<Path>/ a string.Sleep::reset×3 (was the callingInterval::reset),TimerShared::might_be_registered×2 (wasStateCell's),Fuse::poll_next(was the callingPeekable::poll_next), dioxusHasAnimationData::as_anythrough aBox<dyn HasAnimationData>(wasSerializedAnimationData::as_any).MaybeDone::poll×3,Root::poll×3,Notified::poll,MaybeDangling::poll,Fuse::poll_next),NonNull<TimerShared>calls ×3,Sleep::reset.Not in this PR: an earlier draft also made the shared Rust/Go call-shape reader stop treating a receiver that merely starts with the method's name (
builder().build(),abort_handle…abort()) as a bare call. That fixed a few calls but exposed others to the name guess (dioxus'sbuilder().build()→BuildRequest::build), so it was dropped; theas_muthook reads its chain itself instead.Cost: none measurable. The hook's own time, from the resolver's stage profiler (
CODEGRAPH_RESOLVE_PROFILE=2) summed over every worker: 180 ms on tokio (8,014 bare calls checked), 61 ms on dioxus (12,609), against 31–36 s of CPU per full index; a call without.as_mut()/.as_ref()on its line costs one regex test. Interleaved full indexes (CPU, median of 3) moved within this machine's 2–4 s run-to-run spread: tokio 31.2 s → 32.1 s, dioxus 34.2 s → 36.1 s.Tests
__tests__/rust-as-mut-chains.test.ts(7 cases overPin<Box<T>>,Option<T>,Box<T>andNonNull<T>fields,Optionthrough?, pin-project fields pinned and not,Pin<&mut R>generic and named,self.as_mut()throughself: Pin<&mut Self>withPin::get_mut): 5 fail onmain, the other 2 guard behavior main already has. With #2701's and the other Rust suites: 39/39. Full suite (CODEGRAPH_KERNEL_EXPECT=1, kernel built, 4 workers): 7619 passed, 0 failed, 80 skipped.Overlap
The peer session working on Rust receivers typed by their declaration (branch
claude/rust-receiver-types) handlesident.methodrefs only and confirmed it doesn't see these bare chained calls; the two touch different functions.🤖 Generated with Claude Code