Skip to content

Commit 711191c

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 711191c

12 files changed

+223
-2792
lines changed

clang/docs/ClangFormatStyleOptions.rst

+8-2,788
Large diffs are not rendered by default.

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

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

clang/include/clang/Format/Format.h

+44
Original file line numberDiff line numberDiff line change
@@ -2753,6 +2753,48 @@ 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+
///
2773+
/// \code
2774+
/// Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)
2775+
/// \endcode
2776+
///
2777+
/// With ``BinPackParameters`` set to ``OnePerLine`` (or
2778+
/// ``AlwaysOnePerLine``) and
2779+
///
2780+
/// \code{.yaml}
2781+
/// FunctionDeclarationsWithKeywords:
2782+
/// - Name: "Q_PROPERTY"
2783+
/// Keywords: ['READ', 'WRITE', 'MEMBER', 'RESET', 'NOTIFY']
2784+
/// \endcode
2785+
///
2786+
/// the line above will be split on these keywords:
2787+
///
2788+
/// \code
2789+
/// Q_PROPERTY(
2790+
/// int name
2791+
/// READ name
2792+
/// WRITE setName
2793+
/// NOTIFY nameChanged)
2794+
/// \endcode
2795+
/// \version 21
2796+
std::vector<FunctionDeclarationWithKeywords> FunctionDeclarationsWithKeywords;
2797+
27562798
tooling::IncludeStyle IncludeStyle;
27572799

27582800
/// A vector of macros that should be interpreted as conditionals
@@ -5327,6 +5369,8 @@ struct FormatStyle {
53275369
R.ExperimentalAutoDetectBinPacking &&
53285370
FixNamespaceComments == R.FixNamespaceComments &&
53295371
ForEachMacros == R.ForEachMacros &&
5372+
FunctionDeclarationsWithKeywords ==
5373+
R.FunctionDeclarationsWithKeywords &&
53305374
IncludeStyle.IncludeBlocks == R.IncludeStyle.IncludeBlocks &&
53315375
IncludeStyle.IncludeCategories == R.IncludeStyle.IncludeCategories &&
53325376
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

+50-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,25 @@ class AnnotatingParser {
148158
}
149159
}
150160

