Skip to content

Commit 849636c

Browse files
Eugene Shalyginzeule
Eugene Shalygin
authored andcommitted
[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 a481452 commit 849636c

12 files changed

+249
-4
lines changed

clang/docs/ClangFormatStyleOptions.rst

+32
Original file line numberDiff line numberDiff line change
@@ -4740,6 +4740,38 @@ the configuration (without a prefix: ``Auto``).
47404740
replaced with a single newline and form feed followed by the remaining
47414741
newlines.
47424742

4743+
.. _KeywordedFunctionLikeMacros:
4744+
4745+
**KeywordedFunctionLikeMacros** (``List of KeywordedFunctionLikeMacros``) :versionbadge:`clang-format 21` :ref:`<KeywordedFunctionLikeMacros>`
4746+
Allows to format function-like macros with keyworded parameters according
4747+
to the BinPackParameters setting, treating keywords as parameter
4748+
separators.
4749+
4750+
Q_PROPERTY is an example of such a macro:
4751+
4752+
.. code-block:: c++
4753+
4754+
Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)
4755+
4756+
With ``BinPackParameters`` set to ``OnePerLine`` (or
4757+
``AlwaysOnePerLine``) and
4758+
4759+
.. code-block:: yaml
4760+
4761+
KeywordedFunctionLikeMacros:
4762+
- Name: "Q_PROPERTY"
4763+
Keywords: ['READ', 'WRITE', 'MEMBER', 'RESET', 'NOTIFY']
4764+
4765+
the line above will be split on these keywords:
4766+
4767+
.. code-block:: c++
4768+
4769+
Q_PROPERTY(
4770+
int name
4771+
READ name
4772+
WRITE setName
4773+
NOTIFY nameChanged)
4774+
47434775
.. _LambdaBodyIndentation:
47444776

47454777
**LambdaBodyIndentation** (``LambdaBodyIndentationKind``) :versionbadge:`clang-format 13` :ref:`<LambdaBodyIndentation>`

clang/docs/tools/dump_format_style.py

+1
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ class State:
462462
"std::string",
463463
"std::vector<std::string>",
464464
"std::vector<IncludeCategory>",
465+
"std::vector<KeywordedFunctionLikeMacro>",
465466
"std::vector<RawStringFormat>",
466467
"std::optional<unsigned>",
467468
"deprecated",

clang/docs/tools/plurals.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
Strings
22
IncludeCategories
3+
KeywordedFunctionLikeMacros
34
RawStringFormats

clang/include/clang/Format/Format.h

