[compiler-rt] [libcxx] [libunwind] [clang] [openmp] [llvm] [clang-tools-extra] [libcxxabi] [lld] [flang] [lldb] [mlir] [libc] [C23] Implement N3018: The constexpr specifier for object definitions (PR #73099)

Mariya Podchishchaeva via llvm-commits llvm-commits at lists.llvm.org
Thu Nov 30 03:17:25 PST 2023


https://github.com/Fznamznon updated https://github.com/llvm/llvm-project/pull/73099

>From 1d70b7726e7d1f11622a6d5c8246b0737e024c8d Mon Sep 17 00:00:00 2001
From: "Podchishchaeva, Mariya" <mariya.podchishchaeva at intel.com>
Date: Tue, 19 Sep 2023 08:37:18 -0700
Subject: [PATCH 1/6] [C23] Implement N3018: The constexpr specifier for object
 definitions

The implementation mostly reuses C++ code paths where possible,
including narrowing check in order to provide diagnostic messages in
case initializer for constexpr variable is not exactly representable in
target type.

The following won't work due to lack of support for other features:
- Diagnosing of underspecified declarations involving constexpr
- Constexpr attached to compound literals

Also due to lack of support for char8_t some of examples with utf-8
strings don't work properly.
---
 clang/docs/ReleaseNotes.rst                   |   1 +
 .../clang/Basic/DiagnosticSemaKinds.td        |  16 +
 clang/include/clang/Basic/TokenKinds.def      |   2 +-
 clang/lib/AST/Decl.cpp                        |  16 +-
 clang/lib/AST/ExprConstant.cpp                |  17 +-
 clang/lib/Parse/ParseDecl.cpp                 |   2 +
 clang/lib/Sema/SemaDecl.cpp                   | 204 +++++++++++--
 clang/lib/Sema/SemaOverload.cpp               |  36 ++-
 clang/test/C/C2x/n3018.c                      |  86 ++++++
 clang/test/Parser/c23-constexpr.c             |   6 +
 clang/test/Sema/constexpr.c                   | 275 ++++++++++++++++++
 clang/www/c_status.html                       |   2 +-
 12 files changed, 627 insertions(+), 36 deletions(-)
 create mode 100644 clang/test/C/C2x/n3018.c
 create mode 100644 clang/test/Parser/c23-constexpr.c
 create mode 100644 clang/test/Sema/constexpr.c

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index b65106b9106d4d7..cae1707f3e30feb 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -208,6 +208,7 @@ C23 Feature Support
 
 - Clang now supports ``<stdckdint.h>`` which defines several macros for performing
   checked integer arithmetic. It is also exposed in pre-C23 modes.
+- Clang now supports ``N3018 The constexpr specifier for object definitions``.
 
 - Completed the implementation of
   `N2508 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2508.pdf>`_. We
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 990692c06d7d3a8..11f24583dc55a9b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2932,6 +2932,22 @@ def warn_private_extern : Warning<
 def note_private_extern : Note<
   "use __attribute__((visibility(\"hidden\"))) attribute instead">;
 
+// C23 constexpr
+def err_c23_thread_local_constexpr : Error<
+  "thread-local storage is not allowed with constexpr">;
+def err_c23_extern_constexpr : Error<
+  "extern specifier is not allowed with constexpr">;
+def err_c23_constexpr_not_variable : Error<
+  "constexpr is only allowed in variable declarations">;
+def err_c23_constexpr_invalid_type : Error<
+  "constexpr variable cannot have type %0">;
+def err_c23_constexpr_init_not_representable : Error<
+  "constexpr initializer evaluates to %0 which is not exactly representable in type %1">;
+def err_c23_constexpr_init_type_mismatch : Error<
+  "constexpr initializer for type %0 is of type %1">;
+def err_c23_constexpr_pointer_not_null : Error<
+  "constexpr pointer initializer is not null">;
+
 // C++ Concepts
 def err_concept_decls_may_only_appear_in_global_namespace_scope : Error<
   "concept declarations may only appear in global or namespace scope">;
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 3ab420821d82bfe..e9e8f59247662ea 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -393,7 +393,7 @@ CXX11_KEYWORD(alignas               , KEYC23)
 CXX11_UNARY_EXPR_OR_TYPE_TRAIT(alignof, AlignOf, KEYC23)
 CXX11_KEYWORD(char16_t              , KEYNOMS18)
 CXX11_KEYWORD(char32_t              , KEYNOMS18)
-CXX11_KEYWORD(constexpr             , 0)
+CXX11_KEYWORD(constexpr             , KEYC23)
 CXX11_KEYWORD(decltype              , 0)
 CXX11_KEYWORD(noexcept              , 0)
 CXX11_KEYWORD(nullptr               , KEYC23)
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index c5c2edf1bfe3aba..678a366ed29ad78 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2461,7 +2461,7 @@ bool VarDecl::mightBeUsableInConstantExpressions(const ASTContext &C) const {
 
   // OpenCL permits const integral variables to be used in constant
   // expressions, like in C++98.
-  if (!Lang.CPlusPlus && !Lang.OpenCL)
+  if (!Lang.CPlusPlus && !Lang.OpenCL && !Lang.C23)
     return false;
 
   // Function parameters are never usable in constant expressions.
@@ -2485,12 +2485,12 @@ bool VarDecl::mightBeUsableInConstantExpressions(const ASTContext &C) const {
 
   // In C++, const, non-volatile variables of integral or enumeration types
   // can be used in constant expressions.
-  if (getType()->isIntegralOrEnumerationType())
+  if (getType()->isIntegralOrEnumerationType() && !Lang.C23)
     return true;
 
   // Additionally, in C++11, non-volatile constexpr variables can be used in
   // constant expressions.
-  return Lang.CPlusPlus11 && isConstexpr();
+  return (Lang.CPlusPlus11 || Lang.C23) && isConstexpr();
 }
 
 bool VarDecl::isUsableInConstantExpressions(const ASTContext &Context) const {
@@ -2568,11 +2568,11 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
   bool Result = Init->EvaluateAsInitializer(Eval->Evaluated, Ctx, this, Notes,
                                             IsConstantInitialization);
 
-  // In C++, this isn't a constant initializer if we produced notes. In that
+  // In C++/C23, this isn't a constant initializer if we produced notes. In that
   // case, we can't keep the result, because it may only be correct under the
   // assumption that the initializer is a constant context.
-  if (IsConstantInitialization && Ctx.getLangOpts().CPlusPlus &&
-      !Notes.empty())
+  if (IsConstantInitialization &&
+      (Ctx.getLangOpts().CPlusPlus || Ctx.getLangOpts().C23) && !Notes.empty())
     Result = false;
 
   // Ensure the computed APValue is cleaned up later if evaluation succeeded,
@@ -2630,7 +2630,9 @@ bool VarDecl::checkForConstantInitialization(
   // std::is_constant_evaluated()).
   assert(!Eval->WasEvaluated &&
          "already evaluated var value before checking for constant init");
-  assert(getASTContext().getLangOpts().CPlusPlus && "only meaningful in C++");
+  assert((getASTContext().getLangOpts().CPlusPlus ||
+          getASTContext().getLangOpts().C23) &&
+         "only meaningful in C++/C23");
 
   assert(!getInit()->isValueDependent());
 
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 3a41e9718bb5875..fdd4bbafbb73c22 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2231,6 +2231,13 @@ static bool CheckLValueConstantExpression(EvalInfo &Info, SourceLocation Loc,
     return false;
   }
 
+  if (Info.getLangOpts().C23) {
+    auto *VarD = dyn_cast_or_null<VarDecl>(BaseVD);
+    if (VarD && VarD->isConstexpr() && !LVal.isNullPointer()) {
+      Info.report(Loc, diag::err_c23_constexpr_pointer_not_null);
+    }
+  }
+
   // Check that the object is a global. Note that the fake 'this' object we
   // manufacture when checking potential constant expressions is conservatively
   // assumed to be global here.
@@ -4110,6 +4117,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
     }
 
     bool IsConstant = BaseType.isConstant(Info.Ctx);
+    bool ConstexprVar = false;
+    if (const auto *VD = dyn_cast_or_null<VarDecl>(
+            Info.EvaluatingDecl.dyn_cast<const ValueDecl *>()))
+      ConstexprVar = VD->isConstexpr();
 
     // Unless we're looking at a local variable or argument in a constexpr call,
     // the variable we're reading must be const.
@@ -4129,6 +4140,9 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
         return CompleteObject();
       } else if (VD->isConstexpr()) {
         // OK, we can read this variable.
+      } else if (Info.getLangOpts().C23 && ConstexprVar) {
+        Info.FFDiag(E);
+        return CompleteObject();
       } else if (BaseType->isIntegralOrEnumerationType()) {
         if (!IsConstant) {
           if (!IsAccess)
@@ -15704,7 +15718,8 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
   EStatus.Diag = &Notes;
 
   EvalInfo Info(Ctx, EStatus,
-                (IsConstantInitialization && Ctx.getLangOpts().CPlusPlus)
+                (IsConstantInitialization &&
+                 (Ctx.getLangOpts().CPlusPlus || Ctx.getLangOpts().C23))
                     ? EvalInfo::EM_ConstantExpression
                     : EvalInfo::EM_ConstantFold);
   Info.setEvaluatingDecl(VD, Value);
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 8cb5b09fd3b0fa6..7059631515c873f 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4168,6 +4168,8 @@ void Parser::ParseDeclarationSpecifiers(
 
     // constexpr, consteval, constinit specifiers
     case tok::kw_constexpr:
+      if (getLangOpts().C23)
+        Diag(Tok, diag::warn_c23_compat_keyword) << Tok.getName();
       isInvalid = DS.SetConstexprSpec(ConstexprSpecKind::Constexpr, Loc,
                                       PrevSpec, DiagID);
       break;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 4e1857b931cc868..1f74d937bf97ad1 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -5152,13 +5152,17 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
     // and definitions of functions and variables.
     // C++2a [dcl.constexpr]p1: The consteval specifier shall be applied only to
     // the declaration of a function or function template
-    if (Tag)
+    if (Tag) {
       Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_tag)
           << GetDiagnosticTypeSpecifierID(DS)
           << static_cast<int>(DS.getConstexprSpecifier());
-    else
-      Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_wrong_decl_kind)
-          << static_cast<int>(DS.getConstexprSpecifier());
+    } else {
+      if (getLangOpts().C23)
+        Diag(DS.getConstexprSpecLoc(), diag::err_c23_constexpr_not_variable);
+      else
+        Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_wrong_decl_kind)
+            << static_cast<int>(DS.getConstexprSpecifier());
+    }
     // Don't emit warnings after this error.
     return TagD;
   }
@@ -7894,6 +7898,17 @@ NamedDecl *Sema::ActOnVariableDeclarator(
         (getLangOpts().CPlusPlus17 ||
          Context.getTargetInfo().getCXXABI().isMicrosoft()))
       NewVD->setImplicitlyInline();
+
+    if (getLangOpts().C23) {
+      DeclSpec::TSCS TSC = D.getDeclSpec().getThreadStorageClassSpec();
+      if (TSC != TSCS_unspecified) {
+        Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
+             diag::err_c23_thread_local_constexpr);
+      }
+      if (NewVD->hasExternalStorage())
+        Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
+             diag::err_c23_extern_constexpr);
+    }
     break;
 
   case ConstexprSpecKind::Constinit:
