https://github.com/ssubbotin updated https://github.com/llvm/llvm-project/pull/181051
>From 68ec0ba933aa51facc511aa1a69bc1bf4b0970bf Mon Sep 17 00:00:00 2001 From: Sergey Subbotin <[email protected]> Date: Thu, 12 Feb 2026 00:51:35 +0100 Subject: [PATCH 1/6] [clang-format] Add per-operator granularity for BreakBinaryOperations Extend BreakBinaryOperations to accept a structured YAML configuration with per-operator break rules and minimum chain length gating. The new PerOperator field allows specifying different break styles (Never, OnePerLine, RespectPrecedence) for specific operator groups, and MinChainLength gates breaking to chains of N or more operators. The simple scalar form remains fully backward-compatible. --- clang/docs/ClangFormatStyleOptions.rst | 72 ++++++++++------ clang/docs/ReleaseNotes.rst | 2 + clang/include/clang/Format/Format.h | 68 +++++++++++++++- clang/lib/Format/ContinuationIndenter.cpp | 46 +++++++++-- clang/lib/Format/Format.cpp | 35 +++++++- clang/lib/Format/TokenAnnotator.cpp | 22 +++-- clang/unittests/Format/ConfigParseTest.cpp | 38 +++++++-- clang/unittests/Format/FormatTest.cpp | 95 ++++++++++++++++++++-- 8 files changed, 330 insertions(+), 48 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 437f559fc0395..343e9649e43ce 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3613,42 +3613,66 @@ the configuration (without a prefix: ``Auto``). .. _BreakBinaryOperations: -**BreakBinaryOperations** (``BreakBinaryOperationsStyle``) :versionbadge:`clang-format 20` :ref:`¶ <BreakBinaryOperations>` +**BreakBinaryOperations** (``BreakBinaryOperationsOptions``) :versionbadge:`clang-format 20` :ref:`¶ <BreakBinaryOperations>` The break binary operations style to use. - Possible values: + Nested configuration flags: - * ``BBO_Never`` (in configuration: ``Never``) - Don't break binary operations + Options for ``BreakBinaryOperations``. - .. code-block:: c++ + If specified as a simple string (e.g. ``OnePerLine``), it behaves like + the original enum and applies to all binary operators. - aaa + bbbb * ccccc - ddddd + - eeeeeeeeeeeeeeee; + If specified as a struct, allows per-operator configuration: - * ``BBO_OnePerLine`` (in configuration: ``OnePerLine``) - Binary operations will either be all on the same line, or each operation - will have one line each. + .. code-block:: yaml - .. code-block:: c++ + BreakBinaryOperations: + Default: Never + PerOperator: + - Operators: ['&&', '||'] + Style: OnePerLine + MinChainLength: 3 - aaa + - bbbb * - ccccc - - ddddd + - eeeeeeeeeeeeeeee; + * ``BreakBinaryOperationsStyle Default`` :versionbadge:`clang-format 23` - * ``BBO_RespectPrecedence`` (in configuration: ``RespectPrecedence``) - Binary operations of a particular precedence that exceed the column - limit will have one line each. + The default break style for operators not covered by ``PerOperator``. - .. code-block:: c++ + Possible values: + + * ``BBO_Never`` (in configuration: ``Never``) + Don't break binary operations + + .. code-block:: c++ + + aaa + bbbb * ccccc - ddddd + + eeeeeeeeeeeeeeee; + + * ``BBO_OnePerLine`` (in configuration: ``OnePerLine``) + Binary operations will either be all on the same line, or each operation + will have one line each. + + .. code-block:: c++ + + aaa + + bbbb * + ccccc - + ddddd + + eeeeeeeeeeeeeeee; + + * ``BBO_RespectPrecedence`` (in configuration: ``RespectPrecedence``) + Binary operations of a particular precedence that exceed the column + limit will have one line each. + + .. code-block:: c++ + + aaa + + bbbb * ccccc - + ddddd + + eeeeeeeeeeeeeeee; - aaa + - bbbb * ccccc - - ddddd + - eeeeeeeeeeeeeeee; + * ``std::vector<BinaryOperationBreakRule> PerOperator`` Per-operator override rules. .. _BreakConstructorInitializers: diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index a2cdeb7bc1744..c7bacb8671acf 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -388,6 +388,8 @@ clang-format '-'/'+' and the return type in Objective-C method declarations - Add ``AfterComma`` value to ``BreakConstructorInitializers`` to allow breaking constructor initializers after commas, keeping the colon on the same line. +- Extend ``BreakBinaryOperations`` to accept a structured configuration with + per-operator break rules and minimum chain length gating via ``PerOperator``. libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index ca0bd47d10613..1dfec49c5fd44 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -2450,9 +2450,75 @@ struct FormatStyle { BBO_RespectPrecedence }; + /// A rule that specifies how to break a specific set of binary operators. + /// \version 23 + struct BinaryOperationBreakRule { + /// The list of operator tokens this rule applies to, + /// e.g. ``["&&", "||"]``. + std::vector<std::string> Operators; + /// The break style for these operators (defaults to ``OnePerLine``). + BreakBinaryOperationsStyle Style; + /// Minimum number of chained operators before the rule triggers. + /// ``0`` means always break (when the line is too long). + unsigned MinChainLength; + bool operator==(const BinaryOperationBreakRule &R) const { + return Operators == R.Operators && Style == R.Style && + MinChainLength == R.MinChainLength; + } + bool operator!=(const BinaryOperationBreakRule &R) const { + return !(*this == R); + } + }; + + /// Options for ``BreakBinaryOperations``. + /// + /// If specified as a simple string (e.g. ``OnePerLine``), it behaves like + /// the original enum and applies to all binary operators. + /// + /// If specified as a struct, allows per-operator configuration: + /// \code{.yaml} + /// BreakBinaryOperations: + /// Default: Never + /// PerOperator: + /// - Operators: ['&&', '||'] + /// Style: OnePerLine + /// MinChainLength: 3 + /// \endcode + /// \version 23 + struct BreakBinaryOperationsOptions { + /// The default break style for operators not covered by ``PerOperator``. + BreakBinaryOperationsStyle Default; + /// Per-operator override rules. + std::vector<BinaryOperationBreakRule> PerOperator; + const BinaryOperationBreakRule *findRuleForOperator(StringRef Op) const { + for (const auto &Rule : PerOperator) { + for (const auto &O : Rule.Operators) + if (O == Op) + return &Rule; + } + return nullptr; + } + BreakBinaryOperationsStyle getStyleForOperator(StringRef Op) const { + if (const auto *Rule = findRuleForOperator(Op)) + return Rule->Style; + return Default; + } + unsigned getMinChainLengthForOperator(StringRef Op) const { + if (const auto *Rule = findRuleForOperator(Op)) + return Rule->MinChainLength; + return 0; + } + bool operator==(const BreakBinaryOperationsOptions &R) const { + return Default == R.Default && PerOperator == R.PerOperator; + } + bool operator!=(const BreakBinaryOperationsOptions &R) const { + return !(*this == R); + } + }; + /// The break binary operations style to use. /// \version 20 - BreakBinaryOperationsStyle BreakBinaryOperations; + BreakBinaryOperationsOptions BreakBinaryOperations; /// Different ways to break initializers. enum BreakConstructorInitializersStyle : int8_t { diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp index f1edd71df73fa..bd44c29c41043 100644 --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -144,14 +144,50 @@ static bool startsNextOperand(const FormatToken &Current) { return isAlignableBinaryOperator(Previous) && !Current.isTrailingComment(); } +// Returns the number of operators in the chain containing \c Op. +static unsigned getChainLength(const FormatToken &Op) { + const FormatToken *Last = &Op; + while (Last->NextOperator) + Last = Last->NextOperator; + return Last->OperatorIndex + 1; +} + // Returns \c true if \c Current is a binary operation that must break. static bool mustBreakBinaryOperation(const FormatToken &Current, const FormatStyle &Style) { - return Style.BreakBinaryOperations != FormatStyle::BBO_Never && - Current.CanBreakBefore && - (Style.BreakBeforeBinaryOperators == FormatStyle::BOS_None - ? startsNextOperand - : isAlignableBinaryOperator)(Current); + if (!Current.CanBreakBefore) + return false; + + // Determine the operator token: when breaking after the operator, + // it is Current.Previous; when breaking before, it is Current itself. + bool BreakBefore = Style.BreakBeforeBinaryOperators != FormatStyle::BOS_None; + const FormatToken *OpToken = BreakBefore ? &Current : Current.Previous; + + if (!OpToken) + return false; + + // Check that this is an alignable binary operator. + if (BreakBefore) { + if (!isAlignableBinaryOperator(Current)) + return false; + } else { + if (!startsNextOperand(Current)) + return false; + } + + // Look up per-operator rule or fall back to Default. + auto EffStyle = + Style.BreakBinaryOperations.getStyleForOperator(OpToken->TokenText); + if (EffStyle == FormatStyle::BBO_Never) + return false; + + // Check MinChainLength: if the chain is too short, don't force a break. + unsigned MinChain = Style.BreakBinaryOperations.getMinChainLengthForOperator( + OpToken->TokenText); + if (MinChain > 0 && getChainLength(*OpToken) < MinChain) + return false; + + return true; } static bool opensProtoMessageField(const FormatToken &LessTok, diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 738e99dbb56a8..8f394dd37360d 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -31,6 +31,7 @@ using clang::format::FormatStyle; LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::RawStringFormat) +LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::BinaryOperationBreakRule) enum BracketAlignmentStyle : int8_t { BAS_Align, @@ -275,6 +276,38 @@ struct ScalarEnumerationTraits<FormatStyle::BreakBinaryOperationsStyle> { } }; +template <> struct MappingTraits<FormatStyle::BinaryOperationBreakRule> { + static void mapping(IO &IO, FormatStyle::BinaryOperationBreakRule &Value) { + IO.mapOptional("Operators", Value.Operators); + // Default to OnePerLine since a per-operator rule with Never is a no-op. + if (!IO.outputting()) + Value.Style = FormatStyle::BBO_OnePerLine; + IO.mapOptional("Style", Value.Style); + IO.mapOptional("MinChainLength", Value.MinChainLength); + } +}; + +template <> struct MappingTraits<FormatStyle::BreakBinaryOperationsOptions> { + static void enumInput(IO &IO, + FormatStyle::BreakBinaryOperationsOptions &Value) { + IO.enumCase(Value, "Never", + FormatStyle::BreakBinaryOperationsOptions( + {FormatStyle::BBO_Never, {}})); + IO.enumCase(Value, "OnePerLine", + FormatStyle::BreakBinaryOperationsOptions( + {FormatStyle::BBO_OnePerLine, {}})); + IO.enumCase(Value, "RespectPrecedence", + FormatStyle::BreakBinaryOperationsOptions( + {FormatStyle::BBO_RespectPrecedence, {}})); + } + + static void mapping(IO &IO, + FormatStyle::BreakBinaryOperationsOptions &Value) { + IO.mapOptional("Default", Value.Default); + IO.mapOptional("PerOperator", Value.PerOperator); + } +}; + template <> struct ScalarEnumerationTraits<FormatStyle::BreakConstructorInitializersStyle> { static void @@ -1725,7 +1758,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.BreakBeforeInlineASMColon = FormatStyle::BBIAS_OnlyMultiline; LLVMStyle.BreakBeforeTemplateCloser = false; LLVMStyle.BreakBeforeTernaryOperators = true; - LLVMStyle.BreakBinaryOperations = FormatStyle::BBO_Never; + LLVMStyle.BreakBinaryOperations = {FormatStyle::BBO_Never, {}}; LLVMStyle.BreakConstructorInitializers = FormatStyle::BCIS_BeforeColon; LLVMStyle.BreakFunctionDefinitionParameters = false; LLVMStyle.BreakInheritanceList = FormatStyle::BILS_BeforeColon; diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index c66d883b30870..86d11a7e64eb9 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -3276,14 +3276,22 @@ class ExpressionParser { parse(Precedence + 1); int CurrentPrecedence = getCurrentPrecedence(); - if (Style.BreakBinaryOperations == FormatStyle::BBO_OnePerLine && - CurrentPrecedence > prec::Conditional && + if (CurrentPrecedence > prec::Conditional && CurrentPrecedence < prec::PointerToMember) { - // When BreakBinaryOperations is set to BreakAll, - // all operations will be on the same line or on individual lines. - // Override precedence to avoid adding fake parenthesis which could - // group operations of a different precedence level on the same line - CurrentPrecedence = prec::Additive; + // Check whether this operator's effective style is OnePerLine. + // If so, override precedence to avoid adding fake parenthesis which + // could group operations of a different precedence level on the same + // line. + auto EffStyle = Style.BreakBinaryOperations.Default; + if (Current) { + if (const auto *Rule = + Style.BreakBinaryOperations.findRuleForOperator( + Current->TokenText)) { + EffStyle = Rule->Style; + } + } + if (EffStyle == FormatStyle::BBO_OnePerLine) + CurrentPrecedence = prec::Additive; } if (Precedence == CurrentPrecedence && Current && diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index bea6b54883265..95a672157eaed 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -453,13 +453,41 @@ TEST(ConfigParseTest, ParsesConfiguration) { CHECK_PARSE("BreakBeforeBinaryOperators: true", BreakBeforeBinaryOperators, FormatStyle::BOS_All); - Style.BreakBinaryOperations = FormatStyle::BBO_Never; + Style.BreakBinaryOperations = {FormatStyle::BBO_Never, {}}; CHECK_PARSE("BreakBinaryOperations: OnePerLine", BreakBinaryOperations, - FormatStyle::BBO_OnePerLine); + FormatStyle::BreakBinaryOperationsOptions( + {FormatStyle::BBO_OnePerLine, {}})); CHECK_PARSE("BreakBinaryOperations: RespectPrecedence", BreakBinaryOperations, - FormatStyle::BBO_RespectPrecedence); - CHECK_PARSE("BreakBinaryOperations: Never", BreakBinaryOperations, - FormatStyle::BBO_Never); + FormatStyle::BreakBinaryOperationsOptions( + {FormatStyle::BBO_RespectPrecedence, {}})); + CHECK_PARSE( + "BreakBinaryOperations: Never", BreakBinaryOperations, + FormatStyle::BreakBinaryOperationsOptions({FormatStyle::BBO_Never, {}})); + + // Structured form + Style.BreakBinaryOperations = {FormatStyle::BBO_Never, {}}; + CHECK_PARSE("BreakBinaryOperations:\n" + " Default: OnePerLine", + BreakBinaryOperations, + FormatStyle::BreakBinaryOperationsOptions( + {FormatStyle::BBO_OnePerLine, {}})); + + Style.BreakBinaryOperations = {FormatStyle::BBO_Never, {}}; + EXPECT_EQ(0, parseConfiguration("BreakBinaryOperations:\n" + " Default: Never\n" + " PerOperator:\n" + " - Operators: ['&&', '||']\n" + " Style: OnePerLine\n" + " MinChainLength: 3", + &Style) + .value()); + EXPECT_EQ(Style.BreakBinaryOperations.Default, FormatStyle::BBO_Never); + ASSERT_EQ(Style.BreakBinaryOperations.PerOperator.size(), 1u); + std::vector<std::string> ExpectedOps = {"&&", "||"}; + EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].Operators, ExpectedOps); + EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].Style, + FormatStyle::BBO_OnePerLine); + EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].MinChainLength, 3u); Style.BreakConstructorInitializers = FormatStyle::BCIS_BeforeColon; CHECK_PARSE("BreakConstructorInitializers: BeforeComma", diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index d6c49163abbc9..da8558ad25452 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -28403,7 +28403,9 @@ TEST_F(FormatTest, SpaceBetweenKeywordAndLiteral) { TEST_F(FormatTest, BreakBinaryOperations) { auto Style = getLLVMStyleWithColumns(60); - EXPECT_EQ(Style.BreakBinaryOperations, FormatStyle::BBO_Never); + FormatStyle::BreakBinaryOperationsOptions ExpectedDefault = { + FormatStyle::BBO_Never, {}}; + EXPECT_EQ(Style.BreakBinaryOperations, ExpectedDefault); // Logical operations verifyFormat("if (condition1 && condition2) {\n" @@ -28442,7 +28444,7 @@ TEST_F(FormatTest, BreakBinaryOperations) { " longOperand_3_;", Style); - Style.BreakBinaryOperations = FormatStyle::BBO_OnePerLine; + Style.BreakBinaryOperations = {FormatStyle::BBO_OnePerLine, {}}; // Logical operations verifyFormat("if (condition1 && condition2) {\n" @@ -28527,7 +28529,7 @@ TEST_F(FormatTest, BreakBinaryOperations) { " longOperand_3_;", Style); - Style.BreakBinaryOperations = FormatStyle::BBO_RespectPrecedence; + Style.BreakBinaryOperations = {FormatStyle::BBO_RespectPrecedence, {}}; verifyFormat("result = op1 + op2 * op3 - op4;", Style); verifyFormat("result = operand1 +\n" @@ -28559,7 +28561,7 @@ TEST_F(FormatTest, BreakBinaryOperations) { " longOperand_3_;", Style); - Style.BreakBinaryOperations = FormatStyle::BBO_OnePerLine; + Style.BreakBinaryOperations = {FormatStyle::BBO_OnePerLine, {}}; Style.BreakBeforeBinaryOperators = FormatStyle::BOS_NonAssignment; // Logical operations @@ -28640,7 +28642,7 @@ TEST_F(FormatTest, BreakBinaryOperations) { " >> longOperand_3_;", Style); - Style.BreakBinaryOperations = FormatStyle::BBO_RespectPrecedence; + Style.BreakBinaryOperations = {FormatStyle::BBO_RespectPrecedence, {}}; verifyFormat("result = op1 + op2 * op3 - op4;", Style); verifyFormat("result = operand1\n" @@ -28673,6 +28675,89 @@ TEST_F(FormatTest, BreakBinaryOperations) { Style); } +TEST_F(FormatTest, BreakBinaryOperationsPerOperator) { + auto Style = getLLVMStyleWithColumns(60); + + // Per-operator override: && and || are OnePerLine, rest is Never (default). + FormatStyle::BinaryOperationBreakRule LogicalRule; + LogicalRule.Operators = {"&&", "||"}; + LogicalRule.Style = FormatStyle::BBO_OnePerLine; + LogicalRule.MinChainLength = 0; + + Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never; + Style.BreakBinaryOperations.PerOperator = {LogicalRule}; + + // Logical operators break one-per-line when line is too long. + verifyFormat("bool x = loooooooooooooongcondition1 &&\n" + " loooooooooooooongcondition2 &&\n" + " loooooooooooooongcondition3;", + Style); + + // Arithmetic operators stay with default (Never) — no forced break. + verifyFormat("int x = loooooooooooooongop1 + looooooooongop2 +\n" + " loooooooooooooooooooooongop3;", + Style); + + // Short logical chain that fits on one line stays on one line. + verifyFormat("bool x = a && b && c;", Style); + + // Multiple PerOperator groups: && and || plus | operators. + FormatStyle::BinaryOperationBreakRule BitwiseOrRule; + BitwiseOrRule.Operators = {"|"}; + BitwiseOrRule.Style = FormatStyle::BBO_OnePerLine; + BitwiseOrRule.MinChainLength = 0; + + Style.BreakBinaryOperations.PerOperator = {LogicalRule, BitwiseOrRule}; + + // | operators should break one-per-line. + verifyFormat("int x = loooooooooooooooooongval1 |\n" + " loooooooooooooooooongval2 |\n" + " loooooooooooooooooongval3;", + Style); + + // && still works in multi-group configuration. + verifyFormat("bool x = loooooooooooooongcondition1 &&\n" + " loooooooooooooongcondition2 &&\n" + " loooooooooooooongcondition3;", + Style); + + // + operators stay with default (Never) even with multi-group. + verifyFormat("int x = loooooooooooooongop1 + looooooooongop2 +\n" + " loooooooooooooooooooooongop3;", + Style); +} + +TEST_F(FormatTest, BreakBinaryOperationsMinChainLength) { + auto Style = getLLVMStyleWithColumns(60); + + // MinChainLength = 3: chains shorter than 3 don't force breaks. + FormatStyle::BinaryOperationBreakRule LogicalRule; + LogicalRule.Operators = {"&&", "||"}; + LogicalRule.Style = FormatStyle::BBO_OnePerLine; + LogicalRule.MinChainLength = 3; + + Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never; + Style.BreakBinaryOperations.PerOperator = {LogicalRule}; + + // Chain of 2 — should NOT force one-per-line (below MinChainLength). + verifyFormat("bool x = loooooooooooooongcondition1 &&\n" + " loooooooooooooongcondition2;", + Style); + + // Chain of 3 — should force one-per-line. + verifyFormat("bool x = loooooooooooooongcondition1 &&\n" + " loooooooooooooongcondition2 &&\n" + " loooooooooooooongcondition3;", + Style); + + // Chain of 4 — should force one-per-line. + verifyFormat("bool x = looooooooooongcondition1 &&\n" + " looooooooooongcondition2 &&\n" + " looooooooooongcondition3 &&\n" + " looooooooooongcondition4;", + Style); +} + TEST_F(FormatTest, RemoveEmptyLinesInUnwrappedLines) { auto Style = getLLVMStyle(); Style.RemoveEmptyLinesInUnwrappedLines = true; >From 90435206073edfff5da4eb185845c2a999389725 Mon Sep 17 00:00:00 2001 From: Sergey Subbotin <[email protected]> Date: Sun, 15 Feb 2026 22:11:47 +0100 Subject: [PATCH 2/6] [clang-format] Address review: use tok::TokenKind for operator matching - Store tok::TokenKind instead of strings in BinaryOperationBreakRule to handle alternative operator spellings (e.g. 'and' vs '&&') - Use llvm::find instead of manual loop in findRuleForOperator - Rename EffStyle to OperatorBreakStyle and make it const - Simplify MinChainLength check - Use more realistic variable names in tests --- clang/include/clang/Format/Format.h | 24 +++++---- clang/lib/Format/ContinuationIndenter.cpp | 16 +++--- clang/lib/Format/Format.cpp | 25 +++++++++ clang/lib/Format/TokenAnnotator.cpp | 14 ++--- clang/unittests/Format/ConfigParseTest.cpp | 2 +- clang/unittests/Format/FormatTest.cpp | 62 +++++++++++----------- 6 files changed, 82 insertions(+), 61 deletions(-) diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 1dfec49c5fd44..f3dc46111ef7f 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -15,6 +15,7 @@ #define LLVM_CLANG_FORMAT_FORMAT_H #include "clang/Basic/LangOptions.h" +#include "clang/Basic/TokenKinds.h" #include "clang/Tooling/Core/Replacement.h" #include "clang/Tooling/Inclusions/IncludeStyle.h" #include "llvm/ADT/ArrayRef.h" @@ -2453,9 +2454,10 @@ struct FormatStyle { /// A rule that specifies how to break a specific set of binary operators. /// \version 23 struct BinaryOperationBreakRule { - /// The list of operator tokens this rule applies to, - /// e.g. ``["&&", "||"]``. - std::vector<std::string> Operators; + /// The list of operator token kinds this rule applies to. + /// Stored as ``tok::TokenKind`` so that alternative spellings + /// (e.g. ``and`` vs ``&&``) are handled automatically. + std::vector<tok::TokenKind> Operators; /// The break style for these operators (defaults to ``OnePerLine``). BreakBinaryOperationsStyle Style; /// Minimum number of chained operators before the rule triggers. @@ -2490,21 +2492,21 @@ struct FormatStyle { BreakBinaryOperationsStyle Default; /// Per-operator override rules. std::vector<BinaryOperationBreakRule> PerOperator; - const BinaryOperationBreakRule *findRuleForOperator(StringRef Op) const { + const BinaryOperationBreakRule * + findRuleForOperator(tok::TokenKind Kind) const { for (const auto &Rule : PerOperator) { - for (const auto &O : Rule.Operators) - if (O == Op) - return &Rule; + if (llvm::find(Rule.Operators, Kind) != Rule.Operators.end()) + return &Rule; } return nullptr; } - BreakBinaryOperationsStyle getStyleForOperator(StringRef Op) const { - if (const auto *Rule = findRuleForOperator(Op)) + BreakBinaryOperationsStyle getStyleForOperator(tok::TokenKind Kind) const { + if (const auto *Rule = findRuleForOperator(Kind)) return Rule->Style; return Default; } - unsigned getMinChainLengthForOperator(StringRef Op) const { - if (const auto *Rule = findRuleForOperator(Op)) + unsigned getMinChainLengthForOperator(tok::TokenKind Kind) const { + if (const auto *Rule = findRuleForOperator(Kind)) return Rule->MinChainLength; return 0; } diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp index bd44c29c41043..78d532b054c78 100644 --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -176,18 +176,16 @@ static bool mustBreakBinaryOperation(const FormatToken &Current, } // Look up per-operator rule or fall back to Default. - auto EffStyle = - Style.BreakBinaryOperations.getStyleForOperator(OpToken->TokenText); - if (EffStyle == FormatStyle::BBO_Never) + const auto OperatorBreakStyle = + Style.BreakBinaryOperations.getStyleForOperator(OpToken->Tok.getKind()); + if (OperatorBreakStyle == FormatStyle::BBO_Never) return false; // Check MinChainLength: if the chain is too short, don't force a break. - unsigned MinChain = Style.BreakBinaryOperations.getMinChainLengthForOperator( - OpToken->TokenText); - if (MinChain > 0 && getChainLength(*OpToken) < MinChain) - return false; - - return true; + const unsigned MinChain = + Style.BreakBinaryOperations.getMinChainLengthForOperator( + OpToken->Tok.getKind()); + return MinChain == 0 || getChainLength(*OpToken) >= MinChain; } static bool opensProtoMessageField(const FormatToken &LessTok, diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 8f394dd37360d..f6b0e75a68d91 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -32,6 +32,7 @@ using clang::format::FormatStyle; LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::RawStringFormat) LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::BinaryOperationBreakRule) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::tok::TokenKind) enum BracketAlignmentStyle : int8_t { BAS_Align, @@ -276,6 +277,30 @@ struct ScalarEnumerationTraits<FormatStyle::BreakBinaryOperationsStyle> { } }; +template <> struct ScalarTraits<clang::tok::TokenKind> { + static void output(const clang::tok::TokenKind &Value, void *, + llvm::raw_ostream &Out) { + if (const char *Spelling = clang::tok::getPunctuatorSpelling(Value)) + Out << Spelling; + else + Out << clang::tok::getTokenName(Value); + } + + static StringRef input(StringRef Scalar, void *, + clang::tok::TokenKind &Value) { + // Map operator spelling strings to tok::TokenKind. +#define PUNCTUATOR(Name, Spelling) \ + if (Scalar == Spelling) { \ + Value = clang::tok::Name; \ + return {}; \ + } +#include "clang/Basic/TokenKinds.def" + return "unknown operator"; + } + + static QuotingType mustQuote(StringRef) { return QuotingType::None; } +}; + template <> struct MappingTraits<FormatStyle::BinaryOperationBreakRule> { static void mapping(IO &IO, FormatStyle::BinaryOperationBreakRule &Value) { IO.mapOptional("Operators", Value.Operators); diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index 86d11a7e64eb9..6c73e006d3a7b 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -3282,15 +3282,11 @@ class ExpressionParser { // If so, override precedence to avoid adding fake parenthesis which // could group operations of a different precedence level on the same // line. - auto EffStyle = Style.BreakBinaryOperations.Default; - if (Current) { - if (const auto *Rule = - Style.BreakBinaryOperations.findRuleForOperator( - Current->TokenText)) { - EffStyle = Rule->Style; - } - } - if (EffStyle == FormatStyle::BBO_OnePerLine) + const auto OperatorBreakStyle = + Current ? Style.BreakBinaryOperations.getStyleForOperator( + Current->Tok.getKind()) + : Style.BreakBinaryOperations.Default; + if (OperatorBreakStyle == FormatStyle::BBO_OnePerLine) CurrentPrecedence = prec::Additive; } diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 95a672157eaed..a18fd6c81a64c 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -483,7 +483,7 @@ TEST(ConfigParseTest, ParsesConfiguration) { .value()); EXPECT_EQ(Style.BreakBinaryOperations.Default, FormatStyle::BBO_Never); ASSERT_EQ(Style.BreakBinaryOperations.PerOperator.size(), 1u); - std::vector<std::string> ExpectedOps = {"&&", "||"}; + std::vector<tok::TokenKind> ExpectedOps = {tok::ampamp, tok::pipepipe}; EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].Operators, ExpectedOps); EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].Style, FormatStyle::BBO_OnePerLine); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index da8558ad25452..ef0c6ea4a0395 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -28680,7 +28680,7 @@ TEST_F(FormatTest, BreakBinaryOperationsPerOperator) { // Per-operator override: && and || are OnePerLine, rest is Never (default). FormatStyle::BinaryOperationBreakRule LogicalRule; - LogicalRule.Operators = {"&&", "||"}; + LogicalRule.Operators = {tok::ampamp, tok::pipepipe}; LogicalRule.Style = FormatStyle::BBO_OnePerLine; LogicalRule.MinChainLength = 0; @@ -28688,42 +28688,42 @@ TEST_F(FormatTest, BreakBinaryOperationsPerOperator) { Style.BreakBinaryOperations.PerOperator = {LogicalRule}; // Logical operators break one-per-line when line is too long. - verifyFormat("bool x = loooooooooooooongcondition1 &&\n" - " loooooooooooooongcondition2 &&\n" - " loooooooooooooongcondition3;", + verifyFormat("bool valid = isConnectionReady() &&\n" + " isSessionNotExpired() &&\n" + " hasRequiredPermission();", Style); - // Arithmetic operators stay with default (Never) — no forced break. - verifyFormat("int x = loooooooooooooongop1 + looooooooongop2 +\n" - " loooooooooooooooooooooongop3;", + // Arithmetic operators stay with default (Never). + verifyFormat("int total = unitBasePrice + shippingCostPerItem +\n" + " applicableTaxAmount + handlingFeePerUnit;", Style); - // Short logical chain that fits on one line stays on one line. + // Short logical chain that fits stays on one line. verifyFormat("bool x = a && b && c;", Style); // Multiple PerOperator groups: && and || plus | operators. FormatStyle::BinaryOperationBreakRule BitwiseOrRule; - BitwiseOrRule.Operators = {"|"}; + BitwiseOrRule.Operators = {tok::pipe}; BitwiseOrRule.Style = FormatStyle::BBO_OnePerLine; BitwiseOrRule.MinChainLength = 0; Style.BreakBinaryOperations.PerOperator = {LogicalRule, BitwiseOrRule}; // | operators should break one-per-line. - verifyFormat("int x = loooooooooooooooooongval1 |\n" - " loooooooooooooooooongval2 |\n" - " loooooooooooooooooongval3;", + verifyFormat("int flags = OPTION_VERBOSE_OUTPUT |\n" + " OPTION_RECURSIVE_SCAN |\n" + " OPTION_FORCE_OVERWRITE;", Style); // && still works in multi-group configuration. - verifyFormat("bool x = loooooooooooooongcondition1 &&\n" - " loooooooooooooongcondition2 &&\n" - " loooooooooooooongcondition3;", + verifyFormat("bool valid = isConnectionReady() &&\n" + " isSessionNotExpired() &&\n" + " hasRequiredPermission();", Style); - // + operators stay with default (Never) even with multi-group. - verifyFormat("int x = loooooooooooooongop1 + looooooooongop2 +\n" - " loooooooooooooooooooooongop3;", + // + stays with default (Never) even with multi-group. + verifyFormat("int total = unitBasePrice + shippingCostPerItem +\n" + " applicableTaxAmount + handlingFeePerUnit;", Style); } @@ -28732,29 +28732,29 @@ TEST_F(FormatTest, BreakBinaryOperationsMinChainLength) { // MinChainLength = 3: chains shorter than 3 don't force breaks. FormatStyle::BinaryOperationBreakRule LogicalRule; - LogicalRule.Operators = {"&&", "||"}; + LogicalRule.Operators = {tok::ampamp, tok::pipepipe}; LogicalRule.Style = FormatStyle::BBO_OnePerLine; LogicalRule.MinChainLength = 3; Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never; Style.BreakBinaryOperations.PerOperator = {LogicalRule}; - // Chain of 2 — should NOT force one-per-line (below MinChainLength). - verifyFormat("bool x = loooooooooooooongcondition1 &&\n" - " loooooooooooooongcondition2;", + // Chain of 2 — below MinChainLength, no forced one-per-line. + verifyFormat("bool ok =\n" + " isConnectionReady(cfg) && isSessionNotExpired(cfg);", Style); - // Chain of 3 — should force one-per-line. - verifyFormat("bool x = loooooooooooooongcondition1 &&\n" - " loooooooooooooongcondition2 &&\n" - " loooooooooooooongcondition3;", + // Chain of 3 — meets MinChainLength, one-per-line. + verifyFormat("bool ok = isConnectionReady(cfg) &&\n" + " isSessionNotExpired(cfg) &&\n" + " hasRequiredPermission(cfg);", Style); - // Chain of 4 — should force one-per-line. - verifyFormat("bool x = looooooooooongcondition1 &&\n" - " looooooooooongcondition2 &&\n" - " looooooooooongcondition3 &&\n" - " looooooooooongcondition4;", + // Chain of 4 — above MinChainLength, one-per-line. + verifyFormat("bool ok = isConnectionReady(cfg) &&\n" + " isSessionNotExpired(cfg) &&\n" + " hasRequiredPermission(cfg) &&\n" + " isFeatureEnabled(cfg);", Style); } >From 0651ddc26c627a690e16a3e8ef7161160e8f90e6 Mon Sep 17 00:00:00 2001 From: Sergey Subbotin <[email protected]> Date: Sun, 15 Feb 2026 22:34:41 +0100 Subject: [PATCH 3/6] [clang-format] Fix precedence override for per-operator OnePerLine The fake parenthesis precedence override (flattening to prec::Additive) was designed for global OnePerLine where all operators get the same treatment. With per-operator rules, this override reversed the relative precedence of operators like | (overridden to 13) and << (stays at 12), causing sub-expressions like `byte_buffer[1] << 8` to break apart instead of staying grouped within a | chain. Fix: only apply the precedence override when BreakBinaryOperations is globally OnePerLine with no PerOperator rules. Per-operator rules rely on mustBreakBinaryOperation() for forced breaks while preserving natural precedence grouping. Add test for the | + << mixed-precedence case from the RFC. --- clang/lib/Format/TokenAnnotator.cpp | 19 ++++++++++--------- clang/unittests/Format/FormatTest.cpp | 8 ++++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index 6c73e006d3a7b..34e81bbc97578 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -3278,16 +3278,17 @@ class ExpressionParser { int CurrentPrecedence = getCurrentPrecedence(); if (CurrentPrecedence > prec::Conditional && CurrentPrecedence < prec::PointerToMember) { - // Check whether this operator's effective style is OnePerLine. - // If so, override precedence to avoid adding fake parenthesis which - // could group operations of a different precedence level on the same - // line. - const auto OperatorBreakStyle = - Current ? Style.BreakBinaryOperations.getStyleForOperator( - Current->Tok.getKind()) - : Style.BreakBinaryOperations.Default; - if (OperatorBreakStyle == FormatStyle::BBO_OnePerLine) + // When BreakBinaryOperations is globally OnePerLine (no per-operator + // rules), flatten all precedence levels so that every operator is + // treated equally for line-breaking purposes. With per-operator rules + // we must preserve natural precedence so that higher-precedence + // sub-expressions (e.g. `x << 8` inside a `|` chain) stay grouped; + // mustBreakBinaryOperation() handles the forced breaks instead. + if (Style.BreakBinaryOperations.PerOperator.empty() && + Style.BreakBinaryOperations.Default == + FormatStyle::BBO_OnePerLine) { CurrentPrecedence = prec::Additive; + } } if (Precedence == CurrentPrecedence && Current && diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index ef0c6ea4a0395..24397c743c994 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -28725,6 +28725,14 @@ TEST_F(FormatTest, BreakBinaryOperationsPerOperator) { verifyFormat("int total = unitBasePrice + shippingCostPerItem +\n" " applicableTaxAmount + handlingFeePerUnit;", Style); + + // | OnePerLine with << sub-expressions: << stays grouped. + Style.BreakBinaryOperations.PerOperator = {BitwiseOrRule}; + verifyFormat("std::uint32_t a = byte_buffer[0] |\n" + " byte_buffer[1] << 8 |\n" + " byte_buffer[2] << 16 |\n" + " byte_buffer[3] << 24;", + Style); } TEST_F(FormatTest, BreakBinaryOperationsMinChainLength) { >From 33c42b9e1640a935fb2e87653fc820f878f3fbda Mon Sep 17 00:00:00 2001 From: Sergey Subbotin <[email protected]> Date: Sun, 15 Feb 2026 22:58:28 +0100 Subject: [PATCH 4/6] [clang-format] Fix >> per-operator OnePerLine support clang-format splits ">>" into two ">" tokens for template parsing (FormatTokenLexer.cpp line 1419). Unlike "<<" which gets re-merged in most contexts, ">>" only re-merges for "operator>>". This meant per-operator rules for ">>" (tok::greatergreater) never matched the actual ">" (tok::greater) tokens in stream extraction expressions. Fix by also checking tok::greatergreater rules when the token kind is tok::greater in findRuleForOperator(). --- clang/include/clang/Format/Format.h | 8 ++++++++ clang/unittests/Format/FormatTest.cpp | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index f3dc46111ef7f..66712863d9a98 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -2497,6 +2497,14 @@ struct FormatStyle { for (const auto &Rule : PerOperator) { if (llvm::find(Rule.Operators, Kind) != Rule.Operators.end()) return &Rule; + // clang-format splits ">>" into two ">" tokens for template parsing. + // Match ">" against ">>" rules so that per-operator rules for ">>" + // (stream extraction / right shift) work correctly. + if (Kind == tok::greater && + llvm::find(Rule.Operators, tok::greatergreater) != + Rule.Operators.end()) { + return &Rule; + } } return nullptr; } diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 24397c743c994..6ef1a01d0b65a 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -28733,6 +28733,21 @@ TEST_F(FormatTest, BreakBinaryOperationsPerOperator) { " byte_buffer[2] << 16 |\n" " byte_buffer[3] << 24;", Style); + + // >> (stream extraction) OnePerLine: clang-format splits >> into two > + // tokens, but per-operator rules for >> must still work. + FormatStyle::BinaryOperationBreakRule ShiftRightRule; + ShiftRightRule.Operators = {tok::greatergreater}; + ShiftRightRule.Style = FormatStyle::BBO_OnePerLine; + ShiftRightRule.MinChainLength = 0; + + Style.BreakBinaryOperations.PerOperator = {ShiftRightRule}; + verifyFormat("in >>\n" + " packet_id >>\n" + " packet_version >>\n" + " packet_number >>\n" + " packet_scale;", + Style); } TEST_F(FormatTest, BreakBinaryOperationsMinChainLength) { >From 98596cba9387b8ae0bbbe20757dd437172c4c67e Mon Sep 17 00:00:00 2001 From: Sergey Subbotin <[email protected]> Date: Sun, 15 Feb 2026 23:20:28 +0100 Subject: [PATCH 5/6] [clang-format] Count operands (not operators) for MinChainLength MinChainLength is more intuitive when it counts operands: `a && b && c` is a chain of length 3 (3 operands, 2 operators). Previously getChainLength returned the number of operators, so MinChainLength: 3 required 4 operands to trigger. --- clang/include/clang/Format/Format.h | 3 ++- clang/lib/Format/ContinuationIndenter.cpp | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 66712863d9a98..5a8fe727b081c 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -2460,7 +2460,8 @@ struct FormatStyle { std::vector<tok::TokenKind> Operators; /// The break style for these operators (defaults to ``OnePerLine``). BreakBinaryOperationsStyle Style; - /// Minimum number of chained operators before the rule triggers. + /// Minimum number of operands in a chain before the rule triggers. + /// For example, ``a && b && c`` is a chain of length 3. /// ``0`` means always break (when the line is too long). unsigned MinChainLength; bool operator==(const BinaryOperationBreakRule &R) const { diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp index 78d532b054c78..97e1928ab2bf3 100644 --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -144,12 +144,13 @@ static bool startsNextOperand(const FormatToken &Current) { return isAlignableBinaryOperator(Previous) && !Current.isTrailingComment(); } -// Returns the number of operators in the chain containing \c Op. +// Returns the number of operands in the chain containing \c Op. +// For example, `a && b && c` has 3 operands (and 2 operators). static unsigned getChainLength(const FormatToken &Op) { const FormatToken *Last = &Op; while (Last->NextOperator) Last = Last->NextOperator; - return Last->OperatorIndex + 1; + return Last->OperatorIndex + 2; } // Returns \c true if \c Current is a binary operation that must break. >From 6da82873a2c3042e076901f05d9968aa3c686ef6 Mon Sep 17 00:00:00 2001 From: Sergey Subbotin <[email protected]> Date: Tue, 17 Feb 2026 10:10:49 +0100 Subject: [PATCH 6/6] [clang-format] Address second round of review feedback - Merge if/else into else-if in mustBreakBinaryOperation - Use member assignment instead of brace-init for BreakBinaryOperations - Add > and >> edge-case parse tests in ConfigParseTest - Fix dump_format_style.py to expand vector<nested_struct> fields in documentation instead of showing raw C++ type --- clang/docs/ClangFormatStyleOptions.rst | 48 +++++++++++++++++++++- clang/docs/tools/dump_format_style.py | 22 +++++++++- clang/lib/Format/ContinuationIndenter.cpp | 5 +-- clang/unittests/Format/ConfigParseTest.cpp | 23 +++++++++-- clang/unittests/Format/FormatTest.cpp | 9 ++-- 5 files changed, 94 insertions(+), 13 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 343e9649e43ce..660bec61f55cc 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3672,7 +3672,53 @@ the configuration (without a prefix: ``Auto``). eeeeeeeeeeeeeeee; - * ``std::vector<BinaryOperationBreakRule> PerOperator`` Per-operator override rules. + * ``List of BinaryOperationBreakRules PerOperator`` Per-operator override rules. + + * ``std::vector<tok::TokenKind> Operators`` :versionbadge:`clang-format 23` + The list of operator token kinds this rule applies to. + Stored as ``tok::TokenKind`` so that alternative spellings + (e.g. ``and`` vs ``&&``) are handled automatically. + + * ``BreakBinaryOperationsStyle Style`` + The break style for these operators (defaults to ``OnePerLine``). + + Possible values: + + * ``BBO_Never`` (in configuration: ``Never``) + Don't break binary operations + + .. code-block:: c++ + + aaa + bbbb * ccccc - ddddd + + eeeeeeeeeeeeeeee; + + * ``BBO_OnePerLine`` (in configuration: ``OnePerLine``) + Binary operations will either be all on the same line, or each operation + will have one line each. + + .. code-block:: c++ + + aaa + + bbbb * + ccccc - + ddddd + + eeeeeeeeeeeeeeee; + + * ``BBO_RespectPrecedence`` (in configuration: ``RespectPrecedence``) + Binary operations of a particular precedence that exceed the column + limit will have one line each. + + .. code-block:: c++ + + aaa + + bbbb * ccccc - + ddddd + + eeeeeeeeeeeeeeee; + + + * ``unsigned MinChainLength`` Minimum number of operands in a chain before the rule triggers. + For example, ``a && b && c`` is a chain of length 3. + ``0`` means always break (when the line is too long). .. _BreakConstructorInitializers: diff --git a/clang/docs/tools/dump_format_style.py b/clang/docs/tools/dump_format_style.py index f035143f6b3d1..b6a803057cf48 100755 --- a/clang/docs/tools/dump_format_style.py +++ b/clang/docs/tools/dump_format_style.py @@ -399,9 +399,27 @@ class State: ) ) else: - nested_struct.values.append( - NestedField(field_type + " " + field_name, comment, version) + vec_match = re.match( + r"std::vector<(.*)>$", field_type ) + if vec_match and vec_match.group(1) in nested_structs: + inner_struct = nested_structs[vec_match.group(1)] + display = ( + "List of %ss %s" + % (vec_match.group(1), field_name) + ) + nested_struct.values.append( + NestedField(display, comment, version) + ) + nested_struct.values.extend(inner_struct.values) + else: + nested_struct.values.append( + NestedField( + field_type + " " + field_name, + comment, + version, + ) + ) version = None elif state == State.InEnum: if line.startswith("///"): diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp index 97e1928ab2bf3..4137a23230d51 100644 --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -171,9 +171,8 @@ static bool mustBreakBinaryOperation(const FormatToken &Current, if (BreakBefore) { if (!isAlignableBinaryOperator(Current)) return false; - } else { - if (!startsNextOperand(Current)) - return false; + } else if (!startsNextOperand(Current)) { + return false; } // Look up per-operator rule or fall back to Default. diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index a18fd6c81a64c..f55698e299bc3 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -453,7 +453,7 @@ TEST(ConfigParseTest, ParsesConfiguration) { CHECK_PARSE("BreakBeforeBinaryOperators: true", BreakBeforeBinaryOperators, FormatStyle::BOS_All); - Style.BreakBinaryOperations = {FormatStyle::BBO_Never, {}}; + Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never; CHECK_PARSE("BreakBinaryOperations: OnePerLine", BreakBinaryOperations, FormatStyle::BreakBinaryOperationsOptions( {FormatStyle::BBO_OnePerLine, {}})); @@ -465,14 +465,14 @@ TEST(ConfigParseTest, ParsesConfiguration) { FormatStyle::BreakBinaryOperationsOptions({FormatStyle::BBO_Never, {}})); // Structured form - Style.BreakBinaryOperations = {FormatStyle::BBO_Never, {}}; + Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never; CHECK_PARSE("BreakBinaryOperations:\n" " Default: OnePerLine", BreakBinaryOperations, FormatStyle::BreakBinaryOperationsOptions( {FormatStyle::BBO_OnePerLine, {}})); - Style.BreakBinaryOperations = {FormatStyle::BBO_Never, {}}; + Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never; EXPECT_EQ(0, parseConfiguration("BreakBinaryOperations:\n" " Default: Never\n" " PerOperator:\n" @@ -489,6 +489,23 @@ TEST(ConfigParseTest, ParsesConfiguration) { FormatStyle::BBO_OnePerLine); EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].MinChainLength, 3u); + // Parse ">" and ">>" which have edge cases: clang-format splits ">>" into + // two ">" tokens, so ">>" maps to tok::greatergreater while ">" maps to + // tok::greater. + Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never; + EXPECT_EQ(0, parseConfiguration("BreakBinaryOperations:\n" + " Default: Never\n" + " PerOperator:\n" + " - Operators: ['>', '>>']\n" + " Style: OnePerLine", + &Style) + .value()); + ASSERT_EQ(Style.BreakBinaryOperations.PerOperator.size(), 1u); + std::vector<tok::TokenKind> ExpectedShiftOps = {tok::greater, + tok::greatergreater}; + EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].Operators, + ExpectedShiftOps); + Style.BreakConstructorInitializers = FormatStyle::BCIS_BeforeColon; CHECK_PARSE("BreakConstructorInitializers: BeforeComma", BreakConstructorInitializers, FormatStyle::BCIS_BeforeComma); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 6ef1a01d0b65a..66d3e5d6819ca 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -28444,7 +28444,7 @@ TEST_F(FormatTest, BreakBinaryOperations) { " longOperand_3_;", Style); - Style.BreakBinaryOperations = {FormatStyle::BBO_OnePerLine, {}}; + Style.BreakBinaryOperations.Default = FormatStyle::BBO_OnePerLine; // Logical operations verifyFormat("if (condition1 && condition2) {\n" @@ -28529,7 +28529,7 @@ TEST_F(FormatTest, BreakBinaryOperations) { " longOperand_3_;", Style); - Style.BreakBinaryOperations = {FormatStyle::BBO_RespectPrecedence, {}}; + Style.BreakBinaryOperations.Default = FormatStyle::BBO_RespectPrecedence; verifyFormat("result = op1 + op2 * op3 - op4;", Style); verifyFormat("result = operand1 +\n" @@ -28561,7 +28561,7 @@ TEST_F(FormatTest, BreakBinaryOperations) { " longOperand_3_;", Style); - Style.BreakBinaryOperations = {FormatStyle::BBO_OnePerLine, {}}; + Style.BreakBinaryOperations.Default = FormatStyle::BBO_OnePerLine; Style.BreakBeforeBinaryOperators = FormatStyle::BOS_NonAssignment; // Logical operations @@ -28642,7 +28642,7 @@ TEST_F(FormatTest, BreakBinaryOperations) { " >> longOperand_3_;", Style); - Style.BreakBinaryOperations = {FormatStyle::BBO_RespectPrecedence, {}}; + Style.BreakBinaryOperations.Default = FormatStyle::BBO_RespectPrecedence; verifyFormat("result = op1 + op2 * op3 - op4;", Style); verifyFormat("result = operand1\n" @@ -28748,6 +28748,7 @@ TEST_F(FormatTest, BreakBinaryOperationsPerOperator) { " packet_number >>\n" " packet_scale;", Style); + } TEST_F(FormatTest, BreakBinaryOperationsMinChainLength) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
