Skip to content

Commit 486ec4b

Browse files
authored
[clang-format] Add AllowShortNamespacesOnASingleLine option (#105597)
This fixes #101363 which is a resurrection of a previously opened but never completed review: https://reviews.llvm.org/D11851 The feature is to allow code like the following not to be broken across multiple lines: ``` namespace foo { class bar; } namespace foo { namespace bar { class baz; } } ``` Code like this is commonly used for forward declarations, which are ideally kept compact. This is also apparently the format that include-what-you-use will insert for forward declarations. Also, fix an off-by-one error in `CompactNamespaces` code. For nested namespaces with 3 or more namespaces, it was incorrectly compacting lines which were 1 or two spaces over the `ColumnLimit`, leading to incorrect formatting results.
1 parent 91c5de7 commit 486ec4b

File tree

7 files changed

+221
-1
lines changed

7 files changed

+221
-1
lines changed

clang/docs/ClangFormatStyleOptions.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2088,6 +2088,11 @@ the configuration (without a prefix: ``Auto``).
20882088
If ``true``, ``while (true) continue;`` can be put on a single
20892089
line.
20902090

2091+
.. _AllowShortNamespacesOnASingleLine:
2092+
2093+
**AllowShortNamespacesOnASingleLine** (``Boolean``) :versionbadge:`clang-format 20` :ref:`<AllowShortNamespacesOnASingleLine>`
2094+
If ``true``, ``namespace a { class b; }`` can be put on a single line.
2095+
20912096
.. _AlwaysBreakAfterDefinitionReturnType:
20922097

20932098
**AlwaysBreakAfterDefinitionReturnType** (``DefinitionReturnTypeBreakingStyle``) :versionbadge:`clang-format 3.7` :ref:`<AlwaysBreakAfterDefinitionReturnType>`

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,7 @@ clang-format
11231123
``Never``, and ``true`` to ``Always``.
11241124
- Adds ``RemoveEmptyLinesInUnwrappedLines`` option.
11251125
- Adds ``KeepFormFeed`` option and set it to ``true`` for ``GNU`` style.
1126+
- Adds ``AllowShortNamespacesOnASingleLine`` option.
11261127

11271128
libclang
11281129
--------

clang/include/clang/Format/Format.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,10 @@ struct FormatStyle {
988988
/// \version 3.7
989989
bool AllowShortLoopsOnASingleLine;
990990

991+
/// If ``true``, ``namespace a { class b; }`` can be put on a single line.
992+
/// \version 20
993+
bool AllowShortNamespacesOnASingleLine;
994+
991995
/// Different ways to break after the function definition return type.
992996
/// This option is **deprecated** and is retained for backwards compatibility.
993997
enum DefinitionReturnTypeBreakingStyle : int8_t {
@@ -5168,6 +5172,8 @@ struct FormatStyle {
51685172
R.AllowShortIfStatementsOnASingleLine &&
51695173
AllowShortLambdasOnASingleLine == R.AllowShortLambdasOnASingleLine &&
51705174
AllowShortLoopsOnASingleLine == R.AllowShortLoopsOnASingleLine &&
5175+
AllowShortNamespacesOnASingleLine ==
5176+
R.AllowShortNamespacesOnASingleLine &&
51715177
AlwaysBreakBeforeMultilineStrings ==
51725178
R.AlwaysBreakBeforeMultilineStrings &&
51735179
AttributeMacros == R.AttributeMacros &&

clang/lib/Format/Format.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,8 @@ template <> struct MappingTraits<FormatStyle> {
975975
Style.AllowShortLambdasOnASingleLine);
976976
IO.mapOptional("AllowShortLoopsOnASingleLine",
977977
Style.AllowShortLoopsOnASingleLine);
978+
IO.mapOptional("AllowShortNamespacesOnASingleLine",
979+
Style.AllowShortNamespacesOnASingleLine);
978980
IO.mapOptional("AlwaysBreakAfterDefinitionReturnType",
979981
Style.AlwaysBreakAfterDefinitionReturnType);
980982
IO.mapOptional("AlwaysBreakBeforeMultilineStrings",
@@ -1480,6 +1482,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
14801482
LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
14811483
LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All;
14821484
LLVMStyle.AllowShortLoopsOnASingleLine = false;
1485+
LLVMStyle.AllowShortNamespacesOnASingleLine = false;
14831486
LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None;
14841487
LLVMStyle.AlwaysBreakBeforeMultilineStrings = false;
14851488
LLVMStyle.AttributeMacros.push_back("__capability");

clang/lib/Format/UnwrappedLineFormatter.cpp

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,18 @@ class LineJoiner {
361361
const auto *FirstNonComment = TheLine->getFirstNonComment();
362362
if (!FirstNonComment)
363363
return 0;
364+
364365
// FIXME: There are probably cases where we should use FirstNonComment
365366
// instead of TheLine->First.
366367

368+
if (Style.AllowShortNamespacesOnASingleLine &&
369+
TheLine->First->is(tok::kw_namespace) &&
370+
TheLine->Last->is(tok::l_brace)) {
371+
const auto result = tryMergeNamespace(I, E, Limit);
372+
if (result > 0)
373+
return result;
374+
}
375+
367376
if (Style.CompactNamespaces) {
368377
if (const auto *NSToken = TheLine->First->getNamespaceToken()) {
369378
int J = 1;
@@ -373,7 +382,7 @@ class LineJoiner {
373382
ClosingLineIndex == I[J]->MatchingClosingBlockLineIndex &&
374383
I[J]->Last->TotalLength < Limit;
375384
++J, --ClosingLineIndex) {
376-
Limit -= I[J]->Last->TotalLength;
385+
Limit -= I[J]->Last->TotalLength + 1;
377386

378387
// Reduce indent level for bodies of namespaces which were compacted,
379388
// but only if their content was indented in the first place.
@@ -420,6 +429,7 @@ class LineJoiner {
420429
TheLine->First != LastNonComment) {
421430
return MergeShortFunctions ? tryMergeSimpleBlock(I, E, Limit) : 0;
422431
}
432+
423433
// Try to merge a control statement block with left brace unwrapped.
424434
if (TheLine->Last->is(tok::l_brace) && FirstNonComment != TheLine->Last &&
425435
FirstNonComment->isOneOf(tok::kw_if, tok::kw_while, tok::kw_for,
@@ -616,6 +626,72 @@ class LineJoiner {
616626
return 1;
617627
}
618628

629+
unsigned tryMergeNamespace(SmallVectorImpl<AnnotatedLine *>::const_iterator I,
630+
SmallVectorImpl<AnnotatedLine *>::const_iterator E,
631+
unsigned Limit) {
632+
if (Limit == 0)
633+
return 0;
634+
635+
assert(I[1]);
636+
const auto &L1 = *I[1];
637+
if (L1.InPPDirective != (*I)->InPPDirective ||
638+
(L1.InPPDirective && L1.First->HasUnescapedNewline)) {
639+
return 0;
640+
}
641+
642+
if (std::distance(I, E) <= 2)
643+
return 0;
644+
645+
assert(I[2]);
646+
const auto &L2 = *I[2];
647+
if (L2.Type == LT_Invalid)
648+
return 0;
649+
650+
Limit = limitConsideringMacros(I + 1, E, Limit);
651+
652+
if (!nextTwoLinesFitInto(I, Limit))
653+
return 0;
654+
655+
// Check if it's a namespace inside a namespace, and call recursively if so.
656+
// '3' is the sizes of the whitespace and closing brace for " _inner_ }".
657+
if (L1.First->is(tok::kw_namespace)) {
658+
if (L1.Last->is(tok::comment) || !Style.CompactNamespaces)
659+
return 0;
660+
661+
assert(Limit >= L1.Last->TotalLength + 3);
662+
const auto InnerLimit = Limit - L1.Last->TotalLength - 3;
663+
const auto MergedLines = tryMergeNamespace(I + 1, E, InnerLimit);
664+
if (MergedLines == 0)
665+
return 0;
666+
const auto N = MergedLines + 2;
667+
// Check if there is even a line after the inner result.
668+
if (std::distance(I, E) <= N)
669+
return 0;
670+
// Check that the line after the inner result starts with a closing brace
671+
// which we are permitted to merge into one line.
672+
if (I[N]->First->is(tok::r_brace) && !I[N]->First->MustBreakBefore &&
673+
I[MergedLines + 1]->Last->isNot(tok::comment) &&
674+
nextNLinesFitInto(I, I + N + 1, Limit)) {
675+
return N;
676+
}
677+
return 0;
678+
}
679+
680+
// There's no inner namespace, so we are considering to merge at most one
681+
// line.
682+
683+
// The line which is in the namespace should end with semicolon.
684+
if (L1.Last->isNot(tok::semi))
685+
return 0;
686+
687+
// Last, check that the third line starts with a closing brace.
688+
if (L2.First->isNot(tok::r_brace) || L2.First->MustBreakBefore)
689+
return 0;
690+
691+
// If so, merge all three lines.
692+
return 2;
693+
}
694+
619695
unsigned tryMergeSimpleControlStatement(
620696
SmallVectorImpl<AnnotatedLine *>::const_iterator I,
621697
SmallVectorImpl<AnnotatedLine *>::const_iterator E, unsigned Limit) {
@@ -916,6 +992,21 @@ class LineJoiner {
916992
return 1 + I[1]->Last->TotalLength + 1 + I[2]->Last->TotalLength <= Limit;
917993
}
918994

995+
bool nextNLinesFitInto(SmallVectorImpl<AnnotatedLine *>::const_iterator I,
996+
SmallVectorImpl<AnnotatedLine *>::const_iterator E,
997+
unsigned Limit) {
998+
unsigned JoinedLength = 0;
999+
for (const auto *J = I + 1; J != E; ++J) {
1000+
if ((*J)->First->MustBreakBefore)
1001+
return false;
1002+
1003+
JoinedLength += 1 + (*J)->Last->TotalLength;
1004+
if (JoinedLength > Limit)
1005+
return false;
1006+
}
1007+
return true;
1008+
}
1009+
9191010
bool containsMustBreak(const AnnotatedLine *Line) {
9201011
assert(Line->First);
9211012
// Ignore the first token, because in this situation, it applies more to the

clang/unittests/Format/ConfigParseTest.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
159159
CHECK_PARSE_BOOL(AllowShortCompoundRequirementOnASingleLine);
160160
CHECK_PARSE_BOOL(AllowShortEnumsOnASingleLine);
161161
CHECK_PARSE_BOOL(AllowShortLoopsOnASingleLine);
162+
CHECK_PARSE_BOOL(AllowShortNamespacesOnASingleLine);
162163
CHECK_PARSE_BOOL(BinPackArguments);
163164
CHECK_PARSE_BOOL(BreakAdjacentStringLiterals);
164165
CHECK_PARSE_BOOL(BreakAfterJavaFieldAnnotations);

clang/unittests/Format/FormatTest.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4476,6 +4476,7 @@ TEST_F(FormatTest, FormatsCompactNamespaces) {
44764476
"int k; } // namespace out",
44774477
Style);
44784478

4479+
Style.ColumnLimit = 41;
44794480
verifyFormat("namespace A { namespace B { namespace C {\n"
44804481
"}}} // namespace A::B::C",
44814482
"namespace A { namespace B {\n"
@@ -4504,6 +4505,12 @@ TEST_F(FormatTest, FormatsCompactNamespaces) {
45044505
"} // namespace bbbbbb\n"
45054506
"} // namespace aaaaaa",
45064507
Style);
4508+
4509+
verifyFormat("namespace a { namespace b {\n"
4510+
"namespace c {\n"
4511+
"}}} // namespace a::b::c",
4512+
Style);
4513+
45074514
Style.ColumnLimit = 80;
45084515

45094516
// Extra semicolon after 'inner' closing brace prevents merging
@@ -28314,6 +28321,112 @@ TEST_F(FormatTest, KeepFormFeed) {
2831428321
Style);
2831528322
}
2831628323

28324+
TEST_F(FormatTest, ShortNamespacesOption) {
28325+
auto Style = getLLVMStyle();
28326+
Style.AllowShortNamespacesOnASingleLine = true;
28327+
Style.CompactNamespaces = true;
28328+
Style.FixNamespaceComments = false;
28329+
28330+
// Basic functionality.
28331+
verifyFormat("namespace foo { class bar; }", Style);
28332+
verifyFormat("namespace foo::bar { class baz; }", Style);
28333+
verifyFormat("namespace { class bar; }", Style);
28334+
verifyFormat("namespace foo {\n"
28335+
"class bar;\n"
28336+
"class baz;\n"
28337+
"}",
28338+
Style);
28339+
28340+
// Trailing comments prevent merging.
28341+
verifyFormat("namespace foo { namespace baz {\n"
28342+
"class qux;\n"
28343+
"} // comment\n"
28344+
"}",
28345+
Style);
28346+
28347+
// Make sure code doesn't walk too far on unbalanced code.
28348+
verifyFormat("namespace foo {", Style);
28349+
verifyFormat("namespace foo {\n"
28350+
"class baz;",
28351+
Style);
28352+
verifyFormat("namespace foo {\n"
28353+
"namespace bar { class baz; }",
28354+
Style);
28355+
28356+
// Nested namespaces.
28357+
verifyFormat("namespace foo { namespace bar { class baz; } }", Style);
28358+
28359+
// Without CompactNamespaces, we won't merge consecutive namespace
28360+
// declarations.
28361+
Style.CompactNamespaces = false;
28362+
verifyFormat("namespace foo {\n"
28363+
"namespace bar { class baz; }\n"
28364+
"}",
28365+
Style);
28366+
28367+
verifyFormat("namespace foo {\n"
28368+
"namespace bar { class baz; }\n"
28369+
"namespace qux { class quux; }\n"
28370+
"}",
28371+
Style);
28372+
28373+
Style.CompactNamespaces = true;
28374+
28375+
// Varying inner content.
28376+
verifyFormat("namespace foo {\n"
28377+
"int f() { return 5; }\n"
28378+
"}",
28379+
Style);
28380+
verifyFormat("namespace foo { template <T> struct bar; }", Style);
28381+
verifyFormat("namespace foo { constexpr int num = 42; }", Style);
28382+
28383+
// Validate nested namespace wrapping scenarios around the ColumnLimit.
28384+
Style.ColumnLimit = 64;
28385+
28386+
// Validate just under the ColumnLimit.
28387+
verifyFormat(
28388+
"namespace foo { namespace bar { namespace baz { class qux; } } }",
28389+
Style);
28390+
28391+
// Validate just over the ColumnLimit.
28392+
verifyFormat("namespace foo { namespace baar { namespace baaz {\n"
28393+
"class quux;\n"
28394+
"}}}",
28395+
Style);
28396+
28397+
verifyFormat(
28398+
"namespace foo { namespace bar { namespace baz { namespace qux {\n"
28399+
"class quux;\n"
28400+
"}}}}",
28401+
Style);
28402+
28403+
// Validate that the ColumnLimit logic accounts for trailing content as well.
28404+
verifyFormat("namespace foo { namespace bar { class qux; } } // extra",
28405+
Style);
28406+
28407+
verifyFormat("namespace foo { namespace bar { namespace baz {\n"
28408+
"class qux;\n"
28409+
"}}} // extra",
28410+
Style);
28411+
28412+
// FIXME: Ideally AllowShortNamespacesOnASingleLine would disable the trailing
28413+
// namespace comment from 'FixNamespaceComments', as it's not really necessary
28414+
// in this scenario, but the two options work at very different layers of the
28415+
// formatter, so I'm not sure how to make them interact.
28416+
//
28417+
// As it stands, the trailing comment will be added and likely make the line
28418+
// too long to fit within the ColumnLimit, reducing the how likely the line
28419+
// will still fit on a single line. The recommendation for now is to use the
28420+
// concatenated namespace syntax instead. e.g. 'namespace foo::bar'
28421+
Style.FixNamespaceComments = true;
28422+
verifyFormat(
28423+
"namespace foo { namespace bar { namespace baz {\n"
28424+
"class qux;\n"
28425+
"}}} // namespace foo::bar::baz",
28426+
"namespace foo { namespace bar { namespace baz { class qux; } } }",
28427+
Style);
28428+
}
28429+
2831728430
} // namespace
2831828431
} // namespace test
2831928432
} // namespace format

0 commit comments

Comments
 (0)