[clang] b579c5b - [clang-format] Fix spacing before :: after non-macro identifiers (#189024)

via cfe-commits cfe-commits at lists.llvm.org
Sun Mar 29 13:07:38 PDT 2026


Author: Lane0218
Date: 2026-03-29T22:07:32+02:00
New Revision: b579c5b0bcca1b6a8f145011307dcc222b28e4a5

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

LOG: [clang-format] Fix spacing before :: after non-macro identifiers (#189024)

This narrows clang-format's spacing heuristic for `identifier ::`.

Previously, clang-format preserved existing whitespace before `::` after
any
identifier, which caused inputs like:

```c++
template <typename T>
auto mem = &T :: member;
```

to format as:

```c++
template <typename T>
auto mem = &T ::member;
```

This patch preserves that whitespace only for identifiers that look like
object-like macros, such as the existing `ALWAYS_INLINE ::std::string`
case.
Ordinary identifiers now format as expected:

```c++
&T :: member
```

becomes

```c++
&T::member
```

Test:
- `./build-cir/tools/clang/unittests/Format/FormatTests
--gtest_filter=FormatTest.NestedNameSpecifiers`

Fixes #188754

Added: 
    

Modified: 
    clang/lib/Format/FormatToken.h
    clang/lib/Format/QualifierAlignmentFixer.cpp
    clang/lib/Format/QualifierAlignmentFixer.h
    clang/lib/Format/TokenAnnotator.cpp
    clang/unittests/Format/FormatTest.cpp
    clang/unittests/Format/QualifierFixerTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index ba9a95440f0c2..028ef440e689f 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -875,6 +875,33 @@ struct FormatToken {
     return Tok;
   }
 
+  /// Returns \c true if this token likely names an object-like macro.
+  ///
+  /// If \p AllowFollowingColonColon is \c true, a following \c :: does not
+  /// disqualify the token from being considered macro-like.
+  bool isPossibleMacro(bool AllowFollowingColonColon = false) const {
+    if (isNot(tok::identifier))
+      return false;
+
+    assert(!TokenText.empty());
+
+    // T, K, U, V likely could be template arguments.
+    if (TokenText.size() == 1)
+      return false;
+
+    // It's unlikely that qualified names are object-like macros.
+    const auto *Prev = getPreviousNonComment();
+    if (Prev && Prev->is(tok::coloncolon))
+      return false;
+    if (!AllowFollowingColonColon) {
+      const auto *Next = getNextNonComment();
+      if (Next && Next->is(tok::coloncolon))
+        return false;
+    }
+
+    return TokenText == TokenText.upper();
+  }
+
   /// Returns \c true if this token ends a block indented initializer list.
   [[nodiscard]] bool isBlockIndentedInitRBrace(const FormatStyle &Style) const;
 

diff  --git a/clang/lib/Format/QualifierAlignmentFixer.cpp b/clang/lib/Format/QualifierAlignmentFixer.cpp
index eb0f886fc8459..60f2958101c71 100644
--- a/clang/lib/Format/QualifierAlignmentFixer.cpp
+++ b/clang/lib/Format/QualifierAlignmentFixer.cpp
@@ -273,7 +273,7 @@ const FormatToken *LeftRightQualifierAlignmentFixer::analyzeRight(
     return Tok;
 
   // Stay safe and don't move past macros, also don't bother with sorting.
-  if (isPossibleMacro(TypeToken))
+  if (TypeToken->isPossibleMacro())
     return Tok;
 
   // The case `const long long int volatile` -> `long long int const volatile`
@@ -410,7 +410,7 @@ const FormatToken *LeftRightQualifierAlignmentFixer::analyzeLeft(
   }
 
   // Stay safe and don't move past macros, also don't bother with sorting.
-  if (isPossibleMacro(TypeToken))
+  if (TypeToken->isPossibleMacro())
     return Tok;
 
   // Examples given in order of ['const', 'volatile', 'type']
@@ -641,30 +641,5 @@ bool isConfiguredQualifierOrType(const FormatToken *Tok,
                  isConfiguredQualifier(Tok, Qualifiers));
 }
 
-// If a token is an identifier and it's upper case, it could
-// be a macro and hence we need to be able to ignore it.
-bool isPossibleMacro(const FormatToken *Tok) {
-  assert(Tok);
-  if (Tok->isNot(tok::identifier))
-    return false;
-
-  const auto Text = Tok->TokenText;
-  assert(!Text.empty());
-
-  // T,K,U,V likely could be template arguments
-  if (Text.size() == 1)
-    return false;
-
-  // It's unlikely that qualified names are object-like macros.
-  const auto *Prev = Tok->getPreviousNonComment();
-  if (Prev && Prev->is(tok::coloncolon))
-    return false;
-  const auto *Next = Tok->getNextNonComment();
-  if (Next && Next->is(tok::coloncolon))
-    return false;
-
-  return Text == Text.upper();
-}
-
 } // namespace format
 } // namespace clang