@@ -8605,6 +8620,27 @@ static bool checkForConflictWithNonVisibleExternC(Sema &S, const T *ND,
   return false;
 }
 
+static bool CheckC23ConstexprVarTypeQualifiers(Sema &SemaRef,
+                                            SourceLocation VarLoc, QualType T) {
+  if (const auto *A = SemaRef.Context.getAsArrayType(T)) {
+    T = A->getElementType();
+  }
+
+  if (T->isAtomicType() || T.isVolatileQualified() || T.isRestrictQualified()) {
+    SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T;
+    return true;
+  }
+
+  if (T->isRecordType()) {
+    RecordDecl *RD = T->getAsRecordDecl();
+    for (const auto &F : RD->fields())
+      if (CheckC23ConstexprVarTypeQualifiers(SemaRef, VarLoc, F->getType()))
+        return true;
+  }
+
+  return false;
+}
+
 void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
   // If the decl is already known invalid, don't check it.
   if (NewVD->isInvalidDecl())
@@ -8857,7 +8893,9 @@ void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
 
   if (NewVD->isConstexpr() && !T->isDependentType() &&
       RequireLiteralType(NewVD->getLocation(), T,
-                         diag::err_constexpr_var_non_literal)) {
+                         getLangOpts().C23
+                             ? diag::err_c23_constexpr_invalid_type
+                             : diag::err_constexpr_var_non_literal)) {
     NewVD->setInvalidDecl();
     return;
   }
@@ -8870,6 +8908,12 @@ void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
     return;
   }
 
+  if (getLangOpts().C23 && NewVD->isConstexpr() &&
+      CheckC23ConstexprVarTypeQualifiers(*this, NewVD->getLocation(), T)) {
+    NewVD->setInvalidDecl();
+    return;
+  }
+
   // Check that SVE types are only used in functions with SVE available.
   if (T->isSVESizelessBuiltinType() && isa<FunctionDecl>(CurContext)) {
     const FunctionDecl *FD = cast<FunctionDecl>(CurContext);
@@ -9237,6 +9281,22 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D,
   FunctionDecl *NewFD = nullptr;
   bool isInline = D.getDeclSpec().isInlineSpecified();
 
+  ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier();
+  if (ConstexprKind == ConstexprSpecKind::Constinit ||
+      (SemaRef.getLangOpts().C23 &&
+       ConstexprKind == ConstexprSpecKind::Constexpr)) {
+
+    if (SemaRef.getLangOpts().C23)
+      SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(),
+                   diag::err_c23_constexpr_not_variable);
+    else
+      SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(),
+                   diag::err_constexpr_wrong_decl_kind)
+          << static_cast<int>(ConstexprKind);
+    ConstexprKind = ConstexprSpecKind::Unspecified;
+    D.getMutableDeclSpec().ClearConstexprSpec();
+  }
+
   if (!SemaRef.getLangOpts().CPlusPlus) {
     // Determine whether the function was written with a prototype. This is
     // true when:
@@ -9270,15 +9330,6 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D,
   }
 
   ExplicitSpecifier ExplicitSpecifier = D.getDeclSpec().getExplicitSpecifier();
-
-  ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier();
-  if (ConstexprKind == ConstexprSpecKind::Constinit) {
-    SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(),
-                 diag::err_constexpr_wrong_decl_kind)
-        << static_cast<int>(ConstexprKind);
-    ConstexprKind = ConstexprSpecKind::Unspecified;
-    D.getMutableDeclSpec().ClearConstexprSpec();
-  }
   Expr *TrailingRequiresClause = D.getTrailingRequiresClause();
 
   SemaRef.CheckExplicitObjectMemberFunction(DC, D, Name, R);
@@ -13786,7 +13837,9 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
       VDecl->setStorageClass(SC_Extern);
 
     // C99 6.7.8p4. All file scoped initializers need to be constant.
-    if (!getLangOpts().CPlusPlus && !VDecl->isInvalidDecl())
+    // Avoid double diagnosing for constexpr variables.
+    if (!getLangOpts().CPlusPlus && !VDecl->isInvalidDecl() &&
+        !VDecl->isConstexpr())
       CheckForConstantInitializer(Init, DclT);
   }
 
@@ -14240,6 +14293,115 @@ StmtResult Sema::ActOnCXXForRangeIdentifier(Scope *S, SourceLocation IdentLoc,
                                                       : IdentLoc);
 }
 