161+
const FormatStyle::FunctionDeclarationWithKeywords *
162+
findSurroundingFunctionWithKeywordedParameters(
163+
const FormatToken &Token) const {
164+
const FormatToken *Previous = &Token;
165+
while (auto Prev = Previous->getPreviousNonComment())
166+
Previous = Prev;
167+
// Unknown if line ends with ';', FunctionLikeOrFreestandingMacro otherwise.
168+
if (!Previous->isOneOf(TT_FunctionLikeOrFreestandingMacro, TT_Unknown))
169+
return nullptr;
170+
auto I = std::find_if(
171+
Style.FunctionDeclarationsWithKeywords.begin(),
172+
Style.FunctionDeclarationsWithKeywords.end(),
173+
[Previous](
174+
const FormatStyle::FunctionDeclarationWithKeywords &Declaration) {
175+
return Previous->TokenText == Declaration.Name;
176+
});
177+
return I != Style.FunctionDeclarationsWithKeywords.end() ? &*I : nullptr;
178+
}
179+
151180
bool parseAngle() {
152181
if (!CurrentToken)
153182
return false;
@@ -2416,8 +2445,14 @@ class AnnotatingParser {
24162445
Current.setType(TT_BinaryOperator);
24172446
} else if (isStartOfName(Current) &&
24182447
(!Line.MightBeFunctionDecl || Current.NestingLevel != 0)) {
2419-
Contexts.back().FirstStartOfName = &Current;
2420-
Current.setType(TT_StartOfName);
2448+
if (isParametersKeyword(
2449+
Current,
2450+
findSurroundingFunctionWithKeywordedParameters(Current))) {
2451+
Current.setType(TT_FunctionParameterKeyword);
2452+
} else {
2453+
Contexts.back().FirstStartOfName = &Current;
2454+
Current.setType(TT_StartOfName);
2455+
}
24212456
} else if (Current.is(tok::semi)) {
24222457
// Reset FirstStartOfName after finding a semicolon so that a for loop
24232458
// with multiple increment statements is not confused with a for loop
@@ -3784,10 +3819,20 @@ void TokenAnnotator::annotate(AnnotatedLine &Line) {
37843819
static bool isFunctionDeclarationName(const LangOptions &LangOpts,
37853820
const FormatToken &Current,
37863821
const AnnotatedLine &Line,
3822+
const FormatStyle &Style,
37873823
FormatToken *&ClosingParen) {
37883824
if (Current.is(TT_FunctionDeclarationName))
37893825
return true;
37903826

3827+
if (Current.is(TT_FunctionLikeOrFreestandingMacro) &&
3828+
std::find_if(
3829+
Style.FunctionDeclarationsWithKeywords.begin(),
3830+
Style.FunctionDeclarationsWithKeywords.end(),
3831+
[&Current](const FormatStyle::FunctionDeclarationWithKeywords &Decl) {
3832+
return Current.TokenText == Decl.Name;
3833+
}) != Style.FunctionDeclarationsWithKeywords.end()) {
3834+
return true;
3835+
}
37913836
if (!Current.Tok.getIdentifierInfo())
37923837
return false;
37933838

@@ -3994,7 +4039,7 @@ void TokenAnnotator::calculateFormattingInformation(AnnotatedLine &Line) const {
39944039
AfterLastAttribute = Tok;
39954040
if (const bool IsCtorOrDtor = Tok->is(TT_CtorDtorDeclName);
39964041
IsCtorOrDtor ||
3997-
isFunctionDeclarationName(LangOpts, *Tok, Line, ClosingParen)) {
4042+
isFunctionDeclarationName(LangOpts, *Tok, Line, Style, ClosingParen)) {
39984043
if (!IsCtorOrDtor)
39994044
Tok->setFinalizedType(TT_FunctionDeclarationName);
40004045
LineIsFunctionDeclaration = true;
@@ -6218,7 +6263,8 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
62186263
Right.Next->isOneOf(TT_FunctionDeclarationName, tok::kw_const)));
62196264
}
62206265
if (Right.isOneOf(TT_StartOfName, TT_FunctionDeclarationName,
6221-
TT_ClassHeadName, tok::kw_operator)) {
6266+
TT_FunctionParameterKeyword, TT_ClassHeadName,
6267+
tok::kw_operator)) {
62226268
return true;
62236269
}
62246270
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

+57
Original file line numberDiff line numberDiff line change
@@ -29096,6 +29096,63 @@ 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(
29112+
"Q_PROPERTY(int name\n"
29113+
" READ name\n"
29114+
" WRITE setName\n"
29115+
" NOTIFY nameChanged)",
29116+
"Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
29117+
Style40);
29118+
verifyFormat("class A {\n"
29119+
" Q_PROPERTY(int name\n"
29120+
" READ name\n"
29121+
" WRITE setName\n"
29122+
" NOTIFY nameChanged)\n"
29123+
"};",
29124+
"class A { Q_PROPERTY(int name READ name WRITE setName NOTIFY "
29125+
"nameChanged) };",
29126+
Style40);
29127+
29128+
auto Style120 = getLLVMStyleWithColumns(120);
29129+
Style120.FunctionDeclarationsWithKeywords.push_back(QPropertyDeclaration);
29130+
Style120.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
29131+
29132+
verifyFormat(
29133+
"Q_PROPERTY(int name\n"
29134+
" READ name\n"
29135+
" WRITE setName\n"
29136+
" NOTIFY nameChanged)",
29137+
"Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
29138+
Style120);
29139+
verifyFormat("class A {\n"
29140+
" Q_PROPERTY(int name\n"
29141+
" READ name\n"
29142+
" WRITE setName\n"
29143+
" NOTIFY nameChanged)\n"
29144+
"};",
29145+
"class A { Q_PROPERTY(int name READ name WRITE setName NOTIFY "
29146+
"nameChanged) };",
29147+
Style120);
29148+
29149+
Style120.BinPackParameters = FormatStyle::BPPS_BinPack;
29150+
29151+
verifyFormat(
29152+
"Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
29153+
Style120);
29154+
}
29155+
2909929156
} // namespace
2910029157
} // namespace test
2910129158
} // 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)