Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[clang-format] Add first support for reflection#193794

Open
HazardyKnusperkeks wants to merge 1 commit intollvm:mainfrom
HazardyKnusperkeks:reflection
Open

[clang-format] Add first support for reflection#193794
HazardyKnusperkeks wants to merge 1 commit intollvm:mainfrom
HazardyKnusperkeks:reflection

Conversation

@HazardyKnusperkeks
Copy link
Copy Markdown
Contributor

@HazardyKnusperkeks HazardyKnusperkeks commented Apr 23, 2026

Fixes #192040.

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Apr 23, 2026

@llvm/pr-subscribers-clang-format

Author: Björn Schäpers (HazardyKnusperkeks)

Changes

Fixes #192040.


Full diff: https://github.com/llvm/llvm-project/pull/193794.diff

11 Files Affected:

  • (modified) clang/docs/ClangFormatStyleOptions.rst (+10)
  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/include/clang/Format/Format.h (+9)
  • (modified) clang/lib/Format/Format.cpp (+6-4)
  • (modified) clang/lib/Format/FormatToken.h (+3)
  • (modified) clang/lib/Format/FormatTokenLexer.cpp (+18-3)
  • (modified) clang/lib/Format/FormatTokenLexer.h (+1)
  • (modified) clang/lib/Format/TokenAnnotator.cpp (+15-6)
  • (modified) clang/unittests/Format/ConfigParseTest.cpp (+1)
  • (modified) clang/unittests/Format/FormatTest.cpp (+13)
  • (modified) clang/unittests/Format/TokenAnnotatorTest.cpp (+39)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index fe8dfa406bc32..e58023ccc1274 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -7503,6 +7503,16 @@ the configuration (without a prefix: ``Auto``).
   ``SpacesInParensOptions`` to ``true`` except for ``InCStyleCasts`` and
   ``InEmptyParentheses``.
 
+.. _SpacesInSplicers:
+
+**SpacesInSplicers** (``Boolean``) :versionbadge:`clang-format 23` :ref:`¶ <SpacesInSplicers>`
+  If ``true``, spaces will be inserted after ``[:`` and before ``:]``.
+
+  .. code-block:: c++
+
+     true:                                  false:
+     [: ^^int :] i;                 vs.     [:^^int:] i;
+
 .. _SpacesInSquareBrackets:
 
 **SpacesInSquareBrackets** (``Boolean``) :versionbadge:`clang-format 3.7` :ref:`¶ <SpacesInSquareBrackets>`
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 03362cf4e0f8a..3e9ca8a24316b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -691,6 +691,7 @@ clang-format
 - Extend ``BreakBinaryOperations`` to accept a structured configuration with
   per-operator break rules and minimum chain length gating via ``PerOperator``.
 - Add ``AllowShortRecordOnASingleLine`` option and set it to ``EmptyAndAttached`` for LLVM style.
