diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2841,6 +2841,42 @@ bar(); } } +**LambdaBodyIndentation** (``LambdaBodyIndentationKind``) + The indentation style of lambda bodies. ``Signature`` (the default) + causes the lambda body to be indented one additional level relative to + the indentation level of the signature. ``OuterScope`` forces the lambda + body to be indented one additional level relative to the parent scope + containing the lambda signature. For callback-heavy code, it may improve + readability to have the signature indented two levels and to use + ``OuterScope``. The KJ style guide requires ``OuterScope`. + `KJ style guide + `_ + + Possible values: + + * ``LBI_Signature`` (in configuration: ``Signature``) + Align lambda body relative to the lambda signature. This is the default. + + .. code-block:: c++ + + someMethod( + [](SomeReallyLongLambdaSignatureArgument foo) { + return; + }); + + * ``LBI_OuterScope`` (in configuration: ``OuterScope``) + Align lambda body relative to the indentation level of the outer scope + the lambda signature resides in. + + .. code-block:: c++ + + someMethod( + [](SomeReallyLongLambdaSignatureArgument foo) { + return; + }); + + + **Language** (``LanguageKind``) Language, this format style is targeted at. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -259,6 +259,12 @@ - Option ``BreakInheritanceList`` gets a new style, ``AfterComma``. It breaks only after the commas that separate the base-specifiers. +- Option ``LambdaBodyIndentation`` has been added to control how the body of a + lambda is indented. The default ``Signature`` value indents the body one level + relative to whatever indentation the signature has. ``OuterScope`` lets you + change that so that the lambda body is indented one level relative to the scope + containing the lambda, regardless of where the lambda signature was placed. + - ``git-clang-format`` no longer formats changes to symbolic links. (Fixes https://llvm.org/PR46992.) 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 @@ -2490,6 +2490,38 @@ /// Language, this format style is targeted at. LanguageKind Language; + /// Indentation logic for lambda bodies. + enum LambdaBodyIndentationKind : unsigned char { + /// Align lambda body relative to the lambda signature. This is the default. + /// \code + /// someMethod( + /// [](SomeReallyLongLambdaSignatureArgument foo) { + /// return; + /// }); + /// \endcode + LBI_Signature, + /// Align lambda body relative to the indentation level of the outer scope + /// the lambda signature resides in. + /// \code + /// someMethod( + /// [](SomeReallyLongLambdaSignatureArgument foo) { + /// return; + /// }); + /// \endcode + LBI_OuterScope, + }; + + /// The indentation style of lambda bodies. ``Signature`` (the default) + /// causes the lambda body to be indented one additional level relative to + /// the indentation level of the signature. ``OuterScope`` forces the lambda + /// body to be indented one additional level relative to the parent scope + /// containing the lambda signature. For callback-heavy code, it may improve + /// readability to have the signature indented two levels and to use + /// ``OuterScope``. The KJ style guide requires ``OuterScope`. + /// `KJ style guide + /// `_ + LambdaBodyIndentationKind LambdaBodyIndentation; + /// A regular expression matching macros that start a block. /// \code /// # With: @@ -3377,6 +3409,7 @@ JavaScriptWrapImports == R.JavaScriptWrapImports && KeepEmptyLinesAtTheStartOfBlocks == R.KeepEmptyLinesAtTheStartOfBlocks && + LambdaBodyIndentation == R.LambdaBodyIndentation && MacroBlockBegin == R.MacroBlockBegin && MacroBlockEnd == R.MacroBlockEnd && MaxEmptyLinesToKeep == R.MaxEmptyLinesToKeep && 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 @@ -85,6 +85,15 @@ } }; +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, + FormatStyle::LambdaBodyIndentationKind &Value) { + IO.enumCase(Value, "Signature", FormatStyle::LBI_Signature); + IO.enumCase(Value, "OuterScope", FormatStyle::LBI_OuterScope); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::UseTabStyle &Value) { IO.enumCase(Value, "Never", FormatStyle::UT_Never); @@ -649,6 +658,7 @@ IO.mapOptional("JavaScriptWrapImports", Style.JavaScriptWrapImports); IO.mapOptional("KeepEmptyLinesAtTheStartOfBlocks", Style.KeepEmptyLinesAtTheStartOfBlocks); + IO.mapOptional("LambdaBodyIndentation", Style.LambdaBodyIndentation); IO.mapOptional("MacroBlockBegin", Style.MacroBlockBegin); IO.mapOptional("MacroBlockEnd", Style.MacroBlockEnd); IO.mapOptional("MaxEmptyLinesToKeep", Style.MaxEmptyLinesToKeep); @@ -1040,6 +1050,7 @@ LLVMStyle.JavaScriptQuotes = FormatStyle::JSQS_Leave; LLVMStyle.JavaScriptWrapImports = true; LLVMStyle.TabWidth = 8; + LLVMStyle.LambdaBodyIndentation = FormatStyle::LBI_Signature; LLVMStyle.MaxEmptyLinesToKeep = 1; LLVMStyle.KeepEmptyLinesAtTheStartOfBlocks = true; LLVMStyle.NamespaceIndentation = FormatStyle::NI_None; diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -823,8 +823,20 @@ return true; if (NewLine) { - int AdditionalIndent = State.Stack.back().Indent - - Previous.Children[0]->Level * Style.IndentWidth; + const ParenState &P = State.Stack.back(); + + int AdditionalIndent = + P.Indent - Previous.Children[0]->Level * Style.IndentWidth; + + if (Style.LambdaBodyIndentation == FormatStyle::LBI_OuterScope && + P.NestedBlockIndent == P.LastSpace) { + if (State.NextToken->MatchingParen && + State.NextToken->MatchingParen->is(TT_LambdaLBrace)) { + State.Stack.pop_back(); + } + if (LBrace->is(TT_LambdaLBrace)) + AdditionalIndent = 0; + } Penalty += BlockFormatter->format(Previous.Children, DryRun, AdditionalIndent, 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 @@ -18531,6 +18531,7 @@ " aaaaaaaaaaaaaaaaaaaaaaa;\n" " });", getLLVMStyleWithColumns(60)); + verifyFormat("SomeFunction({[&] {\n" " // comment\n" " },\n" @@ -19083,6 +19084,117 @@ " });\n" " });", LLVMWithBeforeLambdaBody); + + // Lambdas with different indentation styles. + Style = getLLVMStyleWithColumns(100); + EXPECT_EQ("SomeResult doSomething(SomeObject promise) {\n" + " return promise.then(\n" + " [this, &someVariable, someObject = " + "std::mv(s)](std::vector evaluated) mutable {\n" + " return someObject.startAsyncAction().then(\n" + " [this, &someVariable](AsyncActionResult result) " + "mutable { result.processMore(); });\n" + " });\n" + "}\n", + format("SomeResult doSomething(SomeObject promise) {\n" + " return promise.then([this, &someVariable, someObject = " + "std::mv(s)](std::vector evaluated) mutable {\n" + " return someObject.startAsyncAction().then([this, " + "&someVariable](AsyncActionResult result) mutable {\n" + " result.processMore();\n" + " });\n" + " });\n" + "}\n", + Style)); + Style.LambdaBodyIndentation = FormatStyle::LBI_OuterScope; + verifyFormat("test() {\n" + " ([]() -> {\n" + " int b = 32;\n" + " return 3;\n" + " }).foo();\n" + "}", + Style); + verifyFormat("test() {\n" + " []() -> {\n" + " int b = 32;\n" + " return 3;\n" + " }\n" + "}", + Style); + verifyFormat("std::sort(v.begin(), v.end(),\n" + " [](const auto &someLongArgumentName, const auto " + "&someOtherLongArgumentName) {\n" + " return someLongArgumentName.someMemberVariable < " + "someOtherLongArgumentName.someMemberVariable;\n" + "});", + Style); + verifyFormat("test() {\n" + " (\n" + " []() -> {\n" + " int b = 32;\n" + " return 3;\n" + " },\n" + " foo, bar)\n" + " .foo();\n" + "}", + Style); + verifyFormat("test() {\n" + " ([]() -> {\n" + " int b = 32;\n" + " return 3;\n" + " })\n" + " .foo()\n" + " .bar();\n" + "}", + Style); + EXPECT_EQ("SomeResult doSomething(SomeObject promise) {\n" + " return promise.then(\n" + " [this, &someVariable, someObject = " + "std::mv(s)](std::vector evaluated) mutable {\n" + " return someObject.startAsyncAction().then(\n" + " [this, &someVariable](AsyncActionResult result) mutable { " + "result.processMore(); });\n" + " });\n" + "}\n", + format("SomeResult doSomething(SomeObject promise) {\n" + " return promise.then([this, &someVariable, someObject = " + "std::mv(s)](std::vector evaluated) mutable {\n" + " return someObject.startAsyncAction().then([this, " + "&someVariable](AsyncActionResult result) mutable {\n" + " result.processMore();\n" + " });\n" + " });\n" + "}\n", + Style)); + EXPECT_EQ("SomeResult doSomething(SomeObject promise) {\n" + " return promise.then([this, &someVariable] {\n" + " return someObject.startAsyncAction().then(\n" + " [this, &someVariable](AsyncActionResult result) mutable { " + "result.processMore(); });\n" + " });\n" + "}\n", + format("SomeResult doSomething(SomeObject promise) {\n" + " return promise.then([this, &someVariable] {\n" + " return someObject.startAsyncAction().then([this, " + "&someVariable](AsyncActionResult result) mutable {\n" + " result.processMore();\n" + " });\n" + " });\n" + "}\n", + Style)); + Style = getGoogleStyle(); + Style.LambdaBodyIndentation = FormatStyle::LBI_OuterScope; + EXPECT_EQ("#define A \\\n" + " [] { \\\n" + " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( \\\n" + " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx); \\\n" + " }", + format("#define A [] { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( \\\n" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx); }", + Style)); + // TODO: The current formatting has a minor issue that's not worth fixing + // right now whereby the closing brace is indented relative to the signature + // instead of being aligned. This only happens with macros. } TEST_F(FormatTest, LambdaWithLineComments) {