fix(cpp): resolve calls through singletons/factories/chained getters (#645) (#742)

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:
Colby Mchenry
2026-06-08 20:18:17 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent a56d9e6941
commit fd03f31b2c
14 changed files with 421 additions and 8 deletions
+11 -1
View File
@@ -9,7 +9,7 @@ import { SqliteDatabase } from './sqlite-adapter';
/**
* Current schema version
*/
export const CURRENT_SCHEMA_VERSION = 4;
export const CURRENT_SCHEMA_VERSION = 5;
/**
* Migration definition
@@ -65,6 +65,16 @@ const migrations: Migration[] = [
`);
},
},
{
version: 5,
description:
'Add nodes.return_type — normalized return/result type for receiver-type inference (C++ singletons/factories, #645)',
up: (db) => {
db.exec(`
ALTER TABLE nodes ADD COLUMN return_type TEXT;
`);
},
},
];
/**
+7 -2
View File
@@ -72,6 +72,7 @@ interface NodeRow {
is_abstract: number;
decorators: string | null;
type_parameters: string | null;
return_type: string | null;
updated_at: number;
}
@@ -133,6 +134,7 @@ function rowToNode(row: NodeRow): Node {
isAbstract: row.is_abstract === 1,
decorators: row.decorators ? safeJsonParse(row.decorators, undefined) : undefined,
typeParameters: row.type_parameters ? safeJsonParse(row.type_parameters, undefined) : undefined,
returnType: row.return_type ?? undefined,
updatedAt: row.updated_at,
};
}
@@ -232,13 +234,13 @@ export class QueryBuilder {
start_line, end_line, start_column, end_column,
docstring, signature, visibility,
is_exported, is_async, is_static, is_abstract,
decorators, type_parameters, updated_at
decorators, type_parameters, return_type, updated_at
) VALUES (
@id, @kind, @name, @qualifiedName, @filePath, @language,
@startLine, @endLine, @startColumn, @endColumn,
@docstring, @signature, @visibility,
@isExported, @isAsync, @isStatic, @isAbstract,
@decorators, @typeParameters, @updatedAt
@decorators, @typeParameters, @returnType, @updatedAt
)
`);
}
@@ -281,6 +283,7 @@ export class QueryBuilder {
isAbstract: node.isAbstract ? 1 : 0,
decorators: node.decorators ? JSON.stringify(node.decorators) : null,
typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
returnType: node.returnType ?? null,
updatedAt: node.updatedAt ?? Date.now(),
});
}
@@ -321,6 +324,7 @@ export class QueryBuilder {
is_abstract = @isAbstract,
decorators = @decorators,
type_parameters = @typeParameters,
return_type = @returnType,
updated_at = @updatedAt
WHERE id = @id
`);
@@ -355,6 +359,7 @@ export class QueryBuilder {
isAbstract: node.isAbstract ? 1 : 0,
decorators: node.decorators ? JSON.stringify(node.decorators) : null,
typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
returnType: node.returnType ?? null,
updatedAt: node.updatedAt ?? Date.now(),
});
}
+1
View File
@@ -37,6 +37,7 @@ CREATE TABLE IF NOT EXISTS nodes (
is_abstract INTEGER DEFAULT 0,
decorators TEXT, -- JSON array
type_parameters TEXT, -- JSON array
return_type TEXT, -- normalized return/result type name (e.g. C++ method return, for receiver-type inference)
updated_at INTEGER NOT NULL
);