Skip to content

Commit bdc4b9d

Browse files
Eugene Shalyginzeule
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 111de45 commit bdc4b9d

File tree

13 files changed

+240
-3
lines changed

13 files changed

+240
-3
lines changed

clang/docs/ClangFormatStyleOptions.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4784,6 +4784,38 @@ the configuration (without a prefix: ``Auto``).
47844784
replaced with a single newline and form feed followed by the remaining
47854785
newlines.
47864786

4787+
.. _KeywordedFunctionLikeMacros:
4788+
4789+
**KeywordedFunctionLikeMacros** (``List of KeywordedFunctionLikeMacros``) :versionbadge:`clang-format 21` :ref:`<KeywordedFunctionLikeMacros>`
4790+
Allows to format function-like macros with keyworded parameters according
4791+
to the BinPackParameters setting, treating keywords as parameter
4792+
separators.
4793+
4794+
Q_PROPERTY is an example of such a macro:
4795+
4796+
.. code-block:: c++
4797+
4798+
Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)
4799+
4800+
With ``BinPackParameters`` set to ``OnePerLine`` (or
4801+
``AlwaysOnePerLine``) and
4802+
4803+
.. code-block:: yaml
4804+
4805+
KeywordedFunctionLikeMacros:
4806+
- Name: "Q_PROPERTY"
4807+
Keywords: ['READ', 'WRITE', 'MEMBER', 'RESET', 'NOTIFY']
4808+
4809+
the line above will be split on these keywords:
4810+
4811+
.. code-block:: c++
4812+
4813+
Q_PROPERTY(
4814+
int name
4815+
READ name
4816+
WRITE setName
4817+
NOTIFY nameChanged)
4818+
47874819
.. _LambdaBodyIndentation:
47884820

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

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,8 @@ clang-format
475475
- Add ``SpaceInEmptyBraces`` option and set it to ``Always`` for WebKit style.
476476
- Add ``NumericLiteralCase`` option for enforcing character case in numeric
477477
literals.
478+
- Allow to apply parameters bin-packing options to function-like macros that
479+
use keywords to delimit parameters (e.g. Q_PROPERTY).
478480

479481
libclang
480482
--------

