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
@@ -811,6 +811,7 @@ export class TreeSitterExtractor {
|
||||
const isExported = this.extractor.isExported?.(node, this.source);
|
||||
const isAsync = this.extractor.isAsync?.(node);
|
||||
const isStatic = this.extractor.isStatic?.(node);
|
||||
const returnType = this.extractor.getReturnType?.(node, this.source);
|
||||
|
||||
const funcNode = this.createNode('function', name, node, {
|
||||
docstring,
|
||||
@@ -819,6 +820,7 @@ export class TreeSitterExtractor {
|
||||
isExported,
|
||||
isAsync,
|
||||
isStatic,
|
||||
returnType,
|
||||
});
|
||||
if (!funcNode) return;
|
||||
|
||||
@@ -930,12 +932,14 @@ export class TreeSitterExtractor {
|
||||
const visibility = this.extractor.getVisibility?.(node);
|
||||
const isAsync = this.extractor.isAsync?.(node);
|
||||
const isStatic = this.extractor.isStatic?.(node);
|
||||
const returnType = this.extractor.getReturnType?.(node, this.source);
|
||||
const extraProps: Partial<Node> = {
|
||||
docstring,
|
||||
signature,
|
||||
visibility,
|
||||
isAsync,
|
||||
isStatic,
|
||||
returnType,
|
||||
};
|
||||
if (receiverType) {
|
||||
extraProps.qualifiedName = `${receiverType}::${name}`;
|
||||
@@ -2457,6 +2461,23 @@ export class TreeSitterExtractor {
|
||||
} else {
|
||||
calleeName = methodName;
|
||||
}
|
||||
} else if (
|
||||
(this.language === 'cpp' || this.language === 'c') &&
|
||||
receiver &&
|
||||
receiver.type === 'call_expression'
|
||||
) {
|
||||
// C/C++ receiver that is itself a call — `Foo::instance().bar()`,
|
||||
// `openSession()->run()`, `mgr.view().render()`. Keep the inner
|
||||
// call so resolution can infer bar()'s class from what the inner
|
||||
// call RETURNS (#645). Encode as `<innerCallee>().<method>`; the
|
||||
// `().` marker never appears in an ordinary ref, so the C++
|
||||
// resolver can detect and split it. Other languages keep the
|
||||
// bare-name behavior (dropping the receiver) below.
|
||||
const innerFn = getChildByField(receiver, 'function');
|
||||
const innerCallee = innerFn
|
||||
? getNodeText(innerFn, this.source).replace(/->/g, '.').replace(/\s+/g, '')
|
||||
: '';
|
||||
calleeName = innerCallee ? `${innerCallee}().${methodName}` : methodName;
|
||||
} else {
|
||||
calleeName = methodName;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user