Skip to content

Commit beab692

Browse files
committed
[clang-format] Handle Java switch expressions
Also adds AllowShortCaseExpressionOnASingleLine option and AlignCaseArrows suboption of AlignConsecutiveShortCaseStatements. Fixes #55903.
1 parent 9154a32 commit beab692

14 files changed

+332
-21
lines changed

clang/docs/ClangFormatStyleOptions.rst

+35-1
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,8 @@ the configuration (without a prefix: ``Auto``).
861861

862862
**AlignConsecutiveShortCaseStatements** (``ShortCaseStatementsAlignmentStyle``) :versionbadge:`clang-format 17` :ref:`<AlignConsecutiveShortCaseStatements>`
863863
Style of aligning consecutive short case labels.
864-
Only applies if ``AllowShortCaseLabelsOnASingleLine`` is ``true``.
864+
Only applies if ``AllowShortCaseExpressionOnASingleLine`` or
865+
``AllowShortCaseLabelsOnASingleLine`` is ``true``.
865866

866867

867868
.. code-block:: yaml
@@ -935,6 +936,24 @@ the configuration (without a prefix: ``Auto``).
935936
default: return "";
936937
}
937938
939+
* ``bool AlignCaseArrows`` Whether to align the case arrows when aligning short case expressions.
940+
941+
.. code-block:: java
942+
943+
true:
944+
i = switch (day) {
945+
case THURSDAY, SATURDAY -> 8;
946+
case WEDNESDAY -> 9;
947+
default -> 0;
948+
};
949+
950+
false:
951+
i = switch (day) {
952+
case THURSDAY, SATURDAY -> 8;
953+
case WEDNESDAY -> 9;
954+
default -> 0;
955+
};
956+
938957
* ``bool AlignCaseColons`` Whether aligned case labels are aligned on the colon, or on the tokens
939958
after the colon.
940959

@@ -1692,6 +1711,21 @@ the configuration (without a prefix: ``Auto``).
16921711

16931712

16941713

1714+
.. _AllowShortCaseExpressionOnASingleLine:
1715+
1716+
**AllowShortCaseExpressionOnASingleLine** (``Boolean``) :versionbadge:`clang-format 19` :ref:`<AllowShortCaseExpressionOnASingleLine>`
1717+
Whether to merge a short switch labeled rule into a single line.
1718+
1719+
.. code-block:: java
1720+
1721+
true: false:
1722+
switch (a) { vs. switch (a) {
1723+
case 1 -> 1; case 1 ->
1724+
default -> 0; 1;
1725+
}; default ->
1726+
0;
1727+
};
1728+
16951729
.. _AllowShortCaseLabelsOnASingleLine:
16961730

16971731
**AllowShortCaseLabelsOnASingleLine** (``Boolean``) :versionbadge:`clang-format 3.6` :ref:`<AllowShortCaseLabelsOnASingleLine>`

clang/docs/ReleaseNotes.rst

+3
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,9 @@ clang-format
834834
``BreakTemplateDeclarations``.
835835
- ``AlwaysBreakAfterReturnType`` is deprecated and renamed to
836836
``BreakAfterReturnType``.
837+
- Handles Java ``switch`` expressions.
838+
- Adds ``AllowShortCaseExpressionOnASingleLine`` option.
839+
- Adds ``AlignCaseArrows`` suboption to ``AlignConsecutiveShortCaseStatements``.
837840

838841
libclang
839842
--------

clang/include/clang/Format/Format.h