+- Add initial support for C++26 reflection, including the new option ``SpacesInSplicers``.
 
 libclang
 --------
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index f89323c71713b..d567da3215019 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -5734,6 +5734,14 @@ struct FormatStyle {
   /// \version 17
   SpacesInParensCustom SpacesInParensOptions;
 
+  /// If ``true``, spaces will be inserted after ``[:`` and before ``:]``.
+  /// \code
+  ///    true:                                  false:
+  ///    [: ^^int :] i;                 vs.     [:^^int:] i;
+  /// \endcode
+  /// \version 23
+  bool SpacesInSplicers;
+
   /// If ``true``, spaces will be inserted after ``[`` and before ``]``.
   /// Lambdas without arguments or unspecified size array declarations will not
   /// be affected.
@@ -6201,6 +6209,7 @@ struct FormatStyle {
                R.SpacesInLineCommentPrefix.Maximum &&
            SpacesInParens == R.SpacesInParens &&
            SpacesInParensOptions == R.SpacesInParensOptions &&
+           SpacesInSplicers == R.SpacesInSplicers &&
            SpacesInSquareBrackets == R.SpacesInSquareBrackets &&
            Standard == R.Standard &&
            StatementAttributeLikeMacros == R.StatementAttributeLikeMacros &&
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 8d447177a18a7..e01076d379a6d 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1450,6 +1450,7 @@ template <> struct MappingTraits<FormatStyle> {
                    Style.SpacesInLineCommentPrefix);
     IO.mapOptional("SpacesInParens", Style.SpacesInParens);
     IO.mapOptional("SpacesInParensOptions", Style.SpacesInParensOptions);
+    IO.mapOptional("SpacesInSplicers", Style.SpacesInSplicers);
     IO.mapOptional("SpacesInSquareBrackets", Style.SpacesInSquareBrackets);
     IO.mapOptional("Standard", Style.Standard);
     IO.mapOptional("StatementAttributeLikeMacros",
@@ -1970,6 +1971,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
   LLVMStyle.SpacesInLineCommentPrefix = {
       /*Minimum=*/1, /*Maximum=*/std::numeric_limits<unsigned>::max()};
   LLVMStyle.SpacesInParens = FormatStyle::SIPO_Never;
+  LLVMStyle.SpacesInSplicers = false;
   LLVMStyle.SpacesInSquareBrackets = false;
   LLVMStyle.Standard = FormatStyle::LS_Latest;
   LLVMStyle.StatementAttributeLikeMacros.push_back("Q_EMIT");
@@ -4405,12 +4407,11 @@ tooling::Replacements sortUsingDeclarations(const FormatStyle &Style,
 LangOptions getFormattingLangOpts(const FormatStyle &Style) {
   LangOptions LangOpts;
 
-  auto LexingStd = Style.Standard;
-  if (LexingStd == FormatStyle::LS_Auto || LexingStd == FormatStyle::LS_Latest)
-    LexingStd = FormatStyle::LS_Cpp20;
+  const auto LexingStd = Style.Standard;
 
   const bool SinceCpp11 = LexingStd >= FormatStyle::LS_Cpp11;
   const bool SinceCpp20 = LexingStd >= FormatStyle::LS_Cpp20;
+  const bool SinceCpp26 = LexingStd >= FormatStyle::LS_Cpp26;
 
   switch (Style.Language) {
   case FormatStyle::LK_C:
@@ -4425,7 +4426,7 @@ LangOptions getFormattingLangOpts(const FormatStyle &Style) {
     LangOpts.CPlusPlus17 = LexingStd >= FormatStyle::LS_Cpp17;
     LangOpts.CPlusPlus20 = SinceCpp20;
     LangOpts.CPlusPlus23 = LexingStd >= FormatStyle::LS_Cpp23;
-    LangOpts.CPlusPlus26 = LexingStd >= FormatStyle::LS_Cpp26;
+    LangOpts.CPlusPlus26 = SinceCpp26;
     [[fallthrough]];
   default:
     LangOpts.CPlusPlus = 1;
@@ -4437,6 +4438,7 @@ LangOptions getFormattingLangOpts(const FormatStyle &Style) {
   // the sequence "<::" will be unconditionally treated as "[:".
   // Cf. Lexer::LexTokenInternal.
   LangOpts.Digraphs = SinceCpp11;
+  LangOpts.Reflection = SinceCpp26;
 
   LangOpts.LineComment = 1;
   LangOpts.Bool = 1;
diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index 68d94b087136d..9ce7c5d7bc6a8 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -150,6 +150,7 @@ namespace format {
   TYPE(RangeBasedForLoopColon)                                                 \
   TYPE(RecordLBrace)                                                           \
   TYPE(RecordRBrace)                                                           \
+  TYPE(ReflectionOperator)                                                     \
   TYPE(RegexLiteral)                                                           \
   TYPE(RequiresClause)                                                         \
   TYPE(RequiresClauseInARequiresExpression)                                    \
@@ -160,6 +161,8 @@ namespace format {
    * field name in the C++ struct literal. Also the method or parameter name   \
    * in the Objective-C method declaration or call. */                         \
   TYPE(SelectorName)                                                           \
+  TYPE(SpliceCloser)                                                           \
+  TYPE(SpliceOpener)                                                           \
   TYPE(StartOfName)                                                            \
   TYPE(StatementAttributeLikeMacro)                                            \
   TYPE(StatementMacro)                                                         \
diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index bda591ab1027e..4e0fc0aad0b63 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -164,6 +164,9 @@ void FormatTokenLexer::tryMergePreviousTokens() {
   if (tryMergeForEach())
     return;
 
+  if (LangOpts.Reflection && tryMergeSplicer())
+    return;
+
   if ((Style.Language == FormatStyle::LK_Cpp ||
        Style.Language == FormatStyle::LK_ObjC) &&
       tryMergeUserDefinedLiteral()) {
@@ -182,6 +185,7 @@ void FormatTokenLexer::tryMergePreviousTokens() {
     if (tryMergeTokens(NullishCoalescingOperator, TT_NullCoalescingOperator)) {
       // Treat like the "||" operator (as opposed to the ternary ?).
       Tokens.back()->Tok.setKind(tok::pipepipe);
+      Tokens.back()->overwriteFixedType(TT_NullCoalescingOperator);
       return;
     }
     if (tryMergeTokens(NullPropagatingOperator, TT_NullPropagatingOperator)) {
@@ -272,8 +276,9 @@ void FormatTokenLexer::tryMergePreviousTokens() {
     // already a merged token.
     if (Tokens.back()->TokenText.size() == 1 &&
         tryMergeTokensAny({{tok::caret, tok::tilde}, {tok::tilde, tok::caret}},
-                          TT_BinaryOperator)) {
+                          TT_Unknown)) {
       Tokens.back()->Tok.setKind(tok::caret);
+      Tokens.back()->overwriteFixedType(TT_Unknown);
       return;
     }
     // Signed shift and distribution weight.
@@ -468,7 +473,8 @@ bool FormatTokenLexer::tryMergeNullishCoalescingEqual() {
       StringRef(NullishCoalescing->TokenText.begin(),
                 Equal->TokenText.end() - NullishCoalescing->TokenText.begin());
   NullishCoalescing->ColumnWidth += Equal->ColumnWidth;
-  NullishCoalescing->setType(TT_NullCoalescingEqual);
+  NullishCoalescing->overwriteFixedType(TT_NullCoalescingEqual);
+  NullishCoalescing->setFinalizedType(TT_NullCoalescingEqual);
   Tokens.erase(Tokens.end() - 1);
   return true;
 }
@@ -606,6 +612,15 @@ bool FormatTokenLexer::tryMergeUserDefinedLiteral() {
   return true;
 }
 
+bool FormatTokenLexer::tryMergeSplicer() {
+  if (tryMergeTokens({tok::l_square, tok::colon}, TT_SpliceOpener))
+    return true;
+  if (!tryMergeTokens({tok::colon, tok::r_square}, TT_SpliceCloser))
+    return false;
+  Tokens.back()->Tok.setKind(tok::r_square);
+  return true;
+}
+
 bool FormatTokenLexer::tryMergeTokens(ArrayRef<tok::TokenKind> Kinds,
                                       TokenType NewType) {
   if (Tokens.size() < Kinds.size())
@@ -637,7 +652,7 @@ bool FormatTokenLexer::tryMergeTokens(size_t Count, TokenType NewType) {
   First[0]->TokenText = StringRef(First[0]->TokenText.data(),
                                   First[0]->TokenText.size() + AddLength);
   First[0]->ColumnWidth += AddLength;
-  First[0]->setType(NewType);
+  First[0]->setFinalizedType(NewType);
   return true;
 }
 
diff --git a/clang/lib/Format/FormatTokenLexer.h b/clang/lib/Format/FormatTokenLexer.h
index 9f5b735efe1d0..29c6d06a2a7da 100644
--- a/clang/lib/Format/FormatTokenLexer.h
+++ b/clang/lib/Format/FormatTokenLexer.h
@@ -56,6 +56,7 @@ class FormatTokenLexer {
   bool tryMergeNullishCoalescingEqual();
   bool tryTransformCSharpForEach();
   bool tryMergeForEach();
+  bool tryMergeSplicer();
 
   // Merge the most recently lexed tokens into a single token if their kinds are
   // correct.
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 898759cb8ea1b..bea63a3702bc7 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -711,7 +711,8 @@ class AnnotatingParser {
     bool StartsObjCMethodExpr =
         !IsCppStructuredBinding && !InsideInlineASM && !CppArrayTemplates &&
         IsCpp && !IsCpp11AttributeSpecifier && !IsCSharpAttributeSpecifier &&
-        Contexts.back().CanBeExpression && Left->isNot(TT_LambdaLSquare) &&
+        Contexts.back().CanBeExpression &&
+        Left->isNoneOf(TT_LambdaLSquare, TT_SpliceOpener) &&
         CurrentToken->isNoneOf(tok::l_brace, tok::r_square) &&
         // Do not consider '[' after a comma inside a braced initializer the
         // start of an ObjC method expression. In braced initializer lists,
@@ -1836,6 +1837,9 @@ class AnnotatingParser {
       if (Style.isTableGen() && !parseTableGenValue())
         return false;
       break;
+    case tok::caretcaret:
+      Tok->setFinalizedType(TT_ReflectionOperator);
+      break;
     default:
       break;
     }
@@ -4902,6 +4906,8 @@ bool TokenAnnotator::spaceRequiredBetween(const AnnotatedLine &Line,
                 LSquareTok.endsSequence(tok::l_square, tok::colon,
                                         TT_SelectorName));
       };
+  if (Left.is(TT_SpliceOpener) || Right.is(TT_SpliceCloser))
+    return Style.SpacesInSplicers;
   if (Left.is(tok::l_square)) {
     return (Left.is(TT_ArrayInitializerLSquare) && Right.isNot(tok::r_square) &&
             SpaceRequiredForArrayInitializerLSquare(Left, Style)) ||
@@ -4920,9 +4926,9 @@ bool TokenAnnotator::spaceRequiredBetween(const AnnotatedLine &Line,
                                           TT_LambdaLSquare)));
   }
   if (Right.is(tok::l_square) &&
-      Right.isNoneOf(TT_ObjCMethodExpr, TT_LambdaLSquare,
-                     TT_DesignatedInitializerLSquare,
-                     TT_StructuredBindingLSquare, TT_AttributeLSquare) &&
+      Right.isNoneOf(
+          TT_ObjCMethodExpr, TT_LambdaLSquare, TT_DesignatedInitializerLSquare,
+          TT_StructuredBindingLSquare, TT_AttributeLSquare, TT_SpliceOpener) &&
       Left.isNoneOf(tok::numeric_constant, TT_DictLiteral) &&
       !(Left.isNot(tok::r_square) && Style.SpaceBeforeSquareBrackets &&
         Right.is(TT_ArraySubscriptLSquare))) {
@@ -5093,6 +5099,8 @@ bool TokenAnnotator::spaceRequiredBetween(const AnnotatedLine &Line,
     // void Fn() const &;
     return getTokenReferenceAlignment(Right) != FormatStyle::PAS_Left;
   }
+  if (Left.is(TT_ReflectionOperator))
+    return false;
 
   return true;
 }
@@ -6641,10 +6649,11 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
   }
 
   return Left.isOneOf(tok::comma, tok::coloncolon, tok::semi, tok::l_brace,
-                      tok::kw_class, tok::kw_struct, tok::comment) ||
+                      tok::kw_class, tok::kw_struct, tok::comment,
+                      TT_SpliceCloser) ||
          Right.isMemberAccess() ||
          Right.isOneOf(TT_TrailingReturnArrow, TT_LambdaArrow, tok::lessless,
-                       tok::colon, tok::l_square, tok::at) ||
+                       tok::colon, tok::l_square, tok::at, TT_SpliceOpener) ||
          (Left.is(tok::r_paren) &&
           Right.isOneOf(tok::identifier, tok::kw_const)) ||
          (Left.is(tok::l_paren) && Right.isNot(tok::r_paren)) ||
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 498e44b190ef2..b374813e880d8 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -208,6 +208,7 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
   CHECK_PARSE_BOOL(RemoveEmptyLinesInUnwrappedLines);
   CHECK_PARSE_BOOL(RemoveSemicolon);
   CHECK_PARSE_BOOL(SkipMacroDefinitionBody);
+  CHECK_PARSE_BOOL(SpacesInSplicers);
   CHECK_PARSE_BOOL(SpacesInSquareBrackets);
   CHECK_PARSE_BOOL(SpacesInContainerLiterals);
   CHECK_PARSE_BOOL(SpaceAfterCStyleCast);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 44c52034f83cb..bac965f7563b9 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -26306,6 +26306,19 @@ TEST_F(FormatTest, LambdaArrowAsTrailingReturnArrow) {
   verifyNoCrash("void foo()([] consteval -> int {}())");
 }
 
+TEST_F(FormatTest, Reflection) {
+  verifyFormat("typename [:^^int:] i = 42;");
+  verifyFormat("obj.[:sub:]");
+  verifyFormat("auto foo() { return [:bar:]; }");
+  verifyFormat("auto x = ^^Bar;");
+
+  auto Style = getLLVMStyle();
+  Style.SpacesInSplicers = true;
+  verifyFormat("typename [: ^^int :] i = 42;", Style);
+  verifyFormat("obj.[: sub :]", Style);
+  verifyFormat("auto foo() { return [: bar :]; }", Style);
+}
+
 } // namespace
 } // namespace test
 } // namespace format
diff --git a/clang/unittests/Format/TokenAnnotatorTest.cpp b/clang/unittests/Format/TokenAnnotatorTest.cpp
index cf2bd9f8bd76a..172625b715180 100644
--- a/clang/unittests/Format/TokenAnnotatorTest.cpp
+++ b/clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -4505,6 +4505,45 @@ TEST_F(TokenAnnotatorTest, AttributeSquares) {
   EXPECT_TRUE(Tokens[15]->EndsCppAttributeGroup);
 }
 
+TEST_F(TokenAnnotatorTest, UnderstandsReflection) {
+  auto Tokens = annotate("auto x = ^^int;");
+  ASSERT_EQ(Tokens.size(), 7u) << Tokens;
+  EXPECT_TOKEN(Tokens[3], tok::caretcaret, TT_ReflectionOperator);
+
+  Tokens = annotate("auto x = ^^static_cast<void(*)(int)>(&foo);");
+  ASSERT_EQ(Tokens.size(), 20u) << Tokens;
+  EXPECT_TOKEN(Tokens[3], tok::caretcaret, TT_ReflectionOperator);
+
+  Tokens = annotate("[: x :]");
+  ASSERT_EQ(Tokens.size(), 4u) << Tokens;
+  EXPECT_TOKEN(Tokens[0], tok::l_square, TT_SpliceOpener);
+  EXPECT_TOKEN(Tokens[2], tok::r_square, TT_SpliceCloser);
+
+  Tokens = annotate("[: ^^int :]");
+  ASSERT_EQ(Tokens.size(), 5u) << Tokens;
+  EXPECT_TOKEN(Tokens[0], tok::l_square, TT_SpliceOpener);
+  EXPECT_TOKEN(Tokens[1], tok::caretcaret, TT_ReflectionOperator);
+  EXPECT_TOKEN(Tokens[3], tok::r_square, TT_SpliceCloser);
+
+  Tokens = annotate("[: ^^&T::member :]");
+  ASSERT_EQ(Tokens.size(), 8u) << Tokens;
+  EXPECT_TOKEN(Tokens[0], tok::l_square, TT_SpliceOpener);
+  EXPECT_TOKEN(Tokens[1], tok::caretcaret, TT_ReflectionOperator);
+  EXPECT_TOKEN(Tokens[6], tok::r_square, TT_SpliceCloser);
+
+  Tokens = annotate("[: func() :]");
+  ASSERT_EQ(Tokens.size(), 6u) << Tokens;
+  EXPECT_TOKEN(Tokens[0], tok::l_square, TT_SpliceOpener);
+  EXPECT_TOKEN(Tokens[4], tok::r_square, TT_SpliceCloser);
+
+  Tokens = annotate("[: condition ? ^^int : ^^double :]");
+  ASSERT_EQ(Tokens.size(), 10u) << Tokens;
+  EXPECT_TOKEN(Tokens[0], tok::l_square, TT_SpliceOpener);
+  EXPECT_TOKEN(Tokens[3], tok::caretcaret, TT_ReflectionOperator);
+  EXPECT_TOKEN(Tokens[6], tok::caretcaret, TT_ReflectionOperator);
+  EXPECT_TOKEN(Tokens[8], tok::r_square, TT_SpliceCloser);
+}
+
 } // namespace
 } // namespace format
 } // namespace clang

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[clang-format] [reflection] Missing space between typename and splicer

2 participants