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

>From f5caba3d2b793ad0591a7bba2d26fdbd5de11f1b 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/tools/dump_format_style.py         |  1 +
 clang/docs/tools/plurals.txt                  |  1 +
 clang/include/clang/Format/Format.h           | 44 ++++++++++++++
 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           | 54 ++++++++++++++++--
 clang/unittests/Format/ConfigParseTest.cpp    | 10 ++++
 clang/unittests/Format/FormatTest.cpp         | 57 +++++++++++++++++++
 clang/unittests/Format/TokenAnnotatorTest.cpp | 33 +++++++++++
 11 files changed, 214 insertions(+), 4 deletions(-)

diff --git a/clang/docs/tools/dump_format_style.py 
b/clang/docs/tools/dump_format_style.py
index f035143f6b3d1..ef0763b0d13f1 100755
--- a/clang/docs/tools/dump_format_style.py
+++ b/clang/docs/tools/dump_format_style.py
@@ -461,6 +461,7 @@ class State:
                 "int",
                 "std::string",
                 "std::vector<std::string>",
+                "std::vector<FunctionDeclarationWithKeywords>",
                 "std::vector<IncludeCategory>",
                 "std::vector<RawStringFormat>",
                 "std::optional<unsigned>",
diff --git a/clang/docs/tools/plurals.txt b/clang/docs/tools/plurals.txt
index e20b7f970ba43..445b5b2dc0f92 100644
--- a/clang/docs/tools/plurals.txt
+++ b/clang/docs/tools/plurals.txt
@@ -1,3 +1,4 @@
 Strings
+FunctionDeclarationWithKeywordes
 IncludeCategories
 RawStringFormats
diff --git a/clang/include/clang/Format/Format.h 
b/clang/include/clang/Format/Format.h
index fec47a248abb4..c2466da722467 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -2753,6 +2753,48 @@ struct FormatStyle {
   /// \version 3.7
   std::vector<std::string> ForEachMacros;
 
+  /// Function-like declaration with keyworded parameters.
+  /// Lists possible keywords for a named macro-like function
+  struct FunctionDeclarationWithKeywords {
+    std::string Name;
+    std::vector<std::string> Keywords;
+
+    bool operator==(const FunctionDeclarationWithKeywords &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
+  /// sepratators.
+  ///
+  /// 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}
+  ///   FunctionDeclarationsWithKeywords:
+  ///   - 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<FunctionDeclarationWithKeywords> 
FunctionDeclarationsWithKeywords;
+
   tooling::IncludeStyle IncludeStyle;
 
   /// A vector of macros that should be interpreted as conditionals
@@ -5327,6 +5369,8 @@ struct FormatStyle {
                R.ExperimentalAutoDetectBinPacking &&
            FixNamespaceComments == R.FixNamespaceComments &&
            ForEachMacros == R.ForEachMacros &&
+           FunctionDeclarationsWithKeywords ==
+               R.FunctionDeclarationsWithKeywords &&
            IncludeStyle.IncludeBlocks == R.IncludeStyle.IncludeBlocks &&
            IncludeStyle.IncludeCategories == R.IncludeStyle.IncludeCategories 
&&
            IncludeStyle.IncludeIsMainRegex ==
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 c67db4752e87b..1a7b2871531b9 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -29,6 +29,7 @@
 using clang::format::FormatStyle;
 
 LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::RawStringFormat)
+LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::FunctionDeclarationWithKeywords)
 
 namespace llvm {
 namespace yaml {
@@ -852,6 +853,14 @@ struct ScalarEnumerationTraits<
   }
 };
 
+template <> struct MappingTraits<FormatStyle::FunctionDeclarationWithKeywords> 
{
+  static void mapping(IO &IO,
+                      FormatStyle::FunctionDeclarationWithKeywords &Function) {
+    IO.mapOptional("Name", Function.Name);
+    IO.mapOptional("Keywords", Function.Keywords);
+  }
+};
+
 template <> struct MappingTraits<FormatStyle> {
   static void mapping(IO &IO, FormatStyle &Style) {
     // When reading, read the language first, we need it for 
getPredefinedStyle.
@@ -1046,6 +1055,8 @@ template <> struct MappingTraits<FormatStyle> {
                    Style.ExperimentalAutoDetectBinPacking);
     IO.mapOptional("FixNamespaceComments", Style.FixNamespaceComments);
     IO.mapOptional("ForEachMacros", Style.ForEachMacros);
+    IO.mapOptional("FunctionDeclarationsWithKeywords",
+                   Style.FunctionDeclarationsWithKeywords);
     IO.mapOptional("IfMacros", Style.IfMacros);
     IO.mapOptional("IncludeBlocks", Style.IncludeStyle.IncludeBlocks);
     IO.mapOptional("IncludeCategories", Style.IncludeStyle.IncludeCategories);
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 3808872d227a9..5580b5ab95ab7 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 35577cd6db7a1..d320fd22a6044 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::FunctionDeclarationWithKeywords *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,25 @@ class AnnotatingParser {
     }
   }
 
+  const FormatStyle::FunctionDeclarationWithKeywords *
+  findSurroundingFunctionWithKeywordedParameters(
+      const FormatToken &Token) const {
+    const FormatToken *Previous = &Token;
+    while (auto Prev = Previous->getPreviousNonComment())
+      Previous = Prev;
+    // Unknown if line ends with ';', FunctionLikeOrFreestandingMacro 
otherwise.
+    if (!Previous->isOneOf(TT_FunctionLikeOrFreestandingMacro, TT_Unknown))
+      return nullptr;
+    auto I = std::find_if(
+        Style.FunctionDeclarationsWithKeywords.begin(),
+        Style.FunctionDeclarationsWithKeywords.end(),
+        [Previous](
+            const FormatStyle::FunctionDeclarationWithKeywords &Declaration) {
+          return Previous->TokenText == Declaration.Name;
+        });
+    return I != Style.FunctionDeclarationsWithKeywords.end() ? &*I : nullptr;
+  }
+
   bool parseAngle() {
     if (!CurrentToken)
       return false;
@@ -2416,8 +2445,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 (isParametersKeyword(
+              Current,
+              findSurroundingFunctionWithKeywordedParameters(Current))) {
+        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
@@ -3784,10 +3819,20 @@ void TokenAnnotator::annotate(AnnotatedLine &Line) {
 static bool isFunctionDeclarationName(const LangOptions &LangOpts,
                                       const FormatToken &Current,
                                       const AnnotatedLine &Line,
+                                      const FormatStyle &Style,
                                       FormatToken *&ClosingParen) {
   if (Current.is(TT_FunctionDeclarationName))
     return true;
 
+  if (Current.is(TT_FunctionLikeOrFreestandingMacro) &&
+      std::find_if(
+          Style.FunctionDeclarationsWithKeywords.begin(),
+          Style.FunctionDeclarationsWithKeywords.end(),
+          [&Current](const FormatStyle::FunctionDeclarationWithKeywords &Decl) 
{
+            return Current.TokenText == Decl.Name;
+          }) != Style.FunctionDeclarationsWithKeywords.end()) {
+    return true;
+  }
   if (!Current.Tok.getIdentifierInfo())
     return false;
 
@@ -3994,7 +4039,7 @@ void 
TokenAnnotator::calculateFormattingInformation(AnnotatedLine &Line) const {
       AfterLastAttribute = Tok;
     if (const bool IsCtorOrDtor = Tok->is(TT_CtorDtorDeclName);
         IsCtorOrDtor ||
-        isFunctionDeclarationName(LangOpts, *Tok, Line, ClosingParen)) {
+        isFunctionDeclarationName(LangOpts, *Tok, Line, Style, ClosingParen)) {
       if (!IsCtorOrDtor)
         Tok->setFinalizedType(TT_FunctionDeclarationName);
       LineIsFunctionDeclaration = true;
@@ -6218,7 +6263,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 cc42642f99252..517ab72d17206 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -1105,6 +1105,16 @@ TEST(ConfigParseTest, ParsesConfiguration) {
               FormatStyle::SDS_Leave);
   CHECK_PARSE("SeparateDefinitionBlocks: Never", SeparateDefinitionBlocks,
               FormatStyle::SDS_Never);
+
+  Style.FunctionDeclarationsWithKeywords.clear();
+  std::vector<FormatStyle::FunctionDeclarationWithKeywords> ExpectedFunctions =
+      {{"MACRO_A", {"PARAM1", "KEYWORD", "KW_2"}}, {"macro", {"mKW1", 
"mKW2"}}};
+  CHECK_PARSE("FunctionDeclarationsWithKeywords:\n"
+              "  - Name: MACRO_A\n"
+              "    Keywords: ['PARAM1', 'KEYWORD', 'KW_2']\n"
+              "  - Name: macro\n"
+              "    Keywords: [ \"mKW1\", \"mKW2\" ]\n",
+              FunctionDeclarationsWithKeywords, ExpectedFunctions);
 }
 
 TEST(ConfigParseTest, ParsesConfigurationWithLanguages) {
diff --git a/clang/unittests/Format/FormatTest.cpp 
b/clang/unittests/Format/FormatTest.cpp
index 5df7865f5a629..efb08c1dccee0 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -29096,6 +29096,63 @@ TEST_F(FormatTest, BreakBeforeClassName) {
                "    ArenaSafeUniquePtr {};");
 }
 
+TEST_F(FormatTest, FunctionDeclarationWithKeywords) {
+  FormatStyle::FunctionDeclarationWithKeywords 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.FunctionDeclarationsWithKeywords.push_back(QPropertyDeclaration);
+  Style40.BinPackParameters = FormatStyle::BPPS_OnePerLine;
+
+  verifyFormat(
+      "Q_PROPERTY(int name\n"
+      "           READ name\n"
+      "           WRITE setName\n"
+      "           NOTIFY nameChanged)",
+      "Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
+      Style40);
+  verifyFormat("class A {\n"
+               "  Q_PROPERTY(int name\n"
+               "             READ name\n"
+               "             WRITE setName\n"
+               "             NOTIFY nameChanged)\n"
+               "};",
+               "class A { Q_PROPERTY(int name READ name WRITE setName NOTIFY "
+               "nameChanged) };",
+               Style40);
+
+  auto Style120 = getLLVMStyleWithColumns(120);
+  Style120.FunctionDeclarationsWithKeywords.push_back(QPropertyDeclaration);
+  Style120.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
+
+  verifyFormat(
+      "Q_PROPERTY(int name\n"
+      "           READ name\n"
+      "           WRITE setName\n"
+      "           NOTIFY nameChanged)",
+      "Q_PROPERTY(int name READ name WRITE setName NOTIFY nameChanged)",
+      Style120);
+  verifyFormat("class A {\n"
+               "  Q_PROPERTY(int name\n"
+               "             READ name\n"
+               "             WRITE setName\n"
+               "             NOTIFY nameChanged)\n"
+               "};",
+               "class A { Q_PROPERTY(int name READ name WRITE setName NOTIFY "
+               "nameChanged) };",
+               Style120);
+
+  Style120.BinPackParameters = FormatStyle::BPPS_BinPack;
+
+  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 1ff785110fc34..df23d862b3554 100644
--- a/clang/unittests/Format/TokenAnnotatorTest.cpp
+++ b/clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -3906,6 +3906,39 @@ TEST_F(TokenAnnotatorTest, 
UserDefinedConversionFunction) {
   EXPECT_TOKEN(Tokens[5], tok::l_paren, TT_FunctionDeclarationLParen);
 }
 
+TEST_F(TokenAnnotatorTest, FunctionDeclarationWithKeywords) {
+  auto Style = getLLVMStyle();
+  FormatStyle::FunctionDeclarationWithKeywords 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.FunctionDeclarationsWithKeywords.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