Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang-format] add option to control bin-packing keyworded parameters #131605

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions clang/docs/ClangFormatStyleOptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4781,6 +4781,38 @@ the configuration (without a prefix: ``Auto``).
replaced with a single newline and form feed followed by the remaining
newlines.

.. _KeywordedFunctionLikeMacros:

**KeywordedFunctionLikeMacros** (``List of KeywordedFunctionLikeMacros``) :versionbadge:`clang-format 21` :ref:`¶ <KeywordedFunctionLikeMacros>`
Allows to format function-like macros with keyworded parameters according
to the BinPackParameters setting, treating keywords as parameter
separators.

Q_PROPERTY is an example of such a macro:

.. code-block:: c++

Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)

With ``BinPackParameters`` set to ``OnePerLine`` (or
``AlwaysOnePerLine``) and

.. code-block:: yaml

KeywordedFunctionLikeMacros:
- Name: "Q_PROPERTY"
Keywords: ['READ', 'WRITE', 'MEMBER', 'RESET', 'NOTIFY']

the line above will be split on these keywords:

.. code-block:: c++

Q_PROPERTY(
int name
READ name
WRITE setName
NOTIFY nameChanged)

.. _LambdaBodyIndentation:

**LambdaBodyIndentation** (``LambdaBodyIndentationKind``) :versionbadge:`clang-format 13` :ref:`¶ <LambdaBodyIndentation>`
Expand Down
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,8 @@ clang-format
top of the file.
- Add ``EnumTrailingComma`` option for inserting/removing commas at the end of
``enum`` enumerator lists.
- Allow to apply parameters bin-packing options to function-like macros that
use keywords to delimit parameters (e.g. Q_PROPERTY).

