Skip to content

Conversation

@changkhothuychung
Copy link
Contributor

@changkhothuychung changkhothuychung commented Oct 22, 2025

(After changing the scope) This PR implements parsing the reflection operator (^^) for primitive types. The goal is to keep the first PR simple. In subsequent PRs, parsing for the remaining requirements will be introduced.

This implementation is based on the fork of @katzdm.

Class CXXReflectExpr is introduced to represent the operand of the reflection operator. For now, in this PR, the type std::meta::info is not implemented yet, so when we construct an AST node CXXReflectExpr, VoidTy is used as placeholder type for now.

The file ParseReflect.cpp is introduced, which for now only has the function ParseCXXReflectExpression. It parses the operand of the reflection operator.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules labels Oct 22, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 22, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-modules

Author: Nhat Nguyen (changkhothuychung)

Changes

Patch is 23.30 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/164692.diff

28 Files Affected:

  • (modified) clang/include/clang/AST/ExprCXX.h (+50)
  • (modified) clang/include/clang/AST/RecursiveASTVisitor.h (+4)
  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+5)
  • (modified) clang/include/clang/Basic/Features.def (+2)
  • (modified) clang/include/clang/Basic/LangOptions.def (+1)
  • (modified) clang/include/clang/Basic/StmtNodes.td (+3)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+1)
  • (modified) clang/include/clang/Driver/Options.td (+6-1)
  • (modified) clang/include/clang/Parse/Parser.h (+6)
  • (modified) clang/include/clang/Sema/Sema.h (+11)
  • (modified) clang/include/clang/Serialization/ASTBitCodes.h (+3)
  • (modified) clang/lib/AST/ExprCXX.cpp (+34)
  • (modified) clang/lib/AST/ExprClassification.cpp (+1)
  • (modified) clang/lib/AST/ExprConstant.cpp (+1)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+6)
  • (modified) clang/lib/AST/StmtPrinter.cpp (+6)
  • (modified) clang/lib/AST/StmtProfile.cpp (+5)
  • (modified) clang/lib/Lex/Lexer.cpp (+3)
  • (modified) clang/lib/Parse/CMakeLists.txt (+1)
  • (modified) clang/lib/Parse/ParseExpr.cpp (+10)
  • (added) clang/lib/Parse/ParseReflect.cpp (+61)
  • (modified) clang/lib/Parse/ParseTentative.cpp (+3)
  • (modified) clang/lib/Sema/SemaExceptionSpec.cpp (+1)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+19)
  • (modified) clang/lib/Sema/TreeTransform.h (+6)
  • (modified) clang/lib/Serialization/ASTReaderStmt.cpp (+9)
  • (modified) clang/lib/Serialization/ASTWriterStmt.cpp (+6)
  • (added) clang/test/Reflection/parsing-reflection.pass.cpp (+21)
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 5f16bac94d5e6..5c8b2209d5364 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -5493,6 +5493,56 @@ class BuiltinBitCastExpr final
   }
 };
 
+/// Represents a C++2c reflect expression (P2996).
+class CXXReflectExpr : public Expr {
+
+  // Source locations.
+  SourceLocation OperatorLoc;
+  SourceRange OperandRange;
+
+  CXXReflectExpr(const ASTContext &C, QualType T, QualType Ty);
+  CXXReflectExpr(const ASTContext &C, QualType T, Decl *Arg, bool IsNamespace);
+  CXXReflectExpr(EmptyShell Empty);
+
+public:
+
+  static CXXReflectExpr *Create(ASTContext &C, SourceLocation OperatorLoc,
+                                SourceLocation ArgLoc, QualType Operand);
+
+  static CXXReflectExpr *Create(ASTContext &C, SourceLocation OperatorLoc,
+                                SourceLocation OperandLoc, Decl *Operand);
+
+  static CXXReflectExpr *CreateEmpty(ASTContext& C);
+
+  SourceLocation getBeginLoc() const LLVM_READONLY { return OperatorLoc; }
+  SourceLocation getEndLoc() const LLVM_READONLY {
+    return OperandRange.getEnd();
+  }
+  SourceRange getSourceRange() const {
+    return SourceRange(getBeginLoc(), getEndLoc());
+  }
+
+  /// Returns location of the '^^'-operator.
+  SourceLocation getOperatorLoc() const { return OperatorLoc; }
+  SourceRange getOperandRange() const { return OperandRange; }
+
+  /// Sets the location of the '^^'-operator.
+  void setOperatorLoc(SourceLocation L) { OperatorLoc = L; }
+  void setOperandRange(SourceRange R) { OperandRange = R; }
+
+  child_range children() {
+    return child_range(child_iterator(), child_iterator());
+  }
+
+  const_child_range children() const {
+    return const_child_range(const_child_iterator(), const_child_iterator());
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CXXReflectExprClass;
+  }
+};
+
 } // namespace clang
 
 #endif // LLVM_CLANG_AST_EXPRCXX_H
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 7a2881f6124f3..7b8a678ffb861 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -2883,6 +2883,10 @@ DEF_TRAVERSE_STMT(CXXUnresolvedConstructExpr, {
   TRY_TO(TraverseTypeLoc(S->getTypeSourceInfo()->getTypeLoc()));
 })
 
+DEF_TRAVERSE_STMT(CXXReflectExpr, {
+  // TODO
+})
+
 // These expressions all might take explicit template arguments.
 // We traverse those if so.  FIXME: implement these.
 DEF_TRAVERSE_STMT(CXXConstructExpr, {})
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index c724136a7fdaf..ea81a2332cd50 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1848,6 +1848,11 @@ def err_placeholder_expected_auto_or_decltype_auto : Error<
   "expected 'auto' or 'decltype(auto)' after concept name">;
 }
 
+let CategoryName = "Reflection Issue" in {
+def err_cannot_reflect_operand : Error<
+  "cannot reflect the provided operand">;
+}
+
 def warn_max_tokens : Warning<
   "the number of preprocessor source tokens (%0) exceeds this token limit (%1)">,
   InGroup<MaxTokens>, DefaultIgnore;
diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def
index 0e91b42a132c1..c9d24430326ff 100644
--- a/clang/include/clang/Basic/Features.def
+++ b/clang/include/clang/Basic/Features.def
@@ -380,6 +380,8 @@ FEATURE(cxx_abi_relative_vtable, LangOpts.CPlusPlus && LangOpts.RelativeCXXABIVT
 
 FEATURE(clang_atomic_attributes, true)
 
+FEATURE(reflection, LangOpts.Reflection)
+
 // CUDA/HIP Features
 FEATURE(cuda_noinline_keyword, LangOpts.CUDA)
 EXTENSION(cuda_implicit_host_device_templates, LangOpts.CUDA && LangOpts.OffloadImplicitHostDeviceTemplates)
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 84f5ab3443a59..b2051fb536432 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -498,6 +498,7 @@ LANGOPT(BoundsSafety, 1, 0, NotCompatible, "Bounds safety extension for C")
 LANGOPT(EnableLifetimeSafety, 1, 0, NotCompatible, "Experimental lifetime safety analysis for C++")
 
 LANGOPT(PreserveVec3Type, 1, 0, NotCompatible, "Preserve 3-component vector type")
+LANGOPT(Reflection      , 1, 0, NotCompatible, "C++26 Reflection")
 
 #undef LANGOPT
 #undef ENUM_LANGOPT
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index bf3686bb372d5..987e1d1408e06 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -177,6 +177,9 @@ def CoyieldExpr : StmtNode<CoroutineSuspendExpr>;
 def ConceptSpecializationExpr : StmtNode<Expr>;
 def RequiresExpr : StmtNode<Expr>;
 
+// c++ 26 reflection
+def CXXReflectExpr : StmtNode<Expr>;
+
 // Obj-C Expressions.
 def ObjCStringLiteral : StmtNode<Expr>;
 def ObjCBoxedExpr : StmtNode<Expr>;
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 564d6010181cc..b7fef8b2de739 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -233,6 +233,7 @@ PUNCTUATOR(greatergreater,      ">>")
 PUNCTUATOR(greaterequal,        ">=")
 PUNCTUATOR(greatergreaterequal, ">>=")
 PUNCTUATOR(caret,               "^")
+PUNCTUATOR(caretcaret,          "^^")
 PUNCTUATOR(caretequal,          "^=")
 PUNCTUATOR(pipe,                "|")
 PUNCTUATOR(pipepipe,            "||")
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 6245cf33a0719..1880459fab52f 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3663,6 +3663,11 @@ defm application_extension : BoolFOption<"application-extension",
   PosFlag<SetTrue, [], [ClangOption, CC1Option],
           "Restrict code to those available for App Extensions">,
   NegFlag<SetFalse>>;
+defm reflection : BoolFOption<"reflection",
+  LangOpts<"Reflection">, DefaultFalse,
+  PosFlag<SetTrue, [], [ClangOption, CC1Option],
+          "Enable C++26 reflection">,
+  NegFlag<SetFalse>>;
 defm sized_deallocation : BoolFOption<"sized-deallocation",
   LangOpts<"SizedDeallocation">, Default<cpp14.KeyPath>,
   PosFlag<SetTrue, [], [], "Enable C++14 sized global deallocation functions">,
@@ -7153,7 +7158,7 @@ defm android_pad_segment : BooleanFFlag<"android-pad-segment">, Group<f_Group>;
 def shared_libflangrt : Flag<["-"], "shared-libflangrt">,
   HelpText<"Link the flang-rt shared library">, Group<Link_Group>,
   Visibility<[FlangOption]>, Flags<[NoArgumentUnused]>;
-def static_libflangrt : Flag<["-"], "static-libflangrt">, 
+def static_libflangrt : Flag<["-"], "static-libflangrt">,
   HelpText<"Link the flang-rt static library">, Group<Link_Group>,
   Visibility<[FlangOption]>, Flags<[NoArgumentUnused]>;
 
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index e301cf1080977..9b8d8f7633f4d 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -150,6 +150,7 @@ enum class TentativeCXXTypeIdContext {
   AsTemplateArgument,
   InTrailingReturnType,
   AsGenericSelectionArgument,
+  AsReflectionOperand
 };
 
 /// The kind of attribute specifier we have found.
@@ -5167,6 +5168,11 @@ class Parser : public CodeCompletionHandler {
   /// Implementations are in ParseHLSL.cpp
   ///@{
 
+
+    //===--------------------------------------------------------------------===//
+    // C++2c: Reflection [P2996]
+    ExprResult ParseCXXReflectExpression(SourceLocation OpLoc);
+
 private:
   bool MaybeParseHLSLAnnotations(Declarator &D,
                                  SourceLocation *EndLoc = nullptr,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f53aafdeb4f36..d07c1160023b3 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14663,6 +14663,17 @@ class Sema final : public SemaBase {
   /// Implementations are in SemaConcept.cpp
   ///@{
 
+public:
+
+    ExprResult ActOnCXXReflectExpr(SourceLocation OpLoc, TypeSourceInfo* T);
+    ExprResult ActOnCXXReflectExpr(SourceLocation OpLoc,
+                                 SourceLocation ArgLoc, Decl *D);
+
+    ExprResult BuildCXXReflectExpr(SourceLocation OperatorLoc,
+                                 SourceLocation OperandLoc, QualType T);
+    ExprResult BuildCXXReflectExpr(SourceLocation OperatorLoc,
+                                  SourceLocation OperandLoc, Decl *D);
+
 public:
   void PushSatisfactionStackEntry(const NamedDecl *D,
                                   const llvm::FoldingSetNodeID &ID) {
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 5d09d5536e5ab..b950c444d9aa2 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1925,6 +1925,9 @@ enum StmtCode {
   EXPR_CONCEPT_SPECIALIZATION,            // ConceptSpecializationExpr
   EXPR_REQUIRES,                          // RequiresExpr
 
+  // Reflection
+  EXPR_REFLECT,
+
   // CUDA
   EXPR_CUDA_KERNEL_CALL, // CUDAKernelCallExpr
 
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 95de6a82a5270..b4758465f669a 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -1939,6 +1939,40 @@ TypeTraitExpr *TypeTraitExpr::CreateDeserialized(const ASTContext &C,
   return new (Mem) TypeTraitExpr(EmptyShell(), IsStoredAsBool);
 }
 
+CXXReflectExpr::CXXReflectExpr(const ASTContext &C, QualType T, QualType Ty)
+: Expr(CXXReflectExprClass, T, VK_PRValue, OK_Ordinary) {}
+
+CXXReflectExpr::CXXReflectExpr(const ASTContext &C, QualType T, Decl *Arg, bool IsNamespace)
+: Expr(CXXReflectExprClass, T, VK_PRValue, OK_Ordinary) {}
+
+CXXReflectExpr::CXXReflectExpr(EmptyShell Empty)
+: Expr(CXXReflectExprClass, Empty) {}
+
+CXXReflectExpr *CXXReflectExpr::Create(ASTContext &C, SourceLocation OperatorLoc,
+                                SourceLocation OperandLoc, QualType Operand) {
+  CXXReflectExpr *E = new (C) CXXReflectExpr(C, C.DependentTy, Operand);
+  E->setOperatorLoc(OperatorLoc);
+  E->setOperandRange(OperandLoc);
+  return E;
+}
+
+CXXReflectExpr *CXXReflectExpr::Create(ASTContext &C,
+                                       SourceLocation OperatorLoc,
+                                       SourceLocation OperandLoc,
+                                       Decl *Operand) {
+  bool IsNamespace = isa<TranslationUnitDecl>(Operand);
+
+  CXXReflectExpr *E = new (C) CXXReflectExpr(C, C.DependentTy, Operand,
+                                             IsNamespace);
+  E->setOperatorLoc(OperatorLoc);
+  E->setOperandRange(OperandLoc);
+  return E;
+}
+
+CXXReflectExpr *CXXReflectExpr::CreateEmpty(ASTContext &C) {
+  return new (C) CXXReflectExpr(EmptyShell());
+}
+
 CUDAKernelCallExpr::CUDAKernelCallExpr(Expr *Fn, CallExpr *Config,
                                        ArrayRef<Expr *> Args, QualType Ty,
                                        ExprValueKind VK, SourceLocation RP,
diff --git a/clang/lib/AST/ExprClassification.cpp b/clang/lib/AST/ExprClassification.cpp
index aeacd0dc765ef..4c53c316e989a 100644
--- a/clang/lib/AST/ExprClassification.cpp
+++ b/clang/lib/AST/ExprClassification.cpp
@@ -216,6 +216,7 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) {
   case Expr::SourceLocExprClass:
   case Expr::ConceptSpecializationExprClass:
   case Expr::RequiresExprClass:
+  case Expr::CXXReflectExprClass:
     return Cl::CL_PRValue;
 
   case Expr::EmbedExprClass:
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index b706b14945b6d..0f14aa3c7258f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -18295,6 +18295,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
   case Expr::ArrayTypeTraitExprClass:
   case Expr::ExpressionTraitExprClass:
   case Expr::CXXNoexceptExprClass:
+  case Expr::CXXReflectExprClass:
     return NoDiag();
   case Expr::CallExprClass:
   case Expr::CXXOperatorCallExprClass: {
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 2173aed5b45af..18bd38c66b5e9 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -4945,6 +4945,12 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
     E = cast<ConstantExpr>(E)->getSubExpr();
     goto recurse;
 
+  case Expr::CXXReflectExprClass: {
+    // TODO: implement this after introducing std::meta::info
+    // and add info in APValue
+    break;
+  }
+
   // FIXME: invent manglings for all these.
   case Expr::BlockExprClass:
   case Expr::ChooseExprClass:
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index 586c3000f105c..8e5bab721e8e4 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -2566,6 +2566,12 @@ void StmtPrinter::VisitCXXUnresolvedConstructExpr(
     OS << ')';
 }
 
+
+void StmtPrinter::VisitCXXReflectExpr(CXXReflectExpr *S) {
+  // TODO: Make this better.
+  OS << "^(...)";
+}
+
 void StmtPrinter::VisitCXXDependentScopeMemberExpr(
                                          CXXDependentScopeMemberExpr *Node) {
   if (!Node->isImplicitAccess()) {
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 589a156a2b6ea..ad37b5e71472e 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -2164,6 +2164,11 @@ StmtProfiler::VisitLambdaExpr(const LambdaExpr *S) {
   ID.AddInteger(Hasher.CalculateHash());
 }
 
+void StmtProfiler::VisitCXXReflectExpr(const CXXReflectExpr *E) {
+  VisitExpr(E);
+  // TODO: 
+}
+
 void
 StmtProfiler::VisitCXXScalarValueInitExpr(const CXXScalarValueInitExpr *S) {
   VisitExpr(S);
diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp
index b282a600c0e56..5df36d041d0c1 100644
--- a/clang/lib/Lex/Lexer.cpp
+++ b/clang/lib/Lex/Lexer.cpp
@@ -4348,6 +4348,9 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) {
     if (Char == '=') {
       CurPtr = ConsumeChar(CurPtr, SizeTmp, Result);
       Kind = tok::caretequal;
+    } else if (LangOpts.Reflection && Char == '^') {
+      CurPtr = ConsumeChar(CurPtr, SizeTmp, Result);
+      Kind = tok::caretcaret;
     } else {
       if (LangOpts.OpenCL && Char == '^')
         Diag(CurPtr, diag::err_opencl_logical_exclusive_or);
diff --git a/clang/lib/Parse/CMakeLists.txt b/clang/lib/Parse/CMakeLists.txt
index e6cbf3b868b7d..8dd120f529b13 100644
--- a/clang/lib/Parse/CMakeLists.txt
+++ b/clang/lib/Parse/CMakeLists.txt
@@ -26,6 +26,7 @@ add_clang_library(clangParse
   ParseTentative.cpp
   Parser.cpp
   ParseOpenACC.cpp
+  ParseReflect.cpp
 
   LINK_LIBS
   clangAST
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 3515343202de1..22963d985b01b 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -1208,6 +1208,13 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand,
     AllowSuffix = false;
     Res = ParseUnaryExprOrTypeTraitExpression();
     break;
+  case tok::caretcaret: {
+    if (getLangOpts().Reflection) {
+      SourceLocation FirstCaret = ConsumeToken(); // eat first '^'
+      Res = ParseCXXReflectExpression(/*OpLoc=*/FirstCaret);
+    }
+    break;
+  }
   case tok::ampamp: {      // unary-expression: '&&' identifier
     if (NotPrimaryExpression)
       *NotPrimaryExpression = true;
@@ -2249,6 +2256,9 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
   else if (getLangOpts().C2y && OpTok.is(tok::kw__Countof))
     Diag(OpTok, diag::warn_c2y_compat_keyword) << OpTok.getName();
 
+  if (OpTok.is(tok::caretcaret))
+    return ParseCXXReflectExpression(OpTok.getLocation());
+
   EnterExpressionEvaluationContext Unevaluated(
       Actions, Sema::ExpressionEvaluationContext::Unevaluated,
       Sema::ReuseLambdaContextDecl);
diff --git a/clang/lib/Parse/ParseReflect.cpp b/clang/lib/Parse/ParseReflect.cpp
new file mode 100644
index 0000000000000..8c974d949d87f
--- /dev/null
+++ b/clang/lib/Parse/ParseReflect.cpp
@@ -0,0 +1,61 @@
+//===--- ParseReflect.cpp - C++2c Reflection Parsing ---------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file implements parsing for reflection facilities.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/LocInfoType.h"
+#include "clang/Basic/DiagnosticParse.h"
+#include "clang/Parse/Parser.h"
+#include "clang/Sema/EnterExpressionEvaluationContext.h"
+using namespace clang;
+
+ExprResult Parser::ParseCXXReflectExpression(SourceLocation OpLoc) {
+  assert(Tok.is(tok::caretcaret) && "expected '^^'");
+  EnterExpressionEvaluationContext Unevaluated(
+      Actions, Sema::ExpressionEvaluationContext::Unevaluated);
+
+  SourceLocation OperandLoc = Tok.getLocation();
+
+  // Parse a leading nested-name-specifier
+  CXXScopeSpec SS;
+  if (ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
+                                     /*ObjectHasErrors=*/false,
+                                     /*EnteringContext=*/false)) {
+    SkipUntil(tok::semi, StopAtSemi | StopBeforeMatch);
+    return ExprError();
+  }
+
+  {
+    TentativeParsingAction TPA(*this);
+
+    if (SS.isValid() &&
+               SS.getScopeRep().getKind() == NestedNameSpecifier::Kind::Global) {
+      // Check for global namespace '^^::'
+      TPA.Commit();
+      Decl *TUDecl = Actions.getASTContext().getTranslationUnitDecl();
+      return Actions.ActOnCXXReflectExpr(OpLoc, SourceLocation(), TUDecl);
+    }
+    TPA.Revert();
+  }
+
+  if (isCXXTypeId(TentativeCXXTypeIdContext::AsReflectionOperand)) {
+    TypeResult TR = ParseTypeName(/*TypeOf=*/nullptr);
+    if (TR.isInvalid())
+      return ExprError();
+
+    TypeSourceInfo *TSI = nullptr;
+    QualType QT = Actions.GetTypeFromParser(TR.get(), &TSI);
+
+    return Actions.ActOnCXXReflectExpr(OpLoc, TSI);
+  }
+
+  Diag(OperandLoc, diag::err_cannot_reflect_operand);
+  return ExprError();
+}
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index 82f2294ff5bb7..8a3ae2232767e 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -574,6 +574,9 @@ bool Parser::isCXXTypeId(TentativeCXXTypeIdContext Context, bool &isAmbiguous) {
     } else if (Context == TentativeCXXTypeIdContext::InTrailingReturnType) {
       TPR = TPResult::True;
       isAmbiguous = true;
+    } else if (Context == TentativeCXXTypeIdContext::AsReflectionOperand) {
+      TPR = TPResult::True;
+      isAmbiguous = true;
     } else
       TPR = TPResult::False;
   }
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index a0483c3027199..ff8d2139289a3 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -1379,6 +1379,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
   case Expr::CXXNoexceptExprClass:
   case Expr::CXXNullPtrLiteralExprClass:
   case Expr::CXXPseudoDestructorExprClass:
+  case Expr::CXXReflectExprClass:
   case Expr::CXXScalarValueInitExprClass:
   case Expr::CXXThisExprClass:
   case Expr::CXXUuidofExprClass:
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 06b2529011c74..95bc91ab29f90 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -17760,6 +17760,25 @@ void Sema::PushExpressionEvaluationContextForFunction(
   }
 }
 
+ExprResult Sema::ActOnCXXReflectExpr(SourceLocation OpLoc, TypeSourceInfo* TSI) {
+  return BuildCXXReflectExpr(OpLoc, TSI->getTypeLoc().getBeginLoc(), TSI->getType());
+}
+
+ExprResult Sema::ActOnCXXReflectExpr(SourceLocation OpLoc,
+                                     SourceLocation ArgLoc, Decl *D) {
+  return BuildCXXReflectExpr(OpLoc, ArgLoc, D);
+}
+
+ExprResult Sema::BuildCXXReflectExpr(SourceLocation OperatorLoc,
+                             ...
[truncated]

@github-actions
Copy link

github-actions bot commented Oct 22, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@erichkeane
Copy link
Collaborator

Please find a way to break this up into meaningful chunks that we can review.

@erichkeane, has some sort of direction been agreed upon for how the functionality will be enabled? If it involves an option flag such as -fexperimental-reflection, would you consider an initial PR that simply adds the flag and the associated documentation to be a "meaningful chunk"?

I think we are generally in favor of that direction, and yes, such a patch would be a good initial patch. I spoke briefly with Dan a few minutes ago, and think that a patch that introduced the basics of 'parsing' (that just did consume + diagnostic + ignore) would be an actionable first set, then we can start adding the AST node as we handle 1 thing at a time. I think that would be eminently more manageable.

@hubert-reinterpretcast
Copy link
Collaborator

a patch that introduced the basics of 'parsing' (that just did consume + diagnostic + ignore) would be an actionable first set

What does this "ignore" look like? I am concerned if we "parse" but are unable to demonstrate that the tokens are accepted in the right grammar contexts. Perhaps we can emit into the AST as if it was a nullptr literal`?

static CXXReflectExpr *Create(ASTContext &C, SourceLocation OperatorLoc,
SourceLocation ArgLoc, QualType Operand);

static CXXReflectExpr *Create(ASTContext &C, SourceLocation OperatorLoc,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grammar says

reflect-expression:
^^ ::
^^ reflection-name
^^ type-id
^^ id-expression
reflection-name:
  nested-name-specifier identifier
  nested-name-specifier template identifier

To model that we should have overloads for

  • TypeLoc
  • Expr*

For Namespaces, I think we can reuse NestedNameSpecifierLoc - to check

For declarations we need to a way to store

  • the location of the identifier
  • the loc of any template keyword

We might want to introduce a new struct for that

{
NestedNameSpecifierLoc NNS;
SourceLocation IdLoc;
SourceLocation TemplateKWLoc = {};
}

But maybe that would be reinventing DeclRefExpr - Can we reuse DeclRefExpr? That would be neat. I don't think DeclRefExpr is meant to support namespaces, aliases, etc though.

SS.getScopeRep().getKind() == NestedNameSpecifier::Kind::Global) {
// global namespace ::.
Decl *TUDecl = Actions.getASTContext().getTranslationUnitDecl();
return Actions.ActOnCXXReflectExpr(DoubleCaretLoc, SourceLocation(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 2 relevant source locations here

  • the location of the declaration
  • the location of :: which we definitively want to keep - cf above comment for ways we could do that`

Comment on lines 35 to 36
if (Tok.isOneOf(tok::identifier, tok::kw_operator, tok::kw_template,
tok::tilde, tok::annot_template_id)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be other relevant annotations here (like type annotation)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cor3ntin are you referring to tok::annot_typename and tok::annot_decltype ?

@hubert-reinterpretcast
Copy link
Collaborator

@hubert-reinterpretcast regarding your question of "Is it intentional that there are no driver changes in this PR to accept this option and consume it for the front-end?", reflection is currently guarded by a flag -freflection, what are you referring to as driver changes?

See https://github.com/bloomberg/clang-p2996/blob/4fd4f57ef9212c847ee05e034aed3bf7c59cb3cc/clang/lib/Driver/ToolChains/Clang.cpp#L7406-L7408.

@erichkeane
Copy link
Collaborator

a patch that introduced the basics of 'parsing' (that just did consume + diagnostic + ignore) would be an actionable first set

What does this "ignore" look like? I am concerned if we "parse" but are unable to demonstrate that the tokens are accepted in the right grammar contexts. Perhaps we can emit into the AST as if it was a nullptr literal`?

IMO the 'parser errors' are sufficient enough for me to show that the parsing contexts/etc are well enough developed to merge. IF there is a mistake, we can always fix it up later.

I did the same with the OpenACC stuff when doing the implementation, and we did the same with concepts when it joined clang. Simply emitting the parser errors/not calling into SEMA at all is sufficient to demonstrate that we are handling the text correctly. IF we wanted to add the 'std::meta' type and have it always just toss in a std::meta with no backing to it/etc, I suspect that might be acceptable if the size of the patch didn't get out of hand.

But my big fear is that I'm expected to review huge patches that I'll never get through them, and as a template maintainer, this is stuff that I SHOULD be doing.

I'd rather small patches that don't really show much/any functionality that I can review in a short amount of time, vs something that takes me several hours (like this one) to look at every time I context switch back.

@changkhothuychung
Copy link
Contributor Author

@hubert-reinterpretcast regarding your question of "Is it intentional that there are no driver changes in this PR to accept this option and consume it for the front-end?", reflection is currently guarded by a flag -freflection, what are you referring to as driver changes?

See https://github.com/bloomberg/clang-p2996/blob/4fd4f57ef9212c847ee05e034aed3bf7c59cb3cc/clang/lib/Driver/ToolChains/Clang.cpp#L7406-L7408.

Ah I see, thanks! Yeah it should be off by default.


let CategoryName = "Reflection Issue" in {
def err_cannot_reflect_operand : Error<
"expected reflectable entity">;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we think of better wording for this? The diagnostic doesn't really help the user to understand how to repair their code -- if they passed in something wrong, they likely don't know what a "reflectable entity" is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now this PR only supports parsing for global namespace and primitive types. If a diagnostics is hit, it could be because the operand is invalid, or it is not yet implemented. The parsing logic is incomplete, so its hard to distinguish the two's for now. I proposed I would complete the parsing logic in the next PR, so we would have a better idea how we want to provide wordings for the diagnostics.

If you have any suggestions for now, please let me know, thanks!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its probably ok to leave this really awful for now, I have no idea what it means either,, but I'm hopeful this'll improve.
DEPENDING on how long we intend this to last, we might think about saying something like, unknown or unimplemented reflectable entity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the message to unknown or unimplemented reflectable entity. Depending on how we want to scope the next PR, I can work on parsing the remaining items in reflect-expression, by then we will have a better idea how we want to have diagnostics messages in specific cases.

@@ -499,6 +498,7 @@ LANGOPT(BoundsSafety, 1, 0, NotCompatible, "Bounds safety extension for C")
LANGOPT(EnableLifetimeSafety, 1, 0, NotCompatible, "Experimental lifetime safety analysis for C++")

LANGOPT(PreserveVec3Type, 1, 0, NotCompatible, "Preserve 3-component vector type")
LANGOPT(Reflection , 1, 0, NotCompatible, "C++26 Reflection")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I (at least partially) disagree. Reflection is so experimental I think it should be an option, but one that can only be enabled in C++26 mode. Given that the feature is changing in the last few minutes of getting C++26 out the door, I think we should treat reflection like we would a TS to some degree so users can opt out of it, especially because of the potential to break code (there's still parsing ambiguities with ^^ and blocks, though far less of them than there were with ^ as the operator).

@@ -3689,6 +3663,11 @@ defm application_extension : BoolFOption<"application-extension",
PosFlag<SetTrue, [], [ClangOption, CC1Option],
"Restrict code to those available for App Extensions">,
NegFlag<SetFalse>>;
defm reflection : BoolFOption<"reflection",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes sense as a CC1-only option rather than a driver option. aka. the driver can say "C++26 mode? enable reflection by default", but there's still a hidden escape hatch to say -Xclang -fno-reflection if you need to. I think the frontend should reject -freflection outside of -std=c++26 (or later) modes, so this isn't a backdoor way to enable reflection in older language modes. WDYT?

E = cast<ConstantExpr>(E)->getSubExpr();
goto recurse;

case Expr::CXXReflectExprClass: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we need a similar case for the Microsoft mangler?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katzdm can you help me on this question?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@changkhothuychung Yes, absolutely; analogous changes should be made to MicrosoftMangle.cpp.

Some context: ItaniumMangle.cpp and MicrosoftMangle.cpp implement two different schemes for mangling the name of a C++ entity; the former implements the Itanium C++ ABI, whereas the latter (as far as I'm aware) does "whatever Microsoft Visual C++ does".

A few things to note here:

  1. In the Clang/P2996 fork, I at some point basically stopped updating MicrosoftMangle.cpp (for no other principled reason than laziness).
  2. Since MicrosoftMangle.cpp attempts to remain compatible with "whatever Microsoft Visual C++" does, any mangling implemented here is just guesswork (which will probably be wrong) until Microsoft implements P2996 (which will surely any day now, right? 😉) Until then, it would probably be appropriate to just issue a diagnostic and fail the build if we try to mangle a reflection with Microsoft mangling.
  3. Please note that there is an active Github issue on the Itanium ABI repository discussing mangling for reflections. Anything mangling implemented in ItaniumMangle.cpp should prefer to follow what's suggested there, rather than follow whatever random nonsense I did in the Clang/P2996 repository. If any issues are encountered, we should of course surface them on that discussion thread.

@AaronBallman - Does that all sound right to you?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AaronBallman - Does that all sound right to you?

That all sounds correct to me!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AaronBallman after looking more into this, I think we can start working on this in subsequent PRs when more facilities are available. For now I'm trying to keep the scope of this PR small to get it merged. Does that sound good to you?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooof, doing this as a 'fallthrough' is probably a mistake. We should do this as a Diags.Report (we have one for 'we dont know what to do wtih this yet`.

We should just error-out, like we do in the !NullOut situation below, but do so always.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make CXXReflectExprClass join the !NullOut situation below for now?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably just not do a fallthrough, and just copy/paste the stuff inside the !NullOpt into it.

@github-actions
Copy link

github-actions bot commented Nov 19, 2025

🐧 Linux x64 Test Results

  • 111692 tests passed
  • 4473 tests skipped

@changkhothuychung
Copy link
Contributor Author

changkhothuychung commented Nov 26, 2025

After an internal discussion with @katzdm and @cor3ntin , we are thinking of reducing the scope of this PR to only parse primitive types for now. Also, the design of CXXReflectExpr will have some changes, therefore I will remove the parsing of :: in this PR and leave it for later. Does anyone have any concern?

@AaronBallman @shafik @hubert-reinterpretcast @Sirraide

@katzdm
Copy link
Contributor

katzdm commented Nov 26, 2025

After an internal discussion with @katzdm and @cor3ntin , we are thinking of reducing the scope of this PR to only parse primitive types for now. Also, the design of CXXReflectExpr will have some changes, therefore I will remove the parsing of :: in this PR and leave it for later. Does anyone have any concern?

@AaronBallman @shafik @hubert-reinterpretcast @Sirraide

A little more detail - this is a sketch I shared of the design that we discussed moving towards:

class CXXReflectExpr : public Expr {
  enum class OperandKind {
    Type,       // TypeLoc
    Namespace,  // NamespaceReference
    Template,   // TemplateReference
    IdExpr,     // DeclRefExpr
  };

  SourceLocation OpLoc;  // Location of '^^'
  OperandKind Kind;
  const void *OperandRef;

  CXXReflectExpr(SourceLocation CaretCaret, const TypeLoc *TL)
  : OpLoc(CaretCaret), OperandRef(TL), Kind(OperandKind::Type) {}

  CXXReflectExpr(SourceLocation CaretCaret, const NamespaceReference *NSRef)
  : OpLoc(CaretCaret), OperandRef(NSRef), Kind(OperandKind::Namespace)

  // ... etc ...

  SourceLocation getBeginLoc() const {
    switch (Kind) {
    case OperandKind::Type:
      return static_cast<const TypeLoc *>(OperandRef)->getBeginLoc();
    case OperandKind::Namespace:
      return static_cast<const NamespaceReference *>(OperandRef)->getBeginLoc();
    case OperandKind::Template:
      return static_cast<const TemplateReference *>(OperandRef)->getBeginLoc();
    case OperandKind::IdExpr:
      return static_cast<const DeclRefExpr *>(OperandRef)->getBeginLoc();
    default:
      llvm_unreachable("unknown operand kind");
    }
  }

  // ...
};

where TemplateReference is a renaming/extension of the existing ConceptReference, and NamespaceReference will be (NestedNameSpecifierLoc | SourceLocation (of the terminal name) | NamespaceBaseDecl). The global namespace can be represented by a NamespaceReference whose NamespaceBaseDecl is nullptr.

The PR following this one would integrate parsing and representation of other reflect-expressions whose operands are types, followed by additional PRs that will handle the other three cases.

@erichkeane
Copy link
Collaborator

After an internal discussion with @katzdm and @cor3ntin , we are thinking of reducing the scope of this PR to only parse primitive types for now. Also, the design of CXXReflectExpr will have some changes, therefore I will remove the parsing of :: in this PR and leave it for later. Does anyone have any concern?
@AaronBallman @shafik @hubert-reinterpretcast @Sirraide

A little more detail - this is a sketch I shared of the design that we discussed moving towards:

class CXXReflectExpr : public Expr {
  enum class OperandKind {
    Type,       // TypeLoc
    Namespace,  // NamespaceReference
    Template,   // TemplateReference
    IdExpr,     // DeclRefExpr
  };

  SourceLocation OpLoc;  // Location of '^^'
  OperandKind Kind;
  const void *OperandRef;

  CXXReflectExpr(SourceLocation CaretCaret, const TypeLoc *TL)
  : OpLoc(CaretCaret), OperandRef(TL), Kind(OperandKind::Type) {}

  CXXReflectExpr(SourceLocation CaretCaret, const NamespaceReference *NSRef)
  : OpLoc(CaretCaret), OperandRef(NSRef), Kind(OperandKind::Namespace)

  // ... etc ...

  SourceLocation getBeginLoc() const {
    switch (Kind) {
    case OperandKind::Type:
      return static_cast<const TypeLoc *>(OperandRef)->getBeginLoc();
    case OperandKind::Namespace:
      return static_cast<const NamespaceReference *>(OperandRef)->getBeginLoc();
    case OperandKind::Template:
      return static_cast<const TemplateReference *>(OperandRef)->getBeginLoc();
    case OperandKind::IdExpr:
      return static_cast<const DeclRefExpr *>(OperandRef)->getBeginLoc();
    default:
      llvm_unreachable("unknown operand kind");
    }
  }

  // ...
};

where TemplateReference is a renaming/extension of the existing ConceptReference, and NamespaceReference will be (NestedNameSpecifierLoc | SourceLocation (of the terminal name) | NamespaceBaseDecl). The global namespace can be represented by a NamespaceReference whose NamespaceBaseDecl is nullptr.

The PR following this one would integrate parsing and representation of other reflect-expressions whose operands are types, followed by additional PRs that will handle the other three cases.

This seems reasonable to me. Instead of a void* for OperandRef, we should just use one of the various forms of variant that we have around (we have a pointer-variant that doesnt' actually take any additional space), and instead of OpLoc, CaretCaretLoc, but otherwise think that looks good.

@katzdm
Copy link
Contributor

katzdm commented Nov 26, 2025

After an internal discussion with @katzdm and @cor3ntin , we are thinking of reducing the scope of this PR to only parse primitive types for now. Also, the design of CXXReflectExpr will have some changes, therefore I will remove the parsing of :: in this PR and leave it for later. Does anyone have any concern?
@AaronBallman @shafik @hubert-reinterpretcast @Sirraide

A little more detail - this is a sketch I shared of the design that we discussed moving towards:

class CXXReflectExpr : public Expr {
  enum class OperandKind {
    Type,       // TypeLoc
    Namespace,  // NamespaceReference
    Template,   // TemplateReference
    IdExpr,     // DeclRefExpr
  };

  SourceLocation OpLoc;  // Location of '^^'
  OperandKind Kind;
  const void *OperandRef;

  CXXReflectExpr(SourceLocation CaretCaret, const TypeLoc *TL)
  : OpLoc(CaretCaret), OperandRef(TL), Kind(OperandKind::Type) {}

  CXXReflectExpr(SourceLocation CaretCaret, const NamespaceReference *NSRef)
  : OpLoc(CaretCaret), OperandRef(NSRef), Kind(OperandKind::Namespace)

  // ... etc ...

  SourceLocation getBeginLoc() const {
    switch (Kind) {
    case OperandKind::Type:
      return static_cast<const TypeLoc *>(OperandRef)->getBeginLoc();
    case OperandKind::Namespace:
      return static_cast<const NamespaceReference *>(OperandRef)->getBeginLoc();
    case OperandKind::Template:
      return static_cast<const TemplateReference *>(OperandRef)->getBeginLoc();
    case OperandKind::IdExpr:
      return static_cast<const DeclRefExpr *>(OperandRef)->getBeginLoc();
    default:
      llvm_unreachable("unknown operand kind");
    }
  }

  // ...
};

where TemplateReference is a renaming/extension of the existing ConceptReference, and NamespaceReference will be (NestedNameSpecifierLoc | SourceLocation (of the terminal name) | NamespaceBaseDecl). The global namespace can be represented by a NamespaceReference whose NamespaceBaseDecl is nullptr.
The PR following this one would integrate parsing and representation of other reflect-expressions whose operands are types, followed by additional PRs that will handle the other three cases.

This seems reasonable to me. Instead of a void* for OperandRef, we should just use one of the various forms of variant that we have around (we have a pointer-variant that doesnt' actually take any additional space), and instead of OpLoc, CaretCaretLoc, but otherwise think that looks good.

I had originally thought of using llvm::PointerUnion, but disliked that we couldn't switch on it (which seems friendlier to me than if-else chains). We could do ``llvm::PointerIntPair<void*, 2, OperandKind>`, though - what do you think? Or would you prefer a more strongly typed pointer?

@erichkeane
Copy link
Collaborator

After an internal discussion with @katzdm and @cor3ntin , we are thinking of reducing the scope of this PR to only parse primitive types for now. Also, the design of CXXReflectExpr will have some changes, therefore I will remove the parsing of :: in this PR and leave it for later. Does anyone have any concern?
@AaronBallman @shafik @hubert-reinterpretcast @Sirraide

A little more detail - this is a sketch I shared of the design that we discussed moving towards:

class CXXReflectExpr : public Expr {
  enum class OperandKind {
    Type,       // TypeLoc
    Namespace,  // NamespaceReference
    Template,   // TemplateReference
    IdExpr,     // DeclRefExpr
  };

  SourceLocation OpLoc;  // Location of '^^'
  OperandKind Kind;
  const void *OperandRef;

  CXXReflectExpr(SourceLocation CaretCaret, const TypeLoc *TL)
  : OpLoc(CaretCaret), OperandRef(TL), Kind(OperandKind::Type) {}

  CXXReflectExpr(SourceLocation CaretCaret, const NamespaceReference *NSRef)
  : OpLoc(CaretCaret), OperandRef(NSRef), Kind(OperandKind::Namespace)

  // ... etc ...

  SourceLocation getBeginLoc() const {
    switch (Kind) {
    case OperandKind::Type:
      return static_cast<const TypeLoc *>(OperandRef)->getBeginLoc();
    case OperandKind::Namespace:
      return static_cast<const NamespaceReference *>(OperandRef)->getBeginLoc();
    case OperandKind::Template:
      return static_cast<const TemplateReference *>(OperandRef)->getBeginLoc();
    case OperandKind::IdExpr:
      return static_cast<const DeclRefExpr *>(OperandRef)->getBeginLoc();
    default:
      llvm_unreachable("unknown operand kind");
    }
  }

  // ...
};

where TemplateReference is a renaming/extension of the existing ConceptReference, and NamespaceReference will be (NestedNameSpecifierLoc | SourceLocation (of the terminal name) | NamespaceBaseDecl). The global namespace can be represented by a NamespaceReference whose NamespaceBaseDecl is nullptr.
The PR following this one would integrate parsing and representation of other reflect-expressions whose operands are types, followed by additional PRs that will handle the other three cases.

This seems reasonable to me. Instead of a void* for OperandRef, we should just use one of the various forms of variant that we have around (we have a pointer-variant that doesnt' actually take any additional space), and instead of OpLoc, CaretCaretLoc, but otherwise think that looks good.

I had originally thought of using llvm::PointerUnion, but disliked that we couldn't switch on it (which seems friendlier to me than if-else chains). We could do ``llvm::PointerIntPair<void*, 2, OperandKind>`, though - what do you think? Or would you prefer a more strongly typed pointer?

I have a slightly-more-than-mild preference for something like PointerUnion, as it better expresses the types it contains/won't allow us to do silly things in there/keeps the type and discriminator in sync in a way that is otherwise unfortunately manual.

IIRC there is a llvm::TypeSwitch type thing that should work on PointerUnion, which at least gets switch-like semantics.

@Sirraide
Copy link
Member

This seems reasonable to me. Instead of a void* for OperandRef, we should just use one of the various forms of variant that we have around (we have a pointer-variant that doesnt' actually take any additional space),

Yeah, I was also going to suggest using llvm::PointerUnion for this; if we force the alignment of all the classes involved to be 8 (which they probably will be anyway), then a union of 4 different pointer types should just work.

@changkhothuychung
Copy link
Contributor Author

Since we removed parsing global namespace in this PR, I also removed tests related to namespace, and just keep the tests related to primitive types. We can add tests for the remaining items in the next PR, and also improve the diagnostics message.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we should be using llvm_unreachable for not implemented, we should diagnose whenever possible, but unreachable is an opt-hint. Though, assert(false...) will only apply during asserts builds, it at least doesn't result in UB.

We do this a few times in this patch.

Else, a couple of suggestions.


let CategoryName = "Reflection Issue" in {
def err_cannot_reflect_operand : Error<
"expected reflectable entity">;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its probably ok to leave this really awful for now, I have no idea what it means either,, but I'm hopeful this'll improve.
DEPENDING on how long we intend this to last, we might think about saying something like, unknown or unimplemented reflectable entity.

E = cast<ConstantExpr>(E)->getSubExpr();
goto recurse;

case Expr::CXXReflectExprClass: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooof, doing this as a 'fallthrough' is probably a mistake. We should do this as a Diags.Report (we have one for 'we dont know what to do wtih this yet`.

We should just error-out, like we do in the !NullOut situation below, but do so always.

// operator and the second being the introducer for the block.
if (OpToken.is(tok::caretcaret)) {
assert(getLangOpts().Reflection &&
"reflection support disabled - compile with -freflection");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

messages to users in 'asserts' is weird. If we want to say something like this, we should be diagnosing.

Copy link
Contributor

@katzdm katzdm Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be a diagnostic - this is more of a "sanity check", since tok::caretcaret should never be lexed unless getLangOpts().Reflection is enabled.

In light of that, maybe change the assert-message. Something like:

"token '^^' can only appear with reflection enabled"

Or just omit the message.

assert(getLangOpts().Reflection);

@changkhothuychung changkhothuychung changed the title [clang]: reflection operator parsing for global namespace and primitive types [clang]: reflection operator parsing for primitive types Dec 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++26 clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category reflection Issues related to C++26 reflection

Projects

None yet

Development

Successfully merging this pull request may close these issues.