fix(resolution): trait dispatch reaches union implementors (#1515)
Making unions first-class nodes leaves the third loss in #1515 open: interfaceOverrideEdges enumerates its concrete side as ['class','struct'], so a union implementor is skipped even though it now has a real node and a real `implements` edge. "Who implements this trait" then answers wrongly rather than incompletely — the struct beside it bridges and the union does not. Add 'union' to that tuple, plus a regression test that pins the Rust trait -> union-impl hop (the struct implementor is the control proving the synthesizer ran). Verified the test fails on the union assertion alone before this change. No EXTRACTION_VERSION bump: main is already at 25 against v1.5.0's 24, so existing indexes are flagged stale for the next release regardless, and over-bumping is what turns the re-index hint into noise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -17,7 +17,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
### Fixes
|
||||
|
||||
- C, C++, Objective-C and Rust unions are now indexed as first-class `union` nodes. A `union` declaration previously produced no symbol at all, so it never appeared in search or `codegraph_explore`, and anything attached to it disappeared with it — in Rust, every `impl SomeTrait for MyUnion` lost its edge, and the methods from that impl were left pointing at a type the graph did not contain. A `typedef union { … } Name;` in C now carries the typedef's name and remains distinguishable from a struct. Re-index after upgrading to replace the earlier struct-shaped union nodes.
|
||||
- C, C++, Objective-C and Rust unions are now indexed as first-class `union` nodes. A `union` declaration previously produced no symbol at all, so it never appeared in search or `codegraph_explore`, and anything attached to it disappeared with it — in Rust, every `impl SomeTrait for MyUnion` lost its edge, the methods from that impl were left pointing at a type the graph did not contain, and asking which types implement a trait quietly skipped the union ones. A union-shaped dispatch table in C now resolves its function pointers like a struct-shaped one. A `typedef union { … } Name;` in C keeps the typedef's name and is no longer mistaken for a plain type alias. Thanks @ctype-lab. Re-index after upgrading to pick up unions in existing projects. (#1515)
|
||||
|
||||
- `codegraph_explore` now concentrates its answer on the code that actually answers your question instead of spreading it across files that merely share a word with it, so more of the answer arrives in a single call. Thanks @LeDuyViet for the detailed measurements and reproduction. (#1500)
|
||||
- Files only weakly related to your question now come back as a name, symbol and line number instead of spending the answer on their source — name one of them in a follow-up `codegraph_explore` to get it back in full. (#1500)
|
||||
|
||||
@@ -1073,6 +1073,52 @@ int invoke() { return Ops::run(); }
|
||||
expect(outgoing.some((e) => e.kind === 'calls' && e.target === run!.id)).toBe(true);
|
||||
});
|
||||
|
||||
it('bridges a Rust trait method to a union implementor (interface-impl)', async () => {
|
||||
// A Rust union can `impl Trait` exactly as a struct can. Trait-dispatch
|
||||
// synthesis enumerates concrete kinds explicitly, so a union implementor
|
||||
// only becomes a candidate if `union` is in that list. Making unions
|
||||
// first-class nodes is not enough on its own: without this, `Reg` has an
|
||||
// `implements` edge and is still silently dropped from the fan-out, so
|
||||
// "who implements this trait" answers wrongly rather than incompletely
|
||||
// — the struct beside it resolves and the union does not (#1515).
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'lib.rs'),
|
||||
`pub union Reg { pub raw: u32 }
|
||||
pub struct Ctl { pub n: u32 }
|
||||
|
||||
pub trait Describe { fn describe(&self) -> String; }
|
||||
|
||||
impl Describe for Reg { fn describe(&self) -> String { "reg".into() } }
|
||||
impl Describe for Ctl { fn describe(&self) -> String { "ctl".into() } }
|
||||
`
|
||||
);
|
||||
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
|
||||
const methods = cg.getNodesByKind('method');
|
||||
const traitMethod = methods.find((n) => n.qualifiedName === 'Describe::describe');
|
||||
const unionImpl = methods.find((n) => n.qualifiedName === 'Reg::describe');
|
||||
const structImpl = methods.find((n) => n.qualifiedName === 'Ctl::describe');
|
||||
expect(traitMethod, 'trait method should be in the graph').toBeDefined();
|
||||
expect(unionImpl, 'union impl method should be in the graph').toBeDefined();
|
||||
expect(structImpl, 'struct impl method should be in the graph').toBeDefined();
|
||||
|
||||
const synth = cg
|
||||
.getOutgoingEdges(traitMethod!.id)
|
||||
.filter((e) => e.kind === 'calls' && e.provenance === 'heuristic');
|
||||
const targets = new Set(synth.map((e) => e.target));
|
||||
|
||||
// The struct implementor bridged before unions were nodes at all; it is
|
||||
// the control that proves the synthesizer ran for this trait.
|
||||
expect(targets.has(structImpl!.id), 'struct implementor should bridge').toBe(true);
|
||||
expect(targets.has(unionImpl!.id), 'union implementor should bridge').toBe(true);
|
||||
|
||||
const unionEdge = synth.find((e) => e.target === unionImpl!.id);
|
||||
expect(
|
||||
(unionEdge!.metadata as { synthesizedBy?: string } | undefined)?.synthesizedBy
|
||||
).toBe('interface-impl');
|
||||
});
|
||||
|
||||
it('records instantiates for C++ stack/brace construction, targeting the class (#1035)', async () => {
|
||||
// `Calculator calc(0)` (direct-init) and `Widget w{1, 2}` (brace-init)
|
||||
// carry the constructor args directly on the declarator — there's no
|
||||
|
||||
@@ -1061,7 +1061,7 @@ async function interfaceOverrideEdges(queries: QueryBuilder, onYield: MaybeYield
|
||||
// Concrete-side kinds vary by language: `class` covers Java / Kotlin /
|
||||
// C# / TS / Swift-classes / Scala-classes; `struct` covers Swift value
|
||||
// types that conform to protocols. Iterate both.
|
||||
const concreteKinds = ['class', 'struct'] as const;
|
||||
const concreteKinds = ['class', 'struct', 'union'] as const;
|
||||
for (const kind of concreteKinds) {
|
||||
for (const cls of queries.iterateNodesByKind(kind)) {
|
||||
if ((++scanned255 & 63) === 0) await onYield();
|
||||
|
||||
Reference in New Issue
Block a user