fix(cpp): a call to an overloaded free function, like protobuf's ParseWithLimit(limit, data, result), reaches the overload its arguments fit (#2612)

A C++ call to a function outside a class took whichever same-named
function the name strategies ranked first, by file and line proximity,
never by its arguments. Methods already picked their overload by argument
count, string literals and written argument types (cppOverloadFor);
functions never did. protobuf's `ParseWithLimit(limit, data, result)` on an
`int32_t result` reached the `int64_t&` overload beside the call, and
rocksdb's `NewLRUCache(cache_opts)` the overload taking a `size_t
capacity`.

Exact, qualified (global, exact, partial) and macro-namespace matching now
keep their choice of function, then let the call's arguments pick among its
overloads, the functions of its qualified name the call's file can see
(cppFunctionOverload, with cppOverloadFor's count-then-type fit; the
macro-namespace scorer breaks its ties by type). The choice stays on a tie.
- An overload only another source file sees (another `.cc` file's
  `static`, an unnamed namespace's) never competes. When the name strategy
  had picked such a function, which was then rejected, the call now links
  to the overload its file sees: leveldb's `GetLengthPrefixedSlice(&input,
  &key)` reached memtable.cc's static and linked nothing.
- A function hides a class of its name, so a call to a name both have
  reaches the function.
- Only C++ calls: protobuf's Rust FFI calls lost their `extern` targets to
  the C definitions when every language went through it.

Argument types read better too: a parameter named like the function it
belongs to, protobuf's `std::string u64(uint64_t u64)`, was read as the
function's own declarator, so `u64` looked like a `std::string`.

Validated on the cgstd kit against main (627018eb), every changed site
classified by argument count against both targets, ~100 type-decided sites
read by hand:
- leveldb: 5 calls linked that linked nothing.
- fmt: 283 retargeted (139 to an overload that takes that many arguments,
  110 by type, 34 multi-line or defaulted); fmt's `fill_n(it, …)` on an
  iterator of unknown type now takes the `basic_appender` overload, right
  for fmt's usual iterator.
- rocksdb: 101 linked, 1118 retargeted (720 by count, 339 by type).
- protobuf: 29 linked, 941 retargeted (372 by count, 558 by type), e.g. the
  compiler helpers' `QualifiedClassName(enum_, …)` reaching the
  `EnumDescriptor*` overload and `Namespace(d->file())` no longer calling
  itself.
- redis (C): byte-identical.
Full suite: 7188 passed, 0 failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-16 03:32:08 +00:00
committed by GitHub
parent 627018eb25
commit 4a591aeee8
3 changed files with 316 additions and 15 deletions
+1
View File
@@ -101,6 +101,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 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.
- In C++, a method defined by a macro of the same file is now indexed where the file invokes the macro, so calls to it link. protocolbuffers/protobuf's descriptor classes only declare most of their accessors and define them further down with macros like `PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, number, int)`, and its reflection defines `GetInt32`, `SetInt32` and the like by pasting names together in a macro. Those methods had no node at all, so calls like `field->number()`, `descriptor->full_name()` or `reflection->GetInt32(…)` linked to nothing. Calls through what such a method returns, like `field->containing_type()->full_name()`, now link too. A macro defined in another file, and a method declared but defined nowhere in the repository, are still not indexed. Re-index C++ projects after upgrading.
- In C++, an explicit specialization of a function or member function template, like protocolbuffers/protobuf's `template <> bool WireFormatLite::ReadPrimitive<int32_t, WireFormatLite::TYPE_INT32>(…)` or facebook/rocksdb's `template <> int64_t Deserialize<int64_t>(…)`, is now indexed under its template's name, so search finds it and calls link to it. Before, it kept its template arguments in its name, and one with a `::` among them was named after the last part, like `TYPE_INT32>`. A call that writes template arguments, like `Deserialize<int64_t>(src, offset)`, links to the specialization written with the same ones, and otherwise to the template itself, as fmt's `num_bits<unsigned short>()` does; a call that writes none links to a specialization only when nothing else has that name. Re-index C++ projects after upgrading.
- In C++, a call to an overloaded function outside a class now links to the overload its arguments fit, by how many it passes and the types they have where the call writes them, as a call to a method already did: protocolbuffers/protobuf's `ParseWithLimit(limit, data, result)` on an `int32_t result` reaches the overload taking an `int32_t&`, and facebook/rocksdb's `NewLRUCache(cache_opts)` the one taking an `LRUCacheOptions`. Before, the overload nearest the call was taken. Such a call is never linked to an overload only another source file can see, like another `.cc` file's `static` function: it now links to the one its own file sees, where before it often linked to nothing. A call to a name that both a function and a class have reaches the function, which hides the class, and a parameter named like the function it belongs to, like protobuf's `std::string u64(uint64_t u64)`, now has its own type. Re-index C++ projects after upgrading.
- In C++, text inside a string literal is no longer taken for a variable's declaration. That covers the C code protocolbuffers/protobuf's upb generator writes in a raw string (`R"cc( … upb_MiniTableField field = $field_init$; … )cc"`) and log messages like facebook/rocksdb's `"… in WriteBatch handler; …"`. Before, a later call like `field.ctype()` on the function's own `upb::FieldDefPtr field` parameter, or `handler->PutCF(…)` on a `WriteBatch::Handler* handler`, was matched against the type that text seemed to name, so it linked to nothing or to another class's method. Re-index C++ projects after upgrading.
- In C++, a call on an `auto` variable now links to the method of the class C++ finds for the type its initializer gives it, as a call on a variable declared with that type does. The type is read from a cast or smart pointer written out, like facebook/rocksdb's `auto iter = std::unique_ptr<Iterator>(…)` or `auto* mutex = static_cast<InstrumentedMutex*>(arg)`, from `new` or `std::make_unique` of a class, and from what a function or method is declared to return, like the `Iterator*` of `txn->GetIterator(…)`, read to the end of an initializer that goes on to the next line. Before, such a variable took the type of an earlier variable of the same name, or none: in RocksDB, `iter->SeekToFirst()` reached `MemTableRep::Iterator` and `mutex->Lock()` reached `port::Mutex`. A chained call like `cfd->current()->storage_info()` now looks up the class the inner call is declared to return the same way, through a smart pointer too, so protocolbuffers/protobuf's Java generator no longer reaches the C# generator's `EnumGenerator`. A type from outside the project no longer links to a class of the project that shares its name: protobuf's `status.ok()` on an `absl::Status` reached `upb::Status::ok`, and fmtlib/fmt's `os.flush()` on a `std::ostream&` reached fmt's own `ostream::flush`. The same holds for a variable declared in a macro body, or typed by what a function in another file returns, and for a class the index doesn't hold whose name only another class shares: protobuf's `_status.ok()` in its `RETURN_IF_ERROR` macro reached `upb::Status::ok`, and `range.options()` on its generated `DescriptorProto::ExtensionRange` reached `Descriptor::ExtensionRange::options`. A namespace a macro stands for is the project's under either name: facebook/rocksdb declares its classes in `namespace ROCKSDB_NAMESPACE` with `#define ROCKSDB_NAMESPACE rocksdb`, so `db->Put(…)` on a `rocksdb::DB*` links to RocksDB's own `DB::Put`. Re-index C++ projects after upgrading.
- In C++, a call on a data member of the calling method's class, like `versions_->GetColumnFamilySet()` in facebook/rocksdb's `db_impl_write.cc`, now links to the method of the member's type wherever the class is declared. Before, only the call's own file and the header of the same name were read for the member, and RocksDB declares `DBImpl` in `db_impl.h` while its methods are spread over `db_impl_write.cc`, `db_impl_compaction_flush.cc`, `db_impl_open.cc` and more, so such calls were guessed by the member's name. A member declared below an inline method that uses it, one a base class declares, and, in a gtest `TEST_F` or `TEST_P` body, one of the test's fixture, like RocksDB's `db_`, are found the same way, and a member of a class nested in the caller's class is no longer taken for the caller's own. A call on a member of a standard-library type, like `std::atomic<bool> shutting_down_`, no longer links to a project method that only shares the method's name. Re-index C++ projects after upgrading.
@@ -0,0 +1,257 @@
/**
* A C++ call to a free function picks among the function's overloads by its
* arguments, as a call to a method already does: their count, then the
* types they have where the call writes them. The name strategies pick a
* function by where it is, so protobuf's
*
* int32_t result;
* ParseWithLimit(limit, data, result);
*
* reached the `ParseWithLimit(…, int64_t& res)` overload defined nearer the
* call, not the `int32_t&` one. Only overloads the call's file can see
* compete: a `static` one in another source file never does.
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import CodeGraph from '../src/index';
import type { Node } from '../src/types';
const lines = (...l: string[]): string => [...l, ''].join('\n');
describe('a C++ call picks the free-function overload its arguments fit', () => {
let root: string;
let cg: CodeGraph;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-free-overloads-'));
const write = (file: string, text: string): void => {
fs.mkdirSync(path.dirname(path.join(root, 'src', file)), { recursive: true });
fs.writeFileSync(path.join(root, 'src', file), text);
};
// protobuf's varint_shuffle_test.cc: each call sits nearer the overload it doesn't take.
write('varint.cc', lines(
'namespace pb {',
'const char* ParseWithLimit(int rtlimit, const char* data, int32_t& res) { return data; }',
'const char* ParseWithLimit(int rtlimit, const char* data, int64_t& res) { return data; }',
'void Parse32(const char* data) { int32_t result; ParseWithLimit(1, data, result); }',
'int Scale(int x, int y) { return x * y; }',
'int Scale(int x) { return x; }',
'int Twice(int v) { return Scale(v, 2); }',
'void Put(const char* text) {}',
'void Put(int value) {}',
'void PutText() { Put("text"); }',
'template <typename T> void Store(T value) {}',
'void Store(int value) {}',
'void StoreName(const std::string& name) { Store(name); }',
'void StoreCount() { Store(1); }',
'} // namespace pb',
));
// Arguments both overloads fit alike; a namesake in another namespace, no overload.
write('render.cc', lines(
'namespace pb {',
'struct Circle {}; struct Square {}; struct Shape {};',
'void Render(const Circle& c) {}',
'void Render(const Square& s) {}',
'void Draw(const Shape& shape) { Render(shape); }',
'} // namespace pb',
));
write('print.cc', lines(
'namespace a {',
'struct Message { Message(const char* text) {} };',
'void Print(const Message& m) {}',
'void Hello() { Print("hi"); }',
'} // namespace a',
'namespace b {',
'void Print(const char* text) {}',
'} // namespace b',
));
// Qualified calls: from another file, from the overloads' own file, from the global scope.
write('codec.h', lines(
'namespace codec {',
'inline void Encode(const char* text, char* out) {}',
'inline void Encode(long value, char* out) {}',
'inline void EncodeHere(char* out) { codec::Encode(7L, out); }',
'} // namespace codec',
));
write('codec.cc', lines(
'#include "src/codec.h"',
'void EncodeThere(char* out) { codec::Encode(7L, out); }',
'void EncodeGlobal(char* out) { ::codec::Encode(7L, out); }',
));
// A function with only specializations: deduction picks one.
write('serialize.h', lines(
'namespace ns {',
'template <typename T> void Serialize(T val, char* dest);',
'template <> inline void Serialize<char>(char t, char* dest) {}',
'template <> inline void Serialize<long>(long t, char* dest) {}',
'} // namespace ns',
));
write('serialize.cc', lines(
'#include "src/serialize.h"',
'void WriteLong(char* dest) { ns::Serialize(1L, dest); }',
));
// An overload only another source file sees, which the arguments fit
// better: indexed after the one the call sees (`Log`), and before it (`Warn`).
write('log.h', lines(
'namespace lg {',
'struct Status { Status(int code) {} };',
'void Log(const Status& status);',
'void Warn(const Status& status);',
'} // namespace lg',
));
write('log.cc', lines(
'#include "src/log.h"',
'namespace lg {',
'void Log(const Status& status) {}',
'void Warn(const Status& status) {}',
'} // namespace lg',
));
write('log_private.cc', lines(
'namespace lg {',
'static void Log(int level) {}',
'} // namespace lg',
));
write('a_private.cc', lines(
'namespace lg {',
'static void Warn(int level) {}',
'} // namespace lg',
));
write('log_use.cc', lines(
'#include "src/log.h"',
'void LogCode() { lg::Log(3); }',
'void WarnCode() { lg::Warn(3); }',
));
// protobuf's conformance suite: a parameter named like its function.
write('wire.h', lines(
'namespace wire {',
'struct Wire {};',
'Wire Fixed64(uint64_t x);',
'template <typename T> Wire Fixed64(T x) { return Fixed64(static_cast<uint64_t>(x)); }',
'} // namespace wire',
));
write('wire.cc', lines(
'#include "src/wire.h"',
'namespace wire {',
'Wire Fixed64(uint64_t x) { return Wire(); }',
'} // namespace wire',
));
write('wire_use.cc', lines(
'#include "src/wire.h"',
'std::string u64(uint64_t u64) {',
' wire::Fixed64(u64);',
' return "";',
'}',
'struct Counter { Counter(int n) {} int value() const { return 0; } };',
'struct Gauge { int value() const { return 1; } };',
'int count(int n) {',
' Counter count(n);',
' return count.value();',
'}',
));
// A struct and a function of one name: the function hides the struct in a call.
write('stat.h', lines(
'namespace sys {',
'struct stat { long size; };',
'inline int stat(const char* path, struct stat* out) { return 0; }',
'} // namespace sys',
));
write('stat.cc', lines(
'#include "src/stat.h"',
'long FileSize(const char* path) { sys::stat st; sys::stat(path, &st); return st.size; }',
'struct Sized : sys::stat {};',
));
// protobuf's Rust bindings: a Rust call to the C function its `extern` block declares.
write('rust/upb/lib.rs', lines('mod sys;', 'mod wire;'));
write('rust/upb/sys/mod.rs', lines('pub mod wire;'));
write('rust/upb/sys/wire/mod.rs', lines('pub mod wire;'));
write('rust/upb/sys/wire/wire.rs', lines('extern "C" {', ' pub fn upb_encode(msg: *const u8, len: usize) -> i32;', '}'));
write('rust/upb/wire.rs', lines(
'use super::sys::wire::wire::{upb_encode};',
'pub fn encode(msg: *const u8) -> i32 {',
' unsafe { upb_encode(msg, 0) }',
'}',
));
write('upb/wire/encode.c', lines('int upb_encode(const char* msg, unsigned long len) { return 0; }'));
// fmt's namespace, opened by a macro: the overloads take as many arguments.
write('fmtlike.h', lines(
'#define MY_BEGIN_NAMESPACE namespace mylib {',
'#define MY_END_NAMESPACE }',
'MY_BEGIN_NAMESPACE',
'inline void write(char* out, const char* text) {}',
'inline void write(char* out, int value) {}',
'MY_END_NAMESPACE',
));
write('fmtlike.cc', lines(
'#include "src/fmtlike.h"',
'void WriteCount(char* out, int count) { mylib::write(out, count); }',
));
cg = await CodeGraph.init(root, { index: true });
}, 120_000);
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
/** `<qualified name>@<file>:<line>` of what the function `name` calls. */
const calls = (name: string): string[] => {
const caller = cg.getNodesByKind('function').find((n: Node) => n.qualifiedName === name)!;
expect(caller, name).toBeDefined();
return cg.getCallees(caller.id).filter((r) => r.edge.kind === 'calls')
.map((r) => `${r.node.qualifiedName}@${path.basename(r.node.filePath)}:${r.node.startLine}`);
};
it('by the types of its arguments', () => {
expect(calls('pb::Parse32')).toEqual(['pb::ParseWithLimit@varint.cc:2']);
expect(calls('pb::StoreName')).toEqual(['pb::Store@varint.cc:11']);
expect(calls('pb::StoreCount')).toEqual(['pb::Store@varint.cc:12']);
// The parameter, not the function it belongs to; a local declared like a call, as before.
expect(calls('u64')).toEqual(['wire::Fixed64@wire.cc:3']);
expect(calls('count')).toContain('Counter::value@wire_use.cc:6');
});
it('a function, not the struct of its name; the struct where a type is named', () => {
expect(calls('FileSize')).toEqual(['sys::stat@stat.h:3']);
const sized = cg.getNodesByKind('struct').find((n: Node) => n.qualifiedName === 'Sized')!;
expect(cg.getOutgoingEdgesFrom([sized.id], ['extends']).map((e) => `${cg.getNode(e.target)?.kind} ${cg.getNode(e.target)?.qualifiedName}`))
.toEqual(['struct sys::stat']);
});
it('by how many arguments it passes, and its string literals', () => {
expect(calls('pb::Twice')).toEqual(['pb::Scale@varint.cc:5']);
expect(calls('pb::PutText')).toEqual(['pb::Put@varint.cc:8']);
});
it('through a qualified name', () => {
expect(calls('EncodeThere')).toEqual(['codec::Encode@codec.h:3']);
expect(calls('codec::EncodeHere')).toEqual(['codec::Encode@codec.h:3']);
expect(calls('EncodeGlobal')).toEqual(['codec::Encode@codec.h:3']);
expect(calls('WriteCount')).toEqual(['write@fmtlike.h:5']);
});
it('among the specializations deduction chooses from', () => {
expect(calls('WriteLong')).toEqual(['ns::Serialize@serialize.h:4']);
});
it('the one picked by where it is, when the arguments fit both alike', () => {
expect(calls('pb::Draw')).toEqual(['pb::Render@render.cc:4']);
});
it("never another namespace's function of the name", () => {
expect(calls('a::Hello')).toEqual(['a::Print@print.cc:3']);
});
it('only for a C++ call: a Rust call keeps the declaration it names', () => {
const encode = cg.getNodesByKind('function').find((n: Node) => n.name === 'encode')!;
expect(encode).toBeDefined();
expect(cg.getCallees(encode.id).filter((r) => r.edge.kind === 'calls')
.map((r) => `${r.node.name}@${r.node.filePath}:${r.node.startLine}`)).toEqual(['upb_encode@src/rust/upb/sys/wire/wire.rs:2']);
});
it('never an overload only another source file sees', () => {
expect(calls('LogCode')).toEqual(['lg::Log@log.cc:3']);
expect(calls('WarnCode')).toEqual(['lg::Warn@log.cc:4']);
});
});
+58 -15
View File
@@ -7989,7 +7989,8 @@ export function matchByExactName(
}
// Multiple matches - try to narrow down
const bestMatch = findBestMatch(ref, candidates, context);
const nearest = findBestMatch(ref, candidates, context);
const bestMatch = nearest && cppFunctionOverload(nearest, candidates, ref, context);
if (bestMatch && isCrossFileReachable(bestMatch, ref, context)) {
// Lower confidence when the match is from a distant/unrelated module
const proximity = computePathProximity(ref.filePath, bestMatch.filePath);
@@ -8045,7 +8046,7 @@ export function matchByQualifiedName(
if (!chosen) return null;
return {
original: ref,
targetNodeId: chosen.id,
targetNodeId: cppFunctionOverload(chosen, global, ref, context).id,
confidence: global.length === 1 || chosen.filePath === ref.filePath ? 0.95 : 0.85,
resolvedBy: 'qualified-name',
};
@@ -8080,7 +8081,7 @@ export function matchByQualifiedName(
if (ordered[0]!.filePath === ref.filePath) {
return {
original: ref,
targetNodeId: ordered[0]!.id,
targetNodeId: cppFunctionOverload(ordered[0]!, ordered, ref, context).id,
confidence: 0.95,
resolvedBy: 'qualified-name',
};
@@ -8127,7 +8128,7 @@ export function matchByQualifiedName(
if (chosen) {
return {
original: ref,
targetNodeId: chosen.id,
targetNodeId: cppFunctionOverload(chosen, partialCandidates, ref, context).id,
confidence: 0.85,
resolvedBy: 'qualified-name',
};
@@ -9223,6 +9224,15 @@ function matchCppDeclarator(line: string, regex: RegExp): RegExpExecArray | null
return m;
}
/** matchCppDeclarator's first match in `line` from `from` on. */
function matchCppDeclaratorFrom(line: string, regex: RegExp, from: number): RegExpExecArray | null {
const global = new RegExp(regex.source, 'g');
global.lastIndex = from;
const m = global.exec(line);
if (m?.[1] !== undefined) m[1] = cppDeclaratorType(m[1]);
return m;
}
const CPP_DECLARATOR_ANNOTATION_WORD = new RegExp(`^${CPP_DECLARATOR_ANNOTATION}$`);
const CPP_WORD_AT = /[A-Za-z_]\w*/y;
@@ -11169,7 +11179,15 @@ function cppOverloadFor(candidates: Node[], name: string, ref: UnresolvedRef, co
if (!args) return overloads[0]!;
const fits = overloads.map((n) => cppOverloadFit(n, name, args, context));
const top = Math.max(...fits);
const tied = overloads.filter((_, i) => fits[i] === top);
return cppArgumentTypeChoice(overloads.filter((_, i) => fits[i] === top), name, args, ref, context);
}
/**
* Of overloads the call's arguments `args` fit alike by count (in
* preference order), the one the types the arguments have where the call
* reads them fit best (cppArgumentTypeFit); the first on a tie.
*/
function cppArgumentTypeChoice(tied: Node[], name: string, args: string[], ref: UnresolvedRef, context: ResolutionContext): Node {
if (tied.length === 1 || args.length === 0) return tied[0]!;
const types = args.map((arg) => cppArgumentType(arg, ref, context));
if (types.every((t) => t === null)) return tied[0]!;
@@ -11185,6 +11203,25 @@ function cppOverloadFor(candidates: Node[], name: string, ref: UnresolvedRef, co
return best;
}
/**
* The C++ function a call of `chosen`'s name reaches among `candidates`:
* of its overloads — the functions of its qualified name the call's file
* can see (isVisibleAcrossFiles), `chosen` first — the one the call's
* arguments fit (cppOverloadFor). The name strategies pick a function by
* where it is; protobuf's `ParseWithLimit(limit, data, result)` on an
* `int32_t result` is the overload taking an `int32_t&`, not the
* `int64_t&` one nearer the call. A function hides a class of its name, so
* a call is to the function when `chosen` is that class. `chosen` itself
* for anything but a C++ call, or when no function is in sight.
*/
function cppFunctionOverload(chosen: Node, candidates: Node[], ref: UnresolvedRef, context: ResolutionContext): Node {
if (ref.language !== 'cpp' || ref.referenceKind !== 'calls') return chosen;
const overloads = candidates.filter((n) => n.kind === 'function' && n.qualifiedName === chosen.qualifiedName &&
isVisibleAcrossFiles(n, ref, context));
if (overloads.length === 0) return chosen;
return cppOverloadFor(overloads.includes(chosen) ? [chosen, ...overloads.filter((n) => n !== chosen)] : overloads, chosen.name, ref, context);
}
/**
* Of the C++ functions `candidates` a call may reach, the ones the template
* arguments it writes allow. An explicit specialization is named like the
@@ -11651,7 +11688,13 @@ function inferCppReceiverType(
const line = cppCodeOf(cppLiteralsBlanked(text, i + 1, rawLines));
if (!line || !receiverPattern.test(line)) continue;
const declaratorMatch = matchCppDeclarator(line, declaratorRegex);
let declaratorMatch = matchCppDeclarator(line, declaratorRegex);
// A parameter named like the function it belongs to is declared after
// the function's own name on its head: protobuf's `std::string
// u64(uint64_t u64) {` gives `u64` a `uint64_t`, not a `std::string`.
if (declaratorMatch && memberCaller?.name === receiverName && line[declaratorMatch.index + declaratorMatch[0].length] === '(') {
declaratorMatch = matchCppDeclaratorFrom(line, declaratorRegex, declaratorMatch.index + declaratorMatch[0].length) ?? declaratorMatch;
}
if (declaratorMatch) {
const normalized = normalizeCppTypeName(declaratorMatch[1] ?? '');
if (normalized === 'auto') {
@@ -16912,17 +16955,17 @@ function matchCppMacroNamespaced(ref: UnresolvedRef, context: ResolutionContext)
if (effective === target) matches.push(n);
}
const pool = cppSpecializationPool(matches, ref, context);
if (pool.length === 0) return null;
// A declaration outside the tests over one in them; among an overload set,
// the one the call's arguments fit: `fmt::format("{}", v)` is format.h's
// `format(format_string, T&&...)`, not color.h's `format(const text_style&, …)`.
// the one the call's arguments fit, by count, then by type:
// `fmt::format("{}", v)` is format.h's `format(format_string, T&&...)`,
// not color.h's `format(const text_style&, …)`.
const args = pool.length > 1 && ref.referenceKind === 'calls' ? cppCallArguments(ref, name, context) : null;
let best: Node | null = null;
let bestScore = -Infinity;
for (const n of pool) {
const score = (isTestPath(n.filePath) ? -10 : 0) + (args ? cppOverloadFit(n, name, args, context) : 0);
if (score > bestScore) { best = n; bestScore = score; }
}
return best ? { original: ref, targetNodeId: best.id, confidence: 0.8, resolvedBy: 'qualified-name' } : null;
const scores = pool.map((n) => (isTestPath(n.filePath) ? -10 : 0) + (args ? cppOverloadFit(n, name, args, context) : 0));
const top = Math.max(...scores);
const tied = pool.filter((_, i) => scores[i] === top);
const best = args ? cppArgumentTypeChoice(tied, name, args, ref, context) : tied[0]!;
return { original: ref, targetNodeId: best.id, confidence: 0.8, resolvedBy: 'qualified-name' };
}
/** Split `a, f(b, c), d<e, f>` at its top-level commas. */