Files
codegraph/__tests__/cpp-macro-namespace.test.ts
Colby MchenryandClaude Opus 5.5 5844eb8986 fix(cpp): a qualified name reaches a declaration in a macro-opened namespace (#2176)
fmt opens its namespace with FMT_BEGIN_NAMESPACE, pybind11 with
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE), rapidjson/spdlog with
…_NAMESPACE_BEGIN — the extractor sees a bare macro line, so every
declaration inside is indexed without its namespace and fmt::format(…)
(1,728 calls on fmt) resolved to nothing.

As a last resort for a qualified C/C++ name, read the project's
namespace macros (object-like and single-parameter function-like
openers, through identifier aliases, inline namespaces transparent;
closers by brace count), the ranges each opens per file, and namespace
aliases (namespace py = pybind11;), and match the declaration whose
macro prefix + qualified name is the name called. fmt +2480, pybind11
+1446, spdlog +264; tokio/serde/gin/excalidraw/Newtonsoft unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:38:59 +00:00

91 lines
2.7 KiB
TypeScript

/**
* A C++ library that opens its namespace with a macro — fmt's
* `FMT_BEGIN_NAMESPACE` (`namespace fmt { inline namespace v12 {`),
* pybind11's `PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)` — has its
* declarations indexed without that namespace, so `fmt::format(…)` found
* nothing: 1,728 calls on fmt. A qualified name now reaches a declaration the
* project's own namespace macros put there, through a namespace alias
* (`namespace py = pybind11;`) too.
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
let root = '';
let cg: CodeGraph;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-cpp-macro-ns-'));
const files: Record<string, string> = {
'include/fmt/base.h': `#pragma once
#define FMT_BEGIN_NAMESPACE \\
namespace fmt { \\
inline namespace v12 {
#define FMT_END_NAMESPACE \\
} \\
}
FMT_BEGIN_NAMESPACE
namespace detail {
inline int to_unsigned(int value) { return value; }
}
inline int format(const char* spec) { return detail::to_unsigned(0); }
FMT_END_NAMESPACE
`,
'include/pybind11/detail/macros.h': `#pragma once
#define PYBIND11_NAMESPACE pybind11
#define PYBIND11_NAMESPACE_BEGIN(name) \\
namespace name { \\
PYBIND11_WARNING_PUSH
#define PYBIND11_NAMESPACE_END(name) \\
PYBIND11_WARNING_POP \\
}
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
inline void print(const char* text) {}
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
`,
'test/format-test.cc': `#include "fmt/base.h"
#include "pybind11/detail/macros.h"
namespace py = pybind11;
int main() {
fmt::format("{}");
fmt::detail::to_unsigned(1);
py::print("hi");
return 0;
}
`,
};
for (const [rel, content] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
});
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
describe('C++ namespaces opened by a macro', () => {
it('put their declarations under the qualified names callers use', () => {
const ids = cg.getNodesInFile('test/format-test.cc').map((n) => n.id);
const targets = cg
.getOutgoingEdgesFrom(ids)
.filter((e) => e.kind === 'calls')
.map((e) => `${cg.getNode(e.target)!.filePath}:${cg.getNode(e.target)!.name}`)
.sort();
expect(targets).toEqual([
'include/fmt/base.h:format',
'include/fmt/base.h:to_unsigned',
'include/pybind11/detail/macros.h:print',
]);
});
});