https://github.com/johnnyb2543 created https://github.com/llvm/llvm-project/pull/210630
This patch resolves a clang-format regression where unspaced User-Defined Literals (UDLs) were being formatted incorrectly. Root Cause The lexer failed to correctly classify the preceding token sequence as a single user-defined literal operator. Since FormatTokenLexer::tryMergeUserDefinedLiteral did not properly merge the operator token and its suffix (e.g., _mag), the internal TokenAnnotator evaluated the subsequent < token as a comparison operator. Changes - Updated FormatTokenLexer::tryMergeUserDefinedLiteral to ensure proper token merging for UDLs - Added unit tests in FormatTest.cpp to verify correct spacing behavior and prevent future regressions. Fixes #210135 >From 36a2d7403fc90e45e58f55983ab1074c28ace5d9 Mon Sep 17 00:00:00 2001 From: John Boncore <[email protected]> Date: Sun, 19 Jul 2026 16:31:10 -0400 Subject: [PATCH] [clang-format] Fix template parsing regression for unspaced user-defined literals --- clang/lib/Format/FormatTokenLexer.cpp | 32 +++++++++++++++++++++++++++ clang/unittests/Format/FormatTest.cpp | 5 ++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp index 0cf01875af833..12815e8cb3679 100644 --- a/clang/lib/Format/FormatTokenLexer.cpp +++ b/clang/lib/Format/FormatTokenLexer.cpp @@ -595,6 +595,38 @@ bool FormatTokenLexer::tryMergeUserDefinedLiteral() { if (Tokens.size() < 2) return false; + // --- INTERCEPT STRING/CHARACTER UDLs ALREADY MERGED BY THE RAW LEXER --- + // --- INTERCEPT STRING/CHARACTER UDLs ALREADY MERGED BY THE RAW LEXER --- + // --- INTERCEPT STRING/CHARACTER UDLs ALREADY MERGED BY THE RAW LEXER --- + if (Tokens.back()->isOneOf(tok::string_literal, tok::char_constant, tok::numeric_constant) && + Tokens.end()[-2]->is(tok::kw_operator)) { + + FormatToken *OpToken = Tokens[Tokens.size() - 2]; + FormatToken *LiteralToken = Tokens.back(); + + // Strict guard: Do not merge if this is a member access call (e.g., x.operator""_a()) + if (Tokens.size() >= 3) { + FormatToken *PrevToken = Tokens[Tokens.size() - 3]; + if (PrevToken->isOneOf(tok::period, tok::arrow) || + PrevToken->TokenText == "." || PrevToken->TokenText == "->") { + return false; + } + } + + // Ensure they are touching in the source text + if (!LiteralToken->hasWhitespaceBefore()) { + OpToken->TokenText = StringRef(OpToken->TokenText.data(), + OpToken->TokenText.size() + LiteralToken->TokenText.size()); + OpToken->ColumnWidth += LiteralToken->ColumnWidth; + + OpToken->Tok.setKind(tok::identifier); + + Tokens.erase(&Tokens.back()); + return true; + } + } + + // --- ORIGINAL NUMERIC LITERAL LOGIC KICKS IN HERE --- auto *First = Tokens.end() - 2; auto &Suffix = First[1]; if (Suffix->hasWhitespaceBefore() || Suffix->TokenText != "$") diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index ec9ad612832f5..3ee336e6c46ad 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -16,7 +16,10 @@ namespace test { namespace { class FormatTest : public test::FormatTestBase {}; - +TEST_F(FormatTest, FormatsUserDefinedLiteralTemplates) { + verifyFormat("template <char... Cs> auto f() { return operator\"\"_mag<Cs...>(); }"); + verifyFormat("template <char... Cs> auto f() { return operator \"\"_mag<Cs...>(); }"); +} TEST_F(FormatTest, MessUp) { EXPECT_EQ("1 2 3", messUp("1 2 3")); EXPECT_EQ("1 2 3", messUp("1\n2\n3")); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
