Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7fa85e1
[clang-format] Add option AllowShortRecordsOnASingleLine
itzexpoexpo Aug 20, 2025
83ef04b
Fixup: option order, inline MergeShortRecord lambda
itzexpoexpo Aug 20, 2025
ca1658a
Use consistently named enum values
itzexpoexpo Aug 24, 2025
af5c04e
Change option name to use singular form
itzexpoexpo Aug 24, 2025
d4387b0
Fix docs after rename
itzexpoexpo Aug 24, 2025
6e64add
Fixup documentation
itzexpoexpo Aug 24, 2025
8776bda
Fix SplitEmptyRecord handling, docs
itzexpoexpo Aug 26, 2025
2e915cf
Fix behavior of Never, add EmptyIfAttached
itzexpoexpo Aug 30, 2025
a1520fd
Fix incorrect handling of left brace wrapping
itzexpoexpo Sep 2, 2025
765c14d
Fixup test cases
itzexpoexpo Sep 3, 2025
dfd4df6
Fixup ShouldBreakBeforeBrace
itzexpoexpo Sep 3, 2025
31308ef
Fixups
itzexpoexpo Sep 4, 2025
4b302a3
Update release notes, fixup UnwrappedLineFormatter
itzexpoexpo Sep 5, 2025
6c03371
Fixup FormatStyle::operator==, misc UnwrappedLineFormatter
itzexpoexpo Sep 7, 2025
b57d58a
Fix interaction between AllowShortRecord and AllowShortBlocks options
itzexpoexpo Sep 7, 2025
31561c1
Fix incorrect merge check
itzexpoexpo Sep 8, 2025
8435780
Add const, change is to isNot
itzexpoexpo Sep 8, 2025
c60e4bb
Extract record merging into a separate function
itzexpoexpo Sep 15, 2025
1ee6503
Minor fixes for tryMergeRecord
itzexpoexpo Sep 15, 2025
018374c
Fix -Wswitch and -Wlogical-op-parentheses errors
itzexpoexpo Sep 18, 2025
5a9fd77
Fixup comments
itzexpoexpo Sep 19, 2025
48de2de
Fix missing empty block check
itzexpoexpo Sep 19, 2025
ccb4f31
Fix linux build failing
itzexpoexpo Sep 20, 2025
1cb4dcb
Comment fixups
itzexpoexpo Sep 21, 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 @@ -592,6 +592,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 @@ -5956,12 +5956,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
127 changes: 108 additions & 19 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,13 +509,9 @@ 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->isNoneOf(tok::kw_class, tok::kw_enum,
tok::kw_struct)) {
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
25 changes: 18 additions & 7 deletions clang/lib/Format/UnwrappedLineParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -948,20 +948,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 @@ -3200,8 +3206,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 @@ -3865,7 +3873,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 @@ -4160,8 +4169,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
Loading
Loading