Repository navigation
fix(cpp): a call on a data member of the caller's class reaches its type's method, wherever the class is declared - #2533
Merged
Conversation
…ype's method, wherever the class is declared Receiver inference read back through the call's file and the header of the same name only, so a member of a class declared elsewhere — rocksdb's DBImpl is declared in db_impl.h, its methods in db_impl_write.cc, db_impl_compaction_flush.cc, db_impl_open.cc and more — was never read, and calls on it (`versions_->GetColumnFamilySet()`, `mutex_.Lock()`) were guessed by the receiver's name. - Where the scan leaves the calling function, the name is looked up as a data member of the function's class before anything outside it (cppCallersMember): the class's own declaration lines wherever it is declared (the declaration in the caller's own translation unit, of classes sharing a name), then the classes it derives from through their base edges, skipping a base that depends on the template's parameters. A gtest `TEST_F` / `TEST_P` body is a method of a class derived from its fixture, so `db_` there is the fixture's. - The member goes into the declaration record as the caller's own, with its class as the lookup site, so #2493's lookup, the library-type gate and #2440's held-type path apply. A member of a template parameter's type, or of an alias of one, and a name the scan saw an unreadable declaration of, stay as before. (isCppTemplateDependentType now also counts a std trait of a parameter: protobuf's `using MapFieldBase = std::conditional_t<kIsMutable, …>`.) - cppDataMemberIn reads only declarations outside parentheses and not followed by an argument list (method declarations have no nodes, so their parameters are in the class's lines), skips nested functions and classes, follows block comments across lines, and blanks string literals. - A member declared below an inline method that uses it, and a member of the class itself rather than of a class nested in it, are found the same way; cppExpressionType's own member fallback became redundant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 13, 2026
…e gives its type to calls on it (#2536) Receiver inference reads a C++ declaration as a type followed by the name. What may stand between them was read as the type: in rocksdb's `Iterator* const iter = iter_guard.get();` the type was `const`, which names nothing, so the scan went on to an earlier function's `iter`, and in protobuf's `io::EpsCopyOutputStream* PROTOBUF_NONNULL stream` the type was a class named `PROTOBUF_NONNULL`. A name an annotation follows (leveldb's `MemTable* imm_ GUARDED_BY(mutex_);`) was not read at all. - The declarator regex (and the range-for type tail) reads past a cv- qualifier or `__restrict` before or after a `*` or `&` (`T* const`, `T const&`), the nullability annotations after one (abseil's and clang's, and the ALL-CAPS macros c-cpp.ts already blanks for the parser), and an ALL-CAPS annotation macro's call after the name. - matchCppDeclarator hands the readers the type without those annotations (cppDeclaratorType); cv-qualifiers stay, since what reads a type reads past them. An ALL-CAPS word counts as an annotation only after a `*` or `&`, so a type's own name stays (`OS_HANDLE* const h`), and template arguments are kept as written. - The call's own file, its same-named header, a class's members and a range-for's variable are read that way; the local-name check for bare calls reads past the qualifiers too. - #2533's test that a method's parameter is no member used `T* const` locals, which the scan couldn't read; it now declares no local, since one the scan reads decides first. 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>
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
C++ receiver inference reads back through the call's file, then the header of the same name (
foo.cc→foo.h). A data member of the calling method's class declared anywhere else was never read, and calls on it were guessed by the receiver's name.facebook/rocksdb declares
DBImplindb_impl.h, while its methods live indb_impl_write.cc,db_impl_compaction_flush.cc,db_impl_open.ccand more. On main before #2532 (c19f0284), 684 of the 686 calls onx_receivers in those files were name guesses at 0.65/0.7 (versions_->GetColumnFamilySet()at 0.7), and 385 more were unresolved.Follows #2532 (merged as
79cd737b), which this was built and measured on.What changed (name-matcher.ts)
Member lookup at the caller's boundary. Where the backward scan leaves the calling function, the name is looked up as a data member of the function's class before anything outside it, as C++ does: block scopes, then the class and its bases, then namespaces. This is
cppCallersMember:cppDataMemberIn, memoized per declaration and name);cppScopeDeclarations);TEST_F(Fixture, …)/TEST_Pbody is a method of a class derived from its fixture, sodb_there is the fixture's member (cppTestFixture).An earlier function's local of the same name, which the old scan reached first, no longer wins over the member.
Same record, same rules. The member goes into the declaration record as the caller's own, with its class as the lookup site (
declared). So fix(cpp): a call on a declared pointer, value or reference reaches the class its type names, not a namesake #2493's lookup, the library-type gate, fix(cpp): a->call on a smart pointer or optional reaches the type it holds #2440's held-type path (std::unique_ptr<VersionSet> versions_) and fix(cpp): a call on anautovariable or a chained call reaches the class C++ finds, not a namesake #2532's chains all apply.What stays as before:
TComparator cmp_);using Shard = typename ClockCache::Shard;, or protobuf'susing MapFieldBase = std::conditional_t<kIsMutable, …>, which madeisCppTemplateDependentTypecountstdtraits too);auto mutex_ = Make();).Reading members correctly:
DB::CreateColumnFamilies(…, std::vector<…>* column_families)read as a member ofDBImpl.cppCodeOftakes a code line that starts with a dereference (*first_key, scratch);) for a comment, which left the paren depth open for the rest of the class.cppExpressionType's own member fallback (from fix(cpp): a range-based for loop overautoelements has the element type of its range #2492, for loop ranges) is now redundant and was removed.Validation
Site-keyed edge diffs (
diff-sites.mjs) against #2532's tip (978709dc; main's79cd737bhas the same tree), both arms built the same way, kernel included:Against
c19f0284, main before #2532 (both PRs together): leveldb −7 / +75 / 51 / 48, fmt −13 / +143 / 47 / 81, rocksdb −897 / +5,510 / 8,060 / 6,035, protobuf −435 / +637 / 423 / 824. Nodes and files are identical.Removed, every group read:
rocksdb (695): calls on members of library types whose name guesses reached project classes:
result_map_.find(std::unordered_map) →toku::omt::find(104);saved_value_.capacity(std::string) →autovector(102);cf_name_.c_str→ gtest'sFilePath(78);compaction_queue_.empty(std::deque),std::set/std::optional/std::thread/std::mutex/std::counting_semaphore/std::arraymembers, smart pointers' ownget/reset.Plus
edits_.size()(10). Main's 0.9 there came from another class'sautovectormember above the method, while this class's ownstd::vector<VersionEdit> edits_is declared below it.protobuf (266):
const Descriptor* descriptor_,enum_,foo_file_), fix(cpp): a call on a declared pointer, value or reference reaches the class its type names, not a namesake #2493's policy;fmt (10) and leveldb (7):
std::vector/std::stringmembers, in fmt's vendored gtest and in leveldb'sDBIter.Retargeted, every group read, all wrong→right:
db_->…(ModelDBat 0.65 →DBat 0.9);IteratorBase,InternalIteratorBase,CompactionIterator), and 80MemTableRep::Iterator→IteratorBase(auto it = db_->NewIterator(…)in gtest bodies, the original namesake);write_batch_→WriteBatchWithIndex;TransactionDB/StackableDB, 299env_→SpecialEnv'sEnvWrapper;env.h/file_system.h, each with its owntarget_(0.9→0.9: the old scan took the nearesttarget_above, another wrapper's);ShortenedIndexBuilder,CompactionProxy,ThreadStatusUpdaterand so on.context_->…→compiler::java::Context(137 of them a 0.9 namesakehpb_generator::Context);TextFormat::FieldValuePrinter,GzipOutputStream::sub_stream_,ParserImpl::error_collector_.IteratorWrapper::iter_→Iterator,MemTableIterator::iter_→SkipList::Iterator,db_→DB.Added: calls on typed members, and chains through them (
db_->CompactRange(…).PermitUncheckedError()).Metadata-only: the same target, now at 0.9.
The A/B rounds also caught three mistakes before this version: parameters read as members, the comment guess, and an alias check that stopped at a mid-name alias (
MemTable::Table::Iterator). Each now has a test.Tests:
__tests__/cpp-member-receiver.test.ts, 10 cases:autovariable or a chained call reaches the class C++ finds, not a namesake #2532's code: the rocksdb reproduction; a member below an inline method plus one a base declares; a gtest fixture member; the class's own member, not a nested class's.CPU (rocksdb, full index, process CPU time, interleaved; main
c19f0284vs this branch on top of #2532): main 151.3 / 154.6 / 153.1 s vs 147.1 / 141.0 / 140.6 s. The member lookup is memoized per caller and name, and per class declaration; no measurable cost.Not in this PR
T* const name = …isn't read by the declarator pattern. That's older than this change: rocksdb'sIterator* const iter = iter_guard.get();.auto iter = NewIterator(…)) still go through the name strategies. Most of the 111 remaining rocksdbMemTableRep::Iteratoredges come from these; the other 42 arememtable.cc's own, and right. There were 342 before fix(cpp): a call on anautovariable or a chained call reaches the class C++ finds, not a namesake #2532 and 181 after it.🤖 Generated with Claude Code