https://github.com/zeule updated https://github.com/llvm/llvm-project/pull/131605
>From 5a1ed72f4be5ddd426a03ce1d1fb9cefc233942a Mon Sep 17 00:00:00 2001 From: Eugene Shalygin <e.shaly...@abberior-instruments.com> Date: Mon, 17 Mar 2025 11:23:35 +0100 Subject: [PATCH] [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. --- clang/docs/ClangFormatStyleOptions.rst | 32 ++++++++++ clang/docs/ReleaseNotes.rst | 2 + clang/docs/tools/dump_format_style.py | 1 + clang/docs/tools/plurals.txt | 1 + clang/include/clang/Format/Format.h | 40 +++++++++++++ clang/lib/Format/ContinuationIndenter.cpp | 4 ++ clang/lib/Format/Format.cpp | 11 ++++ clang/lib/Format/FormatToken.cpp | 2 + clang/lib/Format/FormatToken.h | 1 + clang/lib/Format/TokenAnnotator.cpp | 47 ++++++++++++++- clang/unittests/Format/ConfigParseTest.cpp | 10 ++++ clang/unittests/Format/FormatTest.cpp | 59 +++++++++++++++++++ clang/unittests/Format/TokenAnnotatorTest.cpp | 33 +++++++++++ 13 files changed, 240 insertions(+), 3 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 3f8a5f49313b2..d61bafc1b4dd0 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -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>` diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index daad01919ecd4..9c2e79da722d5 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -498,6 +498,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_OBJECT). libclang -------- diff --git a/clang/docs/tools/dump_format_style.py b/clang/docs/tools/dump_format_style.py index f035143f6b3d1..85732af8e0a60 100755 --- a/clang/docs/tools/dump_format_style.py +++ b/clang/docs/tools/dump_format_style.py @@ -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", diff --git a/clang/docs/tools/plurals.txt b/clang/docs/tools/plurals.txt index e20b7f970ba43..bd08c65df1c52 100644 --- a/clang/docs/tools/plurals.txt +++ b/clang/docs/tools/plurals.txt @@ -1,3 +1,4 @@ Strings IncludeCategories +KeywordedFunctionLikeMacros RawStringFormats diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index cea5e257659d6..846e5a8f7f94b 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -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. @@ -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 && diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp index 1969f4297b211..dd4297de73ac0 100644 --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -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 && diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index b74a8631efe0f..527b466bafde2 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -28,6 +28,7 @@ using clang::format::FormatStyle; +LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::KeywordedFunctionLikeMacro) LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::RawStringFormat) namespace llvm { @@ -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); @@ -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); diff --git a/clang/lib/Format/FormatToken.cpp b/clang/lib/Format/FormatToken.cpp index 7752139142430..28a49124cf21f 100644 --- a/clang/lib/Format/FormatToken.cpp +++ b/clang/lib/Format/FormatToken.cpp @@ -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 != diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h index a5c2388bb143d..cb340e3950823 100644 --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -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) \ diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index d87b3a6088bd8..bfca6426ee4fa 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -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 @@ -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; @@ -2415,8 +2451,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 @@ -6231,7 +6271,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()) diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 2b08b794792e9..4250a277125b0 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -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) { diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 4dfa135120605..f497cbb8ff48b 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -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 diff --git a/clang/unittests/Format/TokenAnnotatorTest.cpp b/clang/unittests/Format/TokenAnnotatorTest.cpp index ac5e979aea071..4223c82cbbb2f 100644 --- a/clang/unittests/Format/TokenAnnotatorTest.cpp +++ b/clang/unittests/Format/TokenAnnotatorTest.cpp @@ -3926,6 +3926,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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits