A C++ method call whose receiver is another call's result — `Foo::instance().bar()`, `WidgetFactory::create().draw()`, `openSession()->run()`, or the same stored in an `auto` local first — lost the receiver's type during extraction. The callee degraded to a bare method name, so when two classes shared a method name the call silently resolved to whichever was indexed first (or not at all), corrupting callers / impact / trace with a plausible-but-wrong edge. Three parts: - Capture C++ return types (new nodes.return_type column, schema v5): the function_definition's `type` field, normalized — smart-pointer pointee unwrapped, void/primitives dropped. - Preserve the inner-call receiver in extraction: a C/C++ field_expression whose receiver is itself a call is encoded `inner().method` instead of dropping to the bare name. Other languages keep the existing behavior. - New resolution strategy (matchCppCallChain): infer the receiver's class from the inner call's return type, then resolve AND validate the method on it. Handles singletons/accessors, factories returning a different type, free-function factories, make_unique/make_shared/new/direct construction, single-level member chains, and namespace-qualified inner calls. A wrong inference yields no edge, never a wrong one. EXTRACTION_VERSION 2->3 (re-index to populate return types). Validated on the issue repro + spdlog: node count stable (no explosion), deterministic, and ~100 pre-existing wrong `.size()`-style edges removed. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a56d9e6941
commit
fd03f31b2c
@@ -2369,6 +2369,41 @@ end
|
||||
});
|
||||
});
|
||||
|
||||
describe('C/C++ return type capture (#645)', () => {
|
||||
it('captures the normalized return type of a C++ method/function', () => {
|
||||
const code = `
|
||||
struct Widget { void draw(); };
|
||||
class Factory { public: static Widget create(); };
|
||||
Widget Factory::create() { return Widget(); }
|
||||
void doNothing() {}
|
||||
`;
|
||||
const result = extractFromSource('f.cpp', code);
|
||||
|
||||
const create = result.nodes.find(
|
||||
(n) => n.name === 'create' && (n.kind === 'method' || n.kind === 'function')
|
||||
);
|
||||
expect(create?.returnType).toBe('Widget');
|
||||
|
||||
// A `void` return records no type, so resolution never tries to resolve a
|
||||
// method on it.
|
||||
const doNothing = result.nodes.find((n) => n.name === 'doNothing');
|
||||
expect(doNothing).toBeDefined();
|
||||
expect(doNothing?.returnType).toBeUndefined();
|
||||
});
|
||||
|
||||
it('unwraps a smart-pointer return type to its pointee', () => {
|
||||
const code = `
|
||||
#include <memory>
|
||||
struct Widget {};
|
||||
std::unique_ptr<Widget> makeWidget() { return nullptr; }
|
||||
`;
|
||||
const result = extractFromSource('f.cpp', code);
|
||||
|
||||
const make = result.nodes.find((n) => n.name === 'makeWidget');
|
||||
expect(make?.returnType).toBe('Widget');
|
||||
});
|
||||
});
|
||||
|
||||
describe('C/C++ imports', () => {
|
||||
it('should extract system include', () => {
|
||||
const code = `#include <iostream>`;
|
||||
|
||||
Reference in New Issue
Block a user