[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