Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -2289,6 +2289,28 @@ For example: BOOST_FOREACH. +**IfMacros** (``std::vector``) + A vector of macros that should be interpreted as conditionals + instead of as function calls. + + These are expected to be macros of the form: + + .. code-block:: c++ + + IF(...) + + else IF(...) + + + In the .clang-format configuration file, this can be configured like: + + .. code-block:: yaml + + IfMacros: ['IF'] + + For example: `KJ_IF_MAYBE + `_ + **IncludeBlocks** (``IncludeBlocksStyle``) Dependent on the value, multiple ``#include`` blocks can be sorted as one and divided based on category. @@ -3341,10 +3363,12 @@ } } - * ``SBPO_ControlStatementsExceptForEachMacros`` (in configuration: ``ControlStatementsExceptForEachMacros``) + * ``SBPO_ControlStatementsExceptControlMacros`` (in configuration: ``ControlStatementsExceptControlMacros``) Same as ``SBPO_ControlStatements`` except this option doesn't apply to - ForEach macros. This is useful in projects where ForEach macros are - treated as function calls instead of control statements. + ForEach and If macros. This is useful in projects where ForEach/If + macros are treated as function calls instead of control statements. + ``SBPO_ControlStatementsExceptForEachMacros`` remains an alias for + backward compatability. .. code-block:: c++ Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -248,6 +248,10 @@ accepts ``AllIfsAndElse`` value that allows to put "else if" and "else" short statements on a single line. (Fixes https://llvm.org/PR50019.) +- Option ``IfMacros`` has been added. This lets you define macros that get + formatted like conditionals much like ``ForEachMacros`` get styled like + foreach loops. + - ``git-clang-format`` no longer formats changes to symbolic links. (Fixes https://llvm.org/PR46992.) Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -2079,6 +2079,26 @@ /// For example: BOOST_FOREACH. std::vector ForEachMacros; + /// A vector of macros that should be interpreted as conditionals + /// instead of as function calls. + /// + /// These are expected to be macros of the form: + /// \code + /// IF(...) + /// + /// else IF(...) + /// + /// \endcode + /// + /// In the .clang-format configuration file, this can be configured like: + /// \code{.yaml} + /// IfMacros: ['IF'] + /// \endcode + /// + /// For example: `KJ_IF_MAYBE + /// `_ + std::vector IfMacros; + /// \brief A vector of macros that should be interpreted as type declarations /// instead of as function calls. /// @@ -2949,8 +2969,10 @@ /// \endcode SBPO_ControlStatements, /// Same as ``SBPO_ControlStatements`` except this option doesn't apply to - /// ForEach macros. This is useful in projects where ForEach macros are - /// treated as function calls instead of control statements. + /// ForEach and If macros. This is useful in projects where ForEach/If + /// macros are treated as function calls instead of control statements. + /// ``SBPO_ControlStatementsExceptForEachMacros`` remains an alias for + /// backward compatability. /// \code /// void f() { /// Q_FOREACH(...) { @@ -2958,7 +2980,7 @@ /// } /// } /// \endcode - SBPO_ControlStatementsExceptForEachMacros, + SBPO_ControlStatementsExceptControlMacros, /// Put a space before opening parentheses only if the parentheses are not /// empty i.e. '()' /// \code Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ clang/lib/Format/Format.cpp @@ -404,8 +404,8 @@ IO.enumCase(Value, "Never", FormatStyle::SBPO_Never); IO.enumCase(Value, "ControlStatements", FormatStyle::SBPO_ControlStatements); - IO.enumCase(Value, "ControlStatementsExceptForEachMacros", - FormatStyle::SBPO_ControlStatementsExceptForEachMacros); + IO.enumCase(Value, "ControlStatementsExceptControlMacros", + FormatStyle::SBPO_ControlStatementsExceptControlMacros); IO.enumCase(Value, "NonEmptyParentheses", FormatStyle::SBPO_NonEmptyParentheses); IO.enumCase(Value, "Always", FormatStyle::SBPO_Always); @@ -413,6 +413,8 @@ // For backward compatibility. IO.enumCase(Value, "false", FormatStyle::SBPO_Never); IO.enumCase(Value, "true", FormatStyle::SBPO_ControlStatements); + IO.enumCase(Value, "ControlStatementsExceptForEachMacros", + FormatStyle::SBPO_ControlStatementsExceptControlMacros); } }; @@ -616,6 +618,7 @@ Style.ExperimentalAutoDetectBinPacking); IO.mapOptional("FixNamespaceComments", Style.FixNamespaceComments); IO.mapOptional("ForEachMacros", Style.ForEachMacros); + IO.mapOptional("IfMacros", Style.IfMacros); IO.mapOptional("StatementAttributeLikeMacros", Style.StatementAttributeLikeMacros); IO.mapOptional("IncludeBlocks", Style.IncludeStyle.IncludeBlocks); @@ -1007,6 +1010,7 @@ LLVMStyle.ForEachMacros.push_back("foreach"); LLVMStyle.ForEachMacros.push_back("Q_FOREACH"); LLVMStyle.ForEachMacros.push_back("BOOST_FOREACH"); + LLVMStyle.IfMacros.push_back("KJ_IF_MAYBE"); LLVMStyle.IncludeStyle.IncludeCategories = { {"^\"(llvm|llvm-c|clang|clang-c)/", 2, 0, false}, {"^(<|\"(gtest|gmock|isl|json)/)", 3, 0, false}, Index: clang/lib/Format/FormatToken.h =================================================================== --- clang/lib/Format/FormatToken.h +++ clang/lib/Format/FormatToken.h @@ -52,6 +52,7 @@ TYPE(FunctionDeclarationName) \ TYPE(FunctionLBrace) \ TYPE(FunctionTypeLParen) \ + TYPE(IfMacro) \ TYPE(ImplicitStringLiteral) \ TYPE(InheritanceColon) \ TYPE(InheritanceComma) \ Index: clang/lib/Format/FormatTokenLexer.cpp =================================================================== --- clang/lib/Format/FormatTokenLexer.cpp +++ clang/lib/Format/FormatTokenLexer.cpp @@ -39,6 +39,8 @@ for (const std::string &ForEachMacro : Style.ForEachMacros) Macros.insert({&IdentTable.get(ForEachMacro), TT_ForEachMacro}); + for (const std::string &IfMacro : Style.IfMacros) + Macros.insert({&IdentTable.get(IfMacro), TT_IfMacro}); for (const std::string &AttributeMacro : Style.AttributeMacros) Macros.insert({&IdentTable.get(AttributeMacro), TT_AttributeMacro}); for (const std::string &StatementMacro : Style.StatementMacros) @@ -1014,6 +1016,13 @@ tok::pp_define) && it != Macros.end()) { FormatTok->setType(it->second); + if (it->second == TT_IfMacro) { + // The lexer token currently has type tok::kw_unknown. However, for this + // substitution to be treated correctly in the TokenAnnotator, faking + // the tok value seems to be needed. Not sure if there's a more elegant + // way. + FormatTok->Tok.setKind(tok::kw_if); + } } else if (FormatTok->is(tok::identifier)) { if (MacroBlockBeginRegex.match(Text)) { FormatTok->setType(TT_MacroBlockBegin); Index: clang/lib/Format/TokenAnnotator.cpp =================================================================== --- clang/lib/Format/TokenAnnotator.cpp +++ clang/lib/Format/TokenAnnotator.cpp @@ -1363,7 +1363,7 @@ // Reset token type in case we have already looked at it and then // recovered from an error (e.g. failure to find the matching >). if (!CurrentToken->isOneOf( - TT_LambdaLSquare, TT_LambdaLBrace, TT_AttributeMacro, + TT_LambdaLSquare, TT_LambdaLBrace, TT_AttributeMacro, TT_IfMacro, TT_ForEachMacro, TT_TypenameMacro, TT_FunctionLBrace, TT_ImplicitStringLiteral, TT_InlineASMBrace, TT_FatArrow, TT_LambdaArrow, TT_NamespaceMacro, TT_OverloadedOperator, @@ -3019,9 +3019,13 @@ (Left.is(tok::r_square) && Left.is(TT_AttributeSquare))) return true; if (Style.SpaceBeforeParens == - FormatStyle::SBPO_ControlStatementsExceptForEachMacros && + FormatStyle::SBPO_ControlStatementsExceptControlMacros && Left.is(TT_ForEachMacro)) return false; + if (Style.SpaceBeforeParens == + FormatStyle::SBPO_ControlStatementsExceptControlMacros && + Left.is(TT_IfMacro)) + return false; return Line.Type == LT_ObjCDecl || Left.is(tok::semi) || (Style.SpaceBeforeParens != FormatStyle::SBPO_Never && (Left.isOneOf(tok::pp_elif, tok::kw_for, tok::kw_while, Index: clang/unittests/Format/FormatTest.cpp =================================================================== --- clang/unittests/Format/FormatTest.cpp +++ clang/unittests/Format/FormatTest.cpp @@ -1335,7 +1335,7 @@ FormatStyle Style = getLLVMStyle(); Style.SpaceBeforeParens = - FormatStyle::SBPO_ControlStatementsExceptForEachMacros; + FormatStyle::SBPO_ControlStatementsExceptControlMacros; verifyFormat("void f() {\n" " foreach(Item *item, itemlist) {}\n" " Q_FOREACH(Item *item, itemlist) {}\n" @@ -16957,6 +16957,8 @@ FormatStyle::SBPO_Always); CHECK_PARSE("SpaceBeforeParens: ControlStatements", SpaceBeforeParens, FormatStyle::SBPO_ControlStatements); + CHECK_PARSE("SpaceBeforeParens: ControlStatementsExceptControlMacros", SpaceBeforeParens, + FormatStyle::SBPO_ControlStatementsExceptControlMacros); CHECK_PARSE("SpaceBeforeParens: NonEmptyParentheses", SpaceBeforeParens, FormatStyle::SBPO_NonEmptyParentheses); // For backward compatibility: @@ -16964,6 +16966,8 @@ FormatStyle::SBPO_Never); CHECK_PARSE("SpaceAfterControlStatementKeyword: true", SpaceBeforeParens, FormatStyle::SBPO_ControlStatements); + CHECK_PARSE("SpaceBeforeParens: ControlStatementsExceptForEachMacros", SpaceBeforeParens, + FormatStyle::SBPO_ControlStatementsExceptControlMacros); Style.ColumnLimit = 123; FormatStyle BaseStyle = getLLVMStyle(); @@ -17111,6 +17115,11 @@ CHECK_PARSE("ForEachMacros: [BOOST_FOREACH, Q_FOREACH]", ForEachMacros, BoostAndQForeach); + Style.IfMacros.clear(); + std::vector CustomIfs; + CustomIfs.push_back("MYIF"); + CHECK_PARSE("IfMacros: [MYIF]", IfMacros, CustomIfs); + Style.AttributeMacros.clear(); CHECK_PARSE("BasedOnStyle: LLVM", AttributeMacros, std::vector{"__capability"}); @@ -19684,11 +19693,16 @@ TEST_F(FormatTest, SpacesInConditionalStatement) { FormatStyle Spaces = getLLVMStyle(); + Spaces.IfMacros.clear(); + Spaces.IfMacros.push_back("MYIF"); Spaces.SpacesInConditionalStatement = true; verifyFormat("for ( int i = 0; i; i++ )\n continue;", Spaces); verifyFormat("if ( !a )\n return;", Spaces); verifyFormat("if ( a )\n return;", Spaces); verifyFormat("if constexpr ( a )\n return;", Spaces); + verifyFormat("MYIF ( a )\n return;", Spaces); + verifyFormat("MYIF ( a )\n return;\nelse MYIF ( b )\n return;", Spaces); + verifyFormat("MYIF ( a )\n return;\nelse\n return;", Spaces); verifyFormat("switch ( a )\ncase 1:\n return;", Spaces); verifyFormat("while ( a )\n return;", Spaces); verifyFormat("while ( (a && b) )\n return;", Spaces); @@ -19697,6 +19711,12 @@ // Check that space on the left of "::" is inserted as expected at beginning // of condition. verifyFormat("while ( ::func() )\n return;", Spaces); + + // Check impact of ControlStatementsExceptControlMacros is honored. + Spaces.SpaceBeforeParens = FormatStyle::SBPO_ControlStatementsExceptControlMacros; + verifyFormat("MYIF( a )\n return;", Spaces); + verifyFormat("MYIF( a )\n return;\nelse MYIF( b )\n return;", Spaces); + verifyFormat("MYIF( a )\n return;\nelse\n return;", Spaces); } TEST_F(FormatTest, AlternativeOperators) {