clang-format: better handle statement macros

Summary:
Some macros are used in the body of function, and actually contain the trailing semicolon: they should thus be automatically followed by a new line, and not get merged with the next line. This is for example the case with Qt's Q_UNUSED macro:

  void foo(int a, int b) {
    Q_UNUSED(a)
    return b;
  }

This patch deals with these cases by introducing a new option to specify list of statement macros. This re-uses the system already in place for foreach macros, to ensure there is no impact on performance.

Reviewers: krasimir, djasper, klimek

Reviewed By: krasimir

Subscribers: acoomans, mgrang, alexfh, klimek, cfe-commits

Differential Revision: https://reviews.llvm.org/D33440

llvm-svn: 343602
This commit is contained in:
Francois Ferrand 2018-10-02 16:37:51 +00:00
parent 59500f7a0b
commit 6f40e21a16
9 changed files with 102 additions and 7 deletions

View File

@ -1979,6 +1979,15 @@ the configuration (without a prefix: ``Auto``).
**StatementMacros** (``std::vector<std::string>``)
A vector of macros that should be interpreted as complete statements.
Typical macros are expressions, and require a semi-colon to be
added; sometimes this is not the case, and this allows to make
clang-format aware of such cases.
For example: Q_UNUSED
**TabWidth** (``unsigned``)
The number of columns used for tab stops.

View File

@ -1051,6 +1051,16 @@ struct FormatStyle {
/// For example: BOOST_FOREACH.
std::vector<std::string> ForEachMacros;
/// A vector of macros that should be interpreted as complete
/// statements.
///
/// Typical macros are expressions, and require a semi-colon to be
/// added; sometimes this is not the case, and this allows to make
/// clang-format aware of such cases.
///
/// For example: Q_UNUSED
std::vector<std::string> StatementMacros;
tooling::IncludeStyle IncludeStyle;
/// Indent case labels one level from the switch statement.
@ -1766,7 +1776,7 @@ struct FormatStyle {
SpacesInParentheses == R.SpacesInParentheses &&
SpacesInSquareBrackets == R.SpacesInSquareBrackets &&
Standard == R.Standard && TabWidth == R.TabWidth &&
UseTab == R.UseTab;
StatementMacros == R.StatementMacros && UseTab == R.UseTab;
}
llvm::Optional<FormatStyle> GetLanguageStyle(LanguageKind Language) const;

View File

@ -469,6 +469,7 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("SpacesInParentheses", Style.SpacesInParentheses);
IO.mapOptional("SpacesInSquareBrackets", Style.SpacesInSquareBrackets);
IO.mapOptional("Standard", Style.Standard);
IO.mapOptional("StatementMacros", Style.StatementMacros);
IO.mapOptional("TabWidth", Style.TabWidth);
IO.mapOptional("UseTab", Style.UseTab);
}
@ -714,6 +715,8 @@ FormatStyle getLLVMStyle() {
LLVMStyle.DisableFormat = false;
LLVMStyle.SortIncludes = true;
LLVMStyle.SortUsingDeclarations = true;
LLVMStyle.StatementMacros.push_back("Q_UNUSED");
LLVMStyle.StatementMacros.push_back("QT_REQUIRE_VERSION");
return LLVMStyle;
}

View File

