Repository navigation
fix(cpp): a call of a member template with explicit template arguments has the type the template returns - #2556
Merged
Conversation
…s has the type the template returns A member call was read only when its callee ended in the method's name, so `reflection->GetRaw<RepeatedPtrField<T>>(…)` and `factory->GetOptions<BlockBasedTableOptions>()` gave the `auto` locals they initialize no type. Such a call now reaches the class's template overloads, and when each is declared to return the same one of its own template parameters (`template <typename Type> const Type& GetRaw(…)`), it returns the call's argument for it. A macro body's `\`-spliced lines are read as one statement. 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
An
autolocal initialized from a call of a C++ member template with explicit template arguments got no type:const auto& rep = reflection->GetRaw<RepeatedPtrField<CPPTYPE>>(message, field);andauto* lhs = r->MutableRaw<InlinedStringField>(…);auto bbto = factory->GetOptions<BlockBasedTableOptions>()andauto del_factory = factory->CheckedCast<CompactOnDeletionCollectorFactory>().There were two reasons:
cppExpressionTyperead a member call only when its callee ended in the method's name, so the explicit<…>made it no call at all.template <typename Type> const Type& GetRaw(…),T* GetOptions(),T* CheckedCast(). Read as written, that return type is template-dependent.Calls on such locals fell to name matching:
rep.size()guessedSooRep::size, andlhs_string->InternalSwap(…)reachedMicroStringorArenaStringPtrby luck.This was found while working on #2555. protobuf's case is the second shape that chip named, reflection_visit_fields.h:142.
What changed
All in
name-matcher.ts:cppExpressionType: a member call may write explicit template arguments (recv->method<Args>(…)). Such a call only reaches template overloads; a non-templateWidget* Find()besidetemplate <typename T> T* Find()is never what it calls.cppMemberTemplateReturn: when every template overload is declared to return the same one of its own parameters, perhaps const, a pointer or a reference, the call returns the argument for it, so qualified. It is read as written at the call. Otherwise the return type is read as written, as before. That still serves a project class template likeRepeatedFieldRef<T>orPtr<T>.cppOwnTemplateParameters: reads a function template's owntemplate <…>header, in order and without defaults. A template's node starts at its declaration in both engines, so the header is the code right before it.cppStatementFrom: a macro body's\-spliced lines are read as one statement.Validation
Site-keyed edge diffs of main
e03ff673against this branch. The base arm is the integration build #2554 and #2555 were merged from, whose tree equalse03ff673; both arms were built from one tree, kernel included.Node and file counts are identical. Every change was triaged against the initializer in the source, and all 50 are right.
protobuf:
SwapFieldHelper(generated_message_reflection.cc), 17 sites. Each receiver is aMutableRaw<T>(…)local, and its calls now reachT's methods:lhs_cord→RepeatedField<absl::Cord>:InternalSwap,Swap(added);lhs_string→RepeatedPtrFieldBase,InlinedStringFieldandMicroStringin turn (retargeted fromMicroString/RepeatedPtrFieldStringAccessorguesses at 0.65). InSwapStringFieldit had reachedArenaStringPtrat 0.9, through an earlier function'sArenaStringPtr* lhs_string;lhs_map→MapFieldBase(retargeted fromUntypedMapBase;Swapfrom 0.65 to 0.9);lhs_rm→RepeatedPtrFieldBase(added).rep.size()reachesRepeatedPtrField::size, andmap.size()reachesMapFieldBase::size. They had reachedSooRepandjava::FieldGeneratorMap. This is the macro case, read with its\splices.outside.get()onauto outside = arena.Make<int>(), aPtr<T>, reachesArena::Ptr::get.rep_str.size()onGetRepeatedFieldRef<std::string>(…), aRepeatedFieldRef<T>whosesizeits specializations declare, no longer guessesSooRep::size.rocksdb:
CheckedCast<T>()locals (del_factory,bfp,collector_factory,merge_operator,adapter) keep their targets, now found at 0.9 instead of 0.65–0.7.comp->Inner()onenv->CheckedCast<CompositeEnvWrapper>();config->GetOption(…)onopt_info->AsRawPointer<Configurable>(…);ChanglingMergeOperator/ChanglingCompactionFilterFactoryName()calls on two-lineCheckedCastinitializers;ttl_mo->CheckedCast<…>()on aCheckedCast<TtlMergeOperator>()local.shared->get()onGetOptions<std::shared_ptr<Configurable>>(…), a pointer to astd::shared_ptr, no longer guessesSharedCleanablePtr::get.Tests
__tests__/cpp-member-template-returns.test.ts, 3 tests:const auto& rep =over a\-splicedreflection->GetRaw<RepeatedPtrField<int>>(…)reachesRepeatedPtrField::size, with an out-of-lineGetRawdefinition and a namesakeSooRep::sizepresent. The extractor doesn't parse#definebodies, so a fixture can't use protobuf's macro itself; the A/B above covers that site.auto bbto = factory->GetOptions<BlockBasedTableOptions>()against its const and non-const overloads.Find<Gadget>()beside a non-templateWidget* Find();Make<Gadget>()with a defaultedtemplate <typename T = Widget>;Get<Widget, Gadget>(1)against overloads returning different parameters, left unguessed.Mutation check (each guard broken alone; every one fails a test):
Find<Gadget>()Get<Widget, Gadget>(1)Make<Gadget>()\GetRawlocalThe
templatekeyword check in the header reader recognizes syntax; no output depends on it alone.One existing test changed:
cpp-auto-local-receiver.test.ts, "an initializer that goes on past its line is read to its end". It expectedChanglingMergeOperator::SetNameat 0.7, reached by a fallback becauseCheckedCast<T>()'sT*couldn't be read. It now gets the same target at 0.9, from the typeCheckedCast<test::ChanglingMergeOperator>()returns, as the A/B did at options_test.cc:3960.Full suite: 7,062 passed, 82 skipped. The one failure in the first run was that changed test, at its old 0.7, and it passes with its new expectation.
🤖 Generated with Claude Code