diff  --git a/clang/lib/Format/QualifierAlignmentFixer.h b/clang/lib/Format/QualifierAlignmentFixer.h
index a0a0d597ebf30..09fcd7b943e67 100644
--- a/clang/lib/Format/QualifierAlignmentFixer.h
+++ b/clang/lib/Format/QualifierAlignmentFixer.h
@@ -38,9 +38,6 @@ bool isConfiguredQualifierOrType(const FormatToken *Tok,
                                  const std::vector<tok::TokenKind> &Qualifiers,
                                  const LangOptions &LangOpts);
 
-// Is the Token likely a Macro
-bool isPossibleMacro(const FormatToken *Tok);
-
 class LeftRightQualifierAlignmentFixer : public TokenAnalyzer {
   std::string Qualifier;
   bool RightAlign;

diff  --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 1ba0d7e891b09..e920b064c0567 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5646,10 +5646,9 @@ bool TokenAnnotator::spaceRequiredBefore(const AnnotatedLine &Line,
     return false;
   }
   if (Right.is(tok::coloncolon) && Left.is(tok::identifier)) {
-    // Generally don't remove existing spaces between an identifier and "::".
-    // The identifier might actually be a macro name such as ALWAYS_INLINE. If
-    // this turns out to be too lenient, add analysis of the identifier itself.
-    return Right.hasWhitespaceBefore();
+    // Preserve the space in constructs such as ALWAYS_INLINE ::std::string.
+    return Left.isPossibleMacro(/*AllowFollowingColonColon=*/true) &&
+           Right.hasWhitespaceBefore();
   }
   if (Right.is(tok::coloncolon) &&
       Left.isNoneOf(tok::l_brace, tok::comment, tok::l_paren)) {

diff  --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 676aa5ad42d2e..e0b2644249e76 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -80,6 +80,8 @@ TEST_F(FormatTest, NestedNameSpecifiers) {
   verifyFormat("static constexpr bool Bar = _Atomic(bar())::value;");
   verifyFormat("bool a = 2 < ::SomeFunction();");
   verifyFormat("ALWAYS_INLINE ::std::string getName();");
+  verifyFormat("template <typename T> auto mem = &T::member;",
+               "template <typename T> auto mem = &T :: member;");
   verifyFormat("some::string getName();");
 }
 

diff  --git a/clang/unittests/Format/QualifierFixerTest.cpp b/clang/unittests/Format/QualifierFixerTest.cpp
index 7ff426d490eab..3ee1ecc9df851 100644
--- a/clang/unittests/Format/QualifierFixerTest.cpp
+++ b/clang/unittests/Format/QualifierFixerTest.cpp
@@ -1126,15 +1126,15 @@ TEST_F(QualifierFixerTest, IsQualifierType) {
 TEST_F(QualifierFixerTest, IsMacro) {
   auto Tokens = annotate("INT INTPR Foo int");
   ASSERT_EQ(Tokens.size(), 5u) << Tokens;
-  EXPECT_TRUE(isPossibleMacro(Tokens[0]));
-  EXPECT_TRUE(isPossibleMacro(Tokens[1]));
-  EXPECT_FALSE(isPossibleMacro(Tokens[2]));
-  EXPECT_FALSE(isPossibleMacro(Tokens[3]));
+  EXPECT_TRUE(Tokens[0]->isPossibleMacro());
+  EXPECT_TRUE(Tokens[1]->isPossibleMacro());
+  EXPECT_FALSE(Tokens[2]->isPossibleMacro());
+  EXPECT_FALSE(Tokens[3]->isPossibleMacro());
 
   Tokens = annotate("FOO::BAR");
   ASSERT_EQ(Tokens.size(), 4u) << Tokens;
-  EXPECT_FALSE(isPossibleMacro(Tokens[0]));
-  EXPECT_FALSE(isPossibleMacro(Tokens[2]));
+  EXPECT_FALSE(Tokens[0]->isPossibleMacro());
+  EXPECT_FALSE(Tokens[2]->isPossibleMacro());
 }
 
 TEST_F(QualifierFixerTest, OverlappingQualifier) {


        


More information about the cfe-commits mailing list