Skip to content

Commit 2c9a741

Browse files
author
Eugene Shalygin
committed
[clang-format] option to control bin-packing keyworded parameters
The Q_PROPERTY declaration is almost like a function declaration, but uses keywords as parameter separators. This allows users to provide list of those keywords to be used to control bin-packing of the macro parameters.
1 parent 31e98c7 commit 2c9a741

12 files changed

+237
-4
lines changed

clang/docs/ClangFormatStyleOptions.rst

+32
Original file line numberDiff line numberDiff line change
@@ -4032,6 +4032,38 @@ the configuration (without a prefix: ``Auto``).
40324032
40334033
For example: BOOST_FOREACH.
40344034

4035+
.. _FunctionDeclarationsWithKeywords:
4036+
4037+
**FunctionDeclarationsWithKeywords** (``List of FunctionDeclarationWithKeywordes``) :versionbadge:`clang-format 21` :ref:`<FunctionDeclarationsWithKeywords>`
4038+
Allows to format function-like macros with keyworded parameters according
4039+
to the BinPackParameters setting, treating keywords as parameter
4040+
sepratators.
4041+
4042+
Q_PROPERTY is an example of such a macro:
4043+
4044+
.. code-block:: c++
4045+
4046+
Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)
4047+
4048+
With ``BinPackParameters`` set to ``OnePerLine`` (or
4049+
``AlwaysOnePerLine``) and
4050+
4051+
.. code-block:: yaml
4052+
4053+
FunctionDeclarationsWithKeywords:
4054+
- Name: "Q_PROPERTY"
4055+
Keywords: ['READ', 'WRITE', 'MEMBER', 'RESET', 'NOTIFY']
4056+
4057+
the line above will be split on these keywords:
4058+
4059+
.. code-block:: c++
4060+
4061+
Q_PROPERTY(
4062+
int name
4063+
READ name
4064+
WRITE setName
4065+
NOTIFY nameChanged)
4066+
40354067
.. _IfMacros:
40364068

40374069
**IfMacros** (``List of Strings``) :versionbadge:`clang-format 13` :ref:`<IfMacros>`

clang/docs/tools/dump_format_style.py

+1
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ class State:
461461
"int",
462462
"std::string",
463463
"std::vector<std::string>",
464+
"std::vector<FunctionDeclarationWithKeywords>",
464465
"std::vector<IncludeCategory>",
465466
"std::vector<RawStringFormat>",
466467
"std::optional<unsigned>",

clang/docs/tools/plurals.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
Strings
2+
FunctionDeclarationWithKeywordes
23
IncludeCategories
34
RawStringFormats

clang/include/clang/Format/Format.h

