Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions clang/docs/ClangFormatStyleOptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2096,6 +2096,42 @@ the configuration (without a prefix: ``Auto``).
**AllowShortNamespacesOnASingleLine** (``Boolean``) :versionbadge:`clang-format 20` :ref:`¶ <AllowShortNamespacesOnASingleLine>`
If ``true``, ``namespace a { class b; }`` can be put on a single line.

.. _AllowShortRecordOnASingleLine:

**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :versionbadge:`clang-format 22` :ref:`¶ <AllowShortRecordOnASingleLine>`
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:`¶ <AlwaysBreakAfterDefinitionReturnType>`
Expand Down
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ AST Matchers
clang-format
------------
- Add ``SpaceInEmptyBraces`` option and set it to ``Always`` for WebKit style.
- Add ``AllowShortRecordOnASingleLine`` option and set it to ``EmptyIfAttached``
for LLVM style.

libclang
--------
Expand Down
31 changes: 31 additions & 0 deletions clang/include/clang/Format/Format.h
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,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 {
Expand Down Expand Up @@ -5395,6 +5425,7 @@ struct FormatStyle {
AllowShortLoopsOnASingleLine == R.AllowShortLoopsOnASingleLine &&
AllowShortNamespacesOnASingleLine ==
R.AllowShortNamespacesOnASingleLine &&
AllowShortRecordOnASingleLine == R.AllowShortRecordOnASingleLine &&
AlwaysBreakBeforeMultilineStrings ==
R.AlwaysBreakBeforeMultilineStrings &&
AttributeMacros == R.AttributeMacros &&
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/Format/Format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,15 @@ template <> struct ScalarEnumerationTraits<FormatStyle::ShortLambdaStyle> {
}
};

template <> struct ScalarEnumerationTraits<FormatStyle::ShortRecordStyle> {
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<FormatStyle::SortIncludesOptions> {
static void enumInput(IO &IO, FormatStyle::SortIncludesOptions &Value) {
IO.enumCase(Value, "Never", FormatStyle::SortIncludesOptions({}));
Expand Down Expand Up @@ -1027,6 +1036,8 @@ template <> struct MappingTraits<FormatStyle> {
Style.AllowShortLoopsOnASingleLine);
IO.mapOptional("AllowShortNamespacesOnASingleLine",
Style.AllowShortNamespacesOnASingleLine);
IO.mapOptional("AllowShortRecordOnASingleLine",
Style.AllowShortRecordOnASingleLine);
IO.mapOptional("AlwaysBreakAfterDefinitionReturnType",
Style.AlwaysBreakAfterDefinitionReturnType);
IO.mapOptional("AlwaysBreakBeforeMultilineStrings",
Expand Down Expand Up @@ -1553,6 +1564,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All;
LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All;
LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
LLVMStyle.AllowShortLoopsOnASingleLine = false;
LLVMStyle.AllowShortNamespacesOnASingleLine = false;
LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None;
Expand Down
14 changes: 9 additions & 5 deletions clang/lib/Format/TokenAnnotator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5949,12 +5949,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 ((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.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never;
}
}

Expand Down
75 changes: 58 additions & 17 deletions clang/lib/Format/UnwrappedLineFormatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,15 +260,23 @@ class LineJoiner {
}
}

// Try merging record blocks that have had their left brace wrapped.
if (NextLine.First->isOneOf(TT_ClassLBrace, TT_StructLBrace,
TT_UnionLBrace) &&
NextLine.First == NextLine.Last && I + 2 != E &&
I[2]->First->isNot(tok::r_brace) &&
Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) {
if (unsigned MergedLines = tryMergeSimpleBlock(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.
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
Expand All @@ -278,9 +286,12 @@ 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)) {
return !Style.BraceWrapping.SplitEmptyRecord && EmptyBlock
return (EmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) ||
(!EmptyBlock && Style.AllowShortBlocksOnASingleLine ==
FormatStyle::SBS_Always)
? tryMergeSimpleBlock(I, E, Limit)
: 0;
}
Expand Down Expand Up @@ -485,20 +496,35 @@ class LineJoiner {
: 0;
}

auto TryMergeShortRecord = [&] {
switch (Style.AllowShortRecordOnASingleLine) {
case FormatStyle::SRS_Never:
return false;
case FormatStyle::SRS_EmptyIfAttached:
case FormatStyle::SRS_Empty:
return NextLine.First->is(tok::r_brace);
case FormatStyle::SRS_Always:
return true;
}
};

if (TheLine->Last->is(tok::l_brace)) {
bool ShouldMerge = false;
// Try to merge records.
if (TheLine->Last->is(TT_EnumLBrace)) {
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)) {
if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) {
// 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 ||
(!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord());
}
} else if (TheLine->InPPDirective ||
!TheLine->First->isOneOf(tok::kw_class, tok::kw_enum,
tok::kw_struct)) {
Expand Down Expand Up @@ -873,10 +899,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;
Expand Down Expand Up @@ -928,9 +961,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.
Expand Down
25 changes: 18 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,
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:
Expand Down Expand Up @@ -3197,8 +3203,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 ||
Expand Down Expand Up @@ -3862,7 +3870,8 @@ bool UnwrappedLineParser::parseEnum() {
}

if (!Style.AllowShortEnumsOnASingleLine &&
ShouldBreakBeforeBrace(Style, InitialToken)) {
ShouldBreakBeforeBrace(Style, InitialToken,
Tokens->peekNextToken()->is(tok::r_brace))) {
addUnwrappedLine();
}
// Parse enum body.
Expand Down Expand Up @@ -4157,8 +4166,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);
Expand Down
10 changes: 10 additions & 0 deletions clang/unittests/Format/ConfigParseTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,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);
Expand Down
Loading
Loading