[clang] [clang-format] Fix spacing before :: after non-macro identifiers (PR #189024)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 27 20:16:29 PDT 2026
https://github.com/Lane0218 updated https://github.com/llvm/llvm-project/pull/189024
>From 04fa0e42a1ddb54251279f9e7224beb39db6e8a7 Mon Sep 17 00:00:00 2001
From: Lane0218 <laneljc at qq.com>
Date: Fri, 27 Mar 2026 23:15:04 +0800
Subject: [PATCH] [clang-format] Fix spacing before :: after non-macro
identifiers
Preserve spaces before :: only for identifiers that likely name
object-like macros, and otherwise format identifier :: as identifier::.
This fixes clang-format leaving dependent nested name specifiers such as
&T :: member half-formatted as &T ::member.
Implement the macro heuristic as a shared FormatToken helper so both
QualifierAlignmentFixer and TokenAnnotator use the same logic. Allow the
TokenAnnotator call site to treat identifiers followed by :: as possible
macros for cases such as ALWAYS_INLINE ::std::string.
Add a regression test for the reported case while keeping the existing
ALWAYS_INLINE ::std::string coverage.
Fixes #188754
---
clang/lib/Format/FormatToken.h | 27 +++++++++++++++++
clang/lib/Format/QualifierAlignmentFixer.cpp | 29 ++-----------------
clang/lib/Format/QualifierAlignmentFixer.h | 3 --
clang/lib/Format/TokenAnnotator.cpp | 7 ++---
clang/unittests/Format/FormatTest.cpp | 2 ++
clang/unittests/Format/QualifierFixerTest.cpp | 12 ++++----
6 files changed, 40 insertions(+), 40 deletions(-)
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 d2cdc28a7da7b..aba7753a5572e 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 2701a7fca7346..6cb28f2c98c2a 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