Skip to content

Conversation

@changkhothuychung
Copy link
Contributor

@changkhothuychung changkhothuychung commented Oct 22, 2025

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

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.

///@{

//===--------------------------------------------------------------------===//
// Parses the operand of reflection operator
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 have a \param and \return

}

void StmtPrinter::VisitCXXReflectExpr(CXXReflectExpr *S) {
// TODO(Reflection): Implement this.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would think you could partially implement this but I guess you are avoiding it to keep things simple?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah im thinking since we don't have enough info in CXXReflectExpr yet, we can leave it for the next round. But let me know what you think we can show now.

@@ -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 == '^') {
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 we should add a check for C++26 as well.

Res = ParseUnaryExprOrTypeTraitExpression();
break;
case tok::caretcaret: {
if (getLangOpts().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 apologize but we should add tests in Objective-C to make sure we don't break things accidently. Maybe @rjmccall might be helpful here in guiding us on things to look out for here.

@ChuanqiXu9
Copy link
Member

If reflection is 'experimental' anything we change that breaks serialization/etc is perfectly fine.

If this is the decision, I think it will be better to explicitly diagnose for any code path that triggers serialization

@erichkeane
Copy link
Collaborator

If reflection is 'experimental' anything we change that breaks serialization/etc is perfectly fine.

If this is the decision, I think it will be better to explicitly diagnose for any code path that triggers serialization

That seems pretty novel? We don't typically diagnose that sort of thing.

@erichkeane
Copy link
Collaborator

Discussing offline-An experimental reflection flag is the best way forward for now. At least until we're out of 'experimental'.

Another thing I was thinking: we should limit where we check it to JUST the places in parsing where we identify the operators. Else, we risk hiding bugs.

1 last thing: we should diagnose in non-reflection mode the token ^ followed by ^. That way we can identify what that could break sooner for users.

@ChuanqiXu9
Copy link
Member

If reflection is 'experimental' anything we change that breaks serialization/etc is perfectly fine.

If this is the decision, I think it will be better to explicitly diagnose for any code path that triggers serialization

That seems pretty novel? We don't typically diagnose that sort of thing.

I just think, if users triggers serialization with reflection now, it is better to diagnose immediately instead of continue silently and the user may meet odd problems...

@erichkeane
Copy link
Collaborator

If reflection is 'experimental' anything we change that breaks serialization/etc is perfectly fine.

If this is the decision, I think it will be better to explicitly diagnose for any code path that triggers serialization

That seems pretty novel? We don't typically diagnose that sort of thing.

I just think, if users triggers serialization with reflection now, it is better to diagnose immediately instead of continue silently and the user may meet odd problems...

Typically we count on the 'experimental' word in the flag to warn that. Do we otherwise diagnose in serialization? I'd rather us not try to do something new there.

@ChuanqiXu9
Copy link
Member

If reflection is 'experimental' anything we change that breaks serialization/etc is perfectly fine.

If this is the decision, I think it will be better to explicitly diagnose for any code path that triggers serialization

That seems pretty novel? We don't typically diagnose that sort of thing.

I just think, if users triggers serialization with reflection now, it is better to diagnose immediately instead of continue silently and the user may meet odd problems...

Typically we count on the 'experimental' word in the flag to warn that. Do we otherwise diagnose in serialization? I'd rather us not try to do something new there.

I just want to offer more info for users so that they can understand the status better and not confusing. This is a suggestion not a requirement.

@Sirraide
Copy link
Member

1 last thing: we should diagnose in non-reflection mode the token ^ followed by ^. That way we can identify what that could break sooner for users.

That’s a good idea, it’d be similar to the C++98 compatibility warning we have that suggests rewriting >> to > >.

Comment on lines +5504 to +5509
/// Represents a C++26 reflect expression [expr.reflect]. The operand of of the
/// expression is either a:
/// - :: (global namespace)
/// - a reflection-name
/// - a type-id
/// - id-expression.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// Represents a C++26 reflect expression [expr.reflect]. The operand of of the
/// expression is either a:
/// - :: (global namespace)
/// - a reflection-name
/// - a type-id
/// - id-expression.
/// Represents a C++26 reflect expression [expr.reflect]. The operand of the
/// expression is either:
/// - :: (global namespace),
/// - a reflection-name,
/// - a type-id, or
/// - an id-expression.

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants