Files
Colby Mchenry 8f8081968e fix(rust): keep self method calls on the correct owner (#1882)
Fixes #1861. Reuses L4XB's receiver retention and owner resolution from #1866, with duplicate-owner and edit/sync guards.

Co-authored-by: L4XB <lukas.buck@e-mail.de>
2026-09-16 07:18:40 +00:00

69 lines
3.1 KiB
TypeScript

import { afterEach, beforeEach, expect, it } from 'vitest';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { CodeGraph } from '../src';
let root: string;
let cg: CodeGraph | undefined;
beforeEach(() => { root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-rust-self-owner-')); });
afterEach(() => { cg?.close(); cg = undefined; fs.rmSync(root, { recursive: true, force: true }); });
async function index(files: Record<string, string>) {
fs.mkdirSync(path.join(root, 'src'));
fs.writeFileSync(path.join(root, 'Cargo.toml'), '[package]\nname="owners"\nversion="0.1.0"\nedition="2021"\n');
for (const [file, text] of Object.entries(files)) fs.writeFileSync(path.join(root, 'src', file), text);
cg = await CodeGraph.init(root, { index: true });
}
function targets(file: string) {
const caller = cg!.getNodesByKind('method').find(n => n.filePath === `src/${file}` && n.qualifiedName === 'Target::run');
expect(caller).toBeDefined();
return cg!.getOutgoingEdges(caller!.id).filter(e => e.kind === 'calls').map(e => {
const target = cg!.getNode(e.target)!;
return `${target.filePath}:${target.qualifiedName}`;
});
}
it('does not borrow a missing method from a same-named type in another module (#1861)', async () => {
await index({
'lib.rs': 'pub mod caller; pub mod decoy;',
'caller.rs': 'pub struct Target;\nimpl Target { pub fn run(&self) { self.reset(); } }',
'decoy.rs': 'pub struct Target;\nimpl Target { pub fn reset(&self) {} }',
});
expect(targets('caller.rs')).toEqual([]);
});
it('keeps a proven local owner despite a same-named type and method in another module (#1861)', async () => {
await index({
'lib.rs': 'pub mod caller; pub mod decoy;',
'caller.rs': 'pub struct Target;\nimpl Target { pub fn reset(&self) {} }\nimpl Target { pub fn run(&self) { self.reset(); } }',
'decoy.rs': 'pub struct Target;\nimpl Target { pub fn reset(&self) {} }',
});
expect(targets('caller.rs')).toEqual(['src/caller.rs:Target::reset']);
fs.writeFileSync(path.join(root, 'src/caller.rs'), 'pub struct Target;\nimpl Target { pub fn run(&self) { self.reset(); } }');
await cg!.sync();
expect(targets('caller.rs')).toEqual([]);
fs.writeFileSync(path.join(root, 'src/caller.rs'), 'pub struct Target;\nimpl Target { pub fn reset(&self) {} }\nimpl Target { pub fn run(&self) { self.reset(); } }');
await cg!.sync();
expect(targets('caller.rs')).toEqual(['src/caller.rs:Target::reset']);
});
it('keeps a unique owner whose impl is split across files (#1861)', async () => {
await index({
'lib.rs': 'pub mod caller;\npub struct Target;\nimpl Target { pub fn reset(&self) {} }',
'caller.rs': 'use crate::Target;\nimpl Target { pub fn run(&self) { self.reset(); } }',
});
expect(targets('caller.rs')).toEqual(['src/lib.rs:Target::reset']);
});
it('declines indistinguishable inline-module owners instead of claiming one (#1861)', async () => {
await index({ 'lib.rs': `mod a {
pub struct Target;
impl Target { pub fn reset(&self) {} }
}
mod b {
pub struct Target;
impl Target { pub fn run(&self) { self.reset(); } }
}` });
expect(targets('lib.rs')).toEqual([]);
});