diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index b746df5dab264..0f60cdb530b8b 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2103,6 +2103,42 @@ the configuration (without a prefix: ``Auto``). **AllowShortNamespacesOnASingleLine** (``Boolean``) :versionbadge:`clang-format 20` :ref:`¶ ` If ``true``, ``namespace a { class b; }`` can be put on a single line. +.. _AllowShortRecordOnASingleLine: + +**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :versionbadge:`clang-format 22` :ref:`¶ ` + Dependent on the value, ``struct bar { int i; };`` can be put on a single + line. + + Possible values: + + * ``SRS_Never`` (in configuration: ``Never``) + Never merge records into a single line. + + * ``SRS_EmptyIfAttached`` (in configuration: ``EmptyIfAttached``) + Only merge empty records if the opening brace was not wrapped, + i.e. the corresponding ``BraceWrapping.After...`` option was not set. + + * ``SRS_Empty`` (in configuration: ``Empty``) + Only merge empty records. + + .. code-block:: c++ + + struct foo {}; + struct bar + { + int i; + }; + + * ``SRS_Always`` (in configuration: ``Always``) + Merge all records that fit on a single line. + + .. code-block:: c++ + + struct foo {}; + struct bar { int i; }; + + + .. _AlwaysBreakAfterDefinitionReturnType: **AlwaysBreakAfterDefinitionReturnType** (``DefinitionReturnTypeBreakingStyle``) :versionbadge:`clang-format 3.7` :ref:`¶ ` diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 977396e249622..5b44f9aa3d370 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -544,6 +544,7 @@ clang-format literals. - Add ``Leave`` suboption to ``IndentPPDirectives``. - Add ``AllowBreakBeforeQtProperty`` option. +- Add ``AllowShortRecordOnASingleLine`` option and set it to ``EmptyIfAttached`` for LLVM style. libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 3df5b92654094..4d3f416975942 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -998,6 +998,36 @@ struct FormatStyle { /// \version 20 bool AllowShortNamespacesOnASingleLine; + /// Different styles for merging short records (``class``,``struct``, and + /// ``union``). + enum ShortRecordStyle : int8_t { + /// Never merge records into a single line. + SRS_Never, + /// Only merge empty records if the opening brace was not wrapped, + /// i.e. the corresponding ``BraceWrapping.After...`` option was not set. + SRS_EmptyIfAttached, + /// Only merge empty records. + /// \code + /// struct foo {}; + /// struct bar + /// { + /// int i; + /// }; + /// \endcode + SRS_Empty, + /// Merge all records that fit on a single line. + /// \code + /// struct foo {}; + /// struct bar { int i; }; + /// \endcode + SRS_Always + }; + + /// Dependent on the value, ``struct bar { int i; };`` can be put on a single + /// line. + /// \version 22 + ShortRecordStyle AllowShortRecordOnASingleLine; + /// Different ways to break after the function definition return type. /// This option is **deprecated** and is retained for backwards compatibility. enum DefinitionReturnTypeBreakingStyle : int8_t { @@ -5481,6 +5511,7 @@ struct FormatStyle { AllowShortLoopsOnASingleLine == R.AllowShortLoopsOnASingleLine && AllowShortNamespacesOnASingleLine == R.AllowShortNamespacesOnASingleLine && + AllowShortRecordOnASingleLine == R.AllowShortRecordOnASingleLine && AlwaysBreakBeforeMultilineStrings == R.AlwaysBreakBeforeMultilineStrings && AttributeMacros == R.AttributeMacros && diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 835071dbe715d..8eeb0ee51e413 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -681,6 +681,15 @@ template <> struct ScalarEnumerationTraits { } }; +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, FormatStyle::ShortRecordStyle &Value) { + IO.enumCase(Value, "Never", FormatStyle::SRS_Never); + IO.enumCase(Value, "EmptyIfAttached", FormatStyle::SRS_EmptyIfAttached); + IO.enumCase(Value, "Empty", FormatStyle::SRS_Empty); + IO.enumCase(Value, "Always", FormatStyle::SRS_Always); + } +}; + template <> struct MappingTraits { static void enumInput(IO &IO, FormatStyle::SortIncludesOptions &Value) { IO.enumCase(Value, "Never", FormatStyle::SortIncludesOptions({})); @@ -1050,6 +1059,8 @@ template <> struct MappingTraits { Style.AllowShortLoopsOnASingleLine); IO.mapOptional("AllowShortNamespacesOnASingleLine", Style.AllowShortNamespacesOnASingleLine); + IO.mapOptional("AllowShortRecordOnASingleLine", + Style.AllowShortRecordOnASingleLine); IO.mapOptional("AlwaysBreakAfterDefinitionReturnType", Style.AlwaysBreakAfterDefinitionReturnType); IO.mapOptional("AlwaysBreakBeforeMultilineStrings", @@ -1580,6 +1591,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All; LLVMStyle.AllowShortLoopsOnASingleLine = false; LLVMStyle.AllowShortNamespacesOnASingleLine = false; + LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None; LLVMStyle.AlwaysBreakBeforeMultilineStrings = false; LLVMStyle.AttributeMacros.push_back("__capability"); diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index 6a8286da73442..adc3115f66e35 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -5959,12 +5959,16 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line, return true; } - // Don't attempt to interpret struct return types as structs. + // Don't attempt to interpret record return types as records. + // FIXME: Not covered by tests. if (Right.isNot(TT_FunctionLBrace)) { - return (Line.startsWith(tok::kw_class) && - Style.BraceWrapping.AfterClass) || - (Line.startsWith(tok::kw_struct) && - Style.BraceWrapping.AfterStruct); + return Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never && + ((Line.startsWith(tok::kw_class) && + Style.BraceWrapping.AfterClass) || + (Line.startsWith(tok::kw_struct) && + Style.BraceWrapping.AfterStruct) || + (Line.startsWith(tok::kw_union) && + Style.BraceWrapping.AfterUnion)); } } diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index ac9d147defc13..9fb52455c4306 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -10,6 +10,7 @@ #include "FormatToken.h" #include "NamespaceEndCommentsFixer.h" #include "WhitespaceManager.h" +#include "clang/Basic/TokenKinds.h" #include "llvm/Support/Debug.h" #include @@ -266,15 +267,22 @@ class LineJoiner { } } + // Try merging record blocks that have had their left brace wrapped into + // a single line. + if (NextLine.First->isOneOf(TT_ClassLBrace, TT_StructLBrace, + TT_UnionLBrace)) { + if (unsigned MergedLines = tryMergeRecord(I, E, Limit)) + return MergedLines; + } + const auto *PreviousLine = I != AnnotatedLines.begin() ? I[-1] : nullptr; - // Handle empty record blocks where the brace has already been wrapped. + + // Handle blocks where the brace has already been wrapped. if (PreviousLine && TheLine->Last->is(tok::l_brace) && TheLine->First == TheLine->Last) { - bool EmptyBlock = NextLine.First->is(tok::r_brace); + const bool EmptyBlock = NextLine.First->is(tok::r_brace); - const FormatToken *Tok = PreviousLine->First; - if (Tok && Tok->is(tok::comment)) - Tok = Tok->getNextNonComment(); + const FormatToken *Tok = PreviousLine->getFirstNonComment(); if (Tok && Tok->getNamespaceToken()) { return !Style.BraceWrapping.SplitEmptyNamespace && EmptyBlock @@ -284,8 +292,11 @@ class LineJoiner { if (Tok && Tok->is(tok::kw_typedef)) Tok = Tok->getNextNonComment(); - if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union, - tok::kw_extern, Keywords.kw_interface)) { + + if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union)) + return tryMergeRecord(I, E, Limit); + + if (Tok && Tok->isOneOf(tok::kw_extern, Keywords.kw_interface)) { return !Style.BraceWrapping.SplitEmptyRecord && EmptyBlock ? tryMergeSimpleBlock(I, E, Limit) : 0; @@ -498,16 +509,12 @@ class LineJoiner { ShouldMerge = Style.AllowShortEnumsOnASingleLine; } else if (TheLine->Last->is(TT_CompoundRequirementLBrace)) { ShouldMerge = Style.AllowShortCompoundRequirementOnASingleLine; - } else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace)) { - // NOTE: We use AfterClass (whereas AfterStruct exists) for both classes - // and structs, but it seems that wrapping is still handled correctly - // elsewhere. - ShouldMerge = !Style.BraceWrapping.AfterClass || - (NextLine.First->is(tok::r_brace) && - !Style.BraceWrapping.SplitEmptyRecord); + } else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace, + TT_UnionLBrace)) { + return tryMergeRecord(I, E, Limit); } else if (TheLine->InPPDirective || !TheLine->First->isOneOf(tok::kw_class, tok::kw_enum, - tok::kw_struct)) { + tok::kw_struct, tok::kw_union)) { // Try to merge a block with left brace unwrapped that wasn't yet // covered. ShouldMerge = !Style.BraceWrapping.AfterFunction || @@ -574,6 +581,73 @@ class LineJoiner { return 0; } + unsigned tryMergeRecord(ArrayRef::const_iterator I, + ArrayRef::const_iterator E, + unsigned Limit) { + const auto *Line = I[0]; + const auto *NextLine = I[1]; + + // Current line begins both record and block, brace was not wrapped. + if (Line->Last->isOneOf(TT_StructLBrace, TT_ClassLBrace, TT_UnionLBrace)) { + auto ShouldWrapLBrace = [&](TokenType LBraceType) { + switch (LBraceType) { + case TT_StructLBrace: + return Style.BraceWrapping.AfterStruct; + case TT_ClassLBrace: + return Style.BraceWrapping.AfterClass; + case TT_UnionLBrace: + return Style.BraceWrapping.AfterUnion; + default: + return false; + }; + }; + + auto TryMergeShortRecord = [&] { + switch (Style.AllowShortRecordOnASingleLine) { + case FormatStyle::SRS_Never: + return false; + case FormatStyle::SRS_Always: + return true; + default: + return NextLine->First->is(tok::r_brace); + } + }; + + if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never && + (!ShouldWrapLBrace(Line->Last->getType()) || + (!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord()))) { + return tryMergeSimpleBlock(I, E, Limit); + } + } + + // Cases where the l_brace was wrapped. + // Current line begins record, next line block. + if (NextLine->First->isOneOf(TT_ClassLBrace, TT_StructLBrace, + TT_UnionLBrace)) { + if (I + 2 == E || I[2]->First->is(tok::r_brace) || + Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always) { + return 0; + } + + return tryMergeSimpleBlock(I, E, Limit); + } + + // Previous line begins record, current line block. + if (I != AnnotatedLines.begin() && + I[-1]->First->isOneOf(tok::kw_struct, tok::kw_class, tok::kw_union)) { + const bool IsEmptyBlock = + Line->Last->is(tok::l_brace) && NextLine->First->is(tok::r_brace); + + if ((IsEmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) || + (!IsEmptyBlock && + Style.AllowShortBlocksOnASingleLine == FormatStyle::SBS_Always)) { + return tryMergeSimpleBlock(I, E, Limit); + } + } + + return 0; + } + unsigned tryMergeSimplePPDirective(ArrayRef::const_iterator I, ArrayRef::const_iterator E, @@ -879,10 +953,17 @@ class LineJoiner { return 1; } else if (Limit != 0 && !Line.startsWithNamespace() && !startsExternCBlock(Line)) { - // We don't merge short records. - if (isRecordLBrace(*Line.Last)) + // Merge short records only when requested. + if (Line.Last->isOneOf(TT_EnumLBrace, TT_RecordLBrace)) return 0; + if (Line.Last->isOneOf(TT_ClassLBrace, TT_StructLBrace, + TT_UnionLBrace) && + Line.Last != Line.First && + Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always) { + return 0; + } + // Check that we still have three lines and they fit into the limit. if (I + 2 == E || I[2]->Type == LT_Invalid) return 0; @@ -934,9 +1015,17 @@ class LineJoiner { return 0; Limit -= 2; unsigned MergedLines = 0; - if (Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never || - (I[1]->First == I[1]->Last && I + 2 != E && - I[2]->First->is(tok::r_brace))) { + + auto TryMergeBlock = [&] { + if (Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never || + Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) { + return true; + } + return I[1]->First == I[1]->Last && I + 2 != E && + I[2]->First->is(tok::r_brace); + }; + + if (TryMergeBlock()) { MergedLines = tryMergeSimpleBlock(I + 1, E, Limit); // If we managed to merge the block, count the statement header, which // is on a separate line. diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 2c9766c9b7bc0..0f0023c243bd2 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -948,20 +948,26 @@ static bool isIIFE(const UnwrappedLine &Line, } static bool ShouldBreakBeforeBrace(const FormatStyle &Style, - const FormatToken &InitialToken) { + const FormatToken &InitialToken, + bool IsEmptyBlock) { tok::TokenKind Kind = InitialToken.Tok.getKind(); if (InitialToken.is(TT_NamespaceMacro)) Kind = tok::kw_namespace; + const bool WrapRecordAllowed = + !IsEmptyBlock || + Style.AllowShortRecordOnASingleLine < FormatStyle::SRS_Empty || + Style.BraceWrapping.SplitEmptyRecord; + switch (Kind) { case tok::kw_namespace: return Style.BraceWrapping.AfterNamespace; case tok::kw_class: - return Style.BraceWrapping.AfterClass; + return Style.BraceWrapping.AfterClass && WrapRecordAllowed; case tok::kw_union: - return Style.BraceWrapping.AfterUnion; + return Style.BraceWrapping.AfterUnion && WrapRecordAllowed; case tok::kw_struct: - return Style.BraceWrapping.AfterStruct; + return Style.BraceWrapping.AfterStruct && WrapRecordAllowed; case tok::kw_enum: return Style.BraceWrapping.AfterEnum; default: @@ -3193,8 +3199,10 @@ void UnwrappedLineParser::parseNamespace() { if (FormatTok->is(tok::l_brace)) { FormatTok->setFinalizedType(TT_NamespaceLBrace); - if (ShouldBreakBeforeBrace(Style, InitialToken)) + if (ShouldBreakBeforeBrace(Style, InitialToken, + Tokens->peekNextToken()->is(tok::r_brace))) { addUnwrappedLine(); + } unsigned AddLevels = Style.NamespaceIndentation == FormatStyle::NI_All || @@ -3858,7 +3866,8 @@ bool UnwrappedLineParser::parseEnum() { } if (!Style.AllowShortEnumsOnASingleLine && - ShouldBreakBeforeBrace(Style, InitialToken)) { + ShouldBreakBeforeBrace(Style, InitialToken, + Tokens->peekNextToken()->is(tok::r_brace))) { addUnwrappedLine(); } // Parse enum body. @@ -4153,8 +4162,10 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) { if (ParseAsExpr) { parseChildBlock(); } else { - if (ShouldBreakBeforeBrace(Style, InitialToken)) + if (ShouldBreakBeforeBrace(Style, InitialToken, + Tokens->peekNextToken()->is(tok::r_brace))) { addUnwrappedLine(); + } unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u; parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false); diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 6111e86ff7076..76d0e6762fc2e 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -655,6 +655,16 @@ TEST(ConfigParseTest, ParsesConfiguration) { CHECK_PARSE("AllowShortLambdasOnASingleLine: true", AllowShortLambdasOnASingleLine, FormatStyle::SLS_All); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; + CHECK_PARSE("AllowShortRecordOnASingleLine: Never", + AllowShortRecordOnASingleLine, FormatStyle::SRS_Never); + CHECK_PARSE("AllowShortRecordOnASingleLine: EmptyIfAttached", + AllowShortRecordOnASingleLine, FormatStyle::SRS_EmptyIfAttached); + CHECK_PARSE("AllowShortRecordOnASingleLine: Empty", + AllowShortRecordOnASingleLine, FormatStyle::SRS_Empty); + CHECK_PARSE("AllowShortRecordOnASingleLine: Always", + AllowShortRecordOnASingleLine, FormatStyle::SRS_Always); + Style.SpaceAroundPointerQualifiers = FormatStyle::SAPQ_Both; CHECK_PARSE("SpaceAroundPointerQualifiers: Default", SpaceAroundPointerQualifiers, FormatStyle::SAPQ_Default); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 6a3385a56f53e..69fcb8db7f906 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15334,6 +15334,89 @@ TEST_F(FormatTest, NeverMergeShortRecords) { Style); } +TEST_F(FormatTest, AllowShortRecordOnASingleLine) { + auto Style = getLLVMStyle(); + EXPECT_EQ(Style.AllowShortRecordOnASingleLine, + FormatStyle::SRS_EmptyIfAttached); + + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; + verifyFormat("class foo {\n" + "};\n" + "class bar {\n" + " int i;\n" + "};", + Style); + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.AfterClass = true; + verifyFormat("class foo\n" + "{\n" + "};\n" + "class bar\n" + "{\n" + " int i;\n" + "};", + Style); + Style.BraceWrapping.SplitEmptyRecord = false; + verifyFormat("class foo\n" + "{};", + Style); + + Style = getLLVMStyle(); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; + verifyFormat("class foo {};\n" + "class bar {\n" + " int i;\n" + "};", + Style); + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.AfterClass = true; + verifyFormat("class foo\n" + "{\n" + "};\n" + "class bar\n" + "{\n" + " int i;\n" + "};", + Style); + Style.BraceWrapping.SplitEmptyRecord = false; + verifyFormat("class foo {};", Style); + + Style = getLLVMStyle(); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; + verifyFormat("class foo {};\n" + "class bar { int i; };", + Style); + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.AfterClass = true; + verifyFormat("class foo\n" + "{\n" + "};\n" + "class bar { int i; };", + Style); + Style.BraceWrapping.SplitEmptyRecord = false; + verifyFormat("class foo {};", Style); + + Style = getLLVMStyle(); + Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Always; + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.AfterClass = true; + + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; + verifyFormat("class foo\n" + "{ int i; };", + Style); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; + verifyFormat("class foo\n" + "{ int i; };", + Style); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; + verifyFormat("class foo\n" + "{\n" + "};\n" + "class foo { int i; };", + Style); +} + TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) { // Elaborate type variable declarations. verifyFormat("struct foo a = {bar};\nint n;");