clang/docs/tools/dump_format_style.py

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3309,6 +3309,45 @@ struct FormatStyle {
33093309
/// \version 20
33103310
bool KeepFormFeed;
33113311

3312+
/// Function-like declaration with keyworded parameters.
3313+
/// Lists possible keywords for a named function-like macro.
3314+
struct KeywordedFunctionLikeMacro {
3315+
std::string Name;
3316+
std::vector<std::string> Keywords;
3317+
3318+
bool operator==(const KeywordedFunctionLikeMacro &Other) const {
3319+
return Name == Other.Name && Keywords == Other.Keywords;
3320+
}
3321+
};
3322+
3323+
/// Allows to format function-like macros with keyworded parameters according
3324+
/// to the BinPackParameters setting, treating keywords as parameter
3325+
/// separators.
3326+
///
3327+
/// Q_PROPERTY is an example of such a macro:
3328+
/// \code
3329+
/// Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)
3330+
/// \endcode
3331+
///
3332+
/// With ``BinPackParameters`` set to ``OnePerLine`` (or
3333+
/// ``AlwaysOnePerLine``) and
3334+
/// \code{.yaml}
3335+
/// KeywordedFunctionLikeMacros:
3336+
/// - Name: "Q_PROPERTY"
3337+
/// Keywords: ['READ', 'WRITE', 'MEMBER', 'RESET', 'NOTIFY']
3338+
/// \endcode
3339+
///
3340+
/// the line above will be split on these keywords:
3341+
/// \code
3342+
/// Q_PROPERTY(
3343+
/// int name
3344+
/// READ name
3345+
/// WRITE setName
3346+
/// NOTIFY nameChanged)
3347+
/// \endcode
3348+
/// \version 21
3349+
std::vector<KeywordedFunctionLikeMacro> KeywordedFunctionLikeMacros;
3350+
33123351
/// Indentation logic for lambda bodies.
33133352
enum LambdaBodyIndentationKind : int8_t {
33143353
/// Align lambda body relative to the lambda signature. This is the default.
@@ -5528,6 +5567,7 @@ struct FormatStyle {
55285567
JavaScriptWrapImports == R.JavaScriptWrapImports &&
55295568
KeepEmptyLines == R.KeepEmptyLines &&
55305569
KeepFormFeed == R.KeepFormFeed && Language == R.Language &&
5570+
KeywordedFunctionLikeMacros == R.KeywordedFunctionLikeMacros &&
55315571
LambdaBodyIndentation == R.LambdaBodyIndentation &&
55325572
LineEnding == R.LineEnding && MacroBlockBegin == R.MacroBlockBegin &&
55335573
MacroBlockEnd == R.MacroBlockEnd && Macros == R.Macros &&

clang/lib/Format/ContinuationIndenter.cpp

Lines changed: 4 additions & 0 deletions
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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
using clang::format::FormatStyle;
3232

33+
LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::KeywordedFunctionLikeMacro)
3334
LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::RawStringFormat)
3435

3536
namespace llvm {
@@ -410,6 +411,14 @@ template <> struct MappingTraits<FormatStyle::KeepEmptyLinesStyle> {
410411
}
411412
};
412413

414+
template <> struct MappingTraits<FormatStyle::KeywordedFunctionLikeMacro> {
415+
static void mapping(IO &IO,
416+
FormatStyle::KeywordedFunctionLikeMacro &Function) {
417+
IO.mapOptional("Name", Function.Name);
418+
IO.mapOptional("Keywords", Function.Keywords);
419+
}
420+
};
421+
413422
template <> struct ScalarEnumerationTraits<FormatStyle::LanguageKind> {
414423
static void enumeration(IO &IO, FormatStyle::LanguageKind &Value) {
415424
IO.enumCase(Value, "C", FormatStyle::LK_C);
@@ -1130,6 +1139,8 @@ template <> struct MappingTraits<FormatStyle> {
11301139
IO.mapOptional("JavaScriptWrapImports", Style.JavaScriptWrapImports);
11311140
IO.mapOptional("KeepEmptyLines", Style.KeepEmptyLines);
11321141
IO.mapOptional("KeepFormFeed", Style.KeepFormFeed);
1142+
IO.mapOptional("KeywordedFunctionLikeMacros",
1143+
Style.KeywordedFunctionLikeMacros);
11331144
IO.mapOptional("LambdaBodyIndentation", Style.LambdaBodyIndentation);
11341145
IO.mapOptional("LineEnding", Style.LineEnding);
11351146
IO.mapOptional("MacroBlockBegin", Style.MacroBlockBegin);

clang/lib/Format/FormatToken.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ bool startsNextParameter(const FormatToken &Current, const FormatStyle &Style) {
329329
}
330330
if (Style.Language == FormatStyle::LK_Proto && Current.is(TT_SelectorName))
331331
return true;
332+
if (Current.is(TT_FunctionParameterKeyword))
333+
return true;
332334
return Previous.is(tok::comma) && !Current.isTrailingComment() &&
333335
((Previous.isNot(TT_CtorInitializerComma) ||
334336
Style.BreakConstructorInitializers !=

clang/lib/Format/FormatToken.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ namespace format {
8585
TYPE(FunctionLBrace) \
8686
TYPE(FunctionLikeMacro) \
8787
TYPE(FunctionLikeOrFreestandingMacro) \
88+
TYPE(FunctionParameterKeyword) \
8889
TYPE(FunctionTypeLParen) \
8990
/* The colons as part of a C11 _Generic selection */ \
9091
TYPE(GenericSelectionColon) \

clang/lib/Format/TokenAnnotator.cpp

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ static bool isCppAttribute(bool IsCpp, const FormatToken &Tok) {
115115
return AttrTok && AttrTok->startsSequence(tok::r_square, tok::r_square);
116116
}
117117

118+
static bool isParametersKeyword(
119+
const FormatToken &Tok,
120+
const FormatStyle::KeywordedFunctionLikeMacro &declaration) {
121+
return std::find(declaration.Keywords.begin(), declaration.Keywords.end(),
122+
Tok.TokenText) != declaration.Keywords.end();
123+
}
124+
118125
/// A parser that gathers additional information about tokens.
119126
///
120127
/// The \c TokenAnnotator tries to match parenthesis and square brakets and
@@ -146,6 +153,25 @@ class AnnotatingParser {
146153
}
147154
}
148155

156+
const FormatStyle::KeywordedFunctionLikeMacro *
157+
findKeywordedFunctionLikeMacro(const FormatToken &LParen) {
158+
const FormatToken *TokBeforeFirstLParent = LParen.getPreviousNonComment();
159+
// Unknown if line ends with ';', FunctionLikeOrFreestandingMacro otherwise.
160+
if (!TokBeforeFirstLParent ||
161+
!TokBeforeFirstLParent->isOneOf(TT_FunctionLikeOrFreestandingMacro,
162+
TT_Unknown)) {
163+
return nullptr;
164+
}
165+
auto I = std::find_if(
166+
Style.KeywordedFunctionLikeMacros.begin(),
167+
Style.KeywordedFunctionLikeMacros.end(),
168+
[TokBeforeFirstLParent](
169+
const FormatStyle::KeywordedFunctionLikeMacro &Declaration) {
170+
return TokBeforeFirstLParent->TokenText == Declaration.Name;
171+
});
172+
return I != Style.KeywordedFunctionLikeMacros.end() ? &*I : nullptr;
173+
}
174+
149175
bool parseAngle() {
150176
if (!CurrentToken)
151177
return false;
@@ -406,6 +432,10 @@ class AnnotatingParser {
406432
OpeningParen.Previous &&
407433
OpeningParen.Previous->isOneOf(tok::kw_for, tok::kw_catch);
408434
Contexts.back().IsExpression = !IsForOrCatch;
435+
} else if (const FormatStyle::KeywordedFunctionLikeMacro *macroStyle =
436+
findKeywordedFunctionLikeMacro(OpeningParen)) {
437+
Contexts.back().ContextType = Context::KeywordedFunctionLikeMacro;
438+
Contexts.back().KeywordedFunctionLikeMacroStyle = macroStyle;
409439
}
410440

411441
if (Style.isTableGen()) {
@@ -2141,6 +2171,8 @@ class AnnotatingParser {
21412171
bool ColonIsObjCMethodExpr = false;
21422172
FormatToken *FirstObjCSelectorName = nullptr;
21432173
FormatToken *FirstStartOfName = nullptr;
2174+
const FormatStyle::KeywordedFunctionLikeMacro
2175+
*KeywordedFunctionLikeMacroStyle = nullptr;
21442176
bool CanBeExpression = true;
21452177
bool CaretFound = false;
21462178
bool InCpp11AttributeSpecifier = false;
@@ -2171,6 +2203,8 @@ class AnnotatingParser {
21712203
C11GenericSelection,
21722204
// Like in the outer parentheses in `ffnand ff1(.q());`.
21732205
VerilogInstancePortList,
2206+
// Like in Q_PROPERTY(...)
2207+
KeywordedFunctionLikeMacro,
21742208
} ContextType = Unknown;
21752209
};
21762210

@@ -2409,8 +2443,14 @@ class AnnotatingParser {
24092443
Current.setType(TT_BinaryOperator);
24102444
} else if (isStartOfName(Current) &&
24112445
(!Line.MightBeFunctionDecl || Current.NestingLevel != 0)) {
2412-
Contexts.back().FirstStartOfName = &Current;
2413-
Current.setType(TT_StartOfName);
2446+
if (Contexts.back().ContextType == Context::KeywordedFunctionLikeMacro &&
2447+
isParametersKeyword(
2448+
Current, *Contexts.back().KeywordedFunctionLikeMacroStyle)) {
2449+
Current.setType(TT_FunctionParameterKeyword);
2450+
} else {
2451+
Contexts.back().FirstStartOfName = &Current;
2452+
Current.setType(TT_StartOfName);
2453+
}
24142454
} else if (Current.is(tok::semi)) {
24152455
// Reset FirstStartOfName after finding a semicolon so that a for loop
24162456
// with multiple increment statements is not confused with a for loop
@@ -6254,7 +6294,8 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
62546294
Right.Next->isOneOf(TT_FunctionDeclarationName, tok::kw_const)));
62556295
}
62566296
if (Right.isOneOf(TT_StartOfName, TT_FunctionDeclarationName,
6257-
TT_ClassHeadName, tok::kw_operator)) {
6297+
TT_FunctionParameterKeyword, TT_ClassHeadName,
6298+
tok::kw_operator)) {
62586299
return true;
62596300
}
62606301
if (Left.is(TT_PointerOrReference))

0 commit comments

Comments
 (0)