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) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
