Repository navigation
Rust: method call on std/external or struct-literal receiver links to a same-named project method #2614
Description
Environment
codegraph 1.6.2 from npm, Linux, rustc 1.99.0, fresh codegraph init. The repro crate passes cargo check.
Problem
In Rust, a method call whose receiver is not a project type still gets linked to a project method that just has the same name. I see four shapes of it:
names.contains(&k)wherenames: &[&str](slice) goes toObj::contains.vis.map.get("a")wheremap: HashMap<String, i32>(std container in a struct field) goes toExt::get, andExt::getis even an associated fn withoutself.J::from(i)whereuse serde_json::Value as Jgoes toFail::from, which is just a localimpl From<io::Error> for Fail.Xyz { x: 1.0 }.to_val()(method on a struct literal) goes toStat::to_valinstead ofXyz::to_val. Both implement the same trait.
In our crate (12 files) this gave 7 false call edges like these. I think #1585 was about exactly case 2 ("receiver's type is not a local one at all (a std container ...)"), it is closed but still happens in 1.6.2.
Minimal repro
Cargo.toml:
[package]
name = "repro"
version = "0.1.0"
edition = "2021"
[dependencies]
serde_json = "1"src/main.rs:
use std::collections::HashMap;
use serde_json::Value as J;
pub enum Val {
Null,
Int(i64),
List(Vec<Val>),
}
pub struct Obj {
keys: Vec<String>,
}
impl Obj {
pub fn contains(&self, k: &str) -> bool {
self.keys.iter().any(|x| x == k)
}
}
pub struct Ext;
impl Ext {
pub fn get(name: &str) -> Ext {
let _ = name;
Ext
}
}
pub struct Fail;
impl From<std::io::Error> for Fail {
fn from(_: std::io::Error) -> Fail {
Fail
}
}
pub struct Xyz {
x: f64,
}
pub struct Stat {
n: f64,
}
pub trait ToVal {
fn to_val(&self) -> Val;
}
impl ToVal for Xyz {
fn to_val(&self) -> Val {
Val::Int(self.x as i64)
}
}
impl ToVal for Stat {
fn to_val(&self) -> Val {
Val::Int(self.n as i64)
}
}
// 1. slice::contains, not Obj::contains
fn slice_has(names: &[&str], k: &str) -> bool {
names.contains(&k)
}
// 2. HashMap::get on a struct field, not Ext::get
pub struct Vis {
map: HashMap<String, i32>,
}
fn map_get(vis: &Vis) -> Option<&i32> {
vis.map.get("a")
}
// 3. serde_json::Value::from via alias J, not Fail::from
fn to_json(i: i64) -> J {
J::from(i)
}
// 4. method on struct literal: Xyz::to_val, not Stat::to_val
fn centroid() -> Val {
Xyz { x: 1.0 }.to_val()
}
fn main() {
let o = Obj { keys: vec![] };
let _ = o.contains("a");
let _ = Ext::get("x");
let _ = Stat { n: 0.0 }.to_val();
let _ = slice_has(&["a"], "a");
let _ = map_get(&Vis { map: HashMap::new() });
let _ = to_json(1);
let _ = centroid();
let _ = Val::List(vec![Val::Null]);
}Actual
Call edges in .codegraph/codegraph.db (enum variant edges skipped):
slice_has -> Obj::contains resolvedBy instance-method, confidence 0.7, refName names.contains (wrong)
map_get -> Ext::get resolvedBy exact-match, confidence 0.9, refName get (wrong)
to_json -> Fail::from resolvedBy instance-method, confidence 0.7, refName J::from (wrong)
centroid -> Stat::to_val resolvedBy exact-match, confidence 0.4, refName to_val (wrong, should be Xyz::to_val)
main -> Obj::contains resolvedBy instance-method, confidence 0.7, refName o.contains (ok)
main -> Ext::get resolvedBy qualified-name, confidence 0.95, refName Ext::get (ok)
And codegraph callers contains lists slice_has as a caller of Obj::contains.
Expected
- slice_has, map_get, to_json: no edge to a project symbol. The receiver type is a slice, a HashMap and serde_json::Value, all outside the project. Unresolved is fine.
- centroid -> Xyz::to_val. The type is written right in the struct literal.
Notes
Case 2 loses the receiver like #1707 (TS chained receiver) and then goes to exact-match by bare name. Case 4 is the same kind of thing as #1861 (pick by name instead of by the known type).
Maybe some small checks before the name fallback would help:
receiver is a struct literal `T { .. }` -> type T, look up T::method only
receiver is a param/field with type from std/ext -> no project edge
path head is a `use ... as X` alias -> resolve through the alias, external -> no project edge
Activity
fix(rust): a method call resolves by the type its receiver is written…
fix(rust): a method call resolves by the type its receiver is written…
fix(rust): a method call resolves by the type its receiver is written…
fix(rust): a method call resolves by the type its receiver is written…
fix(rust): a method call resolves by the type its receiver is written…
fix(rust): a method call resolves by the type its receiver is written…
colbymchenry commentedon Oct 23, 2026
Thanks for the detailed report and the ready-made repro crate!
Case 1 (
names.containson a slice) was already fixed onmainby the typed-receiver work. #2831, now merged, fixes the other three:vis.map.get("a")on aHashMapfield no longer links to a project method.J::from(i)throughuse serde_json::Value as Jno longer links to a project method either.Xyz { x: 1.0 }.to_val()now links toXyz::to_val.On real projects such as tokio, ripgrep and rust-analyzer it removed over a thousand wrong links and retargeted several hundred to the right method. It ships in the next release; please re-index Rust projects after upgrading.
fix(rust): calls on field chains, struct literals and outside-type pa…