+41
Original file line numberDiff line numberDiff line change
@@ -2753,6 +2753,45 @@ struct FormatStyle {
27532753
/// \version 3.7
27542754
std::vector<std::string> ForEachMacros;
27552755

2756+
/// Function-like declaration with keyworded parameters.
2757+
/// Lists possible keywords for a named macro-like function
2758+
struct FunctionDeclarationWithKeywords {
2759+
std::string Name;
2760+
std::vector<std::string> Keywords;
2761+
2762+
bool operator==(const FunctionDeclarationWithKeywords &Other) const {
2763+
return Name == Other.Name && Keywords == Other.Keywords;
2764+
}
2765+
};
2766+
2767+
/// Allows to format function-like macros with keyworded parameters according
2768+
/// to the BinPackParameters setting, treating keywords as parameter
2769+
/// sepratators.
2770+
///
2771+
/// Q_PROPERTY is an example of such a macro:
2772+
/// \code
2773+
/// Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)
2774+
/// \endcode
2775+
///
2776+
/// With ``BinPackParameters`` set to ``OnePerLine`` (or
2777+
/// ``AlwaysOnePerLine``) and
2778+
/// \code{.yaml}
2779+
/// FunctionDeclarationsWithKeywords:
2780+
/// - Name: "Q_PROPERTY"
2781+
/// Keywords: ['READ', 'WRITE', 'MEMBER', 'RESET', 'NOTIFY']
2782+
/// \endcode
2783+
///
2784+
/// the line above will be split on these keywords:
2785+
/// \code
2786+
/// Q_PROPERTY(
2787+
/// int name
2788+
/// READ name
2789+
/// WRITE setName
2790+
/// NOTIFY nameChanged)
2791+
/// \endcode
2792+
/// \version 21
2793+
std::vector<FunctionDeclarationWithKeywords> FunctionDeclarationsWithKeywords;
2794+
27562795
tooling::IncludeStyle IncludeStyle;
27572796

27582797
/// A vector of macros that should be interpreted as conditionals
@@ -5327,6 +5366,8 @@ struct FormatStyle {
53275366
R.ExperimentalAutoDetectBinPacking &&
53285367
FixNamespaceComments == R.FixNamespaceComments &&
53295368
ForEachMacros == R.ForEachMacros &&
5369+
FunctionDeclarationsWithKeywords ==
5370+
R.FunctionDeclarationsWithKeywords &&
53305371
IncludeStyle.IncludeBlocks == R.IncludeStyle.IncludeBlocks &&
53315372
IncludeStyle.IncludeCategories == R.IncludeStyle.IncludeCategories &&
53325373
IncludeStyle.IncludeIsMainRegex ==

clang/lib/Format/ContinuationIndenter.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,10 @@ bool ContinuationIndenter::canBreak(const LineState &State) {
349349
}
350350
}
351351

352+
// Don't break between function parameter keywords and parameter names.
353+
if (Previous.is(TT_FunctionParameterKeyword) && Current.is(TT_StartOfName))
354+
return false;
355+
352356
// Don't allow breaking before a closing brace of a block-indented braced list
353357
// initializer if there isn't already a break.
354358
if (Current.is(tok::r_brace) && Current.MatchingParen &&

clang/lib/Format/Format.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
using clang::format::FormatStyle;
3030

3131
LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::RawStringFormat)
32+
LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::FunctionDeclarationWithKeywords)
3233

3334
namespace llvm {
3435
namespace yaml {
@@ -852,6 +853,14 @@ struct ScalarEnumerationTraits<
852853
}
853854
};
854855