@ -86,6 +86,7 @@ namespace format {
TYPE(RegexLiteral) \
TYPE(SelectorName) \
TYPE(StartOfName) \
TYPE(StatementMacro) \
TYPE(StructuredBindingLSquare) \
TYPE(TemplateCloser) \
TYPE(TemplateOpener) \

View File

@ -37,8 +37,9 @@ FormatTokenLexer::FormatTokenLexer(const SourceManager &SourceMgr, FileID ID,
Lex->SetKeepWhitespaceMode(true);
for (const std::string &ForEachMacro : Style.ForEachMacros)
ForEachMacros.push_back(&IdentTable.get(ForEachMacro));
llvm::sort(ForEachMacros);
Macros.insert({&IdentTable.get(ForEachMacro), TT_ForEachMacro});
for (const std::string &StatementMacro : Style.StatementMacros)
Macros.insert({&IdentTable.get(StatementMacro), TT_StatementMacro});
}
ArrayRef<FormatToken *> FormatTokenLexer::lex() {
@ -657,12 +658,12 @@ FormatToken *FormatTokenLexer::getNextToken() {
}
if (Style.isCpp()) {
auto it = Macros.find(FormatTok->Tok.getIdentifierInfo());
if (!(Tokens.size() > 0 && Tokens.back()->Tok.getIdentifierInfo() &&
Tokens.back()->Tok.getIdentifierInfo()->getPPKeywordID() ==
tok::pp_define) &&
std::find(ForEachMacros.begin(), ForEachMacros.end(),
FormatTok->Tok.getIdentifierInfo()) != ForEachMacros.end()) {
FormatTok->Type = TT_ForEachMacro;
it != Macros.end()) {
FormatTok->Type = it->second;
} else if (FormatTok->is(tok::identifier)) {
if (MacroBlockBeginRegex.match(Text)) {
FormatTok->Type = TT_MacroBlockBegin;

View File

@ -22,6 +22,7 @@
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "llvm/Support/Regex.h"
#include "llvm/ADT/MapVector.h"
#include <stack>
@ -99,7 +100,8 @@ private:
// Index (in 'Tokens') of the last token that starts a new line.
unsigned FirstInLineIndex;
SmallVector<FormatToken *, 16> Tokens;
SmallVector<IdentifierInfo *, 8> ForEachMacros;
llvm::SmallMapVector<IdentifierInfo *, TokenType, 8> Macros;
bool FormattingDisabled;

View File

@ -480,6 +480,10 @@ void UnwrappedLineParser::calculateBraceTypes(bool ExpectClassBody) {
}
LBraceStack.pop_back();
break;
case tok::identifier:
if (!Tok->is(TT_StatementMacro))
break;
LLVM_FALLTHROUGH;
case tok::at:
case tok::semi:
case tok::kw_if:
@ -1108,6 +1112,10 @@ void UnwrappedLineParser::parseStructuralElement() {
return;
}
}
if (Style.isCpp() && FormatTok->is(TT_StatementMacro)) {
parseStatementMacro();
return;
}
// In all other cases, parse the declaration.
break;
default:
@ -1309,6 +1317,11 @@ void UnwrappedLineParser::parseStructuralElement() {
return;
}
if (Style.isCpp() && FormatTok->is(TT_StatementMacro)) {
parseStatementMacro();
return;
}
// See if the following token should start a new unwrapped line.
StringRef Text = FormatTok->TokenText;
nextToken();
@ -2328,6 +2341,16 @@ void UnwrappedLineParser::parseJavaScriptEs6ImportExport() {
}
}
void UnwrappedLineParser::parseStatementMacro()
{
nextToken();
if (FormatTok->is(tok::l_paren))
parseParens();
if (FormatTok->is(tok::semi))
nextToken();
addUnwrappedLine();
}
LLVM_ATTRIBUTE_UNUSED static void printDebugInfo(const UnwrappedLine &Line,
StringRef Prefix = "") {
llvm::dbgs() << Prefix << "Line(" << Line.Level

View File

@ -126,6 +126,7 @@ private:
void parseObjCInterfaceOrImplementation();
bool parseObjCProtocol();
void parseJavaScriptEs6ImportExport();
void parseStatementMacro();
bool tryToParseLambda();
bool tryToParseLambdaIntroducer();
void tryToParseJSFunction();

View File

@ -2660,6 +2660,45 @@ TEST_F(FormatTest, MacroCallsWithoutTrailingSemicolon) {
getLLVMStyleWithColumns(40)));
verifyFormat("MACRO(>)");
// Some macros contain an implicit semicolon.
Style = getLLVMStyle();
Style.StatementMacros.push_back("FOO");
verifyFormat("FOO(a) int b = 0;");
verifyFormat("FOO(a)\n"
"int b = 0;",
Style);
verifyFormat("FOO(a);\n"
"int b = 0;",
Style);
verifyFormat("FOO(argc, argv, \"4.0.2\")\n"
"int b = 0;",
Style);
verifyFormat("FOO()\n"
"int b = 0;",
Style);
verifyFormat("FOO\n"
"int b = 0;",
Style);
verifyFormat("void f() {\n"
" FOO(a)\n"
" return a;\n"
"}",
Style);
verifyFormat("FOO(a)\n"
"FOO(b)",
Style);
verifyFormat("int a = 0;\n"
"FOO(b)\n"
"int c = 0;",
Style);
verifyFormat("int a = 0;\n"
"int x = FOO(a)\n"
"int b = 0;",
Style);
verifyFormat("void foo(int a) { FOO(a) }\n"
"uint32_t bar() {}",
Style);
}
TEST_F(FormatTest, LayoutMacroDefinitionsStatementsSpanningBlocks) {
@ -11095,6 +11134,12 @@ TEST_F(FormatTest, ParsesConfiguration) {
CHECK_PARSE("ForEachMacros: [BOOST_FOREACH, Q_FOREACH]", ForEachMacros,
BoostAndQForeach);
Style.StatementMacros.clear();
CHECK_PARSE("StatementMacros: [QUNUSED]", StatementMacros,
std::vector<std::string>{"QUNUSED"});
CHECK_PARSE("StatementMacros: [QUNUSED, QT_REQUIRE_VERSION]", StatementMacros,
std::vector<std::string>({"QUNUSED", "QT_REQUIRE_VERSION"}));
Style.IncludeStyle.IncludeCategories.clear();
std::vector<tooling::IncludeStyle::IncludeCategory> ExpectedCategories = {
{"abc/.*", 2}, {".*", 1}};