Author: Katze719
Date: 2026-08-17T20:53:52Z
New Revision: 3ec64dd5420f57abc780efa1bc935cfda02f6f8b

URL: 
https://github.com/llvm/llvm-project/commit/3ec64dd5420f57abc780efa1bc935cfda02f6f8b
DIFF: 
https://github.com/llvm/llvm-project/commit/3ec64dd5420f57abc780efa1bc935cfda02f6f8b.diff

LOG: [clang-format] Add support for additional C++ declaration specifiers in 
QualifierOrder (#160853)

Fixes #60866

This PR extends clang-formats `QualifierOrder` option to support
additional C++ declaration specifiers, addressing the limitation where
many common qualifiers were not recognized.

## Problem

Previously, `QualifierOrder` only supported a limited subset of C++
declaration specifiers:
- `const`, `volatile`, `static`, `inline`, `constexpr`, `restrict`,
`friend`

This meant that many common C++ qualifiers like `extern`, `mutable`,
`typedef`, `unsigned`, etc. were not recognized and could not be
properly reordered, limiting the usefulness of the feature.

## Solution

This PR adds support for 11 additional declaration specifiers:

**C++98/C99 specifiers:**
- `typedef` - Type definition specifier
- `extern` - External linkage specifier  
- `mutable` - Mutable member specifier
- `signed`, `unsigned` - Integer signedness specifiers
- `long`, `short` - Integer size specifiers

**C++11 specifiers:**
- `thread_local` - Thread-local storage duration specifier
- `explicit` - Explicit constructor/conversion specifier

**C++20 specifiers:**
- `consteval` - Immediate function specifier
- `constinit` - Constant initialization specifier

Added: 
    

Modified: 
    clang/docs/ClangFormatStyleOptions.md
    clang/docs/ReleaseNotes.md
    clang/include/clang/Format/Format.h
    clang/lib/Format/QualifierAlignmentFixer.cpp
    clang/unittests/Format/QualifierFixerTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ClangFormatStyleOptions.md 
b/clang/docs/ClangFormatStyleOptions.md
index 9b962e6e1e083..8230e6f5139e7 100644
--- a/clang/docs/ClangFormatStyleOptions.md
+++ b/clang/docs/ClangFormatStyleOptions.md
@@ -6393,6 +6393,17 @@ the configuration (without a prefix: `Auto`).
     - `constexpr`
     - `volatile`
     - `restrict`
+    - `typedef`
+    - `consteval`
+    - `constinit`
+    - `thread_local`
+    - `extern`
+    - `mutable`
+    - `signed`
+    - `unsigned`
+    - `long`
+    - `short`
+    - `explicit`
     - `type`
 
   :::{note}
@@ -6402,6 +6413,9 @@ the configuration (without a prefix: `Auto`).
   Items to the left of `type` will be placed to the left of the type and
   aligned in the order supplied. Items to the right of `type` will be
   placed to the right of the type and aligned in the order supplied.
+  If only one of `signed` and `unsigned` is specified, both are placed at
+  that position. The same applies to `long` and `short`. Specifying both
+  members of a pair allows them to be placed independently.
 
   ```yaml
   QualifierOrder: [inline, static, type, const, volatile]

diff  --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 0e40754efd30e..272227b1cdf1e 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -595,6 +595,10 @@ features cannot lower the translation-unit ABI level;
 - Add `SpacesInBlockComments` option to control spacing after `/*` and
   before `*/` in ordinary block comments.
 
+- `QualifierOrder` now supports `typedef`, `consteval`, `constinit`,
+  `thread_local`, `extern`, `mutable`, `signed`, `unsigned`, `long`, `short`,
+  and `explicit` declaration specifiers.
+
 ### libclang
 
 - visit identifier initializers in lambda capture as VarDecl instead of 
VariableRef. Warning: this changes behaviour.

diff  --git a/clang/include/clang/Format/Format.h 
b/clang/include/clang/Format/Format.h
index 3948337d2fc3d..49e4666ae5e99 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -4565,6 +4565,17 @@ struct FormatStyle {
   ///   * `constexpr`
   ///   * `volatile`
   ///   * `restrict`
+  ///   * `typedef`
+  ///   * `consteval`
+  ///   * `constinit`
+  ///   * `thread_local`
+  ///   * `extern`
+  ///   * `mutable`
+  ///   * `signed`
+  ///   * `unsigned`
+  ///   * `long`
+  ///   * `short`
+  ///   * `explicit`
   ///   * `type`
   ///
   /// \note
@@ -4574,6 +4585,9 @@ struct FormatStyle {
   /// Items to the left of `type` will be placed to the left of the type and
   /// aligned in the order supplied. Items to the right of `type` will be
   /// placed to the right of the type and aligned in the order supplied.
+  /// If only one of `signed` and `unsigned` is specified, both are placed at
+  /// that position. The same applies to `long` and `short`. Specifying both
+  /// members of a pair allows them to be placed independently.
   ///
   /// \code{.yaml}
   ///   QualifierOrder: [inline, static, type, const, volatile]

diff  --git a/clang/lib/Format/QualifierAlignmentFixer.cpp 
b/clang/lib/Format/QualifierAlignmentFixer.cpp
index 3d1a9dc868208..29c583855e3f5 100644
--- a/clang/lib/Format/QualifierAlignmentFixer.cpp
+++ b/clang/lib/Format/QualifierAlignmentFixer.cpp
@@ -30,24 +30,28 @@ void addQualifierAlignmentFixerPasses(const FormatStyle 
&Style,
   prepareLeftRightOrderingForQualifierAlignmentFixer(
       Style.QualifierOrder, LeftOrder, RightOrder, ConfiguredQualifierTokens);
 
+  const auto AddPass = [&](const std::string &Qualifier, bool RightAlign) {
+    Passes.emplace_back([&, Qualifier, ConfiguredQualifierTokens,
+                         RightAlign](const Environment &Env) {
+      return LeftRightQualifierAlignmentFixer(
+                 Env, Style, Qualifier, ConfiguredQualifierTokens, RightAlign)
+          .process();
+    });
+  };
+
   // Handle the left and right alignment separately.
   for (const auto &Qualifier : LeftOrder) {
-    Passes.emplace_back(
-        [&, Qualifier, ConfiguredQualifierTokens](const Environment &Env) {
-          return LeftRightQualifierAlignmentFixer(Env, Style, Qualifier,
-                                                  ConfiguredQualifierTokens,
-                                                  /*RightAlign=*/false)
-              .process();
-        });
+    AddPass(Qualifier, /*RightAlign=*/false);
+    // Unlike the other declaration specifiers, `long` can legally occur twice
+    // in the same sequence. A pass moves one occurrence across the type, so a
+    // second pass is needed for `long long` and is otherwise a no-op.
+    if (Qualifier == "long")
+      AddPass(Qualifier, /*RightAlign=*/false);
   }
   for (const auto &Qualifier : RightOrder) {
-    Passes.emplace_back(
-        [&, Qualifier, ConfiguredQualifierTokens](const Environment &Env) {
-          return LeftRightQualifierAlignmentFixer(Env, Style, Qualifier,
-                                                  ConfiguredQualifierTokens,
-                                                  /*RightAlign=*/true)
-              .process();
-        });
+    AddPass(Qualifier, /*RightAlign=*/true);
+    if (Qualifier == "long")
+      AddPass(Qualifier, /*RightAlign=*/true);
   }
 }
 
@@ -175,6 +179,13 @@ static bool isQualifier(const FormatToken *const Tok) {
   case tok::kw___ptr32:
   case tok::kw___ptr64:
   case tok::kw___funcref:
+  case tok::kw_typedef:
+  case tok::kw_consteval:
+  case tok::kw_constinit:
+  case tok::kw_thread_local:
+  case tok::kw_extern:
+  case tok::kw_mutable:
+  case tok::kw_explicit:
     return true;
   default:
     return false;
@@ -544,6 +555,17 @@ tok::TokenKind 
LeftRightQualifierAlignmentFixer::getTokenFromQualifier(
       .Case("constexpr", tok::kw_constexpr)
       .Case("restrict", tok::kw_restrict)
       .Case("friend", tok::kw_friend)
+      .Case("typedef", tok::kw_typedef)
+      .Case("consteval", tok::kw_consteval)
+      .Case("constinit", tok::kw_constinit)
+      .Case("thread_local", tok::kw_thread_local)
+      .Case("extern", tok::kw_extern)
+      .Case("mutable", tok::kw_mutable)
+      .Case("signed", tok::kw_signed)
+      .Case("unsigned", tok::kw_unsigned)
+      .Case("long", tok::kw_long)
+      .Case("short", tok::kw_short)
+      .Case("explicit", tok::kw_explicit)
       .Default(tok::identifier);
 }
 
@@ -623,9 +645,34 @@ void prepareLeftRightOrderingForQualifierAlignmentFixer(
 
     tok::TokenKind QualifierToken =
         LeftRightQualifierAlignmentFixer::getTokenFromQualifier(s);
-    if (QualifierToken != tok::kw_typeof && QualifierToken != tok::identifier)
+    if (QualifierToken != tok::kw_typeof && QualifierToken != tok::identifier) 
{
       Qualifiers.push_back(QualifierToken);
 
+      // Ensure signed/unsigned and long/short qualifier pairs are positioned
+      // together by default unless the user has explicitly specified both in
+      // the QualifierOrder. This allows users to override the default pairing
+      // by listing both qualifiers in the order.
+      auto AddPairedQualifier = [&](tok::TokenKind PairedToken,
+                                    const std::string &PairedName) {
+        if (!llvm::is_contained(Order, PairedName)) {
+          Qualifiers.push_back(PairedToken);
+          if (left)
+            LeftOrder.insert(LeftOrder.begin(), PairedName);
+          else
+            RightOrder.push_back(PairedName);
+        }
+      };
+
+      if (QualifierToken == tok::kw_unsigned)
+        AddPairedQualifier(tok::kw_signed, "signed");
+      else if (QualifierToken == tok::kw_signed)
+        AddPairedQualifier(tok::kw_unsigned, "unsigned");
+      else if (QualifierToken == tok::kw_long)
+        AddPairedQualifier(tok::kw_short, "short");
+      else if (QualifierToken == tok::kw_short)
+        AddPairedQualifier(tok::kw_long, "long");
+    }
+
     if (left) {
       // Reverse the order for left aligned items.
       LeftOrder.insert(LeftOrder.begin(), s);

diff  --git a/clang/unittests/Format/QualifierFixerTest.cpp 
b/clang/unittests/Format/QualifierFixerTest.cpp
index edde7bb3b5c97..87fca83a3d5be 100644
--- a/clang/unittests/Format/QualifierFixerTest.cpp
+++ b/clang/unittests/Format/QualifierFixerTest.cpp
@@ -50,6 +50,31 @@ TEST_F(QualifierFixerTest, RotateTokens) {
             tok::kw_restrict);
   EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("friend"),
             tok::kw_friend);
+  EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("typedef"),
+            tok::kw_typedef);
+  EXPECT_EQ(
+      LeftRightQualifierAlignmentFixer::getTokenFromQualifier("consteval"),
+      tok::kw_consteval);
+  EXPECT_EQ(
+      LeftRightQualifierAlignmentFixer::getTokenFromQualifier("constinit"),
+      tok::kw_constinit);
+  EXPECT_EQ(
+      LeftRightQualifierAlignmentFixer::getTokenFromQualifier("thread_local"),
+      tok::kw_thread_local);
+  EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("extern"),
+            tok::kw_extern);
+  EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("mutable"),
+            tok::kw_mutable);
+  EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("signed"),
+            tok::kw_signed);
+  
EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("unsigned"),
+            tok::kw_unsigned);
+  EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("long"),
+            tok::kw_long);
+  EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("short"),
+            tok::kw_short);
+  
EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("explicit"),
+            tok::kw_explicit);
 }
 
 TEST_F(QualifierFixerTest, FailQualifierInvalidConfiguration) {
@@ -1059,48 +1084,44 @@ TEST_F(QualifierFixerTest, IsQualifierType) {
   ConfiguredTokens.push_back(tok::kw_restrict);
   ConfiguredTokens.push_back(tok::kw_constexpr);
   ConfiguredTokens.push_back(tok::kw_friend);
+  ConfiguredTokens.push_back(tok::kw_typedef);
+  ConfiguredTokens.push_back(tok::kw_consteval);
+  ConfiguredTokens.push_back(tok::kw_constinit);
+  ConfiguredTokens.push_back(tok::kw_thread_local);
+  ConfiguredTokens.push_back(tok::kw_extern);
+  ConfiguredTokens.push_back(tok::kw_mutable);
+  ConfiguredTokens.push_back(tok::kw_unsigned);
+  ConfiguredTokens.push_back(tok::kw_long);
+  ConfiguredTokens.push_back(tok::kw_explicit);
+  ConfiguredTokens.push_back(tok::kw_signed);
+  ConfiguredTokens.push_back(tok::kw_short);
 
   TestLexer lexer{Allocator, Buffers};
   const auto LangOpts = getFormattingLangOpts();
 
   auto Tokens = lexer.lex(
-      "const static inline auto restrict int double long constexpr friend");
-  ASSERT_EQ(Tokens.size(), 11u) << Tokens;
-
-  EXPECT_TRUE(
-      isConfiguredQualifierOrType(Tokens[0], ConfiguredTokens, LangOpts));
-  EXPECT_TRUE(
-      isConfiguredQualifierOrType(Tokens[1], ConfiguredTokens, LangOpts));
-  EXPECT_TRUE(
-      isConfiguredQualifierOrType(Tokens[2], ConfiguredTokens, LangOpts));
-  EXPECT_TRUE(
-      isConfiguredQualifierOrType(Tokens[3], ConfiguredTokens, LangOpts));
-  EXPECT_TRUE(
-      isConfiguredQualifierOrType(Tokens[4], ConfiguredTokens, LangOpts));
-  EXPECT_TRUE(
-      isConfiguredQualifierOrType(Tokens[5], ConfiguredTokens, LangOpts));
-  EXPECT_TRUE(
-      isConfiguredQualifierOrType(Tokens[6], ConfiguredTokens, LangOpts));
-  EXPECT_TRUE(
-      isConfiguredQualifierOrType(Tokens[7], ConfiguredTokens, LangOpts));
-  EXPECT_TRUE(
-      isConfiguredQualifierOrType(Tokens[8], ConfiguredTokens, LangOpts));
-  EXPECT_TRUE(
-      isConfiguredQualifierOrType(Tokens[9], ConfiguredTokens, LangOpts));
-
-  EXPECT_TRUE(isQualifierOrType(Tokens[0], LangOpts));
-  EXPECT_TRUE(isQualifierOrType(Tokens[1], LangOpts));
-  EXPECT_TRUE(isQualifierOrType(Tokens[2], LangOpts));
-  EXPECT_TRUE(isQualifierOrType(Tokens[3], LangOpts));
-  EXPECT_TRUE(isQualifierOrType(Tokens[4], LangOpts));
-  EXPECT_TRUE(isQualifierOrType(Tokens[5], LangOpts));
-  EXPECT_TRUE(isQualifierOrType(Tokens[6], LangOpts));
-  EXPECT_TRUE(isQualifierOrType(Tokens[7], LangOpts));
-  EXPECT_TRUE(isQualifierOrType(Tokens[8], LangOpts));
-  EXPECT_TRUE(isQualifierOrType(Tokens[9], LangOpts));
+      "const static inline restrict constexpr friend typedef consteval "
+      "constinit thread_local extern mutable unsigned long explicit signed "
+      "short");
+  ASSERT_EQ(Tokens.size(), ConfiguredTokens.size() + 1) << Tokens;
+
+  for (size_t i = 0; i < ConfiguredTokens.size(); ++i) {
+    EXPECT_TRUE(isQualifierOrType(Tokens[i], LangOpts))
+        << "Token " << i << " should be recognized by isQualifierOrType";
+    EXPECT_TRUE(
+        isConfiguredQualifierOrType(Tokens[i], ConfiguredTokens, LangOpts))
+        << "Token " << i << " should be recognized";
+  }
+
+  auto TypeTokens = lexer.lex("int double");
+  for (size_t i = 0; i < TypeTokens.size() - 1; ++i) {
+    EXPECT_TRUE(isQualifierOrType(TypeTokens[i], LangOpts));
+    EXPECT_TRUE(
+        isConfiguredQualifierOrType(TypeTokens[i], ConfiguredTokens, 
LangOpts));
+  }
 
   auto NotTokens = lexer.lex("for while do Foo Bar ");
-  ASSERT_EQ(NotTokens.size(), 6u) << Tokens;
+  ASSERT_EQ(NotTokens.size(), 6u) << NotTokens;
 
   EXPECT_FALSE(
       isConfiguredQualifierOrType(NotTokens[0], ConfiguredTokens, LangOpts));
@@ -1440,6 +1461,108 @@ TEST_F(QualifierFixerTest, TemplatesLeft) {
                "TemplateType<Container const> t;", Style);
 }
 
+TEST_F(QualifierFixerTest, NewQualifierSupport) {
+  FormatStyle Style = getLLVMStyle();
+  Style.QualifierAlignment = FormatStyle::QAS_Custom;
+
+  Style.QualifierOrder = {"typedef", "type"};
+  verifyFormat("typedef int MyInt;", Style);
+
+  Style.QualifierOrder = {"consteval", "type"};
+  verifyFormat("consteval int func();", "int consteval func();", Style);
+  verifyFormat("consteval void func();", "void consteval func();", Style);
+
+  Style.QualifierOrder = {"constinit", "static", "type"};
+  verifyFormat("constinit static int var = 10;",
+               "static constinit int var = 10;", Style);
+
+  Style.QualifierOrder = {"thread_local", "static", "type"};
+  verifyFormat("thread_local static int counter;",
+               "static thread_local int counter;", Style);
+
+  Style.QualifierOrder = {"extern", "type"};
+  verifyFormat("extern int global_var;", "int extern global_var;", Style);
+
+  Style.QualifierOrder = {"mutable", "type"};
+  verifyFormat("mutable int cache;", "int mutable cache;", Style);
+
+  Style.QualifierOrder = {"unsigned", "type"};
+  verifyFormat("unsigned int num;", "int unsigned num;", Style);
+  verifyFormat("unsigned long long value;", "long long unsigned value;", 
Style);
+  verifyFormat("signed int num;", "int signed num;", Style);
+  verifyFormat("signed long long value;", "long long signed value;", Style);
+
+  Style.QualifierOrder = {"long", "type"};
+  verifyFormat("long int num;", "int long num;", Style);
+  verifyFormat("long unsigned int num;", "unsigned long int num;", Style);
+  verifyFormat("long long int num;", "int long long num;", Style);
+  verifyFormat("long long unsigned int num;", "unsigned long long int num;",
+               Style);
+
+  Style.QualifierOrder = {"short", "type"};
+  verifyFormat("short int num;", "int short num;", Style);
+  verifyFormat("short unsigned value;", "unsigned short value;", Style);
+
+  Style.QualifierOrder = {"explicit", "type"};
+  verifyFormat("explicit Foo(int x);", Style);
+
+  Style.QualifierOrder = {"extern", "thread_local", "static", "constexpr",
+                          "inline", "unsigned",     "long",   "type",
+                          "const",  "volatile"};
+
+  verifyFormat("extern thread_local static constexpr inline unsigned long int "
+               "const volatile var;",
+               "volatile const extern constexpr thread_local static inline "
+               "unsigned long int var;",
+               Style);
+}
+
+TEST_F(QualifierFixerTest, PairedQualifiersSamePosition) {
+  FormatStyle Style = getLLVMStyle();
+  Style.QualifierAlignment = FormatStyle::QAS_Custom;
+
+  Style.QualifierOrder = {"unsigned", "type"};
+  verifyFormat("unsigned int x;", "int unsigned x;", Style);
+  verifyFormat("signed int x;", "int signed x;", Style);
+  verifyFormat("unsigned long int y;", "long unsigned int y;", Style);
+  verifyFormat("signed long int y;", "long signed int y;", Style);
+
+  Style.QualifierOrder = {"signed", "type"};
+  verifyFormat("signed int x;", "int signed x;", Style);
+  verifyFormat("unsigned int x;", "int unsigned x;", Style);
+
+  Style.QualifierOrder = {"long", "type"};
+  verifyFormat("long int x;", "int long x;", Style);
+  verifyFormat("short int x;", "int short x;", Style);
+  verifyFormat("long unsigned int y;", "unsigned long int y;", Style);
+  verifyFormat("short unsigned int y;", "unsigned short int y;", Style);
+
+  Style.QualifierOrder = {"short", "type"};
+  verifyFormat("short int x;", "int short x;", Style);
+  verifyFormat("long int x;", "int long x;", Style);
+
+  Style.QualifierOrder = {"type", "unsigned"};
+  verifyFormat("int unsigned x;", "unsigned int x;", Style);
+  verifyFormat("int signed x;", "signed int x;", Style);
+
+  Style.QualifierOrder = {"type", "long"};
+  verifyFormat("int long x;", "long int x;", Style);
+  verifyFormat("int short x;", "short int x;", Style);
+  verifyFormat("int long long x;", "long long int x;", Style);
+
+  Style.QualifierOrder = {"static", "unsigned", "long", "type"};
+  verifyFormat("static unsigned long int x;", "long unsigned static int x;",
+               Style);
+  verifyFormat("static signed short int x;", "short signed static int x;",
+               Style);
+
+  Style.QualifierOrder = {"unsigned", "signed", "type"};
+  verifyFormat("unsigned signed int x;", "signed unsigned int x;", Style);
+
+  Style.QualifierOrder = {"long", "short", "type"};
+  verifyFormat("long short int x;", "short long int x;", Style);
+}
+
 TEST_F(QualifierFixerTest, Ranges) {
   FormatStyle Style = getLLVMStyle();
   Style.QualifierAlignment = FormatStyle::QAS_Custom;


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to