test(union): regression cases + kernel-parity torture coverage
Four cases in extraction.test.ts: a Rust union carrying an `impl Trait for` edge and owning the impl's method; a named C union alongside a forward declaration that must NOT mint a node; a `typedef union` taking the typedef name with no `<anonymous>` twin; a C++ union with a member function. Verified they fail without the fix on BOTH extraction paths — the wasm walker via CODEGRAPH_KERNEL=0 and the kernel with a staged build. torture.c / torture.rs gain the same shapes. The parity gate compares the two walkers rather than a snapshot, so the fixtures do not detect the bug on their own — they pin that the fix stays SYMMETRIC. The regression tests above are what pin that it is present. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
85e9ac6762
commit
11acc504b5
@@ -1174,6 +1174,51 @@ impl Counter {
|
||||
);
|
||||
expect(implRefs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should extract union declarations and their impl edges', () => {
|
||||
const code = `
|
||||
pub union Reg {
|
||||
pub raw: u32,
|
||||
pub halves: [u16; 2],
|
||||
}
|
||||
|
||||
pub trait Describe {
|
||||
fn describe(&self) -> u32;
|
||||
}
|
||||
|
||||
impl Describe for Reg {
|
||||
fn describe(&self) -> u32 {
|
||||
unsafe { self.raw }
|
||||
}
|
||||
}
|
||||
`;
|
||||
const result = extractFromSource('reg.rs', code);
|
||||
|
||||
// A union is a type definition, not an alias — it must be a node, or the
|
||||
// impl below has no source endpoint to hang off.
|
||||
const reg = result.nodes.find((n) => n.name === 'Reg');
|
||||
expect(reg).toBeDefined();
|
||||
expect(reg?.kind).toBe('struct');
|
||||
|
||||
const implRef = result.unresolvedReferences.find(
|
||||
(r) => r.referenceKind === 'implements' && r.referenceName === 'Describe'
|
||||
);
|
||||
expect(implRef).toBeDefined();
|
||||
expect(implRef?.fromNodeId).toBe(reg?.id);
|
||||
|
||||
// The impl's method attaches to the union, not to the file — without a Reg
|
||||
// node it was an orphan whose qualifiedName pointed at a type that did not
|
||||
// exist in the graph.
|
||||
const implMethod = result.nodes.find(
|
||||
(n) => n.kind === 'method' && n.qualifiedName?.includes('Reg')
|
||||
);
|
||||
expect(implMethod).toBeDefined();
|
||||
expect(
|
||||
result.edges.some(
|
||||
(e) => e.kind === 'contains' && e.source === reg?.id && e.target === implMethod?.id
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Java Extraction', () => {
|
||||
@@ -5642,6 +5687,71 @@ std::string use() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('C/C++ union declarations', () => {
|
||||
it('extracts a named union as a type node, but not a forward declaration', () => {
|
||||
const code = `
|
||||
union packet_hdr {
|
||||
unsigned int raw;
|
||||
unsigned short port;
|
||||
};
|
||||
|
||||
/* forward declaration — not a definition */
|
||||
union opaque_hdr;
|
||||
|
||||
static unsigned int hdr_raw(union packet_hdr *h) { return h->raw; }
|
||||
`;
|
||||
const result = extractFromSource('packet.c', code);
|
||||
|
||||
const hdr = result.nodes.find((n) => n.name === 'packet_hdr');
|
||||
expect(hdr).toBeDefined();
|
||||
expect(hdr?.kind).toBe('struct');
|
||||
|
||||
// Same rule as `struct Foo;`: bodiless is a forward declaration, so it must
|
||||
// not mint a phantom node beside the real definition.
|
||||
expect(result.nodes.some((n) => n.name === 'opaque_hdr')).toBe(false);
|
||||
|
||||
// Exactly one node for the type — the definition — so a call site or a
|
||||
// `union packet_hdr *` parameter has a single resolution target.
|
||||
expect(result.nodes.filter((n) => n.name === 'packet_hdr')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('gives a typedef union the typedef name, not a second <anonymous> node', () => {
|
||||
const code = `
|
||||
typedef union {
|
||||
unsigned int u;
|
||||
float f;
|
||||
} word_t;
|
||||
`;
|
||||
const result = extractFromSource('word.c', code);
|
||||
|
||||
const word = result.nodes.find((n) => n.name === 'word_t');
|
||||
expect(word?.kind).toBe('struct');
|
||||
// Resolved through the typedef the same way `typedef struct { … } X;` is,
|
||||
// so the anonymous union body does not become its own node.
|
||||
expect(result.nodes.some((n) => n.name === '<anonymous>')).toBe(false);
|
||||
});
|
||||
|
||||
it('extracts a C++ union with member functions', () => {
|
||||
const code = `
|
||||
union Value {
|
||||
int i;
|
||||
double d;
|
||||
int as_int() const { return i; }
|
||||
};
|
||||
`;
|
||||
const result = extractFromSource('value.cpp', code);
|
||||
|
||||
const value = result.nodes.find((n) => n.name === 'Value');
|
||||
expect(value?.kind).toBe('struct');
|
||||
|
||||
const asInt = result.nodes.find((n) => n.name === 'as_int');
|
||||
expect(asInt).toBeDefined();
|
||||
expect(
|
||||
result.edges.some((e) => e.kind === 'contains' && e.source === value?.id && e.target === asInt?.id)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dart mixins and type references', () => {
|
||||
let tempDir: string;
|
||||
let cg: CodeGraph;
|
||||
|
||||
@@ -152,3 +152,19 @@ static void ratelimited_warn(void) {
|
||||
static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 5);
|
||||
use_ptr(&ratelimit, 0);
|
||||
}
|
||||
|
||||
/* named union definition, forward declaration, and anonymous typedef union —
|
||||
the definition is a node, the forward decl is not (#UNION) */
|
||||
union packet_hdr {
|
||||
unsigned int raw;
|
||||
struct { unsigned char ver, flags; } parts;
|
||||
};
|
||||
|
||||
union opaque_hdr;
|
||||
|
||||
typedef union {
|
||||
unsigned int u;
|
||||
float f;
|
||||
} word_t;
|
||||
|
||||
static unsigned int hdr_raw(union packet_hdr *h) { return h->raw; }
|
||||
|
||||
@@ -209,3 +209,10 @@ fn mount() {
|
||||
}
|
||||
|
||||
routes![top_level_h];
|
||||
|
||||
pub union Reg {
|
||||
pub raw: u32,
|
||||
pub halves: [u16; 2],
|
||||
}
|
||||
|
||||
impl Base for Reg {}
|
||||
|
||||
Reference in New Issue
Block a user