[clang] 47ccfd7 - [Clang] Implement P2741R3 - user-generated static_assert messages

Corentin Jabot via cfe-commits cfe-commits at lists.llvm.org
Wed Jul 19 23:33:25 PDT 2023


Author: Corentin Jabot
Date: 2023-07-20T08:33:19+02:00
New Revision: 47ccfd7a89e2a9a747a7114db18db1376324799c

URL: https://github.com/llvm/llvm-project/commit/47ccfd7a89e2a9a747a7114db18db1376324799c
DIFF: https://github.com/llvm/llvm-project/commit/47ccfd7a89e2a9a747a7114db18db1376324799c.diff

LOG: [Clang] Implement P2741R3 - user-generated static_assert messages

Reviewed By: #clang-language-wg, aaron.ballman

Differential Revision: https://reviews.llvm.org/D154290

Added: 
    clang/test/SemaCXX/static-assert-cxx26.cpp

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/AST/DeclCXX.h
    clang/include/clang/AST/Expr.h
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Sema/Sema.h
    clang/lib/AST/DeclCXX.cpp
    clang/lib/AST/DeclPrinter.cpp
    clang/lib/AST/ExprConstant.cpp
    clang/lib/AST/ODRDiagsEmitter.cpp
    clang/lib/Frontend/InitPreprocessor.cpp
    clang/lib/Parse/ParseDeclCXX.cpp
    clang/lib/Sema/SemaDeclCXX.cpp
    clang/lib/Sema/SemaOverload.cpp
    clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
    clang/test/Lexer/cxx-features.cpp
    clang/tools/libclang/CIndex.cpp
    clang/www/cxx_status.html

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 55fa4cb27d0b61..ef1cc898c21f6d 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -137,6 +137,7 @@ C++2c Feature Support
 - Implemented `P2738R1: constexpr cast from void* <https://wg21.link/P2738R1>`_.
 - Partially implemented `P2361R6: Unevaluated strings <https://wg21.link/P2361R6>`_.
   The changes to attributes declarations are not part of this release.
+- Implemented `P2741R3: user-generated static_assert messages  <https://wg21.link/P2741R3>`_.
 
 Resolutions to C++ Defect Reports
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