+static ImplicitConversionKind getConversionKind(QualType FromType,
+                                                QualType ToType) {
+  if (ToType->isIntegerType()) {
+    if (FromType->isComplexType())
+     return ICK_Complex_Real;
+    if (FromType->isFloatingType())
+     return ICK_Floating_Integral;
+    if (FromType->isIntegerType())
+     return ICK_Integral_Conversion;
+  }
+
+  if (ToType->isFloatingType()) {
+    if (FromType->isComplexType())
+     return ICK_Complex_Real;
+    if (FromType->isFloatingType())
+     return ICK_Floating_Conversion;
+    if (FromType->isIntegerType())
+     return ICK_Floating_Integral;
+  }
+
+  return ICK_Identity;
+}
+
+static bool checkC23ConstexprInitConversion(Sema &S, const Expr *Init) {
+  assert(S.getLangOpts().C23);
+  const Expr *InitNoCast = Init->IgnoreImpCasts();
+  StandardConversionSequence SCS;
+  SCS.setAsIdentityConversion();
+  auto FromType = InitNoCast->getType();
+  auto ToType = Init->getType();
+  SCS.setToType(0, FromType);
+  SCS.setToType(1, ToType);
+  SCS.Second = getConversionKind(FromType, ToType);
+
+  APValue Value;
+  QualType PreNarrowingType;
+  // Reuse C++ narrowing check.
+  switch (SCS.getNarrowingKind(S.Context, Init, Value, PreNarrowingType,
+                               /*IgnoreFloatToIntegralConversion*/ false)) {
+  // The value doesn't fit.
+  case NK_Constant_Narrowing:
+    S.Diag(Init->getBeginLoc(), diag::err_c23_constexpr_init_not_representable)
+        << Value.getAsString(S.Context, PreNarrowingType) << ToType;
+    return true;
+
+  // Conversion to a narrower type.
+  case NK_Type_Narrowing:
+    S.Diag(Init->getBeginLoc(), diag::err_c23_constexpr_init_type_mismatch)
+        << ToType << FromType;
+    return true;
+
+  // Since we only reuse narrowing check for C23 constexpr variables here, we're
+  // not really interested in these cases.
+  case NK_Dependent_Narrowing:
+  case NK_Variable_Narrowing:
+  case NK_Not_Narrowing:
+    return false;
+
+  }
+  llvm_unreachable("unhandled case in switch");
+}
+
+static bool checkC23ConstexprInitStringLiteral(const StringLiteral *SE,
+                                               Sema &SemaRef,
+                                               SourceLocation Loc) {
+  assert(SemaRef.getLangOpts().C23);
+  // String literals have the target type attached but underneath may contain
+  // values that don't really fit into the target type. Check that every
+  // character fits.
+  const ConstantArrayType *CAT =
+      SemaRef.Context.getAsConstantArrayType(SE->getType());
+  QualType CharType = CAT->getElementType();
+  uint32_t BitWidth = SemaRef.Context.getTypeSize(CharType);
+  bool isUnsigned = CharType->isUnsignedIntegerType();
+  llvm::APSInt Value(BitWidth, isUnsigned);
+  const StringRef S = SE->getBytes();
+  for (unsigned I = 0, N = SE->getLength(); I != N; ++I) {
+    Value = S[I];
+    if (Value != S[I]) {
+      SemaRef.Diag(Loc, diag::err_c23_constexpr_init_not_representable)
+          << S[I] << CharType;
+      return true;
+    }
+  }
+  return false;
+}
+
+static bool checkC23ConstexprInitializer(Sema &S, const Expr *Init) {
+  const Expr *InitNoCast = Init->IgnoreImpCasts();
+  if (Init->getType() != InitNoCast->getType())
+    if (checkC23ConstexprInitConversion(S, Init))
+      return true;
+
+  if (auto *SE = dyn_cast<StringLiteral>(Init))
+    if (checkC23ConstexprInitStringLiteral(SE, S, Init->getBeginLoc()))
+      return true;
+
+  for (const Stmt *SubStmt : Init->children()) {
+    const Expr *ChildExpr = dyn_cast_or_null<const Expr>(SubStmt);
+    if (!ChildExpr)
+      continue;
+
+    if (checkC23ConstexprInitializer(S, ChildExpr)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
   if (var->isInvalidDecl()) return;
 
@@ -14397,9 +14559,13 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
   QualType baseType = Context.getBaseElementType(type);
   bool HasConstInit = true;
 
+  if (getLangOpts().C23 && var->isConstexpr() && !Init)
+    Diag(var->getLocation(), diag::err_constexpr_var_requires_const_init)
+        << var;
+
   // Check whether the initializer is sufficiently constant.
-  if (getLangOpts().CPlusPlus && !type->isDependentType() && Init &&
-      !Init->isValueDependent() &&
+  if ((getLangOpts().CPlusPlus || getLangOpts().C23) &&
+      !type->isDependentType() && Init && !Init->isValueDependent() &&
       (GlobalStorage || var->isConstexpr() ||
        var->mightBeUsableInConstantExpressions(Context))) {
     // If this variable might have a constant initializer or might be usable in
@@ -14407,7 +14573,7 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
     // do this lazily, because the result might depend on things that change
     // later, such as which constexpr functions happen to be defined.
     SmallVector<PartialDiagnosticAt, 8> Notes;
-    if (!getLangOpts().CPlusPlus11) {
+    if (!getLangOpts().CPlusPlus11 && !getLangOpts().C23) {
       // Prior to C++11, in contexts where a constant initializer is required,
       // the set of valid constant initializers is described by syntactic rules
       // in [expr.const]p2-6.
@@ -14432,6 +14598,8 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
 
     if (HasConstInit) {
       // FIXME: Consider replacing the initializer with a ConstantExpr.
+      if (getLangOpts().C23 && var->isConstexpr())
+        checkC23ConstexprInitializer(*this, Init);
     } else if (var->isConstexpr()) {
       SourceLocation DiagLoc = var->getLocation();
       // If the note doesn't add any useful information other than a source
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 64607e28b8b35e6..dfb1dfc00c1bc49 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -328,7 +328,8 @@ static const Expr *IgnoreNarrowingConversion(ASTContext &Ctx,
 NarrowingKind StandardConversionSequence::getNarrowingKind(
     ASTContext &Ctx, const Expr *Converted, APValue &ConstantValue,
     QualType &ConstantType, bool IgnoreFloatToIntegralConversion) const {
-  assert(Ctx.getLangOpts().CPlusPlus && "narrowing check outside C++");
+  assert((Ctx.getLangOpts().CPlusPlus || Ctx.getLangOpts().C23) &&
+         "narrowing check outside C++");
 
   // C++11 [dcl.init.list]p7:
   //   A narrowing conversion is an implicit conversion ...
@@ -410,20 +411,35 @@ NarrowingKind StandardConversionSequence::getNarrowingKind(
       if (Initializer->isValueDependent())
         return NK_Dependent_Narrowing;
 
-      if (Initializer->isCXX11ConstantExpr(Ctx, &ConstantValue)) {
+      Expr::EvalResult R;
+      if ((Ctx.getLangOpts().C23 &&
+           Initializer->EvaluateAsRValue(R, Ctx)) ||
+          Initializer->isCXX11ConstantExpr(Ctx, &ConstantValue)) {
         // Constant!
+        if (Ctx.getLangOpts().C23)
+          ConstantValue = R.Val;
         assert(ConstantValue.isFloat());
         llvm::APFloat FloatVal = ConstantValue.getFloat();
         // Convert the source value into the target type.
         bool ignored;
-        llvm::APFloat::opStatus ConvertStatus = FloatVal.convert(
+        llvm::APFloat Converted = FloatVal;
+        llvm::APFloat::opStatus ConvertStatus = Converted.convert(
           Ctx.getFloatTypeSemantics(ToType),
           llvm::APFloat::rmNearestTiesToEven, &ignored);
-        // If there was no overflow, the source value is within the range of
-        // values that can be represented.
-        if (ConvertStatus & llvm::APFloat::opOverflow) {
-          ConstantType = Initializer->getType();
-          return NK_Constant_Narrowing;
+        Converted.convert(Ctx.getFloatTypeSemantics(FromType),
+                          llvm::APFloat::rmNearestTiesToEven, &ignored);
+        if (Ctx.getLangOpts().C23) {
+          if (!Converted.bitwiseIsEqual(FloatVal)) {
+            ConstantType = Initializer->getType();
+            return NK_Constant_Narrowing;
+          }
+        } else {
+          // If there was no overflow, the source value is within the range of
+          // values that can be represented.
+          if (ConvertStatus & llvm::APFloat::opOverflow) {
+            ConstantType = Initializer->getType();
+            return NK_Constant_Narrowing;
+          }
         }
       } else {
         return NK_Variable_Narrowing;
@@ -490,6 +506,10 @@ NarrowingKind StandardConversionSequence::getNarrowingKind(
     }
     return NK_Not_Narrowing;
   }
+  case ICK_Complex_Real:
+    if (FromType->isComplexType() && !ToType->isComplexType())
+      return NK_Type_Narrowing;
+    return NK_Not_Narrowing;
 
   default:
     // Other kinds of conversions are not narrowings.
diff --git a/clang/test/C/C2x/n3018.c b/clang/test/C/C2x/n3018.c
new file mode 100644
index 000000000000000..ccc0b708a67e062
--- /dev/null
+++ b/clang/test/C/C2x/n3018.c
@@ -0,0 +1,86 @@
+// RUN: %clang_cc1 -std=c2x -verify -triple x86_64 -pedantic -Wno-conversion -Wno-constant-conversion %s
+
+/* WG14 N3018: Full
+ * The constexpr specifier for object definitions
+ */
+
+#define ULLONG_MAX (__LONG_LONG_MAX__*2ULL+1ULL)
+#define UINT_MAX  (__INT_MAX__  *2U +1U)
+
+void Example0() {
+  constexpr unsigned int minusOne    = -1;
+  // expected-error at -1 {{constexpr initializer evaluates to -1 which is not exactly representable in type 'unsigned int'}}
+  constexpr unsigned int uint_max    = -1U;
+  constexpr double onethird          = 1.0/3.0;
+  constexpr double onethirdtrunc     = (double)(1.0/3.0);
+
+  constexpr char string[] = { "\xFF", };
+  constexpr unsigned char ucstring[] = { "\xFF", };
+  // expected-error at -1 {{constexpr initializer evaluates to -1 which is not exactly representable in type 'const unsigned char'}}
+  constexpr char string1[] = { -1, 0, };
+  constexpr unsigned char ucstring1[] = { -1, 0, };
+  // expected-error at -1 {{constexpr initializer evaluates to -1 which is not exactly representable in type 'unsigned char'}}
+
+  // TODO: Make sure these work correctly once char8_t and _Decimal are supported
+  // constexpr char8_t u8string[] = { 255, 0, }; // ok
+  // constexpr char8_t u8string[]       = { u8"\xFF", };     // ok
+  // constexpr _Decimal32 small         = DEC64_TRUE_MIN * 0;// constraint violation
+}
+
+void Example1() {
+  constexpr int K = 47;
+  enum {
+      A = K,
+  };
+  constexpr int L = K;
+  static int b    = K + 1;
+  int array[K];
+  _Static_assert(K == 47);
+}
+
+constexpr int K = 47;
+static const int b = K + 1;
+
+void Example2() {
+  constexpr int A          = 42LL;
+  constexpr signed short B = ULLONG_MAX;
+  // expected-error at -1 {{constexpr initializer evaluates to 18446744073709551615 which is not exactly representable in type 'short'}}
+  constexpr float C        = 47u;
+
+  constexpr float D = 432000000;
+  constexpr float E = 1.0 / 3.0;
+  // expected-error at -1 {{constexpr initializer evaluates to 3.333333e-01 which is not exactly representable in type 'float'}}
+  constexpr float F = 1.0f / 3.0f;
+}
+
+
+void Example3() {
+  constexpr static unsigned short array[] = {
+      3000,
+      300000,
+      // expected-error at -1 {{constexpr initializer evaluates to 300000 which is not exactly representable in type 'unsigned short'}}
+      -1       // constraint violation, target type is unsigned
+  };
+
+  constexpr static unsigned short array1[] = {
+      3000,
+      3000,
+      -1
+       // expected-error at -1 {{constexpr initializer evaluates to -1 which is not exactly representable in type 'unsigned short'}}
+  };
+
+  struct S {
+      int x, y;
+  };
+  constexpr struct S s = {
+      .x = __INT_MAX__,
+      .y = UINT_MAX,
+      // expected-error at -1 {{constexpr initializer evaluates to 4294967295 which is not exactly representable in type 'int'}}
+  };
+}
+
+void Example4() {
+  struct s { void *p; };
+  constexpr struct s A = { nullptr };
+  constexpr struct s B = A;
+}
diff --git a/clang/test/Parser/c23-constexpr.c b/clang/test/Parser/c23-constexpr.c
new file mode 100644
index 000000000000000..5eddf66a66e74bb
--- /dev/null
+++ b/clang/test/Parser/c23-constexpr.c
@@ -0,0 +1,6 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=c23 -std=c23 %s -Wpre-c2x-compat
+// RUN: %clang_cc1 -fsyntax-only -verify=c17 -std=c17 %s
+
+
+constexpr int a = 0; // c17-error {{unknown type name 'constexpr'}} \
+                        c23-warning {{'constexpr' is incompatible with C standards before C23}}
diff --git a/clang/test/Sema/constexpr.c b/clang/test/Sema/constexpr.c
new file mode 100644
index 000000000000000..650716640e2f470
--- /dev/null
+++ b/clang/test/Sema/constexpr.c
@@ -0,0 +1,275 @@
+// RUN: %clang_cc1 -std=c2x -verify -triple x86_64 -pedantic -Wno-conversion -Wno-constant-conversion -Wno-div-by-zero %s
+
+// Check that constexpr only applies to variables.
+constexpr void f0() {} // expected-error {{constexpr is only allowed in variable declarations}}
+constexpr const int f1() { return 0; } // expected-error {{constexpr is only allowed in variable declarations}}
+
+constexpr struct S1 { int f; }; //expected-error {{struct cannot be marked constexpr}}
+constexpr struct S2 ; // expected-error {{struct cannot be marked constexpr}}
+constexpr union U1; // expected-error {{union cannot be marked constexpr}}
+constexpr union U2 {int a; float b;}; // expected-error {{union cannot be marked constexpr}}
+constexpr enum E1 {A = 1, B = 2} ; // expected-error {{enum cannot be marked constexpr}}
+struct S3 {
+  static constexpr int f = 0; // expected-error {{type name does not allow storage class}}
+  // expected-error at -1 {{type name does not allow constexpr}}
+  // expected-error at -2 {{expected ';' at end}}
+  constexpr int f1 = 0;
+  // expected-error at -1 {{type name does not allow constexpr}}
+  // expected-error at -2 {{expected ';' at end}}
+};
+
+constexpr; // expected-error {{constexpr is only allowed in variable declarations}}
+constexpr int V1 = 3;
+constexpr float V2 = 7.0;
+int V3 = (constexpr)3; // expected-error {{expected expression}}
+
+void f2() {
+  constexpr int a = 0;
+  constexpr float b = 1.7f;
+}
+
+// Check how constexpr works with other storage-class specifiers.
+constexpr auto V4 = 1;
+constexpr static auto V5 = 1;
+constexpr static const auto V6 = 1;
+constexpr static const int V7 = 1;
+constexpr static int V8 = 1;
+
+void f3(constexpr register int P1) { // expected-error {{function parameter cannot be constexpr}}
+  constexpr register int V9 = 0;
+  constexpr register auto V10 = 0.0;
+}
+
+constexpr thread_local int V11 = 38; // expected-error {{thread-local storage is not allowed with constexpr}}
+constexpr static thread_local double V12 = 38; // expected-error {{thread-local storage is not allowed with constexpr}}
+constexpr extern thread_local char V13; // expected-error {{extern specifier is not allowed with constexpr}}
+// expected-error at -1 {{thread-local storage is not allowed with constexpr}}
+// expected-error at -2 {{constexpr variable declaration must be a definition}}
+constexpr thread_local short V14 = 38; // expected-error {{thread-local storage is not allowed with constexpr}}
+
+// Check how constexpr works with qualifiers.
+constexpr _Atomic int V15 = 0; // expected-error {{constexpr variable cannot have type 'const _Atomic(int)'}}
+constexpr _Atomic(int) V16 = 0; // expected-error {{constexpr variable cannot have type 'const _Atomic(int)'}}
+
+constexpr volatile int V17 = 0; // expected-error {{constexpr variable cannot have type 'const volatile int'}}
+
+constexpr int * restrict V18 = 0; // expected-error {{constexpr variable cannot have type 'int *const restrict'}}
+
+typedef _Atomic(int) TheA;
+typedef volatile short TheV;
+typedef float * restrict TheR;
+
+constexpr TheA V19[3] = {};
+// expected-error at -1 {{constexpr variable cannot have type 'const TheA' (aka 'const _Atomic(int)')}}
+constexpr TheV V20[3] = {};
+// expected-error at -1 {{constexpr variable cannot have type 'const TheV' (aka 'const volatile short')}}
+constexpr TheR V21[3] = {};
+// expected-error at -1 {{constexpr variable cannot have type 'const TheR' (aka 'float *const restrict')}}
+
+struct HasA {
+  TheA f;
+  int b;
+};
+
+struct HasV {
+  float b;
+  TheV f;
+};
+
+struct HasR {
+  short b;
+  int a;
+  TheR f;
+};
+
+constexpr struct HasA V22[2] = {};
+// expected-error at -1 {{constexpr variable cannot have type 'TheA' (aka '_Atomic(int)')}}
+constexpr struct HasV V23[2] = {};
+// expected-error at -1 {{constexpr variable cannot have type 'TheV' (aka 'volatile short')}}
+constexpr struct HasR V24[2] = {};
+// expected-error at -1 {{constexpr variable cannot have type 'TheR' (aka 'float *restrict')}}
+
+union U3 {
+  float a;
+  union {
+    struct HasA f;
+    struct HasR f1;
+  };
+};
+
+constexpr union U3 V25 = {};
+// expected-error at -1 {{constexpr variable cannot have type 'TheA' (aka '_Atomic(int)')}}
+constexpr union U3 V26[8] = {};
+// expected-error at -1 {{constexpr variable cannot have type 'TheA' (aka '_Atomic(int)')}}
+
+struct S4 {
+  union U3 f[3];
+};
+
+constexpr struct S4 V27 = {};
+// expected-error at -1 {{constexpr variable cannot have type 'TheA' (aka '_Atomic(int)')}}
+constexpr const int V28 = 28;
+
+// Check that constexpr variable must have a valid initializer which is a
+// constant expression.
+constexpr int V29;
+// expected-error at -1 {{constexpr variable 'V29' must be initialized by a constant expression}}
+
+struct S5 {
+  int f;
+};
+
+constexpr struct S5 V30;
+// expected-error at -1 {{constexpr variable 'V30' must be initialized by a constant expression}}
+constexpr struct S5 V31 = {};
+
+int randomFoo() { return 7; }
+
+constexpr float V32 = randomFoo();
+// expected-error at -1 {{constexpr variable 'V32' must be initialized by a constant expression}}
+
+const int V33 = 4;
+const int V34 = 0;
+const int V35 = 2;
+
+constexpr int V36 = V33 / V34;
+// expected-error at -1 {{constexpr variable 'V36' must be initialized by a constant expression}}
+constexpr int V37 = V33 / V35;
+// expected-error at -1 {{constexpr variable 'V37' must be initialized by a constant expression}}
+constexpr int V38 = 3;
+constexpr int V39 = V38 / V38;
+constexpr int V40 = V38 / 2;
+constexpr int V41 = V38 / 0;
+// expected-error at -1 {{constexpr variable 'V41' must be initialized by a constant expression}}
+// expected-note at -2 {{division by zero}}
+constexpr int V42 = V38 & 0;
+
+constexpr struct S5 V43 = { randomFoo() };
+// expected-error at -1 {{constexpr variable 'V43' must be initialized by a constant expression}}
+constexpr struct S5 V44 = { 0 };
+constexpr struct S5 V45 = { V38 / 0 };
+// expected-error at -1 {{constexpr variable 'V45' must be initialized by a constant expression}}
+// expected-note at -2 {{division by zero}}
+
+constexpr float V46[3] = {randomFoo() };
+// expected-error at -1 {{constexpr variable 'V46' must be initialized by a constant expression}}
+constexpr struct S5 V47[3] = {randomFoo() };
+// expected-error at -1 {{constexpr variable 'V47' must be initialized by a constant expression}}
+
+const static int V48 = V38;
+constexpr static int V49 = V48;
+// expected-error at -1 {{constexpr variable 'V49' must be initialized by a constant expression}}
+
+void f4(const int P1) {
+  constexpr int V = P1;
+// expected-error at -1 {{constexpr variable 'V' must be initialized by a constant expression}}
+
+  constexpr int V1 = 12;
+  constexpr const int *V2 = &V1;
+// expected-error at -1 {{constexpr variable 'V2' must be initialized by a constant expression}}
+// expected-error at -2 {{constexpr pointer initializer is not null}}
+}
+
+// Check that initializer for constexpr variable should match the type of the
+// variable and is exactly representable int the variable's type.
+
+struct S6 {
+  unsigned char a;
+};
+
+struct S7 {
+  union {
+    float a;
+  };
+  unsigned int b;
+};
+
+void f5() {
+  constexpr char V50 = 300;
+  // expected-error at -1 {{constexpr initializer evaluates to 300 which is not exactly representable in type 'char'}}
+  constexpr float V51 = 1.0 / 3.0;
+  // expected-error at -1 {{constexpr initializer evaluates to 3.333333e-01 which is not exactly representable in type 'float'}}
+  constexpr float V52 = 0.7;
+  // expected-error at -1 {{constexpr initializer evaluates to 7.000000e-01 which is not exactly representable in type 'float'}}
+  constexpr float V53 = 1.0f / 3.0f;
+  constexpr float V54 = 432000000000;
+  // expected-error at -1 {{constexpr initializer evaluates to 432000000000 which is not exactly representable in type 'float'}}
+  constexpr unsigned char V55[] = {
+      "\xAF",
+  // expected-error at -1 {{constexpr initializer evaluates to -81 which is not exactly representable in type 'const unsigned char'}}
+  };
+
+  // FIXME Shouldn't be diagnosed if char_8t is supported.
+  constexpr unsigned char V56[] = {
+      u8"\xAF",
+  // expected-error at -1 {{constexpr initializer evaluates to -81 which is not exactly representable in type 'const unsigned char'}}
+  };
+  constexpr struct S6 V57 = {299};
+  // expected-error at -1 {{constexpr initializer evaluates to 299 which is not exactly representable in type 'unsigned char'}}
+  constexpr struct S6 V58 = {-299};
+  // expected-error at -1 {{constexpr initializer evaluates to -299 which is not exactly representable in type 'unsigned char'}}
+  constexpr double V59 = 0.5;
+  constexpr double V60 = 1.0;
+  constexpr float V61 = V59 / V60;
+  constexpr double V62 = 1.7;
+  constexpr float V63 = V59 / V62;
+  // expected-error at -1 {{constexpr initializer evaluates to 2.941176e-01 which is not exactly representable in type 'float'}}
+
+  constexpr unsigned char V64 = '\xAF';
+  // expected-error at -1 {{constexpr initializer evaluates to -81 which is not exactly representable in type 'unsigned char'}}
+  constexpr unsigned char V65 = u8'\xAF';
+
+  constexpr char V66[3] = {300};
+  // expected-error at -1 {{constexpr initializer evaluates to 300 which is not exactly representable in type 'char'}}
+  constexpr struct S6 V67[3] = {300};
+  // expected-error at -1 {{constexpr initializer evaluates to 300 which is not exactly representable in type 'unsigned char'}}
+
+  constexpr struct S7 V68 = {0.3, -1 };
+  // expected-error at -1 {{constexpr initializer evaluates to 3.000000e-01 which is not exactly representable in type 'float'}}
+  constexpr struct S7 V69 = {0.5, -1 };
+  // expected-error at -1 {{constexpr initializer evaluates to -1 which is not exactly representable in type 'unsigned int'}}
+  constexpr struct S7 V70[3] = {{123456789}};
+  // expected-error at -1 {{constexpr initializer evaluates to 123456789 which is not exactly representable in type 'float'}}
+
+  constexpr int V71 = 0.3;
+  // expected-error at -1 {{constexpr initializer for type 'int' is of type 'double'}}
+  constexpr int V72 = V59;
+  // expected-error at -1 {{constexpr initializer for type 'int' is of type 'const double'}}
+  constexpr struct S6 V73 = {V59};
+  // expected-error at -1 {{constexpr initializer for type 'unsigned char' is of type 'const double'}}
+
+  constexpr float V74 = 1;
+  constexpr float V75 = V59;
+  constexpr unsigned int V76[3] = {0.5};
+  // expected-error at -1 {{constexpr initializer for type 'unsigned int' is of type 'double'}}
+
+  constexpr _Complex float V77 = 0;
+  constexpr float V78 = V77;
+  // expected-error at -1 {{constexpr initializer for type 'float' is of type 'const _Complex float'}}
+  constexpr int V79 = V77;
+  // expected-error at -1 {{constexpr initializer for type 'int' is of type 'const _Complex float'}}
+
+}
+
+// Check that initializer for pointer constexpr variable should be null.
+constexpr int V80 = 3;
+constexpr const int *V81 = &V80;
+// expected-error at -1 {{constexpr pointer initializer is not null}}
+constexpr int *V82 = 0;
+constexpr int *V83 = V82;
+constexpr int *V84 = 42;
+// expected-error at -1 {{constexpr variable 'V84' must be initialized by a constant expression}}
+// expected-note at -2 {{this conversion is not allowed in a constant expression}}
+constexpr int *V85 = nullptr;
+
+// Check that constexpr variables should not be VLAs.
+void f6(const int P1) {
+  constexpr int V86[P1] = {};
+// expected-error at -1 {{constexpr variable cannot have type 'const int[P1]'}}
+  const int V87 = 3;
+  constexpr int V88[V87] = {};
+// expected-warning at -1 {{variable length array folded to constant array as an extension}}
+  int V89 = 7;
+  constexpr int V90[V89] = {};
+// expected-error at -1 {{constexpr variable cannot have type 'const int[V89]'}}
+}
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index 91cae138074b335..4644d18b496173a 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -1201,7 +1201,7 @@ <h2 id="c2x">C23 implementation status</h2>
     <tr>
       <td>constexpr for object definitions</td>
       <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3018.htm">N3018</a></td>
-      <td class="none" align="center">No</td>
+      <td class="unreleased" align="center">Clang 18</td>
     </tr>
     <tr>
       <td>Introduce storage class specifiers for compound literals</td>

>From bbb801dde58ecd336369985d146b2592d5748593 Mon Sep 17 00:00:00 2001
From: "Podchishchaeva, Mariya" <mariya.podchishchaeva at intel.com>
Date: Thu, 23 Nov 2023 06:44:12 -0800
Subject: [PATCH 2/6] Fix format, apply comments

---
 clang/lib/AST/ExprConstant.cpp  |  7 +++----
 clang/lib/Sema/SemaDecl.cpp     | 18 +++++++++---------
 clang/lib/Sema/SemaOverload.cpp |  9 ++++-----
 3 files changed, 16 insertions(+), 18 deletions(-)

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index fdd4bbafbb73c22..97827e31d8f4abe 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2232,10 +2232,9 @@ static bool CheckLValueConstantExpression(EvalInfo &Info, SourceLocation Loc,
   }
 
   if (Info.getLangOpts().C23) {
-    auto *VarD = dyn_cast_or_null<VarDecl>(BaseVD);
-    if (VarD && VarD->isConstexpr() && !LVal.isNullPointer()) {
+    if (const auto *VarD = dyn_cast_if_present<VarDecl>(BaseVD);
+        VarD && VarD->isConstexpr() && !LVal.isNullPointer())
       Info.report(Loc, diag::err_c23_constexpr_pointer_not_null);
-    }
   }
 
   // Check that the object is a global. Note that the fake 'this' object we
@@ -4118,7 +4117,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
 
     bool IsConstant = BaseType.isConstant(Info.Ctx);
     bool ConstexprVar = false;
-    if (const auto *VD = dyn_cast_or_null<VarDecl>(
+    if (const auto *VD = dyn_cast_if_present<VarDecl>(
             Info.EvaluatingDecl.dyn_cast<const ValueDecl *>()))
       ConstexprVar = VD->isConstexpr();
 
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1f74d937bf97ad1..1ffc9971ae6984f 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -8621,7 +8621,8 @@ static bool checkForConflictWithNonVisibleExternC(Sema &S, const T *ND,
 }
 
 static bool CheckC23ConstexprVarTypeQualifiers(Sema &SemaRef,
-                                            SourceLocation VarLoc, QualType T) {
+                                               SourceLocation VarLoc,
+                                               QualType T) {
   if (const auto *A = SemaRef.Context.getAsArrayType(T)) {
     T = A->getElementType();
   }
@@ -8632,7 +8633,7 @@ static bool CheckC23ConstexprVarTypeQualifiers(Sema &SemaRef,
   }
 
   if (T->isRecordType()) {
-    RecordDecl *RD = T->getAsRecordDecl();
+    const RecordDecl *RD = T->getAsRecordDecl();
     for (const auto &F : RD->fields())
       if (CheckC23ConstexprVarTypeQualifiers(SemaRef, VarLoc, F->getType()))
         return true;
@@ -14297,20 +14298,20 @@ static ImplicitConversionKind getConversionKind(QualType FromType,
                                                 QualType ToType) {
   if (ToType->isIntegerType()) {
     if (FromType->isComplexType())
-     return ICK_Complex_Real;
+      return ICK_Complex_Real;
     if (FromType->isFloatingType())
-     return ICK_Floating_Integral;
+      return ICK_Floating_Integral;
     if (FromType->isIntegerType())
-     return ICK_Integral_Conversion;
+      return ICK_Integral_Conversion;
   }
 
   if (ToType->isFloatingType()) {
     if (FromType->isComplexType())
-     return ICK_Complex_Real;
+      return ICK_Complex_Real;
     if (FromType->isFloatingType())
-     return ICK_Floating_Conversion;
+      return ICK_Floating_Conversion;
     if (FromType->isIntegerType())
-     return ICK_Floating_Integral;
+      return ICK_Floating_Integral;
   }
 
   return ICK_Identity;
@@ -14350,7 +14351,6 @@ static bool checkC23ConstexprInitConversion(Sema &S, const Expr *Init) {
   case NK_Variable_Narrowing:
   case NK_Not_Narrowing:
     return false;
-
   }
   llvm_unreachable("unhandled case in switch");
 }
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index dfb1dfc00c1bc49..0d3a9701b9501bf 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -412,8 +412,7 @@ NarrowingKind StandardConversionSequence::getNarrowingKind(
         return NK_Dependent_Narrowing;
 
       Expr::EvalResult R;
-      if ((Ctx.getLangOpts().C23 &&
-           Initializer->EvaluateAsRValue(R, Ctx)) ||
+      if ((Ctx.getLangOpts().C23 && Initializer->EvaluateAsRValue(R, Ctx)) ||
           Initializer->isCXX11ConstantExpr(Ctx, &ConstantValue)) {
         // Constant!
         if (Ctx.getLangOpts().C23)
@@ -423,9 +422,9 @@ NarrowingKind StandardConversionSequence::getNarrowingKind(
         // Convert the source value into the target type.
         bool ignored;
         llvm::APFloat Converted = FloatVal;
-        llvm::APFloat::opStatus ConvertStatus = Converted.convert(
-          Ctx.getFloatTypeSemantics(ToType),
-          llvm::APFloat::rmNearestTiesToEven, &ignored);
+        llvm::APFloat::opStatus ConvertStatus =
+            Converted.convert(Ctx.getFloatTypeSemantics(ToType),
+                              llvm::APFloat::rmNearestTiesToEven, &ignored);
         Converted.convert(Ctx.getFloatTypeSemantics(FromType),
                           llvm::APFloat::rmNearestTiesToEven, &ignored);
         if (Ctx.getLangOpts().C23) {

>From 9b4a5ea626138cc110c09ff41fb0793d9fb2a02b Mon Sep 17 00:00:00 2001
From: "Podchishchaeva, Mariya" <mariya.podchishchaeva at intel.com>
Date: Wed, 29 Nov 2023 09:18:51 -0800
Subject: [PATCH 3/6] Apply some of the feedback:

- Minor changes
- Don't reuse requireLiteralType
- Diagnose interaction with extern and thread_local from
  DeclSpec::Finish()
- Add a couple of test cases
---
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +-
 clang/lib/AST/ExprConstant.cpp                |  4 +
 clang/lib/Sema/DeclSpec.cpp                   | 14 +++
 clang/lib/Sema/SemaDecl.cpp                   | 93 +++++++++----------
 clang/test/Sema/constexpr.c                   | 26 ++++--
 5 files changed, 80 insertions(+), 59 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 1841f0f44fb0abf..016a099f012938b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2938,7 +2938,7 @@ def err_c23_thread_local_constexpr : Error<
 def err_c23_extern_constexpr : Error<
   "extern specifier is not allowed with constexpr">;
 def err_c23_constexpr_not_variable : Error<
-  "constexpr is only allowed in variable declarations">;
+  "'constexpr' can only be used in variable declarations">;
 def err_c23_constexpr_invalid_type : Error<
   "constexpr variable cannot have type %0">;
 def err_c23_constexpr_init_not_representable : Error<
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 06f1635b5bd0fed..bfea4414bc544c7 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2231,6 +2231,10 @@ static bool CheckLValueConstantExpression(EvalInfo &Info, SourceLocation Loc,
     return false;
   }
 
+  // C23 6.7.1p6: If an object or subobject declared with storage-class
+  // specifier constexpr has pointer, integer, or arithmetic type, any explicit
+  // initializer value for it shall be null, an integer constant expression, or
+  // an arithmetic constant expression, respectively.
   if (Info.getLangOpts().C23) {
     if (const auto *VarD = dyn_cast_if_present<VarDecl>(BaseVD);
         VarD && VarD->isConstexpr() && !LVal.isNullPointer())
diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp
index 781f24cb71ae994..a3cf215d8a64aa3 100644
--- a/clang/lib/Sema/DeclSpec.cpp
+++ b/clang/lib/Sema/DeclSpec.cpp
@@ -1361,6 +1361,20 @@ void DeclSpec::Finish(Sema &S, const PrintingPolicy &Policy) {
       ThreadStorageClassSpec = TSCS_unspecified;
       ThreadStorageClassSpecLoc = SourceLocation();
     }
+    if (S.getLangOpts().C23 &&
+        getConstexprSpecifier() == ConstexprSpecKind::Constexpr) {
+      S.Diag(ConstexprLoc, diag::err_invalid_decl_spec_combination)
+          << DeclSpec::getSpecifierName(getThreadStorageClassSpec())
+          << SourceRange(getThreadStorageClassSpecLoc());
+    }
+  }
+
+  if (S.getLangOpts().C23 &&
+      getConstexprSpecifier() == ConstexprSpecKind::Constexpr &&
+      StorageClassSpec == SCS_extern) {
+    S.Diag(ConstexprLoc, diag::err_invalid_decl_spec_combination)
+        << DeclSpec::getSpecifierName(getStorageClassSpec())
+        << SourceRange(getStorageClassSpecLoc());
   }
 
   // If no type specifier was provided and we're parsing a language where
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 6aff091a1df3f87..9ee69880d1e36ac 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -5172,17 +5172,15 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
     // and definitions of functions and variables.
     // C++2a [dcl.constexpr]p1: The consteval specifier shall be applied only to
     // the declaration of a function or function template
-    if (Tag) {
+    if (Tag)
       Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_tag)
           << GetDiagnosticTypeSpecifierID(DS)
           << static_cast<int>(DS.getConstexprSpecifier());
-    } else {
-      if (getLangOpts().C23)
-        Diag(DS.getConstexprSpecLoc(), diag::err_c23_constexpr_not_variable);
-      else
-        Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_wrong_decl_kind)
-            << static_cast<int>(DS.getConstexprSpecifier());
-    }
+    else if (getLangOpts().C23)
+      Diag(DS.getConstexprSpecLoc(), diag::err_c23_constexpr_not_variable);
+    else
+      Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_wrong_decl_kind)
+          << static_cast<int>(DS.getConstexprSpecifier());
     // Don't emit warnings after this error.
     return TagD;
   }
@@ -7918,17 +7916,6 @@ NamedDecl *Sema::ActOnVariableDeclarator(
         (getLangOpts().CPlusPlus17 ||
          Context.getTargetInfo().getCXXABI().isMicrosoft()))
       NewVD->setImplicitlyInline();
-
-    if (getLangOpts().C23) {
-      DeclSpec::TSCS TSC = D.getDeclSpec().getThreadStorageClassSpec();
-      if (TSC != TSCS_unspecified) {
-        Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
-             diag::err_c23_thread_local_constexpr);
-      }
-      if (NewVD->hasExternalStorage())
-        Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
-             diag::err_c23_extern_constexpr);
-    }
     break;
 
   case ConstexprSpecKind::Constinit:
@@ -8640,13 +8627,21 @@ static bool checkForConflictWithNonVisibleExternC(Sema &S, const T *ND,
   return false;
 }
 
-static bool CheckC23ConstexprVarTypeQualifiers(Sema &SemaRef,
-                                               SourceLocation VarLoc,
-                                               QualType T) {
-  if (const auto *A = SemaRef.Context.getAsArrayType(T)) {
-    T = A->getElementType();
+static bool CheckC23ConstexprVarType(Sema &SemaRef, SourceLocation VarLoc,
+                                     QualType T) {
+
+  if (T->isVariableArrayType()) {
+    SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T;
+    return true;
   }
 
+  // Arrays are qualified by their element type, so get the base type (this
+  // works on non-arrays as well).
+  T = SemaRef.Context.getBaseElementType(T);
+
+  // C23 6.7.1p4: An object declared with storage-class specifier constexpr or
+  // any of its members, even recursively, shall not have an atomic type, or a
+  // variably modified type, or a type that is volatile or restrict qualified.
   if (T->isAtomicType() || T.isVolatileQualified() || T.isRestrictQualified()) {
     SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T;
     return true;
@@ -8654,9 +8649,10 @@ static bool CheckC23ConstexprVarTypeQualifiers(Sema &SemaRef,
 
   if (T->isRecordType()) {
     const RecordDecl *RD = T->getAsRecordDecl();
-    for (const auto &F : RD->fields())
-      if (CheckC23ConstexprVarTypeQualifiers(SemaRef, VarLoc, F->getType()))
-        return true;
+    if (llvm::any_of(RD->fields(), [&SemaRef, VarLoc](const FieldDecl *F) {
+          return CheckC23ConstexprVarType(SemaRef, VarLoc, F->getType());
+        }))
+      return true;
   }
 
   return false;
@@ -8912,11 +8908,15 @@ void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
     return;
   }
 
+  if (getLangOpts().C23 && NewVD->isConstexpr() &&
+      CheckC23ConstexprVarType(*this, NewVD->getLocation(), T)) {
+    NewVD->setInvalidDecl();
+    return;
+  }
+
   if (NewVD->isConstexpr() && !T->isDependentType() &&
       RequireLiteralType(NewVD->getLocation(), T,
-                         getLangOpts().C23
-                             ? diag::err_c23_constexpr_invalid_type
-                             : diag::err_constexpr_var_non_literal)) {
+                         diag::err_constexpr_var_non_literal)) {
     NewVD->setInvalidDecl();
     return;
   }
@@ -8929,12 +8929,6 @@ void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
     return;
   }
 
-  if (getLangOpts().C23 && NewVD->isConstexpr() &&
-      CheckC23ConstexprVarTypeQualifiers(*this, NewVD->getLocation(), T)) {
-    NewVD->setInvalidDecl();
-    return;
-  }
-
   // Check that SVE types are only used in functions with SVE available.
   if (T->isSVESizelessBuiltinType() && isa<FunctionDecl>(CurContext)) {
     const FunctionDecl *FD = cast<FunctionDecl>(CurContext);
@@ -14314,7 +14308,7 @@ StmtResult Sema::ActOnCXXForRangeIdentifier(Scope *S, SourceLocation IdentLoc,
                                                       : IdentLoc);
 }
 
-static ImplicitConversionKind getConversionKind(QualType FromType,
+static ImplicitConversionKind GetConversionKind(QualType FromType,
                                                 QualType ToType) {
   if (ToType->isIntegerType()) {
     if (FromType->isComplexType())
@@ -14337,16 +14331,16 @@ static ImplicitConversionKind getConversionKind(QualType FromType,
   return ICK_Identity;
 }
 
-static bool checkC23ConstexprInitConversion(Sema &S, const Expr *Init) {
+static bool CheckC23ConstexprInitConversion(Sema &S, const Expr *Init) {
   assert(S.getLangOpts().C23);
-  const Expr *InitNoCast = Init->IgnoreImpCasts();
+  const Expr *InitNoCast = Init->IgnoreParenImpCasts();
   StandardConversionSequence SCS;
   SCS.setAsIdentityConversion();
   auto FromType = InitNoCast->getType();
   auto ToType = Init->getType();
   SCS.setToType(0, FromType);
   SCS.setToType(1, ToType);
-  SCS.Second = getConversionKind(FromType, ToType);
+  SCS.Second = GetConversionKind(FromType, ToType);
 
   APValue Value;
   QualType PreNarrowingType;
@@ -14375,7 +14369,7 @@ static bool checkC23ConstexprInitConversion(Sema &S, const Expr *Init) {
   llvm_unreachable("unhandled case in switch");
 }
 
-static bool checkC23ConstexprInitStringLiteral(const StringLiteral *SE,
+static bool CheckC23ConstexprInitStringLiteral(const StringLiteral *SE,
                                                Sema &SemaRef,
                                                SourceLocation Loc) {
   assert(SemaRef.getLangOpts().C23);
@@ -14400,24 +14394,23 @@ static bool checkC23ConstexprInitStringLiteral(const StringLiteral *SE,
   return false;
 }
 
-static bool checkC23ConstexprInitializer(Sema &S, const Expr *Init) {
-  const Expr *InitNoCast = Init->IgnoreImpCasts();
+static bool CheckC23ConstexprInitializer(Sema &S, const Expr *Init) {
+  const Expr *InitNoCast = Init->IgnoreParenImpCasts();
   if (Init->getType() != InitNoCast->getType())
-    if (checkC23ConstexprInitConversion(S, Init))
+    if (CheckC23ConstexprInitConversion(S, Init))
       return true;
 
-  if (auto *SE = dyn_cast<StringLiteral>(Init))
-    if (checkC23ConstexprInitStringLiteral(SE, S, Init->getBeginLoc()))
+  if (const auto *SE = dyn_cast<StringLiteral>(Init))
+    if (CheckC23ConstexprInitStringLiteral(SE, S, Init->getBeginLoc()))
       return true;
 
   for (const Stmt *SubStmt : Init->children()) {
-    const Expr *ChildExpr = dyn_cast_or_null<const Expr>(SubStmt);
+    const Expr *ChildExpr = dyn_cast_or_null<Expr>(SubStmt);
     if (!ChildExpr)
       continue;
 
-    if (checkC23ConstexprInitializer(S, ChildExpr)) {
+    if (CheckC23ConstexprInitializer(S, ChildExpr))
       return true;
-    }
   }
   return false;
 }
@@ -14619,7 +14612,7 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
     if (HasConstInit) {
       // FIXME: Consider replacing the initializer with a ConstantExpr.
       if (getLangOpts().C23 && var->isConstexpr())
-        checkC23ConstexprInitializer(*this, Init);
+        CheckC23ConstexprInitializer(*this, Init);
     } else if (var->isConstexpr()) {
       SourceLocation DiagLoc = var->getLocation();
       // If the note doesn't add any useful information other than a source
diff --git a/clang/test/Sema/constexpr.c b/clang/test/Sema/constexpr.c
index 650716640e2f470..d8e68b56f918ced 100644
--- a/clang/test/Sema/constexpr.c
+++ b/clang/test/Sema/constexpr.c
@@ -1,8 +1,8 @@
 // RUN: %clang_cc1 -std=c2x -verify -triple x86_64 -pedantic -Wno-conversion -Wno-constant-conversion -Wno-div-by-zero %s
 
 // Check that constexpr only applies to variables.
-constexpr void f0() {} // expected-error {{constexpr is only allowed in variable declarations}}
-constexpr const int f1() { return 0; } // expected-error {{constexpr is only allowed in variable declarations}}
+constexpr void f0() {} // expected-error {{'constexpr' can only be used in variable declarations}}
+constexpr const int f1() { return 0; } // expected-error {{'constexpr' can only be used in variable declarations}}
 
 constexpr struct S1 { int f; }; //expected-error {{struct cannot be marked constexpr}}
 constexpr struct S2 ; // expected-error {{struct cannot be marked constexpr}}
@@ -18,7 +18,7 @@ struct S3 {
   // expected-error at -2 {{expected ';' at end}}
 };
 
-constexpr; // expected-error {{constexpr is only allowed in variable declarations}}
+constexpr; // expected-error {{'constexpr' can only be used in variable declarations}}
 constexpr int V1 = 3;
 constexpr float V2 = 7.0;
 int V3 = (constexpr)3; // expected-error {{expected expression}}
@@ -40,12 +40,12 @@ void f3(constexpr register int P1) { // expected-error {{function parameter cann
   constexpr register auto V10 = 0.0;
 }
 
-constexpr thread_local int V11 = 38; // expected-error {{thread-local storage is not allowed with constexpr}}
-constexpr static thread_local double V12 = 38; // expected-error {{thread-local storage is not allowed with constexpr}}
-constexpr extern thread_local char V13; // expected-error {{extern specifier is not allowed with constexpr}}
-// expected-error at -1 {{thread-local storage is not allowed with constexpr}}
+constexpr thread_local int V11 = 38; // expected-error {{cannot combine with previous '_Thread_local' declaration specifier}}
+constexpr static thread_local double V12 = 38; // expected-error {{cannot combine with previous '_Thread_local' declaration specifier}}
+constexpr extern thread_local char V13; // expected-error {{cannot combine with previous '_Thread_local' declaration specifier}}
+// expected-error at -1 {{cannot combine with previous 'extern' declaration specifier}}
 // expected-error at -2 {{constexpr variable declaration must be a definition}}
-constexpr thread_local short V14 = 38; // expected-error {{thread-local storage is not allowed with constexpr}}
+constexpr thread_local short V14 = 38; // expected-error {{cannot combine with previous '_Thread_local' declaration specifier}}
 
 // Check how constexpr works with qualifiers.
 constexpr _Atomic int V15 = 0; // expected-error {{constexpr variable cannot have type 'const _Atomic(int)'}}
@@ -251,6 +251,16 @@ void f5() {
 
 }
 
+constexpr char string[] = "test""ing this out\xFF";
+constexpr unsigned char ustring[] = "test""ing this out\xFF";
+// expected-error at -1 {{constexpr initializer evaluates to -1 which is not exactly representable in type 'const unsigned char'}}
+constexpr char wstring[] = u8"test"u8"ing this out\xFF";
+constexpr unsigned char wustring[] = u8"test"u8"ing this out\xFF";
+// expected-error at -1 {{constexpr initializer evaluates to -1 which is not exactly representable in type 'const unsigned char'}}
+
+constexpr int i = (12);
+constexpr int j = (i);
+
 // Check that initializer for pointer constexpr variable should be null.
 constexpr int V80 = 3;
 constexpr const int *V81 = &V80;

>From d14018a89eee8cf549b4e2530b9ce4d2f51b27c5 Mon Sep 17 00:00:00 2001
From: "Podchishchaeva, Mariya" <mariya.podchishchaeva at intel.com>
Date: Wed, 29 Nov 2023 09:23:19 -0800
Subject: [PATCH 4/6] Update a release note

---
 clang/docs/ReleaseNotes.rst | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 55822d55b5b4f1c..a2c377d5f0a5dc1 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -208,7 +208,8 @@ C23 Feature Support
 
 - Clang now supports ``<stdckdint.h>`` which defines several macros for performing
   checked integer arithmetic. It is also exposed in pre-C23 modes.
-- Clang now supports ``N3018 The constexpr specifier for object definitions``.
+- Clang now supports `N3018 The constexpr specifier for object definitions`
+  <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3018.htm>`_.
 
 - Completed the implementation of
   `N2508 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2508.pdf>`_. We

>From 85ad720fb8bd78d0e72625557e2a26d9647e74eb Mon Sep 17 00:00:00 2001
From: "Podchishchaeva, Mariya" <mariya.podchishchaeva at intel.com>
Date: Wed, 29 Nov 2023 09:28:05 -0800
Subject: [PATCH 5/6] Remove unused messages

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 016a099f012938b..dc5e58fc4683a43 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2933,10 +2933,6 @@ def note_private_extern : Note<
   "use __attribute__((visibility(\"hidden\"))) attribute instead">;
 
 // C23 constexpr
-def err_c23_thread_local_constexpr : Error<
-  "thread-local storage is not allowed with constexpr">;
-def err_c23_extern_constexpr : Error<
-  "extern specifier is not allowed with constexpr">;
 def err_c23_constexpr_not_variable : Error<
   "'constexpr' can only be used in variable declarations">;
 def err_c23_constexpr_invalid_type : Error<

>From 83d96777a93c38a91fef189b31269f5f6674d671 Mon Sep 17 00:00:00 2001
From: "Podchishchaeva, Mariya" <mariya.podchishchaeva at intel.com>
Date: Thu, 30 Nov 2023 03:07:49 -0800
Subject: [PATCH 6/6] Update comments

---
 clang/lib/AST/Decl.cpp | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 678a366ed29ad78..ce91f396081b15c 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2483,11 +2483,16 @@ bool VarDecl::mightBeUsableInConstantExpressions(const ASTContext &C) const {
   if (!getType().isConstant(C) || getType().isVolatileQualified())
     return false;
 
-  // In C++, const, non-volatile variables of integral or enumeration types
-  // can be used in constant expressions.
+  // In C++, but not in C, const, non-volatile variables of integral or
+  // enumeration types can be used in constant expressions.
   if (getType()->isIntegralOrEnumerationType() && !Lang.C23)
     return true;
 
+  // C23 6.6p7: An identifier that is:
+  // ...
+  // - declared with storage-class specifier constexpr and has an object type,
+  // is a named constant, ... such a named constant is a constant expression
+  // with the type and value of the declared object.
   // Additionally, in C++11, non-volatile constexpr variables can be used in
   // constant expressions.
   return (Lang.CPlusPlus11 || Lang.C23) && isConstexpr();



More information about the llvm-commits mailing list