[clang] [clang-format] Keep compound literals stable in macro bodies (PR #173771)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Jan 10 20:02:43 PST 2026
https://github.com/owenca updated https://github.com/llvm/llvm-project/pull/173771
>From 8dce6c2109e79f002e4becd9276e34f1de51d47e Mon Sep 17 00:00:00 2001
From: Lane0218 <laneljc at qq.com>
Date: Sun, 28 Dec 2025 21:01:23 +0800
Subject: [PATCH 1/6] [clang-format] Keep compound literals stable in macro
bodies
Fixes https://github.com/llvm/llvm-project/issues/173583.
Test: FormatTests
---
clang/lib/Format/UnwrappedLineParser.cpp | 41 ++++++++++++++++++++++--
clang/unittests/Format/FormatTest.cpp | 20 ++++++++++++
2 files changed, 59 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index c1a9161b10720..2d1ddde2b438b 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -491,6 +491,31 @@ void UnwrappedLineParser::calculateBraceTypes(bool ExpectClassBody) {
SmallVector<StackEntry, 8> LBraceStack;
assert(Tok->is(tok::l_brace));
+ constexpr int MaxLookBack = 64;
+ const auto IsAddressOfParenExpression = [](const FormatToken *RightParen) {
+ if (!RightParen || RightParen->isNot(tok::r_paren))
+ return false;
+
+ int ParenDepth = 0;
+ const FormatToken *Current = RightParen;
+ const FormatToken *LeftParen = nullptr;
+ for (int I = 0; I < MaxLookBack && Current; ++I) {
+ if (Current->is(tok::r_paren)) {
+ ++ParenDepth;
+ } else if (Current->is(tok::l_paren)) {
+ --ParenDepth;
+ if (ParenDepth == 0) {
+ LeftParen = Current;
+ break;
+ }
+ }
+ Current = Current->Previous;
+ }
+
+ return LeftParen && LeftParen->Previous &&
+ LeftParen->Previous->is(tok::amp);
+ };
+
do {
auto *NextTok = Tokens->getNextNonComment();
@@ -528,7 +553,16 @@ void UnwrappedLineParser::calculateBraceTypes(bool ExpectClassBody) {
Tok->setBlockKind(BK_Block);
}
} else {
- Tok->setBlockKind(BK_Unknown);
+ // In macro bodies we try to keep compound literal expressions like
+ // `&(type){v}` on a single line. Without this, the '{' can be mistaken
+ // for a block/function body and clang-format will reflow the macro with
+ // backslashes and spaces (e.g. `&(type) { v }`).
+ if (IsCpp && Line->InMacroBody && PrevTok &&
+ IsAddressOfParenExpression(PrevTok)) {
+ Tok->setBlockKind(BK_BracedInit);
+ } else {
+ Tok->setBlockKind(BK_Unknown);
+ }
}
LBraceStack.push_back({Tok, PrevTok});
break;
@@ -2563,7 +2597,10 @@ bool UnwrappedLineParser::parseBracedList(bool IsAngleBracket, bool IsEnum) {
// lists (in so-called TypeMemberLists). Thus, the semicolon cannot be
// used for error recovery if we have otherwise determined that this is
// a braced list.
- if (Style.isJavaScript()) {
+ // In macro bodies we can also see non-syntactic braced lists (e.g.
+ // compound literal expressions) where clang-format should still remain
+ // stable.
+ if (Style.isJavaScript() || (IsCpp && Line->InMacroBody)) {
nextToken();
break;
}
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 3ee7ce38578aa..8e4dc74378691 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -5858,6 +5858,26 @@ TEST_F(FormatTest, RespectWhitespaceInMacroDefinitions) {
verifyFormat("#define false((foo)0)", Style);
}
+TEST_F(FormatTest, CompoundLiteralInMacroDefinition) {
+ // https://github.com/llvm/llvm-project/issues/173583
+ //
+ // A C compound literal `(type){...}` is not a function/block. When used in a
+ // macro definition, clang-format should not treat `&` as a function name and
+ // reformat it as if it were `&(type) { ... }`.
+ FormatStyle Style = getLLVMStyle();
+ Style.Language = FormatStyle::LK_Cpp;
+ Style.IndentWidth = 4;
+ Style.TabWidth = 4;
+ Style.UseTab = FormatStyle::UT_Never;
+ Style.AlignEscapedNewlines = FormatStyle::ENAS_LeftWithLastLine;
+ Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_Empty;
+ Style.BreakBeforeBraces = FormatStyle::BS_Attach;
+
+ verifyNoChange("#define getAddr(v, type) &(type){v}", Style);
+ verifyNoChange("#define getAddr2(v, type) int &(type){v;}", Style);
+ verifyNoChange("#define ctos(c) (char[2]){c, '\\0'}", Style);
+}
+
TEST_F(FormatTest, EmptyLinesInMacroDefinitions) {
verifyFormat("#define A b;",
"#define A \\\n"
>From 47988e008f88aff87df7155e5edcd90b4ce18b4d Mon Sep 17 00:00:00 2001
From: Lane0218 <laneljc at qq.com>
Date: Sun, 28 Dec 2025 22:16:02 +0800
Subject: [PATCH 2/6] [clang-format] Minimize style overrides in compound
literal macro test
Test: FormatTests
---
clang/unittests/Format/FormatTest.cpp | 7 -------
1 file changed, 7 deletions(-)
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 8e4dc74378691..fb5523dc6ce85 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -5865,13 +5865,6 @@ TEST_F(FormatTest, CompoundLiteralInMacroDefinition) {
// macro definition, clang-format should not treat `&` as a function name and
// reformat it as if it were `&(type) { ... }`.
FormatStyle Style = getLLVMStyle();
- Style.Language = FormatStyle::LK_Cpp;
- Style.IndentWidth = 4;
- Style.TabWidth = 4;
- Style.UseTab = FormatStyle::UT_Never;
- Style.AlignEscapedNewlines = FormatStyle::ENAS_LeftWithLastLine;
- Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_Empty;
- Style.BreakBeforeBraces = FormatStyle::BS_Attach;
verifyNoChange("#define getAddr(v, type) &(type){v}", Style);
verifyNoChange("#define getAddr2(v, type) int &(type){v;}", Style);
>From 113856e112cd934ce412f5bbab0686b7507505f8 Mon Sep 17 00:00:00 2001
From: Lane0218 <laneljc at qq.com>
Date: Sun, 28 Dec 2025 22:22:47 +0800
Subject: [PATCH 3/6] [clang-format] Simplify preconditions for macro compound
literals
NFC, addresses review feedback.
Test: FormatTests
---
clang/lib/Format/UnwrappedLineParser.cpp | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 2d1ddde2b438b..a239511eabd89 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -493,8 +493,7 @@ void UnwrappedLineParser::calculateBraceTypes(bool ExpectClassBody) {
constexpr int MaxLookBack = 64;
const auto IsAddressOfParenExpression = [](const FormatToken *RightParen) {
- if (!RightParen || RightParen->isNot(tok::r_paren))
- return false;
+ assert(RightParen && RightParen->is(tok::r_paren));
int ParenDepth = 0;
const FormatToken *Current = RightParen;
@@ -558,7 +557,7 @@ void UnwrappedLineParser::calculateBraceTypes(bool ExpectClassBody) {
// for a block/function body and clang-format will reflow the macro with
// backslashes and spaces (e.g. `&(type) { v }`).
if (IsCpp && Line->InMacroBody && PrevTok &&
- IsAddressOfParenExpression(PrevTok)) {
+ PrevTok->is(tok::r_paren) && IsAddressOfParenExpression(PrevTok)) {
Tok->setBlockKind(BK_BracedInit);
} else {
Tok->setBlockKind(BK_Unknown);
>From 41d1b7ab9ed3a6e1fe1548482ee79bf1caa0daa1 Mon Sep 17 00:00:00 2001
From: Lane0218 <laneljc at qq.com>
Date: Mon, 29 Dec 2025 09:56:37 +0800
Subject: [PATCH 4/6] [clang-format] Use verifyFormat for compound literal
macro test
Drop the invalid getAddr2 macro case.
Test: FormatTests
---
clang/unittests/Format/FormatTest.cpp | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index fb5523dc6ce85..92add80fb82ba 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -5864,11 +5864,8 @@ TEST_F(FormatTest, CompoundLiteralInMacroDefinition) {
// A C compound literal `(type){...}` is not a function/block. When used in a
// macro definition, clang-format should not treat `&` as a function name and
// reformat it as if it were `&(type) { ... }`.
- FormatStyle Style = getLLVMStyle();
-
- verifyNoChange("#define getAddr(v, type) &(type){v}", Style);
- verifyNoChange("#define getAddr2(v, type) int &(type){v;}", Style);
- verifyNoChange("#define ctos(c) (char[2]){c, '\\0'}", Style);
+ verifyFormat("#define getAddr(v, type) &(type){v}");
+ verifyFormat("#define ctos(c) (char[2]){c, '\\0'}");
}
TEST_F(FormatTest, EmptyLinesInMacroDefinitions) {
>From 171035ff4946e36bcc4070d3aa12f31d9105e9cb Mon Sep 17 00:00:00 2001
From: Lane0218 <laneljc at qq.com>
Date: Mon, 29 Dec 2025 10:31:05 +0800
Subject: [PATCH 5/6] [clang-format] Classify compound literal braces in macros
Handle the parser path for &(type){...} by setting the following { to BK_BracedInit.
Test: FormatTests
---
clang/lib/Format/UnwrappedLineParser.cpp | 43 +++----------------
clang/unittests/Format/TokenAnnotatorTest.cpp | 4 ++
2 files changed, 9 insertions(+), 38 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index a239511eabd89..775fb9b7b3ed5 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -491,30 +491,6 @@ void UnwrappedLineParser::calculateBraceTypes(bool ExpectClassBody) {
SmallVector<StackEntry, 8> LBraceStack;
assert(Tok->is(tok::l_brace));
- constexpr int MaxLookBack = 64;
- const auto IsAddressOfParenExpression = [](const FormatToken *RightParen) {
- assert(RightParen && RightParen->is(tok::r_paren));
-
- int ParenDepth = 0;
- const FormatToken *Current = RightParen;
- const FormatToken *LeftParen = nullptr;
- for (int I = 0; I < MaxLookBack && Current; ++I) {
- if (Current->is(tok::r_paren)) {
- ++ParenDepth;
- } else if (Current->is(tok::l_paren)) {
- --ParenDepth;
- if (ParenDepth == 0) {
- LeftParen = Current;
- break;
- }
- }
- Current = Current->Previous;
- }
-
- return LeftParen && LeftParen->Previous &&
- LeftParen->Previous->is(tok::amp);
- };
-
do {
auto *NextTok = Tokens->getNextNonComment();
@@ -552,16 +528,7 @@ void UnwrappedLineParser::calculateBraceTypes(bool ExpectClassBody) {
Tok->setBlockKind(BK_Block);
}
} else {
- // In macro bodies we try to keep compound literal expressions like
- // `&(type){v}` on a single line. Without this, the '{' can be mistaken
- // for a block/function body and clang-format will reflow the macro with
- // backslashes and spaces (e.g. `&(type) { v }`).
- if (IsCpp && Line->InMacroBody && PrevTok &&
- PrevTok->is(tok::r_paren) && IsAddressOfParenExpression(PrevTok)) {
- Tok->setBlockKind(BK_BracedInit);
- } else {
- Tok->setBlockKind(BK_Unknown);
- }
+ Tok->setBlockKind(BK_Unknown);
}
LBraceStack.push_back({Tok, PrevTok});
break;
@@ -2596,10 +2563,7 @@ bool UnwrappedLineParser::parseBracedList(bool IsAngleBracket, bool IsEnum) {
// lists (in so-called TypeMemberLists). Thus, the semicolon cannot be
// used for error recovery if we have otherwise determined that this is
// a braced list.
- // In macro bodies we can also see non-syntactic braced lists (e.g.
- // compound literal expressions) where clang-format should still remain
- // stable.
- if (Style.isJavaScript() || (IsCpp && Line->InMacroBody)) {
+ if (Style.isJavaScript()) {
nextToken();
break;
}
@@ -2700,6 +2664,9 @@ bool UnwrappedLineParser::parseParens(TokenType AmpAmpTokenType,
RParen->setFinalizedType(TT_TypeDeclarationParen);
} else if (Prev->is(tok::greater) && RParen->Previous == LParen) {
Prev->setFinalizedType(TT_TemplateCloser);
+ } else if (FormatTok->is(tok::l_brace) && Prev->is(tok::amp) &&
+ !Prev->Previous) {
+ FormatTok->setBlockKind(BK_BracedInit);
} else if (OptionalParens()) {
LParen->Optional = true;
RParen->Optional = true;
diff --git a/clang/unittests/Format/TokenAnnotatorTest.cpp b/clang/unittests/Format/TokenAnnotatorTest.cpp
index 69c2c2f17b376..a04c6bea4d050 100644
--- a/clang/unittests/Format/TokenAnnotatorTest.cpp
+++ b/clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -3943,6 +3943,10 @@ TEST_F(TokenAnnotatorTest, BraceKind) {
// Not TT_FunctionDeclarationName.
EXPECT_TOKEN(Tokens[6], tok::kw_operator, TT_Unknown);
EXPECT_BRACE_KIND(Tokens[9], BK_BracedInit);
+
+ Tokens = annotate("&(type){v}");
+ ASSERT_EQ(Tokens.size(), 8u) << Tokens;
+ EXPECT_BRACE_KIND(Tokens[4], BK_BracedInit);
}
TEST_F(TokenAnnotatorTest, UnderstandsElaboratedTypeSpecifier) {
>From f1903f36fa91c07bef35444dee8b14767a8d042c Mon Sep 17 00:00:00 2001
From: Lane0218 <laneljc at qq.com>
Date: Sat, 10 Jan 2026 22:01:10 +0800
Subject: [PATCH 6/6] [clang-format] Remove redundant compound literal
FormatTest
The compound literal macro regression is covered at the TokenAnnotator level.
Drop the extra FormatTest case and keep the coverage in TokenAnnotatorTest.
---
clang/unittests/Format/FormatTest.cpp | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 92add80fb82ba..3ee7ce38578aa 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -5858,16 +5858,6 @@ TEST_F(FormatTest, RespectWhitespaceInMacroDefinitions) {
verifyFormat("#define false((foo)0)", Style);
}
-TEST_F(FormatTest, CompoundLiteralInMacroDefinition) {
- // https://github.com/llvm/llvm-project/issues/173583
- //
- // A C compound literal `(type){...}` is not a function/block. When used in a
- // macro definition, clang-format should not treat `&` as a function name and
- // reformat it as if it were `&(type) { ... }`.
- verifyFormat("#define getAddr(v, type) &(type){v}");
- verifyFormat("#define ctos(c) (char[2]){c, '\\0'}");
-}
-
TEST_F(FormatTest, EmptyLinesInMacroDefinitions) {
verifyFormat("#define A b;",
"#define A \\\n"
More information about the cfe-commits
mailing list