fix(c,cpp): a name in C or C++ code outside tests links to what the file's own includes declare (#2684)

PR #2675 narrowed a C or C++ test's same-named candidates to the ones its
translation unit declares. Production code now gets the same rule:
protobuf's Python extension called the copies of upb's C API that the PHP
extension bundles in php-upb.c, and its Java generator's calls of the free
HasHasbit() went to ImmutableFieldGenerator::HasHasbit, a method beside the
caller.

Refinements the wider scope needed:
- a source implements the same-stem header beside it, when it includes one:
  pyext/message.cc is no implementation of google/protobuf/message.h;
- a header's own overloads reach the definitions its source holds:
  conformance/binary_wireformat.h's template Varint calls the .cc's;
- narrowing never leaves a call with only functions its arguments can't
  fill, past one outside the unit that they can: glog's three-argument
  GetStackTrace(...) is not utilities.h's GetStackTrace(). This also mends
  9 breakpad unittest calls #2675 had narrowed the same way;
- nor, for a C call, to definitions only a flag nothing sets compiles,
  past one the build without it does: that stays #2615's choice.
A file-level "call" (a macro, or a declaration read as a call) is left as
it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-19 22:12:26 +00:00
committed by GitHub
parent c58f0417c6
commit 32b839acae
3 changed files with 451 additions and 50 deletions
+1
View File
@@ -129,6 +129,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In C++, an alias that names a class through a class derived from the one that declares it, like protocolbuffers/protobuf's `using ConformanceRequestSetting = BinaryAndJsonConformanceSuite::ConformanceRequestSetting;`, now leads to that class, `ConformanceTestSuite::ConformanceRequestSetting`. Before, an alias was followed only to a class declared under exactly the name it spells, so objects created through this one linked to the alias rather than the class. Re-index C++ projects after upgrading.
- In C++, a name written in a googletest `TEST_F` or `TEST_P` body now finds the test fixture's nested types, those of its base classes included, before anything in the surrounding namespace, as C++ does for the member function such a body is: facebook/rocksdb's `new TestUserDefinedIndexFactory()` links to the class nested in the fixture's base. A class declared inside a test body, or inside any function, is now only found from that function, so the local classes many RocksDB tests each declare under one name, like `FakeWF` or `MyManager`, no longer stand in for one another, and a call like `fwf->SetIOError(true)` reaches the `FakeWF` its own test declares. Re-index C++ projects after upgrading.
- In C and C++ tests, a name that several files declare now links to the one the test's own includes declare, when there is one: protocolbuffers/protobuf's C# generator tests call the `TestSourceDir()` that `googletest.h` declares, not another test helper's they never include; its upb tests call `upb_Arena_New()` and the rest of upb's C API in upb's own files, not the copies bundled for PHP and Ruby, which had left those calls unlinked; and facebook/rocksdb's C API test reaches `rocksdb_open` and its siblings in `db/c.cc`. Re-index C and C++ projects after upgrading.
- In C and C++ code outside tests too, a name that several files declare now links to the one the file's own includes declare, when there is one: protocolbuffers/protobuf's Python extension calls functions like `upb_Encode()` in upb's own files rather than the copies bundled for PHP, and facebook/rocksdb's stress tool calls the `StringToHex()` its own header declares rather than `LDBCommand::StringToHex`. Re-index C and C++ projects after upgrading.
- In C++, a declaration continued on a line that starts with `*`, like facebook/rocksdb's `const auto& el =` over `*reinterpret_cast<std::shared_ptr<EventListener>*>(jhandle);`, is now read, so calls such as `el->OnFlushCompleted(…)` link to `EventListener`'s methods; such a line used to be skipped as part of a comment. Code commented out in a `/* … */` block, and the text of a string literal, like protocolbuffers/protobuf's `[[deprecated("Please use SpaceUsedLong() instead")]]`, are no longer read as declarations, which had sent a call to the wrong method or to none. Re-index C++ projects after upgrading.
- In C++, a call with a comparison or a shift among its arguments, like protocolbuffers/protobuf's `p->Emit({{"declare", has_bit_word_index < 0 ? … : ""}, …}, "…")` or `WriteVarint(num << 3, s)`, is now read with the right number of arguments, so it links to the overload that fits rather than one taking fewer. A parameter or variable whose type has template arguments is now read even after another such parameter on the same line, like facebook/rocksdb's `const autovector<uint32_t>& cf_ids`, or with empty ones, like fmt's `parse_context<>& ctx`: calls on it link to that class's methods, and calls on a `std::vector` declared that way no longer link to a project class's method of the same name. Re-index C++ projects after upgrading.
- In C++, a call passing `nullptr` now links to the overload that takes a `std::nullptr_t`, as C++ picks it, rather than one taking a pointer, and a call no longer links to an overload declared `= delete`, which no call that compiles can reach: protocolbuffers/protobuf's `ptr.reset(nullptr)` on an `Arena::UniquePtr` reaches `reset(std::nullptr_t)`, and `field.Add("foo")` on a `RepeatedPtrField` reaches `Add(Element&&)` instead of the deleted `Add(const Element&)`. Re-index C++ projects after upgrading.
+368
View File
@@ -0,0 +1,368 @@
/**
* A C or C++ name is one its translation unit declares, in production code
* as in tests (cpp-test-translation-unit.test.ts). Of several same-named
* candidates, what the file's includes declare goes first: protobuf's Python
* extension calls `upb_Encode()` from upb/wire/encode.h, not the copy the PHP
* extension bundles in php-upb.c; its Java generator calls the `HasHasbit()`
* helpers.h declares, not `ImmutableFieldGenerator::HasHasbit`, a method
* beside the caller. A source implements the header beside it (the
* Python extension's pyext/message.cc is no implementation of
* google/protobuf/message.h), a header's inline overload reaches the
* definition its own source holds, and a call keeps a function outside the
* unit that takes its arguments over one inside that can't (glog's
* three-argument `GetStackTrace(…)` is not utilities.h's `GetStackTrace()`).
*/
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-tu-'));
const files: Record<string, string> = {
// upb's encoder, and the copy the PHP extension's amalgamation carries.
'upb/wire/encode.h': `#ifndef UPB_WIRE_ENCODE_H_
#define UPB_WIRE_ENCODE_H_
#include <stddef.h>
int upb_Encode(const void* msg, int options, char** buf, size_t* size);
#endif
`,
'upb/wire/encode.c': `#include "upb/wire/encode.h"
int upb_Encode(const void* msg, int options, char** buf, size_t* size) {
*size = 0;
return 0;
}
`,
'php/ext/google/protobuf/php-upb.h': `#include <stddef.h>
int upb_Encode(const void* msg, int options, char** buf, size_t* size);
`,
'php/ext/google/protobuf/php-upb.c': `#include "php-upb.h"
int upb_Encode(const void* msg, int options, char** buf, size_t* size) {
*size = 0;
return 0;
}
`,
'python/message.c': `#include "upb/wire/encode.h"
int PyUpb_Message_Serialize(const void* msg) {
char* buf;
size_t size;
return upb_Encode(msg, 0, &buf, &size);
}
`,
// A free function the Java generator's helpers.h declares, and a field
// generator's method of the same name beside the caller.
'src/google/protobuf/compiler/java/helpers.h': `namespace google {
namespace protobuf {
class FieldDescriptor;
namespace compiler {
namespace java {
inline bool HasHasbit(const FieldDescriptor* descriptor) {
return descriptor != nullptr;
}
} // namespace java
} // namespace compiler
} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/compiler/java/full/field_generator.h': `namespace google {
namespace protobuf {
class FieldDescriptor;
namespace compiler {
namespace java {
class ImmutableFieldGenerator {
public:
bool HasHasbit() const;
private:
const FieldDescriptor* descriptor_;
};
} // namespace java
} // namespace compiler
} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/compiler/java/full/field_generator.cc': `#include "google/protobuf/compiler/java/full/field_generator.h"
#include "google/protobuf/compiler/java/helpers.h"
namespace google {
namespace protobuf {
namespace compiler {
namespace java {
bool ImmutableFieldGenerator::HasHasbit() const {
return ::google::protobuf::compiler::java::HasHasbit(descriptor_);
}
} // namespace java
} // namespace compiler
} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/compiler/java/full/enum_field.cc': `#include "google/protobuf/compiler/java/helpers.h"
#include "google/protobuf/compiler/java/full/field_generator.h"
namespace google {
namespace protobuf {
namespace compiler {
namespace java {
namespace {
int SetEnumVariables(const FieldDescriptor* descriptor) {
if (HasHasbit(descriptor)) {
return 1;
}
return 0;
}
} // namespace
} // namespace java
} // namespace compiler
} // namespace protobuf
} // namespace google
`,
// A header's template overload calls the one its own source defines.
'conformance/binary_wireformat.h': `#include <cstdint>
#include <string>
namespace google {
namespace protobuf {
namespace conformance {
using Wire = std::string;
Wire Varint(uint64_t x);
// Overloads to avoid forcing sign/size conversion warnings on call-sites.
template <typename T>
Wire Varint(T x) {
return Varint(static_cast<uint64_t>(x));
}
} // namespace conformance
} // namespace protobuf
} // namespace google
`,
'conformance/binary_wireformat.cc': `#include "conformance/binary_wireformat.h"
namespace google {
namespace protobuf {
namespace conformance {
Wire Varint(uint64_t x) {
return std::string(1, static_cast<char>(x));
}
} // namespace conformance
} // namespace protobuf
} // namespace google
`,
// glog's three-parameter GetStackTrace is declared in stacktrace.h and
// defined in a header stacktrace.cc includes; utilities.h declares a
// GetStackTrace() of no parameters.
'src/stacktrace.h': `namespace google {
namespace glog_internal_namespace_ {
int GetStackTrace(void** result, int max_depth, int skip_count);
} // namespace glog_internal_namespace_
} // namespace google
`,
'src/stacktrace_generic-inl.h': `#include "stacktrace.h"
namespace google {
namespace glog_internal_namespace_ {
int GetStackTrace(void** result, int max_depth, int skip_count) {
return 0;
}
} // namespace glog_internal_namespace_
} // namespace google
`,
'src/stacktrace.cc': `#include "stacktrace.h"
#include "stacktrace_generic-inl.h"
`,
'src/utilities.h': `#include <string>
namespace google {
std::string GetStackTrace();
} // namespace google
`,
'src/utilities.cc': `#include "utilities.h"
namespace google {
std::string GetStackTrace() {
return "";
}
} // namespace google
`,
'src/logging.cc': `#include <string>
#include "stacktrace.h"
#include "utilities.h"
namespace google {
using namespace glog_internal_namespace_;
int RecordCrashReason(void** stack) {
std::string trace = GetStackTrace();
int depth = GetStackTrace(stack, 32, 4);
return depth + static_cast<int>(trace.size());
}
} // namespace google
`,
// The Python extension's message.cc implements pyext/message.h beside
// it, though it includes google/protobuf/message.h too.
'src/google/protobuf/message.h': `namespace google {
namespace protobuf {
class Message {
public:
virtual ~Message() {}
};
} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/any.pb.h': `#include "google/protobuf/message.h"
namespace google {
namespace protobuf {
class Any : public Message {
public:
void MergeFrom(const Any& from) {}
};
} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/descriptor.pb.h': `#include "google/protobuf/message.h"
namespace google {
namespace protobuf {
class FileDescriptorSet : public Message {
public:
void CopyFrom(const FileDescriptorSet& from);
};
} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/descriptor.pb.cc': `#include "google/protobuf/descriptor.pb.h"
namespace google {
namespace protobuf {
void FileDescriptorSet::CopyFrom(const FileDescriptorSet& from) {
if (&from == this) return;
MergeFrom(from);
}
} // namespace protobuf
} // namespace google
`,
'python/google/protobuf/pyext/message.h': `namespace google {
namespace protobuf {
namespace python {
namespace cmessage {
int MergeFrom(void* self, void* arg);
} // namespace cmessage
} // namespace python
} // namespace protobuf
} // namespace google
`,
'python/google/protobuf/pyext/message.cc': `#include "google/protobuf/message.h"
#include "google/protobuf/pyext/message.h"
namespace google {
namespace protobuf {
namespace python {
namespace cmessage {
int MergeFrom(void* self, void* arg) { return 0; }
} // namespace cmessage
} // namespace python
} // namespace protobuf
} // namespace google
`,
};
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 });
});
/** `qualifiedName (file)` of every call the file's symbols make. */
const callsFrom = (file: string): string[] => {
const ids = cg.getNodesInFile(file).map((n) => n.id);
return cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'calls').map((e) => {
const target = cg.getNode(e.target)!;
return `${target.qualifiedName} (${target.filePath})`;
});
};
describe('a C/C++ reference links what its translation unit declares', () => {
it('calls the C function its includes declare, not a bundled copy', () => {
const calls = callsFrom('python/message.c');
expect(calls).toContain('upb_Encode (upb/wire/encode.c)');
expect(calls.some((c) => c.includes('php-upb'))).toBe(false);
});
it('calls the free function its includes declare, not a class\'s method beside it', () => {
const calls = callsFrom('src/google/protobuf/compiler/java/full/enum_field.cc');
expect(calls).toContain('google::protobuf::compiler::java::HasHasbit (src/google/protobuf/compiler/java/helpers.h)');
expect(calls.some((c) => c.includes('ImmutableFieldGenerator'))).toBe(false);
});
it('lets a header\'s overload call the definition its own source holds', () => {
const calls = callsFrom('conformance/binary_wireformat.h');
expect(calls).toContain('google::protobuf::conformance::Varint (conformance/binary_wireformat.cc)');
});
it('keeps a call off a function its arguments cannot fill', () => {
const calls = callsFrom('src/logging.cc');
expect(calls).toContain('google::GetStackTrace (src/utilities.cc)');
expect(calls).toContain('google::glog_internal_namespace_::GetStackTrace (src/stacktrace_generic-inl.h)');
});
it('reads a source as implementing the header beside it, not a namesake elsewhere', () => {
const calls = callsFrom('src/google/protobuf/descriptor.pb.cc');
expect(calls.some((c) => c.includes('pyext/message.cc'))).toBe(false);
});
});
+82 -50
View File
@@ -1617,38 +1617,34 @@ const CPP_STEM_HEADERS = new WeakMap<ResolutionContext, Map<string, string[]>>()
/**
* The headers a C or C++ file includes under its own name
* (`conformance_test.cc` → `conformance_test.h`): the declarations its
* definitions implement.
* definitions implement. One beside it, when it includes one, is the only
* such header: protobuf's pyext/message.cc implements pyext/message.h, not
* the google/protobuf/message.h it also includes; fmt's src/format.cc, with
* none beside it, implements include/fmt/format.h.
*/
function cppSameStemHeaders(filePath: string, context: ResolutionContext): string[] {
let memo = CPP_STEM_HEADERS.get(context);
if (!memo) CPP_STEM_HEADERS.set(context, (memo = new Map()));
let headers = memo.get(filePath);
if (!headers) {
headers = [];
const found: string[] = [];
const stem = fileStem(filePath);
for (const include of context.getNodesInFile(filePath)) {
if (include.kind !== 'import' || fileStem(include.name) !== stem) continue;
const header = cppIncludedFile(include, context);
if (header && header !== filePath && fileStem(header) === stem) headers.push(header);
if (header && header !== filePath && fileStem(header) === stem) found.push(header);
}
const dir = filePath.slice(0, filePath.lastIndexOf('/') + 1);
const beside = found.filter((header) => header.slice(0, header.lastIndexOf('/') + 1) === dir);
headers = beside.length > 0 ? beside : found;
memo.set(filePath, headers);
}
return headers;
}
const TEST_FILES = new WeakMap<ResolutionContext, Map<string, boolean>>();
const TEST_SUITE_FILES = new WeakMap<ResolutionContext, Map<string, boolean>>();
/** isTestPath, memoized: it is asked of every reference with an ambiguous C or C++ name. */
function isTestFile(filePath: string, context: ResolutionContext): boolean {
let memo = TEST_FILES.get(context);
if (!memo) TEST_FILES.set(context, (memo = new Map()));
let hit = memo.get(filePath);
if (hit === undefined) memo.set(filePath, (hit = isTestPath(filePath)));
return hit;
}
/** isTestSuitePath, memoized: it is asked of every candidate of such a name. */
/** isTestSuitePath, memoized: it is asked of every candidate of an ambiguous C or C++ name. */
function isTestSuiteFile(filePath: string, context: ResolutionContext): boolean {
let memo = TEST_SUITE_FILES.get(context);
if (!memo) TEST_SUITE_FILES.set(context, (memo = new Map()));
@@ -1675,8 +1671,10 @@ function isInCppTranslationUnit(candidate: Node, ref: UnresolvedRef, context: Re
((candidate.kind === 'function' || candidate.kind === 'variable' || candidate.kind === 'constant') &&
isStaticCFunction(candidate, context))) return false;
const suite = isTestSuiteFile(candidate.filePath, context);
return cppSameStemHeaders(candidate.filePath, context)
.some((header) => (!suite || isTestSuiteFile(header, context)) && cppIncluders(header, context).has(ref.filePath));
// (The header may be the reference's own: json.h's inline overloads call
// the ones json.cc defines.)
return cppSameStemHeaders(candidate.filePath, context).some((header) => (!suite || isTestSuiteFile(header, context)) &&
(header === ref.filePath || cppIncluders(header, context).has(ref.filePath)));
}
/**
@@ -1701,29 +1699,49 @@ function isCppNameAloneTarget(n: Node, ref: UnresolvedRef, context: ResolutionCo
}
/**
* Same-named candidates for a C or C++ reference in a test, narrowed to the
* ones its translation unit declares, when there are any. A test is a
* program of its own, so the name it uses is the one its includes declare:
* protobuf's csharp generator tests call `TestSourceDir()`, which
* googletest.h, an include of theirs, declares, and test_util2.h's
* `TestUtil::TestSourceDir`, a test suite none of them includes, took the
* calls; a test of the hpb backend calls upb's `upb_Arena_New()` from
* upb/mem/arena.h, not the copies php-upb.h and ruby-upb.h bundle. For a
* name the reference writes alone (`byName`), only what the name alone can
* mean counts (isCppNameAloneTarget), and a call the extractor recorded
* without the receiver it is written on (`columns()[0].value()`) is left to
* the name strategies. When the unit declares none of them — a test whose
* includes don't resolve — the candidates stay as they are. Not for an
* `#include`, which names a file, nor for production code, which
* isVisibleAcrossFiles keeps from test suites.
* Same-named candidates for a C or C++ reference, narrowed to the ones its
* translation unit declares, when there are any: the name a file uses is
* one its includes declare. protobuf's csharp generator tests call the
* `TestSourceDir()` googletest.h declares, not test_util2.h's
* `TestUtil::TestSourceDir`, a test suite none of them includes; its Python
* and Ruby extensions and upb's own code call `upb_Encode()` in
* upb/wire/encode.c, not the copy php-upb.c bundles; rocksdb's stress tool
* calls the `StringToHex()` its db_stress_common.h declares, not
* `LDBCommand::StringToHex`. For a name the reference writes alone
* (`byName`), only what the name alone can mean counts
* (isCppNameAloneTarget), and a call the extractor recorded without the
* receiver it is written on (`columns()[0].value()`) is left to the name
* strategies. When the unit declares none of them — a file whose includes
* don't resolve — the candidates stay as they are. Not for an `#include`,
* which names a file.
*/
function inCppTestTranslationUnit(candidates: Node[], ref: UnresolvedRef, context: ResolutionContext, byName = false): Node[] {
if (candidates.length < 2 || (ref.language !== 'c' && ref.language !== 'cpp') ||
ref.referenceKind === 'imports' || !isTestFile(ref.filePath, context)) return candidates;
function withinCppTranslationUnit(candidates: Node[], ref: UnresolvedRef, context: ResolutionContext, byName = false): Node[] {
if (candidates.length < 2 || (ref.language !== 'c' && ref.language !== 'cpp') || ref.referenceKind === 'imports') return candidates;
if (byName && ref.referenceKind === 'calls' && cppImplicitReceiver(ref, context) !== 'bare') return candidates;
// A "call" outside any function is a macro, or a declaration read as a call
// (rocksdb's c.h `extern … rocksdb_t*` above `rocksdb_open(…);`): no call.
if (ref.referenceKind === 'calls' && context.getNodeById?.(ref.fromNodeId)?.kind === 'file') return candidates;
const within = candidates.filter((n) => isInCppTranslationUnit(n, ref, context) &&
(!byName || n.filePath === ref.filePath || isCppNameAloneTarget(n, ref, context)));
return within.length > 0 ? within : candidates;
if (within.length === 0 || within.length === candidates.length) return candidates;
// Nor, for a C call, to definitions only a flag nothing sets compiles, past
// one the build without it does: that is cppFunctionOverload's choice
// (lua-cjson's fpconv.h defines `fpconv_init` under `#ifdef
// USE_INTERNAL_FPCONV`; redis's build calls fpconv.c's).
if (ref.language === 'c' && ref.referenceKind === 'calls' && within.every((n) => isCFeatureVariant(n, ref, context)) &&
!isOffByDefault(ref.filePath, ref.line, context) &&
candidates.some((n) => !within.includes(n) && n.kind === 'function' && !isStaticCFunction(n, context) &&
!isCFeatureVariant(n, ref, context))) return candidates;
// Nor to functions too few or too many arguments for the call, past one
// that takes them: glog's `GetStackTrace(stack, ARRAYSIZE(stack), 1)` is
// the one stacktrace.h declares, which the stacktrace_*-inl.h headers its
// stacktrace.cc includes define, not utilities.h's `GetStackTrace()`.
if (ref.referenceKind === 'calls') {
const args = cppCallArguments(ref, within[0]!.name, context);
if (args && within.every((n) => cppArgumentCountFits(n, n.name, args, context) === false) &&
candidates.some((n) => !within.includes(n) && cppArgumentCountFits(n, n.name, args, context) === true)) return candidates;
}
return within;
}
const MINIFIED_SCRIPTS = new WeakMap<ResolutionContext, Map<string, boolean>>();
@@ -8146,7 +8164,7 @@ export function matchByExactName(
: swiftShape && swiftShape.shape !== 'chained' ? nearestSwiftMembers(filtered, ref, context)
: kotlinBare ? lexicalKotlinMembers(filtered, ref, context) : cppSpecializationPool(filtered, ref, context);
// (Above the ceiling below nothing is picked, so no translation unit is read.)
const candidates = ranked.length <= AMBIGUOUS_NAME_CEILING ? inCppTestTranslationUnit(ranked, ref, context, true) : ranked;
const candidates = ranked.length <= AMBIGUOUS_NAME_CEILING ? withinCppTranslationUnit(ranked, ref, context, true) : ranked;
if (candidates.length === 0) {
return null;
@@ -9240,7 +9258,7 @@ export function resolveMethodOnType(
// intentionally in ANOTHER file (#314) — is unaffected: those blocks return
// early whenever an import pins the class.
if (ref.referenceKind === 'function_ref' && matches.length !== 1) return null;
const ordered = preferCallSiteFile(inCppTestTranslationUnit(cppVisibleFirst(matches, ref, context), ref, context), ref.filePath);
const ordered = preferCallSiteFile(withinCppTranslationUnit(cppVisibleFirst(matches, ref, context), ref, context), ref.filePath);
return {
original: ref,
targetNodeId: ordered[0]!.id,
@@ -13185,7 +13203,6 @@ export function clearNameMatcherMemos(context: ResolutionContext): void {
clearCppRawStringMemos(context);
CPP_CLASSES_IN_FILE.delete(context);
CPP_STEM_HEADERS.delete(context);
TEST_FILES.delete(context);
TEST_SUITE_FILES.delete(context);
CPP_BASE_SPECS.delete(context);
CPP_USING_MEMBERS.delete(context);
@@ -14784,7 +14801,7 @@ export function matchMethodCall(
// resolves to `a/`'s method (#1079).
const strat1 = nmTimedT('mc-class', ref, (): ResolvedRef | null => {
let classCandidates = preferCallSiteFile(
inCppTestTranslationUnit(context.getNodesByName(objectOrClass!).filter(isMethodOwnerKind), ref, context),
withinCppTranslationUnit(context.getNodesByName(objectOrClass!).filter(isMethodOwnerKind), ref, context),
ref.filePath,
);
// A C# class the call's namespaces can see before one they can't:
@@ -14887,7 +14904,7 @@ export function matchMethodCall(
if (capitalizedReceiver !== objectOrClass) {
const strat2 = nmTimedT('mc-capital', ref, (): ResolvedRef | null => {
const fuzzyClassCandidates = preferCallSiteFile(
inCppTestTranslationUnit(context.getNodesByName(capitalizedReceiver).filter(isMethodOwnerKind), ref, context),
withinCppTranslationUnit(context.getNodesByName(capitalizedReceiver).filter(isMethodOwnerKind), ref, context),
ref.filePath,
);
for (const classNode of fuzzyClassCandidates) {
@@ -15022,7 +15039,7 @@ export function matchMethodCall(
// the first seen) resolves to the call site's own file rather than the
// first-indexed duplicate (#1079).
const std = isStdMethodName(ref.language, methodName!) && !/^(?:self|Self|this|base)$/.test(objectOrClass!);
for (const method of preferCallSiteFile(inCppTestTranslationUnit(targetMethods, ref, context), ref.filePath)) {
for (const method of preferCallSiteFile(withinCppTranslationUnit(targetMethods, ref, context), ref.filePath)) {
if (std && !receiverNamesOwner(receiverLink(objectOrClass!), method, context)) continue;
// The owner type's own name — not its namespace (`eShop.ClientApp…`
// shares `Client` with every `httpClient`) nor the method's.
@@ -18569,20 +18586,35 @@ function cppOverloadParameters(n: Node, name: string, context: ResolutionContext
return found;
}
/** A C++ parameter that is a pack: `T&&... args`, not the `...` inside `format_string<T...>`. */
function isCppParameterPack(p: string): boolean {
let flat = p;
for (let prev = ''; prev !== flat;) { prev = flat; flat = flat.replace(/<[^<>]*>/g, ''); }
return flat.includes('...');
}
/**
* Whether as many arguments as `args` can fill the parameters of the C or C++
* function `n`, its defaults and a pack counted; null when `n` is no
* function or its parameter list can't be read.
*/
function cppArgumentCountFits(n: Node, name: string, args: string[], context: ResolutionContext): boolean | null {
if (n.kind !== 'function' && n.kind !== 'method') return null;
const list = cppOverloadParameters(n, name, context);
if (list === null) return null;
const params = splitCppTopLevel(cppWithoutComments(list)).filter((p) => p !== 'void');
const required = params.filter((p) => !isCppParameterPack(p) && !/=/.test(p)).length;
return args.length >= required && (params.some(isCppParameterPack) || args.length <= params.length);
}
/** How well a call's arguments fit an overload's parameters: arity, and a string literal's first slot. */
function cppOverloadFit(n: Node, name: string, args: string[], context: ResolutionContext): number {
if (n.kind !== 'function' && n.kind !== 'method') return -1;
const list = cppOverloadParameters(n, name, context);
if (list === null) return 0;
const params = splitCppTopLevel(cppWithoutComments(list)).filter((p) => p !== 'void');
// A pack is `T&&... args`, not the `...` inside `format_string<T...>`.
const isPack = (p: string): boolean => {
let flat = p;
for (let prev = ''; prev !== flat;) { prev = flat; flat = flat.replace(/<[^<>]*>/g, ''); }
return flat.includes('...');
};
const variadic = params.some(isPack);
const required = params.filter((p) => !isPack(p) && !/=/.test(p)).length;
const variadic = params.some(isCppParameterPack);
const required = params.filter((p) => !isCppParameterPack(p) && !/=/.test(p)).length;
let score = 0;
if (args.length < required || (!variadic && args.length > params.length)) score -= 3;
// Each string literal against its parameter: a string type by name over a
@@ -18596,7 +18628,7 @@ function cppOverloadFit(n: Node, name: string, args: string[], context: Resoluti
for (let i = 0; i < args.length && i < params.length; i++) {
const arg = args[i]!;
const param = params[i]!;
if (isPack(param)) break;
if (isCppParameterPack(param)) break;
if (!/^(?:u8|u|U|L)?R?"|^FMT_STRING\s*\(/.test(arg)) continue;
const wideArg = /^LR?"/.test(arg);
const wideParam = /\bw(?:string|char_t|format|string_view)|wchar_t/.test(param);