cor3ntin updated this revision to Diff 538154.
cor3ntin added a comment.

Rebase.
This is now ready for review.

Note that after discussion with CWG, the consensus seems to be that the wording 
is fine, an implementation has to behave

As if the full expression Message.data()[I] is called for each character.
Obviously this is not a viable implementation strategy so we materialize
intermediate expressions.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D154290/new/

https://reviews.llvm.org/D154290

Files:
  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/test/SemaCXX/static-assert-cxx26.cpp
  clang/tools/libclang/CIndex.cpp
  clang/www/cxx_status.html

Index: clang/www/cxx_status.html
===================================================================
--- clang/www/cxx_status.html
+++ clang/www/cxx_status.html
@@ -145,7 +145,7 @@
  <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>
Index: clang/tools/libclang/CIndex.cpp
===================================================================
--- clang/tools/libclang/CIndex.cpp
+++ clang/tools/libclang/CIndex.cpp
@@ -1294,7 +1294,7 @@
 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;
Index: clang/test/SemaCXX/static-assert-cxx26.cpp
===================================================================
--- /dev/null
+++ clang/test/SemaCXX/static-assert-cxx26.cpp
@@ -0,0 +1,127 @@
+// RUN: %clang_cc1 -std=c++2c -fsyntax-only %s -verify
+
+static_assert(true, "");
+static_assert(true, 0); // expected-error {{the message in a static_assert declaration must be a string literal or an object with data() and size() member functions}}
+struct Empty{};
+static_assert(true, Empty{}); // expected-error {{the message in a static_assert declaration must be a string literal or an object with 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 in a static_assert declaration must be a string literal or an object with data() and size() member functions}}
+static_assert(true, NoSize{}); // expected-error {{the message in a static_assert declaration must be a string literal or an object with data() and size() member functions}}
+
+struct InvalidSize {
+    const char* size() const;
+    const char* data() const;
+};
+static_assert(true, InvalidSize{}); // expected-error {{the message in a static_assert declaration 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_assert declaration 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 {{declared here}}
+    constexpr const char* data() const;
+};
+
+static_assert(true, NonConstexprSize{});
+static_assert(false, NonConstexprSize{}); // expected-error {{the message in a static_assert declaration must be produced by 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 {{declared here}}
+};
+
+static_assert(true, NonConstexprData{});
+static_assert(false, NonConstexprData{}); // expected-error {{the message in a static_assert declaration must be produced by 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_assert declaration must be produced by 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 {
+    operator int();
+};
+struct ConvertibleToCharPtr {
+    operator const char*();
+};
+struct MessageFromConvertible {
+    ConvertibleToInt size() const;
+    ConvertibleToCharPtr data() const;
+};
+static_assert(true, MessageFromConvertible{});
+
+
+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_assert declaration must be produced by 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}}
Index: clang/test/Lexer/cxx-features.cpp
===================================================================
--- clang/test/Lexer/cxx-features.cpp
+++ 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
 
Index: clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
===================================================================
--- clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -1429,11 +1429,14 @@
   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) {
Index: clang/lib/Sema/SemaOverload.cpp
===================================================================
--- clang/lib/Sema/SemaOverload.cpp
+++ clang/lib/Sema/SemaOverload.cpp
@@ -5817,14 +5817,14 @@
   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 @@
 
   // 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 @@
         << 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 @@
   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 @@
     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 @@
     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) {
Index: clang/lib/Sema/SemaDeclCXX.cpp
===================================================================
--- clang/lib/Sema/SemaDeclCXX.cpp
+++ clang/lib/Sema/SemaDeclCXX.cpp
@@ -16742,14 +16742,11 @@
                                          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
@@ -16884,13 +16881,113 @@
   }
 }
 
