fix(cpp): compose namespace prefix into out-of-line method qualified names (#1310)

An out-of-line member definition inside a namespace block takes its
qualifiedName from the declarator's receiver, which is spelled RELATIVE
to the enclosing namespace — so `namespace simulator {
ManifestStartup::Output ManifestStartup::Apply(...) {} }` indexed as
ManifestStartup::Apply while the class node carried
simulator::ManifestStartup. Fully-qualified call sites
(simulator::ManifestStartup::Apply(...)) never resolved; callers and
file impact came up empty (#1291).

The receiver-based qualifiedName now composes the active namespace
prefix, anchored at the first prefix segment the receiver re-spells
(so `namespace sim { void sim::M::f() {} }` doesn't double-prefix).
namespacePrefix is only ever non-empty for C++ — Go/Rust/Kotlin/Lua
receivers pass through unchanged.

leveldb re-index: node count byte-stable (3,044), calls edges +6,
namespace-qualified method names 947 -> 1,252.

Fixes #1291

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-16 15:10:55 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent 4dd29ea5c1
commit e437918026
4 changed files with 132 additions and 1 deletions
+63
View File
@@ -2541,6 +2541,69 @@ func main() {
});
});
describe('C++ namespace-qualified static method calls to out-of-line definitions (#1291)', () => {
// The issue's exact shape: nested types + out-of-line static method
// definition inside `namespace simulator { }` in the .cpp, called via the
// fully-qualified path from a different file. The definition's
// qualifiedName previously dropped the namespace (`ManifestStartup::Apply`
// vs the class's `simulator::ManifestStartup`), so `callers` came up empty.
it('resolves simulator::ManifestStartup::Apply(...) from another file', async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-1291-'));
try {
fs.writeFileSync(
path.join(tmpDir, 'manifest_startup.h'),
`#pragma once
namespace simulator {
class ManifestStartup {
public:
struct Input { int a; };
struct Output { int b; };
static Output Apply(const Input& input);
};
}
`
);
fs.writeFileSync(
path.join(tmpDir, 'manifest_startup.cpp'),
`#include "manifest_startup.h"
namespace simulator {
ManifestStartup::Output ManifestStartup::Apply(const Input& input) {
return Output{input.a};
}
}
`
);
fs.writeFileSync(
path.join(tmpDir, 'main.cpp'),
`#include "manifest_startup.h"
int run() {
const auto manifest_result = simulator::ManifestStartup::Apply({1});
return manifest_result.b;
}
`
);
const cg = CodeGraph.initSync(tmpDir);
await cg.indexAll();
const applyDefs = (await cg.searchNodes('Apply', { limit: 20 })).filter(
(r) => r.node.name === 'Apply' && r.node.kind === 'method'
);
expect(applyDefs.length).toBeGreaterThan(0);
const def = applyDefs.find((r) => r.node.filePath.endsWith('manifest_startup.cpp'));
expect(def).toBeDefined();
expect(def!.node.qualifiedName).toBe('simulator::ManifestStartup::Apply');
// The qualified cross-file call resolves: run() is a caller of Apply.
const callers = await cg.getCallers(def!.node.id);
expect(callers.map((c) => c.node.name)).toContain('run');
cg.close();
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}, 30000);
});
describe('C/C++ Import Resolution', () => {
afterEach(() => {
clearCppIncludeDirCache();