libclang
--------
Expand Down
1 change: 1 addition & 0 deletions clang/docs/tools/dump_format_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ class State:
"std::string",
"std::vector<std::string>",
"std::vector<IncludeCategory>",
"std::vector<KeywordedFunctionLikeMacro>",
"std::vector<RawStringFormat>",
"std::optional<unsigned>",
"deprecated",
Expand Down
1 change: 1 addition & 0 deletions clang/docs/tools/plurals.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Strings
IncludeCategories
KeywordedFunctionLikeMacros
RawStringFormats
40 changes: 40 additions & 0 deletions clang/include/clang/Format/Format.h
Original file line number Diff line number Diff line change
Expand Up @@ -3309,6 +3309,45 @@ struct FormatStyle {
/// \version 20
bool KeepFormFeed;

/// Function-like declaration with keyworded parameters.
/// Lists possible keywords for a named function-like macro.
struct KeywordedFunctionLikeMacro {
std::string Name;
std::vector<std::string> Keywords;

bool operator==(const KeywordedFunctionLikeMacro &Other) const {
return Name == Other.Name && Keywords == Other.Keywords;
}
};

/// Allows to format function-like macros with keyworded parameters according
/// to the BinPackParameters setting, treating keywords as parameter
/// separators.
///
/// Q_PROPERTY is an example of such a macro:
/// \code
/// Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)
/// \endcode
///
/// With ``BinPackParameters`` set to ``OnePerLine`` (or
/// ``AlwaysOnePerLine``) and
/// \code{.yaml}
/// KeywordedFunctionLikeMacros:
/// - Name: "Q_PROPERTY"
/// Keywords: ['READ', 'WRITE', 'MEMBER', 'RESET', 'NOTIFY']
/// \endcode
///
/// the line above will be split on these keywords:
/// \code
/// Q_PROPERTY(
/// int name
/// READ name
/// WRITE setName
/// NOTIFY nameChanged)
/// \endcode
/// \version 21
std::vector<KeywordedFunctionLikeMacro> KeywordedFunctionLikeMacros;

/// Indentation logic for lambda bodies.
enum LambdaBodyIndentationKind : int8_t {
/// Align lambda body relative to the lambda signature. This is the default.
Expand Down Expand Up @@ -5386,6 +5425,7 @@ struct FormatStyle {
JavaScriptWrapImports == R.JavaScriptWrapImports &&
KeepEmptyLines == R.KeepEmptyLines &&
KeepFormFeed == R.KeepFormFeed && Language == R.Language &&
KeywordedFunctionLikeMacros == R.KeywordedFunctionLikeMacros &&
LambdaBodyIndentation == R.LambdaBodyIndentation &&
LineEnding == R.LineEnding && MacroBlockBegin == R.MacroBlockBegin &&
MacroBlockEnd == R.MacroBlockEnd && Macros == R.Macros &&
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Format/ContinuationIndenter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,10 @@ bool ContinuationIndenter::canBreak(const LineState &State) {
}
}

// Don't break between function parameter keywords and parameter names.
if (Previous.is(TT_FunctionParameterKeyword) && Current.is(TT_StartOfName))
return false;

// Don't allow breaking before a closing brace of a block-indented braced list
// initializer if there isn't already a break.
if (Current.is(tok::r_brace) && Current.MatchingParen &&
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/Format/Format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

using clang::format::FormatStyle;

LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::KeywordedFunctionLikeMacro)
LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::RawStringFormat)

namespace llvm {
Expand Down Expand Up @@ -408,6 +409,14 @@ template <> struct MappingTraits<FormatStyle::KeepEmptyLinesStyle> {
}
};

template <> struct MappingTraits<FormatStyle::KeywordedFunctionLikeMacro> {
static void mapping(IO &IO,
FormatStyle::KeywordedFunctionLikeMacro &Function) {
IO.mapOptional("Name", Function.Name);
IO.mapOptional("Keywords", Function.Keywords);
}
};

template <> struct ScalarEnumerationTraits<FormatStyle::LanguageKind> {
static void enumeration(IO &IO, FormatStyle::LanguageKind &Value) {
IO.enumCase(Value, "C", FormatStyle::LK_C);
Expand Down Expand Up @@ -1082,6 +1091,8 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("JavaScriptWrapImports", Style.JavaScriptWrapImports);
IO.mapOptional("KeepEmptyLines", Style.KeepEmptyLines);
IO.mapOptional("KeepFormFeed", Style.KeepFormFeed);
IO.mapOptional("KeywordedFunctionLikeMacros",
Style.KeywordedFunctionLikeMacros);
IO.mapOptional("LambdaBodyIndentation", Style.LambdaBodyIndentation);
IO.mapOptional("LineEnding", Style.LineEnding);
IO.mapOptional("MacroBlockBegin", Style.MacroBlockBegin);
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Format/FormatToken.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ bool startsNextParameter(const FormatToken &Current, const FormatStyle &Style) {
}
if (Style.Language == FormatStyle::LK_Proto && Current.is(TT_SelectorName))
return true;
if (Current.is(TT_FunctionParameterKeyword))
return true;
return Previous.is(tok::comma) && !Current.isTrailingComment() &&
((Previous.isNot(TT_CtorInitializerComma) ||
Style.BreakConstructorInitializers !=
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Format/FormatToken.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ namespace format {
TYPE(FunctionDeclarationLParen) \
TYPE(FunctionLBrace) \
TYPE(FunctionLikeOrFreestandingMacro) \
TYPE(FunctionParameterKeyword) \
TYPE(FunctionTypeLParen) \
/* The colons as part of a C11 _Generic selection */ \
TYPE(GenericSelectionColon) \
Expand Down
47 changes: 44 additions & 3 deletions clang/lib/Format/TokenAnnotator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ static bool isCppAttribute(bool IsCpp, const FormatToken &Tok) {
return AttrTok && AttrTok->startsSequence(tok::r_square, tok::r_square);
}

static bool isParametersKeyword(
const FormatToken &Tok,
const FormatStyle::KeywordedFunctionLikeMacro *declaration) {
if (!declaration)
return false;

return std::find(declaration->Keywords.begin(), declaration->Keywords.end(),
Tok.TokenText) != declaration->Keywords.end();
}

/// A parser that gathers additional information about tokens.
///
/// The \c TokenAnnotator tries to match parenthesis and square brakets and
Expand Down Expand Up @@ -148,6 +158,32 @@ class AnnotatingParser {
}
}

const FormatStyle::KeywordedFunctionLikeMacro *
findKeywordedFunctionLikeMacro() const {
const FormatToken *TokBeforeFirstLParent = nullptr;
for (const FormatToken *T = Line.First; T != Line.Last; T = T->Next) {
if (T->Tok.is(tok::l_paren)) {
TokBeforeFirstLParent = T->getPreviousNonComment();
break;
}
}

// Unknown if line ends with ';', FunctionLikeOrFreestandingMacro otherwise.
if (!TokBeforeFirstLParent ||
!TokBeforeFirstLParent->isOneOf(TT_FunctionLikeOrFreestandingMacro,
TT_Unknown)) {
return nullptr;
}
auto I = std::find_if(
Style.KeywordedFunctionLikeMacros.begin(),
Style.KeywordedFunctionLikeMacros.end(),
[TokBeforeFirstLParent](
const FormatStyle::KeywordedFunctionLikeMacro &Declaration) {
return TokBeforeFirstLParent->TokenText == Declaration.Name;
});
return I != Style.KeywordedFunctionLikeMacros.end() ? &*I : nullptr;
}

bool parseAngle() {
if (!CurrentToken)
return false;
Expand Down Expand Up @@ -2419,8 +2455,12 @@ class AnnotatingParser {
Current.setType(TT_BinaryOperator);
} else if (isStartOfName(Current) &&
(!Line.MightBeFunctionDecl || Current.NestingLevel != 0)) {
Contexts.back().FirstStartOfName = &Current;
Current.setType(TT_StartOfName);
if (isParametersKeyword(Current, findKeywordedFunctionLikeMacro())) {
Current.setType(TT_FunctionParameterKeyword);
} else {
Contexts.back().FirstStartOfName = &Current;
Current.setType(TT_StartOfName);
}
} else if (Current.is(tok::semi)) {
// Reset FirstStartOfName after finding a semicolon so that a for loop
// with multiple increment statements is not confused with a for loop
Expand Down Expand Up @@ -6235,7 +6275,8 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
Right.Next->isOneOf(TT_FunctionDeclarationName, tok::kw_const)));
}
if (Right.isOneOf(TT_StartOfName, TT_FunctionDeclarationName,
TT_ClassHeadName, tok::kw_operator)) {
TT_FunctionParameterKeyword, TT_ClassHeadName,
tok::kw_operator)) {
return true;
}
if (Right.isAttribute())
Expand Down
10 changes: 10 additions & 0 deletions clang/unittests/Format/ConfigParseTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,16 @@ TEST(ConfigParseTest, ParsesConfiguration) {
FormatStyle::SDS_Leave);
CHECK_PARSE("SeparateDefinitionBlocks: Never", SeparateDefinitionBlocks,
FormatStyle::SDS_Never);

Style.KeywordedFunctionLikeMacros.clear();
std::vector<FormatStyle::KeywordedFunctionLikeMacro> ExpectedFunctions = {
{"MACRO_A", {"PARAM1", "KEYWORD", "KW_2"}}, {"macro", {"mKW1", "mKW2"}}};
CHECK_PARSE("KeywordedFunctionLikeMacros:\n"
" - Name: MACRO_A\n"
" Keywords: ['PARAM1', 'KEYWORD', 'KW_2']\n"
" - Name: macro\n"
" Keywords: [ \"mKW1\", \"mKW2\" ]\n",
KeywordedFunctionLikeMacros, ExpectedFunctions);
}

TEST(ConfigParseTest, ParsesConfigurationWithLanguages) {
Expand Down
59 changes: 59 additions & 0 deletions clang/unittests/Format/FormatTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29134,6 +29134,65 @@ TEST_F(FormatTest, BreakBeforeClassName) {
" ArenaSafeUniquePtr {};");
}

TEST_F(FormatTest, KeywordedFunctionLikeMacros) {
FormatStyle::KeywordedFunctionLikeMacro QPropertyDeclaration;
QPropertyDeclaration.Name = "Q_PROPERTY";
QPropertyDeclaration.Keywords.push_back("READ");
QPropertyDeclaration.Keywords.push_back("WRITE");
QPropertyDeclaration.Keywords.push_back("NOTIFY");
QPropertyDeclaration.Keywords.push_back("RESET");

auto Style40 = getLLVMStyleWithColumns(40);
Style40.KeywordedFunctionLikeMacros.push_back(QPropertyDeclaration);
Style40.BinPackParameters = FormatStyle::BPPS_OnePerLine;

verifyFormat("Q_PROPERTY(int name\n"
" READ name\n"
" WRITE setName\n"
" NOTIFY nameChanged)",
Style40);
verifyFormat("class A {\n"
" Q_PROPERTY(int name\n"
" READ name\n"
" WRITE setName\n"
" NOTIFY nameChanged)\n"
"};",
Style40);
verifyFormat("/* sdf */ Q_PROPERTY(int name\n"
" READ name\n"
" WRITE setName\n"
" NOTIFY nameChanged)",
Style40);

auto Style120 = getLLVMStyleWithColumns(120);
Style120.KeywordedFunctionLikeMacros.push_back(QPropertyDeclaration);
Style120.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;

verifyFormat("Q_PROPERTY(int name\n"
" READ name\n"
" WRITE setName\n"
" NOTIFY nameChanged)",
Style120);
verifyFormat("class A {\n"
" Q_PROPERTY(int name\n"
" READ name\n"
" WRITE setName\n"
" NOTIFY nameChanged)\n"
"};",
Style120);

Style120.BinPackParameters = FormatStyle::BPPS_BinPack;

verifyFormat(
"Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
Style120);

Style120.BinPackParameters = FormatStyle::BPPS_OnePerLine;
verifyFormat(
"Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
Style120);
}

} // namespace
} // namespace test
} // namespace format
Expand Down
33 changes: 33 additions & 0 deletions clang/unittests/Format/TokenAnnotatorTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3930,6 +3930,39 @@ TEST_F(TokenAnnotatorTest, UserDefinedConversionFunction) {
EXPECT_TOKEN(Tokens[5], tok::l_paren, TT_FunctionDeclarationLParen);
}

