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

>From 352742af34d52dd265cc01ff47ca73047aa423e5 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 c4e82678949ff..3b9c584410864 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -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
 --------
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 dfb59e8d6f420..bbe2b8016a860 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;
@@ -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
@@ -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())
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 af9fd574b068c..bd5190acc8fe5 100644
--- a/clang/unittests/Format/TokenAnnotatorTest.cpp
+++ b/clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -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

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to