Skip to content

Commit 5daa25f

Browse files
francoisferrandFrancois Ferrand
authored andcommitted
clang-format: support aligned nested conditionals formatting
When multiple ternary operators are chained, e.g. like an if/else-if/ else-if/.../else sequence, clang-format will keep aligning the colon with the question mark, which increases the indent for each conditionals: int a = condition1 ? result1 : condition2 ? result2 : condition3 ? result3 : result4; This patch detects the situation (e.g. conditionals used in false branch of another conditional), to avoid indenting in that case: int a = condition1 ? result1 : condition2 ? result2 : condition3 ? result3 : result4; When BreakBeforeTernaryOperators is false, this will format like this: int a = condition1 ? result1 : condition2 ? result2 : conditino3 ? result3 : result4;
1 parent d7ab9e7 commit 5daa25f

File tree

5 files changed

+365
-24
lines changed

5 files changed

+365
-24
lines changed

clang/lib/Format/ContinuationIndenter.cpp

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,12 @@ bool ContinuationIndenter::mustBreak(const LineState &State) {
367367
State.Stack.back().BreakBeforeParameter && !Current.isTrailingComment() &&
368368
!Current.isOneOf(tok::r_paren, tok::r_brace))
369369
return true;
370+
if (State.Stack.back().IsChainedConditional &&
371+
((Style.BreakBeforeTernaryOperators && Current.is(TT_ConditionalExpr) &&
372+
Current.is(tok::colon)) ||
373+
(!Style.BreakBeforeTernaryOperators && Previous.is(TT_ConditionalExpr) &&
374+
Previous.is(tok::colon))))
375+
return true;
370376
if (((Previous.is(TT_DictLiteral) && Previous.is(tok::l_brace)) ||
371377
(Previous.is(TT_ArrayInitializerLSquare) &&
372378
Previous.ParameterCount > 1) ||
@@ -1022,8 +1028,21 @@ unsigned ContinuationIndenter::getNewLineColumn(const LineState &State) {
10221028
if (State.Stack.back().QuestionColumn != 0 &&
10231029
((NextNonComment->is(tok::colon) &&
10241030
NextNonComment->is(TT_ConditionalExpr)) ||
1025-
Previous.is(TT_ConditionalExpr)))
1031+
Previous.is(TT_ConditionalExpr))) {
1032+
if (((NextNonComment->is(tok::colon) && NextNonComment->Next &&
1033+
!NextNonComment->Next->FakeLParens.empty() &&
1034+
NextNonComment->Next->FakeLParens.back() == prec::Conditional) ||
1035+
(Previous.is(tok::colon) && !Current.FakeLParens.empty() &&
1036+
Current.FakeLParens.back() == prec::Conditional)) &&
1037+
!State.Stack.back().IsWrappedConditional) {
1038+
//NOTE: we may tweak this slightly:
1039+
// * not remove the 'lead' ContinuationIndentWidth
1040+
// * always un-indent by the operator when BreakBeforeTernaryOperators=true
1041+
unsigned Indent = State.Stack.back().Indent - Style.ContinuationIndentWidth;
1042+
return Indent;
1043+
}
10261044
return State.Stack.back().QuestionColumn;
1045+
}
10271046
if (Previous.is(tok::comma) && State.Stack.back().VariablePos != 0)
10281047
return State.Stack.back().VariablePos;
10291048
if ((PreviousNonComment &&
@@ -1144,6 +1163,10 @@ unsigned ContinuationIndenter::moveStateToNextToken(LineState &State,
11441163
if (Current.is(TT_ArraySubscriptLSquare) &&
11451164
State.Stack.back().StartOfArraySubscripts == 0)
11461165
State.Stack.back().StartOfArraySubscripts = State.Column;
1166+
if (Current.is(TT_ConditionalExpr) && Current.is(tok::question) &&
1167+
((Current.MustBreakBefore) ||
1168+
(Current.getNextNonComment() && Current.getNextNonComment()->MustBreakBefore)))
1169+
State.Stack.back().IsWrappedConditional = true;
11471170
if (Style.BreakBeforeTernaryOperators && Current.is(tok::question))
11481171
State.Stack.back().QuestionColumn = State.Column;
11491172
if (!Style.BreakBeforeTernaryOperators && Current.isNot(tok::colon)) {
@@ -1284,6 +1307,8 @@ void ContinuationIndenter::moveStatePastFakeLParens(LineState &State,
12841307
NewParenState.Tok = nullptr;
12851308
NewParenState.ContainsLineBreak = false;
12861309
NewParenState.LastOperatorWrapped = true;
1310+
NewParenState.IsChainedConditional = false;
1311+
NewParenState.IsWrappedConditional = false;
12871312
NewParenState.NoLineBreak =
12881313
NewParenState.NoLineBreak || State.Stack.back().NoLineBreakInOperand;
12891314

@@ -1316,14 +1341,20 @@ void ContinuationIndenter::moveStatePastFakeLParens(LineState &State,
13161341
Style.AlignAfterOpenBracket != FormatStyle::BAS_DontAlign)
13171342
NewParenState.StartOfFunctionCall = State.Column;
13181343

1319-
// Always indent conditional expressions. Never indent expression where
1320-
// the 'operator' is ',', ';' or an assignment (i.e. *I <=
1321-
// prec::Assignment) as those have different indentation rules. Indent
1322-
// other expression, unless the indentation needs to be skipped.
1323-
if (*I == prec::Conditional ||
1324-
(!SkipFirstExtraIndent && *I > prec::Assignment &&
1325-
!Current.isTrailingComment()))
1344+
// Indent conditional expressions, unless they are chained "else-if"
1345+
// conditionals. Never indent expression where the 'operator' is ',', ';' or
1346+
// an assignment (i.e. *I <= prec::Assignment) as those have different
1347+
// indentation rules. Indent other expression, unless the indentation needs
1348+
// to be skipped.
1349+
if (*I == prec::Conditional && Previous && Previous->is(tok::colon) &&
1350+
Previous->is(TT_ConditionalExpr) && I == Current.FakeLParens.rbegin() &&
1351+
!State.Stack.back().IsWrappedConditional) {
1352+
NewParenState.IsChainedConditional = true;
1353+
} else if (*I == prec::Conditional ||
1354+
(!SkipFirstExtraIndent && *I > prec::Assignment &&
1355+
!Current.isTrailingComment())) {
13261356
NewParenState.Indent += Style.ContinuationIndentWidth;
1357+
}
13271358
if ((Previous && !Previous->opensScope()) || *I != prec::Comma)
13281359
NewParenState.BreakBeforeParameter = false;
13291360
State.Stack.push_back(NewParenState);

clang/lib/Format/ContinuationIndenter.h

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,15 @@ struct ParenState {
202202
ParenState(const FormatToken *Tok, unsigned Indent, unsigned LastSpace,
203203
bool AvoidBinPacking, bool NoLineBreak)
204204
: Tok(Tok), Indent(Indent), LastSpace(LastSpace),
205-
NestedBlockIndent(Indent), IsAligned(false),
206-
BreakBeforeClosingBrace(false), AvoidBinPacking(AvoidBinPacking),
207-
BreakBeforeParameter(false), NoLineBreak(NoLineBreak),
208-
NoLineBreakInOperand(false), LastOperatorWrapped(true),
209-
ContainsLineBreak(false), ContainsUnwrappedBuilder(false),
210-
AlignColons(true), ObjCSelectorNameFound(false),
211-
HasMultipleNestedBlocks(false), NestedBlockInlined(false),
212-
IsInsideObjCArrayLiteral(false), IsCSharpGenericTypeConstraint(false) {}
205+
NestedBlockIndent(Indent), BreakBeforeClosingBrace(false),
206+
AvoidBinPacking(AvoidBinPacking), BreakBeforeParameter(false),
207+
NoLineBreak(NoLineBreak), NoLineBreakInOperand(false),
208+
LastOperatorWrapped(true), ContainsLineBreak(false),
209+
ContainsUnwrappedBuilder(false), AlignColons(true),
210+
ObjCSelectorNameFound(false), HasMultipleNestedBlocks(false),
211+
NestedBlockInlined(false), IsInsideObjCArrayLiteral(false),
212+
IsCSharpGenericTypeConstraint(false), IsChainedConditional(false),
213+
IsWrappedConditional(false) {}
213214

214215
/// \brief The token opening this parenthesis level, or nullptr if this level
215216
/// is opened by fake parenthesis.
@@ -335,6 +336,14 @@ struct ParenState {
335336

336337
bool IsCSharpGenericTypeConstraint : 1;
337338

339+
/// \brief true if the current \c ParenState represents the false branch of
340+
/// a chained conditional expression (e.g. else-if)
341+
bool IsChainedConditional : 1;
342+
343+
/// \brief true if there conditionnal was wrapped on the first operator (the
344+
/// question mark)
345+
bool IsWrappedConditional : 1;
346+
338347
bool operator<(const ParenState &Other) const {
339348
if (Indent != Other.Indent)
340349
return Indent < Other.Indent;
@@ -376,6 +385,10 @@ struct ParenState {
376385
return NestedBlockInlined;
377386
if (IsCSharpGenericTypeConstraint != Other.IsCSharpGenericTypeConstraint)
378387
return IsCSharpGenericTypeConstraint;
388+
if (IsChainedConditional != Other.IsChainedConditional)
389+
return IsChainedConditional;
390+
if (IsWrappedConditional != Other.IsWrappedConditional)
391+
return IsWrappedConditional;
379392
return false;
380393
}
381394
};

clang/lib/Format/WhitespaceManager.cpp

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const tooling::Replacements &WhitespaceManager::generateReplacements() {
9595
alignConsecutiveMacros();
9696
alignConsecutiveDeclarations();
9797
alignConsecutiveAssignments();
98+
alignChainedConditionals();
9899
alignTrailingComments();
99100
alignEscapedNewlines();
100101
generateChanges();
@@ -227,6 +228,32 @@ void WhitespaceManager::calculateLineBreakInformation() {
227228
LastBlockComment = nullptr;
228229
}
229230
}
231+
232+
// Compute conditional nesting level
233+
// Level is increased for each conditional, unless this conditional continues
234+
// a chain of conditional, i.e. starts immediately after the colon of another
235+
// conditional.
236+
SmallVector<bool, 16> ScopeStack;
237+
int ConditionalsLevel = 0;
238+
for (auto &Change : Changes) {
239+
for (unsigned i = 0, e = Change.Tok->FakeLParens.size(); i != e; ++i) {
240+
bool isNestedConditional =
241+
Change.Tok->FakeLParens[e - 1 - i] == prec::Conditional &&
242+
!(i == 0 && Change.Tok->Previous &&
243+
Change.Tok->Previous->is(TT_ConditionalExpr) &&
244+
Change.Tok->Previous->is(tok::colon));
245+
if (isNestedConditional)
246+
++ConditionalsLevel;
247+
ScopeStack.push_back(isNestedConditional);
248+
}
249+
250+
Change.ConditionalsLevel = ConditionalsLevel;
251+
252+
for (unsigned i = Change.Tok->FakeRParens; i > 0 && ScopeStack.size(); --i) {
253+
if (ScopeStack.pop_back_val())
254+
--ConditionalsLevel;
255+
}
256+
}
230257
}
231258

232259
// Align a single sequence of tokens, see AlignTokens below.
@@ -248,6 +275,7 @@ AlignTokenSequence(unsigned Start, unsigned End, unsigned Column, F &&Matches,
248275
// double z);
249276
// In the above example, we need to take special care to ensure that
250277
// 'double z' is indented along with it's owning function 'b'.
278+
// Special handling is required for 'nested' ternary operators.
251279
SmallVector<unsigned, 16> ScopeStack;
252280

253281
for (unsigned i = Start; i != End; ++i) {
@@ -288,7 +316,10 @@ AlignTokenSequence(unsigned Start, unsigned End, unsigned Column, F &&Matches,
288316
unsigned ScopeStart = ScopeStack.back();
289317
if (Changes[ScopeStart - 1].Tok->is(TT_FunctionDeclarationName) ||
290318
(ScopeStart > Start + 1 &&
291-
Changes[ScopeStart - 2].Tok->is(TT_FunctionDeclarationName)))
319+
Changes[ScopeStart - 2].Tok->is(TT_FunctionDeclarationName)) ||
320+
Changes[i].Tok->is(TT_ConditionalExpr) ||
321+
(Changes[i].Tok->Previous &&
322+
Changes[i].Tok->Previous->is(TT_ConditionalExpr)))
292323
Changes[i].Spaces += Shift;
293324
}
294325

@@ -341,7 +372,7 @@ static unsigned AlignTokens(const FormatStyle &Style, F &&Matches,
341372
// abort when we hit any token in a higher scope than the starting one.
342373
auto IndentAndNestingLevel = StartAt < Changes.size()
343374
? Changes[StartAt].indentAndNestingLevel()
344-
: std::pair<unsigned, unsigned>(0, 0);
375+
: std::tuple<unsigned, unsigned, unsigned>();
345376

346377
// Keep track of the number of commas before the matching tokens, we will only
347378
// align a sequence of matching tokens if they are preceded by the same number
@@ -409,8 +440,8 @@ static unsigned AlignTokens(const FormatStyle &Style, F &&Matches,
409440
StartOfSequence = i;
410441

411442
unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
412-
int LineLengthAfter = -Changes[i].Spaces;
413-
for (unsigned j = i; j != e && Changes[j].NewlinesBefore == 0; ++j)
443+
int LineLengthAfter = Changes[i].TokenLength;
444+
for (unsigned j = i + 1; j != e && Changes[j].NewlinesBefore == 0; ++j)
414445
LineLengthAfter += Changes[j].Spaces + Changes[j].TokenLength;
415446
unsigned ChangeMaxColumn = Style.ColumnLimit - LineLengthAfter;
416447

@@ -608,6 +639,52 @@ void WhitespaceManager::alignConsecutiveDeclarations() {
608639
Changes, /*StartAt=*/0);
609640
}
610641

642+
void WhitespaceManager::alignChainedConditionals()
643+
{
644+
if (Style.BreakBeforeTernaryOperators) {
645+
AlignTokens(Style,
646+
[](Change const &C) {
647+
// Align question operators and last colon
648+
return C.Tok->is(TT_ConditionalExpr) &&
649+
((C.Tok->is(tok::question) && !C.NewlinesBefore) ||
650+
(C.Tok->is(tok::colon) && C.Tok->Next &&
651+
(C.Tok->Next->FakeLParens.size() == 0 ||
652+
C.Tok->Next->FakeLParens.back() !=
653+
prec::Conditional)));
654+
},
655+
Changes, /*StartAt=*/0);
656+
} else {
657+
static auto AlignWrappedOperand = [](Change const &C) {
658+
auto Previous = C.Tok->getPreviousNonComment();//Previous;
659+
return C.NewlinesBefore && Previous && Previous->is(TT_ConditionalExpr) &&
660+
(Previous->is(tok::question) ||
661+
(Previous->is(tok::colon) &&
662+
(C.Tok->FakeLParens.size() == 0 ||
663+
C.Tok->FakeLParens.back() != prec::Conditional)));
664+
};
665+
// Ensure we keep alignment of wrapped operands with non-wrapped operands
666+
// Since we actually align the operators, the wrapped operands need the
667+
// extra offset to be properly aligned.
668+
for (Change & C: Changes) {
669+
if (AlignWrappedOperand(C))
670+
C.StartOfTokenColumn -= 2;
671+
}
672+
AlignTokens(Style,
673+
[this](Change const &C) {
674+
// Align question operators if next operand is not wrapped, as
675+
// well as wrapped operands after question operator or last
676+
// colon in conditional sequence
677+
return (C.Tok->is(TT_ConditionalExpr) &&
678+
C.Tok->is(tok::question) &&
679+
&C != &Changes.back() &&
680+
(&C + 1)->NewlinesBefore == 0 &&
681+
!(&C + 1)->IsTrailingComment) ||
682+
AlignWrappedOperand(C);
683+
},
684+
Changes, /*StartAt=*/0);
685+
}
686+
}
687+
611688
void WhitespaceManager::alignTrailingComments() {
612689
unsigned MinColumn = 0;
613690
unsigned MaxColumn = UINT_MAX;

clang/lib/Format/WhitespaceManager.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "clang/Basic/SourceManager.h"
2020
#include "clang/Format/Format.h"
2121
#include <string>
22+
#include <tuple>
2223

2324
namespace clang {
2425
namespace format {
@@ -158,11 +159,16 @@ class WhitespaceManager {
158159
const Change *StartOfBlockComment;
159160
int IndentationOffset;
160161

161-
// A combination of indent level and nesting level, which are used in
162-
// tandem to compute lexical scope, for the purposes of deciding
162+
// Depth of conditionals. Computed from tracking fake parenthesis, except
163+
// it does not increase the indent for "chained" conditionals.
164+
int ConditionalsLevel;
165+
166+
// A combination of indent, nesting and conditionals levels, which are used
167+
// in tandem to compute lexical scope, for the purposes of deciding
163168
// when to stop consecutive alignment runs.
164-
std::pair<unsigned, unsigned> indentAndNestingLevel() const {
165-
return std::make_pair(Tok->IndentLevel, Tok->NestingLevel);
169+
std::tuple<unsigned, unsigned, unsigned> indentAndNestingLevel() const {
170+
return std::make_tuple(Tok->IndentLevel, Tok->NestingLevel,
171+
ConditionalsLevel);
166172
}
167173
};
168174

@@ -181,6 +187,9 @@ class WhitespaceManager {
181187
/// Align consecutive declarations over all \c Changes.
182188
void alignConsecutiveDeclarations();
183189

190+
/// Align consecutive declarations over all \c Changes.
191+
void alignChainedConditionals();
192+
184193
/// Align trailing comments over all \c Changes.
185194
void alignTrailingComments();
186195

0 commit comments

Comments
 (0)