diff  --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index f4322e1e00f0c6..afec8150c2c9c6 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -4010,12 +4010,12 @@ class UnresolvedUsingIfExistsDecl final : public NamedDecl {
 /// Represents a C++11 static_assert declaration.
 class StaticAssertDecl : public Decl {
   llvm::PointerIntPair<Expr *, 1, bool> AssertExprAndFailed;
-  StringLiteral *Message;
+  Expr *Message;
   SourceLocation RParenLoc;
 
   StaticAssertDecl(DeclContext *DC, SourceLocation StaticAssertLoc,
-                   Expr *AssertExpr, StringLiteral *Message,
-                   SourceLocation RParenLoc, bool Failed)
+                   Expr *AssertExpr, Expr *Message, SourceLocation RParenLoc,
+                   bool Failed)
       : Decl(StaticAssert, DC, StaticAssertLoc),
         AssertExprAndFailed(AssertExpr, Failed), Message(Message),
         RParenLoc(RParenLoc) {}
@@ -4027,15 +4027,15 @@ class StaticAssertDecl : public Decl {
 
   static StaticAssertDecl *Create(ASTContext &C, DeclContext *DC,
                                   SourceLocation StaticAssertLoc,
-                                  Expr *AssertExpr, StringLiteral *Message,
+                                  Expr *AssertExpr, Expr *Message,
                                   SourceLocation RParenLoc, bool Failed);
   static StaticAssertDecl *CreateDeserialized(ASTContext &C, unsigned ID);
 
   Expr *getAssertExpr() { return AssertExprAndFailed.getPointer(); }
   const Expr *getAssertExpr() const { return AssertExprAndFailed.getPointer(); }
 
-  StringLiteral *getMessage() { return Message; }
-  const StringLiteral *getMessage() const { return Message; }
+  Expr *getMessage() { return Message; }
+  const Expr *getMessage() const { return Message; }
 
   bool isFailed() const { return AssertExprAndFailed.getInt(); }
 

diff  --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 7a886f546ed937..f9795b6386c46f 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -762,6 +762,11 @@ class Expr : public ValueStmt {
   /// strlen, false otherwise.
   bool tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const;
 
+  bool EvaluateCharRangeAsString(std::string &Result,
+                                 const Expr *SizeExpression,
+                                 const Expr *PtrExpression, ASTContext &Ctx,
+                                 EvalResult &Status) const;
+
   /// Enumeration used to describe the kind of Null pointer constant
   /// returned from \c isNullPointerConstant().
   enum NullPointerConstantKind {

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 52cca5acfd9245..7faefab0a4187a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -83,8 +83,8 @@ def err_typecheck_converted_constant_expression_indirect : Error<
   "bind reference to a temporary">;
 def err_expr_not_cce : Error<
   "%select{case value|enumerator value|non-type template argument|"
-  "array size|explicit specifier argument|noexcept specifier argument}0 "
-  "is not a constant expression">;
+  "array size|explicit specifier argument|noexcept specifier argument|"
+  "call to 'size()'|call to 'data()'}0 is not a constant expression">;
 def ext_cce_narrowing : ExtWarn<
   "%select{case value|enumerator value|non-type template argument|"
   "array size|explicit specifier argument|noexcept specifier argument}0 "
@@ -1545,6 +1545,24 @@ def err_static_assert_requirement_failed : Error<
   "static assertion failed due to requirement '%0'%select{: %2|}1">;
 def note_expr_evaluates_to : Note<
   "expression evaluates to '%0 %1 %2'">;
+def err_static_assert_invalid_message : Error<
+  "the message in a static assertion must be a string literal or an "
+  "object with 'data()' and 'size()' member functions">;
+def err_static_assert_missing_member_function : Error<
+  "the message object in this static assertion is missing %select{"
+  "a 'size()' member function|"
+  "a 'data()' member function|"
+  "'data()' and 'size()' member functions}0">;
+def err_static_assert_invalid_mem_fn_ret_ty : Error<
+  "the message in a static assertion must have a '%select{size|data}0()' member "
+  "function returning an object convertible to '%select{std::size_t|const char *}0'">;
+def warn_static_assert_message_constexpr : Warning<
+  "the message in this static assertion is not a "
+  "constant expression">,
+  DefaultError, InGroup<DiagGroup<"invalid-static-assert-message">>;
+def err_static_assert_message_constexpr : Error<
+  "the message in a static assertion must be produced by a "
+  "constant expression">;
 
 def warn_consteval_if_always_true : Warning<
   "consteval if is always true in an %select{unevaluated|immediate}0 context">,

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 400b6f71133236..082fa252feabac 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3887,8 +3887,17 @@ class Sema final {
     CCEK_TemplateArg,  ///< Value of a non-type template parameter.
     CCEK_ArrayBound,   ///< Array bound in array declarator or new-expression.
     CCEK_ExplicitBool, ///< Condition in an explicit(bool) specifier.
-    CCEK_Noexcept      ///< Condition in a noexcept(bool) specifier.
+    CCEK_Noexcept,     ///< Condition in a noexcept(bool) specifier.
+    CCEK_StaticAssertMessageSize, ///< Call to size() in a static assert
+                                  ///< message.
+    CCEK_StaticAssertMessageData, ///< Call to data() in a static assert
+                                  ///< message.
   };
+
+  ExprResult BuildConvertedConstantExpression(Expr *From, QualType T,
+                                              CCEKind CCE,
+                                              NamedDecl *Dest = nullptr);
+
   ExprResult CheckConvertedConstantExpression(Expr *From, QualType T,
                                               llvm::APSInt &Value, CCEKind CCE);
   ExprResult CheckConvertedConstantExpression(Expr *From, QualType T,
@@ -7795,15 +7804,16 @@ class Sema final {
   void UnmarkAsLateParsedTemplate(FunctionDecl *FD);
   bool IsInsideALocalClassWithinATemplateFunction();
 
+  bool EvaluateStaticAssertMessageAsString(Expr *Message, std::string &Result,
+                                           ASTContext &Ctx,
+                                           bool ErrorOnInvalidMessage);
   Decl *ActOnStaticAssertDeclaration(SourceLocation StaticAssertLoc,
                                      Expr *AssertExpr,
                                      Expr *AssertMessageExpr,
                                      SourceLocation RParenLoc);
   Decl *BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
-                                     Expr *AssertExpr,
-                                     StringLiteral *AssertMessageExpr,
-                                     SourceLocation RParenLoc,
-                                     bool Failed);
+                                     Expr *AssertExpr, Expr *AssertMessageExpr,
+                                     SourceLocation RParenLoc, bool Failed);
   void DiagnoseStaticAssertDetails(const Expr *E);
 
   FriendDecl *CheckFriendTypeDecl(SourceLocation LocStart,

diff  --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index bbd7901dc8e824..e4572aab5b092a 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -3237,8 +3237,7 @@ void StaticAssertDecl::anchor() {}
 
 StaticAssertDecl *StaticAssertDecl::Create(ASTContext &C, DeclContext *DC,
                                            SourceLocation StaticAssertLoc,
-                                           Expr *AssertExpr,
-                                           StringLiteral *Message,
+                                           Expr *AssertExpr, Expr *Message,
                                            SourceLocation RParenLoc,
                                            bool Failed) {
   return new (C, DC) StaticAssertDecl(DC, StaticAssertLoc, AssertExpr, Message,

diff  --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 4a285f36d92ca8..a6a9911c8992aa 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -949,9 +949,9 @@ void DeclPrinter::VisitStaticAssertDecl(StaticAssertDecl *D) {
   Out << "static_assert(";
   D->getAssertExpr()->printPretty(Out, nullptr, Policy, Indentation, "\n",
                                   &Context);
-  if (StringLiteral *SL = D->getMessage()) {
+  if (Expr *E = D->getMessage()) {
     Out << ", ";
-    SL->printPretty(Out, nullptr, Policy, Indentation, "\n", &Context);
+    E->printPretty(Out, nullptr, Policy, Indentation, "\n", &Context);
   }
   Out << ")";
 }

diff  --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index f0185d016d7163..b5308ed94e5a04 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -50,6 +50,7 @@
 #include "clang/AST/StmtVisitor.h"
 #include "clang/AST/TypeLoc.h"
 #include "clang/Basic/Builtins.h"
+#include "clang/Basic/DiagnosticSema.h"
 #include "clang/Basic/TargetInfo.h"
 #include "llvm/ADT/APFixedPoint.h"
 #include "llvm/ADT/SmallBitVector.h"
@@ -1995,7 +1996,8 @@ static bool IsGlobalLValue(APValue::LValueBase B) {
 
   // ... a null pointer value, or a prvalue core constant expression of type
   // std::nullptr_t.
-  if (!B) return true;
+  if (!B)
+    return true;
 
   if (const ValueDecl *D = B.dyn_cast<const ValueDecl*>()) {
     // ... the address of an object with static storage duration,
@@ -2126,6 +2128,7 @@ static void NoteLValueLocation(EvalInfo &Info, APValue::LValueBase Base) {
       Info.Note((*Alloc)->AllocExpr->getExprLoc(),
                 diag::note_constexpr_dynamic_alloc_here);
   }
+
   // We have no information to show for a typeid(T) object.
 }
 
@@ -16379,6 +16382,45 @@ static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
   }
 }
 
+bool Expr::EvaluateCharRangeAsString(std::string &Result,
+                                     const Expr *SizeExpression,
+                                     const Expr *PtrExpression, ASTContext &Ctx,
+                                     EvalResult &Status) const {
+  LValue String;
+  EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantExpression);
+  Info.InConstantContext = true;
+
+  FullExpressionRAII Scope(Info);
+  APSInt SizeValue;
+  if (!::EvaluateInteger(SizeExpression, SizeValue, Info))
+    return false;
+
+  int64_t Size = SizeValue.getExtValue();
+
+  if (!::EvaluatePointer(PtrExpression, String, Info))
+    return false;
+
+  QualType CharTy = PtrExpression->getType()->getPointeeType();
+  for (int64_t I = 0; I < Size; ++I) {
+    APValue Char;
+    if (!handleLValueToRValueConversion(Info, PtrExpression, CharTy, String,
+                                        Char))
+      return false;
+
+    APSInt C = Char.getInt();
+    Result.push_back(static_cast<char>(C.getExtValue()));
+    if (!HandleLValueArrayAdjustment(Info, PtrExpression, String, CharTy, 1))
+      return false;
+  }
+  if (!Scope.destroy())
+    return false;
+
+  if (!CheckMemoryLeaks(Info))
+    return false;
+
+  return true;
+}
+
 bool Expr::tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const {
   Expr::EvalStatus Status;
   EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantFold);

diff  --git a/clang/lib/AST/ODRDiagsEmitter.cpp b/clang/lib/AST/ODRDiagsEmitter.cpp
index 1994f0865428bd..0189a5de625e08 100644
--- a/clang/lib/AST/ODRDiagsEmitter.cpp
+++ b/clang/lib/AST/ODRDiagsEmitter.cpp
@@ -994,40 +994,43 @@ bool ODRDiagsEmitter::diagnoseMismatch(
       return true;
     }
 
-    const StringLiteral *FirstStr = FirstSA->getMessage();
-    const StringLiteral *SecondStr = SecondSA->getMessage();
-    assert((FirstStr || SecondStr) && "Both messages cannot be empty");
-    if ((FirstStr && !SecondStr) || (!FirstStr && SecondStr)) {
+    const Expr *FirstMessage = FirstSA->getMessage();
+    const Expr *SecondMessage = SecondSA->getMessage();
+    assert((FirstMessage || SecondMessage) && "Both messages cannot be empty");
+    if ((FirstMessage && !SecondMessage) || (!FirstMessage && SecondMessage)) {
       SourceLocation FirstLoc, SecondLoc;
       SourceRange FirstRange, SecondRange;
-      if (FirstStr) {
-        FirstLoc = FirstStr->getBeginLoc();
-        FirstRange = FirstStr->getSourceRange();
+      if (FirstMessage) {
+        FirstLoc = FirstMessage->getBeginLoc();
+        FirstRange = FirstMessage->getSourceRange();
       } else {
         FirstLoc = FirstSA->getBeginLoc();
         FirstRange = FirstSA->getSourceRange();
       }
-      if (SecondStr) {
-        SecondLoc = SecondStr->getBeginLoc();
-        SecondRange = SecondStr->getSourceRange();
+      if (SecondMessage) {
+        SecondLoc = SecondMessage->getBeginLoc();
+        SecondRange = SecondMessage->getSourceRange();
       } else {
         SecondLoc = SecondSA->getBeginLoc();
         SecondRange = SecondSA->getSourceRange();
       }
       DiagError(FirstLoc, FirstRange, StaticAssertOnlyMessage)
-          << (FirstStr == nullptr);
+          << (FirstMessage == nullptr);
       DiagNote(SecondLoc, SecondRange, StaticAssertOnlyMessage)
-          << (SecondStr == nullptr);
+          << (SecondMessage == nullptr);
       return true;
     }
 
-    if (FirstStr && SecondStr &&
-        FirstStr->getString() != SecondStr->getString()) {
-      DiagError(FirstStr->getBeginLoc(), FirstStr->getSourceRange(),
-                StaticAssertMessage);
-      DiagNote(SecondStr->getBeginLoc(), SecondStr->getSourceRange(),
-               StaticAssertMessage);
-      return true;
+    if (FirstMessage && SecondMessage) {
+      unsigned FirstMessageODRHash = computeODRHash(FirstMessage);
+      unsigned SecondMessageODRHash = computeODRHash(SecondMessage);
+      if (FirstMessageODRHash != SecondMessageODRHash) {
+        DiagError(FirstMessage->getBeginLoc(), FirstMessage->getSourceRange(),
+                  StaticAssertMessage);
+        DiagNote(SecondMessage->getBeginLoc(), SecondMessage->getSourceRange(),
+                 StaticAssertMessage);
+        return true;
+      }
     }
     break;
   }

diff  --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 2ab9e652b10c3c..f8fae82fba1208 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -629,8 +629,10 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
     Builder.defineMacro("__cpp_constexpr_in_decltype", "201711L");
     Builder.defineMacro("__cpp_range_based_for",
                         LangOpts.CPlusPlus17 ? "201603L" : "200907");
-    Builder.defineMacro("__cpp_static_assert",
-                        LangOpts.CPlusPlus17 ? "201411L" : "200410");
+    Builder.defineMacro("__cpp_static_assert", LangOpts.CPlusPlus26 ? "202306L"
+                                               : LangOpts.CPlusPlus17
+                                                   ? "201411L"
+                                                   : "200410");
     Builder.defineMacro("__cpp_decltype", "200707L");
     Builder.defineMacro("__cpp_attributes", "200809L");
     Builder.defineMacro("__cpp_rvalue_references", "200610L");

diff  --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 126843d8fd2427..742f3ac0b6f64a 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1016,14 +1016,17 @@ Decl *Parser::ParseStaticAssertDeclaration(SourceLocation &DeclEnd) {
       return nullptr;
     }
 
-    if (!isTokenStringLiteral()) {
+    if (isTokenStringLiteral())
+      AssertMessage = ParseUnevaluatedStringLiteralExpression();
+    else if (getLangOpts().CPlusPlus26)
+      AssertMessage = ParseConstantExpressionInExprEvalContext();
+    else {
       Diag(Tok, diag::err_expected_string_literal)
           << /*Source='static_assert'*/ 1;
       SkipMalformedDecl();
       return nullptr;
     }
 
-    AssertMessage = ParseUnevaluatedStringLiteralExpression();
     if (AssertMessage.isInvalid()) {
       SkipMalformedDecl();
       return nullptr;

diff  --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 4848af9ade35b3..9e2845384bc170 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -16745,14 +16745,11 @@ Decl *Sema::ActOnStaticAssertDeclaration(SourceLocation StaticAssertLoc,
                                          Expr *AssertExpr,
                                          Expr *AssertMessageExpr,
                                          SourceLocation RParenLoc) {
-  StringLiteral *AssertMessage =
-      AssertMessageExpr ? cast<StringLiteral>(AssertMessageExpr) : nullptr;
-
   if (DiagnoseUnexpandedParameterPack(AssertExpr, UPPC_StaticAssertExpression))
     return nullptr;
 
   return BuildStaticAssertDeclaration(StaticAssertLoc, AssertExpr,
-                                      AssertMessage, RParenLoc, false);
+                                      AssertMessageExpr, RParenLoc, false);
 }
 
 /// Convert \V to a string we can present to the user in a diagnostic
@@ -16887,13 +16884,147 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) {
   }
 }
 
+bool Sema::EvaluateStaticAssertMessageAsString(Expr *Message,
+                                               std::string &Result,
+                                               ASTContext &Ctx,
+                                               bool ErrorOnInvalidMessage) {
+  assert(Message);
+  assert(!Message->isTypeDependent() && !Message->isValueDependent() &&
+         "can't evaluate a dependant static assert message");
+
+  if (const auto *SL = dyn_cast<StringLiteral>(Message)) {
+    assert(SL->isUnevaluated() && "expected an unevaluated string");
+    Result.assign(SL->getString().begin(), SL->getString().end());
+    return true;
+  }
+
+  SourceLocation Loc = Message->getBeginLoc();
+  QualType T = Message->getType().getNonReferenceType();
+  auto *RD = T->getAsCXXRecordDecl();
+  if (!RD) {
+    Diag(Loc, diag::err_static_assert_invalid_message);
+    return false;
+  }
+
+  auto FindMember = [&](StringRef Member, bool &Empty,
+                        bool Diag = false) -> std::optional<LookupResult> {
+    QualType ObjectType = Message->getType();
+    Expr::Classification ObjectClassification =
+        Message->Classify(getASTContext());
+
+    DeclarationName DN = PP.getIdentifierInfo(Member);
+    LookupResult MemberLookup(*this, DN, Loc, Sema::LookupMemberName);
+    LookupQualifiedName(MemberLookup, RD);
+    Empty = MemberLookup.empty();
+    OverloadCandidateSet Candidates(MemberLookup.getNameLoc(),
+                                    OverloadCandidateSet::CSK_Normal);
+    for (NamedDecl *D : MemberLookup) {
+      AddMethodCandidate(DeclAccessPair::make(D, D->getAccess()), ObjectType,
+                         ObjectClassification, /*Args=*/{}, Candidates);
+    }
+    OverloadCandidateSet::iterator Best;
+    switch (Candidates.BestViableFunction(*this, Loc, Best)) {
+    case OR_Success:
+      return MemberLookup;
+    default:
+      if (Diag)
+        Candidates.NoteCandidates(
+            PartialDiagnosticAt(
+                Loc, PDiag(diag::err_static_assert_invalid_mem_fn_ret_ty)
+                         << (Member == "data")),
+            *this, OCD_AllCandidates, /*Args=*/{});
+    }
+    return std::nullopt;
+  };
+
+  bool SizeNotFound, DataNotFound;
+  std::optional<LookupResult> SizeMember = FindMember("size", SizeNotFound);
+  std::optional<LookupResult> DataMember = FindMember("data", DataNotFound);
+  if (SizeNotFound || DataNotFound) {
+    Diag(Loc, diag::err_static_assert_missing_member_function)
+        << ((SizeNotFound && DataNotFound) ? 2
+            : SizeNotFound                 ? 0
+                                           : 1);
+    return false;
+  }
+
+  if (!SizeMember || !DataMember) {
+    if (!SizeMember)
+      FindMember("size", SizeNotFound, /*Diag=*/true);
+    if (!DataMember)
+      FindMember("data", DataNotFound, /*Diag=*/true);
+    return false;
+  }
+
+  auto BuildExpr = [&](LookupResult &LR) {
+    ExprResult Res = BuildMemberReferenceExpr(
+        Message, Message->getType(), Message->getBeginLoc(), false,
+        CXXScopeSpec(), SourceLocation(), nullptr, LR, nullptr, nullptr);
+    if (Res.isInvalid())
+      return ExprError();
+    Res = BuildCallExpr(nullptr, Res.get(), Loc, std::nullopt, Loc, nullptr,
+                        false, true);
+    if (Res.isInvalid())
+      return ExprError();
+    if (Res.get()->isTypeDependent() || Res.get()->isValueDependent())
+      return ExprError();
+    return TemporaryMaterializationConversion(Res.get());
+  };
+
+  ExprResult SizeE = BuildExpr(*SizeMember);
+  ExprResult DataE = BuildExpr(*DataMember);
+
+  QualType SizeT = Context.getSizeType();
+  QualType ConstCharPtr =
+      Context.getPointerType(Context.getConstType(Context.CharTy));
+
+  ExprResult EvaluatedSize =
+      SizeE.isInvalid() ? ExprError()
+                        : BuildConvertedConstantExpression(
+                              SizeE.get(), SizeT, CCEK_StaticAssertMessageSize);
+  if (EvaluatedSize.isInvalid()) {
+    Diag(Loc, diag::err_static_assert_invalid_mem_fn_ret_ty) << /*size*/ 0;
+    return false;
+  }
+
+  ExprResult EvaluatedData =
+      DataE.isInvalid()
+          ? ExprError()
+          : BuildConvertedConstantExpression(DataE.get(), ConstCharPtr,
+                                             CCEK_StaticAssertMessageData);
+  if (EvaluatedData.isInvalid()) {
+    Diag(Loc, diag::err_static_assert_invalid_mem_fn_ret_ty) << /*data*/ 1;
+    return false;
+  }
+
+  if (!ErrorOnInvalidMessage &&
+      Diags.isIgnored(diag::warn_static_assert_message_constexpr, Loc))
+    return true;
+
+  Expr::EvalResult Status;
+  SmallVector<PartialDiagnosticAt, 8> Notes;
+  Status.Diag = &Notes;
+  if (!Message->EvaluateCharRangeAsString(Result, EvaluatedSize.get(),
+                                          EvaluatedData.get(), Ctx, Status) ||
+      !Notes.empty()) {
+    Diag(Message->getBeginLoc(),
+         ErrorOnInvalidMessage ? diag::err_static_assert_message_constexpr
+                               : diag::warn_static_assert_message_constexpr);
+    for (const auto &Note : Notes)
+      Diag(Note.first, Note.second);
+    return !ErrorOnInvalidMessage;
+  }
+  return true;
+}
+
 Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
-                                         Expr *AssertExpr,
-                                         StringLiteral *AssertMessage,
+                                         Expr *AssertExpr, Expr *AssertMessage,
                                          SourceLocation RParenLoc,
                                          bool Failed) {
   assert(AssertExpr != nullptr && "Expected non-null condition");
   if (!AssertExpr->isTypeDependent() && !AssertExpr->isValueDependent() &&
+      (!AssertMessage || (!AssertMessage->isTypeDependent() &&
+                          !AssertMessage->isValueDependent())) &&
       !Failed) {
     // In a static_assert-declaration, the constant-expression shall be a
     // constant expression that can be contextually converted to bool.
@@ -16927,6 +17058,14 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
                        FoldKind).isInvalid())
       Failed = true;
 
+    // If the static_assert passes, only verify that
+    // the message is grammatically valid without evaluating it.
+    if (!Failed && AssertMessage && Cond.getBoolValue()) {
+      std::string Str;
+      EvaluateStaticAssertMessageAsString(AssertMessage, Str, Context,
+                                          /*ErrorOnInvalidMessage=*/false);
+    }
+
     // CWG2518
     // [dcl.pre]/p10  If [...] the expression is evaluated in the context of a
     // template definition, the declaration has no effect.
@@ -16934,14 +17073,17 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
         getLangOpts().CPlusPlus && CurContext->isDependentContext();
 
     if (!Failed && !Cond && !InTemplateDefinition) {
-
       SmallString<256> MsgBuffer;
       llvm::raw_svector_ostream Msg(MsgBuffer);
+      bool HasMessage = AssertMessage;
       if (AssertMessage) {
-        const auto *MsgStr = cast<StringLiteral>(AssertMessage);
-        Msg << MsgStr->getString();
+        std::string Str;
+        HasMessage =
+            EvaluateStaticAssertMessageAsString(
+                AssertMessage, Str, Context, /*ErrorOnInvalidMessage=*/true) ||
+            !Str.empty();
+        Msg << Str;
       }
-
       Expr *InnerCond = nullptr;
       std::string InnerCondDescription;
       std::tie(InnerCond, InnerCondDescription) =
@@ -16950,7 +17092,7 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
         // Drill down into concept specialization expressions to see why they
         // weren't satisfied.
         Diag(AssertExpr->getBeginLoc(), diag::err_static_assert_failed)
-            << !AssertMessage << Msg.str() << AssertExpr->getSourceRange();
+            << !HasMessage << Msg.str() << AssertExpr->getSourceRange();
         ConstraintSatisfaction Satisfaction;
         if (!CheckConstraintSatisfaction(InnerCond, Satisfaction))
           DiagnoseUnsatisfiedConstraint(Satisfaction);
@@ -16958,12 +17100,12 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
                            && !isa<IntegerLiteral>(InnerCond)) {
         Diag(InnerCond->getBeginLoc(),
              diag::err_static_assert_requirement_failed)
-            << InnerCondDescription << !AssertMessage << Msg.str()
+            << InnerCondDescription << !HasMessage << Msg.str()
             << InnerCond->getSourceRange();
         DiagnoseStaticAssertDetails(InnerCond);
       } else {
         Diag(AssertExpr->getBeginLoc(), diag::err_static_assert_failed)
-            << !AssertMessage << Msg.str() << AssertExpr->getSourceRange();
+            << !HasMessage << Msg.str() << AssertExpr->getSourceRange();
         PrintContextStack();
       }
       Failed = true;

diff  --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 45a9e5dc98c032..c70d37de0a0130 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -5817,14 +5817,14 @@ static bool CheckConvertedConstantConversions(Sema &S,
   llvm_unreachable("unknown conversion kind");
 }
 
-/// CheckConvertedConstantExpression - Check that the expression From is a
-/// converted constant expression of type T, perform the conversion and produce
-/// the converted expression, per C++11 [expr.const]p3.
-static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From,
-                                                   QualType T, APValue &Value,
+/// BuildConvertedConstantExpression - Check that the expression From is a
+/// converted constant expression of type T, perform the conversion but
+/// does not evaluate the expression
+static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From,
+                                                   QualType T,
                                                    Sema::CCEKind CCE,
-                                                   bool RequireInt,
-                                                   NamedDecl *Dest) {
+                                                   NamedDecl *Dest,
+                                                   APValue &PreNarrowingValue) {
   assert(S.getLangOpts().CPlusPlus11 &&
          "converted constant expression outside C++11");
 
@@ -5908,7 +5908,6 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From,
 
   // Check for a narrowing implicit conversion.
   bool ReturnPreNarrowingValue = false;
-  APValue PreNarrowingValue;
   QualType PreNarrowingType;
   switch (SCS->getNarrowingKind(S.Context, Result.get(), PreNarrowingValue,
                                 PreNarrowingType)) {
@@ -5942,12 +5941,19 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From,
         << CCE << /*Constant*/ 0 << From->getType() << T;
     break;
   }
+  if (!ReturnPreNarrowingValue)
+    PreNarrowingValue = {};
 
-  if (Result.get()->isValueDependent()) {
-    Value = APValue();
-    return Result;
-  }
+  return Result;
+}
 
+/// EvaluateConvertedConstantExpression - Evaluate an Expression
+/// That is a converted constant expression
+/// (which was built with BuildConvertedConstantExpression)
+static ExprResult EvaluateConvertedConstantExpression(
+    Sema &S, Expr *E, QualType T, APValue &Value, Sema::CCEKind CCE,
+    bool RequireInt, const APValue &PreNarrowingValue) {
+  ExprResult Result = E;
   // Check the expression is a constant expression.
   SmallVector<PartialDiagnosticAt, 8> Notes;
   Expr::EvalResult Eval;
@@ -5961,7 +5967,7 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From,
   else
     Kind = ConstantExprKind::Normal;
 
-  if (!Result.get()->EvaluateAsConstantExpr(Eval, S.Context, Kind) ||
+  if (!E->EvaluateAsConstantExpr(Eval, S.Context, Kind) ||
       (RequireInt && !Eval.Val.isInt())) {
     // The expression can't be folded, so we can't keep it at this position in
     // the AST.
@@ -5972,7 +5978,7 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From,
     if (Notes.empty()) {
       // It's a constant expression.
       Expr *E = ConstantExpr::Create(S.Context, Result.get(), Value);
-      if (ReturnPreNarrowingValue)
+      if (!PreNarrowingValue.isAbsent())
         Value = std::move(PreNarrowingValue);
       return E;
     }
@@ -5988,14 +5994,42 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From,
     for (unsigned I = 0; I < Notes.size(); ++I)
       S.Diag(Notes[I].first, Notes[I].second);
   } else {
-    S.Diag(From->getBeginLoc(), diag::err_expr_not_cce)
-        << CCE << From->getSourceRange();
+    S.Diag(E->getBeginLoc(), diag::err_expr_not_cce)
+        << CCE << E->getSourceRange();
     for (unsigned I = 0; I < Notes.size(); ++I)
       S.Diag(Notes[I].first, Notes[I].second);
   }
   return ExprError();
 }
 
+/// CheckConvertedConstantExpression - Check that the expression From is a
+/// converted constant expression of type T, perform the conversion and produce
+/// the converted expression, per C++11 [expr.const]p3.
+static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From,
+                                                   QualType T, APValue &Value,
+                                                   Sema::CCEKind CCE,
+                                                   bool RequireInt,
+                                                   NamedDecl *Dest) {
+
+  APValue PreNarrowingValue;
+  ExprResult Result = BuildConvertedConstantExpression(S, From, T, CCE, Dest,
+                                                       PreNarrowingValue);
+  if (Result.isInvalid() || Result.get()->isValueDependent()) {
+    Value = APValue();
+    return Result;
+  }
+  return EvaluateConvertedConstantExpression(S, Result.get(), T, Value, CCE,
+                                             RequireInt, PreNarrowingValue);
+}
+
+ExprResult Sema::BuildConvertedConstantExpression(Expr *From, QualType T,
+                                                  CCEKind CCE,
+                                                  NamedDecl *Dest) {
+  APValue PreNarrowingValue;
+  return ::BuildConvertedConstantExpression(*this, From, T, CCE, Dest,
+                                            PreNarrowingValue);
+}
+
 ExprResult Sema::CheckConvertedConstantExpression(Expr *From, QualType T,
                                                   APValue &Value, CCEKind CCE,
                                                   NamedDecl *Dest) {

diff  --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 9e5f85b0f9166b..f78d46f5950360 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -1429,11 +1429,14 @@ Decl *TemplateDeclInstantiator::VisitStaticAssertDecl(StaticAssertDecl *D) {
   if (InstantiatedAssertExpr.isInvalid())
     return nullptr;
 
-  return SemaRef.BuildStaticAssertDeclaration(D->getLocation(),
-                                              InstantiatedAssertExpr.get(),
-                                              D->getMessage(),
-                                              D->getRParenLoc(),
-                                              D->isFailed());
+  ExprResult InstantiatedMessageExpr =
+      SemaRef.SubstExpr(D->getMessage(), TemplateArgs);
+  if (InstantiatedMessageExpr.isInvalid())
+    return nullptr;
+
+  return SemaRef.BuildStaticAssertDeclaration(
+      D->getLocation(), InstantiatedAssertExpr.get(),
+      InstantiatedMessageExpr.get(), D->getRParenLoc(), D->isFailed());
 }
 
 Decl *TemplateDeclInstantiator::VisitEnumDecl(EnumDecl *D) {

diff  --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp
index 5c5408c29f879d..94b26773b36e2f 100644
--- a/clang/test/Lexer/cxx-features.cpp
+++ b/clang/test/Lexer/cxx-features.cpp
@@ -169,10 +169,6 @@
 #error "wrong value for __cpp_if_constexpr"
 #endif
 
-// range_based_for checked below
-
-// static_assert checked below
-
 #if check(deduction_guides, 0, 0, 0, 201703, 201703, 201703, 201703)
 // FIXME: 201907 in C++20
 #error "wrong value for __cpp_deduction_guides"
@@ -308,7 +304,7 @@
 #error "wrong value for __cpp_range_based_for"
 #endif
 
-#if check(static_assert, 0, 200410, 200410, 201411, 201411, 201411, 201411)
+#if check(static_assert, 0, 200410, 200410, 201411, 201411, 201411, 202306)
 #error "wrong value for __cpp_static_assert"
 #endif
 

diff  --git a/clang/test/SemaCXX/static-assert-cxx26.cpp b/clang/test/SemaCXX/static-assert-cxx26.cpp
new file mode 100644
index 00000000000000..9eddc4830e080e
--- /dev/null
+++ b/clang/test/SemaCXX/static-assert-cxx26.cpp
@@ -0,0 +1,291 @@
+// RUN: %clang_cc1 -std=c++2c -fsyntax-only %s -verify
+
+static_assert(true, "");
+static_assert(true, 0); // expected-error {{the message in a static assertion must be a string literal or an object with 'data()' and 'size()' member functions}}
+struct Empty{};
+static_assert(true, Empty{}); // expected-error {{the message object in this static assertion is missing 'data()' and 'size()' member functions}}
+struct NoData {
+    unsigned long size() const;
+};
+struct NoSize {
+    const char* data() const;
+};
+static_assert(true, NoData{}); // expected-error {{the message object in this static assertion is missing a 'data()' member function}}
+static_assert(true, NoSize{}); // expected-error {{the message object in this static assertion is missing a 'size()' member function}}
+
+struct InvalidSize {
+    const char* size() const;
+    const char* data() const;
+};
+static_assert(true, InvalidSize{}); // expected-error {{the message in a static assertion must have a 'size()' member function returning an object convertible to 'std::size_t'}} \
+                                    // expected-error {{value of type 'const char *' is not implicitly convertible to 'unsigned long'}}
+struct InvalidData {
+    unsigned long size() const;
+    unsigned long data() const;
+};
+static_assert(true, InvalidData{}); // expected-error {{the message in a static assertion must have a 'data()' member function returning an object convertible to 'const char *'}} \
+                                    // expected-error {{value of type 'unsigned long' is not implicitly convertible to 'const char *'}}
+
+struct NonConstexprSize {
+    unsigned long size() const; // expected-note 2{{declared here}}
+    constexpr const char* data() const;
+};
+
+static_assert(true, NonConstexprSize{}); // expected-error {{the message in this static assertion is not a constant expression}} \
+                                         // expected-note  {{non-constexpr function 'size' cannot be used in a constant expression}}
+
+static_assert(false, NonConstexprSize{}); // expected-error {{the message in a static assertion must be produced by a constant expression}} \
+                                          // expected-error {{static assertion failed}} \
+                                          // expected-note  {{non-constexpr function 'size' cannot be used in a constant expression}}
+
+struct NonConstexprData {
+    constexpr unsigned long size() const {
+        return 32;
+    }
+    const char* data() const;  // expected-note 2{{declared here}}
+};
+
+static_assert(true, NonConstexprData{});  // expected-error {{the message in this static assertion is not a constant expression}} \
+                                          // expected-note  {{non-constexpr function 'data' cannot be used in a constant expression}}
+
+static_assert(false, NonConstexprData{}); // expected-error {{the message in a static assertion must be produced by a constant expression}} \
+                                          // expected-error {{static assertion failed}} \
+                                          // expected-note  {{non-constexpr function 'data' cannot be used in a constant expression}}
+
+struct string_view {
+    int S;
+    const char* D;
+    constexpr string_view(const char* Str) : S(__builtin_strlen(Str)), D(Str) {}
+    constexpr string_view(int Size, const char* Str) : S(Size), D(Str) {}
+    constexpr int size() const {
+        return S;
+    }
+    constexpr const char* data() const {
+        return D;
+    }
+};
+
+constexpr const char g_[] = "long string";
+
+template <typename T, int S>
+struct array {
+    constexpr unsigned long size() const {
+        return S;
+    }
+    constexpr const char* data() const {
+        return d_;
+    }
+    const char d_[S];
+};
+
+static_assert(false, string_view("test")); // expected-error {{static assertion failed: test}}
+static_assert(false, string_view("😀")); // expected-error {{static assertion failed: 😀}}
+static_assert(false, string_view(0, nullptr)); // expected-error {{static assertion failed:}}
+static_assert(false, string_view(1, "ABC")); // expected-error {{static assertion failed: A}}
+static_assert(false, string_view(42, "ABC")); // expected-error {{static assertion failed: ABC}} \
+                                              // expected-error {{the message in a static assertion must be produced by a constant expression}} \
+                                              // expected-note {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}}
+static_assert(false, array<char, 2>{'a', 'b'}); // expected-error {{static assertion failed: ab}}
+
+
+
+struct ConvertibleToInt {
+    constexpr operator int() {
+        return 4;
+    }
+};
+struct ConvertibleToCharPtr {
+    constexpr operator const char*() {
+        return "test";
+    }
+};
+struct MessageFromConvertible {
+    constexpr ConvertibleToInt size() const {
+        return {};
+    }
+    constexpr ConvertibleToCharPtr data() const {
+        return {};
+    }
+};
+
+static_assert(true, MessageFromConvertible{});
+static_assert(false, MessageFromConvertible{}); // expected-error{{static assertion failed: test}}
+
+
+
+struct Leaks {
+    constexpr unsigned long size() const {
+        return 2;
+    }
+    constexpr const char* data() const {
+        return new char[2]{'u', 'b'}; // expected-note {{allocation performed here was not deallocated}}
+    }
+};
+
+static_assert(false, Leaks{}); //expected-error {{the message in a static assertion must be produced by a constant expression}} \
+                              // expected-error {{static assertion failed: ub}}
+
+struct RAII {
+    const char* d = new char[2]{'o', 'k'};
+    constexpr unsigned long size() const {
+        return 2;
+    }
+
+    constexpr const char* data() const {
+        return d;
+    }
+
+    constexpr ~RAII() {
+        delete[] d;
+    }
+};
+static_assert(false, RAII{}); // expected-error {{static assertion failed: ok}}
+
+namespace MoreTemporary {
+
+struct Data{
+constexpr operator const char*() const {
+    return d;
+}
+char d[6] = { "Hello" };
+};
+
+struct Size {
+     constexpr operator int() const {
+        return 5;
+    }
+};
+
+struct Message {
+    constexpr auto size() const {
+        return Size{};
+    }
+    constexpr auto data() const {
+        return Data{};
+    }
+};
+
+static_assert(false, Message{}); // expected-error {{static assertion failed: Hello}}
+
+}
+
+struct MessageInvalidSize {
+    constexpr auto size(int) const; // expected-note {{candidate function not viable: requires 1 argument, but 0 were provided}}
+    constexpr auto data() const;
+};
+struct MessageInvalidData {
+    constexpr auto size() const;
+    constexpr auto data(int) const; // expected-note {{candidate function not viable: requires 1 argument, but 0 were provided}}
+};
+
+static_assert(false, MessageInvalidSize{});  // expected-error {{static assertion failed}} \
+                                             // expected-error {{the message in a static assertion must have a 'size()' member function returning an object convertible to 'std::size_t'}}
+static_assert(false, MessageInvalidData{});  // expected-error {{static assertion failed}} \
+                                             // expected-error {{the message in a static assertion must have a 'data()' member function returning an object convertible to 'const char *'}}
+
+struct NonConstMembers {
+    constexpr int size() {
+        return 1;
+    }
+    constexpr const char* data() {
+        return "A";
+    }
+};
+
+static_assert(false, NonConstMembers{}); // expected-error {{static assertion failed: A}}
+
+struct DefaultArgs {
+    constexpr int size(int i = 0) {
+        return 2;
+    }
+    constexpr const char* data(int i =0, int j = 42) {
+        return "OK";
+    }
+};
+
+static_assert(false, DefaultArgs{}); // expected-error {{static assertion failed: OK}}
+
+struct Variadic {
+    constexpr int size(auto...) {
+        return 2;
+    }
+    constexpr const char* data(auto...) {
+        return "OK";
+    }
+};
+
+static_assert(false, Variadic{}); // expected-error {{static assertion failed: OK}}
+
+template <typename T>
+struct DeleteAndRequires {
+    constexpr int size() = delete; // expected-note {{candidate function has been explicitly deleted}}
+    constexpr const char* data() requires false; // expected-note {{candidate function not viable: constraints not satisfied}} \
+                                                 // expected-note {{because 'false' evaluated to false}}
+};
+static_assert(false, DeleteAndRequires<void>{});
+// expected-error at -1 {{static assertion failed}} \
+// expected-error at -1 {{the message in a static assertion must have a 'size()' member function returning an object convertible to 'std::size_t'}}\
+// expected-error at -1 {{the message in a static assertion must have a 'data()' member function returning an object convertible to 'const char *'}}
+
+class Private {
+    constexpr int size(int i = 0) { // expected-note {{implicitly declared private here}}
+        return 2;
+    }
+    constexpr const char* data(int i =0, int j = 42) { // expected-note {{implicitly declared private here}}
+        return "OK";
+    }
+};
+
+static_assert(false, Private{}); // expected-error {{'data' is a private member of 'Private'}}\
+                                 // expected-error {{'size' is a private member of 'Private'}}\
+                                 // expected-error {{static assertion failed: OK}}
+
+struct MessageOverload {
+    constexpr int size() {
+        return 1;
+    }
+    constexpr int size() const;
+
+    constexpr const char* data() {
+        return "A";
+    }
+    constexpr const char* data() const;
+};
+
+static_assert(false, MessageOverload{}); // expected-error {{static assertion failed: A}}
+
+struct InvalidPtr {
+    consteval auto size() {
+        return 42;
+    }
+    consteval const char *data() {
+    const char *ptr; // Garbage
+    return ptr; // expected-note {{read of uninitialized object is not allowed in a constant expression}}
+    }
+};
+
+static_assert(false, InvalidPtr{}); // expected-error{{the message in a static assertion must be produced by a constant expression}} \
+                           //expected-error {{static assertion failed}} \
+                           // expected-note {{in call to 'InvalidPtr{}.data()'}}
+
+namespace DependentMessage {
+template <typename Ty>
+struct Good {
+  static_assert(false, Ty{}); // expected-error {{static assertion failed: hello}}
+};
+
+template <typename Ty>
+struct Bad {
+  static_assert(false, Ty{}); // expected-error {{the message in a static assertion must be a string literal or an object with 'data()' and 'size()' member functions}} \
+                              // expected-error {{static assertion failed}}
+};
+
+struct Frobble {
+  constexpr int size() const { return 5; }
+  constexpr const char *data() const { return "hello"; }
+};
+
+Good<Frobble> a; // expected-note {{in instantiation}}
+Bad<int> b; // expected-note {{in instantiation}}
+
+}

diff  --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index ec309fa2caaabf..0c629fe336fad8 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -1294,7 +1294,7 @@ bool CursorVisitor::VisitUnresolvedUsingTypenameDecl(
 bool CursorVisitor::VisitStaticAssertDecl(StaticAssertDecl *D) {
   if (Visit(MakeCXCursor(D->getAssertExpr(), StmtParent, TU, RegionOfInterest)))
     return true;
-  if (StringLiteral *Message = D->getMessage())
+  if (auto *Message = dyn_cast<StringLiteral>(D->getMessage()))
     if (Visit(MakeCXCursor(Message, StmtParent, TU, RegionOfInterest)))
       return true;
   return false;

diff  --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index e27c0c7ed31752..23dc53b5a3cbc3 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -145,7 +145,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
  <tr>
   <td>User-generated <tt>static_assert</tt> messages</td>
   <td><a href="https://wg21.link/P2741R3">P2741R3</a></td>
-  <td class="none" align="center">No</td>
+  <td class="unreleased" align="center">Clang 17</td>
  </tr>
  <tr>
   <td>Placeholder variables with no name</td>


        


More information about the cfe-commits mailing list