Skip to content

Commit d81f003

Browse files
committed
[clang-format] Fix formatting of struct-like records followed by variable declaration.
Fixes #24781. Fixes #38160. This patch splits `TT_RecordLBrace` for classes/enums/structs/unions (and other records, e.g. interfaces) and uses the brace type to avoid the error-prone scanning for record token. The mentioned bugs were provoked by the scanning being too limited (and so not considering `const` or `constexpr`, or other qualifiers, on an anonymous struct variable declaration). Moreover, the proposed solution is more efficient as we parse tokens once only (scanning being parsing too). Reviewed By: MyDeveloperDay, HazardyKnusperkeks Differential Revision: https://reviews.llvm.org/D119785
1 parent 014c033 commit d81f003

File tree

6 files changed

+98
-35
lines changed

6 files changed

+98
-35
lines changed

clang/lib/Format/FormatToken.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ namespace format {
3737
TYPE(BlockComment) \
3838
TYPE(BracedListLBrace) \
3939
TYPE(CastRParen) \
40+
TYPE(ClassLBrace) \
4041
TYPE(CompoundRequirementLBrace) \
4142
TYPE(ConditionalExpr) \
4243
TYPE(ConflictAlternative) \
@@ -47,6 +48,7 @@ namespace format {
4748
TYPE(DesignatedInitializerLSquare) \
4849
TYPE(DesignatedInitializerPeriod) \
4950
TYPE(DictLiteral) \
51+
TYPE(EnumLBrace) \
5052
TYPE(FatArrow) \
5153
TYPE(ForEachMacro) \
5254
TYPE(FunctionAnnotationRParen) \
@@ -108,6 +110,7 @@ namespace format {
108110
TYPE(StartOfName) \
109111
TYPE(StatementAttributeLikeMacro) \
110112
TYPE(StatementMacro) \
113+
TYPE(StructLBrace) \
111114
TYPE(StructuredBindingLSquare) \
112115
TYPE(TemplateCloser) \
113116
TYPE(TemplateOpener) \
@@ -119,6 +122,7 @@ namespace format {
119122
TYPE(TypeDeclarationParen) \
120123
TYPE(TypenameMacro) \
121124
TYPE(UnaryOperator) \
125+
TYPE(UnionLBrace) \
122126
TYPE(UntouchableMacroFunc) \
123127
TYPE(CSharpStringLiteral) \
124128
TYPE(CSharpNamedArgumentColon) \

clang/lib/Format/TokenAnnotator.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,11 +1425,12 @@ class AnnotatingParser {
14251425
TT_LambdaArrow, TT_NamespaceMacro, TT_OverloadedOperator,
14261426
TT_RegexLiteral, TT_TemplateString, TT_ObjCStringLiteral,
14271427
TT_UntouchableMacroFunc, TT_StatementAttributeLikeMacro,
1428-
TT_FunctionLikeOrFreestandingMacro, TT_RecordLBrace,
1429-
TT_RequiresClause, TT_RequiresClauseInARequiresExpression,
1430-
TT_RequiresExpression, TT_RequiresExpressionLParen,
1431-
TT_RequiresExpressionLBrace, TT_BinaryOperator,
1432-
TT_CompoundRequirementLBrace, TT_BracedListLBrace))
1428+
TT_FunctionLikeOrFreestandingMacro, TT_ClassLBrace, TT_EnumLBrace,
1429+
TT_RecordLBrace, TT_StructLBrace, TT_UnionLBrace, TT_RequiresClause,
1430+
TT_RequiresClauseInARequiresExpression, TT_RequiresExpression,
1431+
TT_RequiresExpressionLParen, TT_RequiresExpressionLBrace,
1432+
TT_BinaryOperator, TT_CompoundRequirementLBrace,
1433+
TT_BracedListLBrace))
14331434
CurrentToken->setType(TT_Unknown);
14341435
CurrentToken->Role.reset();
14351436
CurrentToken->MatchingParen = nullptr;

clang/lib/Format/UnwrappedLineFormatter.cpp

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ bool startsExternCBlock(const AnnotatedLine &Line) {
2626
NextNext && NextNext->is(tok::l_brace);
2727
}
2828

29+
bool isRecordLBrace(const FormatToken &Tok) {
30+
return Tok.isOneOf(TT_ClassLBrace, TT_EnumLBrace, TT_RecordLBrace,
31+
TT_StructLBrace, TT_UnionLBrace);
32+
}
33+
2934
/// Tracks the indent level of \c AnnotatedLines across levels.
3035
///
3136
/// \c nextLine must be called for each \c AnnotatedLine, after which \c
@@ -310,7 +315,7 @@ class LineJoiner {
310315
for (const FormatToken *RecordTok = (*J)->Last; RecordTok;
311316
RecordTok = RecordTok->Previous)
312317
if (RecordTok->is(tok::l_brace))
313-
return RecordTok->is(TT_RecordLBrace);
318+
return isRecordLBrace(*RecordTok);
314319
}
315320
}
316321

