diff --git a/CHANGELOG.md b/CHANGELOG.md index 384ddbee00..4722c5fb64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,13 +92,13 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - In C++, a base class like `Message` in `class DynamicMessage final : public Message` now links to the class C++ itself would pick: the one declared in the namespaces around the class, or brought in by the file's `using` declarations and namespace aliases, and the class a `typedef` or `using` alias names. Before, a base class was matched by its name alone, so it could land on a class of the same name in another namespace or another language: in protocolbuffers/protobuf, `DynamicMessage` derived from the JSON parser's internal `Message` class, and in google/leveldb and RocksDB, iterators derived from an unrelated nested `Iterator` class such as the skip list's. The wrong base showed up in `codegraph_explore`'s type hierarchy and in impact, and tied the base's methods to the wrong overrides. A template parameter used as a base no longer links to a class that shares its name, and a base written from the global scope, like `public ::testing::Test`, now links to its class. Re-index C++ projects after upgrading. - In C and C++, a header or source file named like a test, such as protocolbuffers/protobuf's `conformance_test.h` and `test_runner.h`, now counts as part of the code that `#include`s it, directly or through other headers. Before, calls from that code into it were dropped, so protobuf's conformance suites' `suite_.ReportFailure(…)` and `RunValidInputTest(…)` calls, and its unit tests' calls to the shared `TestUtil` helpers, linked to nothing. A test file that only tests include is still kept out of reach of the rest of your code. Re-index C and C++ projects after upgrading. - In C++, a class, struct or method now keeps the namespaces and the class it is declared in when the parser misreads something earlier in the file, such as an unknown macro in front of a member or in a class header. Before, everything after the misread code could lose them, or land inside a class it isn't in: in protocolbuffers/protobuf, `FieldDescriptor` was indexed without `google::protobuf::`, and in RocksDB, the `Opts` struct declared inside a cache table class was indexed outside it, so finding a class's base classes and the methods called on its objects fell back to guessing by name. Methods that had come loose from their class are its members again, and classes the misread code had hidden, like leveldb's POSIX file and environment classes, are now indexed. Re-index C++ projects after upgrading. -- In C++, a call on a variable, parameter or member declared as a standard-library or other outside type with a lowercase name, like `std::string`, `std::vector`, `std::set` or `absl::flat_hash_set`, no longer links to a project method that only shares the method's name, even one of a project class in another namespace with the type's name, like your own `util::flat_hash_set`. In google/leveldb, `saved_key_.clear()` on a `std::string saved_key_;` reached `Slice::clear`, and in protocolbuffers/protobuf, `proto.append(…)` on a `std::string` reached an unrelated `LeftoverBuffer::append`. The same goes for `->` through a raw pointer like `std::string* out`, while `->` through a smart pointer, iterator or optional still links to the type it holds. Comments also no longer pass for a variable's declaration, so more calls now link to the method of the type the variable is really declared with. Re-index C++ projects after upgrading. +- In C++, a call on a variable, parameter or member declared as a standard-library or other outside type with a lowercase name, like `std::string`, `std::vector`, `std::set` or `absl::flat_hash_set`, no longer links to a project method that only shares the method's name, even one of a project class in another namespace with the type's name, like your own `util::flat_hash_set`. A variable declared outside the calling function, like another function's or a file's `std::string text`, no longer links its calls to such a class either. In google/leveldb, `saved_key_.clear()` on a `std::string saved_key_;` reached `Slice::clear`, and in protocolbuffers/protobuf, `proto.append(…)` on a `std::string` reached an unrelated `LeftoverBuffer::append`. The same goes for `->` through a raw pointer like `std::string* out`, while `->` through a smart pointer, iterator or optional still links to the type it holds. Comments also no longer pass for a variable's declaration, so more calls now link to the method of the type the variable is really declared with. Re-index C++ projects after upgrading. - In C++, a class written with attribute macros the parser can't read now keeps its members. That covers several macros between `class` and the class name, like protocolbuffers/protobuf's generated `class PROTOBUF_EXPORT PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Any final`, a macro with arguments there, like RocksDB's `struct ALIGN_AS(64U) HandleImpl`, or one before a template specialization, and macros on a member: in front of it (`PROTOBUF_FUTURE_ADD_EARLY_NODISCARD`), between a pointer and its name (`PROTOBUF_NONNULL`), after its parameters or its name (`ABSL_ATTRIBUTE_LIFETIME_BOUND`, google/leveldb's `LOCKS_EXCLUDED(mu_)` and `GUARDED_BY(mu_)`), or alone on a line above a comment ({fmt}'s `FMT_BEGIN_EXPORT`). Before, protobuf's generated message classes were missing and their members, like `GetDescriptor`, `GetReflection` and `unknown_fields`, were indexed as functions outside any class, and leveldb's annotated methods were indexed under the annotation's name, so calls to them linked to nothing. Re-index C++ projects after upgrading. - In C++, a call on a range-based `for` loop's variable, like `suite->SetVerbose(verbose)` inside `for (ConformanceTestSuite *suite : suites)`, now links to the method of the type the loop declares. Before, the loop's declaration was never read, so the call was matched by the variable's name alone: in RocksDB, `log->file()` on a `log::Writer` went to `BlobLogWriter::file`, and a loop variable named like a member of its class was taken for that member. The variable counts only inside its loop. Re-index C++ projects after upgrading. - In C++, a call made with `->` on a variable, parameter or member declared as a `std::unique_ptr`, `std::shared_ptr` or `std::optional`, like `iter->Valid()` on a `std::unique_ptr iter`, now links to that method of the type it holds, or of a class that type inherits from, with the type looked up the way C++ does it, so a class of the same name nested elsewhere is not mistaken for it. Before, the call went to whichever project method shared its name: in facebook/rocksdb, `iter->Valid()` reached `ArenaWrappedDBIter::Valid` and `fs->GetFileSize(…)` on a `std::shared_ptr` reached a test's stand-in file system, and calls like `it->Next()` were often not linked at all. When what it holds is a template parameter or a standard-library type, like `std::unique_ptr>`, or a class that has no method of that name and neither do the classes it derives from, the call no longer links to a project method; a class derived from something that can't be followed, like a `decltype(…)`, may still have it, so that call is matched as before. Re-index C++ projects after upgrading. - In C++, a call a method makes with no receiver, or through `this->`, now links to the method C++ itself would call: one of the method's own class, else one the class inherits from a base class, else one of a class it is nested in. The same goes for a call in a lambda inside the method, and of a class's overloads the one the call's arguments fit is taken. Before, the call was matched by its name alone, so another class's method of that name could win because it was declared nearer or found first: in protocolbuffers/protobuf, every generated message's `Swap` and move assignment calling `InternalSwap(…)` reached `Any::InternalSwap`, and the `GetArena()` messages inherit from `MessageLite` reached an arena helper's `GetArena`, while googletest's `~linked_ptr() { depart(); }`, vendored in facebook/rocksdb, reached `linked_ptr_internal::depart`. Calls C++ doesn't look up in the class are matched as before: a free function's, a call to a parameter or local of that name, and a template's bare call into a base class that depends on its template parameters. Re-index C++ projects after upgrading. Thanks @danusha2345 for reporting the C++ case in #1952. - In C++, a call on the variable of a range-based `for` loop over `auto` elements, like `cfd->Ref()` inside facebook/rocksdb's `for (auto cfd : cfds)`, now links to the method of the type the loop goes over. That type is read from what the loop iterates: a `std::vector`, `std::set`, `absl::Span` or similar container, protocolbuffers/protobuf's `RepeatedPtrField` or RocksDB's `autovector`, whether a variable, parameter or member holds it, an `auto` variable is set to it or a function or method returns it, and a project class you loop over through its `begin()`, like RocksDB's `ColumnFamilySet`. Before, the call was matched by the variable's name, or by an earlier variable of that name: in RocksDB, `blob_file.GetBlobFileNumber()` over a list of `BlobFileGarbage` reached `BlobFileAddition::GetBlobFileNumber`, and in protobuf's Objective-C generator, calls over a list of `std::unique_ptr` reached other generators' methods. A call on an element of a standard-library type, like `file.find(…)` over a `std::vector`, no longer links to a project method, and `->` on an element held in a `std::unique_ptr` or `std::shared_ptr` reaches the type it holds. Re-index C++ projects after upgrading. -- In C++, a call on a variable, parameter or member declared as one of your classes, like `iter->Valid()` on an `Iterator* iter` or `it.Valid()` on a `const Iterator& it`, now links to that method of the class C++ itself would find for the type where the call is written, or of a class it inherits from. Before, the method was picked by the type's last name, so a class of the same name nested elsewhere could win: in facebook/rocksdb, calls on an `Iterator` reached `MemTableRep::Iterator`, which declares `Valid` and `Next` itself while the `Iterator` the code means inherits them, google/leveldb's reached `SkipList::Iterator`, and in protocolbuffers/protobuf the Java generator's `context->options()` reached the hpb generator's `Context` and `text_parser.Parse(…)` on a `TextFormat::Parser` reached the `.proto` parser. When that class and the classes it derives from have no method of that name, the call no longer links to an unrelated class's method that shares it, as protobuf's `field->full_name()` on a `FieldDescriptor*` did. A type is also found through a `using` declaration the file writes, like protobuf's `using google::protobuf::Descriptor;`, before a class of the same name declared at global scope elsewhere in the repository; this also applies to base classes and to the type a smart pointer holds. Re-index C++ projects after upgrading. +- In C++, a call on a variable, parameter or member declared as one of your classes, like `iter->Valid()` on an `Iterator* iter` or `it.Valid()` on a `const Iterator& it`, now links to that method of the class C++ itself would find for the type where the call is written, or of a class it inherits from. Before, the method was picked by the type's last name, so a class of the same name nested elsewhere could win: in facebook/rocksdb, calls on an `Iterator` reached `MemTableRep::Iterator`, which declares `Valid` and `Next` itself while the `Iterator` the code means inherits them, google/leveldb's reached `SkipList::Iterator`, and in protocolbuffers/protobuf the Java generator's `context->options()` reached the hpb generator's `Context` and `text_parser.Parse(…)` on a `TextFormat::Parser` reached the `.proto` parser. When that class and the classes it derives from have no method of that name, the call no longer links to an unrelated class's method that shares it, as protobuf's `field->full_name()` on a `FieldDescriptor*` did. A type is also found through a `using` declaration the file writes, like protobuf's `using google::protobuf::Descriptor;`, before a class of the same name declared at global scope elsewhere in the repository; this also applies to base classes and to the type a smart pointer holds. A type written from the global scope, like `::upb::Arena& arena`, is looked up there, so a namespace's own `upb::Arena` is not taken for it, nor is an alias of that name the calling function declares. Re-index C++ projects after upgrading. - In C++, text inside a string literal is no longer taken for a variable's declaration. That covers the C code protocolbuffers/protobuf's upb generator writes in a raw string (`R"cc( … upb_MiniTableField field = $field_init$; … )cc"`) and log messages like facebook/rocksdb's `"… in WriteBatch handler; …"`. Before, a later call like `field.ctype()` on the function's own `upb::FieldDefPtr field` parameter, or `handler->PutCF(…)` on a `WriteBatch::Handler* handler`, was matched against the type that text seemed to name, so it linked to nothing or to another class's method. Re-index C++ projects after upgrading. - In C++, a call on an `auto` variable now links to the method of the class C++ finds for the type its initializer gives it, as a call on a variable declared with that type does. The type is read from a cast or smart pointer written out, like facebook/rocksdb's `auto iter = std::unique_ptr(…)` or `auto* mutex = static_cast(arg)`, from `new` or `std::make_unique` of a class, and from what a function or method is declared to return, like the `Iterator*` of `txn->GetIterator(…)`, read to the end of an initializer that goes on to the next line. Before, such a variable took the type of an earlier variable of the same name, or none: in RocksDB, `iter->SeekToFirst()` reached `MemTableRep::Iterator` and `mutex->Lock()` reached `port::Mutex`. A chained call like `cfd->current()->storage_info()` now looks up the class the inner call is declared to return the same way, through a smart pointer too, so protocolbuffers/protobuf's Java generator no longer reaches the C# generator's `EnumGenerator`. A type from outside the project no longer links to a class of the project that shares its name: protobuf's `status.ok()` on an `absl::Status` reached `upb::Status::ok`, and fmtlib/fmt's `os.flush()` on a `std::ostream&` reached fmt's own `ostream::flush`. The same holds for a variable declared in a macro body, or typed by what a function in another file returns, and for a class the index doesn't hold whose name only another class shares: protobuf's `_status.ok()` in its `RETURN_IF_ERROR` macro reached `upb::Status::ok`, and `range.options()` on its generated `DescriptorProto::ExtensionRange` reached `Descriptor::ExtensionRange::options`. A namespace a macro stands for is the project's under either name: facebook/rocksdb declares its classes in `namespace ROCKSDB_NAMESPACE` with `#define ROCKSDB_NAMESPACE rocksdb`, so `db->Put(…)` on a `rocksdb::DB*` links to RocksDB's own `DB::Put`. Re-index C++ projects after upgrading. - In C++, a call on a data member of the calling method's class, like `versions_->GetColumnFamilySet()` in facebook/rocksdb's `db_impl_write.cc`, now links to the method of the member's type wherever the class is declared. Before, only the call's own file and the header of the same name were read for the member, and RocksDB declares `DBImpl` in `db_impl.h` while its methods are spread over `db_impl_write.cc`, `db_impl_compaction_flush.cc`, `db_impl_open.cc` and more, so such calls were guessed by the member's name. A member declared below an inline method that uses it, one a base class declares, and, in a gtest `TEST_F` or `TEST_P` body, one of the test's fixture, like RocksDB's `db_`, are found the same way, and a member of a class nested in the caller's class is no longer taken for the caller's own. A call on a member of a standard-library type, like `std::atomic shutting_down_`, no longer links to a project method that only shares the method's name. Re-index C++ projects after upgrading. diff --git a/__tests__/cpp-receiver-global-far-library.test.ts b/__tests__/cpp-receiver-global-far-library.test.ts new file mode 100644 index 0000000000..ae8f877f4f --- /dev/null +++ b/__tests__/cpp-receiver-global-far-library.test.ts @@ -0,0 +1,278 @@ +/** + * Two ways a C++ receiver's declared type reached a class it doesn't name. + * + * - A type written from the global scope. The declaration a call's receiver + * was read from lost its leading `::`, so `::upb::Arena& a` inside `namespace + * hpb` was looked up from `hpb` and found a namespace-local `hpb::upb::Arena` + * first; and a function-local `using Foo = Bar;` was taken for `::Foo`. + * - A lowercase type from outside the project, declared outside the calling + * function (another function's variable of that name, a file's global): its + * last name still found a project class sharing it, as + * `util::flat_hash_set::insert` for an `absl::flat_hash_set`, or a + * global `string` class's `clear` for a `std::string`. Such a declaration + * may be another variable's, so the call is guessed for as when no class + * shares the type's name, never linked to the namesake. + */ +import { describe, it, expect, afterEach } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { CodeGraph } from '../src'; +import type { Node } from '../src/types'; + +const roots: string[] = []; +afterEach(() => { + for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true }); +}); + +async function indexed(files: Record): Promise { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-global-far-')); + roots.push(root); + for (const [rel, content] of Object.entries(files)) { + fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true }); + fs.writeFileSync(path.join(root, rel), content); + } + return CodeGraph.init(root, { index: true }); +} + +function callable(cg: CodeGraph, qualifiedName: string): Node { + const node = [...cg.getNodesByKind('method'), ...cg.getNodesByKind('function')].find((n) => n.qualifiedName === qualifiedName); + if (!node) throw new Error(`no function or method ${qualifiedName}`); + return node; +} + +/** `calls` callees of a function or method, as `qualifiedName`. */ +function calls(cg: CodeGraph, caller: string): string[] { + return cg + .getCallees(callable(cg, caller).id) + .filter((r) => r.edge.kind === 'calls') + .map((r) => r.node.qualifiedName) + .sort(); +} + +const lines = (...l: string[]): string => [...l, ''].join('\n'); + +describe('a C++ receiver declared with a type written from the global scope', () => { + const ARENAS = { + 'upb/mem/arena.hpp': lines( + 'namespace upb {', + 'class Arena {', + ' public:', + ' bool Fuse(Arena& other) { return true; }', + '};', + '} // namespace upb', + ), + 'hpb/upb_arena.h': lines( + 'namespace hpb {', + 'namespace upb {', + 'class Arena {', + ' public:', + ' bool Fuse(Arena& other) { return false; }', + '};', + '} // namespace upb', + '} // namespace hpb', + ), + }; + + it('reproduction: `::upb::Arena& a` inside `namespace hpb` is the global `upb::Arena`', async () => { + const cg = await indexed({ + ...ARENAS, + 'hpb/join.cc': lines( + '#include "upb/mem/arena.hpp"', + '#include "hpb/upb_arena.h"', + 'namespace hpb {', + 'bool Join(::upb::Arena& a, ::upb::Arena& b) {', + ' return a.Fuse(b);', + '}', + '} // namespace hpb', + ), + }); + try { + expect(calls(cg, 'hpb::Join')).toEqual(['upb::Arena::Fuse']); + } finally { + cg.close(); + } + }); + + it('declared over two lines, and as a range-based for loop\'s variable', async () => { + const cg = await indexed({ + ...ARENAS, + 'hpb/fuse.cc': lines( + '#include ', + '#include "upb/mem/arena.hpp"', + '#include "hpb/upb_arena.h"', + 'namespace hpb {', + 'bool Pair(::upb::Arena& b) {', + ' ::upb::Arena', + ' paired;', + ' return paired.Fuse(b);', + '}', + 'void FuseAll(std::vector<::upb::Arena>& arenas, ::upb::Arena& b) {', + ' for (::upb::Arena& each : arenas) {', + ' each.Fuse(b);', + ' }', + '}', + '} // namespace hpb', + ), + }); + try { + expect(calls(cg, 'hpb::Pair')).toEqual(['upb::Arena::Fuse']); + expect(calls(cg, 'hpb::FuseAll')).toEqual(['upb::Arena::Fuse']); + } finally { + cg.close(); + } + }); + + it('a `::` after a template\'s arguments is no global scope: `Outer::Iterator it` is Outer\'s', async () => { + const cg = await indexed({ + 'src/outer.h': lines( + 'class Iterator {', + ' public:', + ' bool Valid() const { return false; }', + '};', + 'namespace ns {', + 'template ', + 'class Outer {', + ' public:', + ' class Iterator {', + ' public:', + ' bool Valid() const { return true; }', + ' };', + ' bool Scan() {', + ' Outer::Iterator it;', + ' return it.Valid();', + ' }', + '};', + '} // namespace ns', + ), + }); + try { + expect(calls(cg, 'ns::Outer::Scan')).toEqual(['ns::Outer::Iterator::Valid']); + } finally { + cg.close(); + } + }); + + it('is no alias of that name the calling function declares', async () => { + const cg = await indexed({ + 'src/use.cc': lines( + 'class Foo {', + ' public:', + ' void Run() {}', + '};', + 'class Bar {', + ' public:', + ' void Run() {}', + '};', + 'void Use() {', + ' using Foo = Bar;', + ' ::Foo f;', + ' f.Run();', + '}', + ), + }); + try { + expect(calls(cg, 'Use')).toEqual(['Foo::Run']); + } finally { + cg.close(); + } + }); +}); + +describe('a lowercase outside type declared outside the calling function', () => { + it('reproduction: an `absl::flat_hash_set` is not the project\'s `util::flat_hash_set`', async () => { + const cg = await indexed({ + 'util/hash_set.h': lines( + 'namespace util {', + 'template ', + 'class flat_hash_set {', + ' public:', + ' void insert(const T& v) {}', + '};', + '} // namespace util', + ), + 'util/list.h': lines( + 'class List {', + ' public:', + ' void insert(int v) {}', + '};', + ), + 'db/seen.cc': lines( + '#include "util/hash_set.h"', + '#include "util/list.h"', + 'void Open() {', + ' absl::flat_hash_set seen;', + '}', + 'void Mark(int v) {', + ' seen.insert(v);', + '}', + ), + }); + try { + expect(calls(cg, 'Mark')).toEqual([]); + } finally { + cg.close(); + } + }); + + it('a `std::string` is not a global project class named `string`', async () => { + const cg = await indexed({ + 'src/string.h': lines( + 'class string {', + ' public:', + ' void clear() {}', + '};', + ), + 'src/buffer.h': lines( + 'class Buffer {', + ' public:', + ' void clear() {}', + '};', + ), + 'src/text.cc': lines( + '#include ', + '#include "src/string.h"', + '#include "src/buffer.h"', + 'void Load() {', + ' std::string text;', + '}', + 'void Reset() {', + ' text.clear();', + '}', + ), + }); + try { + expect(calls(cg, 'Reset')).toEqual([]); + } finally { + cg.close(); + } + }); + + it('the project\'s own lowercase type is still its own', async () => { + const cg = await indexed({ + 'util/hash_set.h': lines( + 'namespace util {', + 'template ', + 'class flat_hash_set {', + ' public:', + ' void insert(const T& v) {}', + '};', + '} // namespace util', + ), + 'db/keep.cc': lines( + '#include "util/hash_set.h"', + 'void Open() {', + ' util::flat_hash_set mine;', + '}', + 'void Keep(int v) {', + ' mine.insert(v);', + '}', + ), + }); + try { + expect(calls(cg, 'Keep')).toEqual(['util::flat_hash_set::insert']); + } finally { + cg.close(); + } + }); +}); diff --git a/src/resolution/cpp-type-aliases.ts b/src/resolution/cpp-type-aliases.ts index a708e11ca4..49b341b992 100644 --- a/src/resolution/cpp-type-aliases.ts +++ b/src/resolution/cpp-type-aliases.ts @@ -531,7 +531,8 @@ export function resolveCppAliasedType(rawType: string, ref: UnresolvedRef, conte if (!name) return undefined; const caller = context.getNodeById?.(ref.fromNodeId); const scope = callerScope(caller); - const local = localAliasedType(name.names[0]!, caller, ref, context); + // (`::Foo` is no alias the calling function declares.) + const local = name.global ? undefined : localAliasedType(name.names[0]!, caller, ref, context); const resolved = local !== undefined ? expand(local, name.names.slice(1), name.templated.slice(1), scope, ref, context, 1, caller ? templateParametersAround(caller, context) : undefined) diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index f0d5ad222b..02f3266170 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -9187,8 +9187,14 @@ const CPP_FUNCTION_WRAPPER_TYPE = String.raw`(?:::\s*)?(?:std\s*::\s*(?:function * into `b`'s type. */ const CPP_TEMPLATE_ARGUMENTS = String.raw`<(?:[^<>;=(){}]|<(?:[^<>;=(){}]|<[^<>;=(){}]*>)*>)*>`; -/** A declared type as the declarator regex reads it, what it carries included: `const FieldDescriptor* PROTOBUF_NULLABLE*`. */ -const CPP_DECLARATOR_TYPE = String.raw`(?:${CPP_FUNCTION_WRAPPER_TYPE}|[A-Za-z_][\w:]*(?:\s*${CPP_TEMPLATE_ARGUMENTS})?)(?:\s*(?:${CPP_DECLARATOR_CV}|[*&]+(?:\s*${CPP_DECLARATOR_ANNOTATION})*))*`; +/** + * A declared type as the declarator regex reads it, what it carries included: + * `const FieldDescriptor* PROTOBUF_NULLABLE*`, and a leading `::` that names + * the global scope (`::upb::Arena arena`), which C++ lookup starts from. A + * `::` after a name, a template's arguments or a `)` is no such `::`: in + * `SkipList::Iterator iter`, `Iterator` is SkipList's. + */ +const CPP_DECLARATOR_TYPE = String.raw`(?:${CPP_FUNCTION_WRAPPER_TYPE}|(?:(?):])::\s*)?[A-Za-z_][\w:]*(?:\s*${CPP_TEMPLATE_ARGUMENTS})?)(?:\s*(?:${CPP_DECLARATOR_CV}|[*&]+(?:\s*${CPP_DECLARATOR_ANNOTATION})*))*`; /** A line that is a declared type and nothing else, what may come before a type included: `const std::map&`. */ const CPP_TYPE_LINE = new RegExp(String.raw`^\s*(?:(?:const|volatile|static|mutable|constexpr|inline)\s+)*${CPP_DECLARATOR_TYPE}\s*$`); /** An annotation macro's call after a declared name: leveldb's `VersionSet* const versions_ GUARDED_BY(mutex_);`. */ @@ -10399,9 +10405,10 @@ function isCppOutsideQualifiedType(raw: string, site: CppNameSite, context: Reso * scopes in front of what is written, and the index leaves some out of a name * (one a macro opens, one a parse error lost). A namespace alias stands for * what it names, and a macro that names a namespace (`ROCKSDB_NAMESPACE`) for - * that namespace. Never so for a name one of the project's types qualifies, - * for an unqualified name, or for the end of a declaration begun on an - * earlier line. + * that namespace. A `std::` name is the very name or none of the project's: + * a global `string` class is no `std::string`. Never so for a name one of the + * project's types qualifies, for an unqualified name, or for the end of a + * declaration begun on an earlier line. */ function cppFindsOnlyNamesakes(raw: string, context: ResolutionContext, method?: string): boolean { if ((raw.match(//g)?.length ?? 0)) return false; @@ -10412,7 +10419,9 @@ function cppFindsOnlyNamesakes(raw: string, context: ResolutionContext, method?: const alias = cppNamespaceAliases(context).get(segments[0]!); const written = [segments, ...(alias ? [[...alias.split('::'), ...segments.slice(1)]] : [])] .flatMap((s) => cppNamespaceSpellings(s.join('::'), context)); - const fits = (owner: string): boolean => written.some((w) => owner === w || owner.endsWith(`::${w}`) || w.endsWith(`::${owner}`)); + const std = segments[0] === 'std'; + const fits = (owner: string): boolean => written.some((w) => + owner === w || owner.endsWith(`::${w}`) || (!std && w.endsWith(`::${owner}`))); const owners = cppTypesNamed(name, context).map((n) => n.qualifiedName); if (method) { const methods = context.getMethodMatches?.(name, method, 'cpp') ?? context.getNodesByName(method).filter((m) => @@ -13362,20 +13371,22 @@ export function matchMethodCall( // project's own `hpb::StatusOr`. The name strategies decide. if (inferredType && isCppArrowThroughOutsideType(objectOrClass!, cppDecl, ref, context)) inferredType = null; // Below, the type's last name finds a method of any class of that name. - // A capitalized type written with its namespace whose every such class - // is a namesake (cppFindsOnlyNamesakes) calls none of them, and is past - // guessing by the receiver's name too, as isUndeclaredTypeName rules - // where no class shares the name: protobuf's `absl::Status _status` in - // a macro body reached `upb::Status::ok`, and its generated - // `DescriptorProto::ExtensionRange`, which the index doesn't hold, - // reached `Descriptor::ExtensionRange::options`. A `->` on a value calls - // a member of what it holds, which the name strategies decide. (A - // lowercase type is isCppLibraryType's; a member of a template - // parameter, `typename PolicyT::Payload`, is whatever the template - // argument makes it.) - if (inferredType && /^[A-Z]/.test(inferredType) && cppDecl.raw !== undefined && !cppDecl.aliased && + // A type written with its namespace whose every such class is a + // namesake (cppFindsOnlyNamesakes) calls none of them: protobuf's + // `absl::Status _status` in a macro body reached `upb::Status::ok`, and + // its generated `DescriptorProto::ExtensionRange`, which the index + // doesn't hold, reached `Descriptor::ExtensionRange::options`. A + // capitalized one is past guessing by the receiver's name too, as + // isUndeclaredTypeName rules where no class shares the name; a `->` on + // a value calls a member of what it holds, which the name strategies + // decide. A lowercase one is guessed for as where no class shares its + // name: the library-type rule below decides, or the name strategies for + // a declaration read outside the calling function, which may be another + // variable's. (A member of a template parameter, `typename + // PolicyT::Payload`, is whatever the template argument makes it.) + if (inferredType && cppDecl.raw !== undefined && !cppDecl.aliased && cppFindsOnlyNamesakes(cppDecl.raw, context, methodName!) && !isCppTemplateMember(cppDecl.raw, ref, context)) { - if (cppDecl.pointer || cppMemberOperator(objectOrClass!, ref, context) !== '->') return null; + if (/^[A-Z]/.test(inferredType) && (cppDecl.pointer || cppMemberOperator(objectOrClass!, ref, context) !== '->')) return null; inferredType = null; } }