mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 13:53:01 +08:00
fix(cpp): a call on the member of the pair a map's iterator points at, it->second.Flush(), calls the value's method (#2601)
The extractor records `it->second.Flush()`, `kv.second.Flush()` and `(*it).first.Print()` by their bare names, which the name strategies gave to whichever class had a method of that name — 45 `std::string::c_str()` calls went to googletest's `FilePath::c_str` — and a local such a member initializes (`auto& v = it->second`) had no type. `x.first` / `x.second` (and `->`) on a `std::pair` now type as the pair's elements in C++ expression typing (cppPairMember), so a map's iterator, range-for element or declared pair reaches its key and value, and #2598's bare-name receiver reader also takes `name->second`, `name.first` and `(*name).second`. A member typed by another class template's parameter (`CoreLocalArray<T>::AccessElementAndIndex()`'s `std::pair<T*, size_t>`) goes on as before: only the calling function's own template parameter names no particular class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -95,7 +95,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- In C++, a call on a variable, parameter or member declared as a standard-library or other outside type with a lowercase name, like `std::string`, `std::vector`, `std::set` or `absl::flat_hash_set`, no longer links to a project method that only shares the method's name, even one of a project class in another namespace with the type's name, like your own `util::flat_hash_set`. A variable declared outside the calling function, like another function's or a file's `std::string text`, no longer links its calls to such a class either. In google/leveldb, `saved_key_.clear()` on a `std::string saved_key_;` reached `Slice::clear`, and in protocolbuffers/protobuf, `proto.append(…)` on a `std::string` reached an unrelated `LeftoverBuffer::append`. The same goes for `->` through a raw pointer like `std::string* out`, while `->` through a smart pointer, iterator or optional still links to the type it holds. Comments also no longer pass for a variable's declaration, so more calls now link to the method of the type the variable is really declared with. Re-index C++ projects after upgrading.
|
||||
- In C++, a class written with attribute macros the parser can't read now keeps its members. That covers several macros between `class` and the class name, like protocolbuffers/protobuf's generated `class PROTOBUF_EXPORT PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Any final`, a macro with arguments there, like RocksDB's `struct ALIGN_AS(64U) HandleImpl`, or one before a template specialization, and macros on a member: in front of it (`PROTOBUF_FUTURE_ADD_EARLY_NODISCARD`), between a pointer and its name (`PROTOBUF_NONNULL`), after its parameters or its name (`ABSL_ATTRIBUTE_LIFETIME_BOUND`, google/leveldb's `LOCKS_EXCLUDED(mu_)` and `GUARDED_BY(mu_)`), or alone on a line above a comment ({fmt}'s `FMT_BEGIN_EXPORT`). Before, protobuf's generated message classes were missing and their members, like `GetDescriptor`, `GetReflection` and `unknown_fields`, were indexed as functions outside any class, and leveldb's annotated methods were indexed under the annotation's name, so calls to them linked to nothing. Re-index C++ projects after upgrading.
|
||||
- In C++, a call on a range-based `for` loop's variable, like `suite->SetVerbose(verbose)` inside `for (ConformanceTestSuite *suite : suites)`, now links to the method of the type the loop declares. Before, the loop's declaration was never read, so the call was matched by the variable's name alone: in RocksDB, `log->file()` on a `log::Writer` went to `BlobLogWriter::file`, and a loop variable named like a member of its class was taken for that member. The variable counts only inside its loop. Re-index C++ projects after upgrading.
|
||||
- In C++, a call made with `->` on a variable, parameter or member declared as a `std::unique_ptr`, `std::shared_ptr` or `std::optional`, like `iter->Valid()` on a `std::unique_ptr<Iterator> iter`, now links to that method of the type it holds, or of a class that type inherits from, with the type looked up the way C++ does it, so a class of the same name nested elsewhere is not mistaken for it. Before, the call went to whichever project method shared its name: in facebook/rocksdb, `iter->Valid()` reached `ArenaWrappedDBIter::Valid` and `fs->GetFileSize(…)` on a `std::shared_ptr<FileSystem>` reached a test's stand-in file system, and calls like `it->Next()` were often not linked at all. When what it holds is a template parameter or a standard-library type, like `std::unique_ptr<std::deque<…>>`, or a class that has no method of that name and neither do the classes it derives from, the call no longer links to a project method; a class derived from something that can't be followed, like a `decltype(…)`, may still have it, so that call is matched as before. The same goes for `->` on a container's iterator, like `it->data()` on a `std::deque<Slice>::const_iterator it` or `loc->path_size()` on protocolbuffers/protobuf's `RepeatedPtrField<SourceCodeInfo_Location>::iterator loc`: the call links to that method of the container's element type, and to no project method when the element is a standard-library type like `std::string`. That includes an iterator an `auto` variable gets from a standard-library container's `begin()`, `find()` and the like, or from a standard search like `std::lower_bound`, as in facebook/rocksdb's `auto it = logs_.begin(); it->IsSyncing()`. A call on a dereferenced iterator or pointer, like `(*it)->Visit()` over a `std::vector<Node*>` or `(*it).Weight()` over a `std::vector<Node>`, and on a variable a dereference sets, like `auto first = *nodes.begin()`, links the same way, where before such a call went to whichever class had a method of that name. Re-index C++ projects after upgrading.
|
||||
- In C++, a call made with `->` on a variable, parameter or member declared as a `std::unique_ptr`, `std::shared_ptr` or `std::optional`, like `iter->Valid()` on a `std::unique_ptr<Iterator> iter`, now links to that method of the type it holds, or of a class that type inherits from, with the type looked up the way C++ does it, so a class of the same name nested elsewhere is not mistaken for it. Before, the call went to whichever project method shared its name: in facebook/rocksdb, `iter->Valid()` reached `ArenaWrappedDBIter::Valid` and `fs->GetFileSize(…)` on a `std::shared_ptr<FileSystem>` reached a test's stand-in file system, and calls like `it->Next()` were often not linked at all. When what it holds is a template parameter or a standard-library type, like `std::unique_ptr<std::deque<…>>`, or a class that has no method of that name and neither do the classes it derives from, the call no longer links to a project method; a class derived from something that can't be followed, like a `decltype(…)`, may still have it, so that call is matched as before. The same goes for `->` on a container's iterator, like `it->data()` on a `std::deque<Slice>::const_iterator it` or `loc->path_size()` on protocolbuffers/protobuf's `RepeatedPtrField<SourceCodeInfo_Location>::iterator loc`: the call links to that method of the container's element type, and to no project method when the element is a standard-library type like `std::string`. That includes an iterator an `auto` variable gets from a standard-library container's `begin()`, `find()` and the like, or from a standard search like `std::lower_bound`, as in facebook/rocksdb's `auto it = logs_.begin(); it->IsSyncing()`. A call on a dereferenced iterator or pointer, like `(*it)->Visit()` over a `std::vector<Node*>` or `(*it).Weight()` over a `std::vector<Node>`, and on a variable a dereference sets, like `auto first = *nodes.begin()`, links the same way, where before such a call went to whichever class had a method of that name. So does a call on the key or value a map's iterator points at, like `it->second.Flush()`, `it->second->Flush()` or a range-based `for` loop's `kv.second.Flush()`, and on the `first` or `second` of a `std::pair` the code declares. Re-index C++ projects after upgrading.
|
||||
- In C++, a call a method makes with no receiver, or through `this->`, now links to the method C++ itself would call: one of the method's own class, else one the class inherits from a base class, else one of a class it is nested in. The same goes for a call in a lambda inside the method, and of a class's overloads the one the call's arguments fit is taken. Before, the call was matched by its name alone, so another class's method of that name could win because it was declared nearer or found first: in protocolbuffers/protobuf, every generated message's `Swap` and move assignment calling `InternalSwap(…)` reached `Any::InternalSwap`, and the `GetArena()` messages inherit from `MessageLite` reached an arena helper's `GetArena`, while googletest's `~linked_ptr() { depart(); }`, vendored in facebook/rocksdb, reached `linked_ptr_internal::depart`. Calls C++ doesn't look up in the class are matched as before: a free function's, a call to a parameter or local of that name, and a template's bare call into a base class that depends on its template parameters. Re-index C++ projects after upgrading. Thanks @danusha2345 for reporting the C++ case in #1952.
|
||||
- In C++, a call on the variable of a range-based `for` loop over `auto` elements, like `cfd->Ref()` inside facebook/rocksdb's `for (auto cfd : cfds)`, now links to the method of the type the loop goes over. That type is read from what the loop iterates: a `std::vector`, `std::set`, `absl::Span` or similar container, protocolbuffers/protobuf's `RepeatedPtrField` or RocksDB's `autovector`, whether a variable, parameter or member holds it, an `auto` variable is set to it or a function or method returns it, and a project class you loop over through its `begin()`, like RocksDB's `ColumnFamilySet`. Before, the call was matched by the variable's name, or by an earlier variable of that name: in RocksDB, `blob_file.GetBlobFileNumber()` over a list of `BlobFileGarbage` reached `BlobFileAddition::GetBlobFileNumber`, and in protobuf's Objective-C generator, calls over a list of `std::unique_ptr<MessageGenerator>` reached other generators' methods. A call on an element of a standard-library type, like `file.find(…)` over a `std::vector<std::string>`, no longer links to a project method, and `->` on an element held in a `std::unique_ptr` or `std::shared_ptr` reaches the type it holds. Re-index C++ projects after upgrading.
|
||||
- In C++, a call on a variable, parameter or member declared as one of your classes, like `iter->Valid()` on an `Iterator* iter` or `it.Valid()` on a `const Iterator& it`, now links to that method of the class C++ itself would find for the type where the call is written, or of a class it inherits from. Before, the method was picked by the type's last name, so a class of the same name nested elsewhere could win: in facebook/rocksdb, calls on an `Iterator` reached `MemTableRep::Iterator`, which declares `Valid` and `Next` itself while the `Iterator` the code means inherits them, google/leveldb's reached `SkipList::Iterator`, and in protocolbuffers/protobuf the Java generator's `context->options()` reached the hpb generator's `Context` and `text_parser.Parse(…)` on a `TextFormat::Parser` reached the `.proto` parser. When that class and the classes it derives from have no method of that name, the call no longer links to an unrelated class's method that shares it, as protobuf's `field->full_name()` on a `FieldDescriptor*` did. A type is also found through a `using` declaration the file writes, like protobuf's `using google::protobuf::Descriptor;`, before a class of the same name declared at global scope elsewhere in the repository; this also applies to base classes and to the type a smart pointer holds. A type written from the global scope, like `::upb::Arena& arena`, is looked up there, so a namespace's own `upb::Arena` is not taken for it, nor is an alias of that name the calling function declares. A type written as a member of a class template, like `SkipList<Key, Comparator>::Iterator iter` in leveldb's and RocksDB's skip-list tests, is now read whole, so its calls reach the skip list's own iterator rather than the general `Iterator`, and RocksDB's skip-list memtable iterator no longer links its calls on the skip list's iterator to its own methods; a standard container's `iterator` written this way is the library's, never a class of yours named `iterator`. Re-index C++ projects after upgrading.
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* A C++ call on a member of the `std::pair` a map's iterator points at, like
|
||||
* `it->second.Flush()`, or a range-for's `kv.second.Flush()`, calls a member
|
||||
* of the map's value (or key, for `first`). The extractor records such a
|
||||
* call by its bare name, which went to whichever class had a method of that
|
||||
* name, and a local the member initializes (`auto& v = it->second`) had no
|
||||
* type.
|
||||
*/
|
||||
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-map-iterator-'));
|
||||
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` callees of a function or method, as `qualifiedName`. */
|
||||
function calls(cg: CodeGraph, caller: string): string[] {
|
||||
return cg
|
||||
.getCallees(callable(cg, caller).id)
|
||||
.filter((r) => r.edge.kind === 'calls')
|
||||
.map((r) => r.node.qualifiedName)
|
||||
.sort();
|
||||
}
|
||||
|
||||
const lines = (...l: string[]): string => [...l, ''].join('\n');
|
||||
|
||||
/** `Other` comes first: a call matched by its bare name lands on it. */
|
||||
const TYPES = {
|
||||
'src/types.h': lines(
|
||||
'class Other {',
|
||||
' public:',
|
||||
' void Flush() {}',
|
||||
' void Print() const {}',
|
||||
' bool ok() const { return false; }',
|
||||
' int size() const { return 0; }',
|
||||
'};',
|
||||
'class Writer {',
|
||||
' public:',
|
||||
' void Flush() {}',
|
||||
'};',
|
||||
'class Buffer {',
|
||||
' public:',
|
||||
' void Flush() {}',
|
||||
'};',
|
||||
'class Key {',
|
||||
' public:',
|
||||
' void Print() const {}',
|
||||
'};',
|
||||
'class Status {',
|
||||
' public:',
|
||||
' bool ok() const { return true; }',
|
||||
'};',
|
||||
),
|
||||
};
|
||||
|
||||
describe('a C++ call on the member of the pair a map\'s iterator points at', () => {
|
||||
it('reproduction: `it->second->Flush()` and `it->second.Flush()`', async () => {
|
||||
const cg = await indexed({
|
||||
...TYPES,
|
||||
'src/maps.cc': lines(
|
||||
'#include <map>',
|
||||
'#include <string>',
|
||||
'#include "src/types.h"',
|
||||
'void FlushAll(std::map<std::string, Writer*>& writers) {',
|
||||
' for (auto it = writers.begin(); it != writers.end(); ++it) it->second->Flush();',
|
||||
'}',
|
||||
'void FlushOne(std::map<int, Buffer>& buffers, int k) {',
|
||||
' auto it = buffers.find(k);',
|
||||
' it->second.Flush();',
|
||||
'}',
|
||||
),
|
||||
});
|
||||
try {
|
||||
expect(calls(cg, 'FlushAll')).toEqual(['Writer::Flush']);
|
||||
expect(calls(cg, 'FlushOne')).toEqual(['Buffer::Flush']);
|
||||
} finally {
|
||||
cg.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('a range-for\'s pair, a dereferenced iterator\'s, the key, and a local the member initializes', async () => {
|
||||
const cg = await indexed({
|
||||
...TYPES,
|
||||
'src/walk.cc': lines(
|
||||
'#include <map>',
|
||||
'#include "src/types.h"',
|
||||
'void Each(std::map<int, Buffer>& buffers) {',
|
||||
' for (auto& kv : buffers) kv.second.Flush();',
|
||||
'}',
|
||||
'void Deref(std::map<int, Buffer>& buffers) {',
|
||||
' auto it = buffers.begin();',
|
||||
' (*it).second.Flush();',
|
||||
'}',
|
||||
'void Keys(std::map<Key, int>& counts) {',
|
||||
' auto it = counts.begin();',
|
||||
' it->first.Print();',
|
||||
'}',
|
||||
'void Bound(std::map<int, Buffer>& buffers, int k) {',
|
||||
' auto it = buffers.find(k);',
|
||||
' auto& v = it->second;',
|
||||
' v.Flush();',
|
||||
'}',
|
||||
),
|
||||
});
|
||||
try {
|
||||
expect(calls(cg, 'Each')).toEqual(['Buffer::Flush']);
|
||||
expect(calls(cg, 'Deref')).toEqual(['Buffer::Flush']);
|
||||
expect(calls(cg, 'Keys')).toEqual(['Key::Print']);
|
||||
expect(calls(cg, 'Bound')).toEqual(['Buffer::Flush']);
|
||||
} finally {
|
||||
cg.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('a pair the code declares, and a member of a library type, which has no project method', async () => {
|
||||
const cg = await indexed({
|
||||
...TYPES,
|
||||
'src/pairs.cc': lines(
|
||||
'#include <map>',
|
||||
'#include <string>',
|
||||
'#include <utility>',
|
||||
'#include "src/types.h"',
|
||||
'bool Check() {',
|
||||
' std::pair<Status, std::string> status_and_path;',
|
||||
' return status_and_path.first.ok();',
|
||||
'}',
|
||||
'int Length(std::map<int, std::string>& names) {',
|
||||
' auto it = names.begin();',
|
||||
' return it->second.size();',
|
||||
'}',
|
||||
),
|
||||
});
|
||||
try {
|
||||
expect(calls(cg, 'Check')).toEqual(['Status::ok']);
|
||||
expect(calls(cg, 'Length')).toEqual([]);
|
||||
} finally {
|
||||
cg.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('a member another class template\'s parameter types goes on as before', async () => {
|
||||
const cg = await indexed({
|
||||
'util/core_local.h': lines(
|
||||
'#include <cstddef>',
|
||||
'#include <utility>',
|
||||
'template <typename T>',
|
||||
'class CoreLocalArray {',
|
||||
' public:',
|
||||
' std::pair<T*, size_t> AccessElementAndIndex() const { return {nullptr, 0}; }',
|
||||
'};',
|
||||
),
|
||||
'util/cache.cc': lines(
|
||||
'#include "util/core_local.h"',
|
||||
'class ZSTDCachedData {',
|
||||
' public:',
|
||||
' int GetUncompressData(long idx) { return 0; }',
|
||||
'};',
|
||||
'class Rep {',
|
||||
' public:',
|
||||
' int Get() {',
|
||||
' auto p = per_core_.AccessElementAndIndex();',
|
||||
' return p.first->GetUncompressData(0);',
|
||||
' }',
|
||||
' private:',
|
||||
' CoreLocalArray<ZSTDCachedData> per_core_;',
|
||||
'};',
|
||||
),
|
||||
});
|
||||
try {
|
||||
expect(calls(cg, 'Rep::Get')).toEqual(['CoreLocalArray::AccessElementAndIndex', 'ZSTDCachedData::GetUncompressData']);
|
||||
} finally {
|
||||
cg.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('a call on `this` is still the class\'s own, whatever its members are named', async () => {
|
||||
const cg = await indexed({
|
||||
...TYPES,
|
||||
'src/holder.cc': lines(
|
||||
'#include "src/types.h"',
|
||||
'class Holder {',
|
||||
' public:',
|
||||
' void Flush() {}',
|
||||
' void Run() { this->Flush(); }',
|
||||
'};',
|
||||
),
|
||||
});
|
||||
try {
|
||||
expect(calls(cg, 'Holder::Run')).toEqual(['Holder::Flush']);
|
||||
} finally {
|
||||
cg.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -9862,9 +9862,12 @@ function cppExpressionType(expr: string, at: UnresolvedRef, context: ResolutionC
|
||||
const created = /^new\s+((?:::\s*)?[A-Za-z_][\w:]*(?:\s*<[^;{}()]*>)?)\s*(?:[({[][\s\S]*)?$/.exec(text);
|
||||
if (created) return { ...callers, text: `${created[1]!.trim()}*` };
|
||||
if (!text.endsWith(')')) {
|
||||
// `compact_->sub_compact_states`: a data member of the object's class.
|
||||
// `compact_->sub_compact_states`: a data member of the object's class;
|
||||
// `it->second` through a map's iterator: the member of the `std::pair`.
|
||||
const field = /^(.+?)\s*(\.|->)\s*([A-Za-z_]\w*)$/.exec(text);
|
||||
const object = field && cppMemberObject(field[1]!, field[2]!, at, context, depth);
|
||||
const element = object && cppPairMember(object, field![3]!, at, context);
|
||||
if (element) return element;
|
||||
const cls = object && cppWrittenClass(object, at, context);
|
||||
return cls ? cppDataMemberType(cls.qualifiedName, field![3]!, context) : null;
|
||||
}
|
||||
@@ -10790,25 +10793,34 @@ function cppUtf8Column(line: string, column: number): number | null {
|
||||
return bytes === column && index !== column ? index : null;
|
||||
}
|
||||
|
||||
/** A call on a dereferenced variable, up to the member's name: `(*it)->`, `(*node).`. */
|
||||
const CPP_DEREFERENCED_RECEIVER = /^\(\s*\*\s*([A-Za-z_]\w*)\s*\)\s*(->|\.)\s*(?:template\s+)?/;
|
||||
/**
|
||||
* A call's receiver the extractor drops, up to the member's name: a
|
||||
* dereferenced variable (`(*it)->`), the member of a pair a variable holds
|
||||
* or points at (`it->second.`, `entry.first->`), or both (`(*it).second.`).
|
||||
* Groups: the receiver, the operator.
|
||||
*/
|
||||
const CPP_UNSPELLED_RECEIVER = /^((?:\(\s*\*\s*[A-Za-z_]\w*\s*\))(?:\s*(?:->|\.)\s*(?:first|second)\b)?|[A-Za-z_]\w*\s*(?:->|\.)\s*(?:first|second)\b)\s*(->|\.)\s*(?:template\s+)?/;
|
||||
|
||||
/**
|
||||
* The variable a C++ call the extractor recorded by its bare name is made on
|
||||
* through a dereference, with the operator — `it` and `->` for
|
||||
* `(*it)->Visit()` — read at the call's column, where its receiver starts.
|
||||
* Null for any other call, `(*this).Visit()` included (cppImplicitReceiver's).
|
||||
* The receiver a C++ call the extractor recorded by its bare name is made
|
||||
* on, when it is one CPP_UNSPELLED_RECEIVER reads, with the operator — `(*it)`
|
||||
* and `->` for `(*it)->Visit()`, `it->second` and `.` for
|
||||
* `it->second.Flush()` — read at the call's column, where the receiver
|
||||
* starts. Null for any other call: on `(*this)` (cppImplicitReceiver's), or
|
||||
* on a receiver the extractor spells.
|
||||
*/
|
||||
function cppDereferencedReceiver(ref: UnresolvedRef, context: ResolutionContext): { name: string; op: string } | null {
|
||||
function cppUnspelledReceiver(ref: UnresolvedRef, context: ResolutionContext): { expression: string; op: string } | null {
|
||||
const line = (context.getFileLines?.(ref.filePath) ?? context.readFile(ref.filePath)?.split(/\r?\n/))?.[ref.line - 1];
|
||||
if (line === undefined) return null;
|
||||
const at = (column: number): { name: string; op: string } | null => {
|
||||
const at = (column: number): { expression: string; op: string } | null => {
|
||||
const text = line.slice(column);
|
||||
const receiver = CPP_DEREFERENCED_RECEIVER.exec(text);
|
||||
if (!receiver || receiver[1] === 'this') return null;
|
||||
const receiver = CPP_UNSPELLED_RECEIVER.exec(text);
|
||||
if (!receiver || receiver[1]!.replace(/\s+/g, '') === '(*this)') return null;
|
||||
const rest = text.slice(receiver[0].length);
|
||||
if (!rest.startsWith(ref.referenceName) || /[\w$]/.test(rest.charAt(ref.referenceName.length))) return null;
|
||||
return cppArgumentListFollows(rest, ref.referenceName.length) ? { name: receiver[1]!, op: receiver[2]! } : null;
|
||||
return cppArgumentListFollows(rest, ref.referenceName.length)
|
||||
? { expression: receiver[1]!.replace(/\s+/g, ''), op: receiver[2]! }
|
||||
: null;
|
||||
};
|
||||
const found = at(Math.max(0, ref.column));
|
||||
if (found || !/[^\x00-\x7f]/.test(line)) return found;
|
||||
@@ -10817,28 +10829,35 @@ function cppDereferencedReceiver(ref: UnresolvedRef, context: ResolutionContext)
|
||||
}
|
||||
|
||||
/**
|
||||
* A C++ call the extractor recorded by its bare name on a dereferenced
|
||||
* variable — `(*it)->Visit()` over a `std::vector<Node*>`, `(*vit).Weight()`
|
||||
* over a `std::vector<Node>`, `(*node).Weight()` on a `Node*` — calls a
|
||||
* member of the class the dereferenced value has (cppMemberObject), looked
|
||||
* up as C++ looks it up where that type is written. By its bare name the
|
||||
* call went to whichever class had a method of that name.
|
||||
* A C++ call the extractor recorded by its bare name on a receiver it drops
|
||||
* (cppUnspelledReceiver) — `(*it)->Visit()` over a `std::vector<Node*>`,
|
||||
* `(*node).Weight()` on a `Node*`, `it->second.Flush()` through a map's
|
||||
* iterator — calls a member of the class that receiver has
|
||||
* (cppMemberObject), looked up as C++ looks it up where that type is
|
||||
* written. By its bare name the call went to whichever class had a method of
|
||||
* that name.
|
||||
* - The member of that class, or of a class it derives from.
|
||||
* - null when no project method is called: a library type (`(*it).size()`
|
||||
* over a `std::vector<std::string>`), a template parameter's, or a class
|
||||
* that surely lacks the method (cppLacksMethod).
|
||||
* over a `std::vector<std::string>`, `it->second.size()` on a map of
|
||||
* strings), a template parameter's, or a class that surely lacks the
|
||||
* method (cppLacksMethod).
|
||||
* - undefined when the call is on no such receiver, or its type can't be
|
||||
* read or placed: the name strategies decide, as before.
|
||||
*/
|
||||
function matchCppDereferencedCall(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null | undefined {
|
||||
const receiver = cppDereferencedReceiver(ref, context);
|
||||
function matchCppUnspelledReceiverCall(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null | undefined {
|
||||
const receiver = cppUnspelledReceiver(ref, context);
|
||||
if (!receiver) return undefined;
|
||||
const object = cppMemberObject(`(*${receiver.name})`, receiver.op, ref, context, 0);
|
||||
const object = cppMemberObject(receiver.expression, receiver.op, ref, context, 0);
|
||||
if (!object || isCppPointerType(object.text)) return undefined;
|
||||
if (isCppLibraryType(object.text, context)) return null;
|
||||
const found = cppClassWritten(object.text, object, ref, context);
|
||||
if (found === undefined) return undefined;
|
||||
if (found === null) return null;
|
||||
// A template parameter's type names no particular class when the calling
|
||||
// function writes it; one another template writes is what the call's
|
||||
// specialization makes it — rocksdb's `CoreLocalArray<T>` returns a
|
||||
// `std::pair<T*, size_t>` from `AccessElementAndIndex()` — so the call
|
||||
// goes on as before.
|
||||
if (found === null) return isCppTemplateParameterType(object) && object.scope !== context.getNodeById?.(ref.fromNodeId)?.qualifiedName ? undefined : null;
|
||||
if (!found.exact || !CPP_CLASS_KINDS.has(found.node.kind)) return undefined;
|
||||
const method = cppMethodOf(found.node, ref.referenceName, ref, context);
|
||||
if (method) return { original: ref, targetNodeId: method.id, confidence: 0.9, resolvedBy: 'instance-method' };
|
||||
@@ -11770,6 +11789,19 @@ function cppBindingElements(type: CppWrittenType, at: UnresolvedRef, context: Re
|
||||
return cppTemplateArguments(followed.text)?.map((text) => ({ ...followed, text: cppWithoutNullability(text) })) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The member `first` or `second` of a `std::pair` of the type `pair`, as a
|
||||
* map's iterator points at one: its first or second element. Null for any
|
||||
* other member or type.
|
||||
*/
|
||||
function cppPairMember(pair: CppWrittenType, member: string, at: UnresolvedRef, context: ResolutionContext): CppWrittenType | null {
|
||||
const index = member === 'first' ? 0 : member === 'second' ? 1 : -1;
|
||||
const followed = index >= 0 ? cppFollowWrittenAliases(pair, at, context) : null;
|
||||
if (!followed || cppTypeSegments(followed.text)?.join('::') !== 'std::pair') return null;
|
||||
const element = cppTemplateArguments(followed.text)?.[index];
|
||||
return element ? { ...followed, text: cppWithoutNullability(element) } : null;
|
||||
}
|
||||
|
||||
/** Where the statement that `text` begins ends: its first `;` outside brackets, or -1. */
|
||||
function cppStatementEnd(text: string): number {
|
||||
let depth = 0;
|
||||
@@ -16436,13 +16468,14 @@ function matchReferenceInner(
|
||||
if (prefixed !== undefined) return prefixed;
|
||||
}
|
||||
|
||||
// A C++ call on a dereferenced variable, which the extractor records by its
|
||||
// bare name — `(*it)->Visit()` — is a call on what the variable points at,
|
||||
// or on nothing of the project's: the strategies below would take any
|
||||
// `Visit`, even a free function of that name.
|
||||
// A C++ call on a dereferenced variable or a pair's member, which the
|
||||
// extractor records by its bare name — `(*it)->Visit()`,
|
||||
// `it->second.Flush()` — is a call on what that receiver is, or on nothing
|
||||
// of the project's: the strategies below would take any `Visit`, even a
|
||||
// free function of that name.
|
||||
if (ref.language === 'cpp' && ref.referenceKind === 'calls' && /^[A-Za-z_]\w*$/.test(ref.referenceName)) {
|
||||
const dereferenced = nmTimedT('cppDeref', ref, () => matchCppDereferencedCall(ref, context));
|
||||
if (dereferenced !== undefined) return dereferenced;
|
||||
const unspelled = nmTimedT('cppUnspelled', ref, () => matchCppUnspelledReceiverCall(ref, context));
|
||||
if (unspelled !== undefined) return unspelled;
|
||||
}
|
||||
|
||||
// Try strategies in order of confidence
|
||||
|
||||
Reference in New Issue
Block a user