diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2694,6 +2694,23 @@ Use tabs whenever we need to fill whitespace that spans at least from one tab stop to the next one. +**WhitespaceSensitiveMacros** (``std::vector``) + A vector of macros which are whitespace-sensitive and should not be touched. + + These are expected to be macros of the form: + + .. code-block:: c++ + + STRINGIZE(...) + + In the .clang-format configuration file, this can be configured like: + + .. code-block:: yaml + + WhitespaceSensitiveMacros: ['STRINGIZE', 'PP_STRINGIZE'] + + For example: BOOST_PP_STRINGIZE. + .. END_FORMAT_STYLE_OPTIONS diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -1425,6 +1425,17 @@ /// For example: TESTSUITE std::vector NamespaceMacros; + /// A vector of macros which are whitespace-sensitive and shouldn't be + /// touched. + /// + /// These are expected to be macros of the form: + /// \code + /// STRINGIZE(...) + /// \endcode + /// + /// For example: STRINGIZE + std::vector WhitespaceSensitiveMacros; + tooling::IncludeStyle IncludeStyle; /// Indent case labels one level from the switch statement. diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -599,6 +599,8 @@ IO.mapOptional("TypenameMacros", Style.TypenameMacros); IO.mapOptional("UseCRLF", Style.UseCRLF); IO.mapOptional("UseTab", Style.UseTab); + IO.mapOptional("WhitespaceSensitiveMacros", + Style.WhitespaceSensitiveMacros); } }; @@ -933,6 +935,9 @@ LLVMStyle.SortUsingDeclarations = true; LLVMStyle.StatementMacros.push_back("Q_UNUSED"); LLVMStyle.StatementMacros.push_back("QT_REQUIRE_VERSION"); + LLVMStyle.WhitespaceSensitiveMacros.push_back("STRINGIZE"); + LLVMStyle.WhitespaceSensitiveMacros.push_back("PP_STRINGIZE"); + LLVMStyle.WhitespaceSensitiveMacros.push_back("BOOST_PP_STRINGIZE"); // Defaults that differ when not C++. if (Language == FormatStyle::LK_TableGen) { diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -102,6 +102,7 @@ TYPE(TrailingUnaryOperator) \ TYPE(TypenameMacro) \ TYPE(UnaryOperator) \ + TYPE(UntouchableMacroFunc) \ TYPE(CSharpStringLiteral) \ TYPE(CSharpNamedArgumentColon) \ TYPE(CSharpNullable) \ diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp --- a/clang/lib/Format/FormatTokenLexer.cpp +++ b/clang/lib/Format/FormatTokenLexer.cpp @@ -43,6 +43,11 @@ Macros.insert({&IdentTable.get(TypenameMacro), TT_TypenameMacro}); for (const std::string &NamespaceMacro : Style.NamespaceMacros) Macros.insert({&IdentTable.get(NamespaceMacro), TT_NamespaceMacro}); + for (const std::string &WhitespaceSensitiveMacro : + Style.WhitespaceSensitiveMacros) { + Macros.insert( + {&IdentTable.get(WhitespaceSensitiveMacro), TT_UntouchableMacroFunc}); + } } ArrayRef FormatTokenLexer::lex() { diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -160,6 +160,27 @@ return false; } + bool parseUntouchableParens() { + while (CurrentToken) { + CurrentToken->Finalized = true; + switch (CurrentToken->Tok.getKind()) { + case tok::l_paren: + next(); + if (!parseUntouchableParens()) + return false; + continue; + case tok::r_paren: + next(); + return true; + default: + // no-op + break; + } + next(); + } + return false; + } + bool parseParens(bool LookForDecls = false) { if (!CurrentToken) return false; @@ -171,6 +192,11 @@ Contexts.back().ColonIsForRangeExpr = Contexts.size() == 2 && Contexts[0].ColonIsForRangeExpr; + if (Left->Previous && Left->Previous->is(TT_UntouchableMacroFunc)) { + Left->Finalized = true; + return parseUntouchableParens(); + } + bool StartsObjCMethodExpr = false; if (FormatToken *MaybeSel = Left->Previous) { // @selector( starts a selector. @@ -1311,7 +1337,7 @@ TT_TypenameMacro, TT_FunctionLBrace, TT_ImplicitStringLiteral, TT_InlineASMBrace, TT_JsFatArrow, TT_LambdaArrow, TT_NamespaceMacro, TT_OverloadedOperator, TT_RegexLiteral, TT_TemplateString, - TT_ObjCStringLiteral)) + TT_ObjCStringLiteral, TT_UntouchableMacroFunc)) CurrentToken->setType(TT_Unknown); CurrentToken->Role.reset(); CurrentToken->MatchingParen = nullptr; @@ -3970,7 +3996,7 @@ << " C=" << Tok->CanBreakBefore << " T=" << getTokenTypeName(Tok->getType()) << " S=" << Tok->SpacesRequiredBefore - << " B=" << Tok->BlockParameterCount + << " F=" << Tok->Finalized << " B=" << Tok->BlockParameterCount << " BK=" << Tok->BlockKind << " P=" << Tok->SplitPenalty << " Name=" << Tok->Tok.getName() << " L=" << Tok->TotalLength << " PPK=" << Tok->PackingKind << " FakeLParens="; diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -14021,6 +14021,19 @@ CHECK_PARSE("NamespaceMacros: [TESTSUITE, SUITE]", NamespaceMacros, std::vector({"TESTSUITE", "SUITE"})); + Style.WhitespaceSensitiveMacros.clear(); + CHECK_PARSE("WhitespaceSensitiveMacros: [STRINGIZE]", + WhitespaceSensitiveMacros, std::vector{"STRINGIZE"}); + CHECK_PARSE("WhitespaceSensitiveMacros: [STRINGIZE, ASSERT]", + WhitespaceSensitiveMacros, + std::vector({"STRINGIZE", "ASSERT"})); + Style.WhitespaceSensitiveMacros.clear(); + CHECK_PARSE("WhitespaceSensitiveMacros: ['STRINGIZE']", + WhitespaceSensitiveMacros, std::vector{"STRINGIZE"}); + CHECK_PARSE("WhitespaceSensitiveMacros: ['STRINGIZE', 'ASSERT']", + WhitespaceSensitiveMacros, + std::vector({"STRINGIZE", "ASSERT"})); + Style.IncludeStyle.IncludeCategories.clear(); std::vector ExpectedCategories = { {"abc/.*", 2, 0}, {".*", 1, 0}}; @@ -16530,6 +16543,36 @@ verifyFormat("foo(operator, , -42);", Style); } +TEST_F(FormatTest, WhitespaceSensitiveMacros) { + FormatStyle Style = getLLVMStyle(); + Style.WhitespaceSensitiveMacros.push_back("FOO"); + + // Don't use the helpers here, since 'mess up' will change the whitespace + // and these are all whitespace sensitive by definition + EXPECT_EQ("FOO(String-ized&Messy+But(: :Still)=Intentional);", + format("FOO(String-ized&Messy+But(: :Still)=Intentional);", Style)); + EXPECT_EQ( + "FOO(String-ized&Messy+But\\(: :Still)=Intentional);", + format("FOO(String-ized&Messy+But\\(: :Still)=Intentional);", Style)); + EXPECT_EQ("FOO(String-ized&Messy+But,: :Still=Intentional);", + format("FOO(String-ized&Messy+But,: :Still=Intentional);", Style)); + EXPECT_EQ("FOO(String-ized&Messy+But,: :\n" + " Still=Intentional);", + format("FOO(String-ized&Messy+But,: :\n" + " Still=Intentional);", + Style)); + Style.AlignConsecutiveAssignments = true; + EXPECT_EQ("FOO(String-ized=&Messy+But,: :\n" + " Still=Intentional);", + format("FOO(String-ized=&Messy+But,: :\n" + " Still=Intentional);", + Style)); + + Style.ColumnLimit = 21; + EXPECT_EQ("FOO(String-ized&Messy+But: :Still=Intentional);", + format("FOO(String-ized&Messy+But: :Still=Intentional);", Style)); +} + TEST_F(FormatTest, VeryLongNamespaceCommentSplit) { // These tests are not in NamespaceFixer because that doesn't // test its interaction with line wrapping