https://github.com/itzexpoexpo updated https://github.com/llvm/llvm-project/pull/151970
From 2c26e66e16bf2c42019e8a5bce421f106fcf978d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Mon, 4 Aug 2025 15:19:53 +0200 Subject: [PATCH 1/2] [clang-format] Add option to omit wrapping for empty records Currently, clang-format does not allow empty records to be formatted on a single line if the corresponding `BraceWrapping.After*` option is set to true. This results in unnecessarily wrapped code: struct foo { int i; }; struct bar { }; This patch adds the `BraceWrapping.WrapEmptyRecord` option, which allows `class`, `struct`, and `union` declarations with empty bodies to be formatted as one-liners, even when `AfterRecord: true`. As such, the following becomes possible: struct foo { int i; }; struct bar {}; --- clang/include/clang/Format/Format.h | 28 ++++++++++++++++++++++ clang/lib/Format/Format.cpp | 13 +++++++++- clang/lib/Format/TokenAnnotator.cpp | 9 +++---- clang/lib/Format/UnwrappedLineParser.cpp | 20 ++++++++++------ clang/unittests/Format/FormatTest.cpp | 30 ++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 12 deletions(-) diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 31582a40de866..cc79dcb7b53ec 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -1355,6 +1355,32 @@ struct FormatStyle { BWACS_Always }; + enum BraceWrapEmptyRecordStyle : int8_t { + /// Use default wrapping rules for records + /// (AfterClass,AfterStruct,AfterUnion) + /// \code + /// class foo + /// { + /// int foo; + /// }; + /// + /// class foo + /// { + /// }; + /// \endcode + BWER_Default, + /// Override wrapping for empty records + /// \code + /// class foo + /// { + /// int foo; + /// }; + /// + /// class foo {}; + /// \endcode + BWER_Never + }; + /// Precise control over the wrapping of braces. /// \code /// # Should be declared this way: @@ -1585,6 +1611,8 @@ struct FormatStyle { /// \endcode /// bool SplitEmptyNamespace; + /// Wrap empty record (``class``/``struct``/``union``). + BraceWrapEmptyRecordStyle WrapEmptyRecord; }; /// Control of individual brace wrapping cases. diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 063780721423f..0d72410f00c27 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -200,6 +200,7 @@ template <> struct MappingTraits<FormatStyle::BraceWrappingFlags> { IO.mapOptional("SplitEmptyFunction", Wrapping.SplitEmptyFunction); IO.mapOptional("SplitEmptyRecord", Wrapping.SplitEmptyRecord); IO.mapOptional("SplitEmptyNamespace", Wrapping.SplitEmptyNamespace); + IO.mapOptional("WrapEmptyRecord", Wrapping.WrapEmptyRecord); } }; @@ -232,6 +233,15 @@ struct ScalarEnumerationTraits< } }; +template <> +struct ScalarEnumerationTraits<FormatStyle::BraceWrapEmptyRecordStyle> { + static void enumeration(IO &IO, + FormatStyle::BraceWrapEmptyRecordStyle &Value) { + IO.enumCase(Value, "Default", FormatStyle::BWER_Default); + IO.enumCase(Value, "Never", FormatStyle::BWER_Never); + } +}; + template <> struct ScalarEnumerationTraits< FormatStyle::BreakBeforeConceptDeclarationsStyle> { @@ -1392,7 +1402,8 @@ static void expandPresetsBraceWrapping(FormatStyle &Expanded) { /*IndentBraces=*/false, /*SplitEmptyFunction=*/true, /*SplitEmptyRecord=*/true, - /*SplitEmptyNamespace=*/true}; + /*SplitEmptyNamespace=*/true, + /*WrapEmptyRecord=*/FormatStyle::BWER_Default}; switch (Expanded.BreakBeforeBraces) { case FormatStyle::BS_Linux: Expanded.BraceWrapping.AfterClass = true; diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index 4801d27b1395a..22132f6d2fd3b 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -5935,10 +5935,11 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line, // Don't attempt to interpret struct return types as structs. if (Right.isNot(TT_FunctionLBrace)) { - return (Line.startsWith(tok::kw_class) && - Style.BraceWrapping.AfterClass) || - (Line.startsWith(tok::kw_struct) && - Style.BraceWrapping.AfterStruct); + return ((Line.startsWith(tok::kw_class) && + Style.BraceWrapping.AfterClass) || + (Line.startsWith(tok::kw_struct) && + Style.BraceWrapping.AfterStruct)) && + Style.BraceWrapping.WrapEmptyRecord == FormatStyle::BWER_Default; } } diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 91b8fdc8a3c38..e3efb0804d988 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -952,20 +952,26 @@ static bool isIIFE(const UnwrappedLine &Line, } static bool ShouldBreakBeforeBrace(const FormatStyle &Style, - const FormatToken &InitialToken) { + const FormatToken &InitialToken, + const FormatToken &NextToken) { tok::TokenKind Kind = InitialToken.Tok.getKind(); if (InitialToken.is(TT_NamespaceMacro)) Kind = tok::kw_namespace; + bool IsEmptyBlock = NextToken.is(tok::r_brace); + bool WrapRecordAllowed = + !(IsEmptyBlock && + Style.BraceWrapping.WrapEmptyRecord == FormatStyle::BWER_Never); + 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: @@ -3191,7 +3197,7 @@ void UnwrappedLineParser::parseNamespace() { if (FormatTok->is(tok::l_brace)) { FormatTok->setFinalizedType(TT_NamespaceLBrace); - if (ShouldBreakBeforeBrace(Style, InitialToken)) + if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) addUnwrappedLine(); unsigned AddLevels = @@ -3856,7 +3862,7 @@ bool UnwrappedLineParser::parseEnum() { } if (!Style.AllowShortEnumsOnASingleLine && - ShouldBreakBeforeBrace(Style, InitialToken)) { + ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) { addUnwrappedLine(); } // Parse enum body. @@ -4151,7 +4157,7 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) { if (ParseAsExpr) { parseChildBlock(); } else { - if (ShouldBreakBeforeBrace(Style, InitialToken)) + if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) addUnwrappedLine(); unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u; diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 96cc650f52a5d..3a5233674bd0f 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15615,6 +15615,36 @@ TEST_F(FormatTest, NeverMergeShortRecords) { Style); } +TEST_F(FormatTest, WrapEmptyRecords) { + FormatStyle Style = getLLVMStyle(); + + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.AfterStruct = true; + Style.BraceWrapping.AfterClass = true; + Style.BraceWrapping.AfterUnion = true; + Style.BraceWrapping.SplitEmptyRecord = false; + + verifyFormat("class foo\n{\n void bar();\n};", Style); + verifyFormat("class foo\n{};", Style); + + verifyFormat("struct foo\n{\n int bar;\n};", Style); + verifyFormat("struct foo\n{};", Style); + + verifyFormat("union foo\n{\n int bar;\n};", Style); + verifyFormat("union foo\n{};", Style); + + Style.BraceWrapping.WrapEmptyRecord = FormatStyle::BWER_Never; + + verifyFormat("class foo\n{\n void bar();\n};", Style); + verifyFormat("class foo {};", Style); + + verifyFormat("struct foo\n{\n int bar;\n};", Style); + verifyFormat("struct foo {};", Style); + + verifyFormat("union foo\n{\n int bar;\n};", Style); + verifyFormat("union foo {};", Style); +} + TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) { // Elaborate type variable declarations. verifyFormat("struct foo a = {bar};\nint n;"); From ce10b36f779b54b07cd90dd4023c949166380327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Mon, 4 Aug 2025 16:11:48 +0200 Subject: [PATCH 2/2] Fix missing style option docs --- clang/docs/ClangFormatStyleOptions.rst | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 02986a94a656c..f3ee029b6c2bf 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2579,6 +2579,36 @@ the configuration (without a prefix: ``Auto``). {} { } + * ``BraceWrapEmptyRecordStyle WrapEmptyRecord`` + Wrap empty record (``class``/``struct``/``union``). + + Possible values: + + * ``BWËR_Never`` (in configuration: ``Never``) + Never wrap braces of empty records. + + .. code-block:: c++ + + class foo + { + int foo; + }; + + class foo{}; + + * ``BWER_Default`` (in configuration: ``MultiLine``) + Use default wrapping rules for records. (``AfterClass``, ``AfterStruct``, ``AfterUnion``) + + .. code-block:: c++ + + class foo + { + int foo; + }; + + class foo + { + }; .. _BracedInitializerIndentWidth: _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits