Skip to content

Rust: a receiver with more than one field access is dropped from the call reference (self.a.b.m() recorded as m) #2836

Closed

Description

@jocax

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;

pub struct Services { pub store: Box<dyn Store> }
pub struct Svc { pub services: Services, pub store: Box<dyn Store> }

impl Svc {
    pub fn a(&self) { self.services.store.put("a"); }
    pub fn b(&self) { let _ = self.services.store.put("b"); }
    pub fn c(&self) {
        self
            .services
            .store
            .put("c");
    }
    pub fn d(&self) { self.store.put("d"); }
    pub fn e(&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()).

Related: #2614 (std/external receivers linking to same-named project methods), #750 (chained factory/singleton resolution).

Activity

  1. colbymchenry commented on Oct 23, 2026

    @colbymchenry
    Owner

    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.

  2. added a commit that references this issue on Oct 23, 2026
    9c4794d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions