Skip to content

fix(cpp): an alias of a type written from the global scope names that global type - #2568

Merged
colbymchenry merged 1 commit into
mainfrom
claude/cpp-alias-namesake-receiver
Oct 15, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/cpp-alias-namesake-receiver

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

What

This is the hpb/arena.h follow-up from #2567. protocolbuffers/protobuf's hpb declares class Arena { … backend::Arena arena_; }, where:

  • namespace backend = hpb::internal::backend::upb; (in hpb/multibackend.h);
  • namespace hpb::internal::backend::upb { using Arena = ::upb::Arena; } (in hpb/backend/types.h).

So arena_.Fuse(other.arena_) in hpb::Arena::Fuse should reach upb::Arena::Fuse. On main, hpb::Arena::Fuse, IsFused and RefArena each linked to themselves, at 0.9.

Cause: cppTypeName drops a leading ::. Following the alias looked its target up as upb::Arena from the alias's own scope, hpb::internal::backend::upb, where the nearest upb is that namespace itself. The alias named itself, the lookup ended on no class, and the call fell back to the type's last name. That fallback prefers the call site's file, which here is the calling method.

Fix: TypeName records a leading :: (global), and a global name is looked up from the global scope alone in each place that resolves one:

cppClassNamed needed nothing, since its callers already pass only the global scope for :: names. A receiver's declared type never carries a leading ::, because the declarator pattern starts at a letter, so nothing changes there.

Validation

Tests: __tests__/cpp-global-alias-target.test.ts, 4 tests, all failing on main:

  • the hpb chain;
  • an alias to ::upb::Arena beside a namespace-local upb::Arena (main: hpb::upb::Arena::Fuse);
  • an auto variable typed through such an alias (main: the caller's file's hpb::Arena::Fuse);
  • a base class written ::lib::Options beside a nested app::lib::Options (main: app::Inner::Run).

All 42 C/C++ test files pass (396 tests). Full suite: 7,123 passed, 0 failed (82 skipped).

Mutations: each change, switched off, fails its own test.

Mutation Tests that fail
global never set all 4
follow lookup scopes hpb chain, auto
expand's follow global hpb chain
expand's lookup scope namespace-local namesake
base clause ::lib::Options
cppAliasedTypeWritten auto

Site-keyed A/B (main 00d7430 vs this branch, kit %TEMP%\cgstd, diff-sites.mjs): leveldb, fmt, redis and rocksdb are byte-identical. protobuf has exactly 3 retargets, all right:

  • hpb/arena.h:38 arena_.Fuse: hpb::Arena::Fuse (itself) → upb::Arena::Fuse
  • hpb/arena.h:39 arena_.IsFused: hpb::Arena::IsFused (itself) → upb::Arena::IsFused
  • hpb/arena.h:50 arena_.RefArena: hpb::Arena::RefArena (itself) → upb::Arena::RefArena

CHANGELOG: #2399's [Unreleased] alias bullet was extended rather than given a new one, since it isn't released yet.

🤖 Generated with Claude Code

… global type

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>
@colbymchenry
colbymchenry merged commit 759acbb into main Oct 15, 2026
@colbymchenry
colbymchenry deleted the claude/cpp-alias-namesake-receiver branch October 15, 2026 04:21
colbymchenry added a commit that referenced this pull request Oct 15, 2026
…there, and a far lowercase outside type reaches no namesake (#2569)

The two follow-ups #2567 and #2568 left open.

A receiver declared with a leading `::` lost it: the declarator regex
started a type at a letter, so `::upb::Arena& a` inside `namespace hpb`
was looked up from `hpb` and found a namespace-local `hpb::upb::Arena`.
The declared type now keeps a leading `::` where one can stand (not after
a name, a template's arguments or a `)`, so `SkipList<Key,
Comparator>::Iterator iter` is not read as `::Iterator`), and
resolveCppAliasedType no longer takes a function-local `using Foo = Bar;`
for `::Foo`.

A lowercase outside type declared outside the calling function still used
the last-name typed match: #2413's library rule skips such declarations,
and #2567's namesake gate took capitalized types only. So a far
`absl::flat_hash_set<int> seen` linked `seen.insert(v)` to the project's
`util::flat_hash_set::insert`, and a far `std::string text` linked
`text.clear()` to a global project `string` class. The gate now covers
lowercase types, which are decided as when no class shares their name,
and cppFindsOnlyNamesakes reads a `std::` name exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant