Repository navigation
fix(cpp): structured bindings and function wrappers give their types to calls on them - #2540
Merged
Merged
Conversation
…e gives its type to calls on it 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>
…to calls on them
Receiver inference had no type for a name a structured binding declares,
for what a `std::function` returns, or for a class member whose type is
written on the line above its name. rocksdb's MultiCfIteratorImpl writes
`for (auto& [cfh, iter] : cfh_iter_pairs_) { iter->Valid(); … }` over a
member declared across two lines as a
`std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>`;
the calls were guessed by the method's name.
- cppStructuredBindingType reads a binding's type at the name's place: an
element of the range a range-for over the binding iterates, when the
call is in its body, or of a declaration's initializer the calling
function makes, a `std::pair` or `std::tuple` (cppBindingElements). A
map's range elements are pairs of its key and value (CPP_LIBRARY_MAPS).
- cppStatementFrom reads a statement on to its `;`, for a binding's
initializer and an `auto` local's.
- The declarator regex reads `std::function`, `std::move_only_function`,
`std::copyable_function`, `absl::FunctionRef` and `absl::AnyInvocable`
types, whose template argument is a function type, and
cppExpressionType gives a call on such a variable what the function
type returns (cppWrapperReturnType).
- cppDataMemberIn reads a member whose type is the whole line above its
name (CPP_TYPE_LINE), so a default member initializer continued on the
next line (`bool ready_ = flag_ &&` above `it_;`) isn't taken for one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md # src/resolution/name-matcher.ts
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.
Stacked on #2536 (base branch
claude/cpp-const-declarators). Merge #2536 first; GitHub then retargets this PR tomain.Summary
C++ receiver inference had no type for three kinds of declaration, so calls on them were guessed by the method's name:
MultiCfIteratorImplwritesfor (auto& [cfh, iter] : cfh_iter_pairs_) { iter->Valid(); … }. A declaration form is protocolbuffers/protobuf'sauto [dst_ext, is_new] = Insert(arena, dst_number); dst_ext->Free();.std::functionreturns. For examplecreate_iterator()->Valid()on astd::function<Iterator*()>. The declarator pattern didn't read a wrapper type at all, since its template argument has parentheses.cfh_iter_pairs_above is one:std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>on one line,cfh_iter_pairs_;on the next.What changed (name-matcher.ts)
cppStructuredBindingType. A binding's name has the type of thestd::pair/std::tupleelement at its place (cppBindingElements). The value bound is:cppForBodyEncloses, as for a loop's variable);Anything else, such as an aggregate struct, stays unknown and is skipped as before (
cppRebindsReceiver).Maps (
std::map/unordered_map, abseil'sflat_hash_map/node_hash_map/btree_map, …) havestd::pair<const K, V>range elements (CPP_LIBRARY_MAPS).cppStatementFromreads a statement on to its;, for a binding's initializer and, as before, anautolocal's.Function wrappers. The declarator type reads
std::function,std::move_only_function,std::copyable_function,absl::FunctionRefandabsl::AnyInvocablewith their function-type argument (CPP_FUNCTION_WRAPPER_TYPE).cppExpressionTypegives a call on such a variable what the function type returns (cppWrapperReturnType), for chains andautolocals.cppDataMemberInreads a member whose name begins its line when the line above is a type and nothing else (CPP_TYPE_LINE). So a default member initializer continued on the next line (bool ready_ = flag_ &&/it_;) isn't taken for a declaration. Inline method bodies were never read for members.Validation
Site-keyed edge diffs (
diff-sites.mjs) of #2536's head924db7c6against this branch. Both arms were built from the same tree, kernel included.Nodes and files are identical between the arms. Every removed group and every added, retargeted and metadata site was read:
std::priority_queueheap_/erased_heap_(had reachedBinaryHeap/PreparedHeapmethods),std::vector<std::unique_ptr<TruncatedRangeDelIterator>> range_tombstone_iters_(FragmentedRangeTombstoneList::empty), maps'find/erase(toku::omt::find,PreparedHeap::erase), vectors'emplace_back(autovector); protobuf'sabsl::flat_hash_mapmembers (google::protobuf::Map::find/contains/try_emplace);const auto status = do_work(i); status.ok()on anabsl::AnyInvocable<absl::Status(int)>(had reachedupb::Status::ok).dst_ext->Free()(auto [dst_ext, is_new] = Insert(…), astd::pair<Extension*, bool>);a->ptr()/b->ptr()afterauto [a, b] = GetArenaPairWithOffset(…), whosestd::pair<std::shared_ptr<const upb::Arena>, …>return type spans two lines;start.value()/limit.value()afterauto [start, limit] = MaybeAddTimestampsToRange(…),OptSlice; two-line members such asflushed_save_points_->size()(std::unique_ptr<autovector<…>>),trace_sequence_f_->Append(…)(WritableFile),compression_types_used.Contains(…)(SmallEnumSet).iterator->Valid()/Seek(…)fromMemTableRep::IteratortoIteratorBase;collector->NeedCompact()over a two-linestd::vector<std::unique_ptr<InternalTblPropColl>>(wasUserKeyTablePropertiesCollector's);unpacked_value.ToString()afterauto [unpacked_value, unix_write_time] = …(wasIndexValue's, nowSlice's);cached_range_tombstone_.Size()(CoreLocalArray);trace_sequence_f_->Close()(WritableFile).OptSlice::has_value,CacheEntryStatsCollector::GetStats, …).Tests
__tests__/cpp-binding-and-wrapper-receivers.test.tshas 6 cases checking targets and confidence:std::functioncalled directly and through anautolocal;The red phase on #2536's code fails the first three.
Mutation check. 10 guards were each disabled alone (
mutate-fnb.mjs), and each one fails a test:I dropped two checks I'd first written, because neither could fail a test:
cppWrapperReturnType, since only a wrapper's declaration is read with a function-type argument.The arm without both checks gives exactly the same graph as the A/B arm above, on all five repos.
Full suite: 6,978 passed, 82 skipped. 9 tests failed in 7 files, all in areas this change does not touch: git index currency, sync, the MCP daemon, stale-index refusal, the unindexed-root policy, worktree detection, and a 60-second
function-reftimeout. The run overlapped a mutation run and two repo indexings, and those 7 files pass when rerun alone (140 passed, 1 skipped).🤖 Generated with Claude Code