cor3ntin updated this revision to Diff 538579.
cor3ntin marked 3 inline comments as done.
cor3ntin added a comment.

Address Sergei's feedback

- Add more tests
- Support non const member functions
- Make sure diagnostics messages are never produced twice
- Support returning intermediate objects from data()/size()


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,194 @@
+// 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}}
+
+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;
+    constexpr auto data() const;
+};
+struct MessageInvalidData {
+    constexpr auto size() const;
+    constexpr auto data(int) const;
+};
+
+static_assert(false, MessageInvalidSize{});  // expected-error {{static assertion failed}} \
+                                             // 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(false, MessageInvalidData{});  // expected-error {{static assertion failed}} \
+                                             // expected-error {{the message in a static_assert declaration must be a string literal or an object with data() and size() member functions}}
+
+struct NonConstMembers {
+    constexpr int size() {
+        return 1;
+    }
+    constexpr const char* data() {
+        return "A";
+    }
+};
+
+static_assert(false, NonConstMembers{}); // expected-error {{static assertion failed: A}}
+
+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}}
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,117 @@
   }
 }
 
+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 auto *SL = dyn_cast<StringLiteral>(Message)) {
+    assert(SL->isUnevaluated() && "expected an unevaluated string");
+    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 (const NamedDecl *D : MemberLookup) {
+      if (const auto *FD = dyn_cast<FunctionDecl>(D->getUnderlyingDecl());
+          FD && FD->getMinRequiredArguments() == 0) {
+        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 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(Message->getBeginLoc(), diag::err_static_assert_invalid_size);
+    return false;
+  }
+
+  ExprResult EvaluatedData =
+      DataE.isInvalid()
+          ? ExprError()
+          : BuildConvertedConstantExpression(DataE.get(), ConstCharPtr,
+                                             CCEK_StaticAssertMessageData);
+  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.
@@ -16924,6 +17025,14 @@
                        FoldKind).isInvalid())
       Failed = true;
 
+    // If the static_assert passes, only verify that
+    // the message is grammatically valid without evaluating it.
+    if (!Failed && AssertMessage && !!Cond) {
+      std::string Str;
+      EvaluateStaticAssertMessageAsString(AssertMessage, Str, Context,
+                                          /*CheckOnly=*/true);
+    }
+
     // CWG2518
     // [dcl.pre]/p10  If [...] the expression is evaluated in the context of a
     // template definition, the declaration has no effect.
@@ -16931,14 +17040,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 +17058,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 +17066,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