mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-27 04:49:43 +08:00
fix(cpp): a gtest TEST_F or TEST_P body finds its fixture's types, and a function's local class is seen only from that function (#2551)
A TEST_F / TEST_P body is a member function of a class derived from its fixture, so its unqualified names find the fixture's nested types (its bases' included) before the enclosing namespaces'. And since the index names a function's local class after the function, every TEST_F body's local `FakeWF` or `MyManager` shared one qualified name: a class local to a function is now visible only from inside that function, and a namesake class's methods are its own body's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
754cc45ad7
commit
d94a92ea89
@@ -75,6 +75,42 @@ function isCFamily(n: Node): boolean {
|
||||
return n.language === 'cpp' || n.language === 'c';
|
||||
}
|
||||
|
||||
/**
|
||||
* The declarations code at `site` can see (visibleFrom), where a class local
|
||||
* to a function counts only in that function's lines: the index names it
|
||||
* after the function, and every gtest `TEST_F` body in a namespace is
|
||||
* `ns::TEST_F`, so one body's local `Listener` passed for any other's — in
|
||||
* the body itself, and in the methods of a local class of its own.
|
||||
*/
|
||||
function seenFrom(site: CppNameSite, context: ResolutionContext): (n: Node) => boolean {
|
||||
const visible = visibleFrom(site.file);
|
||||
return (n) => {
|
||||
if (!visible(n)) return false;
|
||||
const fn = cppFunctionAround(n, context);
|
||||
return !fn || (fn.filePath === site.file && site.line >= fn.startLine && site.line <= fn.endLine);
|
||||
};
|
||||
}
|
||||
|
||||
const LOCAL_TO = new WeakMap<ResolutionContext, Map<string, Node | null>>();
|
||||
|
||||
/**
|
||||
* The function a C or C++ class is local to: the one of the scope its
|
||||
* qualified name is in whose lines hold it. Null for a class of a namespace
|
||||
* or of another class.
|
||||
*/
|
||||
function cppFunctionAround(cls: Node, context: ResolutionContext): Node | null {
|
||||
let memo = LOCAL_TO.get(context);
|
||||
if (!memo) LOCAL_TO.set(context, (memo = new Map()));
|
||||
const hit = memo.get(cls.id);
|
||||
if (hit !== undefined) return hit;
|
||||
const scope = cppParentScope(cls.qualifiedName);
|
||||
const fn = (scope ? context.getNodesByQualifiedName(scope) : []).find((f) =>
|
||||
(f.kind === 'function' || f.kind === 'method') && f.filePath === cls.filePath &&
|
||||
f.startLine <= cls.startLine && f.endLine >= cls.startLine) ?? null;
|
||||
memo.set(cls.id, fn);
|
||||
return fn;
|
||||
}
|
||||
|
||||
/** The declarations code in `file` can see: a header's, and the file's own. */
|
||||
function visibleFrom(file: string): (n: Node) => boolean {
|
||||
return (n) => n.filePath === file || !SOURCE_FILE.test(n.filePath);
|
||||
@@ -110,6 +146,13 @@ export interface CppNameSite {
|
||||
parameters: ReadonlySet<string>;
|
||||
/** A class the name cannot be: the deriving class, in its own base clause. */
|
||||
self?: Node;
|
||||
/**
|
||||
* A class looked in right after `scope` itself, its bases included: a gtest
|
||||
* `TEST_F` body is a member function of a class derived from its fixture,
|
||||
* so the fixture's nested types are what its unqualified names find
|
||||
* before the enclosing namespaces'.
|
||||
*/
|
||||
classScope?: string;
|
||||
}
|
||||
|
||||
/** The class a C or C++ type name names. */
|
||||
@@ -133,8 +176,9 @@ export function cppClassWritten(written: string, site: CppNameSite, ref: Unresol
|
||||
const global = written.startsWith('::');
|
||||
if (!global && site.parameters.has(segments[0]!)) return null;
|
||||
|
||||
const visible = visibleFrom(site.file);
|
||||
const visible = seenFrom(site, context);
|
||||
const scopes = cppScopesWithin(site.scope);
|
||||
if (site.classScope) scopes.splice(1, 0, site.classScope);
|
||||
const resolved = (node: Node): CppWrittenClass => ({ node, exact: true });
|
||||
|
||||
// The scopes around the name short of the global one, or only the global one for `::Base`.
|
||||
@@ -234,7 +278,7 @@ function classNode(named: CppNamedClass, site: CppNameSite, context: ResolutionC
|
||||
.filter((n) => isCFamily(n) && CLASS_KINDS.has(n.kind) && n.id !== site.self?.id);
|
||||
// A lookup that ended on the class itself (its namespace lost) found nothing.
|
||||
if (classes.length === 0) return undefined;
|
||||
const visible = classes.filter(visibleFrom(site.file));
|
||||
const visible = classes.filter(seenFrom(site, context));
|
||||
return pick(visible.length > 0 ? visible : classes, site.file);
|
||||
}
|
||||
|
||||
@@ -288,9 +332,10 @@ interface CppUsing {
|
||||
|
||||
const USINGS = new WeakMap<ResolutionContext, Map<string, CppUsing[]>>();
|
||||
|
||||
/** Drop the per-file `using` memo (see ReferenceResolver.clearCaches). */
|
||||
/** Drop the per-file `using` memo and the local-class memo (see ReferenceResolver.clearCaches). */
|
||||
export function clearCppSupertypeMemos(context: ResolutionContext): void {
|
||||
USINGS.delete(context);
|
||||
LOCAL_TO.delete(context);
|
||||
}
|
||||
|
||||
const PATH = String.raw`(?:::\s*)?[A-Za-z_]\w*(?:\s*::\s*[A-Za-z_]\w*)*`;
|
||||
|
||||
@@ -10163,13 +10163,21 @@ function matchCppHeldTypeCall(raw: string, methodName: string, ref: UnresolvedRe
|
||||
|
||||
const CPP_CLASS_KINDS: ReadonlySet<string> = new Set(['class', 'struct', 'union']);
|
||||
|
||||
/** Where a type the calling function writes is looked up from: the function itself, so a class local to it is found first. */
|
||||
/**
|
||||
* Where a type the calling function writes is looked up from: the function
|
||||
* itself, so a class local to it is found first, then — in a gtest `TEST_F`
|
||||
* or `TEST_P` body — the test's fixture (cppTestFixture) and its bases, as
|
||||
* for the member function of a class derived from it that the body is:
|
||||
* rocksdb's `new TestUserDefinedIndexFactory()` is the fixture base's own.
|
||||
*/
|
||||
function cppCallSite(caller: Node, ref: UnresolvedRef, context: ResolutionContext): CppNameSite {
|
||||
const fixture = cppTestFixture(caller, ref, context);
|
||||
return {
|
||||
scope: caller.kind === 'file' ? '' : caller.qualifiedName,
|
||||
file: ref.filePath,
|
||||
line: ref.line,
|
||||
parameters: cppTemplateParameters(caller, context),
|
||||
...(fixture ? { classScope: fixture } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10333,10 +10341,12 @@ function cppBasesKnown(cls: Node, context: ResolutionContext, depth = 0, seen =
|
||||
* call's arguments fit (cppOverloadFor) — else the nearest one of a class
|
||||
* it derives from, through the base edges every declaration of `cls` has.
|
||||
* When other classes share `cls`'s qualified name, its own are the ones
|
||||
* defined where it is: leveldb's db_bench.cc and db_bench_sqlite3.cc each
|
||||
* declare a `RandomGenerator`, with a `Generate` that takes a `size_t` and
|
||||
* an `int`. (A chained call `inner(…).name(…)` keeps the first: the
|
||||
* arguments at its position are the inner call's.)
|
||||
* defined in its body, else where it is: the local classes of gtest bodies
|
||||
* in one namespace are each `ns::TEST_F::Listener`, and leveldb's
|
||||
* db_bench.cc and db_bench_sqlite3.cc each declare a `RandomGenerator`, with
|
||||
* a `Generate` that takes a `size_t` and an `int`. (A chained call
|
||||
* `inner(…).name(…)` keeps the first: the arguments at its position are the
|
||||
* inner call's.)
|
||||
*/
|
||||
function cppMethodOf(cls: Node, name: string, ref: UnresolvedRef, context: ResolutionContext, depth = 0, seen = new Set<string>()): Node | null {
|
||||
const want = `${cls.qualifiedName}::${name}`;
|
||||
@@ -10346,7 +10356,11 @@ function cppMethodOf(cls: Node, name: string, ref: UnresolvedRef, context: Resol
|
||||
if (own.length > 0) {
|
||||
const ordered = preferCallSiteFile(cppVisibleFirst(own, ref, context), ref.filePath);
|
||||
if (ref.referenceName.includes('().')) return ordered[0]!;
|
||||
const defined = cppClassDecls(cls.qualifiedName, context).length > 1
|
||||
const namesakes = cppClassDecls(cls.qualifiedName, context).length > 1;
|
||||
const inBody = namesakes
|
||||
? ordered.filter((n) => n.filePath === cls.filePath && n.startLine >= cls.startLine && n.endLine <= cls.endLine)
|
||||
: [];
|
||||
const defined = inBody.length > 0 ? inBody : namesakes
|
||||
? ordered.filter((n) => n.filePath === cls.filePath || cppIncluders(cls.filePath, context).has(n.filePath))
|
||||
: ordered;
|
||||
return cppOverloadFor(defined.length > 0 ? defined : ordered, name, ref, context);
|
||||
|
||||
Reference in New Issue
Block a user