Repository navigation
fix(cpp): a call on an auto variable or a chained call reaches the class C++ finds, not a namesake - #2532
Merged
Conversation
…class C++ finds, not a namesake #2493 resolved calls on declared pointers, values and references by C++ lookup. Calls whose receiver type came from an initializer or a return type still went by the type's last name: - An `auto` local now has the type its initializer has, as written where that is declared: a cast or smart pointer written out (`std::unique_ptr<Iterator>(…)`, `static_cast<InstrumentedMutex*>(arg)`), `new` / `std::make_unique` / a construction of a class, or what a function or method is declared to return (`txn->GetIterator(…)`), with the initializer read to its `;` across lines. Its calls then go through matchCppDeclaredTypeCall from that site. A type that depends on a template parameter (`T*`, `std::shared_ptr<T>`) is left to the old path. - A chained call `a->b()->c()` looks `c` up on the class C++ finds for the written return type of `b`, through a smart pointer too (matchCppChainOnWrittenType), before the last-name path. - A type from outside the project gets no project method: a std/abseil library type (#2413's gate, now applied before the last-name match, so a namesake class like fmt's own `ostream` no longer wins), and a capitalized type under a namespace the project declares no type in (`absl::Status`, which reached `upb::Status::ok`). - A bare call in a method types an `auto` local from the class's own or inherited method; a `template <…>` header followed by macros or specifiers still names its parameters; a trailing annotation macro (`T* PROTOBUF_NONNULL`) is no part of a return type; and memoized `auto` types are worked out with a fresh depth budget, so a deep chain that reads a local first no longer leaves it untyped for its other calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 13, 2026
…xture's method (#2537) The index holds a gtest `TEST_F(Fixture, Name)` / `TEST_P` body as a function named `TEST_F`; gtest makes it a method of a class derived from `Fixture`. #2533 gave such a body the fixture's data members (cppTestFixture), not its methods. rocksdb's tests call `DBTestBase`'s `Put(…)`, `Flush()`, `Key(i)` and `NewIterator(…)` bare, and those calls were guessed by name, often reaching a namesake in another class at 0.4 (`ColumnFamilyTestBase::Put`); `auto iter = NewIterator(…)` gave `iter` no type, so `iter->SeekToFirst()` reached the nested `MemTableRep::Iterator`. - cppImplicitThisMethod is #2479's lookup, taken out of matchCppImplicitThisCall: the caller's class, its bases, the classes around it, with cppTestFixture as the owner of a gtest body. matchCppImplicitThisCall keeps the local-shadowing check for the link. - cppExpressionType types a bare call by the same lookup, as on `this`, instead of the class's first declaration. Of two classes sharing a name in different test files it reads the caller's own, and in a template a dependent base a using-declaration names still counts, as before (protobuf's `using RepeatedFieldWrapper<T>::MutableRepeatedField;`). `this` in a gtest body points to the fixture. - A `->` call on a value of a type from outside the project no longer takes the type's last name for the class it is in (isCppArrowThroughOutsideType); the name strategies decide. matchCppDeclaredTypeCall steps aside for `->` on a value before #2532's outside-type check, so typing JsonTest's `auto m = ToProto<Any>(…)` (an `absl::StatusOr<Any>`) had sent `m->value()` to protobuf's own `hpb::StatusOr::value` at 0.9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 15, 2026
…hares its last name, and rocksdb::DB is RocksDB's own (#2567) A C++ receiver declared with a namespace-qualified type is from outside the project only when no class of the project can be what that name names. Three cases read that wrong on main: - A namespace a macro stands for. RocksDB declares its classes in `namespace ROCKSDB_NAMESPACE` under `#define ROCKSDB_NAMESPACE rocksdb`, so the index names them `ROCKSDB_NAMESPACE::DB`, and #2532's outside rule (cppProjectScopeNames) never saw `rocksdb`: `rocksdb::DB* db; db->Put(…)` got no edge. cppProjectScopeNames now adds the name a single-name macro stands for, in both directions (cppNameMacros, the map cppNamespaceMacros already builds). - A lowercase outside type beside a project namesake. isCppLibraryType (#2413) took a non-std lowercase type as the library's only when no project type had its name at all, so `absl::flat_hash_set<int> seen; seen.insert(…)` reached a project's own `util::flat_hash_set::insert`. It is now also the library's when every project type of that name is a namesake (cppFindsOnlyNamesakes). - A capitalized type whose last name finds only namesakes, on the paths #2493's call-site rules leave to the last-name match: a declaration read outside the calling function (protobuf's `absl::Status _status` in its RETURN_IF_ERROR macro body reached `upb::Status::ok`; `auto bytes = ::hpb::Serialize(…); bytes.value()` reached `hpb::StatusOr::value`), and a namespace the project uses (its generated `DescriptorProto::ExtensionRange`, which the index doesn't hold, reached `Descriptor::ExtensionRange::options`). That call now gets no edge, as isUndeclaredTypeName rules where no class shares the name; `->` on a value still goes to the name strategies, and a template parameter's member is left alone. cppFindsOnlyNamesakes: every type of the last name, and every owner of a method of the called name (the index can hold a class's methods without the class), has a qualified name that ends unlike the written one, neither the other's tail; namespace aliases are expanded and macro-named namespaces read both ways. A name a project type qualifies, an unqualified name and an unbalanced `<>` are never judged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 15, 2026
… 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
#2493 made a call on a declared pointer, value or reference resolve on the class C++ name lookup finds for its type. Three kinds of typed receivers still went by the type's last name, so a namesake class could win:
autolocals. The type came frominferCppAutoInitializerType, which reads onlynew X,X(…),Class::fn(…)andmake_unique<X>, and keeps only the last name. When it couldn't read the initializer, the scan went on to an earlier declaration of the same name, often in another function. In facebook/rocksdb,auto iter = std::unique_ptr<Iterator>(…)andauto it = db_->NewIterator(…)read an earlier test'sIterator* iter, anditer->SeekToFirst()reached the nestedMemTableRep::Iterator(342 edges on main).a->b()->c()). The inner call's return type was its last name: protobuf's Java generator reached the C# generator'sEnumGenerator, and a chain returningabsl::Statusreachedupb::Status.absl::Status status; status.ok()reachedupb::Status::ok(the fix(cpp): a call on a declared pointer, value or reference reaches the class its type names, not a namesake #2493 follow-up), and fmt'sstd::ostream& os; os.flush()reached fmt's ownostream::flush.What changed (name-matcher.ts)
autolocals (cppAutoLocalType) take the type of their initializer as written where it is declared (cppExpressionType), and calls on them go throughmatchCppDeclaredTypeCallfrom that site.cppExpressionTypenow also reads:static_cast<T*>(…),std::unique_ptr<T>(…);new T(…),std::make_unique<T>/make_shared<T>, and a constructionT(…)of a class C++ lookup finds;auto* h = micro_rep();).The initializer is read up to its
;, across lines:auto* op = opts->merge_operatorcontinues on the next line as->CheckedCast<Foo>(). A type that depends on a template parameter (T*, or a library template of one likestd::shared_ptr<T>) is left to the old path; a smart pointer to one of the project's class templates (std::unique_ptr<ExternalTableReaderBase<Mode>>) is not. Without the template arguments substituted, overloads like protobuf'sDownCastMessage<T>(const T&/T*/std::shared_ptr<T>) can't be told apart.Chains (
matchCppChainOnWrittenType, tried before the last-name path):cis looked up on the class C++ finds forb's written return type, through a pointer or smart pointer. The inner receiver's operator is read from the source, since the extractor spells->as.. When the type can't be read or placed, the last-name path decides as before.Outside types get no project method:
matchCppDeclaredTypeCall, before the last-name match, so a namesake class can't win first. The old gate inmatchMethodCallhad the same conditions and was removed.isCppOutsideQualifiedType), is from outside the project, for chains too. The names come from type nodes only, because protobuf's specializations parse as methods namedgoogle::protobuf::absl::Cord.stdand lowercase names stay fix(cpp): a receiver declared as a std or other outside type with a lowercase name calls that type's own member #2413's business:std::conditional_t<…, Slice>names a project type.Small fixes found by the A/B:
template <…>header followed by macros or specifiers still names its parameters (collectTemplateHeader).T* PROTOBUF_NONNULL) is no part of a written return type.autolocals and loop elements are worked out with a fresh depth budget. A deep chain that read a local first left it untyped for that local's other calls (cfd->current()in rocksdb's compaction test).elementfield is nowdeclared, since it also holds anautolocal's type.Validation
Site-keyed edge diffs (
diff-sites.mjs), mainc19f0284vs this branch, both built from the same tree (kernel included):Nodes and files are identical between the arms.
Removed, every site triaged:
std::vector/std::string/std::optional/std::unique_ptr::get, e.g.blob_files.empty()onBlobFilesreachedCompactionInputFiles::empty.With(e1).With(e2),mock->Inner()->Inner()), checked mechanically.auto* state = new IteratorState(…)whereWriteUnpreparedTxnDB::IteratorStateis defined out of line and indexed<anonymous>, so its typed receiver gets no method. A declaredIteratorState* statebehaves the same way on main, and the name guess happened to be right.absl::Status/absl::StatusOrreceivers (8 of them chains):upb::Status::ok,hpb::StatusOr::ok,SymbolCheckerError::message.DescriptorProto::ExtensionRangehad reachedDescriptor::ExtensionRange).os.flush()onstd::ostream&, and 2 same-edge-kept sites.Retargeted, every group read, all wrong→right:
ArenaWrappedDBIter→IteratorBaseand 160MemTableRep::Iterator→IteratorBase(auto iter = std::unique_ptr<Iterator>(…));SharedBlobFileMetaData→BlobFileMetaData;port::Mutex→InstrumentedMutex(static_cast<InstrumentedMutex*>(arg)),ProtectionInfoKVOC→ProtectionInfoKVOS(a class template's return type),TableReader,Transaction,FileSystem,Envand so on._GetDescriptor(self)->index()in each pyext namespace now reaches that namespace's descriptor class, rather thanDescriptor'sindex;micro_rep()→MicroRep, notLargeRep.Added:
it->key().data()→Slice(the namesake'skey()returnedconst char*),target()->…,dbfull()->…; protobufGetDescriptor(self)->FindFieldByName(…).autolocals.Tests:
__tests__/cpp-auto-local-receiver.test.ts, 12 cases:CPU (rocksdb, full index, process CPU time, interleaved): main 132.1 / 142.1 / 146.9 s vs this branch 130.6 / 148.7 / 130.1 s. No measurable cost.
Not in this PR
TEST_F/TEST_Pbodies don't have the fixture's members:auto iter = db_->NewIterator(…)there stays untyped. The follow-up PR (data members declared in another header) adds fixture members.auto iter = NewIterator(…)) would need the fixture's methods: about 50 rocksdbMemTableRep::Iteratoredges remain because of them.🤖 Generated with Claude Code