@@ -457,27 +462,30 @@ class LineJoiner {
457462
}
458463
}
459464

460-
// Try to merge a block with left brace unwrapped that wasn't yet covered.
461465
if (TheLine->Last->is(tok::l_brace)) {
462-
const FormatToken *Tok = TheLine->First;
463466
bool ShouldMerge = false;
464-
if (Tok->is(tok::kw_typedef)) {
465-
Tok = Tok->getNextNonComment();
466-
assert(Tok);
467-
}
468-
if (Tok->isOneOf(tok::kw_class, tok::kw_struct)) {
467+
// Try to merge records.
468+
if (TheLine->Last->is(TT_EnumLBrace)) {
469+
ShouldMerge = Style.AllowShortEnumsOnASingleLine;
470+
} else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace)) {
471+
// NOTE: We use AfterClass (whereas AfterStruct exists) for both classes
472+
// and structs, but it seems that wrapping is still handled correctly
473+
// elsewhere.
469474
ShouldMerge = !Style.BraceWrapping.AfterClass ||
470475
(NextLine.First->is(tok::r_brace) &&
471476
!Style.BraceWrapping.SplitEmptyRecord);
472-
} else if (Tok->is(tok::kw_enum)) {
473-
ShouldMerge = Style.AllowShortEnumsOnASingleLine;
474477
} else {
478+
// Try to merge a block with left brace unwrapped that wasn't yet
479+
// covered.
480+
assert(!TheLine->First->isOneOf(tok::kw_class, tok::kw_enum,
481+
tok::kw_struct));
475482
ShouldMerge = !Style.BraceWrapping.AfterFunction ||
476483
(NextLine.First->is(tok::r_brace) &&
477484
!Style.BraceWrapping.SplitEmptyFunction);
478485
}
479486
return ShouldMerge ? tryMergeSimpleBlock(I, E, Limit) : 0;
480487
}
488+
481489
// Try to merge a function block with left brace wrapped.
482490
if (NextLine.First->is(TT_FunctionLBrace) &&
483491
Style.BraceWrapping.AfterFunction) {
@@ -715,6 +723,7 @@ class LineJoiner {
715723
const FormatToken *Next = Tok->getNextNonComment();
716724
return !Next || Next->is(tok::semi);
717725
};
726+
718727
if (ShouldMerge()) {
719728
// We merge empty blocks even if the line exceeds the column limit.
720729
Tok->SpacesRequiredBefore = Style.SpaceInEmptyBlock ? 1 : 0;
@@ -723,18 +732,7 @@ class LineJoiner {
723732
} else if (Limit != 0 && !Line.startsWithNamespace() &&
724733
!startsExternCBlock(Line)) {
725734
// We don't merge short records.
726-
FormatToken *RecordTok = Line.First;
727-
// Skip record modifiers.
728-
while (RecordTok->Next &&
729-
RecordTok->isOneOf(tok::kw_typedef, tok::kw_export,
730-
Keywords.kw_declare, Keywords.kw_abstract,
731-
tok::kw_default, Keywords.kw_override,
732-
tok::kw_public, tok::kw_private,
733-
tok::kw_protected, Keywords.kw_internal))
734-
RecordTok = RecordTok->Next;
735-
if (RecordTok &&
736-
RecordTok->isOneOf(tok::kw_class, tok::kw_union, tok::kw_struct,
737-
Keywords.kw_interface))
735+
if (isRecordLBrace(*Line.Last))
738736
return 0;
739737

740738
// Check that we still have three lines and they fit into the limit.

clang/lib/Format/UnwrappedLineParser.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3160,7 +3160,7 @@ bool UnwrappedLineParser::parseEnum() {
31603160
// Just a declaration or something is wrong.
31613161
if (FormatTok->isNot(tok::l_brace))
31623162
return true;
3163-
FormatTok->setType(TT_RecordLBrace);
3163+
FormatTok->setType(TT_EnumLBrace);
31643164
FormatTok->setBlockKind(BK_Block);
31653165

31663166
if (Style.Language == FormatStyle::LK_Java) {
@@ -3397,8 +3397,22 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr) {
33973397
nextToken();
33983398
}
33993399
}
3400+
3401+
auto GetBraceType = [](const FormatToken &RecordTok) {
3402+
switch (RecordTok.Tok.getKind()) {
3403+
case tok::kw_class:
3404+
return TT_ClassLBrace;
3405+
case tok::kw_struct:
3406+
return TT_StructLBrace;
3407+
case tok::kw_union:
3408+
return TT_UnionLBrace;
3409+
default:
3410+
// Useful for e.g. interface.
3411+
return TT_RecordLBrace;
3412+
}
3413+
};
34003414
if (FormatTok->Tok.is(tok::l_brace)) {
3401-
FormatTok->setType(TT_RecordLBrace);
3415+
FormatTok->setType(GetBraceType(InitialToken));
34023416
if (ParseAsExpr) {
34033417
parseChildBlock();
34043418
} else {

clang/unittests/Format/FormatTest.cpp

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3395,10 +3395,43 @@ TEST_F(FormatTest, BreakInheritanceStyle) {
33953395
StyleWithInheritanceBreakAfterComma);
33963396
}
33973397

3398-
TEST_F(FormatTest, FormatsVariableDeclarationsAfterStructOrClass) {
3398+
TEST_F(FormatTest, FormatsVariableDeclarationsAfterRecord) {
33993399
verifyFormat("class A {\n} a, b;");
34003400
verifyFormat("struct A {\n} a, b;");
3401-
verifyFormat("union A {\n} a;");
3401+
verifyFormat("union A {\n} a, b;");
3402+
3403+
verifyFormat("constexpr class A {\n} a, b;");
3404+
verifyFormat("constexpr struct A {\n} a, b;");
3405+
verifyFormat("constexpr union A {\n} a, b;");
3406+
3407+
verifyFormat("namespace {\nclass A {\n} a, b;\n} // namespace");
3408+
verifyFormat("namespace {\nstruct A {\n} a, b;\n} // namespace");
3409+
verifyFormat("namespace {\nunion A {\n} a, b;\n} // namespace");
3410+
3411+
verifyFormat("namespace {\nconstexpr class A {\n} a, b;\n} // namespace");
3412+
verifyFormat("namespace {\nconstexpr struct A {\n} a, b;\n} // namespace");
3413+
verifyFormat("namespace {\nconstexpr union A {\n} a, b;\n} // namespace");
3414+
3415+
verifyFormat("namespace ns {\n"
3416+
"class {\n"
3417+
"} a, b;\n"
3418+
"} // namespace ns");
3419+
verifyFormat("namespace ns {\n"
3420+
"const class {\n"
3421+
"} a, b;\n"
3422+
"} // namespace ns");
3423+
verifyFormat("namespace ns {\n"
3424+
"constexpr class C {\n"
3425+
"} a, b;\n"
3426+
"} // namespace ns");
3427+
verifyFormat("namespace ns {\n"
3428+
"class { /* comment */\n"
3429+
"} a, b;\n"
3430+
"} // namespace ns");
3431+
verifyFormat("namespace ns {\n"
3432+
"const class { /* comment */\n"
3433+
"} a, b;\n"
3434+
"} // namespace ns");
34023435
}
34033436

34043437
TEST_F(FormatTest, FormatsEnum) {

clang/unittests/Format/TokenAnnotatorTest.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,25 +78,38 @@ TEST_F(TokenAnnotatorTest, UnderstandsUsesOfStarAndAmpInMacroDefinition) {
7878
TEST_F(TokenAnnotatorTest, UnderstandsClasses) {
7979
auto Tokens = annotate("class C {};");
8080
EXPECT_EQ(Tokens.size(), 6u) << Tokens;
81-
EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_RecordLBrace);
81+
EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_ClassLBrace);
82+
83+
Tokens = annotate("const class C {} c;");
84+
EXPECT_EQ(Tokens.size(), 8u) << Tokens;
85+
EXPECT_TOKEN(Tokens[3], tok::l_brace, TT_ClassLBrace);
86+
87+
Tokens = annotate("const class {} c;");
88+
EXPECT_EQ(Tokens.size(), 7u) << Tokens;
89+
EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_ClassLBrace);
8290
}
8391

8492
TEST_F(TokenAnnotatorTest, UnderstandsStructs) {
8593
auto Tokens = annotate("struct S {};");
8694
EXPECT_EQ(Tokens.size(), 6u) << Tokens;
87-
EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_RecordLBrace);
95+
EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_StructLBrace);
8896
}
8997

9098
TEST_F(TokenAnnotatorTest, UnderstandsUnions) {
9199
auto Tokens = annotate("union U {};");
92100
EXPECT_EQ(Tokens.size(), 6u) << Tokens;
93-
EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_RecordLBrace);
101+
EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_UnionLBrace);
102+
103+
Tokens = annotate("union U { void f() { return; } };");
104+
EXPECT_EQ(Tokens.size(), 14u) << Tokens;
105+
EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_UnionLBrace);
106+
EXPECT_TOKEN(Tokens[7], tok::l_brace, TT_FunctionLBrace);
94107
}
95108

96109
TEST_F(TokenAnnotatorTest, UnderstandsEnums) {
97110
auto Tokens = annotate("enum E {};");
98111
EXPECT_EQ(Tokens.size(), 6u) << Tokens;
99-
EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_RecordLBrace);
112+
EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_EnumLBrace);
100113
}
101114

102115
TEST_F(TokenAnnotatorTest, UnderstandsLBracesInMacroDefinition) {

0 commit comments

Comments
 (0)