mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
bb817d4538
Receiver inference had no type for a name a structured binding declares,
for what a `std::function` returns, or for a class member whose type is
written on the line above its name. rocksdb's MultiCfIteratorImpl writes
`for (auto& [cfh, iter] : cfh_iter_pairs_) { iter->Valid(); … }` over a
member declared across two lines as a
`std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>`;
the calls were guessed by the method's name.
- cppStructuredBindingType reads a binding's type at the name's place: an
element of the range a range-for over the binding iterates, when the
call is in its body, or of a declaration's initializer the calling
function makes, a `std::pair` or `std::tuple` (cppBindingElements). A
map's range elements are pairs of its key and value (CPP_LIBRARY_MAPS).
- cppStatementFrom reads a statement on to its `;`, for a binding's
initializer and an `auto` local's.
- The declarator regex reads `std::function`, `std::move_only_function`,
`std::copyable_function`, `absl::FunctionRef` and `absl::AnyInvocable`
types, whose template argument is a function type, and
cppExpressionType gives a call on such a variable what the function
type returns (cppWrapperReturnType).
- cppDataMemberIn reads a member whose type is the whole line above its
name (CPP_TYPE_LINE), so a default member initializer continued on the
next line (`bool ready_ = flag_ &&` above `it_;`) isn't taken for one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
238 lines
8.6 KiB
TypeScript
238 lines
8.6 KiB
TypeScript
/**
|
|
* A C++ call on a name a structured binding declares, or on what a
|
|
* `std::function` returns, links to the method of the type C++ gives it.
|
|
*
|
|
* facebook/rocksdb's MultiCfIteratorImpl writes `for (auto& [cfh, iter] :
|
|
* cfh_iter_pairs_) { iter->Valid(); … }` over a member declared, across two
|
|
* lines, as a `std::vector<std::pair<ColumnFamilyHandle*,
|
|
* std::unique_ptr<Iterator>>>`; a binding's names had no type, so the calls
|
|
* were guessed by the method's name. `iter` is the pair's second element, a
|
|
* `std::unique_ptr<Iterator>`, and `->` reaches `Iterator`'s method. A map's
|
|
* elements pair its key and value; a declaration's (`auto [a, b] = f();`)
|
|
* are what its initializer returns, a `std::pair` or `std::tuple`. And a
|
|
* call through a `std::function<Iterator*()>`, or the other wrappers, has
|
|
* the type the function type returns.
|
|
*/
|
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { CodeGraph } from '../src';
|
|
import type { Node } from '../src/types';
|
|
|
|
const roots: string[] = [];
|
|
afterEach(() => {
|
|
for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
async function indexed(files: Record<string, string>): Promise<CodeGraph> {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-binding-'));
|
|
roots.push(root);
|
|
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);
|
|
}
|
|
return CodeGraph.init(root, { index: true });
|
|
}
|
|
|
|
function callable(cg: CodeGraph, qualifiedName: string): Node {
|
|
const node = [...cg.getNodesByKind('method'), ...cg.getNodesByKind('function')].find((n) => n.qualifiedName === qualifiedName);
|
|
if (!node) throw new Error(`no function or method ${qualifiedName}`);
|
|
return node;
|
|
}
|
|
|
|
/** `calls` edges of a function or method, as `qualifiedName @confidence`, one per call. */
|
|
function calls(cg: CodeGraph, caller: string): string[] {
|
|
return cg
|
|
.getOutgoingEdgesFrom([callable(cg, caller).id], ['calls'])
|
|
.map((e) => `${cg.getNode(e.target)?.qualifiedName} @${(e.metadata as { confidence?: number } | undefined)?.confidence}`)
|
|
.sort();
|
|
}
|
|
|
|
const lines = (...text: string[]): string => [...text, ''].join('\n');
|
|
|
|
// `Valid` is also a namesake class's, so a guess by the name can miss.
|
|
const ITERATORS = lines(
|
|
'#include <memory>',
|
|
'namespace rocksdb {',
|
|
'class ColumnFamilyHandle {};',
|
|
'class IteratorBase {',
|
|
' public:',
|
|
' virtual bool Valid() const { return false; }',
|
|
'};',
|
|
'class Iterator : public IteratorBase {};',
|
|
'class MemTableIterator {',
|
|
' public:',
|
|
' bool Valid() const { return true; }',
|
|
'};',
|
|
'} // namespace rocksdb',
|
|
);
|
|
|
|
describe('C++ structured bindings and function wrappers as receivers', () => {
|
|
it("is the pair element a range-for's binding names, over a member declared across two lines", async () => {
|
|
const cg = await indexed({
|
|
'include/rocksdb/iterator.h': ITERATORS,
|
|
'db/multi_cf_iterator_impl.h': lines(
|
|
'#include "include/rocksdb/iterator.h"',
|
|
'namespace rocksdb {',
|
|
'class MultiCfIteratorImpl {',
|
|
' public:',
|
|
' void SeekToFirst() {',
|
|
' for (auto& [cfh, iter] : cfh_iter_pairs_) {',
|
|
' iter->Valid();',
|
|
' }',
|
|
' }',
|
|
' private:',
|
|
' std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>',
|
|
' cfh_iter_pairs_;',
|
|
'};',
|
|
'} // namespace rocksdb',
|
|
),
|
|
});
|
|
try {
|
|
expect(calls(cg, 'rocksdb::MultiCfIteratorImpl::SeekToFirst')).toEqual(['rocksdb::IteratorBase::Valid @0.9']);
|
|
} finally {
|
|
cg.close();
|
|
}
|
|
});
|
|
|
|
it("is a map's value, and a declaration's pair or tuple element", async () => {
|
|
const cg = await indexed({
|
|
'include/rocksdb/iterator.h': ITERATORS,
|
|
'db/use.cc': lines(
|
|
'#include "include/rocksdb/iterator.h"',
|
|
'namespace rocksdb {',
|
|
'std::pair<ColumnFamilyHandle*, Iterator*> MakePair() { return {nullptr, nullptr}; }',
|
|
'std::tuple<int, Iterator*, bool> MakeTuple() { return {0, nullptr, false}; }',
|
|
'void ByName(const absl::flat_hash_map<std::string, Iterator*>& iters) {',
|
|
' for (const auto& [name, iter] : iters) {',
|
|
' iter->Valid();',
|
|
' }',
|
|
'}',
|
|
'void ByPair() {',
|
|
' auto [cfh, iter] = MakePair();',
|
|
' iter->Valid();',
|
|
'}',
|
|
'void ByTuple() {',
|
|
' const auto& [count, iter, done] = MakeTuple();',
|
|
' iter->Valid();',
|
|
'}',
|
|
'} // namespace rocksdb',
|
|
),
|
|
});
|
|
try {
|
|
for (const caller of ['ByName', 'ByPair', 'ByTuple']) {
|
|
expect(calls(cg, `rocksdb::${caller}`).filter((c) => c.includes('Valid'))).toEqual(['rocksdb::IteratorBase::Valid @0.9']);
|
|
}
|
|
} finally {
|
|
cg.close();
|
|
}
|
|
});
|
|
|
|
it("is what a std::function's function type returns, called or as an auto local's initializer", async () => {
|
|
const cg = await indexed({
|
|
'include/rocksdb/iterator.h': ITERATORS,
|
|
'db/scan.cc': lines(
|
|
'#include "include/rocksdb/iterator.h"',
|
|
'namespace rocksdb {',
|
|
'void Scan(std::function<Iterator*()> create_iterator) {',
|
|
' create_iterator()->Valid();',
|
|
' auto* it = create_iterator();',
|
|
' it->Valid();',
|
|
'}',
|
|
'} // namespace rocksdb',
|
|
),
|
|
});
|
|
try {
|
|
expect(calls(cg, 'rocksdb::Scan')).toEqual(['rocksdb::IteratorBase::Valid @0.85', 'rocksdb::IteratorBase::Valid @0.9']);
|
|
} finally {
|
|
cg.close();
|
|
}
|
|
});
|
|
|
|
it("gives no type from another function's binding, or a template's arguments that aren't a pair's", async () => {
|
|
const cg = await indexed({
|
|
'include/rocksdb/iterator.h': ITERATORS,
|
|
'db/use.cc': lines(
|
|
'#include "include/rocksdb/iterator.h"',
|
|
'namespace rocksdb {',
|
|
'MemTableIterator* iter = nullptr;',
|
|
'std::pair<ColumnFamilyHandle*, Iterator*> MakePair() { return {nullptr, nullptr}; }',
|
|
'// Members in another order than the template parameters.',
|
|
'template <class A, class B> struct Duo { B second; A first; };',
|
|
'Duo<Iterator*, MemTableIterator*> MakeDuo() { return {}; }',
|
|
'void Earlier() {',
|
|
' auto [cfh, iter] = MakePair();',
|
|
'}',
|
|
'void Later() {',
|
|
' iter->Valid();',
|
|
'}',
|
|
'void Unpack() {',
|
|
' auto [p, q] = MakeDuo();',
|
|
' p->Valid();',
|
|
'}',
|
|
'} // namespace rocksdb',
|
|
),
|
|
});
|
|
try {
|
|
expect(calls(cg, 'rocksdb::Later').filter((c) => c === 'rocksdb::IteratorBase::Valid @0.9')).toEqual([]);
|
|
expect(calls(cg, 'rocksdb::Unpack').filter((c) => c === 'rocksdb::IteratorBase::Valid @0.9')).toEqual([]);
|
|
} finally {
|
|
cg.close();
|
|
}
|
|
});
|
|
|
|
it("reads a member's type from the line above only when that line is a type", async () => {
|
|
const cg = await indexed({
|
|
'include/rocksdb/iterator.h': ITERATORS,
|
|
'db/reader.h': lines(
|
|
'#include "include/rocksdb/iterator.h"',
|
|
'namespace rocksdb {',
|
|
'class Reader {',
|
|
' public:',
|
|
' bool Check() const { return it_->Valid(); }',
|
|
' private:',
|
|
' bool flag_ = true;',
|
|
' // A default member initializer continued on the next line.',
|
|
' bool ready_ = flag_ &&',
|
|
' it_;',
|
|
' MemTableIterator* it_ = nullptr;',
|
|
'};',
|
|
'} // namespace rocksdb',
|
|
),
|
|
});
|
|
try {
|
|
expect(calls(cg, 'rocksdb::Reader::Check')).toEqual(['rocksdb::MemTableIterator::Valid @0.9']);
|
|
} finally {
|
|
cg.close();
|
|
}
|
|
});
|
|
|
|
it("gives no type for a call outside the loop, or a binding of something that isn't a pair or tuple", async () => {
|
|
const cg = await indexed({
|
|
'include/rocksdb/iterator.h': ITERATORS,
|
|
'db/use.cc': lines(
|
|
'#include "include/rocksdb/iterator.h"',
|
|
'namespace rocksdb {',
|
|
'struct Point { Iterator* first; Iterator* second; };',
|
|
'void After(const std::vector<std::pair<ColumnFamilyHandle*, Iterator*>>& pairs, MemTableIterator* iter) {',
|
|
' for (const auto& [cfh, iter] : pairs) {',
|
|
' }',
|
|
' iter->Valid();',
|
|
'}',
|
|
'void Aggregate(Point point) {',
|
|
' auto [x, y] = point;',
|
|
' y->Valid();',
|
|
'}',
|
|
'} // namespace rocksdb',
|
|
),
|
|
});
|
|
try {
|
|
expect(calls(cg, 'rocksdb::After').filter((c) => c === 'rocksdb::IteratorBase::Valid @0.9')).toEqual([]);
|
|
expect(calls(cg, 'rocksdb::Aggregate').filter((c) => c === 'rocksdb::IteratorBase::Valid @0.9')).toEqual([]);
|
|
} finally {
|
|
cg.close();
|
|
}
|
|
});
|
|
});
|