You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

537 lines
15 KiB

/**
* @file check_template.cpp
*
* splitted from tokenize_cleanup.cpp
*
* @author Guy Maurel 2022
* @license GPL v2+
*/
#include "tokenize_cleanup.h"
#include "check_template.h"
#include "chunk.h"
#include "combine.h"
#include "flag_braced_init_list.h"
#include "flag_decltype.h"
#include "prototypes.h"
using namespace uncrustify;
bool invalid_open_angle_template(Chunk *prev)
{
if (prev->IsNullChunk())
{
return(false);
}
// A template requires a word/type right before the open angle
return( prev->IsNot(CT_WORD)
&& prev->IsNot(CT_TYPE)
&& prev->IsNot(CT_COMMA)
&& prev->IsNot(CT_QUALIFIER)
&& prev->IsNot(CT_OPERATOR_VAL)
&& prev->GetParentType() != CT_OPERATOR);
}
Chunk *handle_double_angle_close(Chunk *pc)
{
Chunk *next = pc->GetNext();
if (next->IsNotNullChunk())
{
if ( pc->Is(CT_ANGLE_CLOSE)
&& next->Is(CT_ANGLE_CLOSE)
&& pc->GetParentType() == CT_NONE
&& (pc->GetOrigColEnd() + 1) == next->GetOrigCol()
&& next->GetParentType() == CT_NONE)
{
pc->Str().append('>');
pc->SetType(CT_SHIFT);
pc->SetOrigColEnd(next->GetOrigColEnd());
Chunk *tmp = next->GetNextNcNnl();
Chunk::Delete(next);
next = tmp;
}
else
{
// bug #663
pc->SetType(CT_COMPARE);
}
}
return(next);
}
void check_template(Chunk *start, bool in_type_cast)
{
LOG_FMT(LTEMPL, "%s(%d): orig line %zu, orig col %zu:\n",
__func__, __LINE__, start->GetOrigLine(), start->GetOrigCol());
Chunk *prev = start->GetPrevNcNnl(E_Scope::PREPROC);
if (prev->IsNullChunk())
{
return;
}
Chunk *end;
Chunk *pc;
if (prev->Is(CT_TEMPLATE))
{
LOG_FMT(LTEMPL, "%s(%d): CT_TEMPLATE:\n", __func__, __LINE__);
// We have: "template< ... >", which is a template declaration
size_t level = 1;
size_t parens = 0;
for (pc = start->GetNextNcNnl(E_Scope::PREPROC);
pc->IsNotNullChunk();
pc = pc->GetNextNcNnl(E_Scope::PREPROC))
{
LOG_FMT(LTEMPL, "%s(%d): type is %s, level is %zu\n",
__func__, __LINE__, get_token_name(pc->GetType()), level);
if ( (pc->GetStr()[0] == '>')
&& (pc->Len() > 1))
{
if (pc->GetStr()[1] == '=') // Issue #1462 and #2565
{
LOG_FMT(LTEMPL, "%s(%d): do not split '%s' at orig line %zu, orig col %zu\n",
__func__, __LINE__, pc->Text(), pc->GetOrigLine(), pc->GetOrigCol());
}
else
{
LOG_FMT(LTEMPL, "%s(%d): {split '%s' at orig line %zu, orig col %zu}\n",
__func__, __LINE__, pc->Text(), pc->GetOrigLine(), pc->GetOrigCol());
split_off_angle_close(pc);
}
}
if (pc->Is(CT_DECLTYPE))
{
flag_cpp_decltype(pc);
}
else if (pc->Is(CT_PAREN_OPEN))
{
++parens;
}
else if (pc->Is(CT_PAREN_CLOSE))
{
--parens;
}
if (parens == 0)
{
if (pc->IsString("<"))
{
level++;
}
else if (pc->IsString(">"))
{
if (level == 0)
{
fprintf(stderr, "%s(%d): level is ZERO, cannot be decremented, at line %zu, column %zu\n",
__func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol());
log_flush(true);
exit(EX_SOFTWARE);
}
level--;
if (level == 0)
{
break;
}
}
}
}
end = pc;
}
else
{
/*
* We may have something like "a< ... >", which is a template where
* '...' may consist of anything except a semicolon, unbalanced
* parens, or braces (with one exception being braced initializers
* embedded within decltypes).
*
* For example, braces may be encountered as such in the following
* snippet of valid C++ code:
*
* template<typename T,
* typename = enable_if_t<is_same<typename decay<T>::type,
* decltype (make_index_sequence<5> { })>::value>>
* void foo(T &&arg)
* {
*
* }
*
* Finally, if we are inside an 'if' statement and hit a CT_BOOL,
* then it isn't a template.
*/
if (invalid_open_angle_template(prev))
{
LOG_FMT(LTEMPL, "%s(%d): - after type %s + ( - Not a template\n",
__func__, __LINE__, get_token_name(prev->GetType()));
start->SetType(CT_COMPARE);
return;
}
LOG_FMT(LTEMPL, "%s(%d): - prev->GetType() is %s -\n",
__func__, __LINE__, get_token_name(prev->GetType()));
// Scan back and make sure we aren't inside square parenthesis
bool in_if = false;
bool hit_semicolon = false;
pc = start->GetPrevNcNnl(E_Scope::PREPROC);
while (pc->IsNotNullChunk())
{
if ( ( pc->Is(CT_SEMICOLON)
&& hit_semicolon)
|| pc->Is(CT_SQUARE_CLOSE))
{
break;
}
if (pc->Is(CT_DECLTYPE))
{
flag_cpp_decltype(pc);
}
if (pc->Is(CT_BRACE_OPEN))
{
if ( !pc->TestFlags(PCF_IN_DECLTYPE)
|| !detect_cpp_braced_init_list(pc->GetPrev(), pc))
{
break;
}
flag_cpp_braced_init_list(pc->GetPrev(), pc);
}
if ( pc->Is(CT_BRACE_CLOSE)
&& pc->GetParentType() != CT_BRACED_INIT_LIST
&& !pc->TestFlags(PCF_IN_DECLTYPE))
{
break;
}
if ( pc->Is(CT_SEMICOLON)
&& !hit_semicolon)
{
hit_semicolon = true;
}
if ( ( ( pc->Is(CT_IF)
|| pc->Is(CT_RETURN)
|| pc->Is(CT_WHILE)
|| pc->Is(CT_WHILE_OF_DO))
&& !hit_semicolon)
|| ( pc->Is(CT_FOR)
&& hit_semicolon))
{
in_if = true;
break;
}
pc = pc->GetPrevNcNnl(E_Scope::PREPROC);
}
/*
* Scan forward to the angle close
* If we have a comparison in there, then it can't be a template.
*/
const int max_token_count = 1024;
E_Token tokens[max_token_count];
size_t num_tokens = 1;
tokens[0] = CT_ANGLE_OPEN;
for (pc = start->GetNextNcNnl(E_Scope::PREPROC);
pc->IsNotNullChunk();
pc = pc->GetNextNcNnl(E_Scope::PREPROC))
{
constexpr static auto LCURRENT = LTEMPL;
LOG_FMT(LTEMPL, "%s(%d): pc orig line is %zu, orig col is %zu, type is %s, num_tokens is %zu\n",
__func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), get_token_name(pc->GetType()), num_tokens);
log_rule_B("tok_split_gte");
if (pc->Is(CT_BRACE_OPEN)) // Issue #2886
{
// look for the closing brace
Chunk *A = pc->GetClosingParen();
LOG_FMT(LTEMPL, "%s(%d): A orig line is %zu, orig col is %zu, type is %s\n",
__func__, __LINE__, A->GetOrigLine(), A->GetOrigCol(), get_token_name(A->GetType()));
pc = A->GetNext();
}
if ( (tokens[num_tokens - 1] == CT_ANGLE_OPEN)
&& (pc->GetStr()[0] == '>')
&& (pc->Len() > 1)
&& ( options::tok_split_gte()
|| ( ( pc->IsString(">>")
|| pc->IsString(">>>"))
&& ( num_tokens >= 2
|| ( num_tokens >= 1
&& in_type_cast)))))
{
LOG_FMT(LTEMPL, "%s(%d): {split '%s' at orig line %zu, orig col %zu}\n",
__func__, __LINE__, pc->Text(), pc->GetOrigLine(), pc->GetOrigCol());
split_off_angle_close(pc);
}
if (pc->IsString("<"))
{
if ( num_tokens > 0 && (tokens[num_tokens - 1] == CT_PAREN_OPEN)
&& invalid_open_angle_template(pc->GetPrev()))
{
pc->SetType(CT_COMPARE); // Issue #3127
}
else
{
tokens[num_tokens] = CT_ANGLE_OPEN;
num_tokens++;
}
}
else if (pc->IsString(">"))
{
if (num_tokens > 0 && (tokens[num_tokens - 1] == CT_PAREN_OPEN))
{
handle_double_angle_close(pc);
}
else if (--num_tokens <= 0)
{
break;
}
else if (tokens[num_tokens] != CT_ANGLE_OPEN)
{
break; // unbalanced parentheses
}
}
else if ( in_if
&& ( pc->Is(CT_BOOL)
|| pc->Is(CT_COMPARE)))
{
break;
}
else if (pc->Is(CT_BRACE_OPEN))
{
if ( !pc->TestFlags(PCF_IN_DECLTYPE)
|| !detect_cpp_braced_init_list(pc->GetPrev(), pc))
{
break;
}
auto brace_open = pc->GetNextNcNnl();
auto brace_close = brace_open->GetClosingParen();
brace_open->SetParentType(CT_BRACED_INIT_LIST);
brace_close->SetParentType(CT_BRACED_INIT_LIST);
}
else if ( pc->Is(CT_BRACE_CLOSE)
&& pc->GetParentType() != CT_BRACED_INIT_LIST
&& !pc->TestFlags(PCF_IN_DECLTYPE))
{
break;
}
else if (pc->Is(CT_SEMICOLON))
{
break;
}
else if (pc->Is(CT_PAREN_OPEN))
{
if (num_tokens >= max_token_count - 1)
{
break;
}
tokens[num_tokens] = CT_PAREN_OPEN;
num_tokens++;
}
else if ( pc->Is(CT_QUESTION) // Issue #2949
&& language_is_set(LANG_CPP))
{
break;
}
else if (pc->Is(CT_PAREN_CLOSE))
{
if (num_tokens == 0)
{
fprintf(stderr, "%s(%d): num_tokens is ZERO, cannot be decremented, at line %zu, column %zu\n",
__func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol());
log_flush(true);
exit(EX_SOFTWARE);
}
num_tokens--;
if (tokens[num_tokens] != CT_PAREN_OPEN)
{
break; // unbalanced parentheses
}
}
}
end = pc;
}
if (end->Is(CT_ANGLE_CLOSE))
{
pc = end->GetNextNcNnl(E_Scope::PREPROC);
if ( pc->IsNullChunk()
|| pc->IsNot(CT_NUMBER))
{
LOG_FMT(LTEMPL, "%s(%d): Template detected\n", __func__, __LINE__);
LOG_FMT(LTEMPL, "%s(%d): from orig line %zu, orig col %zu\n",
__func__, __LINE__, start->GetOrigLine(), start->GetOrigCol());
LOG_FMT(LTEMPL, "%s(%d): to orig line %zu, orig col %zu\n",
__func__, __LINE__, end->GetOrigLine(), end->GetOrigCol());
start->SetParentType(CT_TEMPLATE);
check_template_args(start, end);
end->SetParentType(CT_TEMPLATE);
end->SetFlagBits(PCF_IN_TEMPLATE);
return;
}
}
LOG_FMT(LTEMPL, "%s(%d): - Not a template: end = %s\n",
__func__, __LINE__, (end->IsNotNullChunk() ? get_token_name(end->GetType()) : "<null>"));
start->SetType(CT_COMPARE);
} // check_template
void check_template_arg(Chunk *start, Chunk *end)
{
LOG_FMT(LTEMPL, "%s(%d): Template argument detected\n", __func__, __LINE__);
LOG_FMT(LTEMPL, "%s(%d): from orig line %zu, orig col %zu\n",
__func__, __LINE__, start->GetOrigLine(), start->GetOrigCol());
LOG_FMT(LTEMPL, "%s(%d): to orig line %zu, orig col %zu\n",
__func__, __LINE__, end->GetOrigLine(), end->GetOrigCol());
// Issue #1127
// MyFoo<mySize * 2> foo1;
// MyFoo<2*mySize * 2> foo1;
// Issue #1346
// use it as ONE line:
// typename std::enable_if<!std::is_void<T>::value,
// QVector<T> >::type dummy(const std::function<T*(const S&)>&
// pFunc, const QVector<S>& pItems)
// we need two runs
// 1. run to test if expression is numeric
bool expressionIsNumeric = false;
Chunk *pc = start;
while (pc != end)
{
Chunk *next = pc->GetNextNcNnl(E_Scope::PREPROC);
pc->SetFlagBits(PCF_IN_TEMPLATE);
if ( pc->Is(CT_DECLTYPE)
|| pc->Is(CT_SIZEOF))
{
expressionIsNumeric = true;
break;
}
if (next->IsNot(CT_PAREN_OPEN))
{
if ( pc->Is(CT_NUMBER)
|| pc->Is(CT_ARITH)
|| pc->Is(CT_SHIFT))
{
expressionIsNumeric = true;
break;
}
}
pc = next;
}
LOG_FMT(LTEMPL, "%s(%d): expressionIsNumeric is %s\n",
__func__, __LINE__, expressionIsNumeric ? "TRUE" : "FALSE");
// 2. run to do the work
if (!expressionIsNumeric)
{
pc = start;
while (pc != end)
{
Chunk *next = pc->GetNextNcNnl(E_Scope::PREPROC);
pc->SetFlagBits(PCF_IN_TEMPLATE);
Chunk *prev = pc->GetPrevNcNnl(E_Scope::PREPROC);
Chunk *prev2 = prev->GetPrevNcNnl(E_Scope::PREPROC);
if ( prev->Is(CT_ELLIPSIS) // Issue #3309
&& prev2->Is(CT_TYPENAME))
{
pc->SetType(CT_PARAMETER_PACK);
}
else
{
make_type(pc);
}
pc = next;
}
}
} // check_template_arg
void check_template_args(Chunk *start, Chunk *end)
{
std::vector<E_Token> tokens;
// Scan for commas
Chunk *pc;
for (pc = start->GetNextNcNnl(E_Scope::PREPROC);
pc->IsNotNullChunk() && pc != end;
pc = pc->GetNextNcNnl(E_Scope::PREPROC))
{
switch (pc->GetType())
{
case CT_COMMA:
if (tokens.empty())
{
// Check current argument
check_template_args(start, pc);
start = pc;
}
break;
case CT_ANGLE_OPEN:
case CT_PAREN_OPEN:
tokens.push_back(pc->GetType());
break;
case CT_ANGLE_CLOSE:
if ( !tokens.empty()
&& tokens.back() == CT_ANGLE_OPEN)
{
tokens.pop_back();
}
break;
case CT_PAREN_CLOSE:
if ( !tokens.empty()
&& tokens.back() == CT_PAREN_OPEN)
{
tokens.pop_back();
}
break;
default:
break;
}
}
// Check whatever is left
check_template_arg(start, end);
} // check_template_args