TEST_F(TokenAnnotatorTest, KeywordedFunctionLikeMacro) {
auto Style = getLLVMStyle();
FormatStyle::KeywordedFunctionLikeMacro QPropertyDeclaration;
QPropertyDeclaration.Name = "Q_PROPERTY";
QPropertyDeclaration.Keywords.push_back("READ");
QPropertyDeclaration.Keywords.push_back("WRITE");
QPropertyDeclaration.Keywords.push_back("NOTIFY");
QPropertyDeclaration.Keywords.push_back("RESET");
Style.KeywordedFunctionLikeMacros.push_back(QPropertyDeclaration);

auto Tokens = annotate(
"Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)",
Style);
ASSERT_EQ(Tokens.size(), 12u) << Tokens;
EXPECT_TOKEN(Tokens[0], tok::identifier, TT_Unknown);
EXPECT_TOKEN(Tokens[4], tok::identifier, TT_FunctionParameterKeyword);
EXPECT_TOKEN(Tokens[5], tok::identifier, TT_StartOfName);
EXPECT_TOKEN(Tokens[6], tok::identifier, TT_FunctionParameterKeyword);
EXPECT_TOKEN(Tokens[7], tok::identifier, TT_StartOfName);
EXPECT_TOKEN(Tokens[8], tok::identifier, TT_FunctionParameterKeyword);
EXPECT_TOKEN(Tokens[9], tok::identifier, TT_StartOfName);

Tokens = annotate("struct S { Q_OBJECT\n"
"Q_PROPERTY(int value READ value WRITE setValue "
"NOTIFY valueChanged)\n };",
Style);
ASSERT_EQ(Tokens.size(), 18u) << Tokens;
EXPECT_TOKEN(Tokens[4], tok::identifier, TT_FunctionLikeOrFreestandingMacro);
EXPECT_TOKEN(Tokens[8], tok::identifier, TT_FunctionParameterKeyword);
EXPECT_TOKEN(Tokens[10], tok::identifier, TT_FunctionParameterKeyword);
EXPECT_TOKEN(Tokens[12], tok::identifier, TT_FunctionParameterKeyword);
}

} // namespace
} // namespace format
} // namespace clang