A Rust call through a chained associated function — `Foo::new().bar()`, `Foo::with(cfg).build()` — dropped the receiver to a bare method name, which then attached to a same-named method on an unrelated type (a wrong edge) or didn't resolve. Ports the #645/#608 mechanism for Rust's `::` receivers: - Part 1: capture Rust return types; `-> Self` yields the `self` marker (resolved to the impl's own type, like PHP), references/generics are unwrapped/reduced. - Part 2: encode an associated-function chain (`Foo::new().bar`), gated to a scoped_identifier receiver so instance chains (`x.foo().bar()`) keep bare-name. - Part 3: resolve via matchScopedCallChain (PHP's `::` resolver, generalized), validated by resolveMethodOnType. Wire Rust into the conformance second pass (matchScopedCallChain variant) so a chained method provided by a trait the type implements (`impl Trait for Type` → existing implements edges) resolves too. Validated: synthetic decoy + args + Self + trait-default-conformance + absent safety tests; full suite green (lone failure is the known-flaky #662 daemon test, passes in isolation). Real-repo A/B vs main: clap (329 .rs) a net precision win — **+937 added (96% correct builder methods), 622 wrong->right retargets** (`Command::new().arg()` was mis-resolving to `ArgGroup::arg`, now `Command::arg`), +162 net unique edges; the pure-drops are largely wrong bare-name edges the fix correctly stops emitting. tokio-rs/bytes 0/0 (no regression). Known limit: the single-hop mechanism re-encodes only the first hop of a chain (deeper hops keep bare-name) — clap's unusually deep builder chains are partly covered. EXTRACTION_VERSION 10 -> 11. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7c7f0dd56f
commit
5805f01957
@@ -2534,4 +2534,87 @@ class Caller {
|
||||
expect(callerNamesOf('Other::onlyOther')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rust chained associated-function call resolution (#645/#608 mechanism)', () => {
|
||||
function callerNamesOf(qualifiedName: string): string[] {
|
||||
const target = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName);
|
||||
if (!target) return [];
|
||||
const names = cg
|
||||
.getIncomingEdges(target.id)
|
||||
.filter((e) => e.kind === 'calls')
|
||||
.map((e) => cg.getNode(e.source)?.name)
|
||||
.filter((n): n is string => !!n);
|
||||
return [...new Set(names)].sort();
|
||||
}
|
||||
|
||||
it('resolves Foo::new().bar() (and a Self return) via the associated fn, never a same-named decoy', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'main.rs'),
|
||||
`struct Aaa { _x: i32 }
|
||||
impl Aaa { fn bar(&self) {} }
|
||||
struct Foo { _x: i32 }
|
||||
impl Foo {
|
||||
fn new() -> Foo { Foo { _x: 0 } }
|
||||
fn make() -> Self { Foo { _x: 0 } }
|
||||
fn bar(&self) {}
|
||||
}
|
||||
fn caller() {
|
||||
Foo::new().bar();
|
||||
Foo::make().bar();
|
||||
}
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
expect(callerNamesOf('Foo::bar')).toEqual(['caller']);
|
||||
expect(callerNamesOf('Aaa::bar')).toEqual([]);
|
||||
});
|
||||
|
||||
it('resolves a chain that passes arguments — Foo::with(c).build()', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'main.rs'),
|
||||
`struct Config;
|
||||
struct Foo { _x: i32 }
|
||||
impl Foo {
|
||||
fn with(c: Config) -> Foo { Foo { _x: 0 } }
|
||||
fn build(&self) {}
|
||||
}
|
||||
fn caller() { Foo::with(Config).build(); }
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
expect(callerNamesOf('Foo::build')).toEqual(['caller']);
|
||||
});
|
||||
|
||||
it('resolves a chained method from a trait the type implements (default method, via conformance)', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'main.rs'),
|
||||
`struct Foo { _x: i32 }
|
||||
impl Foo { fn new() -> Foo { Foo { _x: 0 } } }
|
||||
struct Decoy { _x: i32 }
|
||||
impl Decoy { fn draw(&self) {} }
|
||||
trait Drawable { fn draw(&self) {} }
|
||||
impl Drawable for Foo {}
|
||||
fn caller() { Foo::new().draw(); }
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
expect(callerNamesOf('Drawable::draw')).toEqual(['caller']);
|
||||
expect(callerNamesOf('Decoy::draw')).toEqual([]);
|
||||
});
|
||||
|
||||
it('creates NO edge when neither the type nor a supertype has the method (silent miss)', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'main.rs'),
|
||||
`struct Foo { _x: i32 }
|
||||
impl Foo { fn new() -> Foo { Foo { _x: 0 } } }
|
||||
struct Other { _x: i32 }
|
||||
impl Other { fn only_other(&self) {} }
|
||||
fn caller() { Foo::new().only_other(); }
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
// Foo has no only_other() — must not mis-attach to the same-named Other::only_other.
|
||||
expect(callerNamesOf('Other::only_other')).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user