fix(cpp): an alias naming a class through a derived class leads to the class it inherits (#2549)

protobuf's `using ConformanceRequestSetting =
BinaryAndJsonConformanceSuite::ConformanceRequestSetting;` names a class the
suite inherits from `ConformanceTestSuite`. An alias was followed only to a
class declared under exactly the name it spells, so constructions through it
stopped at the alias. Its target is now looked up a segment at a time through
each class's bases, as C++ does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-14 06:31:04 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 6df4827625
commit 754cc45ad7
4 changed files with 178 additions and 5 deletions
+25 -4
View File
@@ -438,6 +438,22 @@ function baseClassesOf(cls: string, context: ResolutionContext): string[] {
return bases;
}
/**
* The qualified name of the class `names` spells, looked up from `scope`:
* spelled out in a scope around it (classNamed), else a segment at a time as
* a declared type is, so a class one of the named class's bases declares is
* found. protobuf's conformance suites alias `using ConformanceRequestSetting =
* BinaryAndJsonConformanceSuite::ConformanceRequestSetting;`, a class their
* base `ConformanceTestSuite` declares.
*/
function classReached(names: readonly string[], scope: string, ref: UnresolvedRef, context: ResolutionContext): string | undefined {
const spelled = classNamed(names, scopesWithin(scope), context);
if (spelled) return spelled;
// (The walk stops on a class only once every segment is taken.)
const { found } = lookupSegments({ names: [...names], templated: names.map(() => false) }, scopesWithin(scope), ref, context);
return found && typeof found === 'object' && 'cls' in found ? found.cls : undefined;
}
/** The qualified name of the class `names` spells, looked up from `scopes`. */
function classNamed(names: readonly string[], scopes: readonly string[], context: ResolutionContext): string | undefined {
const spelled = names.join('::');
@@ -521,9 +537,14 @@ export function cppAliasedTypeName(resolved: CppAliasedType): string {
return [resolved.target[resolved.target.length - 1]!, ...resolved.rest].join('::');
}
/** The qualified name of the project class a type resolved through aliases is, if it is one: `leveldb::SkipList::Iterator`. */
export function cppAliasedClass(resolved: CppAliasedType, context: ResolutionContext): string | undefined {
return classNamed([...resolved.target, ...resolved.rest], scopesWithin(resolved.scope), context);
/**
* The qualified name of the project class a type resolved through aliases is,
* if it is one: `leveldb::SkipList::Iterator`. With the ref it is resolved
* for, a class a base of the named class declares counts (classReached).
*/
export function cppAliasedClass(resolved: CppAliasedType, context: ResolutionContext, ref?: UnresolvedRef): string | undefined {
const names = [...resolved.target, ...resolved.rest];
return ref ? classReached(names, resolved.scope, ref, context) : classNamed(names, scopesWithin(resolved.scope), context);
}
/** What a C++ type name names when it is looked up for a class (see cppClassNamed). */
@@ -573,7 +594,7 @@ export function cppClassNamed(
// The aliased type is written in the alias's own scope.
const aliased = expand(text ? aliasedTypeIn(text, alias.name) : null, name.names.slice(next), name.templated.slice(next),
parentScope(alias.qualifiedName), ref, context, 1, templateParametersAround(alias, context));
const cls = aliased && !aliased.pointer && classNamed([...aliased.target, ...aliased.rest], scopesWithin(aliased.scope), context);
const cls = aliased && !aliased.pointer && classReached([...aliased.target, ...aliased.rest], aliased.scope, ref, context);
if (cls) return { cls };
// A member of a type outside the project (`Alias::Inner`) is not the alias.
return next === name.names.length ? { alias } : null;
+1 -1
View File
@@ -11405,7 +11405,7 @@ function matchCppConstruction(ref: UnresolvedRef, context: ResolutionContext): R
: { original: ref, targetNodeId: node.id, confidence: 0.7, resolvedBy: 'exact-match' };
if (cppDeclaresLocalAlias(written, ref, context)) {
const aliased = resolveCppAliasedType(written, ref, context);
const named = aliased && !aliased.pointer ? cppAliasedClass(aliased, context) : undefined;
const named = aliased && !aliased.pointer ? cppAliasedClass(aliased, context, ref) : undefined;
const cls = named ? context.getNodesByQualifiedName(named).find((n) => CPP_CLASS_KINDS.has(n.kind)) : undefined;
return cls ? made(cls) : null;
}