+bool Sema::EvaluateStaticAssertMessageAsString(Expr *Message,
+                                               std::string &Result,
+                                               ASTContext &Ctx,
+                                               bool CheckOnly) {
+  SourceLocation Loc = Message->getBeginLoc();
+  assert(Message);
+  assert(!Message->isTypeDependent() && !Message->isValueDependent() &&
+         "can't evaluate a dependant static assert message");
+
+  if (const StringLiteral *SL = dyn_cast<StringLiteral>(Message);
+      SL && SL->getCharByteWidth() == 1) {
+    Result.assign(SL->getString().begin(), SL->getString().end());
+    return true;
+  }
+
+  QualType T = Message->getType().getNonReferenceType();
+  auto *RD = T->getAsCXXRecordDecl();
+  if (!RD) {
+    Diag(Message->getBeginLoc(), diag::err_static_assert_invalid_message);
+    return false;
+  }
+
+  auto FindMember = [&](StringRef Member) -> std::optional<LookupResult> {
+    DeclarationName DN = PP.getIdentifierInfo(Member);
+    LookupResult MemberLookup(*this, DN, Loc, Sema::LookupMemberName);
+    LookupQualifiedName(MemberLookup, RD);
+    if (MemberLookup.isAmbiguous())
+      return std::nullopt;
+    for (NamedDecl *D : MemberLookup) {
+      if (FunctionDecl *FD = dyn_cast<FunctionDecl>(D->getUnderlyingDecl());
+          FD && FD->getMinRequiredArguments() == 0 &&
+          FD->getType()->castAs<FunctionProtoType>()->isConst()) {
+        return MemberLookup;
+        break;
+      }
+    }
+    return std::nullopt;
+  };
+
+  std::optional<LookupResult> SizeMember = FindMember("size");
+  std::optional<LookupResult> DataMember = FindMember("data");
+  if (!SizeMember || !DataMember) {
+    Diag(Message->getBeginLoc(), diag::err_static_assert_invalid_message);
+    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 Res;
+  };
+
+  ExprResult SizeE = BuildExpr(*SizeMember);
+  ExprResult DataE = BuildExpr(*DataMember);
+
+  QualType SizeT = Context.getSizeType();
+  QualType ConstCharPtr =
+      Context.getPointerType(Context.getConstType(Context.CharTy));
+
+  ExprResult EvaluatedSize = BuildConvertedConstantExpression(
+      SizeE.get(), SizeT, CCEK_StaticAssertMessageSize);
+  if (EvaluatedSize.isInvalid()) {
+    Diag(Message->getBeginLoc(), diag::err_static_assert_invalid_size);
+    return false;
+  }
+
+  ExprResult EvaluatedData = BuildConvertedConstantExpression(
+      DataE.get(), ConstCharPtr, CCEK_StaticAssertMessageSize);
+  if (EvaluatedData.isInvalid()) {
+    Diag(Message->getBeginLoc(), diag::err_static_assert_invalid_data);
+    return false;
+  }
+
+  if (CheckOnly)
+    return true;
+
+  Expr::EvalResult Status;
+  SmallVector<PartialDiagnosticAt, 8> Notes;
+  Status.Diag = &Notes;
+  if (!Message->EvaluateCharPointerAsString(Result, EvaluatedSize.get(),
+                                            EvaluatedData.get(), Ctx, Status) ||
+      !Notes.empty()) {
+    Diag(Message->getBeginLoc(), diag::err_static_assert_message_constexpr);
+    for (unsigned I = 0; I < Notes.size(); ++I)
+      Diag(Notes[I].first, Notes[I].second);
+    return false;
+  }
+  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.
@@ -16918,6 +17015,12 @@
       FoldKind = AllowFold;
     }
 
+    if (!Failed && AssertMessage) {
+      std::string Str;
+      EvaluateStaticAssertMessageAsString(AssertMessage, Str, Context,
+                                          /*CheckOnly=*/true);
+    }
+
     if (!Failed && VerifyIntegerConstantExpression(
                        BaseExpr, &Cond,
                        diag::err_static_assert_expression_is_not_constant,
@@ -16931,14 +17034,16 @@
         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, /*CheckOnly=*/false) ||
+                     !Str.empty();
+        Msg << Str;
       }
-
       Expr *InnerCond = nullptr;
       std::string InnerCondDescription;
       std::tie(InnerCond, InnerCondDescription) =
@@ -16947,7 +17052,7 @@
         // 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);
@@ -16955,12 +17060,12 @@
                            && !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;
