Commit f6f07a4f authored by Igor Kushnir's avatar Igor Kushnir
Browse files

ParamIterator: add support for zero parameters: () or <>

The displayed tooltips for macros and identifiers with zero parameters
were correct even before this commit. For example,
"Function macro: mempty()" for `#define mempty()`;
"ClassName< > objectName" for objectName in `ClassName<> objectName;`.
However the unit tests revealed that the corresponding MacroDefinition
object contained a single parameter with an empty name. This didn't pose
any practical problems, but wrong models could cause issues later.

MacroNavigationContext::html() displayed parentheses only if at least
one parameter existed. In order to keep the tooltip correct (as opposed
to "Function macro: mempty" for the example above), display parentheses
if the macro is function-like instead.
parent 0d1381aa
......@@ -388,6 +388,16 @@ ParamIterator::ParamIterator(QStringView parens, QStringView source, int offset)
d->m_curEnd = d->next();
if (d->m_curEnd != d->m_source.length()) {
d->m_prefix = d->sourceRange(offset, parenBegin);
if (d->m_source[d->m_curEnd] == d->m_parens[1]) {
const auto singleParam = d->sourceRange(d->m_cur, d->m_curEnd);
if (consistsOfWhitespace(singleParam)) {
// Only whitespace characters are present between parentheses => assume that
// there are zero parameters, not a single empty parameter, and stop iterating.
d->m_cur = d->m_end = d->m_curEnd + 1;
}
}
return;
} // else: the paren was not closed. It might be an identifier like "operator<", so count everything as prefix.
} // else: we have neither found an ending-character, nor an opening-paren, so take the whole input and end.
......
......@@ -186,6 +186,15 @@ void TestStringHelpers::testParamIterator_data()
addTest("hardToParse<A<B>", {"A<B"});
addTest("hardToParse<(A>B)>", {"(A>B)"});
// Such zero/empty tparam strings are actually passed to ParamIterator() while libstdc++ headers are parsed.
addTest("_Index_tuple<>", {});
addTest("const __shared_count< \t>", {});
addTest("_Rb_tree_impl<_Key_compare, >", {"_Key_compare", ""});
addTest("__hash_enum<_Tp,\t \t>", {"_Tp", ""});
addTest("__uniq_ptr_data<_Tp, _Dp, , >", {"_Tp", "_Dp", "", ""});
addMacroTest("Q_UNIMPLEMENTED() qWarning(\"Unimplemented code.\")", {});
addMacroTest("Q_FALLTHROUGH( ) [[clang::fallthrough]]", {});
addMacroTest("( /*a)b*/ x , /*,*/y,z )", {"/*a)b*/ x", "/*,*/y", "z"});
}
......
......@@ -30,6 +30,24 @@ QString MacroNavigationContext::name() const
return m_macro->identifier().toString();
}
static QString parameterListString(const MacroDefinition& macro)
{
// A function-like macro can have 0 parameters, e.g. `#define m() 1`.
// Check isFunctionLike() rather than parametersSize() here in order to display "()" in this case.
if (!macro.isFunctionLike()) {
Q_ASSERT(macro.parametersSize() == 0);
return QString();
}
QStringList parameterList;
parameterList.reserve(macro.parametersSize());
FOREACH_FUNCTION (const auto& parameter, macro.parameters) {
parameterList << parameter.str();
}
return QLatin1Char('(') + parameterList.join(QLatin1String(", ")) + QLatin1Char(')');
}
QString MacroNavigationContext::html(bool shorten)
{
Q_UNUSED(shorten);
......@@ -37,15 +55,6 @@ QString MacroNavigationContext::html(bool shorten)
modifyHtml() += QStringLiteral("<html><body><p>");
QStringList parameterList;
parameterList.reserve(m_macro->parametersSize());
FOREACH_FUNCTION(const auto& parameter, m_macro->parameters) {
parameterList << parameter.str();
}
const QString parameters = (!parameterList.isEmpty() ?
QLatin1Char('(') + parameterList.join(QLatin1String(", ")) + QLatin1Char(')') :
QString());
const QUrl url = m_macro->url().toUrl();
const QString path = url.toLocalFile();
KTextEditor::Cursor cursor(m_macro->rangeInCurrentRevision().start());
......@@ -54,7 +63,7 @@ QString MacroNavigationContext::html(bool shorten)
"%2: the macro name and arguments",
"%1: %2",
(m_macro->isFunctionLike() ? i18n("Function macro") : i18n("Macro")),
importantHighlight(name()) + parameters);
importantHighlight(name()) + parameterListString(*m_macro));
modifyHtml() += QStringLiteral("<br/>");
modifyHtml() += i18nc("%1: the link to the definition", "Defined in: %1",
createLink(QStringLiteral("%1 :%2").arg(url.fileName()).arg(cursor.line()+1), path, action));
......
......@@ -234,6 +234,11 @@ void TestDUChain::testMacroDefinition()
QCOMPARE(macro->definition().str(), definition);
QCOMPARE(macro->isFunctionLike(), isFunctionLike);
// The displayed tooltip for these incorrectly parsed macros looks good, but the bogus
// single macro parameter in the MacroDefinition object could cause issues in the future.
QEXPECT_FAIL("only_escaped_newline_in_parens(\\\n)", "difficult to parse, uncommon, not yet a problem", Continue);
QEXPECT_FAIL("only_comment_in_parens(/*comment*/)", "difficult to parse, uncommon, not yet a problem", Continue);
QCOMPARE(macro->parametersSize(), parameters.size());
const IndexedString* actualParam = macro->parameters();
......@@ -264,6 +269,9 @@ void TestDUChain::testMacroDefinition_data()
addTest("m/*o*/long", "/*o*/long");
addTest("m/*(x)*/ long", "/*(x)*/ long");
addTest("m()", "", true);
addTest("macro( ) C", "C", true);
addTest("mac(x)", "", true, {"x"});
addTest("VARG_1(...)", "", true, {"..."});
addTest("mac(x) x", "x", true, {"x"});
......@@ -283,8 +291,13 @@ void TestDUChain::testMacroDefinition_data()
addTest("MM\t\\\n\t471", "\\\n\t471");
addTest("S1\\\n get()", "get()");
addTest("S1\\\nget(\t)", "", true);
addTest("m_2\\ \t\n(){return;}", "{return;}", true);
addTest("A0B9\\\n(N)\\\n(N-9)", "\\\n(N-9)", true, {"N"});
addTest("\t \\\t\nmacro\\ \n\\\nIden\\\ntifier(x,\\\t\n y)x \t\ty", "x \t\ty", true, {"x", "\\\t\n y"});
addTest("only_escaped_newline_in_parens(\\\n)", "", true);
addTest("only_comment_in_parens(/*comment*/)", "", true);
}
void TestDUChain::testInclude()
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment