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);
|
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', () => {
|
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', () => {
|
describe('Dart mixins and type references', () => {
|
||||||
let tempDir: string;
|
let tempDir: string;
|
||||||
let cg: CodeGraph;
|
let cg: CodeGraph;
|
||||||
|
|||||||
@@ -152,3 +152,19 @@ static void ratelimited_warn(void) {
|
|||||||
static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 5);
|
static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 5);
|
||||||
use_ptr(&ratelimit, 0);
|
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];
|
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