Skip to content

Commit 9283a76

Browse files
committed
Fix recursive deduction
1 parent 06e5836 commit 9283a76

2 files changed

Lines changed: 81 additions & 7 deletions

File tree

‎lib/templatesimplifier.cpp‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2241,6 +2241,18 @@ bool TemplateSimplifier::alreadyHasNamespace(const TokenAndName &templateDeclara
22412241
return Token::simpleMatch(tok->tokAt(offset), scope.c_str(), scope.size());
22422242
}
22432243

2244+
// Is the call at the name token a call of the function template with deduced template
2245+
// arguments that type deduction can handle? deducibleParameterCount is the parameter
2246+
// count of the template, -1 when deduction does not support it.
2247+
static bool isDeducibleCall(const Token* nameTok, int deducibleParameterCount)
2248+
{
2249+
if (deducibleParameterCount < 0 || nameTok->strAt(1) != "(")
2250+
return false;
2251+
std::vector<const Token*> args;
2252+
getFunctionArguments(nameTok, args);
2253+
return args.size() == static_cast<std::size_t>(deducibleParameterCount);
2254+
}
2255+
22442256
struct newInstantiation {
22452257
newInstantiation(Token* t, std::string s) : token(t), scope(std::move(s)) {}
22462258
Token* token;
@@ -2263,6 +2275,8 @@ void TemplateSimplifier::expandTemplate(
22632275
const bool isFunction = templateDeclaration.isFunction();
22642276
const bool isSpecialization = templateDeclaration.isSpecialization();
22652277
const bool isVariable = templateDeclaration.isVariable();
2278+
// the parameter count of a function template whose calls type deduction can handle
2279+
const int deducibleParameterCount = isFunction ? DeductionCache().supportedParameterCount(templateDeclaration) : -1;
22662280

22672281
// the incremental symbol database update only supports expansions that copy a
22682282
// function template (see SymbolDatabase::addSymbolsForNewTokenRanges) - everything
@@ -2854,6 +2868,13 @@ void TemplateSimplifier::expandTemplate(
28542868
if (Token::Match(tok3->tokAt(-3), "> friend class|struct|union")) {
28552869
if (copy)
28562870
mTokenList.addtoken(tok3);
2871+
} else if (copy && tok3 != templateDeclarationNameToken &&
2872+
isDeducibleCall(tok3, deducibleParameterCount)) {
2873+
// a recursive call with deduced template arguments: the arguments
2874+
// may have other types than this instantiation, so the call is
2875+
// left to type deduction - the declaration must stay until then
2876+
mTokenList.addtoken(tok3);
2877+
mPendingTypeDeductions = true;
28572878
} else if (copy) {
28582879
// add namespace if necessary
28592880
if (!templateDeclaration.scope().empty() &&
@@ -4512,8 +4533,10 @@ void TemplateSimplifier::simplifyTemplates(const std::time_t maxtime)
45124533

45134534
mTokenizer.calculateScopes();
45144535

4515-
// all template names expanded during this call; merged into mExpandedTemplateNames
4516-
// when this call is done so later calls can reuse the expansions
4536+
// all template names expanded during this call - an instantiation is expanded only
4537+
// once even though the declaration may stay for later passes (deferred removal);
4538+
// merged into mExpandedTemplateNames when this call is done so later calls can
4539+
// reuse the expansions too
45174540
std::set<std::string> expandedTemplateNamesThisCall;
45184541

45194542
unsigned int passCount = 0;
@@ -4637,8 +4660,6 @@ void TemplateSimplifier::simplifyTemplates(const std::time_t maxtime)
46374660
return false;
46384661
});
46394662

4640-
std::set<std::string> expandedtemplates;
4641-
46424663
for (auto iter1 = mTemplateDeclarations.crbegin(); iter1 != mTemplateDeclarations.crend(); ++iter1) {
46434664
if (iter1->isAlias() || iter1->isFriend())
46444665
continue;
@@ -4657,15 +4678,13 @@ void TemplateSimplifier::simplifyTemplates(const std::time_t maxtime)
46574678
*iter1,
46584679
specializations,
46594680
maxtime,
4660-
expandedtemplates);
4681+
expandedTemplateNamesThisCall);
46614682
if (instantiated) {
46624683
mInstantiatedTemplates.push_back(*iter1);
46634684
mTemplateNamePos.clear(); // positions might be invalid after instantiations
46644685
}
46654686
}
46664687

4667-
expandedTemplateNamesThisCall.insert(expandedtemplates.cbegin(), expandedtemplates.cend());
4668-
46694688
for (auto it = mInstantiatedTemplates.cbegin(); it != mInstantiatedTemplates.cend(); ++it) {
46704689
auto decl = std::find_if(mTemplateDeclarations.begin(), mTemplateDeclarations.end(), [&it](const TokenAndName& decl) {
46714690
return decl.token() == it->token();

‎test/testsimplifytemplate.cpp‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ class TestSimplifyTemplate : public TestFixture {
318318
TEST_CASE(templateTypeDeductionNested); // calls inside a body created by deduction are deduced too
319319
TEST_CASE(templateTypeDeductionOutOfLine); // member template defined out of line, class in a namespace
320320
TEST_CASE(templateTypeDeductionShadowedType); // the deduced type name means another type in the template's scope
321+
TEST_CASE(templateTypeDeductionRecursion); // recursive calls are deduced, not renamed to the current instantiation
321322
TEST_CASE(templateTypeDeductionEnum); // enum arguments deduce the enum, not its underlying type
322323
TEST_CASE(templateTypeDeductionArray); // array arguments: only the decayed dimension is a pointer
323324
TEST_CASE(templateTypeDeductionConstPointer); // "const T *": the parameter provides the pointee const
@@ -7507,6 +7508,60 @@ class TestSimplifyTemplate : public TestFixture {
75077508
}
75087509
}
75097510

7511+
void templateTypeDeductionRecursion()
7512+
{ // a call of the template inside its own body must not be renamed to the
7513+
// instantiation being expanded - the argument types may differ
7514+
{ // the recursive call instantiates another type
7515+
const char code[] = "template<class T> int f2(T t) { long l = 0; return f2(l); }\n"
7516+
"int g(int i) { return f2(i); }";
7517+
const char exp[] = "int f2<int> ( int t ) ; "
7518+
"int f2<long> ( long t ) ; "
7519+
"int g ( int i ) { return f2<int> ( i ) ; } "
7520+
"int f2<int> ( int t ) { long l ; l = 0 ; return f2<long> ( l ) ; } "
7521+
"int f2<long> ( long t ) { long l ; l = 0 ; return f2<long> ( l ) ; }";
7522+
ASSERT_EQUALS(exp, tok(code));
7523+
ASSERT_EQUALS(exp, tok(code, dinit(CheckOptions, $.templateFullRebuild = true)));
7524+
}
7525+
{ // explicit template arguments at the call site: same expansion
7526+
const char code[] = "template<class T> int f2(T t) { long l = 0; return f2(l); }\n"
7527+
"int g(int i) { return f2<int>(i); }";
7528+
const char exp[] = "int f2<int> ( int t ) ; "
7529+
"int f2<long> ( long t ) ; "
7530+
"int g ( int i ) { return f2<int> ( i ) ; } "
7531+
"int f2<int> ( int t ) { long l ; l = 0 ; return f2<long> ( l ) ; } "
7532+
"int f2<long> ( long t ) { long l ; l = 0 ; return f2<long> ( l ) ; }";
7533+
ASSERT_EQUALS(exp, tok(code));
7534+
}
7535+
{ // a literal argument
7536+
const char code[] = "template<class T> int f(T t) { return f(\"x\"); }\n"
7537+
"int g(int i) { return f(i); }";
7538+
const char exp[] = "int f<int> ( int t ) ; "
7539+
"int f<constchar*> ( const char * t ) ; "
7540+
"int g ( int i ) { return f<int> ( i ) ; } "
7541+
"int f<int> ( int t ) { return f<constchar*> ( \"x\" ) ; } "
7542+
"int f<constchar*> ( const char * t ) { return f<constchar*> ( \"x\" ) ; }";
7543+
ASSERT_EQUALS(exp, tok(code));
7544+
}
7545+
{ // the recursion uses the same type
7546+
const char code[] = "template<class T> int f(T t) { if (t) return f(t - 1); return 0; }\n"
7547+
"int g(int i) { return f(i); }";
7548+
const char exp[] = "int f<int> ( int t ) ; "
7549+
"int g ( int i ) { return f<int> ( i ) ; } "
7550+
"int f<int> ( int t ) { if ( t ) { return f<int> ( t - 1 ) ; } return 0 ; }";
7551+
ASSERT_EQUALS(exp, tok(code));
7552+
ASSERT_EQUALS(exp, tok(code, dinit(CheckOptions, $.templateFullRebuild = true)));
7553+
}
7554+
{ // a declaration that deduction does not handle (T&&): the recursive call is
7555+
// renamed to the instantiation as before, it would stay unresolved otherwise
7556+
const char code[] = "template<class T> int f(T&& t) { return f(1); }\n"
7557+
"int g(int i) { return f<int>(i); }";
7558+
const char exp[] = "int f<int> ( int && t ) ; "
7559+
"int g ( int i ) { return f<int> ( i ) ; } "
7560+
"int f<int> ( int && t ) { return f<int> ( 1 ) ; }";
7561+
ASSERT_EQUALS(exp, tok(code));
7562+
}
7563+
}
7564+
75107565
void templateTypeDeductionEnum()
75117566
{
75127567
{ // an enum variable has the ValueType of the underlying integer type - the enum

0 commit comments

Comments
 (0)