Skip to content

Commit c5a4291

Browse files
authored
[clang-format] Add BreakBinaryOperations configuration (#95013)
By default, clang-format packs binary operations, but it may be desirable to have compound operations be on individual lines instead of being packed. This PR adds the option `BreakBinaryOperations` to break up large compound binary operations to be on one line each. This applies to all logical and arithmetic/bitwise binary operations Maybe partially addresses #79487 ? Closes #58014 Closes #57280
1 parent 2ba1cc8 commit c5a4291

File tree

8 files changed

+379
-1
lines changed

8 files changed

+379
-1
lines changed

clang/docs/ClangFormatStyleOptions.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3300,6 +3300,46 @@ the configuration (without a prefix: ``Auto``).
33003300
firstValue :
33013301
SecondValueVeryVeryVeryVeryLong;
33023302

3303+
.. _BreakBinaryOperations:
3304+
3305+
**BreakBinaryOperations** (``BreakBinaryOperationsStyle``) :versionbadge:`clang-format 20` :ref:`<BreakBinaryOperations>`
3306+
The break constructor initializers style to use.
3307+
3308+
Possible values:
3309+
3310+
* ``BBO_Never`` (in configuration: ``Never``)
3311+
Don't break binary operations
3312+
3313+
.. code-block:: c++
3314+
3315+
aaa + bbbb * ccccc - ddddd +
3316+
eeeeeeeeeeeeeeee;
3317+
3318+
* ``BBO_OnePerLine`` (in configuration: ``OnePerLine``)
3319+
Binary operations will either be all on the same line, or each operation
3320+
will have one line each.
3321+
3322+
.. code-block:: c++
3323+
3324+
aaa +
3325+
bbbb *
3326+
ccccc -
3327+
ddddd +
3328+
eeeeeeeeeeeeeeee;
3329+
3330+
* ``BBO_RespectPrecedence`` (in configuration: ``RespectPrecedence``)
3331+
Binary operations of a particular precedence that exceed the column
3332+
limit will have one line each.
3333+
3334+
.. code-block:: c++
3335+
3336+
aaa +
3337+
bbbb * ccccc -
3338+
ddddd +
3339+
eeeeeeeeeeeeeeee;
3340+
3341+
3342+
33033343
.. _BreakConstructorInitializers:
33043344

33053345
**BreakConstructorInitializers** (``BreakConstructorInitializersStyle``) :versionbadge:`clang-format 5` :ref:`<BreakConstructorInitializers>`

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ AST Matchers
316316
clang-format
317317
------------
318318

319+
- Adds ``BreakBinaryOperations`` option.
320+
319321
libclang
320322
--------
321323

clang/include/clang/Format/Format.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2231,6 +2231,41 @@ struct FormatStyle {
22312231
/// \version 3.7
22322232
bool BreakBeforeTernaryOperators;
22332233

2234+
/// Different ways to break binary operations.
2235+
enum BreakBinaryOperationsStyle : int8_t {
2236+
/// Don't break binary operations
2237+
/// \code
2238+
/// aaa + bbbb * ccccc - ddddd +
2239+
/// eeeeeeeeeeeeeeee;
2240+
/// \endcode
2241+
BBO_Never,
2242+
2243+
/// Binary operations will either be all on the same line, or each operation
2244+
/// will have one line each.
2245+
/// \code
2246+
/// aaa +
2247+
/// bbbb *
2248+
/// ccccc -
2249+
/// ddddd +
2250+
/// eeeeeeeeeeeeeeee;
2251+
/// \endcode
2252+
BBO_OnePerLine,
2253+
2254+
/// Binary operations of a particular precedence that exceed the column
2255+
/// limit will have one line each.
2256+
/// \code
2257+
/// aaa +
2258+
/// bbbb * ccccc -
2259+
/// ddddd +
2260+
/// eeeeeeeeeeeeeeee;
2261+
/// \endcode
2262+
BBO_RespectPrecedence
2263+
};
2264+
2265+
/// The break constructor initializers style to use.
2266+
/// \version 20
2267+
BreakBinaryOperationsStyle BreakBinaryOperations;
2268+
22342269
/// Different ways to break initializers.
22352270
enum BreakConstructorInitializersStyle : int8_t {
22362271
/// Break constructor initializers before the colon and after the commas.
@@ -5037,6 +5072,7 @@ struct FormatStyle {
50375072
BreakBeforeConceptDeclarations == R.BreakBeforeConceptDeclarations &&
50385073
BreakBeforeInlineASMColon == R.BreakBeforeInlineASMColon &&
50395074
BreakBeforeTernaryOperators == R.BreakBeforeTernaryOperators &&
5075+
BreakBinaryOperations == R.BreakBinaryOperations &&
50405076
BreakConstructorInitializers == R.BreakConstructorInitializers &&
50415077
BreakFunctionDefinitionParameters ==
50425078
R.BreakFunctionDefinitionParameters &&

clang/lib/Format/ContinuationIndenter.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ static bool startsSegmentOfBuilderTypeCall(const FormatToken &Tok) {
131131
// Returns \c true if \c Current starts a new parameter.
132132
static bool startsNextParameter(const FormatToken &Current,
133133
const FormatStyle &Style) {
134-
const FormatToken &Previous = *Current.Previous;
134+
assert(Current.Previous);
135+
const auto &Previous = *Current.Previous;
135136
if (Current.is(TT_CtorInitializerComma) &&
136137
Style.BreakConstructorInitializers == FormatStyle::BCIS_BeforeComma) {
137138
return true;
@@ -146,6 +147,31 @@ static bool startsNextParameter(const FormatToken &Current,
146147
Style.BreakInheritanceList != FormatStyle::BILS_BeforeComma));
147148
}
148149

150+
// Returns \c true if \c Token in an alignable binary operator
151+
static bool isAlignableBinaryOperator(const FormatToken &Token) {
152+
// No need to align binary operators that only have two operands.
153+
bool HasTwoOperands = Token.OperatorIndex == 0 && !Token.NextOperator;
154+
return Token.is(TT_BinaryOperator) && !HasTwoOperands &&
155+
Token.getPrecedence() > prec::Conditional &&
156+
Token.getPrecedence() < prec::PointerToMember;
157+
}
158+
159+
// Returns \c true if \c Current starts the next operand in a binary operation.
160+
static bool startsNextOperand(const FormatToken &Current) {
161+
assert(Current.Previous);
162+
const auto &Previous = *Current.Previous;
163+
return isAlignableBinaryOperator(Previous) && !Current.isTrailingComment();
164+
}
165+
166+
// Returns \c true if \c Current is a binary operation that must break.
167+
static bool mustBreakBinaryOperation(const FormatToken &Current,
168+
const FormatStyle &Style) {
169+
return Style.BreakBinaryOperations != FormatStyle::BBO_Never &&
170+
(Style.BreakBeforeBinaryOperators == FormatStyle::BOS_None
171+
? startsNextOperand
172+
: isAlignableBinaryOperator)(Current);
173+
}
174+
149175
static bool opensProtoMessageField(const FormatToken &LessTok,
150176
const FormatStyle &Style) {
151177
if (LessTok.isNot(tok::less))
@@ -869,6 +895,9 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun,
869895
}
870896
if (CurrentState.AvoidBinPacking && startsNextParameter(Current, Style))
871897
CurrentState.NoLineBreak = true;
898+
if (mustBreakBinaryOperation(Current, Style))
899+
CurrentState.NoLineBreak = true;
900+
872901
if (startsSegmentOfBuilderTypeCall(Current) &&
873902
State.Column > getNewLineColumn(State)) {
874903
CurrentState.ContainsUnwrappedBuilder = true;
@@ -1235,6 +1264,9 @@ unsigned ContinuationIndenter::addTokenOnNewLine(LineState &State,
12351264
}
12361265
}
12371266

1267+
if (mustBreakBinaryOperation(Current, Style))
1268+
CurrentState.BreakBeforeParameter = true;
1269+
12381270
return Penalty;
12391271
}
12401272

clang/lib/Format/Format.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,16 @@ struct ScalarEnumerationTraits<FormatStyle::BreakBeforeInlineASMColonStyle> {
244244
}
245245
};
246246

247+
template <>
248+
struct ScalarEnumerationTraits<FormatStyle::BreakBinaryOperationsStyle> {
249+
static void enumeration(IO &IO,
250+
FormatStyle::BreakBinaryOperationsStyle &Value) {
251+
IO.enumCase(Value, "Never", FormatStyle::BBO_Never);
252+
IO.enumCase(Value, "OnePerLine", FormatStyle::BBO_OnePerLine);
253+
IO.enumCase(Value, "RespectPrecedence", FormatStyle::BBO_RespectPrecedence);
254+
}
255+
};
256+
247257
template <>
248258
struct ScalarEnumerationTraits<FormatStyle::BreakConstructorInitializersStyle> {
249259
static void
@@ -968,6 +978,7 @@ template <> struct MappingTraits<FormatStyle> {
968978
Style.BreakBeforeInlineASMColon);
969979
IO.mapOptional("BreakBeforeTernaryOperators",
970980
Style.BreakBeforeTernaryOperators);
981+
IO.mapOptional("BreakBinaryOperations", Style.BreakBinaryOperations);
971982
IO.mapOptional("BreakConstructorInitializers",
972983
Style.BreakConstructorInitializers);
973984
IO.mapOptional("BreakFunctionDefinitionParameters",
@@ -1480,6 +1491,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
14801491
LLVMStyle.BreakBeforeConceptDeclarations = FormatStyle::BBCDS_Always;
14811492
LLVMStyle.BreakBeforeInlineASMColon = FormatStyle::BBIAS_OnlyMultiline;
14821493
LLVMStyle.BreakBeforeTernaryOperators = true;
1494+
LLVMStyle.BreakBinaryOperations = FormatStyle::BBO_Never;
14831495
LLVMStyle.BreakConstructorInitializers = FormatStyle::BCIS_BeforeColon;
14841496
LLVMStyle.BreakFunctionDefinitionParameters = false;
14851497
LLVMStyle.BreakInheritanceList = FormatStyle::BILS_BeforeColon;

clang/lib/Format/TokenAnnotator.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3175,6 +3175,15 @@ class ExpressionParser {
31753175
parse(Precedence + 1);
31763176

31773177
int CurrentPrecedence = getCurrentPrecedence();
3178+
if (Style.BreakBinaryOperations == FormatStyle::BBO_OnePerLine &&
3179+
CurrentPrecedence > prec::Conditional &&
3180+
CurrentPrecedence < prec::PointerToMember) {
3181+
// When BreakBinaryOperations is set to BreakAll,
3182+
// all operations will be on the same line or on individual lines.
3183+
// Override precedence to avoid adding fake parenthesis which could
3184+
// group operations of a different precedence level on the same line
3185+
CurrentPrecedence = prec::Additive;
3186+
}
31783187

31793188
if (Precedence == CurrentPrecedence && Current &&
31803189
Current->is(TT_SelectorName)) {

clang/unittests/Format/ConfigParseTest.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,14 @@ TEST(ConfigParseTest, ParsesConfiguration) {
404404
CHECK_PARSE("BreakBeforeBinaryOperators: true", BreakBeforeBinaryOperators,
405405
FormatStyle::BOS_All);
406406

407+
Style.BreakBinaryOperations = FormatStyle::BBO_Never;
408+
CHECK_PARSE("BreakBinaryOperations: OnePerLine", BreakBinaryOperations,
409+
FormatStyle::BBO_OnePerLine);
410+
CHECK_PARSE("BreakBinaryOperations: RespectPrecedence", BreakBinaryOperations,
411+
FormatStyle::BBO_RespectPrecedence);
412+
CHECK_PARSE("BreakBinaryOperations: Never", BreakBinaryOperations,
413+
FormatStyle::BBO_Never);
414+
407415
Style.BreakConstructorInitializers = FormatStyle::BCIS_BeforeColon;
408416
CHECK_PARSE("BreakConstructorInitializers: BeforeComma",
409417
BreakConstructorInitializers, FormatStyle::BCIS_BeforeComma);

0 commit comments

Comments
 (0)