mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-22 01:07:18 +08:00
fix(c,cpp): a name in a C or C++ test links to what the test's own includes declare (#2675)
When several files declare a name a C or C++ test uses, the test now links
to one its translation unit declares, when there is one: a definition in its
own file, in a file it includes (directly or through other includes), or in
the source file that implements such a header (the same-stem file that
includes it), unless that source keeps the definition to itself (a `static`,
an unnamed namespace, a class it declares). Before, the candidates were
ranked by directory and index order, so a test took whichever namesake came
first:
- protobuf's C# generator tests called test_util2.h's
`TestUtil::TestSourceDir()`, a test suite they never include, instead of
the `TestSourceDir()` googletest.h declares;
- protobuf's upb and hpb tests left `upb_Arena_New()` and the rest of upb's
C API unlinked: the copies bundled for PHP and Ruby won the ranking and
were then rejected;
- rocksdb's c_test.c left `rocksdb_open()` and the rest of the C API in
db/c.cc unlinked, and its histogram test's `clock->SleepForMicroseconds()`
went to emulated_clock.h's `EmulatedSystemClock`, which it doesn't include.
The narrowing applies to test files only, to the exact-name strategy, typed
receivers (resolveMethodOnType), and the class-name and receiver-word
strategies of matchMethodCall. When the translation unit declares none of
the candidates, as when a test's includes don't resolve (jemalloc's tests
include a generated header), the candidates stay as they were. A name a
test writes alone (no receiver, no qualifier) is narrowed only to what C++
finds for it outside a class: a function, a namespace-scope type or a
constructor, never another class's method or nested type, and a call the
extractor recorded without the receiver it is written on is left as it
was. The qualified-name strategy is unchanged.
Validated on main 3695818f (site-keyed edge diffs, every group triaged):
rocksdb +1,442 / 47 retargeted / -12, protobuf +2,316 / 55 / -67,
fmt +33 / 40, breakpad +13 / 14, glog +22, leveldb +10, llm.c 6
retargeted; jq, tokio, excalidraw, okhttp and typeorm byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- In C++, a call whose receiver's type can't be read no longer links to a method of a class nested in the class the receiver's name suggests, or of a class whose name only contains that name. Before, such a guess took any method of the call's name whose qualified name contained the class's name: protocolbuffers/protobuf's `descriptor->file()` in a template reached `Descriptor::ExtensionRange::file`, and facebook/rocksdb's `CompositeEnv::IsInstanceOf(name)` reached `CompositeEnvWrapper::IsInstanceOf`. A call written through a class, like `PessimisticTransaction::PopSavePoint()` in an override, now links to the method that class inherits, here `TransactionBaseImpl::PopSavePoint`. Re-index C++ projects after upgrading.
|
||||
- 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++, 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.
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* A C or C++ test is a program of its own, so a name it uses is one its
|
||||
* translation unit declares. Of several same-named candidates, a test suite
|
||||
* the test does not include is not one of them (protobuf's csharp generator
|
||||
* tests called `TestSourceDir()`, which googletest.h declares, and
|
||||
* test_util2.h's `TestUtil::TestSourceDir`, included by none of them, took
|
||||
* the calls), and what the unit includes goes before what it doesn't (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; rocksdb's histogram test sleeps
|
||||
* on a `SystemClock`, not on emulated_clock.h's `EmulatedSystemClock`). A
|
||||
* test whose includes don't resolve (jemalloc's tests include a generated
|
||||
* header) keeps the one candidate there is, and a definition in the test's
|
||||
* own file still comes first (#1079).
|
||||
*/
|
||||
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-test-tu-'));
|
||||
const files: Record<string, string> = {
|
||||
// googletest.h declares TestSourceDir(); googletest.cc defines it.
|
||||
'src/google/protobuf/testing/googletest.h': `#ifndef GOOGLE_PROTOBUF_TESTING_GOOGLETEST_H__
|
||||
#define GOOGLE_PROTOBUF_TESTING_GOOGLETEST_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
|
||||
std::string TestSourceDir();
|
||||
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
#endif
|
||||
`,
|
||||
'src/google/protobuf/testing/googletest.cc': `#include "google/protobuf/testing/googletest.h"
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
|
||||
std::string TestSourceDir() { return "src"; }
|
||||
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
// A test suite's header no csharp test includes.
|
||||
'src/google/protobuf/test_util2.h': `#ifndef GOOGLE_PROTOBUF_TEST_UTIL2_H__
|
||||
#define GOOGLE_PROTOBUF_TEST_UTIL2_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
namespace TestUtil {
|
||||
|
||||
inline std::string TestSourceDir() { return "src/google/protobuf"; }
|
||||
|
||||
} // namespace TestUtil
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
#endif
|
||||
`,
|
||||
'src/google/protobuf/compiler/csharp/csharp_bootstrap_unittest.cc': `#include <string>
|
||||
|
||||
#include "google/protobuf/testing/googletest.h"
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
namespace compiler {
|
||||
namespace csharp {
|
||||
|
||||
int GeneratedDescriptorsAreUpToDate() {
|
||||
std::string dir = TestSourceDir();
|
||||
return static_cast<int>(dir.size());
|
||||
}
|
||||
|
||||
} // namespace csharp
|
||||
} // namespace compiler
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
// A receiver whose type isn't read is guessed by its name, among the
|
||||
// classes the test includes: rocksdb's histogram test sleeps on its
|
||||
// clock, which no emulated_clock.h include of its declares.
|
||||
'include/rocksdb/system_clock.h': `namespace rocksdb {
|
||||
|
||||
class SystemClock {
|
||||
public:
|
||||
virtual ~SystemClock() {}
|
||||
virtual void SleepForMicroseconds(int micros) = 0;
|
||||
};
|
||||
|
||||
class SystemClockWrapper : public SystemClock {
|
||||
public:
|
||||
void SleepForMicroseconds(int micros) override { target_->SleepForMicroseconds(micros); }
|
||||
|
||||
private:
|
||||
SystemClock* target_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
`,
|
||||
'env/emulated_clock.h': `#include "rocksdb/system_clock.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class EmulatedSystemClock : public SystemClockWrapper {
|
||||
public:
|
||||
void SleepForMicroseconds(int micros) override {}
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
`,
|
||||
'test_util/mock_time_env.h': `#include <memory>
|
||||
|
||||
#include "rocksdb/system_clock.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class MockSystemClock : public SystemClockWrapper {
|
||||
public:
|
||||
void MockSleepForMicroseconds(int micros) {}
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
`,
|
||||
'monitoring/histogram_test.cc': `#include <memory>
|
||||
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
std::shared_ptr<MockSystemClock> clock = std::make_shared<MockSystemClock>();
|
||||
} // namespace
|
||||
|
||||
void PopulateHistogram(int low, int high) {
|
||||
for (int i = low; i <= high; i++) {
|
||||
clock->SleepForMicroseconds(i);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
`,
|
||||
// A test that declares the class it calls on: its own file comes first,
|
||||
// whatever it includes (#1079).
|
||||
'src/util/collector.h': `#include <string>
|
||||
|
||||
class Collector {
|
||||
public:
|
||||
std::string last_error() const { return error_; }
|
||||
|
||||
private:
|
||||
std::string error_;
|
||||
};
|
||||
`,
|
||||
// upb's arena, and the copy a bundled amalgamation carries.
|
||||
'php/ext/google/protobuf/php-upb.h': `typedef struct upb_Arena upb_Arena;
|
||||
|
||||
static inline upb_Arena* upb_Arena_New(void) { return 0; }
|
||||
`,
|
||||
'upb/mem/arena.h': `typedef struct upb_Arena upb_Arena;
|
||||
|
||||
static inline upb_Arena* upb_Arena_New(void) { return 0; }
|
||||
`,
|
||||
'hpb/backend/upb/interop_test.cc': `#include "upb/mem/arena.h"
|
||||
|
||||
int ArenaInterop() {
|
||||
upb_Arena* arena = upb_Arena_New();
|
||||
return arena == 0;
|
||||
}
|
||||
`,
|
||||
'src/util/own_collector_unittest.cc': `#include "util/collector.h"
|
||||
|
||||
namespace {
|
||||
class Collector {
|
||||
public:
|
||||
int last_error() const { return 0; }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
int OwnCollector() {
|
||||
Collector collector;
|
||||
return collector.last_error();
|
||||
}
|
||||
`,
|
||||
// jemalloc's tests include a header the build generates, so nothing
|
||||
// shows the test includes its helpers: the one helper there is stays.
|
||||
'deps/jemalloc/test/src/SFMT.c': `unsigned gen_rand32(void* ctx) {
|
||||
return 4;
|
||||
}
|
||||
`,
|
||||
'deps/jemalloc/test/unit/SFMT.c': `#include "test/jemalloc_test.h"
|
||||
|
||||
unsigned test_gen_rand32(void* ctx) {
|
||||
return gen_rand32(ctx);
|
||||
}
|
||||
`,
|
||||
};
|
||||
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++ test links what its translation unit declares', () => {
|
||||
it('passes over a test suite it does not include for the declaration it does', () => {
|
||||
const calls = callsFrom('src/google/protobuf/compiler/csharp/csharp_bootstrap_unittest.cc');
|
||||
expect(calls).toContain('google::protobuf::TestSourceDir (src/google/protobuf/testing/googletest.cc)');
|
||||
expect(calls.some((c) => c.includes('test_util2.h'))).toBe(false);
|
||||
});
|
||||
|
||||
it('guesses a receiver among the classes its includes declare', () => {
|
||||
const calls = callsFrom('monitoring/histogram_test.cc');
|
||||
expect(calls).toContain('rocksdb::SystemClock::SleepForMicroseconds (include/rocksdb/system_clock.h)');
|
||||
expect(calls.some((c) => c.includes('emulated_clock.h'))).toBe(false);
|
||||
});
|
||||
|
||||
it('calls the copy of a function its includes declare', () => {
|
||||
expect(callsFrom('hpb/backend/upb/interop_test.cc')).toContain('upb_Arena_New (upb/mem/arena.h)');
|
||||
});
|
||||
|
||||
it('still prefers a declaration in the test file itself', () => {
|
||||
const calls = callsFrom('src/util/own_collector_unittest.cc');
|
||||
expect(calls.some((c) => c.endsWith('(src/util/own_collector_unittest.cc)') && c.includes('last_error'))).toBe(true);
|
||||
expect(calls.some((c) => c.includes('(src/util/collector.h)'))).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps the only candidate when its includes do not show the unit', () => {
|
||||
expect(callsFrom('deps/jemalloc/test/unit/SFMT.c')).toContain('gen_rand32 (deps/jemalloc/test/src/SFMT.c)');
|
||||
});
|
||||
});
|
||||
+125
-12
@@ -1608,14 +1608,122 @@ const fileStem = (filePath: string): string => {
|
||||
function isIncludedCppTestSuite(candidate: Node, ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
if ((candidate.language !== 'c' && candidate.language !== 'cpp') || (ref.language !== 'c' && ref.language !== 'cpp')) return false;
|
||||
if (cppIncluders(candidate.filePath, context).has(ref.filePath)) return true;
|
||||
const stem = fileStem(candidate.filePath);
|
||||
for (const include of context.getNodesInFile(candidate.filePath)) {
|
||||
if (include.kind !== 'import' || fileStem(include.name) !== stem) continue;
|
||||
const header = cppIncludedFile(include, context);
|
||||
if (header && header !== candidate.filePath && fileStem(header) === stem && isTestSuitePath(header) &&
|
||||
cppIncluders(header, context).has(ref.filePath)) return true;
|
||||
return cppSameStemHeaders(candidate.filePath, context)
|
||||
.some((header) => isTestSuiteFile(header, context) && cppIncluders(header, context).has(ref.filePath));
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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 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);
|
||||
}
|
||||
memo.set(filePath, headers);
|
||||
}
|
||||
return false;
|
||||
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. */
|
||||
function isTestSuiteFile(filePath: string, context: ResolutionContext): boolean {
|
||||
let memo = TEST_SUITE_FILES.get(context);
|
||||
if (!memo) TEST_SUITE_FILES.set(context, (memo = new Map()));
|
||||
let hit = memo.get(filePath);
|
||||
if (hit === undefined) memo.set(filePath, (hit = isTestSuitePath(filePath)));
|
||||
return hit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a C or C++ definition is in `ref`'s translation unit, as its
|
||||
* includes show it: in the reference's own file, in a file the reference's
|
||||
* file includes, directly or through other includes, or in the source file
|
||||
* that implements such a header — the one named like it that includes it
|
||||
* (`googletest.cc` for `googletest.h`) — unless the source keeps it to
|
||||
* itself (a `static`, or what isCppTranslationUnitLocal finds). A test
|
||||
* suite's source implements only a test suite's header, as in
|
||||
* isIncludedCppTestSuite.
|
||||
*/
|
||||
function isInCppTranslationUnit(candidate: Node, ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
if (candidate.filePath === ref.filePath) return true;
|
||||
if (candidate.language !== 'c' && candidate.language !== 'cpp') return false;
|
||||
if (cppIncluders(candidate.filePath, context).has(ref.filePath)) return true;
|
||||
if (!C_SOURCE_EXT.test(candidate.filePath) || isCppTranslationUnitLocal(candidate, ref, context) ||
|
||||
((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));
|
||||
}
|
||||
|
||||
/**
|
||||
* What a C or C++ name written alone can mean outside a class, by its kind:
|
||||
* a call reaches a function, or a type and its constructor (`Foo(…)`); a
|
||||
* construction makes a type (`std::string buffer(n, c)` is no enum's
|
||||
* `string`); any other reference names anything but a member. Nothing a
|
||||
* class declares is in reach — its methods, its nested types, their
|
||||
* constructors: only an object, a qualified name or the class's own code
|
||||
* reaches them. gmock's `Value(result, …)` matcher is no
|
||||
* `RepeatedFieldAccessor::Value`, breakpad's `EXPECT_CALL(handler, Entry(…))`
|
||||
* no `CallFrameInfo::Entry` struct.
|
||||
*/
|
||||
function isCppNameAloneTarget(n: Node, ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
const constructor = n.kind === 'method' && n.qualifiedName.endsWith(`${n.name}::${n.name}`);
|
||||
const fits = ref.referenceKind === 'calls' ? n.kind === 'function' || constructor || CPP_TYPE_KINDS.has(n.kind)
|
||||
: ref.referenceKind === 'instantiates' ? constructor || CPP_TYPE_KINDS.has(n.kind)
|
||||
: constructor || (n.kind !== 'method' && n.kind !== 'field' && n.kind !== 'property');
|
||||
if (!fits) return false;
|
||||
const scope = cppParentScope(constructor ? cppParentScope(n.qualifiedName) : n.qualifiedName);
|
||||
return !cppTypeDecls(scope, context).some((d) => CPP_CLASS_KINDS.has(d.kind));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
if (byName && ref.referenceKind === 'calls' && cppImplicitReceiver(ref, context) !== 'bare') 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;
|
||||
}
|
||||
|
||||
const MINIFIED_SCRIPTS = new WeakMap<ResolutionContext, Map<string, boolean>>();
|
||||
@@ -8033,10 +8141,12 @@ export function matchByExactName(
|
||||
// file's symbol of that name, so a bare call has no cross-file candidate.
|
||||
!(bareJs && n.filePath !== ref.filePath && isLocallyBoundJsName(ref.referenceName, ref.filePath, context))
|
||||
);
|
||||
const candidates = dartBare ? nearestDartMembers(preferOwnDartLibrary(filtered, ref, context), ref, context)
|
||||
const ranked = dartBare ? nearestDartMembers(preferOwnDartLibrary(filtered, ref, context), ref, context)
|
||||
: dartTypeRef && dartPrefix === null ? preferOwnDartLibrary(filtered, ref, context)
|
||||
: 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;
|
||||
|
||||
if (candidates.length === 0) {
|
||||
return null;
|
||||
@@ -9130,7 +9240,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(cppVisibleFirst(matches, ref, context), ref.filePath);
|
||||
const ordered = preferCallSiteFile(inCppTestTranslationUnit(cppVisibleFirst(matches, ref, context), ref, context), ref.filePath);
|
||||
return {
|
||||
original: ref,
|
||||
targetNodeId: ordered[0]!.id,
|
||||
@@ -13074,6 +13184,9 @@ export function clearNameMatcherMemos(context: ResolutionContext): void {
|
||||
clearCppNamespaceMemos(context);
|
||||
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);
|
||||
CPP_MEMBER_MACRO_ARGS.delete(context);
|
||||
@@ -14671,7 +14784,7 @@ export function matchMethodCall(
|
||||
// resolves to `a/`'s method (#1079).
|
||||
const strat1 = nmTimedT('mc-class', ref, (): ResolvedRef | null => {
|
||||
let classCandidates = preferCallSiteFile(
|
||||
context.getNodesByName(objectOrClass!).filter(isMethodOwnerKind),
|
||||
inCppTestTranslationUnit(context.getNodesByName(objectOrClass!).filter(isMethodOwnerKind), ref, context),
|
||||
ref.filePath,
|
||||
);
|
||||
// A C# class the call's namespaces can see before one they can't:
|
||||
@@ -14774,7 +14887,7 @@ export function matchMethodCall(
|
||||
if (capitalizedReceiver !== objectOrClass) {
|
||||
const strat2 = nmTimedT('mc-capital', ref, (): ResolvedRef | null => {
|
||||
const fuzzyClassCandidates = preferCallSiteFile(
|
||||
context.getNodesByName(capitalizedReceiver).filter(isMethodOwnerKind),
|
||||
inCppTestTranslationUnit(context.getNodesByName(capitalizedReceiver).filter(isMethodOwnerKind), ref, context),
|
||||
ref.filePath,
|
||||
);
|
||||
for (const classNode of fuzzyClassCandidates) {
|
||||
@@ -14909,7 +15022,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(targetMethods, ref.filePath)) {
|
||||
for (const method of preferCallSiteFile(inCppTestTranslationUnit(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.
|
||||
|
||||
Reference in New Issue
Block a user