mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-21 16:57:09 +08:00
fix(cpp): an alias of a type written from the global scope names that global type (#2568)
protobuf's hpb declares `class Arena { … backend::Arena arena_; }`, where
`namespace backend = hpb::internal::backend::upb;` and that namespace says
`using Arena = ::upb::Arena;`. cppTypeName dropped the leading `::`, so the
alias's target was looked up as `upb::Arena` from the alias's own scope,
whose nearest `upb` is that very namespace: the alias named itself, the
lookup ended on no class, and `arena_.Fuse(other.arena_)` fell back to the
type's last name, linking `hpb::Arena::Fuse` (and `IsFused`, `RefArena`) to
itself instead of `upb::Arena::Fuse`.
TypeName now records a leading `::` (`global`), and a global name is looked
up from the global scope alone: when an alias's target is followed (follow,
and the scope expand looks the target up from and hands on), in a class's
base clause (baseClassesOf), and in cppAliasedTypeWritten, which #2532's
`auto` locals and chains walk alias by alias.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,13 @@ export interface CppAliasedType {
|
||||
interface TypeName {
|
||||
names: string[];
|
||||
templated: boolean[];
|
||||
/**
|
||||
* Written from the global scope (`::upb::Arena`), so looked up there alone:
|
||||
* protobuf's `namespace hpb::internal::backend::upb { using Arena =
|
||||
* ::upb::Arena; }` would otherwise find its own namespace's `upb::Arena`,
|
||||
* the alias itself.
|
||||
*/
|
||||
global?: boolean;
|
||||
}
|
||||
|
||||
/** `SkipList<const char*, Cmp<int>>::Iterator` → `SkipList::Iterator`. */
|
||||
@@ -105,13 +112,19 @@ function cppTypeName(raw: string): TypeName | null {
|
||||
}
|
||||
parts.push(part.trim());
|
||||
templated.push(args);
|
||||
if (parts[0] === '') {
|
||||
// A leading `::` names the global scope.
|
||||
// A leading `::` names the global scope.
|
||||
const global = parts[0] === '';
|
||||
if (global) {
|
||||
parts.shift();
|
||||
templated.shift();
|
||||
}
|
||||
if (parts.length === 0 || !parts.every((p) => /^[A-Za-z_]\w*$/.test(p))) return null;
|
||||
return { names: parts, templated };
|
||||
return global ? { names: parts, templated, global } : { names: parts, templated };
|
||||
}
|
||||
|
||||
/** The scopes a written name is looked up from: the global scope alone for `::Name`. */
|
||||
function scopesFor(name: TypeName, scopes: readonly string[]): readonly string[] {
|
||||
return name.global ? [''] : scopes;
|
||||
}
|
||||
|
||||
/** Is a written type a pointer (`Table*`, `const Foo* const`), not counting its template arguments? */
|
||||
@@ -430,7 +443,7 @@ function baseClassesOf(cls: string, context: ResolutionContext): string[] {
|
||||
for (const part of splitTopLevel(text.slice(colon + 1, brace))) {
|
||||
const name = cppTypeName(part.replace(/\b(?:public|protected|private|virtual)\b/g, ' '));
|
||||
if (!name) continue;
|
||||
const base = classNamed(name.names, scopesWithin(parentScope(cls)), context);
|
||||
const base = classNamed(name.names, scopesFor(name, scopesWithin(parentScope(cls))), context);
|
||||
if (base && base !== cls && !bases.includes(base)) bases.push(base);
|
||||
}
|
||||
}
|
||||
@@ -621,7 +634,7 @@ export function cppAliasedTypeWritten(
|
||||
): { text: string; alias: Node } | null | undefined {
|
||||
const name = cppTypeName(written);
|
||||
if (!name) return undefined;
|
||||
const { found, next } = lookupSegments(name, scopes, ref, context);
|
||||
const { found, next } = lookupSegments(name, scopesFor(name, scopes), ref, context);
|
||||
if (!found || typeof found !== 'object' || 'cls' in found) return undefined;
|
||||
if ('ambiguous' in found || next !== name.names.length || isAliasTemplate(found.alias, context)) return null;
|
||||
const text = declarationText(found.alias, context);
|
||||
@@ -689,7 +702,7 @@ function follow(
|
||||
context: ResolutionContext,
|
||||
hops: number,
|
||||
): CppAliasedType | null | undefined {
|
||||
const { found, next } = lookupSegments(name, scopesWithin(scope), ref, context);
|
||||
const { found, next } = lookupSegments(name, scopesFor(name, scopesWithin(scope)), ref, context);
|
||||
if (!found || typeof found !== 'object' || !('alias' in found)) return undefined;
|
||||
const alias = found.alias;
|
||||
if (hops >= MAX_HOPS) return null;
|
||||
@@ -721,11 +734,13 @@ function expand(
|
||||
if (!target || parameters?.has(target.names[0]!)) return null;
|
||||
const pointer = isCppPointerType(written);
|
||||
const names = [...target.names, ...rest];
|
||||
const further = follow({ names, templated: [...target.templated, ...restTemplated] }, scope, ref, context, hops);
|
||||
const further = follow({ names, templated: [...target.templated, ...restTemplated], global: target.global }, scope, ref, context, hops);
|
||||
if (further === null) return null;
|
||||
if (further) return { ...further, pointer: further.pointer || pointer };
|
||||
const cls = classNamed(names, scopesWithin(scope), context);
|
||||
// `::upb::Arena` is looked up from the global scope, whatever scope writes it.
|
||||
const from = target.global ? '' : scope;
|
||||
const cls = classNamed(names, scopesWithin(from), context);
|
||||
const classTemplate = !!cls && context.getNodesByQualifiedName(cls)
|
||||
.some((n) => isCFamily(n) && CLASS_KINDS.has(n.kind) && templateParametersAround(n, context).size > 0);
|
||||
return { target: target.names, rest: [...rest], scope, pointer, classTemplate };
|
||||
return { target: target.names, rest: [...rest], scope: from, pointer, classTemplate };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user