diff --git a/gitnexus/test/fixtures/lang-resolution/rust-dyn-trait-object/src/lib.rs b/gitnexus/test/fixtures/lang-resolution/rust-dyn-trait-object/src/lib.rs new file mode 100644 index 000000000..d25dad83d --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/rust-dyn-trait-object/src/lib.rs @@ -0,0 +1,15 @@ +pub trait Behaviour { + fn trait_target(&self) -> u32; +} + +pub struct Impl1; + +impl Behaviour for Impl1 { + fn trait_target(&self) -> u32 { + 7 + } +} + +pub fn calls_via_dyn(b: &dyn Behaviour) -> u32 { + b.trait_target() +} diff --git a/gitnexus/test/integration/resolvers/rust.test.ts b/gitnexus/test/integration/resolvers/rust.test.ts index 9cd4f94e8..87aa6899b 100644 --- a/gitnexus/test/integration/resolvers/rust.test.ts +++ b/gitnexus/test/integration/resolvers/rust.test.ts @@ -1951,6 +1951,29 @@ describe('Rust abstract dispatch (Repository trait)', () => { }); }); +// --------------------------------------------------------------------------- +// #2604: trait-object (&dyn Trait) receiver dispatch +// --------------------------------------------------------------------------- + +describe('Rust dyn trait-object dispatch (#2604)', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo(path.join(FIXTURES, 'rust-dyn-trait-object'), () => {}); + }, 60000); + + it('detects Impl1 struct and Behaviour trait', () => { + expect(getNodesByLabel(result, 'Struct')).toContain('Impl1'); + expect(getNodesByLabel(result, 'Trait')).toContain('Behaviour'); + }); + + it('emits exactly one CALLS edge from calls_via_dyn(b: &dyn Behaviour) to trait_target', () => { + const calls = getRelationships(result, 'CALLS'); + const dynCalls = calls.filter((c) => c.source === 'calls_via_dyn' && c.target === 'trait_target'); + expect(dynCalls.length).toBe(1); + }); +}); + // --------------------------------------------------------------------------- // SM-11: Rust Child extends Parent — qualified-syntax MRO //