https://github.com/christophe-calmejane updated https://github.com/llvm/llvm-project/pull/182319
>From 5c43b28d0a4923ac57433eadfedf49a8a9894d10 Mon Sep 17 00:00:00 2001 From: Christophe Calmejane <[email protected]> Date: Wed, 18 Feb 2026 17:17:36 +0100 Subject: [PATCH 1/2] [clang-format] Fix BeforeLambdaBody causing incorrect argument wrapping When BraceWrapping.BeforeLambdaBody is true and a function call contains a lambda that is not the last argument (either followed by more arguments or when multiple lambdas are present), the DisallowLineBreaks heuristic in ContinuationIndenter::addTokenOnCurrentLine incorrectly prevents line breaks in the parent scope. This causes all arguments before the lambda to be wrapped to a new line, even though they would fit on the first line. The DisallowLineBreaks heuristic is designed to keep arguments together when lambda bodies are inlined. However, with BeforeLambdaBody, lambda bodies are intentionally expanded using Allman-style braces, so the heuristic should not apply. Fix: Return false from DisallowLineBreaks when BeforeLambdaBody is set. Before: func( a, b, [](int x) { return x; }, c); After: func(a, b, [](int x) { return x; }, c); --- clang/lib/Format/ContinuationIndenter.cpp | 7 +++ clang/unittests/Format/FormatTest.cpp | 58 +++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp index f1edd71df73fa..f097704fa8720 100644 --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -767,6 +767,13 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun, return false; } + // When BeforeLambdaBody is set, lambda bodies are intentionally expanded + // onto their own lines. Do not prevent line breaks in the parent scope, + // as that would force all arguments (including those before the lambda) + // to be wrapped to new lines. + if (Style.BraceWrapping.BeforeLambdaBody) + return false; + // For example, `/*Newline=*/false`. if (Previous.is(TT_BlockComment) && Current.SpacesRequiredBefore == 0) return false; diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index d6c49163abbc9..5e56fce1f8d8c 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -24325,6 +24325,64 @@ TEST_F(FormatTest, FormatsLambdas) { " })));\n" "}", Style); + + // Verify that BeforeLambdaBody does not cause arguments before the lambda to + // be wrapped to a new line when the lambda is not the last argument. + FormatStyle BeforeLambdaBodyStyle = getLLVMStyle(); + BeforeLambdaBodyStyle.BreakBeforeBraces = FormatStyle::BS_Custom; + BeforeLambdaBodyStyle.BraceWrapping.BeforeLambdaBody = true; + BeforeLambdaBodyStyle.AllowShortLambdasOnASingleLine = + FormatStyle::ShortLambdaStyle::SLS_None; + + // Lambda followed by another argument: args before lambda stay on first line. + verifyFormat("void test() {\n" + " func(a, b,\n" + " [](int x)\n" + " {\n" + " return x;\n" + " },\n" + " c);\n" + "}", + BeforeLambdaBodyStyle); + // Two lambdas as arguments: args before lambdas stay on first line. + verifyFormat("void test() {\n" + " func(a, b,\n" + " [](int x)\n" + " {\n" + " return x;\n" + " },\n" + " [](int y)\n" + " {\n" + " return y;\n" + " });\n" + "}", + BeforeLambdaBodyStyle); + + BeforeLambdaBodyStyle.AllowShortLambdasOnASingleLine = + FormatStyle::ShortLambdaStyle::SLS_Empty; + // With SLS_Empty, lambda followed by another argument. + verifyFormat("void test() {\n" + " func(a, b,\n" + " [](int x)\n" + " {\n" + " return x;\n" + " },\n" + " c);\n" + "}", + BeforeLambdaBodyStyle); + // With SLS_Empty, two lambdas as arguments. + verifyFormat("void test() {\n" + " func(a, b,\n" + " [](int x)\n" + " {\n" + " return x;\n" + " },\n" + " [](int y)\n" + " {\n" + " return y;\n" + " });\n" + "}", + BeforeLambdaBodyStyle); } TEST_F(FormatTest, LambdaWithLineComments) { >From 369ea82d0816da3bbc1a109473c842c544a8d365 Mon Sep 17 00:00:00 2001 From: Christophe Calmejane <[email protected]> Date: Fri, 20 Feb 2026 14:27:16 +0100 Subject: [PATCH 2/2] Changed implementation according to code review --- clang/lib/Format/ContinuationIndenter.cpp | 18 ++++++++++-------- clang/lib/Format/TokenAnnotator.cpp | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp index f097704fa8720..8caab0e03218d 100644 --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -441,7 +441,7 @@ bool ContinuationIndenter::mustBreak(const LineState &State) { if (Style.BraceWrapping.BeforeLambdaBody && Current.CanBreakBefore && Current.is(TT_LambdaLBrace) && Previous.isNot(TT_LineComment)) { auto LambdaBodyLength = getLengthToMatchingParen(Current, State.Stack); - return LambdaBodyLength > getColumnLimit(State); + return Current.MustBreakBefore || LambdaBodyLength > getColumnLimit(State); } if (Current.MustBreakBefore || (Current.is(TT_InlineASMColon) && @@ -767,13 +767,6 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun, return false; } - // When BeforeLambdaBody is set, lambda bodies are intentionally expanded - // onto their own lines. Do not prevent line breaks in the parent scope, - // as that would force all arguments (including those before the lambda) - // to be wrapped to new lines. - if (Style.BraceWrapping.BeforeLambdaBody) - return false; - // For example, `/*Newline=*/false`. if (Previous.is(TT_BlockComment) && Current.SpacesRequiredBefore == 0) return false; @@ -788,6 +781,15 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun, if (Prev->BlockParameterCount == 0) return false; + // If any lambda brace in the argument list has MustBreakBefore set + // (e.g. for Allman-style BeforeLambdaBody), the "all arguments on one + // line" strategy is impossible, so allow natural line wrapping. + for (const auto *Tok = Prev->Next; Tok && Tok != Prev->MatchingParen; + Tok = Tok->Next) { + if (Tok->is(TT_LambdaLBrace) && Tok->MustBreakBefore) + return false; + } + // Multiple lambdas in the same function call. if (Prev->BlockParameterCount > 1) return true; diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index c66d883b30870..4843a7cb46e9f 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -6077,6 +6077,23 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line, return true; } + // When BeforeLambdaBody is set, force a break before function argument lambda + // braces. This ensures Allman-style formatting is applied even when the + // DisallowLineBreaks heuristic would otherwise prevent line breaks in the + // parent scope (e.g. with multiple lambdas or a lambda followed by another + // argument). Only force the break when the lambda brace will definitely be on + // a new line: SLS_None always breaks, and SLS_Empty breaks for non-empty + // lambdas. For SLS_Inline/SLS_All, the brace may stay inline for short + // lambdas, so we let the runtime decision in mustBreak() handle it. + if (Style.BraceWrapping.BeforeLambdaBody && Right.is(TT_LambdaLBrace) && + IsFunctionArgument(Right)) { + auto SLS = Style.AllowShortLambdasOnASingleLine; + if (SLS == FormatStyle::SLS_None || + (SLS == FormatStyle::SLS_Empty && !Right.Children.empty())) { + return true; + } + } + // Put multiple Java annotation on a new line. if ((Style.isJava() || Style.isJavaScript()) && Left.is(TT_LeadingJavaAnnotation) && _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