Index: clang/lib/Parse/ParseDeclCXX.cpp
===================================================================
--- clang/lib/Parse/ParseDeclCXX.cpp
+++ clang/lib/Parse/ParseDeclCXX.cpp
@@ -1016,14 +1016,17 @@
       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;
Index: clang/lib/Frontend/InitPreprocessor.cpp
===================================================================
--- clang/lib/Frontend/InitPreprocessor.cpp
+++ clang/lib/Frontend/InitPreprocessor.cpp
@@ -620,8 +620,10 @@
     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");
Index: clang/lib/AST/ODRDiagsEmitter.cpp
===================================================================
--- clang/lib/AST/ODRDiagsEmitter.cpp
+++ clang/lib/AST/ODRDiagsEmitter.cpp
@@ -994,40 +994,43 @@
       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;
   }
Index: clang/lib/AST/ExprConstant.cpp
===================================================================
--- clang/lib/AST/ExprConstant.cpp
+++ 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 @@
 
   // ... 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 @@
       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,47 @@
   }
 }
 
+bool Expr::EvaluateCharPointerAsString(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) ||
+        !Char.isInt())
+      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);
Index: clang/lib/AST/DeclPrinter.cpp
===================================================================
--- clang/lib/AST/DeclPrinter.cpp
+++ clang/lib/AST/DeclPrinter.cpp
@@ -949,7 +949,8 @@
   Out << "static_assert(";
   D->getAssertExpr()->printPretty(Out, nullptr, Policy, Indentation, "\n",
                                   &Context);
-  if (StringLiteral *SL = D->getMessage()) {
+  if (StringLiteral *SL =
+          llvm::dyn_cast_if_present<StringLiteral>(D->getMessage())) {
     Out << ", ";
     SL->printPretty(Out, nullptr, Policy, Indentation, "\n", &Context);
   }
Index: clang/lib/AST/DeclCXX.cpp
===================================================================
--- clang/lib/AST/DeclCXX.cpp
+++ clang/lib/AST/DeclCXX.cpp
@@ -3237,8 +3237,7 @@
 
 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,
Index: clang/include/clang/Sema/Sema.h
===================================================================
--- clang/include/clang/Sema/Sema.h
+++ clang/include/clang/Sema/Sema.h
@@ -3877,8 +3877,17 @@
     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,
@@ -7785,15 +7794,15 @@
   void UnmarkAsLateParsedTemplate(FunctionDecl *FD);
   bool IsInsideALocalClassWithinATemplateFunction();
 
+  bool EvaluateStaticAssertMessageAsString(Expr *Message, std::string &Result,
+                                           ASTContext &Ctx, bool CheckOnly);
   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,
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -83,7 +83,7 @@
   "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 "
+  "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|"
@@ -1542,6 +1542,14 @@
   "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_assert "
+  "declaration must be a string literal or an object with data() and size() member functions">;
+def err_static_assert_invalid_size : Error<"the message in a static_assert declaration "
+  "must have a size() member function returning an object convertible to std::size_t">;
+def err_static_assert_invalid_data : Error<"the message in a static_assert declaration "
+  "must have a data() member function returning an object convertible to const char*">;
+def err_static_assert_message_constexpr : Error<"the message in a static_assert declaration "
+  "must be produced by constant expression">;
 
 def warn_consteval_if_always_true : Warning<
   "consteval if is always true in an %select{unevaluated|immediate}0 context">,
Index: clang/include/clang/AST/Expr.h
===================================================================
--- clang/include/clang/AST/Expr.h
+++ clang/include/clang/AST/Expr.h
@@ -762,6 +762,11 @@
   /// strlen, false otherwise.
   bool tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const;
 
+  bool EvaluateCharPointerAsString(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 {
Index: clang/include/clang/AST/DeclCXX.h
===================================================================
--- clang/include/clang/AST/DeclCXX.h
+++ clang/include/clang/AST/DeclCXX.h
@@ -4010,12 +4010,12 @@
 /// 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 @@
 
   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(); }
 
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ clang/docs/ReleaseNotes.rst
@@ -137,6 +137,7 @@
 - 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
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to