From e922563e0509976152736fa9ad6064f85668620b Mon Sep 17 00:00:00 2001 From: ctype_lab Date: Thu, 6 Aug 2026 17:05:33 +0900 Subject: [PATCH] fix(resolution): recognize union instantiation --- __tests__/resolution.test.ts | 47 ++++++++++++++++++++++++++++++++++ src/resolution/index.ts | 7 +++-- src/resolution/name-matcher.ts | 1 + 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/__tests__/resolution.test.ts b/__tests__/resolution.test.ts index eca1778..6b64241 100644 --- a/__tests__/resolution.test.ts +++ b/__tests__/resolution.test.ts @@ -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', diff --git a/src/resolution/index.ts b/src/resolution/index.ts index ef3a5fc..01f615b 100644 --- a/src/resolution/index.ts +++ b/src/resolution/index.ts @@ -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'; } } diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index 208213f..6510514 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -2107,6 +2107,7 @@ function findBestMatch( if ( candidate.kind === 'class' || candidate.kind === 'struct' || + candidate.kind === 'union' || candidate.kind === 'interface' ) { score += 25;