A C++ call written through a class, `X::m()`, whose class has no node in the
index fell through to the last strategy of matchMethodCall, which scores
every method named `m` by the words its class shares with `X` and picks one.
protocolbuffers/protobuf declares its descriptor messages in descriptor.pb.h,
which is over the indexer's size cap, so `FeatureSet::default_instance()`
linked to `UnknownFieldSet::default_instance`, `FeatureSetDefaults::GetDescriptor()`
to `JavaFeatures_NestInFileClassFeature::GetDescriptor`, and the options
messages' `default_instance()` to `pb::enumvalue::JsonEnumValueOptions`'. A
class whose name only shares a word with `X` is another class: one named `X`
was looked for first, by qualified name and by class name.
That guess is now dropped when `X` is a capitalized name (isUndeclaredTypeName)
that no C or C++ class, struct, union, enum, alias or namespace node has, and
that the calling file makes nothing of itself (cppNamesBroughtIn): not a
template parameter of any `template <…>` head in it, not an alias it declares
(a function-local `using FD = FieldDescriptor;`, a typedef, or one a macro it
expands declares, like rocksdb's IMPORT_RIBBON_IMPL_TYPES), not a namespace it
opens, aliases or uses, and not googletest's TypeParam or TestFixture in a
typed test. The one method of a name in the project is still taken as before.
The scans read the file once (cppTypeNamesDeclared in cpp-type-aliases.ts,
shared with the template-head reader) and the project's headers once for
macro bodies, only when a guess would otherwise be returned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Overload fit by argument type (cppArgumentTypeChoice) scored a `const T*`
parameter and its `T*` twin alike, and the one declared first won: protobuf's
`DynamicCastMessage<T>(ptr.get())` on a `std::shared_ptr<Message>` took the
`const MessageLite*` overload.
- cppConstTwinChoice: of the best-fitting overload's twins, alike but for
the `const` of a pointer or reference parameter, the one that adds `const`
to the fewest arguments.
- cppWrittenTypeFit: a pointer or reference to a non-const type never takes
a const object, nor a reference a value a call returns (cppNamesObject
tells a named object from a call's result); an object is no pointer, nor a
pointer to characters a pointer to another type (or the reverse). None of
that for an argument of a template parameter's type, and the pointer and
`const` rules not for a parameter the function's own template parameter
types (it deduces the `const`).
- The argument's type keeps a `const` its declaration writes in front
(`const Buffer* b`, `constexpr char key[]`), which the declarator regex
leaves out; an array is a pointer to its element; `&ref` is a pointer to
the referee; cppLibraryElementOf keeps a `const` in a holder's template
argument (`std::shared_ptr<const Message>`).
- Template headers are read 8 lines up, not 3 (fmt's `join`).
A/B on the cgstd kit against main 0893606e: leveldb and redis identical;
fmt 14, rocksdb 25 and protobuf 194 calls retargeted, each triaged against
C++'s choice (const twins, objects and arrays off the wrong pointer
overloads, temporaries to `T&&`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
protobuf's `using ConformanceRequestSetting =
BinaryAndJsonConformanceSuite::ConformanceRequestSetting;` names a class the
suite inherits from `ConformanceTestSuite`. An alias was followed only to a
class declared under exactly the name it spells, so constructions through it
stopped at the alias. Its target is now looked up a segment at a time through
each class's bases, as C++ does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A class, struct or union whose declaration writes its name in a scope —
leveldb's `struct SkipList<Key, Comparator>::Node { … }`, rocksdb's
`struct BlockBasedTable::Rep { … }` — was indexed as `<anonymous>`, so its
members belonged to no class anyone names and a call on a `Node*` or `Rep*`
was guessed by the method's name. Such a class now takes its own name,
qualified under the written scope the way an out-of-line method is, and its
members are qualified under it, in the TS extractor and the kernel alike. A
specialization (`struct fmt::formatter<Answer>`) stays unnamed: named after
its template, it stood in for the template and every other specialization.
Constructions are recorded by the type's last name, so with `DBImpl::Writer`
named, leveldb's `new log::Writer(…)` in `DBImpl` methods went to it. A C++
construction now makes the class C++ lookup finds for the type as written at
the construction (matchCppConstruction): read with its template arguments
(`new Outer<int>::Inner(…)` makes an `Outer::Inner`), at 0.9, or 0.7 when it
is only the class of that name the file can see; an enum lookup finds; through
an alias the calling function declares, the class it names; and nothing for a
template parameter, a member of a type from outside the project or a local
alias of one. Of several classes or aliases the file declares under one
qualified name (the local classes of separate gtest TEST_F bodies, the members
of unnamed specializations), the one declared around the construction. A `<`
or `>` in parentheses inside template arguments is now read as a comparison.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#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>
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>
#2439 typed a range-for variable when the loop names its type. A loop
over `auto` elements names none, so receiver inference kept scanning as
for an `auto` local with an unreadable initializer: it reached an earlier
declaration of the same name, often another variable's, or fell through
to the guess by the receiver's name. facebook/rocksdb's db_impl_write.cc
loops `for (auto cfd : cfds)` over a `const autovector<ColumnFamilyData*>&`.
The loop's range is now read from its header and typed as written,
template arguments and all, since the index keeps only a return type's
last name:
- a name the calling function declares, or a data member of its class
read from the class's own lines (`versions_` from db_impl_write.cc);
- an `auto` local's initializer;
- `*x`, `x.m`, `x->m`, `this`, and calls whose declaration writes their
return type, free or on the object's class (own or inherited);
- aliases followed with their template arguments (cppAliasedTypeWritten);
- the element: a standard or abseil sequence container's first template
argument, protobuf's RepeatedPtrField and rocksdb's autovector's, or what
a project class's begin() returns, dereferenced (ColumnFamilySet's
iterator's operator* gives ColumnFamilyData*). Nullability annotations
are dropped.
The element goes through the same declaration record as any declared
receiver, so the typed path, #2413's gate for a standard-library element
and #2440's held-type lookup for `->` on a smart-pointer element apply
unchanged. Classes are looked up with #2440's cppClassWritten, methods
through its cppMethodOf, and matchCppHeldTypeCall now reads its held type
through the shared cppStdHeldType.
A/B on main 8a5ba3d0, site-keyed: leveldb identical; fmt +1 / 3
retargeted; rocksdb -68 / +173 / 104 retargeted / 231 confidence only;
protobuf -13 / +25 / 7 / 59. Every removal and retarget was a wrong
guess; 4 of rocksdb's additions reach MemTableRep::Iterator through the
typed path's last-name lookup, as a declared `Iterator* it` does on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A C++ call made with `->` on a receiver the calling function or its class
declares as a `std::unique_ptr<T>`, `std::shared_ptr<T>` or
`std::optional<T>` now resolves on `T`: on the class C++ name lookup finds for
it where the call is written, or on a class that class derives from. Before,
receiver inference read the type as `unique_ptr`, found no method, and the
call fell through to the guess by method name: rocksdb's
`std::unique_ptr<Iterator> iter; iter->Valid()` reached
`ArenaWrappedDBIter::Valid`.
The held type goes through #2407's lookup (`cppClassWritten`, extracted from
`matchCppSupertype`), not `resolveMethodOnType` on its last name: rocksdb has
22 classes named `Iterator`, and the one the calls mean inherits its methods
from `IteratorBase`, so a last-name lookup reaches `MemTableRep::Iterator`.
The method is taken from that class or its bases through the class's own
extends edges (`getSupertypeNodes`). A template parameter, a library type or
an alias of one gets no edge; a type the project doesn't declare, or an alias
the lookup can't follow, leaves the call as it was.
The namespace-macro helpers move from name-matcher.ts to cpp-namespaces.ts
unchanged, so name-matcher can import cpp-supertypes without a cycle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
normalizeCppTypeName cut each `<…>` at its first `>`, so a declared type
with template arguments nested in its template arguments kept debris:
`autovector<std::pair<int, FileMetaData*>>` read as `autovector >` and
`Striped<CacheAlignedWrapper<port::Mutex>>` as `Striped >`. No class has
such a name, so the call was never resolved on the receiver's class. A
capitalized debris name made isUndeclaredTypeName take the type for an
outside one and silence the call (rocksdb's `mutex_.Get(key)`), and a
lowercase one fell to a guess by the receiver's words (rocksdb's
`files_marked_for_compaction_.clear()` reached `CompactionInputFiles::clear`).
Template arguments are now stripped with their nesting by
cpp-type-aliases' depth-tracking helper, exported as
stripCppTemplateArguments; cpp-supertypes' identical private copy now
imports it. A `>` that closes no `<` is the end of a declaration begun on
an earlier line (`std::unique_ptr<BlobContents>>>& blob_reqs` under
`autovector<std::pair<…,`): a depth strip alone would read the argument's
type there, so such text names no type. The scan back to the receiver's
declaration then marks it shadowed (#2413), as it does for a declaration
whose type it can't read, so a member or earlier variable of the same
name found further up doesn't decide the call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A C++ base class (`class DynamicMessage final : public Message`) was
resolved by its name alone, so a base named like a class of another
namespace, or of another language, bound to whichever namesake ranked
first. In protocolbuffers/protobuf, `DynamicMessage`, `MapEntry`,
`ZeroFieldsBase` and two arena_unittest classes derived from
`json_internal::ResolverPool::Message`; before #2397 they derived from the
PHP extension's C struct `Message`. The Go framework resolver claimed
those: protobuf ships Go examples, so the resolver is detected, its
`resolve()` runs for every language's refs (`languages` only gates
extraction), its PascalCase-struct rule found the C struct, and the
framework language gate lets C++ reach C. google/leveldb's and rocksdb's
iterators derived from an unrelated nested `Iterator` (`SkipList::Iterator`,
`WriteThread::WriteGroup::Iterator`), and leveldb's test comparators from a
`Comparator` struct that skiplist_test.cc declares for itself.
C and C++ inheritance refs now resolve in their own matcher
(cpp-supertypes.ts), ahead of every other strategy and with no
fallthrough, so no framework resolver, import strategy or name matcher sees
them, and only C and C++ declarations are candidates. The base is looked
up the way C++ looks it up:
- from the scope the deriving class is declared in, outwards, using the
lookup cpp-type-aliases.ts (#2399) already has: a class scope with its
bases, a qualified name a segment at a time, an alias followed to the
class it names; `::Base` from the global scope only;
- then through what the file writes before the class: namespace aliases
(`namespace _pbi = ::google::protobuf::internal;`), using-declarations
and using-directives, and fmt's macro-opened namespaces;
- a class a .cc/.c file defines belongs to that translation unit;
- a template parameter, or a member of one, names no class: no edge.
When none of that finds the class, the only visible C/C++ class with that
qualified tail is taken, since the index loses some classes' namespaces
(`class PROTOBUF_EXPORT FieldDescriptor`, classes after a partial
specialization); several are a guess, so no edge.
The base is read as written, template arguments included, so an alias
template (`bool_constant<…>`) is found. An alias that leads to no project
class (one of `std::integral_constant`, one each `#if` arm declares
differently) is linked itself, as before.
Validated with site-keyed edge diffs on leveldb, fmt, protobuf and
rocksdb, and redis, SDWebImage and gson as controls (dumps
byte-identical): first against main 02d22aeb, then again on main
dd706469 after #2405, #2408 and #2400 landed, with identical site
diffs (#2402, merged after, changes only Go's implements synthesis).
Every removed edge was wrong: 9 protobuf
`suite_.ReportFailure` guesses that reached `ForkPipeRunner`, cpp-override
edges from the wrong bases, and two rocksdb bases that are template
parameters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
google/leveldb's MemTable::Add calls `table_.Insert(buf)` on `Table table_;`,
declared next to `typedef SkipList<const char*, KeyComparator> Table;` in the
class. Receiver inference returned `Table`, no class `Table` has an `Insert`
(the one class `Table` is the unrelated leveldb::Table, and C++ type_alias
nodes record no aliased type), so the call fell through to the receiver-word
guess HandleTable::Insert. It now reaches SkipList::Insert.
cpp-type-aliases.ts reads the alias from source and looks it up the way C++
does: an alias declared earlier in the calling function, the caller's class
and its bases (read from the class's own base clause), then the namespaces
around it. A qualified name resolves a segment at a time (Table::Iterator is
SkipList::Iterator). Aliases of a template parameter, a dependent typename or
a decltype name no particular class. The constructor path uses the same
lookup, so `Table::Iterator iter(&table_);` constructs SkipList::Iterator and
an alias hides an outer class of its name.
When the aliased type lacks the method, a `.` call or a call through a
pointer on a receiver the caller itself declares gets no guessed edge. A `->`
through an iterator or smart pointer, a project class template (whose
specializations can add members) and declarations read from outside the
caller keep the previous behavior.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>