Repository navigation
Description
Environment
codegraph 1.6.2 from npm, and current main (f72c9d2) built from source and run on Node 22.23.4. Linux, rustc 1.99.1, fresh codegraph init. The repro crate passes cargo check.
Problem
This is the rest of #2614. Thanks for the fixes there: all four shapes from its repro crate are fine on current main. But some other shapes still link a method call to a project method that only has the same name. In shapes 2–5 the receiver is not a project type at all; in shape 1 the path names a project type that has no hand-written default. In our crate (42 files) this gives 14 false call edges on current main (15 on 1.6.2).
Rules::default()whereRuleshas#[derive(Default)]goes toTokens::default, the only hand-writtenimpl Defaultin the crate. The type is written right in the path.ext::Builder::new().enable().build()(a builder from another crate) goes toGraph::build, which is an associated fn withoutself.std::fs::OpenOptions::new()….write(true)written on several lines inside a closure goes toLog::writefromimpl Write for Log. On one line outside a closure it is fine.ext::GzBuilder::new().mtime(0).write(data, 6)and thenenc.finish()?.flush()?go toLog::writeandLog::flush.let mut stdout = ext::stdout(); stdout.flush()goes toLog::flush.
Minimal repro
Two crates side by side; only app is indexed (codegraph init app), so ext stays outside the project.
ext/Cargo.toml:
[package]
name = "ext"
version = "0.1.0"
edition = "2021"ext/src/lib.rs:
pub struct Builder {
on: bool,
}
impl Builder {
pub fn new() -> Self {
Builder { on: false }
}
pub fn enable(mut self) -> Self {
self.on = true;
self
}
pub fn build(self) -> bool {
self.on
}
}
pub struct GzBuilder {
m: u32,
}
impl GzBuilder {
pub fn new() -> Self {
GzBuilder { m: 0 }
}
pub fn mtime(mut self, m: u32) -> Self {
self.m = m;
self
}
pub fn write(self, sink: Vec<u8>, level: u32) -> Encoder {
Encoder { sink, level: level + self.m }
}
}
pub struct Encoder {
sink: Vec<u8>,
level: u32,
}
impl Encoder {
pub fn finish(self) -> Result<Sink, ()> {
Ok(Sink { data: self.sink, level: self.level })
}
}
pub struct Sink {
data: Vec<u8>,
level: u32,
}
impl Sink {
pub fn flush(&mut self) -> Result<(), ()> {
self.data.clear();
Ok(())
}
}
pub fn stdout() -> Sink {
Sink { data: vec![], level: 0 }
}app/Cargo.toml:
[package]
name = "repro_receiver"
version = "0.1.0"
edition = "2021"
[dependencies]
ext = { path = "../ext" }app/src/main.rs:
use std::io::Write;
// 1. derive(Default) type called through its own path
#[derive(Default)]
pub struct Rules {
n: u32,
}
pub struct Tokens {
n: u32,
}
impl Default for Tokens {
fn default() -> Self {
Tokens { n: 1 }
}
}
fn rules() -> Rules {
Rules::default()
}
// 2. builder chain from an outside crate; the project has an associated fn `build` without self
pub struct Graph {
n: u32,
}
impl Graph {
pub fn build(n: u32) -> Graph {
Graph { n }
}
}
fn runtime() -> bool {
ext::Builder::new().enable().build()
}
// the project implements io::Write for its own type
pub struct Log {
buf: Vec<u8>,
}
impl Write for Log {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
self.buf.extend_from_slice(b);
Ok(b.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
// control: std `OpenOptions::write` on one line and std `flush` — no false edge
fn open(p: &str) -> std::io::Result<std::fs::File> {
std::fs::OpenOptions::new().write(true).create(true).open(p)
}
fn out() -> std::io::Result<()> {
std::io::stdout().flush()
}
// 3. multi-line std builder chain inside a closure (`.write(true)` is OpenOptions::write)
fn take_lock(p: &str) -> std::io::Result<std::fs::File> {
let open = move || -> std::io::Result<std::fs::File> {
std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.open(p)
};
open()
}
// 4. builder method `write` of an outside crate and `flush` on what its chain returns
fn write_single(data: Vec<u8>) -> Result<(), ()> {
let enc = ext::GzBuilder::new().mtime(0).write(data, 6);
enc.finish()?.flush()?;
Ok(())
}
// 5. local of an outside type, then `flush`
fn to_stdout() -> Result<(), ()> {
let mut stdout = ext::stdout();
stdout.flush()
}
fn main() {
let r = rules();
let t = Tokens::default();
let g = Graph::build(r.n + t.n);
let mut l = Log { buf: vec![] };
let _ = l.write(b"x");
let _ = l.flush();
let _ = (runtime(), open("/dev/null"), out(), g.n, take_lock("/dev/null").is_ok(), write_single(vec![]), to_stdout());
}Actual
Call edges in app/.codegraph/codegraph.db on current main (1.6.2 gives the same edges):
main -> Graph::build resolvedBy qualified-name, confidence 0.95, refName Graph::build (ok)
main -> Log::flush resolvedBy instance-method, confidence 0.9, refName l.flush (ok)
main -> Log::write resolvedBy instance-method, confidence 0.9, refName l.write (ok)
main -> Tokens::default resolvedBy qualified-name, confidence 0.95, refName Tokens::default (ok)
rules -> Tokens::default resolvedBy instance-method, confidence 0.7, refName Rules::default (wrong)
runtime -> Graph::build resolvedBy exact-match, confidence 0.9, refName build (wrong)
take_lock -> Log::write resolvedBy exact-match, confidence 0.9, refName write (wrong)
to_stdout -> Log::flush resolvedBy instance-method, confidence 0.7, refName stdout.flush (wrong)
write_single -> Log::flush resolvedBy exact-match, confidence 0.9, refName flush (wrong)
write_single -> Log::write resolvedBy exact-match, confidence 0.9, refName write (wrong)
open (OpenOptions on one line) and out (std::io::stdout().flush()) get no false edge.
Expected
- rules: no project edge. The derived
defaultis not in the source, and the path namesRules, neverTokens. - runtime, take_lock, write_single, to_stdout: no project edge. The receivers are
ext::Builder,OpenOptions,ext::GzBuilderandext::Sink— all outside the project. Unresolved is fine.
Notes
Shapes 2–5 lose the receiver in a chain or in a local of an outside type and then fall back to the bare name (exact-match on build, write, flush). Graph::build has no self, so a method call x.build() can never reach it — maybe a small check before the name fallback would help. Shape 1 is close to what #3203 (open) does for a path that names a type.
Related, I checked before posting: #2614 (closed, other shapes), #3203 (open PR), #2697 and #2715 (closed).
Activity