Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
58f7f5e
[clang-format] Add option AllowShortRecordsOnASingleLine
itzexpoexpo Aug 20, 2025
999b2fa
Fixup: option order, inline MergeShortRecord lambda
itzexpoexpo Aug 20, 2025
eec3dfa
Use consistently named enum values
itzexpoexpo Aug 24, 2025
6707088
Change option name to use singular form
itzexpoexpo Aug 24, 2025
0792716
Fix docs after rename
itzexpoexpo Aug 24, 2025
b3d6b3b
Fixup documentation
itzexpoexpo Aug 24, 2025
9284180
Fix SplitEmptyRecord handling, docs
itzexpoexpo Aug 26, 2025
42b1918
Fix behavior of Never, add EmptyIfAttached
itzexpoexpo Aug 30, 2025
4a11e4d
Fix incorrect handling of left brace wrapping
itzexpoexpo Sep 2, 2025
6f11ac2
Fixup test cases
itzexpoexpo Sep 3, 2025
e1062d7
Fixup ShouldBreakBeforeBrace
itzexpoexpo Sep 3, 2025
7604f55
Fixups
itzexpoexpo Sep 4, 2025
03ca006
Update release notes, fixup UnwrappedLineFormatter
itzexpoexpo Sep 5, 2025
0fa29c1
Fixup FormatStyle::operator==, misc UnwrappedLineFormatter
itzexpoexpo Sep 7, 2025
86073c9
Fix interaction between AllowShortRecord and AllowShortBlocks options
itzexpoexpo Sep 7, 2025
d35768a
Fix incorrect merge check
itzexpoexpo Sep 8, 2025
5892208
Add const, change is to isNot
itzexpoexpo Sep 8, 2025
73cdd98
Extract record merging into a separate function
itzexpoexpo Sep 15, 2025
b2b5f91
Minor fixes for tryMergeRecord
itzexpoexpo Sep 15, 2025
1b4f84f
Fix -Wswitch and -Wlogical-op-parentheses errors
itzexpoexpo Sep 18, 2025
1346788
Fixup comments
itzexpoexpo Sep 19, 2025
f464367
Fix missing empty block check
itzexpoexpo Sep 19, 2025
a75325b
Fix linux build failing
itzexpoexpo Sep 20, 2025
0e5f589
Comment fixups
itzexpoexpo Sep 21, 2025
4715615
Fix ReleaseNotes conflict
itzexpoexpo Sep 27, 2025
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 @@ -2103,6 +2103,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
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------
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 @@ -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 {
Expand Down Expand Up @@ -5481,6 +5511,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 @@ -681,6 +681,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 @@ -1050,6 +1059,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 @@ -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");
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 @@ -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));
}
}

Expand Down
129 changes: 109 additions & 20 deletions clang/lib/Format/UnwrappedLineFormatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "FormatToken.h"
#include "NamespaceEndCommentsFixer.h"
#include "WhitespaceManager.h"
#include "clang/Basic/TokenKinds.h"
#include "llvm/Support/Debug.h"
#include <queue>

Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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 ||
Expand Down Expand Up @@ -574,6 +581,73 @@ class LineJoiner {
return 0;
}

unsigned tryMergeRecord(ArrayRef<AnnotatedLine *>::const_iterator I,
ArrayRef<AnnotatedLine *>::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<AnnotatedLine *>::const_iterator I,
ArrayRef<AnnotatedLine *>::const_iterator E,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
Loading
Loading