Repository navigation
fix(cpp): a receiver declared with nested template arguments calls its own class's method - #2443
Merged
Merged
Conversation
…s own class's method 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. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # src/resolution/name-matcher.ts
… what is above it #2413 marks the receiver as shadowed when the scan back to its declaration passes one whose type it can't read, so a member or earlier variable of the same name found further up doesn't decide the call. A declaration whose type began on an earlier line is such a one now that its last line reads as no type, where it used to stop the scan with a type no class has. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 7, 2026
Merged
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 reduces a receiver's declared type to its last name in
normalizeCppTypeName. The function cut each<…>at its first>(.replace(/<[^>]*>/g, ' ')), so nested template arguments left debris that names no class:autovector<std::pair<int, FileMetaData*>>autovector >autovectorStriped<CacheAlignedWrapper<port::Mutex>>Striped >Stripedformatter<remove_cvref_t<T>, Char>formatter , Char>formatterA call on such a receiver was never looked up on its class. What happened next depended on the debris:
isUndeclaredTypeNametook it for an outside type and dropped the call. rocksdb'smutex_.Get(key)on aStriped<…<…>>had no edge.files_marked_for_compaction_.clear()reachedCompactionInputFiles::clear.Changes:
normalizeCppTypeNamenow strips template arguments by depth with the helpercpp-type-aliases.tsalready had, exported asstripCppTemplateArguments.cpp-supertypes.tshad an identical private copy; it now imports the shared one.>that closes no<(cppTypeBeganAbove) means the type began on an earlier line, as with rocksdb'sstd::unique_ptr<BlobContents>>>& blob_reqsunderautovector<std::pair<BlobRangeReadRequest*,. A depth strip alone would read the inner argument's type there. So that text names no type, and the scan back to the declaration sets fix(cpp): a receiver declared as a std or other outside type with a lowercase name calls that type's own member #2413'sshadowedflag on it, the way fix(cpp): a receiver declared as a std or other outside type with a lowercase name calls that type's own member #2413 treatsauto x = Make();. A same-named member found further up then doesn't decide the call. Before this change, such a line stopped the scan with a debris type, which fix(cpp): a receiver declared as a std or other outside type with a lowercase name calls that type's own member #2413's library-type check already declines.Validation
Site-keyed before/after edge diffs (
diff-sites.mjs):ed199e60vs this branch. They share a kernel built from the same source, and theirdistdiffers only inname-matcher.js,cpp-type-aliases.jsandcpp-supertypes.js.d8a7f868, before fix(cpp): a receiver declared as a std or other outside type with a lowercase name calls that type's own member #2413, fix(cpp): a class keeps its namespaces and enclosing class past code the parser misreads #2426 and fix(c,cpp): a file named like a test is visible to the code that includes it #2421 landed. It was redone after syncing. All 99 changed sites are identical in both runs; one target only gained its namespace through fix(cpp): a class keeps its namespaces and enclosing class past code the parser misreads #2426.cdb994b4, after syncing in fix(cpp): a class keeps its members past attribute macros the parser can't read #2449's C/C++ extractor change (attribute macros), plus fix(go): a call on an embedded struct's method does not reach the embedder's method of that name #2430 (Go) and fix(react): a type argument or a name the component binds itself renders no JSX child #2442 (JSX). The changed sites in leveldb, fmt, rocksdb and protobuf are identical, site by site. redis wasn't re-run: it's C, and receiver inference runs only for C++.cdf06812against its parentce78b63d. That parent adds fix(c,cpp): a file named like a Google unittest, foo_unittest.cc, is a test #2434'sfoo_unittest.cctest naming, plus fix(go): implementing an interface needs matching parameter and result counts #2447, fix(go): a dotted call is written through its own receiver, and a local named like an import is that variable #2448, fix(go): a call through a type assertion reaches the asserted type's method #2444, fix(go): a defined type links the types it is defined from #2441, fix(react-router): a lazy route links the page a barrel module forwards #2436, fix(js,ts):require('./x').defaultreaches an ES module's default export #2433, fix(js,ts): a catch-all or node_modules path alias no longer makes package imports look local #2437 and perf(resolution): read a bare-recorded call's receiver on its own line first — graph byte-identical #2431. The changed sites in the four C++ repos are again identical. fix(go): implementing an interface needs matching parameter and result counts #2447 landed between the last pre-merge check and the merge, and the squash applied exactly the tested patch (samegit patch-id).A probe arm logged every receiver whose type now reads differently: leveldb 8, fmt 27, protobuf 243 and rocksdb 1,351 call sites. Most are std containers (
vector,unique_ptr,map…) that no project class is named after, so their outcome is unchanged. Every changed edge site maps to one of those logged receivers.rocksdb (all 92 sites checked). Receivers are declared as
autovector<…<…>>(85 sites) orStriped<CacheAlignedWrapper<port::Mutex>>(7). Each declaration that was read is the receiver's own: 70 are in the calling function's body or parameters and 22 are members of the caller's class. Each new target is that class's own method.autovector::{size, empty, back, push_back, clear, begin, end}calls. These had no edge before: each method has several owners, and the receiver's words name none of them.Striped::Getcalls (mutex_,loader_mutex_). These were silenced before by the capitalizedStriped >.autovector's methods at 0.9. Nine guesses wereCompactionInputFiles::{clear,size,empty}(the receivers arefiles_marked_for_compaction_,bottommost_files_…), one wasLazyWideColumnsBatch::sizeand oneFileChecksumList::size. All 11 old targets were wrong.autovector::emplace_back, now as a typed edge (0.9) instead of a guess for being the only method of that name (0.7).protobuf (6 added).
4 right:
hpb::StatusOr<std::unique_ptr<int>>inhpb/status_test.ccnow reacheshpb::StatusOr::ok/value. These were silenced before.2 wrong, both in
json/internal/parser.cc:absl::StatusOr<absl::Span<char>> decoded; decoded.ok()→hpb::StatusOr::ok;absl::StatusOr<absl::optional<int32_t>> x; x->has_value()→google::protobuf::Option::has_value, a 0.7 guess for being the only method of that name.Both come from a problem main already has with types that have no nested arguments. The normalizer keeps only the last name, so
absl::StatusOris taken for the project'shpb::StatusOr. On main,absl::StatusOr<int32_t> value; value.ok()(parser.cc:341, also descriptor.cc:5163, option_interpreter.cc:1086, compiler/plugin.cc:107…) already links tohpb::StatusOr::ok. Nestedabsl::StatusOrreceivers now join them. Of the 33 such receivers that now read asStatusOr, these 2 changed edges; the rest callstatus(),size()orvalue_or(), whichhpb::StatusOrlacks and no guess picks. A follow-up task is filed to type a namespace-qualified receiver by C++ name lookup.fmt (2 removed). Both are
value_formatter_.format(*it, ctx)inranges.h'sformatter<join_view<…>, Char>, wherevalue_formatter_is declaredformatter<remove_cvref_t<value_type>, Char>.detail::native_formatter::format, 0.65). They were right only for built-in element types, whose formatter specialization derives fromnative_formatter.formatter, like fmt's receivers declared with flatformatter<…>types. Its only indexedformatistest/format-test.cc'sformatter<incomplete_type>::format.isVisibleAcrossFilesdrops that one, since a header can't see it, so no edge remains.impl_.format,underlying_.parse,formatter_.format…) already ends unresolved this way. fmt's partial specializations are indexed as<anonymous>, so nothing reachesnative_formatterby name.Stray
>rule. It fired at 2 receiver sites across the four C++ repos, rocksdbblob_reqsand protobufdeps. Neither changed an edge.Tests
__tests__/cpp-nested-template-receiver.test.ts(6 integration tests and 1 unit block):CompactionInputFiles::clearguess;push_back@0.7guess, withsizemissing;Striped::Getedge.CachableEntry::size.shadowedflag. Without it, astd::string blob_reqsmember hidden by the parameter would silence the call.EBUSY, writer lock, and the MPEG-TS 2 s threshold.cpp-attribute-macrosandkernel-ccpp-parity,cpp-brace-scopes,go-embedding-no-override,jsx-child-disambiguation, and the C++ receiver and base-class suites.tscpasses, including a type-check of the new test file.🤖 Generated with Claude Code