Skip to content

[clang-format] Add option to omit wrapping for empty records #151970

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
30 changes: 30 additions & 0 deletions clang/docs/ClangFormatStyleOptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
28 changes: 28 additions & 0 deletions clang/include/clang/Format/Format.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -1585,6 +1611,8 @@ struct FormatStyle {
/// \endcode
///
bool SplitEmptyNamespace;
/// Wrap empty record (``class``/``struct``/``union``).
BraceWrapEmptyRecordStyle WrapEmptyRecord;
};

/// Control of individual brace wrapping cases.
Expand Down
13 changes: 12 additions & 1 deletion clang/lib/Format/Format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};

Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 5 additions & 4 deletions clang/lib/Format/TokenAnnotator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
20 changes: 13 additions & 7 deletions clang/lib/Format/UnwrappedLineParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -3856,7 +3862,7 @@ bool UnwrappedLineParser::parseEnum() {
}

if (!Style.AllowShortEnumsOnASingleLine &&
ShouldBreakBeforeBrace(Style, InitialToken)) {
ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) {
addUnwrappedLine();
}
// Parse enum body.
Expand Down Expand Up @@ -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;
Expand Down
30 changes: 30 additions & 0 deletions clang/unittests/Format/FormatTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;");
Expand Down
Loading