diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -214,6 +214,22 @@ someLongFunction( argument1, argument2); + * ``BAS_BlockIndent`` (in configuration: ``BlockIndent``) + Always break after an open bracket, if the parameters don't fit + on a single line. Closing brackets will be placed on a new line. + E.g.: + + .. code-block:: c++ + + someLongFunction( + argument1, argument2 + ) + + + .. warning:: + + Note: This currently only applies to parentheses. + **AlignArrayOfStructures** (``ArrayInitializerAlignmentStyle``) :versionbadge:`clang-format 13` diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -300,6 +300,10 @@ - Option ``AllowShortEnumsOnASingleLine: false`` has been improved, it now correctly places the opening brace according to ``BraceWrapping.AfterEnum``. +- Option ``AlignAfterOpenBracket: BlockIndent`` has been added. If set, it will + always break after an open bracket, if the parameters don't fit on a single + line. Closing brackets will be placed on a new line. + - Option ``QualifierAlignment`` has been added in order to auto-arrange the positioning of specifiers/qualifiers `const` `volatile` `static` `inline` `constexpr` `restrict` 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 @@ -82,6 +82,19 @@ /// argument1, argument2); /// \endcode BAS_AlwaysBreak, + /// Always break after an open bracket, if the parameters don't fit + /// on a single line. Closing brackets will be placed on a new line. + /// E.g.: + /// \code + /// someLongFunction( + /// argument1, argument2 + /// ) + /// \endcode + /// + /// \warning + /// Note: This currently only applies to parentheses. + /// \endwarning + BAS_BlockIndent, }; /// If ``true``, horizontally aligns arguments after an open bracket. diff --git a/clang/lib/Format/ContinuationIndenter.h b/clang/lib/Format/ContinuationIndenter.h --- a/clang/lib/Format/ContinuationIndenter.h +++ b/clang/lib/Format/ContinuationIndenter.h @@ -203,15 +203,15 @@ bool AvoidBinPacking, bool NoLineBreak) : Tok(Tok), Indent(Indent), LastSpace(LastSpace), NestedBlockIndent(Indent), IsAligned(false), - BreakBeforeClosingBrace(false), AvoidBinPacking(AvoidBinPacking), - BreakBeforeParameter(false), NoLineBreak(NoLineBreak), - NoLineBreakInOperand(false), LastOperatorWrapped(true), - ContainsLineBreak(false), ContainsUnwrappedBuilder(false), - AlignColons(true), ObjCSelectorNameFound(false), - HasMultipleNestedBlocks(false), NestedBlockInlined(false), - IsInsideObjCArrayLiteral(false), IsCSharpGenericTypeConstraint(false), - IsChainedConditional(false), IsWrappedConditional(false), - UnindentOperator(false) {} + BreakBeforeClosingBrace(false), BreakBeforeClosingParen(false), + AvoidBinPacking(AvoidBinPacking), BreakBeforeParameter(false), + NoLineBreak(NoLineBreak), NoLineBreakInOperand(false), + LastOperatorWrapped(true), ContainsLineBreak(false), + ContainsUnwrappedBuilder(false), AlignColons(true), + ObjCSelectorNameFound(false), HasMultipleNestedBlocks(false), + NestedBlockInlined(false), IsInsideObjCArrayLiteral(false), + IsCSharpGenericTypeConstraint(false), IsChainedConditional(false), + IsWrappedConditional(false), UnindentOperator(false) {} /// \brief The token opening this parenthesis level, or nullptr if this level /// is opened by fake parenthesis. @@ -277,6 +277,13 @@ /// was a newline after the beginning left brace. bool BreakBeforeClosingBrace : 1; + /// Whether a newline needs to be inserted before the block's closing + /// paren. + /// + /// We only want to insert a newline before the closing paren if there also + /// was a newline after the beginning left paren. + bool BreakBeforeClosingParen : 1; + /// Avoid bin packing, i.e. multiple parameters/elements on multiple /// lines, in this context. bool AvoidBinPacking : 1; @@ -362,6 +369,8 @@ return IsAligned; if (BreakBeforeClosingBrace != Other.BreakBeforeClosingBrace) return BreakBeforeClosingBrace; + if (BreakBeforeClosingParen != Other.BreakBeforeClosingParen) + return BreakBeforeClosingParen; if (QuestionColumn != Other.QuestionColumn) return QuestionColumn < Other.QuestionColumn; if (AvoidBinPacking != Other.AvoidBinPacking) diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -341,6 +341,8 @@ if (State.Stack.back().BreakBeforeClosingBrace && Current.closesBlockOrBlockTypeList(Style)) return true; + if (State.Stack.back().BreakBeforeClosingParen && Current.is(tok::r_paren)) + return true; if (Previous.is(tok::semi) && State.LineContainsContinuedForLoopSection) return true; if (Style.Language == FormatStyle::LK_ObjC && @@ -639,10 +641,12 @@ State.Stack.back().ColonPos = FirstColonPos; } - // In "AlwaysBreak" mode, enforce wrapping directly after the parenthesis by - // disallowing any further line breaks if there is no line break after the - // opening parenthesis. Don't break if it doesn't conserve columns. - if (Style.AlignAfterOpenBracket == FormatStyle::BAS_AlwaysBreak && + // In "AlwaysBreak" or "BlockIndent" mode, enforce wrapping directly after the + // parenthesis by disallowing any further line breaks if there is no line + // break after the opening parenthesis. Don't break if it doesn't conserve + // columns. + if ((Style.AlignAfterOpenBracket == FormatStyle::BAS_AlwaysBreak || + Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent) && (Previous.isOneOf(tok::l_paren, TT_TemplateOpener, tok::l_square) || (Previous.is(tok::l_brace) && Previous.isNot(BK_Block) && Style.Cpp11BracedListStyle)) && @@ -943,6 +947,10 @@ opensProtoMessageField(*PreviousNonComment, Style))) State.Stack.back().BreakBeforeClosingBrace = true; + if (PreviousNonComment && PreviousNonComment->is(tok::l_paren)) + State.Stack.back().BreakBeforeClosingParen = + Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent; + if (State.Stack.back().AvoidBinPacking) { // If we are breaking after '(', '{', '<', or this is the break after a ':' // to start a member initializater list in a constructor, this should not @@ -1037,6 +1045,9 @@ (!Current.Next || Current.Next->isOneOf(tok::semi, tok::kw_const, tok::l_brace))) return State.Stack[State.Stack.size() - 2].LastSpace; + if (Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent && + Current.is(tok::r_paren) && State.Stack.size() > 1) + return State.Stack[State.Stack.size() - 2].LastSpace; if (NextNonComment->is(TT_TemplateString) && NextNonComment->closesScope()) return State.Stack[State.Stack.size() - 2].LastSpace; if (Current.is(tok::identifier) && Current.Next && 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 @@ -384,6 +384,7 @@ IO.enumCase(Value, "Align", FormatStyle::BAS_Align); IO.enumCase(Value, "DontAlign", FormatStyle::BAS_DontAlign); IO.enumCase(Value, "AlwaysBreak", FormatStyle::BAS_AlwaysBreak); + IO.enumCase(Value, "BlockIndent", FormatStyle::BAS_BlockIndent); // For backward compatibility. IO.enumCase(Value, "true", FormatStyle::BAS_Align); 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 @@ -4316,7 +4316,7 @@ if (Right.is(TT_ImplicitStringLiteral)) return false; - if (Right.is(tok::r_paren) || Right.is(TT_TemplateCloser)) + if (Right.is(TT_TemplateCloser)) return false; if (Right.is(tok::r_square) && Right.MatchingParen && Right.MatchingParen->is(TT_LambdaLSquare)) @@ -4327,6 +4327,18 @@ if (Right.is(tok::r_brace)) return Right.MatchingParen && Right.MatchingParen->is(BK_Block); + // We only break before r_paren if we're in a block indented context. + if (Right.is(tok::r_paren)) { + if (Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent) { + return Right.MatchingParen && + !(Right.MatchingParen->Previous && + (Right.MatchingParen->Previous->is(tok::kw_for) || + Right.MatchingParen->Previous->isIf())); + } + + return false; + } + // Allow breaking after a trailing annotation, e.g. after a method // declaration. if (Left.is(TT_TrailingAnnotation)) 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 @@ -19167,6 +19167,8 @@ FormatStyle::BAS_DontAlign); CHECK_PARSE("AlignAfterOpenBracket: AlwaysBreak", AlignAfterOpenBracket, FormatStyle::BAS_AlwaysBreak); + CHECK_PARSE("AlignAfterOpenBracket: BlockIndent", AlignAfterOpenBracket, + FormatStyle::BAS_BlockIndent); // For backward compatibility: CHECK_PARSE("AlignAfterOpenBracket: false", AlignAfterOpenBracket, FormatStyle::BAS_DontAlign); @@ -23696,6 +23698,224 @@ Style); } +TEST_F(FormatTest, AlignAfterOpenBracketBlockIndent) { + auto Style = getLLVMStyle(); + + StringRef Short = "functionCall(paramA, paramB, paramC);\n" + "void functionDecl(int a, int b, int c);"; + + StringRef Medium = "functionCall(paramA, paramB, paramC, paramD, paramE, " + "paramF, paramG, paramH, paramI);\n" + "void functionDecl(int argumentA, int argumentB, int " + "argumentC, int argumentD, int argumentE);"; + + verifyFormat(Short, Style); + + StringRef NoBreak = "functionCall(paramA, paramB, paramC, paramD, paramE, " + "paramF, paramG, paramH,\n" + " paramI);\n" + "void functionDecl(int argumentA, int argumentB, int " + "argumentC, int argumentD,\n" + " int argumentE);"; + + verifyFormat(NoBreak, Medium, Style); + verifyFormat(NoBreak, + "functionCall(\n" + " paramA,\n" + " paramB,\n" + " paramC,\n" + " paramD,\n" + " paramE,\n" + " paramF,\n" + " paramG,\n" + " paramH,\n" + " paramI\n" + ");\n" + "void functionDecl(\n" + " int argumentA,\n" + " int argumentB,\n" + " int argumentC,\n" + " int argumentD,\n" + " int argumentE\n" + ");", + Style); + + verifyFormat("outerFunctionCall(nestedFunctionCall(argument1),\n" + " nestedLongFunctionCall(argument1, " + "argument2, argument3,\n" + " argument4, " + "argument5));", + Style); + + Style.AlignAfterOpenBracket = FormatStyle::BAS_BlockIndent; + + verifyFormat(Short, Style); + verifyFormat( + "functionCall(\n" + " paramA, paramB, paramC, paramD, paramE, paramF, paramG, paramH, " + "paramI\n" + ");\n" + "void functionDecl(\n" + " int argumentA, int argumentB, int argumentC, int argumentD, int " + "argumentE\n" + ");", + Medium, Style); + + Style.AllowAllArgumentsOnNextLine = false; + Style.AllowAllParametersOfDeclarationOnNextLine = false; + + verifyFormat(Short, Style); + verifyFormat( + "functionCall(\n" + " paramA, paramB, paramC, paramD, paramE, paramF, paramG, paramH, " + "paramI\n" + ");\n" + "void functionDecl(\n" + " int argumentA, int argumentB, int argumentC, int argumentD, int " + "argumentE\n" + ");", + Medium, Style); + + Style.BinPackArguments = false; + Style.BinPackParameters = false; + + verifyFormat(Short, Style); + + verifyFormat("functionCall(\n" + " paramA,\n" + " paramB,\n" + " paramC,\n" + " paramD,\n" + " paramE,\n" + " paramF,\n" + " paramG,\n" + " paramH,\n" + " paramI\n" + ");\n" + "void functionDecl(\n" + " int argumentA,\n" + " int argumentB,\n" + " int argumentC,\n" + " int argumentD,\n" + " int argumentE\n" + ");", + Medium, Style); + + verifyFormat("outerFunctionCall(\n" + " nestedFunctionCall(argument1),\n" + " nestedLongFunctionCall(\n" + " argument1,\n" + " argument2,\n" + " argument3,\n" + " argument4,\n" + " argument5\n" + " )\n" + ");", + Style); + + verifyFormat("int a = (int)b;", Style); + verifyFormat("int a = (int)b;", + "int a = (\n" + " int\n" + ") b;", + Style); + + verifyFormat("return (true);", Style); + verifyFormat("return (true);", + "return (\n" + " true\n" + ");", + Style); + + verifyFormat("void foo();", Style); + verifyFormat("void foo();", + "void foo(\n" + ");", + Style); + + verifyFormat("void foo() {}", Style); + verifyFormat("void foo() {}", + "void foo(\n" + ") {\n" + "}", + Style); + + verifyFormat("auto string = std::string();", Style); + verifyFormat("auto string = std::string();", + "auto string = std::string(\n" + ");", + Style); + + verifyFormat("void (*functionPointer)() = nullptr;", Style); + verifyFormat("void (*functionPointer)() = nullptr;", + "void (\n" + " *functionPointer\n" + ")\n" + "(\n" + ") = nullptr;", + Style); +} + +TEST_F(FormatTest, AlignAfterOpenBracketBlockIndentIfStatement) { + auto Style = getLLVMStyle(); + + verifyFormat("if (foo()) {\n" + " return;\n" + "}", + Style); + + verifyFormat("if (quitelongarg !=\n" + " (alsolongarg - 1)) { // ABC is a very longgggggggggggg " + "comment\n" + " return;\n" + "}", + Style); + + Style.AlignAfterOpenBracket = FormatStyle::BAS_BlockIndent; + + verifyFormat("if (foo()) {\n" + " return;\n" + "}", + Style); + + verifyFormat("if (quitelongarg !=\n" + " (alsolongarg - 1)) { // ABC is a very longgggggggggggg " + "comment\n" + " return;\n" + "}", + Style); +} + +TEST_F(FormatTest, AlignAfterOpenBracketBlockIndentForStatement) { + auto Style = getLLVMStyle(); + + verifyFormat("for (int i = 0; i < 5; ++i) {\n" + " doSomething();\n" + "}", + Style); + + verifyFormat("for (int myReallyLongCountVariable = 0; " + "myReallyLongCountVariable < count;\n" + " myReallyLongCountVariable++) {\n" + " doSomething();\n" + "}", + Style); + + Style.AlignAfterOpenBracket = FormatStyle::BAS_BlockIndent; + + verifyFormat("for (int i = 0; i < 5; ++i) {\n" + " doSomething();\n" + "}", + Style); + + verifyFormat("for (int myReallyLongCountVariable = 0; " + "myReallyLongCountVariable < count;\n" + " myReallyLongCountVariable++) {\n" + " doSomething();\n" + "}", + Style); +} + } // namespace } // namespace format } // namespace clang