https://github.com/zeule updated 
https://github.com/llvm/llvm-project/pull/131605

>From bdc4b9df9946eb2575a47d494768bfeed106eba4 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 6be4d512bda6a..eb15ad31f5c0c 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -4784,6 +4784,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 6eb2a52e80ba9..591aedbac8f28 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -475,6 +475,8 @@ clang-format
 - Add ``SpaceInEmptyBraces`` option and set it to ``Always`` for WebKit style.
 - Add ``NumericLiteralCase`` option for enforcing character case in numeric
   literals.
+- Allow to apply parameters bin-packing options to function-like macros that
+  use keywords to delimit parameters (e.g. Q_PROPERTY).
 
 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 03cff5f8cfb66..bd7ee56ebc22f 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.
@@ -5528,6 +5567,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 888d0faf80931..aa855c4384b9d 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 f095d2c18cfcf..f8778836b7a2e 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -30,6 +30,7 @@
 
 using clang::format::FormatStyle;
 
+LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::KeywordedFunctionLikeMacro)
 LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::RawStringFormat)
 
 namespace llvm {
@@ -410,6 +411,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);
@@ -1130,6 +1139,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 0d8ae1c4a77eb..3d21dd421a302 100644
--- a/clang/lib/Format/FormatToken.cpp
+++ b/clang/lib/Format/FormatToken.cpp
@@ -329,6 +329,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 9252a795a0b5e..540195304df1e 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -85,6 +85,7 @@ namespace format {
   TYPE(FunctionLBrace)                                                         
\
   TYPE(FunctionLikeMacro)                                                      
\
   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 bbb7ef2c337d6..4a0869f00cc23 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -115,6 +115,13 @@ 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) {
+  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
@@ -146,6 +153,25 @@ class AnnotatingParser {
     }
   }
 
+  const FormatStyle::KeywordedFunctionLikeMacro *
+  findKeywordedFunctionLikeMacro(const FormatToken &LParen) {
+    const FormatToken *TokBeforeFirstLParent = LParen.getPreviousNonComment();
+    // 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;
@@ -406,6 +432,10 @@ class AnnotatingParser {
           OpeningParen.Previous &&
           OpeningParen.Previous->isOneOf(tok::kw_for, tok::kw_catch);
       Contexts.back().IsExpression = !IsForOrCatch;
+    } else if (const FormatStyle::KeywordedFunctionLikeMacro *macroStyle =
+                   findKeywordedFunctionLikeMacro(OpeningParen)) {
+      Contexts.back().ContextType = Context::KeywordedFunctionLikeMacro;
+      Contexts.back().KeywordedFunctionLikeMacroStyle = macroStyle;
     }
 
     if (Style.isTableGen()) {
@@ -2141,6 +2171,8 @@ class AnnotatingParser {
     bool ColonIsObjCMethodExpr = false;
     FormatToken *FirstObjCSelectorName = nullptr;
     FormatToken *FirstStartOfName = nullptr;
+    const FormatStyle::KeywordedFunctionLikeMacro
+        *KeywordedFunctionLikeMacroStyle = nullptr;
     bool CanBeExpression = true;
     bool CaretFound = false;
     bool InCpp11AttributeSpecifier = false;
@@ -2171,6 +2203,8 @@ class AnnotatingParser {
       C11GenericSelection,
       // Like in the outer parentheses in `ffnand ff1(.q());`.
       VerilogInstancePortList,
+      // Like in Q_PROPERTY(...)
+      KeywordedFunctionLikeMacro,
     } ContextType = Unknown;
   };
 
@@ -2409,8 +2443,14 @@ class AnnotatingParser {
       Current.setType(TT_BinaryOperator);
     } else if (isStartOfName(Current) &&
                (!Line.MightBeFunctionDecl || Current.NestingLevel != 0)) {
-      Contexts.back().FirstStartOfName = &Current;
-      Current.setType(TT_StartOfName);
+      if (Contexts.back().ContextType == Context::KeywordedFunctionLikeMacro &&
+          isParametersKeyword(
+              Current, *Contexts.back().KeywordedFunctionLikeMacroStyle)) {
+        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
@@ -6254,7 +6294,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 (Left.is(TT_PointerOrReference))
diff --git a/clang/unittests/Format/ConfigParseTest.cpp 
b/clang/unittests/Format/ConfigParseTest.cpp
index 7c993c0f8fd33..11f85a13880cb 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -1138,6 +1138,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 4e9d31895998f..9fb15265c937d 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -28701,6 +28701,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 141b0001cb52d..2862ea7f7378b 100644
--- a/clang/unittests/Format/TokenAnnotatorTest.cpp
+++ b/clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -4152,6 +4152,39 @@ TEST_F(TokenAnnotatorTest, LineCommentTrailingBackslash) 
{
   EXPECT_TOKEN(Tokens[1], tok::comment, TT_LineComment);
 }
 
+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

Reply via email to