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
34 changes: 33 additions & 1 deletion clang/docs/ClangFormatStyleOptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2555,7 +2555,8 @@ the configuration (without a prefix: ``Auto``).
{} {
}

* ``bool SplitEmptyRecord`` If ``false``, empty record (e.g. class, struct or union) body
* ``bool SplitEmptyRecord`` This option is **deprecated**. See `WrapEmptyRecord`.
If ``false``, empty record (e.g. class, struct or union) body
can be put on a single line. This option is used only if the opening
brace of the record has already been wrapped, i.e. the ``AfterClass``
(for classes) brace wrapping mode is set.
Expand All @@ -2579,6 +2580,37 @@ the configuration (without a prefix: ``Auto``).
{} {
}

* ``BraceWrapEmptyRecordStyle WrapEmptyRecord``
Wrap empty record (``class``/``struct``/``union``).

Possible values:

* ``BWER_Default`` (in configuration: ``Default``)
Use default wrapping rules for empty records
(``AfterClass``,``AfterStruct``,``AfterUnion``).

.. code-block:: c++

class foo
{
};

* ``BWER_BeforeBrace`` (in configuration: ``BeforeBrace``)
Only wrap before brace.

.. code-block:: c++

class foo
{};

* ``BWER_Never`` (in configuration: ``Never``)
Wrap neither before nor after the brace.

.. code-block:: c++

class foo {};



.. _BracedInitializerIndentWidth:

Expand Down
2 changes: 1 addition & 1 deletion clang/docs/tools/dump_format_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ class State:
else:
state = State.InNestedStruct
field_type, field_name = re.match(
r"([<>:\w(,\s)]+)\s+(\w+);", line
r"(?:// )?([<>:\w(,\s)]+)\s+(\w+);", line
).groups()
# if not version:
# self.__warning(f"missing version for {field_name}", line)
Expand Down
30 changes: 28 additions & 2 deletions clang/include/clang/Format/Format.h
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,29 @@ struct FormatStyle {
BWACS_Always
};

/// Override wrapping of empty records.
enum BraceWrapEmptyRecordStyle : int8_t {
/// Use default wrapping rules for empty records
/// (``AfterClass``,``AfterStruct``,``AfterUnion``).
/// \code
/// class foo
/// {
/// };
/// \endcode
BWER_Default,
/// Only wrap before brace.
/// \code
/// class foo
/// {};
/// \endcode
BWER_BeforeBrace,
/// Wrap neither before nor after the brace.
/// \code
/// class foo {};
/// \endcode
BWER_Never
};

/// Precise control over the wrapping of braces.
/// \code
/// # Should be declared this way:
Expand Down Expand Up @@ -1561,6 +1584,7 @@ struct FormatStyle {
/// \endcode
///
bool SplitEmptyFunction;
/// This option is **deprecated**. See `WrapEmptyRecord`.
/// If ``false``, empty record (e.g. class, struct or union) body
/// can be put on a single line. This option is used only if the opening
/// brace of the record has already been wrapped, i.e. the ``AfterClass``
Expand All @@ -1571,8 +1595,8 @@ struct FormatStyle {
/// {} {
/// }
/// \endcode
///
bool SplitEmptyRecord;
// bool SplitEmptyRecord;

/// If ``false``, empty namespace body can be put on a single line.
/// This option is used only if the opening brace of the namespace has
/// already been wrapped, i.e. the ``AfterNamespace`` brace wrapping mode is
Expand All @@ -1585,6 +1609,8 @@ struct FormatStyle {
/// \endcode
///
bool SplitEmptyNamespace;
/// Wrap empty record (``class``/``struct``/``union``).
BraceWrapEmptyRecordStyle WrapEmptyRecord;
};

/// Control of individual brace wrapping cases.
Expand Down
34 changes: 26 additions & 8 deletions clang/lib/Format/Format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ template <> struct ScalarEnumerationTraits<FormatStyle::BraceBreakingStyle> {

template <> struct MappingTraits<FormatStyle::BraceWrappingFlags> {
static void mapping(IO &IO, FormatStyle::BraceWrappingFlags &Wrapping) {
// For backward compatibility.
if (!IO.outputting())
IO.mapOptional("SplitEmptyRecord", Wrapping.WrapEmptyRecord);

IO.mapOptional("AfterCaseLabel", Wrapping.AfterCaseLabel);
IO.mapOptional("AfterClass", Wrapping.AfterClass);
IO.mapOptional("AfterControlStatement", Wrapping.AfterControlStatement);
Expand All @@ -198,8 +202,8 @@ template <> struct MappingTraits<FormatStyle::BraceWrappingFlags> {
IO.mapOptional("BeforeWhile", Wrapping.BeforeWhile);
IO.mapOptional("IndentBraces", Wrapping.IndentBraces);
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 +236,20 @@ 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, "BeforeBrace", FormatStyle::BWER_BeforeBrace);
IO.enumCase(Value, "Never", FormatStyle::BWER_Never);

// For backward compatibility.
IO.enumCase(Value, "false", FormatStyle::BWER_BeforeBrace);
IO.enumCase(Value, "true", FormatStyle::BWER_Default);
}
};

template <>
struct ScalarEnumerationTraits<
FormatStyle::BreakBeforeConceptDeclarationsStyle> {
Expand Down Expand Up @@ -1391,8 +1409,8 @@ static void expandPresetsBraceWrapping(FormatStyle &Expanded) {
/*BeforeWhile=*/false,
/*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 All @@ -1407,7 +1425,7 @@ static void expandPresetsBraceWrapping(FormatStyle &Expanded) {
Expanded.BraceWrapping.AfterUnion = true;
Expanded.BraceWrapping.AfterExternBlock = true;
Expanded.BraceWrapping.SplitEmptyFunction = true;
Expanded.BraceWrapping.SplitEmptyRecord = false;
Expanded.BraceWrapping.WrapEmptyRecord = FormatStyle::BWER_BeforeBrace;
break;
case FormatStyle::BS_Stroustrup:
Expanded.BraceWrapping.AfterFunction = true;
Expand Down Expand Up @@ -1461,8 +1479,8 @@ static void expandPresetsBraceWrapping(FormatStyle &Expanded) {
/*BeforeWhile=*/true,
/*IndentBraces=*/true,
/*SplitEmptyFunction=*/true,
/*SplitEmptyRecord=*/true,
/*SplitEmptyNamespace=*/true};
/*SplitEmptyNamespace=*/true,
/*WrapEmptyRecord=*/FormatStyle::BWER_Default};
break;
case FormatStyle::BS_WebKit:
Expanded.BraceWrapping.AfterFunction = true;
Expand Down Expand Up @@ -1561,8 +1579,8 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
/*BeforeWhile=*/false,
/*IndentBraces=*/false,
/*SplitEmptyFunction=*/true,
/*SplitEmptyRecord=*/true,
/*SplitEmptyNamespace=*/true};
/*SplitEmptyNamespace=*/true,
/*WrapEmptyRecord=*/FormatStyle::BWER_Default};
LLVMStyle.BreakAdjacentStringLiterals = true;
LLVMStyle.BreakAfterAttributes = FormatStyle::ABS_Leave;
LLVMStyle.BreakAfterJavaFieldAnnotations = false;
Expand Down
13 changes: 8 additions & 5 deletions clang/lib/Format/TokenAnnotator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5933,12 +5933,15 @@ 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.
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) ||
(Line.startsWith(tok::kw_union) &&
Style.BraceWrapping.AfterUnion)) &&
Style.BraceWrapping.WrapEmptyRecord == FormatStyle::BWER_Default;
}
}

