diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -948,6 +948,24 @@ } else { } + * ``bool BeforeLambdaBody`` Wrap lambda block. + + .. code-block:: c++ + + true: + connect( + []() + { + foo(); + bar(); + }); + + false: + connect([]() { + foo(); + bar(); + }); + * ``bool IndentBraces`` Indent the wrapped braces themselves. * ``bool SplitEmptyFunction`` If ``false``, empty function body can be put on a single line. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -172,6 +172,25 @@ } } +- Option ``BraceWrapping.BeforeLambdaBody`` has been added to manage lambda + line break inside function parameter call in Allman style. + + .. code-block:: c++ + + true: + connect( + []() + { + foo(); + bar(); + }); + + false: + connect([]() { + foo(); + bar(); + }); + libclang -------- 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 @@ -992,6 +992,23 @@ /// } /// \endcode bool BeforeElse; + /// Wrap lambda block. + /// \code + /// true: + /// connect( + /// []() + /// { + /// foo(); + /// bar(); + /// }); + /// + /// false: + /// connect([]() { + /// foo(); + /// bar(); + /// }); + /// \endcode + bool BeforeLambdaBody; /// Indent the wrapped braces themselves. bool IndentBraces; /// If ``false``, empty function body can be put on a single line. 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 @@ -329,6 +329,11 @@ bool ContinuationIndenter::mustBreak(const LineState &State) { const FormatToken &Current = *State.NextToken; const FormatToken &Previous = *Current.Previous; + if (Style.BraceWrapping.BeforeLambdaBody && Current.CanBreakBefore && + Current.is(TT_LambdaLBrace)) { + auto LambdaBodyLength = getLengthToMatchingParen(Current, State.Stack); + return (LambdaBodyLength > getColumnLimit(State)); + } if (Current.MustBreakBefore || Current.is(TT_InlineASMColon)) return true; if (State.Stack.back().BreakBeforeClosingBrace && @@ -1079,6 +1084,18 @@ return State.Stack.back().Indent; } +static bool hasNestedBlockInlined(const FormatToken *Previous, + const FormatToken &Current, + const FormatStyle &Style) { + if (Previous->isNot(tok::l_paren)) + return true; + if (Previous->ParameterCount > 1) + return true; + + // Also a nested block if contains a lambda inside function with 1 parameter + return (Style.BraceWrapping.BeforeLambdaBody && Current.is(TT_LambdaLSquare)); +} + unsigned ContinuationIndenter::moveStateToNextToken(LineState &State, bool DryRun, bool Newline) { assert(State.Stack.size()); @@ -1181,8 +1198,7 @@ Previous->isOneOf(TT_BinaryOperator, TT_ConditionalExpr)) && !Previous->isOneOf(TT_DictLiteral, TT_ObjCMethodExpr)) { State.Stack.back().NestedBlockInlined = - !Newline && - (Previous->isNot(tok::l_paren) || Previous->ParameterCount > 1); + !Newline && hasNestedBlockInlined(Previous, Current, Style); } moveStatePastFakeLParens(State, Newline); @@ -1418,7 +1434,21 @@ ParenState(&Current, NewIndent, LastSpace, AvoidBinPacking, NoLineBreak)); State.Stack.back().NestedBlockIndent = NestedBlockIndent; State.Stack.back().BreakBeforeParameter = BreakBeforeParameter; - State.Stack.back().HasMultipleNestedBlocks = Current.BlockParameterCount > 1; + State.Stack.back().HasMultipleNestedBlocks = (Current.BlockParameterCount > 1); + + if (Style.BraceWrapping.BeforeLambdaBody && + Current.Next != nullptr && Current.Tok.is(tok::l_paren)) { + // Search for any parameter that is a lambda + FormatToken const *next = Current.Next; + while (next != nullptr) { + if (next->is(TT_LambdaLSquare)) { + State.Stack.back().HasMultipleNestedBlocks = true; + break; + } + next = next->Next; + } + } + State.Stack.back().IsInsideObjCArrayLiteral = Current.is(TT_ArrayInitializerLSquare) && Current.Previous && Current.Previous->is(tok::at); 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 @@ -579,6 +579,7 @@ IO.mapOptional("AfterExternBlock", Wrapping.AfterExternBlock); IO.mapOptional("BeforeCatch", Wrapping.BeforeCatch); IO.mapOptional("BeforeElse", Wrapping.BeforeElse); + IO.mapOptional("BeforeLambdaBody", Wrapping.BeforeLambdaBody); IO.mapOptional("IndentBraces", Wrapping.IndentBraces); IO.mapOptional("SplitEmptyFunction", Wrapping.SplitEmptyFunction); IO.mapOptional("SplitEmptyRecord", Wrapping.SplitEmptyRecord); @@ -666,8 +667,8 @@ false, false, false, false, false, false, false, false, false, - false, true, true, - true}; + false, false, true, + true, true}; switch (Style.BreakBeforeBraces) { case FormatStyle::BS_Linux: Expanded.BraceWrapping.AfterClass = true; @@ -715,14 +716,15 @@ Expanded.BraceWrapping.AfterExternBlock = true; Expanded.BraceWrapping.BeforeCatch = true; Expanded.BraceWrapping.BeforeElse = true; + Expanded.BraceWrapping.BeforeLambdaBody = true; break; case FormatStyle::BS_GNU: - Expanded.BraceWrapping = {true, true, FormatStyle::BWACS_Always, - true, true, true, - true, true, true, - true, true, true, - true, true, true, - true}; + Expanded.BraceWrapping = {true, true, FormatStyle::BWACS_Always, + true, true, true, + true, true, true, + true, true, true, + false, true, true, + true, true}; break; case FormatStyle::BS_WebKit: Expanded.BraceWrapping.AfterFunction = true; @@ -766,8 +768,8 @@ false, false, false, false, false, false, false, false, false, - false, true, true, - true}; + false, false, true, + true, true}; LLVMStyle.BreakAfterJavaFieldAnnotations = false; LLVMStyle.BreakConstructorInitializers = FormatStyle::BCIS_BeforeColon; LLVMStyle.BreakInheritanceList = FormatStyle::BILS_BeforeColon; 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 @@ -3122,6 +3122,56 @@ !Tok.isOneOf(TT_ObjCBlockLBrace, TT_LambdaLBrace, TT_DictLiteral); } +// Returns 'true' if 'Tok' is an function argument. +static bool IsFunctionArgument(const FormatToken &Tok) { + return Tok.MatchingParen && Tok.MatchingParen->Next && + Tok.MatchingParen->Next->isOneOf(tok::comma, tok::r_paren); +} + +static bool +isItAnEmptyLambdaAllowed(const FormatToken &Tok, + FormatStyle::ShortLambdaStyle ShortLambdaOption) { + return Tok.Children.empty() && ShortLambdaOption != FormatStyle::SLS_None; +} + +static bool +isItAInlineLambdaAllowed(const FormatToken &Tok, + FormatStyle::ShortLambdaStyle ShortLambdaOption) { + return (ShortLambdaOption == FormatStyle::SLS_Inline && + IsFunctionArgument(Tok)) || + (ShortLambdaOption == FormatStyle::SLS_All); +} + +static bool isOneChildWithoutMustBreakBefore(const FormatToken &Tok) { + if (Tok.Children.size() != 1) + return false; + FormatToken *curElt = Tok.Children[0]->First; + while (curElt) { + if (curElt->MustBreakBefore) + return false; + curElt = curElt->Next; + } + return true; +} +static bool +isAllmanLambdaBrace(const FormatToken &Tok) { + return (Tok.is(tok::l_brace) && Tok.BlockKind == BK_Block && + !Tok.isOneOf(TT_ObjCBlockLBrace, TT_DictLiteral)); +} + +static bool +isAllmanBraceIncludedBreakableLambda(const FormatToken &Tok, + FormatStyle::ShortLambdaStyle ShortLambdaOption) { + if (!isAllmanLambdaBrace(Tok)) + return false; + + if (isItAnEmptyLambdaAllowed(Tok, ShortLambdaOption)) + return false; + + return !isItAInlineLambdaAllowed(Tok, ShortLambdaOption) || + !isOneChildWithoutMustBreakBefore(Tok); +} + bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line, const FormatToken &Right) { const FormatToken &Left = *Right.Previous; @@ -3257,6 +3307,14 @@ } if (Right.is(TT_InlineASMBrace)) return Right.HasUnescapedNewline; + + auto ShortLambdaOption = Style.AllowShortLambdasOnASingleLine; + if (Style.BraceWrapping.BeforeLambdaBody && + (isAllmanBraceIncludedBreakableLambda(Left, ShortLambdaOption) || + isAllmanBraceIncludedBreakableLambda(Right, ShortLambdaOption))) { + return true; + } + if (isAllmanBrace(Left) || isAllmanBrace(Right)) return (Line.startsWith(tok::kw_enum) && Style.BraceWrapping.AfterEnum) || (Line.startsWith(tok::kw_typedef, tok::kw_enum) && @@ -3268,8 +3326,7 @@ return true; if (Left.is(TT_LambdaLBrace)) { - if (Left.MatchingParen && Left.MatchingParen->Next && - Left.MatchingParen->Next->isOneOf(tok::comma, tok::r_paren) && + if (IsFunctionArgument(Left) && Style.AllowShortLambdasOnASingleLine == FormatStyle::SLS_Inline) return false; @@ -3674,11 +3731,21 @@ if ((Left.is(TT_AttributeSquare) && Right.is(tok::l_square)) || (Left.is(tok::r_square) && Right.is(TT_AttributeSquare))) return false; + + auto ShortLambdaOption = Style.AllowShortLambdasOnASingleLine; + if (Style.BraceWrapping.BeforeLambdaBody) { + if (isAllmanLambdaBrace(Left)) + return !isItAnEmptyLambdaAllowed(Left, ShortLambdaOption); + if (isAllmanLambdaBrace(Right)) + return !isItAnEmptyLambdaAllowed(Right, ShortLambdaOption); + } + return Left.isOneOf(tok::comma, tok::coloncolon, tok::semi, tok::l_brace, tok::kw_class, tok::kw_struct, tok::comment) || Right.isMemberAccess() || Right.isOneOf(TT_TrailingReturnArrow, TT_LambdaArrow, tok::lessless, tok::colon, tok::l_square, tok::at) || + (Style.BraceWrapping.BeforeLambdaBody && Right.is(tok::l_brace)) || (Left.is(tok::r_paren) && Right.isOneOf(tok::identifier, tok::kw_const)) || (Left.is(tok::l_paren) && !Right.is(tok::r_paren)) || 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 @@ -12673,6 +12673,7 @@ CHECK_PARSE_NESTED_BOOL(BraceWrapping, AfterExternBlock); CHECK_PARSE_NESTED_BOOL(BraceWrapping, BeforeCatch); CHECK_PARSE_NESTED_BOOL(BraceWrapping, BeforeElse); + CHECK_PARSE_NESTED_BOOL(BraceWrapping, BeforeLambdaBody); CHECK_PARSE_NESTED_BOOL(BraceWrapping, IndentBraces); CHECK_PARSE_NESTED_BOOL(BraceWrapping, SplitEmptyFunction); CHECK_PARSE_NESTED_BOOL(BraceWrapping, SplitEmptyRecord); @@ -13962,6 +13963,245 @@ "function([]() { return b; }, a)", MergeInline); verifyFormat("function(a, []() { return b; })", "function(a, []() { return b; })", MergeInline); + + // Check option "BraceWrapping.BeforeLambdaBody" and different state of + // AllowShortLambdasOnASingleLine + FormatStyle LLVMWithBeforeLambdaBody = getLLVMStyle(); + LLVMWithBeforeLambdaBody.BreakBeforeBraces = FormatStyle::BS_Custom; + LLVMWithBeforeLambdaBody.BraceWrapping.BeforeLambdaBody = true; + LLVMWithBeforeLambdaBody.AllowShortLambdasOnASingleLine = + FormatStyle::ShortLambdaStyle::SLS_None; + verifyFormat("FctWithOneNestedLambdaInline_SLS_None(\n" + " []()\n" + " {\n" + " return 17;\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithOneNestedLambdaEmpty_SLS_None(\n" + " []()\n" + " {\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat("auto fct_SLS_None = []()\n" + "{\n" + " return 17;\n" + "};", + LLVMWithBeforeLambdaBody); + verifyFormat("TwoNestedLambdas_SLS_None(\n" + " []()\n" + " {\n" + " return Call(\n" + " []()\n" + " {\n" + " return 17;\n" + " });\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat("void Fct()\n" + "{\n" + " return {[]()\n" + " {\n" + " return 17;\n" + " }};\n" + "}", + LLVMWithBeforeLambdaBody); + + LLVMWithBeforeLambdaBody.AllowShortLambdasOnASingleLine = + FormatStyle::ShortLambdaStyle::SLS_Empty; + verifyFormat("FctWithOneNestedLambdaInline_SLS_Empty(\n" + " []()\n" + " {\n" + " return 17;\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithOneNestedLambdaEmpty_SLS_Empty([]() {});", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithOneNestedLambdaEmptyInsideAVeryVeryVeryVeryVeryVeryVeryL" + "ongFunctionName_SLS_Empty(\n" + " []() {});", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithMultipleParams_SLS_Empty(A, B,\n" + " []()\n" + " {\n" + " return 17;\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat("auto fct_SLS_Empty = []()\n" + "{\n" + " return 17;\n" + "};", + LLVMWithBeforeLambdaBody); + verifyFormat("TwoNestedLambdas_SLS_Empty(\n" + " []()\n" + " {\n" + " return Call([]() {});\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat("TwoNestedLambdas_SLS_Empty(A,\n" + " []()\n" + " {\n" + " return Call([]() {});\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat( + "FctWithLongLineInLambda_SLS_Empty(\n" + " []()\n" + " {\n" + " return HereAVeryLongLine(ThatWillBeFormatted, OnMultipleLine,\n" + " AndShouldNotBeConsiderAsInline,\n" + " LambdaBodyMustBeBreak);\n" + " });", + LLVMWithBeforeLambdaBody); + + LLVMWithBeforeLambdaBody.AllowShortLambdasOnASingleLine = + FormatStyle::ShortLambdaStyle::SLS_Inline; + verifyFormat("FctWithOneNestedLambdaInline_SLS_Inline([]() { return 17; });", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithOneNestedLambdaEmpty_SLS_Inline([]() {});", + LLVMWithBeforeLambdaBody); + verifyFormat("auto fct_SLS_Inline = []()\n" + "{\n" + " return 17;\n" + "};", + LLVMWithBeforeLambdaBody); + verifyFormat("TwoNestedLambdas_SLS_Inline([]() { return Call([]() { return " + "17; }); });", + LLVMWithBeforeLambdaBody); + verifyFormat( + "FctWithLongLineInLambda_SLS_Inline(\n" + " []()\n" + " {\n" + " return HereAVeryLongLine(ThatWillBeFormatted, OnMultipleLine,\n" + " AndShouldNotBeConsiderAsInline,\n" + " LambdaBodyMustBeBreak);\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithMultipleParams_SLS_Inline(" + "VeryLongParameterThatShouldAskToBeOnMultiLine,\n" + " []() { return 17; });", + LLVMWithBeforeLambdaBody); + verifyFormat( + "FctWithMultipleParams_SLS_Inline(FirstParam, []() { return 17; });", + LLVMWithBeforeLambdaBody); + + LLVMWithBeforeLambdaBody.AllowShortLambdasOnASingleLine = + FormatStyle::ShortLambdaStyle::SLS_All; + verifyFormat("FctWithOneNestedLambdaInline_SLS_All([]() { return 17; });", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithOneNestedLambdaEmpty_SLS_All([]() {});", + LLVMWithBeforeLambdaBody); + verifyFormat("auto fct_SLS_All = []() { return 17; };", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithOneParam_SLS_All(\n" + " []()\n" + " {\n" + " // A cool function...\n" + " return 43;\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithMultipleParams_SLS_All(" + "VeryLongParameterThatShouldAskToBeOnMultiLine,\n" + " []() { return 17; });", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithMultipleParams_SLS_All(A, []() { return 17; });", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithMultipleParams_SLS_All(A, B, []() { return 17; });", + LLVMWithBeforeLambdaBody); + verifyFormat( + "FctWithLongLineInLambda_SLS_All(\n" + " []()\n" + " {\n" + " return HereAVeryLongLine(ThatWillBeFormatted, OnMultipleLine,\n" + " AndShouldNotBeConsiderAsInline,\n" + " LambdaBodyMustBeBreak);\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat( + "auto fct_SLS_All = []()\n" + "{\n" + " return HereAVeryLongLine(ThatWillBeFormatted, OnMultipleLine,\n" + " AndShouldNotBeConsiderAsInline,\n" + " LambdaBodyMustBeBreak);\n" + "};", + LLVMWithBeforeLambdaBody); + LLVMWithBeforeLambdaBody.BinPackParameters = false; + verifyFormat("FctAllOnSameLine_SLS_All([]() { return S; }, Fst, Second);", + LLVMWithBeforeLambdaBody); + verifyFormat( + "FctWithLongLineInLambda_SLS_All([]() { return SomeValueNotSoLong; },\n" + " FirstParam,\n" + " SecondParam,\n" + " ThirdParam,\n" + " FourthParam);", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithLongLineInLambda_SLS_All(\n" + " []() { return " + "SomeValueVeryVeryVeryVeryVeryVeryVeryVeryVeryLong; },\n" + " FirstParam,\n" + " SecondParam,\n" + " ThirdParam,\n" + " FourthParam);", + LLVMWithBeforeLambdaBody); + verifyFormat( + "FctWithLongLineInLambda_SLS_All(FirstParam,\n" + " SecondParam,\n" + " ThirdParam,\n" + " FourthParam,\n" + " []() { return SomeValueNotSoLong; });", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithLongLineInLambda_SLS_All(\n" + " []()\n" + " {\n" + " return " + "HereAVeryLongLineThatWillBeFormattedOnMultipleLineAndShouldNotB" + "eConsiderAsInline;\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat( + "FctWithLongLineInLambda_SLS_All(\n" + " []()\n" + " {\n" + " return HereAVeryLongLine(ThatWillBeFormatted, OnMultipleLine,\n" + " AndShouldNotBeConsiderAsInline,\n" + " LambdaBodyMustBeBreak);\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithTwoParams_SLS_All(\n" + " []()\n" + " {\n" + " // A cool function...\n" + " return 43;\n" + " },\n" + " 87);", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithTwoParams_SLS_All([]() { return 43; }, 87);", + LLVMWithBeforeLambdaBody); + verifyFormat("FctWithOneNestedLambdas_SLS_All([]() { return 17; });", + LLVMWithBeforeLambdaBody); + verifyFormat( + "TwoNestedLambdas_SLS_All([]() { return Call([]() { return 17; }); });", + LLVMWithBeforeLambdaBody); + verifyFormat("TwoNestedLambdas_SLS_All([]() { return Call([]() { return 17; " + "}); }, x);", + LLVMWithBeforeLambdaBody); + verifyFormat("TwoNestedLambdas_SLS_All(\n" + " []()\n" + " {\n" + " // A cool function...\n" + " return Call([]() { return 17; });\n" + " });", + LLVMWithBeforeLambdaBody); + verifyFormat("TwoNestedLambdas_SLS_All(\n" + " []()\n" + " {\n" + " return Call(\n" + " []()\n" + " {\n" + " // A cool function...\n" + " return 17;\n" + " });\n" + " });", + LLVMWithBeforeLambdaBody); } TEST_F(FormatTest, EmptyLinesInLambdas) {