fix(resolution): recognize union instantiation

This commit is contained in:
ctype_lab
2026-08-06 17:11:26 +09:00
parent ba58365c6f
commit e922563e05
3 changed files with 53 additions and 2 deletions
+47
View File
@@ -1020,6 +1020,31 @@ def bootstrap():
expect(callsToUserService).toHaveLength(0);
});
it('promotes calls→instantiates when target resolves to a C++ union', async () => {
// `Packet()` value-initializes the union. The extractor emits a calls
// reference for that expression, so resolution must preserve the
// class-like promotion that unions received when they were structs.
fs.writeFileSync(
path.join(tempDir, 'packet.cpp'),
`union Packet { unsigned int raw; };
void initialize() { Packet(); }
`
);
cg = await CodeGraph.init(tempDir, { index: true });
cg.resolveReferences();
const packet = cg.getNodesByKind('union').find((n) => n.name === 'Packet');
const initialize = cg.getNodesByKind('function').find((n) => n.name === 'initialize');
expect(packet).toBeDefined();
expect(initialize).toBeDefined();
const outgoing = cg.getOutgoingEdges(initialize!.id);
expect(outgoing.some((e) => e.kind === 'instantiates' && e.target === packet!.id)).toBe(true);
expect(outgoing.some((e) => e.kind === 'calls' && e.target === packet!.id)).toBe(false);
});
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
@@ -2169,6 +2194,28 @@ func main() {
expect(result?.targetNodeId).toBe('class:logger.ts:Logger:10');
});
it('prefers a union candidate over a function for `instantiates` refs', () => {
const fn: Node = {
id: 'func:packet.cpp:Packet:5', kind: 'function', name: 'Packet',
qualifiedName: 'packet.cpp::Packet', filePath: 'packet.cpp', language: 'cpp',
startLine: 5, endLine: 7, startColumn: 0, endColumn: 0, updatedAt: Date.now(),
};
const union: Node = {
id: 'union:packet.hpp:Packet:10', kind: 'union', name: 'Packet',
qualifiedName: 'packet.hpp::Packet', filePath: 'packet.hpp', language: 'cpp',
startLine: 10, endLine: 14, startColumn: 0, endColumn: 0, updatedAt: Date.now(),
};
const ref = {
fromNodeId: 'func:main.cpp:initialize:1',
referenceName: 'Packet',
referenceKind: 'instantiates' as const,
line: 5, column: 0, filePath: 'main.cpp', language: 'cpp' as const,
};
const result = matchReference(ref, baseContext([fn, union]));
expect(result?.targetNodeId).toBe('union:packet.hpp:Packet:10');
});
it('prefers a function candidate over a non-function for `decorates` refs', () => {
const variable: Node = {
id: 'var:config.ts:Inject:5', kind: 'variable', name: 'Inject',
+5 -2
View File
@@ -1079,13 +1079,16 @@ export class ReferenceResolver {
}
// Promote "calls" to "instantiates" when the resolved target is a
// class/struct. Languages without a `new` keyword (Python, Ruby)
// class/struct/union. Languages without a `new` keyword (Python, Ruby)
// express instantiation as `Foo()` — extraction can't tell that
// apart from a function call without symbol info, but resolution
// can: if `Foo` resolves to a class, the call IS an instantiation.
if (kind === 'calls') {
const targetNode = this.queries.getNodeById(ref.targetNodeId);
if (targetNode && (targetNode.kind === 'class' || targetNode.kind === 'struct')) {
if (
targetNode &&
(targetNode.kind === 'class' || targetNode.kind === 'struct' || targetNode.kind === 'union')
) {
kind = 'instantiates';
}
}
+1
View File
@@ -2107,6 +2107,7 @@ function findBestMatch(
if (
candidate.kind === 'class' ||
candidate.kind === 'struct' ||
candidate.kind === 'union' ||
candidate.kind === 'interface'
) {
score += 25;