You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Rust: a receiver with more than one field access is dropped from the call reference (self.a.b.m() recorded as m) #2836
A Rust method call whose receiver has more than one field access is recorded with the bare method name: self.services.store.put(…) becomes put. The receiver is lost before resolution, so the call stays unresolved or is matched by name only, and refName on a resolved edge carries no receiver either. One field access keeps it.
Repro
1.6.2, a Cargo workspace with api (trait Store { fn put(&self, key: &str); }) and app depending on it:
use api::Store;pubstructServices{pubstore:Box<dynStore>}pubstructSvc{pubservices:Services,pubstore:Box<dynStore>}implSvc{pubfna(&self){self.services.store.put("a");}pubfnb(&self){let _ = self.services.store.put("b");}pubfnc(&self){self.services.store.put("c");}pubfnd(&self){self.store.put("d");}pubfne(&self){self.store.put("e");}}
unresolved_refs after codegraph init:
call in
reference_name
a — two fields, one line
put
b — two fields, let _ =
put
c — two fields, split by rustfmt
put
d — one field, one line
self.store.put
e — one field, split
self.store.put
A call inside the chain drops it the same way: self.store().put("m") is recorded as put.
Why it matters
This is the shape of most service-layer calls (self.services.storage.update(…)), and rustfmt splits it over several lines, which made it look like a line-break problem at first. Without the receiver:
receiver-type inference has nothing to start from, so the call is unresolved or name-matched;
name-matched edges get the same low confidence (0.4, exact-match) as iterator calls such as v.into_iter().next() landing on a project method named next, so confidence cannot separate a real call from a false one;
tools reading metadata.refName cannot tell which field the call goes through.
Expected
reference_name / refName keep the full receiver path, e.g. self.services.store.put, as they do for self.store.put. For a call in the chain, at least the path up to the call (self.store()).
Thanks @jocax for the precise report and the workspace repro!
On main, #2831 had already made self.services.store.put(…) link to Store::put by reading the receiver where the call is written. But both extractors still recorded the call as a bare put, and self.store().put("m") stayed unresolved.
#2846 is now merged. It keeps the whole receiver chain in the recorded name: self.services.store.put, including when rustfmt splits it over lines, and self.store().put for a call inside the chain. It also links a call on what a method returns by that method's declared return type, so self.store().put("m") now reaches Store::put.
Receivers written through ?, .await or an index still keep the bare name for now. It ships in the next release.
Activity
colbymchenry commentedon Oct 23, 2026
Thanks @jocax for the precise report and the workspace repro!
On
main, #2831 had already madeself.services.store.put(…)link toStore::putby reading the receiver where the call is written. But both extractors still recorded the call as a bareput, andself.store().put("m")stayed unresolved.#2846 is now merged. It keeps the whole receiver chain in the recorded name:
self.services.store.put, including when rustfmt splits it over lines, andself.store().putfor a call inside the chain. It also links a call on what a method returns by that method's declared return type, soself.store().put("m")now reachesStore::put.Receivers written through
?,.awaitor an index still keep the bare name for now. It ships in the next release.fix(rust): a call keeps its whole receiver chain, and links through m…