+40
Original file line numberDiff line numberDiff line change
@@ -3276,6 +3276,45 @@ struct FormatStyle {
32763276
/// \version 20
32773277
bool KeepFormFeed;
32783278

3279+
/// Function-like declaration with keyworded parameters.
3280+
/// Lists possible keywords for a named function-like macro.
3281+
struct KeywordedFunctionLikeMacro {
3282+
std::string Name;
3283+
std::vector<std::string> Keywords;
3284+
3285+
bool operator==(const KeywordedFunctionLikeMacro &Other) const {
3286+
return Name == Other.Name && Keywords == Other.Keywords;
3287+
}
3288+
};
3289+
3290+
/// Allows to format function-like macros with keyworded parameters according
3291+
/// to the BinPackParameters setting, treating keywords as parameter
3292+
/// separators.
3293+
///
3294+
/// Q_PROPERTY is an example of such a macro:
3295+
/// \code
3296+
/// Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)
3297+
/// \endcode
3298+
///
3299+
/// With ``BinPackParameters`` set to ``OnePerLine`` (or
3300+
/// ``AlwaysOnePerLine``) and
3301+
/// \code{.yaml}
3302+
/// KeywordedFunctionLikeMacros:
3303+
/// - Name: "Q_PROPERTY"
3304+
/// Keywords: ['READ', 'WRITE', 'MEMBER', 'RESET', 'NOTIFY']
3305+
/// \endcode
3306+
///
3307+
/// the line above will be split on these keywords:
3308+
/// \code
3309+
/// Q_PROPERTY(
3310+
/// int name
3311+
/// READ name
3312+
/// WRITE setName
3313+
/// NOTIFY nameChanged)
3314+
/// \endcode
3315+
/// \version 21
3316+
std::vector<KeywordedFunctionLikeMacro> KeywordedFunctionLikeMacros;
3317+
32793318
/// Indentation logic for lambda bodies.
32803319
enum LambdaBodyIndentationKind : int8_t {
32813320
/// Align lambda body relative to the lambda signature. This is the default.
@@ -5352,6 +5391,7 @@ struct FormatStyle {
53525391
JavaScriptWrapImports == R.JavaScriptWrapImports &&
53535392
KeepEmptyLines == R.KeepEmptyLines &&
53545393
KeepFormFeed == R.KeepFormFeed && Language == R.Language &&
5394+
KeywordedFunctionLikeMacros == R.KeywordedFunctionLikeMacros &&
53555395
LambdaBodyIndentation == R.LambdaBodyIndentation &&
53565396
LineEnding == R.LineEnding && MacroBlockBegin == R.MacroBlockBegin &&
53575397
MacroBlockEnd == R.MacroBlockEnd && Macros == R.Macros &&

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
@@ -28,6 +28,7 @@
2828

2929
using clang::format::FormatStyle;
3030

31+
LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::KeywordedFunctionLikeMacro)
3132
LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::RawStringFormat)
3233

3334
namespace llvm {
@@ -399,6 +400,14 @@ template <> struct MappingTraits<FormatStyle::KeepEmptyLinesStyle> {
399400
}
400401
};
401402

403+
template <> struct MappingTraits<FormatStyle::KeywordedFunctionLikeMacro> {
404+
static void mapping(IO &IO,
405+
FormatStyle::KeywordedFunctionLikeMacro &Function) {
406+
IO.mapOptional("Name", Function.Name);
407+
IO.mapOptional("Keywords", Function.Keywords);
408+
}
409+
};
410+
402411
template <> struct ScalarEnumerationTraits<FormatStyle::LanguageKind> {
403412
static void enumeration(IO &IO, FormatStyle::LanguageKind &Value) {
404413
IO.enumCase(Value, "C", FormatStyle::LK_C);
@@ -1072,6 +1081,8 @@ template <> struct MappingTraits<FormatStyle> {
10721081
IO.mapOptional("JavaScriptWrapImports", Style.JavaScriptWrapImports);
10731082
IO.mapOptional("KeepEmptyLines", Style.KeepEmptyLines);
10741083
IO.mapOptional("KeepFormFeed", Style.KeepFormFeed);
1084+
IO.mapOptional("KeywordedFunctionLikeMacros",
1085+
Style.KeywordedFunctionLikeMacros);
10751086
IO.mapOptional("LambdaBodyIndentation", Style.LambdaBodyIndentation);
10761087
IO.mapOptional("LineEnding", Style.LineEnding);
10771088
IO.mapOptional("MacroBlockBegin", Style.MacroBlockBegin);

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

+55-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::KeywordedFunctionLikeMacro *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,32 @@ class AnnotatingParser {
148158
}
149159
}
150160

161+
const FormatStyle::KeywordedFunctionLikeMacro *
162+
findKeywordedFunctionLikeMacro() const {
163+
const FormatToken *TokBeforeFirstLParent = nullptr;
164+
for (const FormatToken *T = Line.First; T != Line.Last; T = T->Next) {
165+
if (T->Tok.is(tok::l_paren)) {
166+
TokBeforeFirstLParent = T->getPreviousNonComment();
167+
break;
168+
}
169+
}
170+
171+
// Unknown if line ends with ';', FunctionLikeOrFreestandingMacro otherwise.
172+
if (!TokBeforeFirstLParent ||
173+
!TokBeforeFirstLParent->isOneOf(TT_FunctionLikeOrFreestandingMacro,
174+
TT_Unknown)) {
175+
return nullptr;
176+
}
177+
auto I = std::find_if(
178+
Style.KeywordedFunctionLikeMacros.begin(),
179+
Style.KeywordedFunctionLikeMacros.end(),
180+
[TokBeforeFirstLParent](
181+
const FormatStyle::KeywordedFunctionLikeMacro &Declaration) {
182+
return TokBeforeFirstLParent->TokenText == Declaration.Name;
183+
});
184+
return I != Style.KeywordedFunctionLikeMacros.end() ? &*I : nullptr;
185+
}
186+
151187
bool parseAngle() {
152188
if (!CurrentToken)
153189
return false;
@@ -2415,8 +2451,12 @@ class AnnotatingParser {
24152451
Current.setType(TT_BinaryOperator);
24162452
} else if (isStartOfName(Current) &&
24172453
(!Line.MightBeFunctionDecl || Current.NestingLevel != 0)) {
2418-
Contexts.back().FirstStartOfName = &Current;
2419-
Current.setType(TT_StartOfName);
2454+
if (isParametersKeyword(Current, findKeywordedFunctionLikeMacro())) {
2455+
Current.setType(TT_FunctionParameterKeyword);
2456+
} else {
2457+
Contexts.back().FirstStartOfName = &Current;
2458+
Current.setType(TT_StartOfName);
2459+
}
24202460
} else if (Current.is(tok::semi)) {
24212461
// Reset FirstStartOfName after finding a semicolon so that a for loop
24222462
// with multiple increment statements is not confused with a for loop
@@ -3783,10 +3823,20 @@ void TokenAnnotator::annotate(AnnotatedLine &Line) {
37833823
static bool isFunctionDeclarationName(const LangOptions &LangOpts,
37843824
const FormatToken &Current,
37853825
const AnnotatedLine &Line,
3826+
const FormatStyle &Style,
37863827
FormatToken *&ClosingParen) {
37873828
if (Current.is(TT_FunctionDeclarationName))
37883829
return true;
37893830

3831+
if (Current.is(TT_FunctionLikeOrFreestandingMacro) &&
3832+
std::find_if(
3833+
Style.KeywordedFunctionLikeMacros.begin(),
3834+
Style.KeywordedFunctionLikeMacros.end(),
3835+
[&Current](const FormatStyle::KeywordedFunctionLikeMacro &Decl) {
3836+
return Current.TokenText == Decl.Name;
3837+
}) != Style.KeywordedFunctionLikeMacros.end()) {
3838+
return true;
3839+
}
37903840
if (!Current.Tok.getIdentifierInfo())
37913841
return false;
37923842

@@ -3993,7 +4043,7 @@ void TokenAnnotator::calculateFormattingInformation(AnnotatedLine &Line) const {
39934043
AfterLastAttribute = Tok;
39944044
if (const bool IsCtorOrDtor = Tok->is(TT_CtorDtorDeclName);
39954045
IsCtorOrDtor ||
3996-
isFunctionDeclarationName(LangOpts, *Tok, Line, ClosingParen)) {
4046+
isFunctionDeclarationName(LangOpts, *Tok, Line, Style, ClosingParen)) {
39974047
if (!IsCtorOrDtor)
39984048
Tok->setFinalizedType(TT_FunctionDeclarationName);
39994049
LineIsFunctionDeclaration = true;
@@ -6231,7 +6281,8 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
62316281
Right.Next->isOneOf(TT_FunctionDeclarationName, tok::kw_const)));
62326282
}
62336283
if (Right.isOneOf(TT_StartOfName, TT_FunctionDeclarationName,
6234-
TT_ClassHeadName, tok::kw_operator)) {
6284+
TT_FunctionParameterKeyword, TT_ClassHeadName,
6285+
tok::kw_operator)) {
62356286
return true;
62366287
}
62376288
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.KeywordedFunctionLikeMacros.clear();
1110+
std::vector<FormatStyle::KeywordedFunctionLikeMacro> ExpectedFunctions = {
1111+
{"MACRO_A", {"PARAM1", "KEYWORD", "KW_2"}}, {"macro", {"mKW1", "mKW2"}}};
1112+
CHECK_PARSE("KeywordedFunctionLikeMacros:\n"
1113+
" - Name: MACRO_A\n"
1114+
" Keywords: ['PARAM1', 'KEYWORD', 'KW_2']\n"
1115+
" - Name: macro\n"
1116+
" Keywords: [ \"mKW1\", \"mKW2\" ]\n",
1117+
KeywordedFunctionLikeMacros, ExpectedFunctions);
11081118
}
11091119

11101120
TEST(ConfigParseTest, ParsesConfigurationWithLanguages) {

clang/unittests/Format/FormatTest.cpp

+59
Original file line numberDiff line numberDiff line change
@@ -29102,6 +29102,65 @@ TEST_F(FormatTest, BreakBeforeClassName) {
2910229102
" ArenaSafeUniquePtr {};");
2910329103
}
2910429104

29105+
TEST_F(FormatTest, KeywordedFunctionLikeMacros) {
29106+
FormatStyle::KeywordedFunctionLikeMacro QPropertyDeclaration;
29107+
QPropertyDeclaration.Name = "Q_PROPERTY";
29108+
QPropertyDeclaration.Keywords.push_back("READ");
29109+
QPropertyDeclaration.Keywords.push_back("WRITE");
29110+
QPropertyDeclaration.Keywords.push_back("NOTIFY");
29111+
QPropertyDeclaration.Keywords.push_back("RESET");
29112+
29113+
auto Style40 = getLLVMStyleWithColumns(40);
29114+
Style40.KeywordedFunctionLikeMacros.push_back(QPropertyDeclaration);
29115+
Style40.BinPackParameters = FormatStyle::BPPS_OnePerLine;
29116+
29117+
verifyFormat("Q_PROPERTY(int name\n"
29118+
" READ name\n"
29119+
" WRITE setName\n"
29120+
" NOTIFY nameChanged)",
29121+
Style40);
29122+
verifyFormat("class A {\n"
29123+
" Q_PROPERTY(int name\n"
29124+
" READ name\n"
29125+
" WRITE setName\n"
29126+
" NOTIFY nameChanged)\n"
29127+
"};",
29128+
Style40);
29129+
verifyFormat("/* sdf */ Q_PROPERTY(int name\n"
29130+
" READ name\n"
29131+
" WRITE setName\n"
29132+
" NOTIFY nameChanged)",
29133+
Style40);
29134+
29135+
auto Style120 = getLLVMStyleWithColumns(120);
29136+
Style120.KeywordedFunctionLikeMacros.push_back(QPropertyDeclaration);
29137+
Style120.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
29138+
29139+
verifyFormat("Q_PROPERTY(int name\n"
29140+
" READ name\n"
29141+
" WRITE setName\n"
29142+
" NOTIFY nameChanged)",
29143+
Style120);
29144+
verifyFormat("class A {\n"
29145+
" Q_PROPERTY(int name\n"
29146+
" READ name\n"
29147+
" WRITE setName\n"
29148+
" NOTIFY nameChanged)\n"
29149+
"};",
29150+
Style120);
29151+
29152+
Style120.BinPackParameters = FormatStyle::BPPS_BinPack;
29153+
29154+
verifyFormat(
29155+
"Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
29156+
Style120);
29157+
29158+
Style120.BinPackParameters = FormatStyle::BPPS_OnePerLine;
29159+
verifyFormat(
29160+
"Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
29161+
Style120);
29162+
}
29163+
2910529164
} // namespace
2910629165
} // namespace test
2910729166
} // namespace format

clang/unittests/Format/TokenAnnotatorTest.cpp

+33
Original file line numberDiff line numberDiff line change
@@ -3926,6 +3926,39 @@ TEST_F(TokenAnnotatorTest, UserDefinedConversionFunction) {
39263926
EXPECT_TOKEN(Tokens[5], tok::l_paren, TT_FunctionDeclarationLParen);
39273927
}
39283928

3929+
TEST_F(TokenAnnotatorTest, KeywordedFunctionLikeMacro) {
3930+
auto Style = getLLVMStyle();
3931+
FormatStyle::KeywordedFunctionLikeMacro QPropertyDeclaration;
3932+
QPropertyDeclaration.Name = "Q_PROPERTY";
3933+
QPropertyDeclaration.Keywords.push_back("READ");
3934+
QPropertyDeclaration.Keywords.push_back("WRITE");
3935+
QPropertyDeclaration.Keywords.push_back("NOTIFY");
3936+
QPropertyDeclaration.Keywords.push_back("RESET");
3937+
Style.KeywordedFunctionLikeMacros.push_back(QPropertyDeclaration);
3938+
3939+
auto Tokens = annotate(
3940+
"Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)",
3941+
Style);
3942+
ASSERT_EQ(Tokens.size(), 12u) << Tokens;
3943+
EXPECT_TOKEN(Tokens[0], tok::identifier, TT_Unknown);
3944+
EXPECT_TOKEN(Tokens[4], tok::identifier, TT_FunctionParameterKeyword);
3945+
EXPECT_TOKEN(Tokens[5], tok::identifier, TT_StartOfName);
3946+
EXPECT_TOKEN(Tokens[6], tok::identifier, TT_FunctionParameterKeyword);
3947+
EXPECT_TOKEN(Tokens[7], tok::identifier, TT_StartOfName);
3948+
EXPECT_TOKEN(Tokens[8], tok::identifier, TT_FunctionParameterKeyword);
3949+
EXPECT_TOKEN(Tokens[9], tok::identifier, TT_StartOfName);
3950+
3951+
Tokens = annotate(
3952+
"struct S { Q_OBJECT\n Q_PROPERTY(int value READ value WRITE setValue "
3953+
"NOTIFY valueChanged)\n };",
3954+
Style);
3955+
ASSERT_EQ(Tokens.size(), 18u) << Tokens;
3956+
EXPECT_TOKEN(Tokens[4], tok::identifier, TT_FunctionLikeOrFreestandingMacro);
3957+
EXPECT_TOKEN(Tokens[8], tok::identifier, TT_FunctionParameterKeyword);
3958+
EXPECT_TOKEN(Tokens[10], tok::identifier, TT_FunctionParameterKeyword);
3959+
EXPECT_TOKEN(Tokens[12], tok::identifier, TT_FunctionParameterKeyword);
3960+
}
3961+
39293962
} // namespace
39303963
} // namespace format
39313964
} // namespace clang

0 commit comments

Comments
 (0)