856+
template <> struct MappingTraits<FormatStyle::FunctionDeclarationWithKeywords> {
857+
static void mapping(IO &IO,
858+
FormatStyle::FunctionDeclarationWithKeywords &Function) {
859+
IO.mapOptional("Name", Function.Name);
860+
IO.mapOptional("Keywords", Function.Keywords);
861+
}
862+
};
863+
855864
template <> struct MappingTraits<FormatStyle> {
856865
static void mapping(IO &IO, FormatStyle &Style) {
857866
// When reading, read the language first, we need it for getPredefinedStyle.
@@ -1046,6 +1055,8 @@ template <> struct MappingTraits<FormatStyle> {
10461055
Style.ExperimentalAutoDetectBinPacking);
10471056
IO.mapOptional("FixNamespaceComments", Style.FixNamespaceComments);
10481057
IO.mapOptional("ForEachMacros", Style.ForEachMacros);
1058+
IO.mapOptional("FunctionDeclarationsWithKeywords",
1059+
Style.FunctionDeclarationsWithKeywords);
10491060
IO.mapOptional("IfMacros", Style.IfMacros);
10501061
IO.mapOptional("IncludeBlocks", Style.IncludeStyle.IncludeBlocks);
10511062
IO.mapOptional("IncludeCategories", Style.IncludeStyle.IncludeCategories);

clang/lib/Format/FormatToken.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ bool startsNextParameter(const FormatToken &Current, const FormatStyle &Style) {
331331
}
332332
if (Style.Language == FormatStyle::LK_Proto && Current.is(TT_SelectorName))
333333
return true;
334+
if (Current.is(TT_FunctionParameterKeyword))
335+
return true;
334336
return Previous.is(tok::comma) && !Current.isTrailingComment() &&
335337
((Previous.isNot(TT_CtorInitializerComma) ||
336338
Style.BreakConstructorInitializers !=

clang/lib/Format/FormatToken.h

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ namespace format {
8484
TYPE(FunctionDeclarationLParen) \
8585
TYPE(FunctionLBrace) \
8686
TYPE(FunctionLikeOrFreestandingMacro) \
87+
TYPE(FunctionParameterKeyword) \
8788
TYPE(FunctionTypeLParen) \
8889
/* The colons as part of a C11 _Generic selection */ \
8990
TYPE(GenericSelectionColon) \

clang/lib/Format/TokenAnnotator.cpp

+47-4
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ static bool isCppAttribute(bool IsCpp, const FormatToken &Tok) {
116116
return AttrTok && AttrTok->startsSequence(tok::r_square, tok::r_square);
117117
}
118118

119+
static bool isParametersKeyword(
120+
const FormatToken &Tok,
121+
const FormatStyle::FunctionDeclarationWithKeywords *declaration) {
122+
if (!declaration)
123+
return false;
124+
125+
return std::find(declaration->Keywords.begin(), declaration->Keywords.end(),
126+
Tok.TokenText) != declaration->Keywords.end();
127+
}
128+
119129
/// A parser that gathers additional information about tokens.
120130
///
121131
/// The \c TokenAnnotator tries to match parenthesis and square brakets and
@@ -148,6 +158,23 @@ class AnnotatingParser {
148158
}
149159
}
150160

161+
const FormatStyle::FunctionDeclarationWithKeywords *
162+
findSurroundingFunctionWithKeywordedParameters() const {
163+
// Unknown if line ends with ';', FunctionLikeOrFreestandingMacro otherwise.
164+
if (!Line.First ||
165+
!Line.First->isOneOf(TT_FunctionLikeOrFreestandingMacro, TT_Unknown)) {
166+
return nullptr;
167+
}
168+
auto I = std::find_if(
169+
Style.FunctionDeclarationsWithKeywords.begin(),
170+
Style.FunctionDeclarationsWithKeywords.end(),
171+
[this](
172+
const FormatStyle::FunctionDeclarationWithKeywords &Declaration) {
173+
return Line.First->TokenText == Declaration.Name;
174+
});
175+
return I != Style.FunctionDeclarationsWithKeywords.end() ? &*I : nullptr;
176+
}
177+
151178
bool parseAngle() {
152179
if (!CurrentToken)
153180
return false;
@@ -2416,8 +2443,13 @@ class AnnotatingParser {
24162443
Current.setType(TT_BinaryOperator);
24172444
} else if (isStartOfName(Current) &&
24182445
(!Line.MightBeFunctionDecl || Current.NestingLevel != 0)) {
2419-
Contexts.back().FirstStartOfName = &Current;
2420-
Current.setType(TT_StartOfName);
2446+
if (isParametersKeyword(
2447+
Current, findSurroundingFunctionWithKeywordedParameters())) {
2448+
Current.setType(TT_FunctionParameterKeyword);
2449+
} else {
2450+
Contexts.back().FirstStartOfName = &Current;
2451+
Current.setType(TT_StartOfName);
2452+
}
24212453
} else if (Current.is(tok::semi)) {
24222454
// Reset FirstStartOfName after finding a semicolon so that a for loop
24232455
// with multiple increment statements is not confused with a for loop
@@ -3784,10 +3816,20 @@ void TokenAnnotator::annotate(AnnotatedLine &Line) {
37843816
static bool isFunctionDeclarationName(const LangOptions &LangOpts,
37853817
const FormatToken &Current,
37863818
const AnnotatedLine &Line,
3819+
const FormatStyle &Style,
37873820
FormatToken *&ClosingParen) {
37883821
if (Current.is(TT_FunctionDeclarationName))
37893822
return true;
37903823

3824+
if (Current.is(TT_FunctionLikeOrFreestandingMacro) &&
3825+
std::find_if(
3826+
Style.FunctionDeclarationsWithKeywords.begin(),
3827+
Style.FunctionDeclarationsWithKeywords.end(),
3828+
[&Current](const FormatStyle::FunctionDeclarationWithKeywords &Decl) {
3829+
return Current.TokenText == Decl.Name;
3830+
}) != Style.FunctionDeclarationsWithKeywords.end()) {
3831+
return true;
3832+
}
37913833
if (!Current.Tok.getIdentifierInfo())
37923834
return false;
37933835

@@ -3994,7 +4036,7 @@ void TokenAnnotator::calculateFormattingInformation(AnnotatedLine &Line) const {
39944036
AfterLastAttribute = Tok;
39954037
if (const bool IsCtorOrDtor = Tok->is(TT_CtorDtorDeclName);
39964038
IsCtorOrDtor ||
3997-
isFunctionDeclarationName(LangOpts, *Tok, Line, ClosingParen)) {
4039+
isFunctionDeclarationName(LangOpts, *Tok, Line, Style, ClosingParen)) {
39984040
if (!IsCtorOrDtor)
39994041
Tok->setFinalizedType(TT_FunctionDeclarationName);
40004042
LineIsFunctionDeclaration = true;
@@ -6218,7 +6260,8 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
62186260
Right.Next->isOneOf(TT_FunctionDeclarationName, tok::kw_const)));
62196261
}
62206262
if (Right.isOneOf(TT_StartOfName, TT_FunctionDeclarationName,
6221-
TT_ClassHeadName, tok::kw_operator)) {
6263+
TT_FunctionParameterKeyword, TT_ClassHeadName,
6264+
tok::kw_operator)) {
62226265
return true;
62236266
}
62246267
if (Right.isAttribute())

clang/unittests/Format/ConfigParseTest.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,16 @@ TEST(ConfigParseTest, ParsesConfiguration) {
11051105
FormatStyle::SDS_Leave);
11061106
CHECK_PARSE("SeparateDefinitionBlocks: Never", SeparateDefinitionBlocks,
11071107
FormatStyle::SDS_Never);
1108+
1109+
Style.FunctionDeclarationsWithKeywords.clear();
1110+
std::vector<FormatStyle::FunctionDeclarationWithKeywords> ExpectedFunctions =
1111+
{{"MACRO_A", {"PARAM1", "KEYWORD", "KW_2"}}, {"macro", {"mKW1", "mKW2"}}};
1112+
CHECK_PARSE("FunctionDeclarationsWithKeywords:\n"
1113+
" - Name: MACRO_A\n"
1114+
" Keywords: ['PARAM1', 'KEYWORD', 'KW_2']\n"
1115+
" - Name: macro\n"
1116+
" Keywords: [ \"mKW1\", \"mKW2\" ]\n",
1117+
FunctionDeclarationsWithKeywords, ExpectedFunctions);
11081118
}
11091119

11101120
TEST(ConfigParseTest, ParsesConfigurationWithLanguages) {

clang/unittests/Format/FormatTest.cpp

+54
Original file line numberDiff line numberDiff line change
@@ -29096,6 +29096,60 @@ TEST_F(FormatTest, BreakBeforeClassName) {
2909629096
" ArenaSafeUniquePtr {};");
2909729097
}
2909829098

29099+
TEST_F(FormatTest, FunctionDeclarationWithKeywords) {
29100+
FormatStyle::FunctionDeclarationWithKeywords QPropertyDeclaration;
29101+
QPropertyDeclaration.Name = "Q_PROPERTY";
29102+
QPropertyDeclaration.Keywords.push_back("READ");
29103+
QPropertyDeclaration.Keywords.push_back("WRITE");
29104+
QPropertyDeclaration.Keywords.push_back("NOTIFY");
29105+
QPropertyDeclaration.Keywords.push_back("RESET");
29106+
29107+
auto Style40 = getLLVMStyleWithColumns(40);
29108+
Style40.FunctionDeclarationsWithKeywords.push_back(QPropertyDeclaration);
29109+
Style40.BinPackParameters = FormatStyle::BPPS_OnePerLine;
29110+
29111+
verifyFormat("Q_PROPERTY(int name\n"
29112+
" READ name\n"
29113+
" WRITE setName\n"
29114+
" NOTIFY nameChanged)",
29115+
Style40);
29116+
verifyFormat("class A {\n"
29117+
" Q_PROPERTY(int name\n"
29118+
" READ name\n"
29119+
" WRITE setName\n"
29120+
" NOTIFY nameChanged)\n"
29121+
"};",
29122+
Style40);
29123+
29124+
auto Style120 = getLLVMStyleWithColumns(120);
29125+
Style120.FunctionDeclarationsWithKeywords.push_back(QPropertyDeclaration);
29126+
Style120.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
29127+
29128+
verifyFormat("Q_PROPERTY(int name\n"
29129+
" READ name\n"
29130+
" WRITE setName\n"
29131+
" NOTIFY nameChanged)",
29132+
Style120);
29133+
verifyFormat("class A {\n"
29134+
" Q_PROPERTY(int name\n"
29135+
" READ name\n"
29136+
" WRITE setName\n"
29137+
" NOTIFY nameChanged)\n"
29138+
"};",
29139+
Style120);
29140+
29141+
Style120.BinPackParameters = FormatStyle::BPPS_BinPack;
29142+
29143+
verifyFormat(
29144+
"Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
29145+
Style120);
29146+
29147+
Style120.BinPackParameters = FormatStyle::BPPS_OnePerLine;
29148+
verifyFormat(
29149+
"Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
29150+
Style120);
29151+
}
29152+
2909929153
} // namespace
2910029154
} // namespace test
2910129155
} // namespace format

clang/unittests/Format/TokenAnnotatorTest.cpp

+33
Original file line numberDiff line numberDiff line change
@@ -3906,6 +3906,39 @@ TEST_F(TokenAnnotatorTest, UserDefinedConversionFunction) {
39063906
EXPECT_TOKEN(Tokens[5], tok::l_paren, TT_FunctionDeclarationLParen);
39073907
}
39083908

3909+
TEST_F(TokenAnnotatorTest, FunctionDeclarationWithKeywords) {
3910+
auto Style = getLLVMStyle();
3911+
FormatStyle::FunctionDeclarationWithKeywords QPropertyDeclaration;
3912+
QPropertyDeclaration.Name = "Q_PROPERTY";
3913+
QPropertyDeclaration.Keywords.push_back("READ");
3914+
QPropertyDeclaration.Keywords.push_back("WRITE");
3915+
QPropertyDeclaration.Keywords.push_back("NOTIFY");
3916+
QPropertyDeclaration.Keywords.push_back("RESET");
3917+
Style.FunctionDeclarationsWithKeywords.push_back(QPropertyDeclaration);
3918+
3919+
auto Tokens = annotate(
3920+
"Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)",
3921+
Style);
3922+
ASSERT_EQ(Tokens.size(), 12u) << Tokens;
3923+
EXPECT_TOKEN(Tokens[0], tok::identifier, TT_Unknown);
3924+
EXPECT_TOKEN(Tokens[4], tok::identifier, TT_FunctionParameterKeyword);
3925+
EXPECT_TOKEN(Tokens[5], tok::identifier, TT_StartOfName);
3926+
EXPECT_TOKEN(Tokens[6], tok::identifier, TT_FunctionParameterKeyword);
3927+
EXPECT_TOKEN(Tokens[7], tok::identifier, TT_StartOfName);
3928+
EXPECT_TOKEN(Tokens[8], tok::identifier, TT_FunctionParameterKeyword);
3929+
EXPECT_TOKEN(Tokens[9], tok::identifier, TT_StartOfName);
3930+
3931+
Tokens = annotate(
3932+
"struct S { Q_OBJECT\n Q_PROPERTY(int value READ value WRITE setValue "
3933+
"NOTIFY valueChanged)\n };",
3934+
Style);
3935+
ASSERT_EQ(Tokens.size(), 18u) << Tokens;
3936+
EXPECT_TOKEN(Tokens[4], tok::identifier, TT_FunctionLikeOrFreestandingMacro);
3937+
EXPECT_TOKEN(Tokens[8], tok::identifier, TT_FunctionParameterKeyword);
3938+
EXPECT_TOKEN(Tokens[10], tok::identifier, TT_FunctionParameterKeyword);
3939+
EXPECT_TOKEN(Tokens[12], tok::identifier, TT_FunctionParameterKeyword);
3940+
}
3941+
39093942
} // namespace
39103943
} // namespace format
39113944
} // namespace clang

0 commit comments

Comments
 (0)