Expand Down
21 changes: 13 additions & 8 deletions clang/lib/Format/UnwrappedLineFormatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,16 @@ class LineJoiner {
Tok = Tok->getNextNonComment();
if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union,
tok::kw_extern, Keywords.kw_interface)) {
return !Style.BraceWrapping.SplitEmptyRecord && EmptyBlock
return Style.BraceWrapping.WrapEmptyRecord !=
FormatStyle::BWER_Default &&
EmptyBlock
? tryMergeSimpleBlock(I, E, Limit)
: 0;
}

if (Tok && Tok->is(tok::kw_template) &&
Style.BraceWrapping.SplitEmptyRecord && EmptyBlock) {
Style.BraceWrapping.WrapEmptyRecord == FormatStyle::BWER_Default &&
EmptyBlock) {
return 0;
}
}
Expand Down Expand Up @@ -453,9 +456,10 @@ class LineJoiner {
}
}

// Don't merge an empty template class or struct if SplitEmptyRecords
// is defined.
if (PreviousLine && Style.BraceWrapping.SplitEmptyRecord &&
// Merge an empty class or struct only if WrapEmptyRecord
// is not set to Default.
if (PreviousLine &&
Style.BraceWrapping.WrapEmptyRecord == FormatStyle::BWER_Default &&
TheLine->Last->is(tok::l_brace) && PreviousLine->Last) {
const FormatToken *Previous = PreviousLine->Last;
if (Previous) {
Expand Down Expand Up @@ -493,9 +497,10 @@ class LineJoiner {
// 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);
ShouldMerge =
!Style.BraceWrapping.AfterClass ||
(NextLine.First->is(tok::r_brace) &&
Style.BraceWrapping.WrapEmptyRecord != FormatStyle::BWER_Default);
} else if (TheLine->InPPDirective ||
!TheLine->First->isOneOf(tok::kw_class, tok::kw_enum,
tok::kw_struct)) {
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
19 changes: 18 additions & 1 deletion clang/unittests/Format/ConfigParseTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
CHECK_PARSE_NESTED_BOOL(BraceWrapping, BeforeWhile);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, IndentBraces);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, SplitEmptyFunction);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, SplitEmptyRecord);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, SplitEmptyNamespace);
CHECK_PARSE_NESTED_BOOL(KeepEmptyLines, AtEndOfFile);
CHECK_PARSE_NESTED_BOOL(KeepEmptyLines, AtStartOfBlock);
Expand Down Expand Up @@ -759,6 +758,24 @@ TEST(ConfigParseTest, ParsesConfiguration) {
" AfterControlStatement: false",
BraceWrapping.AfterControlStatement, FormatStyle::BWACS_Never);

Style.BraceWrapping.WrapEmptyRecord = FormatStyle::BWER_Default;
CHECK_PARSE("BraceWrapping:\n"
" WrapEmptyRecord: BeforeBrace",
BraceWrapping.WrapEmptyRecord, FormatStyle::BWER_BeforeBrace);
CHECK_PARSE("BraceWrapping:\n"
" WrapEmptyRecord: Default",
BraceWrapping.WrapEmptyRecord, FormatStyle::BWER_Default);
CHECK_PARSE("BraceWrapping:\n"
" WrapEmptyRecord: Never",
BraceWrapping.WrapEmptyRecord, FormatStyle::BWER_Never);
// For backward compatibility:
CHECK_PARSE("BraceWrapping:\n"
" WrapEmptyRecord: true",
BraceWrapping.WrapEmptyRecord, FormatStyle::BWER_Default);
CHECK_PARSE("BraceWrapping:\n"
" WrapEmptyRecord: false",
BraceWrapping.WrapEmptyRecord, FormatStyle::BWER_BeforeBrace);

Style.BreakAfterReturnType = FormatStyle::RTBS_All;
CHECK_PARSE("BreakAfterReturnType: None", BreakAfterReturnType,
FormatStyle::RTBS_None);
Expand Down
Loading
Loading