+35-1
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,23 @@ struct FormatStyle {
375375
/// }
376376
/// \endcode
377377
bool AcrossComments;
378+
/// Whether to align the case arrows when aligning short case expressions.
379+
/// \code{.java}
380+
/// true:
381+
/// i = switch (day) {
382+
/// case THURSDAY, SATURDAY -> 8;
383+
/// case WEDNESDAY -> 9;
384+
/// default -> 0;
385+
/// };
386+
///
387+
/// false:
388+
/// i = switch (day) {
389+
/// case THURSDAY, SATURDAY -> 8;
390+
/// case WEDNESDAY -> 9;
391+
/// default -> 0;
392+
/// };
393+
/// \endcode
394+
bool AlignCaseArrows;
378395
/// Whether aligned case labels are aligned on the colon, or on the tokens
379396
/// after the colon.
380397
/// \code
@@ -396,12 +413,14 @@ struct FormatStyle {
396413
bool operator==(const ShortCaseStatementsAlignmentStyle &R) const {
397414
return Enabled == R.Enabled && AcrossEmptyLines == R.AcrossEmptyLines &&
398415
AcrossComments == R.AcrossComments &&
416+
AlignCaseArrows == R.AlignCaseArrows &&
399417
AlignCaseColons == R.AlignCaseColons;
400418
}
401419
};
402420

403421
/// Style of aligning consecutive short case labels.
404-
/// Only applies if ``AllowShortCaseLabelsOnASingleLine`` is ``true``.
422+
/// Only applies if ``AllowShortCaseExpressionOnASingleLine`` or
423+
/// ``AllowShortCaseLabelsOnASingleLine`` is ``true``.
405424
///
406425
/// \code{.yaml}
407426
/// # Example of usage:
@@ -724,6 +743,19 @@ struct FormatStyle {
724743
/// \version 3.5
725744
ShortBlockStyle AllowShortBlocksOnASingleLine;
726745

746+
/// Whether to merge a short switch labeled rule into a single line.
747+
/// \code{.java}
748+
/// true: false:
749+
/// switch (a) { vs. switch (a) {
750+
/// case 1 -> 1; case 1 ->
751+
/// default -> 0; 1;
752+
/// }; default ->
753+
/// 0;
754+
/// };
755+
/// \endcode
756+
/// \version 19
757+
bool AllowShortCaseExpressionOnASingleLine;
758+
727759
/// If ``true``, short case labels will be contracted to a single line.
728760
/// \code
729761
/// true: false:
@@ -4923,6 +4955,8 @@ struct FormatStyle {
49234955
AllowBreakBeforeNoexceptSpecifier ==
49244956
R.AllowBreakBeforeNoexceptSpecifier &&
49254957
AllowShortBlocksOnASingleLine == R.AllowShortBlocksOnASingleLine &&
4958+
AllowShortCaseExpressionOnASingleLine ==
4959+
R.AllowShortCaseExpressionOnASingleLine &&
49264960
AllowShortCaseLabelsOnASingleLine ==
49274961
R.AllowShortCaseLabelsOnASingleLine &&
49284962
AllowShortCompoundRequirementOnASingleLine ==

clang/lib/Format/Format.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ struct MappingTraits<FormatStyle::ShortCaseStatementsAlignmentStyle> {
100100
IO.mapOptional("Enabled", Value.Enabled);
101101
IO.mapOptional("AcrossEmptyLines", Value.AcrossEmptyLines);
102102
IO.mapOptional("AcrossComments", Value.AcrossComments);
103+
IO.mapOptional("AlignCaseArrows", Value.AlignCaseArrows);
103104
IO.mapOptional("AlignCaseColons", Value.AlignCaseColons);
104105
}
105106
};
@@ -911,6 +912,8 @@ template <> struct MappingTraits<FormatStyle> {
911912
Style.AllowBreakBeforeNoexceptSpecifier);
912913
IO.mapOptional("AllowShortBlocksOnASingleLine",
913914
Style.AllowShortBlocksOnASingleLine);
915+
IO.mapOptional("AllowShortCaseExpressionOnASingleLine",
916+
Style.AllowShortCaseExpressionOnASingleLine);
914917
IO.mapOptional("AllowShortCaseLabelsOnASingleLine",
915918
Style.AllowShortCaseLabelsOnASingleLine);
916919
IO.mapOptional("AllowShortCompoundRequirementOnASingleLine",
@@ -1423,6 +1426,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
14231426
LLVMStyle.AllowAllParametersOfDeclarationOnNextLine = true;
14241427
LLVMStyle.AllowBreakBeforeNoexceptSpecifier = FormatStyle::BBNSS_Never;
14251428
LLVMStyle.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Never;
1429+
LLVMStyle.AllowShortCaseExpressionOnASingleLine = true;
14261430
LLVMStyle.AllowShortCaseLabelsOnASingleLine = false;
14271431
LLVMStyle.AllowShortCompoundRequirementOnASingleLine = true;
14281432
LLVMStyle.AllowShortEnumsOnASingleLine = true;

clang/lib/Format/FormatToken.h

+3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ namespace format {
3838
/* l_brace of a block that is not the body of a (e.g. loop) statement. */ \
3939
TYPE(BlockLBrace) \
4040
TYPE(BracedListLBrace) \
41+
TYPE(CaseLabelArrow) \
4142
/* The colon at the end of a case label. */ \
4243
TYPE(CaseLabelColon) \
4344
TYPE(CastRParen) \
@@ -148,6 +149,8 @@ namespace format {
148149
TYPE(StructLBrace) \
149150
TYPE(StructRBrace) \
150151
TYPE(StructuredBindingLSquare) \
152+
TYPE(SwitchExpressionLabel) \
153+
TYPE(SwitchExpressionLBrace) \
151154
TYPE(TableGenBangOperator) \
152155
TYPE(TableGenCondOperator) \
153156
TYPE(TableGenCondOperatorColon) \

clang/lib/Format/TokenAnnotator.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -5051,6 +5051,8 @@ bool TokenAnnotator::spaceRequiredBefore(const AnnotatedLine &Line,
50515051
return true; // "x! as string", "x! in y"
50525052
}
50535053
} else if (Style.Language == FormatStyle::LK_Java) {
5054+
if (Left.is(TT_CaseLabelArrow) || Right.is(TT_CaseLabelArrow))
5055+
return true;
50545056
if (Left.is(tok::r_square) && Right.is(tok::l_brace))
50555057
return true;
50565058
// spaces inside square brackets.

clang/lib/Format/UnwrappedLineFormatter.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,12 @@ class LineJoiner {
515515
}
516516
}
517517

518+
if (TheLine->First->is(TT_SwitchExpressionLabel)) {
519+
return Style.AllowShortCaseExpressionOnASingleLine
520+
? tryMergeShortCaseLabels(I, E, Limit)
521+
: 0;
522+
}
523+
518524
if (TheLine->Last->is(tok::l_brace)) {
519525
bool ShouldMerge = false;
520526
// Try to merge records.

clang/lib/Format/UnwrappedLineParser.cpp

+37-9
Original file line numberDiff line numberDiff line change
@@ -430,9 +430,9 @@ bool UnwrappedLineParser::parseLevel(const FormatToken *OpeningBrace,
430430
unsigned StoredPosition = Tokens->getPosition();
431431
auto *Next = Tokens->getNextNonComment();
432432
FormatTok = Tokens->setPosition(StoredPosition);
433-
if (Next->isNot(tok::colon)) {
434-
// default not followed by ':' is not a case label; treat it like
435-
// an identifier.
433+
if (!Next->isOneOf(tok::colon, tok::arrow)) {
434+
// default not followed by `:` or `->` is not a case label; treat it
435+
// like an identifier.
436436
parseStructuralElement();
437437
break;
438438
}
@@ -451,6 +451,7 @@ bool UnwrappedLineParser::parseLevel(const FormatToken *OpeningBrace,
451451
}
452452
if (!SwitchLabelEncountered &&
453453
(Style.IndentCaseLabels ||
454+
(OpeningBrace && OpeningBrace->is(TT_SwitchExpressionLBrace)) ||
454455
(Line->InPPDirective && Line->Level == 1))) {
455456
++Line->Level;
456457
}
@@ -1519,24 +1520,32 @@ void UnwrappedLineParser::parseStructuralElement(
15191520
// 'switch: string' field declaration.
15201521
break;
15211522
}
1522-
parseSwitch();
1523+
parseSwitch(/*IsExpr=*/false);
15231524
return;
1524-
case tok::kw_default:
1525+
case tok::kw_default: {
15251526
// In Verilog default along with other labels are handled in the next loop.
15261527
if (Style.isVerilog())
15271528
break;
15281529
if (Style.isJavaScript() && Line->MustBeDeclaration) {
15291530
// 'default: string' field declaration.
15301531
break;
15311532
}
1533+
auto *Default = FormatTok;
15321534
nextToken();
15331535
if (FormatTok->is(tok::colon)) {
15341536
FormatTok->setFinalizedType(TT_CaseLabelColon);
15351537
parseLabel();
15361538
return;
15371539
}
1540+
if (FormatTok->is(tok::arrow)) {
1541+
FormatTok->setFinalizedType(TT_CaseLabelArrow);
1542+
Default->setFinalizedType(TT_SwitchExpressionLabel);
1543+
parseLabel();
1544+
return;
1545+
}
15381546
// e.g. "default void f() {}" in a Java interface.
15391547
break;
1548+
}
15401549
case tok::kw_case:
15411550
// Proto: there are no switch/case statements.
15421551
if (Style.Language == FormatStyle::LK_Proto) {
@@ -2061,6 +2070,11 @@ void UnwrappedLineParser::parseStructuralElement(
20612070
case tok::kw_new:
20622071
parseNew();
20632072
break;
2073+
case tok::kw_switch:
2074+
if (Style.Language == FormatStyle::LK_Java)
2075+
parseSwitch(/*IsExpr=*/true);
2076+
nextToken();
2077+
break;
20642078
case tok::kw_case:
20652079
// Proto: there are no switch/case statements.
20662080
if (Style.Language == FormatStyle::LK_Proto) {
@@ -2583,6 +2597,9 @@ bool UnwrappedLineParser::parseParens(TokenType AmpAmpTokenType) {
25832597
else
25842598
nextToken();
25852599
break;
2600+
case tok::kw_switch:
2601+
parseSwitch(/*IsExpr=*/true);
2602+
break;
25862603
case tok::kw_requires: {
25872604
auto RequiresToken = FormatTok;
25882605
nextToken();
@@ -3240,6 +3257,7 @@ void UnwrappedLineParser::parseLabel(bool LeftAlignLabel) {
32403257

32413258
void UnwrappedLineParser::parseCaseLabel() {
32423259
assert(FormatTok->is(tok::kw_case) && "'case' expected");
3260+
auto *Case = FormatTok;
32433261

32443262
// FIXME: fix handling of complex expressions here.
32453263
do {
@@ -3248,11 +3266,16 @@ void UnwrappedLineParser::parseCaseLabel() {
32483266
FormatTok->setFinalizedType(TT_CaseLabelColon);
32493267
break;
32503268
}
3269+
if (Style.Language == FormatStyle::LK_Java && FormatTok->is(tok::arrow)) {
3270+
FormatTok->setFinalizedType(TT_CaseLabelArrow);
3271+
Case->setFinalizedType(TT_SwitchExpressionLabel);
3272+
break;
3273+
}
32513274
} while (!eof());
32523275
parseLabel();
32533276
}
32543277

3255-
void UnwrappedLineParser::parseSwitch() {
3278+
void UnwrappedLineParser::parseSwitch(bool IsExpr) {
32563279
assert(FormatTok->is(tok::kw_switch) && "'switch' expected");
32573280
nextToken();
32583281
if (FormatTok->is(tok::l_paren))
@@ -3262,10 +3285,15 @@ void UnwrappedLineParser::parseSwitch() {
32623285

32633286
if (FormatTok->is(tok::l_brace)) {
32643287
CompoundStatementIndenter Indenter(this, Style, Line->Level);
3265-
FormatTok->setFinalizedType(TT_ControlStatementLBrace);
3266-
parseBlock();
3288+
FormatTok->setFinalizedType(IsExpr ? TT_SwitchExpressionLBrace
3289+
: TT_ControlStatementLBrace);
3290+
if (IsExpr)
3291+
parseChildBlock();
3292+
else
3293+
parseBlock();
32673294
setPreviousRBraceType(TT_ControlStatementRBrace);
3268-
addUnwrappedLine();
3295+
if (!IsExpr)
3296+
addUnwrappedLine();
32693297
} else {
32703298
addUnwrappedLine();
32713299
++Line->Level;

clang/lib/Format/UnwrappedLineParser.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ class UnwrappedLineParser {
157157
void parseDoWhile();
158158
void parseLabel(bool LeftAlignLabel = false);
159159
void parseCaseLabel();
160-
void parseSwitch();
160+
void parseSwitch(bool IsExpr);
161161
void parseNamespace();
162162
bool parseModuleImport();
163163
void parseNew();

clang/lib/Format/WhitespaceManager.cpp

+14-8
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ const tooling::Replacements &WhitespaceManager::generateReplacements() {
107107
llvm::sort(Changes, Change::IsBeforeInFile(SourceMgr));
108108
calculateLineBreakInformation();
109109
alignConsecutiveMacros();
110-
alignConsecutiveShortCaseStatements();
110+
alignConsecutiveShortCaseStatements(/*IsExpr=*/true);
111+
alignConsecutiveShortCaseStatements(/*IsExpr=*/false);
111112
alignConsecutiveDeclarations();
112113
alignConsecutiveBitFields();
113114
alignConsecutiveAssignments();
@@ -878,22 +879,27 @@ void WhitespaceManager::alignConsecutiveColons(
878879
Changes, /*StartAt=*/0, AlignStyle);
879880
}
880881

881-
void WhitespaceManager::alignConsecutiveShortCaseStatements() {
882+
void WhitespaceManager::alignConsecutiveShortCaseStatements(bool IsExpr) {
882883
if (!Style.AlignConsecutiveShortCaseStatements.Enabled ||
883-
!Style.AllowShortCaseLabelsOnASingleLine) {
884+
!(IsExpr ? Style.AllowShortCaseExpressionOnASingleLine
885+
: Style.AllowShortCaseLabelsOnASingleLine)) {
884886
return;
885887
}
886888

889+
const auto Type = IsExpr ? TT_CaseLabelArrow : TT_CaseLabelColon;
890+
const auto &Option = Style.AlignConsecutiveShortCaseStatements;
891+
const bool AlignArrowOrColon =
892+
IsExpr ? Option.AlignCaseArrows : Option.AlignCaseColons;
893+
887894
auto Matches = [&](const Change &C) {
888-
if (Style.AlignConsecutiveShortCaseStatements.AlignCaseColons)
889-
return C.Tok->is(TT_CaseLabelColon);
895+
if (AlignArrowOrColon)
896+
return C.Tok->is(Type);
890897

891898
// Ignore 'IsInsideToken' to allow matching trailing comments which
892899
// need to be reflowed as that causes the token to appear in two
893900
// different changes, which will cause incorrect alignment as we'll
894901
// reflow early due to detecting multiple aligning tokens per line.
895-
return !C.IsInsideToken && C.Tok->Previous &&
896-
C.Tok->Previous->is(TT_CaseLabelColon);
902+
return !C.IsInsideToken && C.Tok->Previous && C.Tok->Previous->is(Type);
897903
};
898904

899905
unsigned MinColumn = 0;
@@ -944,7 +950,7 @@ void WhitespaceManager::alignConsecutiveShortCaseStatements() {
944950
if (Changes[I].Tok->isNot(tok::comment))
945951
LineIsComment = false;
946952

947-
if (Changes[I].Tok->is(TT_CaseLabelColon)) {
953+
if (Changes[I].Tok->is(Type)) {
948954
LineIsEmptyCase =
949955
!Changes[I].Tok->Next || Changes[I].Tok->Next->isTrailingComment();
950956

clang/lib/Format/WhitespaceManager.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ class WhitespaceManager {
233233
void alignChainedConditionals();
234234

235235
/// Align consecutive short case statements over all \c Changes.
236-
void alignConsecutiveShortCaseStatements();
236+
void alignConsecutiveShortCaseStatements(bool IsExpr);
237237

238238
/// Align consecutive TableGen DAGArg colon over all \c Changes.
239239
void alignConsecutiveTableGenBreakingDAGArgColons();

0 commit comments

Comments
 (0)