Repository navigation
fix(cpp): a call on a declared pointer, value or reference reaches the class its type names, not a namesake - #2493
Merged
Conversation
…e class its type names, not a namesake
Receiver typing resolved a C++ call on a variable, parameter or member by
the declared type's last name: `Iterator* iter; iter->Valid()` became
resolveMethodOnType('Iterator', 'Valid'), which takes any class named
`Iterator` that declares `Valid`. facebook/rocksdb has 22 such classes,
and the `Iterator` the code means inherits `Valid`, `Next` and `key` from
`IteratorBase`, so nested namesakes like `MemTableRep::Iterator` took the
calls (google/leveldb's went to `SkipList::Iterator`).
A declaration the calling function or its class writes, called with the
operator it takes (`->` on a pointer, `.` on a value or reference), now
goes through the same C++ lookup #2440 uses for smart pointers'
held types (cppClassWritten from the caller's scopes, then the file's
`using`s, aliases followed) and cppMethodOf (the class's own method, else
one through its own base edges):
- An alias #2399 followed from the caller (function-local typedefs
included) is the class it names, by qualified name. An `auto` loop's
element type (#2492) is looked up where its range's type is written.
- A class found only as the one class of that name the file can see, a
template parameter's type, a library type, or a type nothing visible
declares: the call goes on as before.
- A class with no such method gets no edge, unless it is a class template
or a base on the way is one the index doesn't link (a `decltype(…)`
base, an alias each `#if` arm declares differently), which may declare
it: then as before.
- Declarations read from elsewhere, or past one the scan couldn't read,
are unchanged.
cppClassWritten now tries the file's `using`s before the global scope:
protobuf's upb generator writes `using google::protobuf::Descriptor;`
inside its namespace, and the PHP extension declares a global C struct
`Descriptor` in a header.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 12, 2026
colbymchenry
added a commit
that referenced
this pull request
Oct 12, 2026
…only when its bases are known (#2530) matchCppHeldTypeCall (#2440) returned null (no edge) for a `->` call on a `std::unique_ptr`/`std::shared_ptr`/`std::optional` whose held class lacks the method, unless that class is a class template. #2493 found the gap in the same rule for raw pointers and values: a base the index can't link may declare the method. protobuf's `class FlatAllocator : public decltype(ApplyTypeList<FlatAllocatorImpl>(…))` has no extends edge to `FlatAllocatorImpl`, and rocksdb's `WritePreparedTxnDB` derives from an alias each `#if` arm declares differently. Both paths now share one verdict, cppLacksMethod: no class template, and cppBasesKnown (every base on the way is linked to a class, or is a library type). Otherwise the call goes on as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 13, 2026
…class C++ finds, not a namesake (#2532) #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
…ype's method, wherever the class is declared (#2533) 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 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>
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
A C++ call on a variable, parameter or member the calling function or its class declares now resolves on the class C++ name lookup finds for the declared type, not on any class sharing the type's last name.
Iterator* iter; iter->Valid()becameresolveMethodOnType('Iterator', 'Valid'), which takes any class namedIteratorthat declaresValid. facebook/rocksdb has 22 classes namedIterator, and the one the code means (ROCKSDB_NAMESPACE::Iterator) inheritsValid/Next/keyfromIteratorBase, so nested namesakes likeMemTableRep::Iteratortook the calls. google/leveldb'sIterator*calls went toSkipList::Iteratorthe same way.This routes the raw-pointer / value / reference typed path through the lookup #2440 added for smart-pointer held types (
cppClassWritten+cppMethodOf).What changed
matchCppDeclaredTypeCall(name-matcher.ts) runs right after C++ receiver inference, before the last-name typed match:CppReceiverDeclaration.callers, notshadowed), called with the operator the declaration takes (->on a pointer,.on a value or reference). Declarations read from elsewhere are untouched (fix(cpp): a receiver declared through a typedef or using alias calls the type it names #2399 measured that a blanket change there removes right edges).cppClassWrittenfrom the calling function's scopes, then the file'susings, aliases followed.cppAliasedClass).autoloop's element type (fix(cpp): a range-based for loop overautoelements has the element type of its range #2492) is looked up where its range's type is written (decl.element).cppBasesKnown: adecltype(…)base, an alias each#ifarm declares differently), which may declare it;cppClassWritten(cpp-supertypes.ts) now tries the file'susings before the global scope. protobuf's upb generator writesusing google::protobuf::Descriptor;insidenamespace upb::generator, and the PHP extension declares a global C structDescriptorin a header; the lookup took the C struct, which has no methods. C++ finds the using's name at the namespace's level first, and a TU can't see both.Validation
Site-keyed edge diffs (
diff-sites.mjs), main01ab45d0vs this branch, both built from the same tree (kernel included):No
extends/implementsedge moved in any repo, and no #2440 held-type site moved.Retargeted / added, grouped by (old owner → new owner), each pattern checked at the source:
MemTableRep::Iterator→IteratorBase1,081; gtest'sCartesianProductGenerator2::Iterator→ each generator's ownIterator8;TraceRecord::Handler→ the inherited nestedTraceRecordResult::Handler4; a class's own nestedStatistics/HandleImpl3; added: an anonymous-namespaceStatereaching itsBitFieldsbase'sGet46.hpb_generator::Context→compiler::java::Context186 /compiler::rust::Context180 /upb::generator::reflection::Context16;compiler::Parser→TextFormat::Parser7;hpb::SourceLocation→io::Printer::SourceLocation5 (throughusing SourceLocation = io::Printer::SourceLocation;); objectivec's ownFieldGeneratorMap/FileGenerator,io::ErrorCollector,upb::Arena,TextFormat::Printer,RepeatedFieldProxyBase, the unittest's ownSimpleErrorCollector.SkipList::Iterator→leveldb::Iterator261; addedWriteBatch::Iterate'shandler->Put/Delete→WriteBatch::Handler2.ROCKSDB_NAMESPACE, inherited nested classes, aliases); those were read by hand.Removed (class found, no such method):
FieldDescriptor,Descriptor,FileDescriptor, …), whose accessors the index doesn't hold (declared behindPROTOBUF_FUTURE_ADD_EARLY_NODISCARD), sofield->full_name()was guessed ontoupb::FieldDefPtr, generatedField,Type, … at 0.65–0.8. The rest:Symbol(macro-declared accessors), upb C structs, andJavaGenerator/python::Generatorplugin calls that went to another generator's setter. Every removed target's owner is unrelated to the receiver's class and its bases.MultiScanException& ex; ex.what()→ the Java bindings'KVException::what(23;whatisstd::runtime_error's), andm_owners->erase()on astd::set-basedTxnidVector→PreparedHeap::erase.Decisions measured along the way
InlineSkipList<C>::Nodeis indexed<anonymous>, soNodematchedFlushScheduler::Node; protobuf's generatedFieldOptionsmatchedTailCallTableInfo::FieldOptions). Main's guesses there were right 4 of 6 times in protobuf.template <typename BinaryHeap>instantiated with the classBinaryHeap).class FlatAllocator : public decltype(ApplyTypeList<FlatAllocatorImpl>(…))(95 rightFlatAllocatorImpl::PlanArrayedges kept) and rocksdb'sWritePreparedTxnDB : public WritePreparedTxnDBBase, an alias each#if USE_COROUTINESarm declares differently (13 sites, 11 of main's guesses wrong, 2 right; kept as before).usingbefore the global scope recovered the one right edge the first version lost (message->FindFieldByNumberin the upb generator).Tests
__tests__/cpp-declared-type-receiver.test.ts(8 cases):std::runtime_error), all pass here;usingbefore a global C struct (fails if the reorder is reverted — mutation-checked); a template parameter and a class another source file defines keep main's edges; adecltype(…)base keeps main's edge (fails if the base check is removed — mutation-checked); a declaration read from an earlier function stays as before.All 309 tests in the C++ resolution suites pass. Full suite: 6,922 passed, 0 failed, 82 skipped (551 files).
CPU (process CPU time of a full index, interleaved runs,
cpu-bench.sh): rocksdb main 144.1 / 141.7 / 143.3 s vs this branch 139.7 / 145.9 / 146.8 s; protobuf main 350.0 / 324.4 s vs 341.7 / 300.0 s. No measurable cost.Follow-ups (not in this PR)
->call on a smart pointer or optional reaches the type it holds #2440's held-type path returns no edge for a held class lacking the method without the base-completeness check added here; worth applying the samecppBasesKnownthere.upb_MiniTableField field = $field_init$;insideR"cc(…)cc"), 8 sites here.absl::Status s; s.ok()→upb::Status::ok) still falls through to the last-name match; that verdict was split off to a separate session's work, by agreement.🤖 Generated with Claude Code