https://github.com/LeGusto updated https://github.com/llvm/llvm-project/pull/212328
From a42fc20b9252a4f6e70ef497d46d172b513fa346 Mon Sep 17 00:00:00 2001 From: LeGusto <[email protected]> Date: Mon, 27 Jul 2026 22:00:43 +0300 Subject: [PATCH 1/2] [clang-format] Don't case-fix numeric literals in macro invocations NumericLiteralCaseFixer transformed macro invocations such as FOO(0xa) to FOO(0xA), which is incorrect when the argument is stringized. Fix recognizes possible macro invocations in NumericLiteralCaseFixer::process by replicating the logic of FormatToken::isPossibleMacro and tracking the nesting of the invocation parentheses. Addresses #200731 --- clang/lib/Format/NumericLiteralCaseFixer.cpp | 26 +++++++++++++++++-- .../Format/NumericLiteralCaseTest.cpp | 26 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/clang/lib/Format/NumericLiteralCaseFixer.cpp b/clang/lib/Format/NumericLiteralCaseFixer.cpp index b58b3c7ee0189..ba55ebdcb8f41 100644 --- a/clang/lib/Format/NumericLiteralCaseFixer.cpp +++ b/clang/lib/Format/NumericLiteralCaseFixer.cpp @@ -43,6 +43,11 @@ static bool isNumericLiteralCaseFixerNeeded(const FormatStyle &Style) { Option.ExponentLetter != Leave || Option.Suffix != Leave; } +// An all-caps identifier is likely a macro; see FormatToken::isPossibleMacro. +static bool isPossibleMacroName(StringRef Name) { + return Name.size() > 1 && Name == Name.upper(); +} + static std::string transformComponent(StringRef Component, FormatStyle::NumericLiteralComponentStyle ConfigValue) { @@ -137,8 +142,25 @@ NumericLiteralCaseFixer::process(const Environment &Env, Token Tok; tooling::Replacements Result; + int MacroArgDepth = 0; // Paren depth inside a possible macro invocation. + + for (bool Skip = false, AfterMacroName = false, AfterScope = false; + !Lex.LexFromRawLexer(Tok);) { + // Leave arguments of a likely macro invocation (e.g. FOO(0xa)) untouched. + if (Tok.is(tok::raw_identifier)) { + AfterMacroName = + !AfterScope && isPossibleMacroName(Tok.getRawIdentifier()); + } else if (Tok.is(tok::l_paren)) { + if (MacroArgDepth > 0 || AfterMacroName) + ++MacroArgDepth; + AfterMacroName = false; + } else { + if (Tok.is(tok::r_paren) && MacroArgDepth > 0) + --MacroArgDepth; + AfterMacroName = false; + } + AfterScope = Tok.is(tok::coloncolon); - for (bool Skip = false; !Lex.LexFromRawLexer(Tok);) { // Skip tokens that are too small to contain a formattable literal. // Size=2 is the smallest possible literal that could contain formattable // components, for example "1u". @@ -157,7 +179,7 @@ NumericLiteralCaseFixer::process(const Environment &Env, continue; } - if (Skip || Tok.isNot(tok::numeric_constant) || + if (Skip || MacroArgDepth > 0 || Tok.isNot(tok::numeric_constant) || !AffectedRangeMgr.affectsCharSourceRange( CharSourceRange::getCharRange(Location, Tok.getEndLoc()))) { continue; diff --git a/clang/unittests/Format/NumericLiteralCaseTest.cpp b/clang/unittests/Format/NumericLiteralCaseTest.cpp index ecd230d73f692..f59fbed2c5ab0 100644 --- a/clang/unittests/Format/NumericLiteralCaseTest.cpp +++ b/clang/unittests/Format/NumericLiteralCaseTest.cpp @@ -340,6 +340,32 @@ TEST_F(NumericLiteralCaseTest, UnderScoreSeparatorLanguages) { verifyFormat("o = 0o0_10_010;", "o = 0O0_10_010;", Style); } +TEST_F(NumericLiteralCaseTest, IgnoresMacroInvocations) { + // Arguments of a likely macro invocation aren't literals. + constexpr StringRef A("FOO(0xabc);"); + constexpr StringRef B("FOO(bar(1), 0xabc);"); + // Non-macro contexts, where literals are still formatted. + constexpr StringRef C("foo(0xabc);"); + constexpr StringRef D("FOO(1) + 0xabc;"); + constexpr StringRef E("T(0xabc);"); + constexpr StringRef F("NS::FOO(0xabc);"); + verifyFormat(A); + verifyFormat(B); + verifyFormat(C); + verifyFormat(D); + verifyFormat(E); + verifyFormat(F); + + auto Style = getLLVMStyle(); + Style.NumericLiteralCase.HexDigit = FormatStyle::NLCS_Upper; + verifyFormat(A, Style); + verifyFormat(B, Style); + verifyFormat("foo(0xABC);", C, Style); + verifyFormat("FOO(1) + 0xABC;", D, Style); + verifyFormat("T(0xABC);", E, Style); + verifyFormat("NS::FOO(0xABC);", F, Style); +} + } // namespace } // namespace test } // namespace format From 8c051f3ba1809cf2c559252c6f5caaa53d72e659 Mon Sep 17 00:00:00 2001 From: LeGusto <[email protected]> Date: Sun, 6 Sep 2026 02:38:41 +0300 Subject: [PATCH 2/2] Change custom macro detection logic to instead check WhitespaceSensitiveMacros --- clang/lib/Format/NumericLiteralCaseFixer.cpp | 23 ++++++------ .../Format/NumericLiteralCaseTest.cpp | 36 ++++++++++++------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/clang/lib/Format/NumericLiteralCaseFixer.cpp b/clang/lib/Format/NumericLiteralCaseFixer.cpp index ba55ebdcb8f41..a28853c36f65b 100644 --- a/clang/lib/Format/NumericLiteralCaseFixer.cpp +++ b/clang/lib/Format/NumericLiteralCaseFixer.cpp @@ -16,6 +16,7 @@ #include "NumericLiteralInfo.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringSet.h" #include <algorithm> @@ -43,11 +44,6 @@ static bool isNumericLiteralCaseFixerNeeded(const FormatStyle &Style) { Option.ExponentLetter != Leave || Option.Suffix != Leave; } -// An all-caps identifier is likely a macro; see FormatToken::isPossibleMacro. -static bool isPossibleMacroName(StringRef Name) { - return Name.size() > 1 && Name == Name.upper(); -} - static std::string transformComponent(StringRef Component, FormatStyle::NumericLiteralComponentStyle ConfigValue) { @@ -135,6 +131,12 @@ NumericLiteralCaseFixer::process(const Environment &Env, const auto &SourceMgr = Env.getSourceManager(); AffectedRangeManager AffectedRangeMgr(SourceMgr, Env.getCharRanges()); + // Don't recase pp-numbers inside whitespace-sensitive macros (e.g. + // STRINGIZE(0xa)); recasing a stringized argument would change the result. + llvm::StringSet<> UntouchableMacros; + for (StringRef Macro : Style.WhitespaceSensitiveMacros) + UntouchableMacros.insert(Macro); + const auto ID = Env.getFileID(); const auto LangOpts = getFormattingLangOpts(Style); Lexer Lex(ID, SourceMgr.getBufferOrFake(ID), SourceMgr, LangOpts); @@ -142,14 +144,12 @@ NumericLiteralCaseFixer::process(const Environment &Env, Token Tok; tooling::Replacements Result; - int MacroArgDepth = 0; // Paren depth inside a possible macro invocation. + int MacroArgDepth = 0; // Paren depth inside an untouchable macro invocation. - for (bool Skip = false, AfterMacroName = false, AfterScope = false; - !Lex.LexFromRawLexer(Tok);) { - // Leave arguments of a likely macro invocation (e.g. FOO(0xa)) untouched. + for (bool Skip = false, AfterMacroName = false; !Lex.LexFromRawLexer(Tok);) { + // Track whether we are inside the argument list of an untouchable macro. if (Tok.is(tok::raw_identifier)) { - AfterMacroName = - !AfterScope && isPossibleMacroName(Tok.getRawIdentifier()); + AfterMacroName = UntouchableMacros.contains(Tok.getRawIdentifier()); } else if (Tok.is(tok::l_paren)) { if (MacroArgDepth > 0 || AfterMacroName) ++MacroArgDepth; @@ -159,7 +159,6 @@ NumericLiteralCaseFixer::process(const Environment &Env, --MacroArgDepth; AfterMacroName = false; } - AfterScope = Tok.is(tok::coloncolon); // Skip tokens that are too small to contain a formattable literal. // Size=2 is the smallest possible literal that could contain formattable diff --git a/clang/unittests/Format/NumericLiteralCaseTest.cpp b/clang/unittests/Format/NumericLiteralCaseTest.cpp index f59fbed2c5ab0..f1b9f1e6b3e08 100644 --- a/clang/unittests/Format/NumericLiteralCaseTest.cpp +++ b/clang/unittests/Format/NumericLiteralCaseTest.cpp @@ -340,15 +340,19 @@ TEST_F(NumericLiteralCaseTest, UnderScoreSeparatorLanguages) { verifyFormat("o = 0o0_10_010;", "o = 0O0_10_010;", Style); } -TEST_F(NumericLiteralCaseTest, IgnoresMacroInvocations) { - // Arguments of a likely macro invocation aren't literals. - constexpr StringRef A("FOO(0xabc);"); - constexpr StringRef B("FOO(bar(1), 0xabc);"); - // Non-macro contexts, where literals are still formatted. - constexpr StringRef C("foo(0xabc);"); - constexpr StringRef D("FOO(1) + 0xabc;"); - constexpr StringRef E("T(0xabc);"); - constexpr StringRef F("NS::FOO(0xabc);"); +TEST_F(NumericLiteralCaseTest, IgnoresWhitespaceSensitiveMacros) { + // Arguments of a whitespace-sensitive macro (e.g. a stringizing macro) must + // not be recased; everything else still is. + constexpr StringRef A("a = STRINGIZE(0xabc);"); + constexpr StringRef B("b = PP_STRINGIZE(0xa, 0xb);"); + // Nested parens and multiple literals inside the macro are all left alone. + constexpr StringRef C("c = STRINGIZE(f(0xa) + 0xb);"); + // A literal outside the macro is still formatted. + constexpr StringRef D("d = STRINGIZE(1) + 0xabc;"); + // A macro that is not whitespace-sensitive, and a plain literal, are still + // formatted. + constexpr StringRef E("e = FOO(0xabc);"); + constexpr StringRef F("f = 0xabc;"); verifyFormat(A); verifyFormat(B); verifyFormat(C); @@ -358,12 +362,18 @@ TEST_F(NumericLiteralCaseTest, IgnoresMacroInvocations) { auto Style = getLLVMStyle(); Style.NumericLiteralCase.HexDigit = FormatStyle::NLCS_Upper; + // STRINGIZE and PP_STRINGIZE are whitespace-sensitive by default. verifyFormat(A, Style); verifyFormat(B, Style); - verifyFormat("foo(0xABC);", C, Style); - verifyFormat("FOO(1) + 0xABC;", D, Style); - verifyFormat("T(0xABC);", E, Style); - verifyFormat("NS::FOO(0xABC);", F, Style); + verifyFormat(C, Style); + verifyFormat("d = STRINGIZE(1) + 0xABC;", D, Style); + verifyFormat("e = FOO(0xABC);", E, Style); + verifyFormat("f = 0xABC;", F, Style); + + // A user-declared macro is honored too, including its nested arguments. + Style.WhitespaceSensitiveMacros.push_back("FOO"); + verifyFormat(E, Style); + verifyFormat("g = FOO(h(0xa), 0xbc);", Style); } } // namespace _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
