From 467688527017efb611b09c0cbf92f2d3ecdd1724 Mon Sep 17 00:00:00 2001
From: Corentin Jabot
Date: Mon, 8 May 2023 12:18:43 +0200
Subject: [PATCH] [clang] Implement P2564 "consteval must propagate up"
Reviewed By: aaron.ballman, #clang-language-wg
Differential Revision: https://reviews.llvm.org/D151094
---
clang/docs/ReleaseNotes.rst | 1 +
clang/include/clang/AST/Decl.h | 15 ++
clang/include/clang/AST/DeclBase.h | 8 +-
clang/include/clang/AST/Expr.h | 8 +
clang/include/clang/AST/ExprCXX.h | 8 +
clang/include/clang/AST/Stmt.h | 2 +
clang/include/clang/Basic/DiagnosticSemaKinds.td | 17 ++-
clang/include/clang/Sema/ScopeInfo.h | 6 +-
clang/include/clang/Sema/Sema.h | 24 ++-
clang/lib/AST/ASTImporter.cpp | 5 +-
clang/lib/AST/Decl.cpp | 39 +++++
clang/lib/AST/DeclPrinter.cpp | 2 +
clang/lib/AST/Expr.cpp | 2 +
clang/lib/AST/ExprCXX.cpp | 1 +
clang/lib/AST/ExprConstant.cpp | 15 +-
clang/lib/AST/JSONNodeDumper.cpp | 3 +
clang/lib/AST/TextNodeDumper.cpp | 6 +
clang/lib/AST/VTableBuilder.cpp | 2 +-
clang/lib/CodeGen/CodeGenModule.cpp | 9 +-
clang/lib/Frontend/InitPreprocessor.cpp | 2 +-
clang/lib/Parse/ParseDeclCXX.cpp | 1 +
clang/lib/Sema/ScopeInfo.cpp | 1 +
clang/lib/Sema/SemaDecl.cpp | 5 +-
clang/lib/Sema/SemaDeclCXX.cpp | 82 ++++++++++
clang/lib/Sema/SemaExpr.cpp | 141 ++++++++++++++---
clang/lib/Sema/SemaLambda.cpp | 4 +
clang/lib/Sema/SemaStmt.cpp | 6 +-
clang/lib/Sema/SemaTemplateDeduction.cpp | 33 ++++
clang/lib/Serialization/ASTReaderStmt.cpp | 16 +-
clang/lib/Serialization/ASTWriterDecl.cpp | 5 +-
clang/lib/Serialization/ASTWriterStmt.cpp | 5 +-
.../SemaCXX/cxx2a-consteval-default-params.cpp | 36 ++---
clang/test/SemaCXX/cxx2a-consteval.cpp | 10 +-
clang/test/SemaCXX/cxx2b-consteval-propagate.cpp | 166 +++++++++++++++++++++
clang/www/cxx_status.html | 2 +-
35 files changed, 600 insertions(+), 88 deletions(-)
create mode 100644 clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 295b3b9..ad8d1aa 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -126,6 +126,7 @@ C++23 Feature Support
functions. Which include allowing non-literal types as return values and parameters, allow calling of
non-constexpr functions and constructors.
- Clang now supports `requires cplusplus23` for module maps.
+- Implemented `P2564R3: consteval needs to propagate up `_.
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 37512cc..b9c3bb0 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2378,6 +2378,21 @@ public:
return getConstexprKind() == ConstexprSpecKind::Consteval;
}
+ void setBodyContainsImmediateEscalatingExpressions(bool Set) {
+ FunctionDeclBits.BodyContainsImmediateEscalatingExpression = Set;
+ }
+
+ bool BodyContainsImmediateEscalatingExpressions() const {
+ return FunctionDeclBits.BodyContainsImmediateEscalatingExpression;
+ }
+
+ bool isImmediateEscalating() const;
+
+ // The function is a C++ immediate function.
+ // This can be either a consteval function, or an immediate escalating
+ // function containing an immediate escalating expression.
+ bool isImmediateFunction() const;
+
/// Whether the instantiation of this function is pending.
/// This bit is set when the decision to instantiate this function is made
/// and unset if and when the function body is created. That leaves out
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index 511afb1..eea8a11 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -1659,6 +1659,8 @@ class DeclContext {
/// Kind of contexpr specifier as defined by ConstexprSpecKind.
uint64_t ConstexprKind : 2;
+ uint64_t BodyContainsImmediateEscalatingExpression : 1;
+
uint64_t InstantiationIsPending : 1;
/// Indicates if the function uses __try.
@@ -1693,7 +1695,7 @@ class DeclContext {
};
/// Number of non-inherited bits in FunctionDeclBitfields.
- enum { NumFunctionDeclBits = 29 };
+ enum { NumFunctionDeclBits = 30 };
/// Stores the bits used by CXXConstructorDecl. If modified
/// NumCXXConstructorDeclBits and the accessor
@@ -1705,12 +1707,12 @@ class DeclContext {
/// For the bits in FunctionDeclBitfields.
uint64_t : NumFunctionDeclBits;
- /// 22 bits to fit in the remaining available space.
+ /// 21 bits to fit in the remaining available space.
/// Note that this makes CXXConstructorDeclBitfields take
/// exactly 64 bits and thus the width of NumCtorInitializers
/// will need to be shrunk if some bit is added to NumDeclContextBitfields,
/// NumFunctionDeclBitfields or CXXConstructorDeclBitfields.
- uint64_t NumCtorInitializers : 19;
+ uint64_t NumCtorInitializers : 18;
uint64_t IsInheritingConstructor : 1;
/// Whether this constructor has a trail-allocated explicit specifier.
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 0536ee7..34aa636 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -1436,6 +1436,14 @@ public:
return DeclRefExprBits.RefersToEnclosingVariableOrCapture;
}
+ bool isImmediateEscalating() const {
+ return DeclRefExprBits.IsImmediateEscalating;
+ }
+
+ void setIsImmediateEscalating(bool Set) {
+ DeclRefExprBits.IsImmediateEscalating = Set;
+ }
+
static bool classof(const Stmt *T) {
return T->getStmtClass() == DeclRefExprClass;
}
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 16f54d7..f5e8052 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -1681,6 +1681,14 @@ public:
getArgs()[Arg] = ArgExpr;
}
+ bool isImmediateEscalating() const {
+ return CXXConstructExprBits.IsImmediateEscalating;
+ }
+
+ void setIsImmediateEscalating(bool Set) {
+ CXXConstructExprBits.IsImmediateEscalating = Set;
+ }
+
SourceLocation getBeginLoc() const LLVM_READONLY;
SourceLocation getEndLoc() const LLVM_READONLY;
SourceRange getParenOrBraceRange() const { return ParenOrBraceRange; }
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 7e72e44..9a77f87 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -384,6 +384,7 @@ protected:
unsigned HadMultipleCandidates : 1;
unsigned RefersToEnclosingVariableOrCapture : 1;
unsigned NonOdrUseReason : 2;
+ unsigned IsImmediateEscalating : 1;
/// The location of the declaration name itself.
SourceLocation Loc;
@@ -827,6 +828,7 @@ protected:
unsigned StdInitListInitialization : 1;
unsigned ZeroInitialization : 1;
unsigned ConstructionKind : 3;
+ unsigned IsImmediateEscalating : 1;
SourceLocation Loc;
};
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e03e27d..1d4743a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2656,10 +2656,23 @@ def warn_cxx14_compat_constexpr_not_const : Warning<
"in C++14; add 'const' to avoid a change in behavior">,
InGroup>;
def err_invalid_consteval_take_address : Error<
- "cannot take address of consteval %select{function|call operator of}1 %0 outside"
+ "cannot take address of %select{immediate|consteval}2 "
+ "%select{function|call operator of}1 %0 outside"
" of an immediate invocation">;
def err_invalid_consteval_call : Error<
- "call to consteval function %q0 is not a constant expression">;
+ "call to %select{immediate|consteval}1 function "
+ "%q0 is not a constant expression">;
+
+def err_immediate_function_used_before_definition : Error<
+ "immediate function %0 used before it is defined">;
+
+def note_immediate_function_reason : Note<
+ "%0 is an immediate function because its body "
+ "%select{evaluates the address of %select{an immediate|a consteval}2 "
+ "function %1|contains a call to %select{an immediate|a consteval}2 "
+ "%select{function|constructor}4 %1 and that call is not a constant "
+ "expression}3">;
+
def note_invalid_consteval_initializer : Note<
"in the default initalizer of %0">;
def note_invalid_consteval_initializer_here : Note<
diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h
index 5888dee0..26c0387 100644
--- a/clang/include/clang/Sema/ScopeInfo.h
+++ b/clang/include/clang/Sema/ScopeInfo.h
@@ -172,6 +172,9 @@ public:
/// in the function. One of co_return, co_await, or co_yield.
unsigned char FirstCoroutineStmtKind : 2;
+ /// Whether we found an immediate-escalating expression.
+ bool FoundImmediateEscalatingExpression : 1;
+
/// First coroutine statement in the current function.
/// (ex co_return, co_await, co_yield)
SourceLocation FirstCoroutineStmtLoc;
@@ -388,7 +391,8 @@ public:
HasPotentialAvailabilityViolations(false), ObjCShouldCallSuper(false),
ObjCIsDesignatedInit(false), ObjCWarnForNoDesignatedInitChain(false),
ObjCIsSecondaryInit(false), ObjCWarnForNoInitDelegation(false),
- NeedsCoroutineSuspends(true), ErrorTrap(Diag) {}
+ NeedsCoroutineSuspends(true), FoundImmediateEscalatingExpression(false),
+ ErrorTrap(Diag) {}
virtual ~FunctionScopeInfo();
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a158469..7855921 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1066,9 +1066,13 @@ public:
S.PushFunctionScope();
S.PushExpressionEvaluationContext(
Sema::ExpressionEvaluationContext::PotentiallyEvaluated);
- if (auto *FD = dyn_cast(DC))
+ if (auto *FD = dyn_cast(DC)) {
FD->setWillHaveBody(true);
- else
+ S.ExprEvalContexts.back().InImmediateFunctionContext =
+ FD->isImmediateFunction();
+ S.ExprEvalContexts.back().InImmediateEscalatingFunctionContext =
+ S.getLangOpts().CPlusPlus20 && FD->isImmediateEscalating();
+ } else
assert(isa(DC));
}
@@ -1247,7 +1251,7 @@ public:
/// In addition of being constant evaluated, the current expression
/// occurs in an immediate function context - either a consteval function
- /// or a consteval if function.
+ /// or a consteval if statement.
ImmediateFunctionContext,
/// The current expression is potentially evaluated at run time,
@@ -1328,6 +1332,7 @@ public:
// an immediate function context, so they need to be tracked independently.
bool InDiscardedStatement;
bool InImmediateFunctionContext;
+ bool InImmediateEscalatingFunctionContext;
bool IsCurrentlyCheckingDefaultArgumentOrInitializer = false;
@@ -1356,7 +1361,8 @@ public:
: Context(Context), ParentCleanup(ParentCleanup),
NumCleanupObjects(NumCleanupObjects), NumTypos(0),
ManglingContextDecl(ManglingContextDecl), ExprContext(ExprContext),
- InDiscardedStatement(false), InImmediateFunctionContext(false) {}
+ InDiscardedStatement(false), InImmediateFunctionContext(false),
+ InImmediateEscalatingFunctionContext(false) {}
bool isUnevaluated() const {
return Context == ExpressionEvaluationContext::Unevaluated ||
@@ -6546,6 +6552,13 @@ public:
/// invocation.
ExprResult CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl);
+ bool CheckImmediateEscalatingFunctionDefinition(
+ FunctionDecl *FD, bool HasImmediateEscalatingExpression);
+
+ void MarkExpressionAsImmediateEscalating(Expr *E);
+
+ void DiagnoseImmediateEscalatingReason(const clang::FunctionDecl *FD);
+
bool CompleteConstructorCall(CXXConstructorDecl *Constructor,
QualType DeclInitType, MultiExprArg ArgsPtr,
SourceLocation Loc,
@@ -9159,6 +9172,9 @@ public:
bool DeduceReturnType(FunctionDecl *FD, SourceLocation Loc,
bool Diagnose = true);
+ bool CheckIfFunctionSpecializationIsImmediate(FunctionDecl *FD,
+ SourceLocation Loc);
+
/// Declare implicit deduction guides for a class template if we've
/// not already done so.
void DeclareImplicitDeductionGuides(TemplateDecl *Template,
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 46f4a2b..5e1ddad 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -7126,6 +7126,7 @@ ExpectedStmt ASTNodeImporter::VisitDeclRefExpr(DeclRefExpr *E) {
E->getValueKind(), ToFoundD, ToResInfo, E->isNonOdrUse());
if (E->hadMultipleCandidates())
ToE->setHadMultipleCandidates(true);
+ ToE->setIsImmediateEscalating(E->isImmediateEscalating());
return ToE;
}
@@ -7943,12 +7944,14 @@ ExpectedStmt ASTNodeImporter::VisitCXXConstructExpr(CXXConstructExpr *E) {
if (Error Err = ImportContainerChecked(E->arguments(), ToArgs))
return std::move(Err);
- return CXXConstructExpr::Create(
+ CXXConstructExpr *ToE = CXXConstructExpr::Create(
Importer.getToContext(), ToType, ToLocation, ToConstructor,
E->isElidable(), ToArgs, E->hadMultipleCandidates(),
E->isListInitialization(), E->isStdInitListInitialization(),
E->requiresZeroInitialization(), E->getConstructionKind(),
ToParenOrBraceRange);
+ ToE->setIsImmediateEscalating(E->isImmediateEscalating());
+ return ToE;
}
ExpectedStmt ASTNodeImporter::VisitExprWithCleanups(ExprWithCleanups *E) {
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 85d8f11..517df05 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3002,6 +3002,7 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC,
FunctionDeclBits.HasImplicitReturnZero = false;
FunctionDeclBits.IsLateTemplateParsed = false;
FunctionDeclBits.ConstexprKind = static_cast(ConstexprKind);
+ FunctionDeclBits.BodyContainsImmediateEscalatingExpression = false;
FunctionDeclBits.InstantiationIsPending = false;
FunctionDeclBits.UsesSEHTry = false;
FunctionDeclBits.UsesFPIntrin = UsesFPIntrin;
@@ -3167,6 +3168,44 @@ static bool isNamed(const NamedDecl *ND, const char (&Str)[Len]) {
return II && II->isStr(Str);
}
+bool FunctionDecl::isImmediateEscalating() const {
+ // C++23 [expr.const]/p17
+ // An immediate-escalating function is
+ // - the call operator of a lambda that is not declared with the consteval
+ // specifier,
+ if (isLambdaCallOperator(this) && !isConsteval())
+ return true;
+ // - a defaulted special member function that is not declared with the
+ // consteval specifier,
+ if (isDefaulted() && !isConsteval())
+ return true;
+ // - a function that results from the instantiation of a templated entity
+ // defined with the constexpr specifier.
+ TemplatedKind TK = getTemplatedKind();
+ if (TK != TK_NonTemplate && TK != TK_DependentNonTemplate &&
+ isConstexprSpecified())
+ return true;
+ return false;
+}
+
+bool FunctionDecl::isImmediateFunction() const {
+ // C++23 [expr.const]/p18
+ // An immediate function is a function or constructor that is
+ // - declared with the consteval specifier
+ if (isConsteval())
+ return true;
+ // - an immediate-escalating function F whose function body contains an
+ // immediate-escalating expression
+ if (isImmediateEscalating() && BodyContainsImmediateEscalatingExpressions())
+ return true;
+
+ if (const auto *MD = dyn_cast(this);
+ MD && MD->isLambdaStaticInvoker())
+ return MD->getParent()->getLambdaCallOperator()->isImmediateFunction();
+
+ return false;
+}
+
bool FunctionDecl::isMain() const {
const TranslationUnitDecl *tunit =
dyn_cast(getDeclContext()->getRedeclContext());
diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 932e9b2..4a285f3 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -622,6 +622,8 @@ void DeclPrinter::VisitFunctionDecl(FunctionDecl *D) {
if (D->isConstexprSpecified() && !D->isExplicitlyDefaulted())
Out << "constexpr ";
if (D->isConsteval()) Out << "consteval ";
+ else if (D->isImmediateFunction())
+ Out << "immediate ";
ExplicitSpecifier ExplicitSpec = ExplicitSpecifier::getFromDecl(D);
if (ExplicitSpec.isSpecified())
printExplicitSpecifier(ExplicitSpec, Out, Policy, Indentation, Context);
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 0de44f3..1fbb76a 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -492,6 +492,7 @@ DeclRefExpr::DeclRefExpr(const ASTContext &Ctx, ValueDecl *D,
DeclRefExprBits.RefersToEnclosingVariableOrCapture =
RefersToEnclosingVariableOrCapture;
DeclRefExprBits.NonOdrUseReason = NOUR;
+ DeclRefExprBits.IsImmediateEscalating = false;
DeclRefExprBits.Loc = L;
setDependence(computeDependence(this, Ctx));
}
@@ -529,6 +530,7 @@ DeclRefExpr::DeclRefExpr(const ASTContext &Ctx,
getTrailingObjects()->initializeFrom(
TemplateKWLoc);
}
+ DeclRefExprBits.IsImmediateEscalating = false;
DeclRefExprBits.HadMultipleCandidates = 0;
setDependence(computeDependence(this, Ctx));
}
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 9e1c39f..09e64b8 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -1136,6 +1136,7 @@ CXXConstructExpr::CXXConstructExpr(
CXXConstructExprBits.StdInitListInitialization = StdInitListInitialization;
CXXConstructExprBits.ZeroInitialization = ZeroInitialization;
CXXConstructExprBits.ConstructionKind = ConstructKind;
+ CXXConstructExprBits.IsImmediateEscalating = false;
CXXConstructExprBits.Loc = Loc;
Stmt **TrailingArgs = getTrailingArgs();
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index f2517de..115460f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2163,13 +2163,12 @@ static bool CheckLValueConstantExpression(EvalInfo &Info, SourceLocation Loc,
}
}
- if (auto *FD = dyn_cast_or_null(BaseVD)) {
- if (FD->isConsteval()) {
- Info.FFDiag(Loc, diag::note_consteval_address_accessible)
- << !Type->isAnyPointerType();
- Info.Note(FD->getLocation(), diag::note_declared_at);
- return false;
- }
+ if (auto *FD = dyn_cast_or_null(BaseVD);
+ FD && FD->isImmediateFunction()) {
+ Info.FFDiag(Loc, diag::note_consteval_address_accessible)
+ << !Type->isAnyPointerType();
+ Info.Note(FD->getLocation(), diag::note_declared_at);
+ return false;
}
// Check that the object is a global. Note that the fake 'this' object we
@@ -2305,7 +2304,7 @@ static bool CheckMemberPointerConstantExpression(EvalInfo &Info,
const auto *FD = dyn_cast_or_null(Member);
if (!FD)
return true;
- if (FD->isConsteval()) {
+ if (FD->isImmediateFunction()) {
Info.FFDiag(Loc, diag::note_consteval_address_accessible) << /*pointer*/ 0;
Info.Note(FD->getLocation(), diag::note_declared_at);
return false;
diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp
index 43b7b71..be4ad65 100644
--- a/clang/lib/AST/JSONNodeDumper.cpp
+++ b/clang/lib/AST/JSONNodeDumper.cpp
@@ -889,6 +889,7 @@ void JSONNodeDumper::VisitFunctionDecl(const FunctionDecl *FD) {
attributeOnlyIfTrue("explicitlyDeleted", FD->isDeletedAsWritten());
attributeOnlyIfTrue("constexpr", FD->isConstexpr());
attributeOnlyIfTrue("variadic", FD->isVariadic());
+ attributeOnlyIfTrue("immediate", FD->isImmediateFunction());
if (FD->isDefaulted())
JOS.attribute("explicitlyDefaulted",
@@ -1249,6 +1250,7 @@ void JSONNodeDumper::VisitDeclRefExpr(const DeclRefExpr *DRE) {
case NOUR_Constant: JOS.attribute("nonOdrUseReason", "constant"); break;
case NOUR_Discarded: JOS.attribute("nonOdrUseReason", "discarded"); break;
}
+ attributeOnlyIfTrue("isImmediateEscalating", DRE->isImmediateEscalating());
}
void JSONNodeDumper::VisitSYCLUniqueStableNameExpr(
@@ -1408,6 +1410,7 @@ void JSONNodeDumper::VisitCXXConstructExpr(const CXXConstructExpr *CE) {
attributeOnlyIfTrue("initializer_list", CE->isStdInitListInitialization());
attributeOnlyIfTrue("zeroing", CE->requiresZeroInitialization());
attributeOnlyIfTrue("hadMultipleCandidates", CE->hadMultipleCandidates());
+ attributeOnlyIfTrue("isImmediateEscalating", CE->isImmediateEscalating());
switch (CE->getConstructionKind()) {
case CXXConstructExpr::CK_Complete:
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index b89a13d..a174faa 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -283,6 +283,8 @@ void TextNodeDumper::Visit(const Decl *D) {
OS << " constexpr";
if (FD->isConsteval())
OS << " consteval";
+ else if (FD->isImmediateFunction())
+ OS << " immediate";
if (FD->isMultiVersion())
OS << " multiversion";
}
@@ -1046,6 +1048,8 @@ void TextNodeDumper::VisitDeclRefExpr(const DeclRefExpr *Node) {
case NOUR_Constant: OS << " non_odr_use_constant"; break;
case NOUR_Discarded: OS << " non_odr_use_discarded"; break;
}
+ if (Node->isImmediateEscalating())
+ OS << " immediate-escalating";
}
void TextNodeDumper::VisitUnresolvedLookupExpr(
@@ -1227,6 +1231,8 @@ void TextNodeDumper::VisitCXXConstructExpr(const CXXConstructExpr *Node) {
OS << " std::initializer_list";
if (Node->requiresZeroInitialization())
OS << " zeroing";
+ if (Node->isImmediateEscalating())
+ OS << " immediate-escalating";
}
void TextNodeDumper::VisitCXXBindTemporaryExpr(
diff --git a/clang/lib/AST/VTableBuilder.cpp b/clang/lib/AST/VTableBuilder.cpp
index bc9a83b..2a6f1e2 100644
--- a/clang/lib/AST/VTableBuilder.cpp
+++ b/clang/lib/AST/VTableBuilder.cpp
@@ -2259,7 +2259,7 @@ VTableLayout::VTableLayout(ArrayRef VTableIndices,
VTableLayout::~VTableLayout() { }
bool VTableContextBase::hasVtableSlot(const CXXMethodDecl *MD) {
- return MD->isVirtual() && !MD->isConsteval();
+ return MD->isVirtual() && !MD->isImmediateFunction();
}
ItaniumVTableContext::ItaniumVTableContext(
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 41ceebf..ac8ff64 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4231,8 +4231,8 @@ llvm::Constant *CodeGenModule::GetAddrOfFunction(GlobalDecl GD,
bool ForVTable,
bool DontDefer,
ForDefinition_t IsForDefinition) {
- assert(!cast(GD.getDecl())->isConsteval() &&
- "consteval function should never be emitted");
+ assert(!cast(GD.getDecl())->isImmediateFunction() &&
+ "an immediate function should never be emitted");
// If there was no specific requested type, just convert it now.
if (!Ty) {
const auto *FD = cast(GD.getDecl());
@@ -6333,9 +6333,8 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
return;
// Consteval function shouldn't be emitted.
- if (auto *FD = dyn_cast(D))
- if (FD->isConsteval())
- return;
+ if (auto *FD = dyn_cast(D); FD && FD->isImmediateFunction())
+ return;
switch (D->getKind()) {
case Decl::CXXConversion:
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 34b478c..2d5be2b 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -683,7 +683,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
// Refer to the discussion of this at https://reviews.llvm.org/D128619.
Builder.defineMacro("__cpp_concepts", "201907L");
Builder.defineMacro("__cpp_conditional_explicit", "201806L");
- //Builder.defineMacro("__cpp_consteval", "201811L");
+ // Builder.defineMacro("__cpp_consteval", "202211L");
Builder.defineMacro("__cpp_constexpr_dynamic_alloc", "201907L");
Builder.defineMacro("__cpp_constinit", "201907L");
Builder.defineMacro("__cpp_impl_coroutine", "201902L");
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 32c928e5..eacb4fa 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -3215,6 +3215,7 @@ ExprResult Parser::ParseCXXMemberInitializer(Decl *D, bool IsFunction,
? Sema::ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed
: Sema::ExpressionEvaluationContext::PotentiallyEvaluated,
D);
+ Actions.ExprEvalContexts.back().InImmediateEscalatingFunctionContext = true;
if (TryConsumeToken(tok::equal, EqualLoc)) {
if (Tok.is(tok::kw_delete)) {
// In principle, an initializer of '= delete p;' is legal, but it will
diff --git a/clang/lib/Sema/ScopeInfo.cpp b/clang/lib/Sema/ScopeInfo.cpp
index e313052..92ce513 100644
--- a/clang/lib/Sema/ScopeInfo.cpp
+++ b/clang/lib/Sema/ScopeInfo.cpp
@@ -39,6 +39,7 @@ void FunctionScopeInfo::Clear() {
FirstReturnLoc = SourceLocation();
FirstCXXOrObjCTryLoc = SourceLocation();
FirstSEHTryLoc = SourceLocation();
+ FoundImmediateEscalatingExpression = false;
// Coroutine state
FirstCoroutineStmtLoc = SourceLocation();
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 992d497..e90a154 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15198,6 +15198,8 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
// function is entered, we need to reset this tracking, since the entered
// function might be not an immediate function.
ExprEvalContexts.back().InImmediateFunctionContext = FD->isConsteval();
+ ExprEvalContexts.back().InImmediateEscalatingFunctionContext =
+ getLangOpts().CPlusPlus20 && FD->isImmediateEscalating();
// Check for defining attributes before the check for redefinition.
if (const auto *Attr = FD->getAttr()) {
@@ -15507,10 +15509,11 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
// one is already popped when finishing the lambda in BuildLambdaExpr().
// This is meant to pop the context added in ActOnStartOfFunctionDef().
ExitFunctionBodyRAII ExitRAII(*this, isLambdaCallOperator(FD));
-
if (FD) {
FD->setBody(Body);
FD->setWillHaveBody(false);
+ CheckImmediateEscalatingFunctionDefinition(
+ FD, FSI->FoundImmediateEscalatingExpression);
if (getLangOpts().CPlusPlus14) {
if (!FD->isInvalidDecl() && Body && !FD->isDependentContext() &&
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 9b3bcc2..6496c42 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -2437,6 +2437,88 @@ static bool CheckConstexprFunctionBody(Sema &SemaRef, const FunctionDecl *Dcl,
return true;
}
+bool Sema::CheckImmediateEscalatingFunctionDefinition(
+ FunctionDecl *FD, bool HasImmediateEscalatingExpression) {
+ if (!FD->hasBody() || !getLangOpts().CPlusPlus20 ||
+ !FD->isImmediateEscalating())
+ return true;
+ FD->setBodyContainsImmediateEscalatingExpressions(
+ HasImmediateEscalatingExpression);
+ if (HasImmediateEscalatingExpression) {
+ auto it = UndefinedButUsed.find(FD->getCanonicalDecl());
+ if (it != UndefinedButUsed.end()) {
+ Diag(it->second, diag::err_immediate_function_used_before_definition)
+ << it->first;
+ Diag(FD->getLocation(), diag::note_defined_here) << FD;
+ if (FD->isImmediateFunction() && !FD->isConsteval())
+ DiagnoseImmediateEscalatingReason(FD);
+ return false;
+ }
+ }
+ return true;
+}
+
+void Sema::DiagnoseImmediateEscalatingReason(const FunctionDecl *FD) {
+ assert(FD->isImmediateEscalating() && !FD->isConsteval() &&
+ "expected an immediate function");
+ assert(FD->hasBody() && "expected the function to have a body");
+ struct ImmediateEscalatingExpressionsVisitor
+ : public RecursiveASTVisitor {
+ using Base = RecursiveASTVisitor;
+ Sema &SemaRef;
+ const FunctionDecl *FD;
+ ImmediateEscalatingExpressionsVisitor(Sema &SemaRef, const FunctionDecl *FD)
+ : SemaRef(SemaRef), FD(FD) {}
+
+ bool shouldVisitImplicitCode() const { return true; }
+ bool shouldVisitLambdaBody() const { return false; }
+
+ bool TraverseCallExpr(CallExpr *E) {
+ if (const auto *DR =
+ dyn_cast(E->getCallee()->IgnoreImplicit());
+ DR && DR->isImmediateEscalating()) {
+ SemaRef.Diag(E->getBeginLoc(), diag::note_immediate_function_reason)
+ << FD << E->getDirectCallee() << E->getDirectCallee()->isConsteval()
+ << /*Call*/ 1 << /*Function*/ 0 << E->getSourceRange();
+ }
+ for (auto A : E->arguments()) {
+ getDerived().TraverseStmt(A);
+ }
+ return true;
+ }
+ bool VisitDeclRefExpr(DeclRefExpr *E) {
+ if (const auto *ReferencedFn = dyn_cast(E->getDecl());
+ ReferencedFn && E->isImmediateEscalating()) {
+ SemaRef.Diag(E->getBeginLoc(), diag::note_immediate_function_reason)
+ << FD << ReferencedFn << ReferencedFn->isConsteval()
+ << /*Address*/ 0 << /*Function*/ 0 << E->getSourceRange();
+ }
+ return true;
+ }
+ bool VisitCXXConstructExpr(CXXConstructExpr *E) {
+ CXXConstructorDecl *D = E->getConstructor();
+ if (E->isImmediateEscalating()) {
+ SemaRef.Diag(E->getBeginLoc(), diag::note_immediate_function_reason)
+ << FD << D << D->isConsteval() << /*Call*/ 1 << /*Constructor*/ 1
+ << E->getSourceRange();
+ }
+ return true;
+ }
+
+ // These nodes can never contain an immediate escalating expression,
+ // we can skip them to avoid unecessary work.
+ bool TraverseDecl(Decl *D) {
+ if (isa(D))
+ return true;
+ return Base::TraverseDecl(D);
+ }
+ bool TraverseType(QualType T) { return true; }
+ bool VisitBlockExpr(BlockExpr *T) { return true; }
+
+ } Visitor(*this, FD);
+ Visitor.TraverseStmt(FD->getBody());
+}
+
/// Get the class that is directly named by the current context. This is the
/// class for which an unqualified-id in this scope could name a constructor
/// or destructor.
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index d206278..7db8ee6 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6007,8 +6007,9 @@ bool Sema::CheckCXXDefaultArgExpr(SourceLocation CallLoc, FunctionDecl *FD,
// is a function parameter scope of an immediate function.
EnterExpressionEvaluationContext EvalContext(
*this,
- FD->isConsteval() ? ExpressionEvaluationContext::ImmediateFunctionContext
- : ExpressionEvaluationContext::PotentiallyEvaluated,
+ FD->isImmediateFunction()
+ ? ExpressionEvaluationContext::ImmediateFunctionContext
+ : ExpressionEvaluationContext::PotentiallyEvaluated,
Param);
ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer =
SkipImmediateInvocations;
@@ -6019,13 +6020,21 @@ bool Sema::CheckCXXDefaultArgExpr(SourceLocation CallLoc, FunctionDecl *FD,
}
struct ImmediateCallVisitor : public RecursiveASTVisitor {
+ const ASTContext &Context;
+ ImmediateCallVisitor(const ASTContext &Ctx) : Context(Ctx) {}
+
bool HasImmediateCalls = false;
+ bool IsImmediateInvocation = false;
bool shouldVisitImplicitCode() const { return true; }
bool VisitCallExpr(CallExpr *E) {
- if (const FunctionDecl *FD = E->getDirectCallee())
- HasImmediateCalls |= FD->isConsteval();
+ if (const FunctionDecl *FD = E->getDirectCallee()) {
+ HasImmediateCalls |= FD->isImmediateFunction();
+ if (FD->isConsteval() && !E->isCXX11ConstantExpr(Context))
+ IsImmediateInvocation = true;
+ }
+
return RecursiveASTVisitor::VisitStmt(E);
}
@@ -6104,7 +6113,7 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
// scope is a function parameter scope of an immediate function.
EnterExpressionEvaluationContext EvalContext(
*this,
- FD->isConsteval()
+ FD->isImmediateFunction()
? ExpressionEvaluationContext::ImmediateFunctionContext
: ExpressionEvaluationContext::PotentiallyEvaluated,
Param);
@@ -6117,7 +6126,7 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
// An immediate invocation that is not evaluated where it appears is
// evaluated and checked for whether it is a constant expression at the
// point where the enclosing initializer is used in a function call.
- ImmediateCallVisitor V;
+ ImmediateCallVisitor V(getASTContext());
if (!NestedDefaultChecking)
V.TraverseDecl(Param);
if (V.HasImmediateCalls) {
@@ -6198,10 +6207,17 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
// evaluated and checked for whether it is a constant expression at the
// point where the enclosing initializer is used in a [...] a constructor
// definition, or an aggregate initialization.
- ImmediateCallVisitor V;
+ ImmediateCallVisitor V(getASTContext());
if (!NestedDefaultChecking)
V.TraverseDecl(Field);
if (V.HasImmediateCalls) {
+ // C++23 [expr.const]/p15
+ // An aggregate initialization is an immediate invocation
+ // if it evaluates a default member initializer that has a subexpression
+ // that is an immediate-escalating expression.
+ ExprEvalContexts.back().InImmediateFunctionContext |=
+ V.IsImmediateInvocation;
+
ExprEvalContexts.back().DelayedDefaultInitializationContext = {Loc, Field,
CurContext};
ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer =
@@ -17925,9 +17941,17 @@ Sema::PushExpressionEvaluationContext(
ExprEvalContexts.back().InDiscardedStatement =
ExprEvalContexts[ExprEvalContexts.size() - 2]
.isDiscardedStatementContext();
+
+ // C++23 [expr.const]/p15
+ // An expression or conversion is in an immediate function context if [...]
+ // it is a subexpression of a manifestly constant-evaluated expression or
+ // conversion.
+ const auto &Prev = ExprEvalContexts[ExprEvalContexts.size() - 2];
ExprEvalContexts.back().InImmediateFunctionContext =
- ExprEvalContexts[ExprEvalContexts.size() - 2]
- .isImmediateFunctionContext();
+ Prev.isImmediateFunctionContext() || Prev.isConstantEvaluated();
+
+ ExprEvalContexts.back().InImmediateEscalatingFunctionContext =
+ Prev.InImmediateEscalatingFunctionContext;
Cleanup.reset();
if (!MaybeODRUseExprs.empty())
@@ -18006,9 +18030,30 @@ void Sema::CheckUnusedVolatileAssignment(Expr *E) {
}
}
+void Sema::MarkExpressionAsImmediateEscalating(Expr *E) {
+ assert(!FunctionScopes.empty() && "Expected a function scope");
+ assert(getLangOpts().CPlusPlus20 &&
+ ExprEvalContexts.back().InImmediateEscalatingFunctionContext &&
+ "Cannot mark an immediate escalating expression outside of an "
+ "immediate escalating context");
+ if (auto *Call = dyn_cast(E->IgnoreImplicit());
+ Call && Call->getCallee()) {
+ if (auto *DeclRef =
+ dyn_cast(Call->getCallee()->IgnoreImplicit()))
+ DeclRef->setIsImmediateEscalating(true);
+ } else if (auto *Ctr = dyn_cast(E->IgnoreImplicit())) {
+ Ctr->setIsImmediateEscalating(true);
+ } else if (auto *DeclRef = dyn_cast(E->IgnoreImplicit())) {
+ DeclRef->setIsImmediateEscalating(true);
+ } else {
+ assert(false && "expected an immediately escalating expression");
+ }
+ getCurFunction()->FoundImmediateEscalatingExpression = true;
+}
+
ExprResult Sema::CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl) {
if (isUnevaluatedContext() || !E.isUsable() || !Decl ||
- !Decl->isConsteval() || isConstantEvaluated() ||
+ !Decl->isImmediateFunction() || isConstantEvaluated() ||
isCheckingDefaultArgumentOrInitializer() ||
RebuildingImmediateInvocation || isImmediateFunctionContext())
return E;
@@ -18022,6 +18067,32 @@ ExprResult Sema::CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl) {
dyn_cast(Call->getCallee()->IgnoreImplicit()))
ExprEvalContexts.back().ReferenceToConsteval.erase(DeclRef);
+ // C++23 [expr.const]/p16
+ // An expression or conversion is immediate-escalating if it is not initially
+ // in an immediate function context and it is [...] an immediate invocation
+ // that is not a constant expression and is not a subexpression of an
+ // immediate invocation.
+ APValue Cached;
+ auto CheckConstantExpressionAndKeepResult = [&]() {
+ llvm::SmallVector Notes;
+ Expr::EvalResult Eval;
+ Eval.Diag = &Notes;
+ bool Res = E.get()->EvaluateAsConstantExpr(
+ Eval, getASTContext(), ConstantExprKind::ImmediateInvocation);
+ if (Res && Notes.empty()) {
+ Cached = std::move(Eval.Val);
+ return true;
+ }
+ return false;
+ };
+
+ if (!E.get()->isValueDependent() &&
+ ExprEvalContexts.back().InImmediateEscalatingFunctionContext &&
+ !CheckConstantExpressionAndKeepResult()) {
+ MarkExpressionAsImmediateEscalating(E.get());
+ return E;
+ }
+
E = MaybeCreateExprWithCleanups(E);
ConstantExpr *Res = ConstantExpr::Create(
@@ -18029,6 +18100,8 @@ ExprResult Sema::CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl) {
ConstantExpr::getStorageKind(Decl->getReturnType().getTypePtr(),
getASTContext()),
/*IsImmediateInvocation*/ true);
+ if (Cached.hasValue())
+ Res->MoveIntoResult(Cached, getASTContext());
/// Value-dependent constant expressions should not be immediately
/// evaluated until they are instantiated.
if (!Res->isValueDependent())
@@ -18056,14 +18129,17 @@ static void EvaluateAndDiagnoseImmediateInvocation(
FD = Call->getConstructor();
else
llvm_unreachable("unhandled decl kind");
- assert(FD && FD->isConsteval());
- SemaRef.Diag(CE->getBeginLoc(), diag::err_invalid_consteval_call) << FD;
+ assert(FD && FD->isImmediateFunction());
+ SemaRef.Diag(CE->getBeginLoc(), diag::err_invalid_consteval_call)
+ << FD << FD->isConsteval();
if (auto Context =
SemaRef.InnermostDeclarationWithDelayedImmediateInvocations()) {
SemaRef.Diag(Context->Loc, diag::note_invalid_consteval_initializer)
<< Context->Decl;
SemaRef.Diag(Context->Decl->getBeginLoc(), diag::note_declared_at);
}
+ if (!FD->isConsteval())
+ SemaRef.DiagnoseImmediateEscalatingReason(FD);
for (auto &Note : Notes)
SemaRef.Diag(Note.first, Note.second);
return;
@@ -18209,13 +18285,36 @@ HandleImmediateInvocations(Sema &SemaRef,
if (!CE.getInt())
EvaluateAndDiagnoseImmediateInvocation(SemaRef, CE);
for (auto *DR : Rec.ReferenceToConsteval) {
- NamedDecl *ND = cast(DR->getDecl());
- if (auto *MD = llvm::dyn_cast(ND);
+ const auto *FD = cast(DR->getDecl());
+ const NamedDecl *ND = FD;
+ if (const auto *MD = dyn_cast(ND);
MD && (MD->isLambdaStaticInvoker() || isLambdaCallOperator(MD)))
ND = MD->getParent();
- SemaRef.Diag(DR->getBeginLoc(), diag::err_invalid_consteval_take_address)
- << ND << isa(ND);
- SemaRef.Diag(ND->getLocation(), diag::note_declared_at);
+
+ // C++23 [expr.const]/p16
+ // An expression or conversion is immediate-escalating if it is not
+ // initially in an immediate function context and it is [...] a
+ // potentially-evaluated id-expression that denotes an immediate function
+ // that is not a subexpression of an immediate invocation.
+ bool ImmediateEscalating = false;
+ bool IsPotentiallyEvaluated =
+ Rec.Context ==
+ Sema::ExpressionEvaluationContext::PotentiallyEvaluated ||
+ Rec.Context ==
+ Sema::ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed;
+ if (SemaRef.inTemplateInstantiation() && IsPotentiallyEvaluated)
+ ImmediateEscalating = Rec.InImmediateEscalatingFunctionContext;
+
+ if (!Rec.InImmediateEscalatingFunctionContext ||
+ (SemaRef.inTemplateInstantiation() && !ImmediateEscalating)) {
+ SemaRef.Diag(DR->getBeginLoc(), diag::err_invalid_consteval_take_address)
+ << ND << isa(ND) << FD->isConsteval();
+ SemaRef.Diag(ND->getLocation(), diag::note_declared_at);
+ if (FD->isImmediateEscalating() && !FD->isConsteval())
+ SemaRef.DiagnoseImmediateEscalatingReason(FD);
+ } else {
+ SemaRef.MarkExpressionAsImmediateEscalating(DR);
+ }
}
}
@@ -20299,12 +20398,14 @@ void Sema::MarkDeclRefReferenced(DeclRefExpr *E, const Expr *Base) {
!Method->getDevirtualizedMethod(Base, getLangOpts().AppleKext))
OdrUse = false;
- if (auto *FD = dyn_cast(E->getDecl()))
+ if (auto *FD = dyn_cast(E->getDecl())) {
if (!isUnevaluatedContext() && !isConstantEvaluated() &&
!isImmediateFunctionContext() &&
- !isCheckingDefaultArgumentOrInitializer() && FD->isConsteval() &&
- !RebuildingImmediateInvocation && !FD->isDependentContext())
+ !isCheckingDefaultArgumentOrInitializer() &&
+ FD->isImmediateFunction() && !RebuildingImmediateInvocation &&
+ !FD->isDependentContext())
ExprEvalContexts.back().ReferenceToConsteval.insert(E);
+ }
MarkExprReferenced(*this, E->getLocation(), E->getDecl(), E, OdrUse,
RefsMinusAssignments);
}
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index c1d70ad..339d16b 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1421,6 +1421,10 @@ void Sema::ActOnStartOfLambdaDefinition(LambdaIntroducer &Intro,
LSI->CallOperator->isConsteval()
? ExpressionEvaluationContext::ImmediateFunctionContext
: ExpressionEvaluationContext::PotentiallyEvaluated);
+ ExprEvalContexts.back().InImmediateFunctionContext =
+ LSI->CallOperator->isConsteval();
+ ExprEvalContexts.back().InImmediateEscalatingFunctionContext =
+ getLangOpts().CPlusPlus20 && LSI->CallOperator->isImmediateEscalating();
}
void Sema::ActOnLambdaError(SourceLocation StartLoc, Scope *CurScope,
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 2c9a17a..bc9490a 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -932,11 +932,12 @@ StmtResult Sema::ActOnIfStmt(SourceLocation IfLoc,
}
if (ConstevalOrNegatedConsteval) {
- bool Immediate = isImmediateFunctionContext();
+ bool Immediate = ExprEvalContexts.back().Context ==
+ ExpressionEvaluationContext::ImmediateFunctionContext;
if (CurContext->isFunctionOrMethod()) {
const auto *FD =
dyn_cast(Decl::castFromDeclContext(CurContext));
- if (FD && FD->isConsteval())
+ if (FD && FD->isImmediateFunction())
Immediate = true;
}
if (isUnevaluatedContext() || Immediate)
@@ -4753,6 +4754,7 @@ void Sema::ActOnCapturedRegionStart(SourceLocation Loc, Scope *CurScope,
PushExpressionEvaluationContext(
ExpressionEvaluationContext::PotentiallyEvaluated);
+ ExprEvalContexts.back().InImmediateEscalatingFunctionContext = false;
}
void Sema::ActOnCapturedRegionStart(SourceLocation Loc, Scope *CurScope,
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index b3dc61a..5e3af14 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4390,6 +4390,12 @@ Sema::TemplateDeductionResult Sema::DeduceTemplateArguments(
DeduceReturnType(Specialization, Info.getLocation(), false))
return TDK_MiscellaneousDeductionFailure;
+ if (IsAddressOfFunction && getLangOpts().CPlusPlus20 &&
+ Specialization->isImmediateEscalating() &&
+ CheckIfFunctionSpecializationIsImmediate(Specialization,
+ Info.getLocation()))
+ return TDK_MiscellaneousDeductionFailure;
+
// If the function has a dependent exception specification, resolve it now,
// so we can check that the exception specification matches.
auto *SpecializationFPT =
@@ -5011,6 +5017,33 @@ bool Sema::DeduceReturnType(FunctionDecl *FD, SourceLocation Loc,
return StillUndeduced;
}
+bool Sema::CheckIfFunctionSpecializationIsImmediate(FunctionDecl *FD,
+ SourceLocation Loc) {
+ assert(FD->isImmediateEscalating());
+
+ if (isLambdaConversionOperator(FD)) {
+ CXXRecordDecl *Lambda = cast(FD)->getParent();
+ FunctionDecl *CallOp = Lambda->getLambdaCallOperator();
+
+ // For a generic lambda, instantiate the call operator if needed.
+ if (auto *Args = FD->getTemplateSpecializationArgs()) {
+ CallOp = InstantiateFunctionDeclaration(
+ CallOp->getDescribedFunctionTemplate(), Args, Loc);
+ if (!CallOp || CallOp->isInvalidDecl())
+ return true;
+ runWithSufficientStackSpace(
+ Loc, [&] { InstantiateFunctionDefinition(Loc, CallOp); });
+ }
+ return CallOp->isInvalidDecl();
+ }
+
+ if (FD->getTemplateInstantiationPattern()) {
+ runWithSufficientStackSpace(
+ Loc, [&] { InstantiateFunctionDefinition(Loc, FD); });
+ }
+ return false;
+}
+
/// If this is a non-static member function,
static void
AddImplicitObjectParameterType(ASTContext &Context,
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 69ce98a..c241a96 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -595,6 +595,7 @@ void ASTStmtReader::VisitDeclRefExpr(DeclRefExpr *E) {
E->DeclRefExprBits.HadMultipleCandidates = Record.readInt();
E->DeclRefExprBits.RefersToEnclosingVariableOrCapture = Record.readInt();
E->DeclRefExprBits.NonOdrUseReason = Record.readInt();
+ E->DeclRefExprBits.IsImmediateEscalating = Record.readInt();
unsigned NumTemplateArgs = 0;
if (E->hasTemplateKWAndArgsInfo())
NumTemplateArgs = Record.readInt();
@@ -1703,6 +1704,7 @@ void ASTStmtReader::VisitCXXConstructExpr(CXXConstructExpr *E) {
E->CXXConstructExprBits.StdInitListInitialization = Record.readInt();
E->CXXConstructExprBits.ZeroInitialization = Record.readInt();
E->CXXConstructExprBits.ConstructionKind = Record.readInt();
+ E->CXXConstructExprBits.IsImmediateEscalating = Record.readInt();
E->CXXConstructExprBits.Loc = readSourceLocation();
E->Constructor = readDeclAs();
E->ParenOrBraceRange = readSourceRange();
@@ -2933,12 +2935,14 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
case EXPR_DECL_REF:
S = DeclRefExpr::CreateEmpty(
- Context,
- /*HasQualifier=*/Record[ASTStmtReader::NumExprFields],
- /*HasFoundDecl=*/Record[ASTStmtReader::NumExprFields + 1],
- /*HasTemplateKWAndArgsInfo=*/Record[ASTStmtReader::NumExprFields + 2],
- /*NumTemplateArgs=*/Record[ASTStmtReader::NumExprFields + 2] ?
- Record[ASTStmtReader::NumExprFields + 6] : 0);
+ Context,
+ /*HasQualifier=*/Record[ASTStmtReader::NumExprFields],
+ /*HasFoundDecl=*/Record[ASTStmtReader::NumExprFields + 1],
+ /*HasTemplateKWAndArgsInfo=*/Record[ASTStmtReader::NumExprFields + 2],
+ /*NumTemplateArgs=*/
+ Record[ASTStmtReader::NumExprFields + 2]
+ ? Record[ASTStmtReader::NumExprFields + 7]
+ : 0);
break;
case EXPR_INTEGER_LITERAL:
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index bd93547..36f882a 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -580,7 +580,7 @@ void ASTDeclWriter::VisitDeclaratorDecl(DeclaratorDecl *D) {
}
void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
- static_assert(DeclContext::NumFunctionDeclBits == 29,
+ static_assert(DeclContext::NumFunctionDeclBits == 30,
"You need to update the serializer after you change the "
"FunctionDeclBits");
@@ -1495,7 +1495,7 @@ void ASTDeclWriter::VisitCXXMethodDecl(CXXMethodDecl *D) {
}
void ASTDeclWriter::VisitCXXConstructorDecl(CXXConstructorDecl *D) {
- static_assert(DeclContext::NumCXXConstructorDeclBits == 22,
+ static_assert(DeclContext::NumCXXConstructorDeclBits == 21,
"You need to update the serializer after you change the "
"CXXConstructorDeclBits");
@@ -2425,6 +2425,7 @@ void ASTWriter::WriteDeclAbbrevs() {
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); //HadMultipleCandidates
Abv->Add(BitCodeAbbrevOp(0)); // RefersToEnclosingVariableOrCapture
Abv->Add(BitCodeAbbrevOp(0)); // NonOdrUseReason
+ Abv->Add(BitCodeAbbrevOp(0)); // IsImmediateEscalating
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // DeclRef
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Location
DeclRefExprAbbrev = Stream.EmitAbbrev(std::move(Abv));
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 368ab0d..896e24c 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -613,6 +613,7 @@ void ASTStmtWriter::VisitDeclRefExpr(DeclRefExpr *E) {
Record.push_back(E->hadMultipleCandidates());
Record.push_back(E->refersToEnclosingVariableOrCapture());
Record.push_back(E->isNonOdrUse());
+ Record.push_back(E->isImmediateEscalating());
if (E->hasTemplateKWAndArgsInfo()) {
unsigned NumTemplateArgs = E->getNumTemplateArgs();
@@ -624,7 +625,8 @@ void ASTStmtWriter::VisitDeclRefExpr(DeclRefExpr *E) {
if ((!E->hasTemplateKWAndArgsInfo()) && (!E->hasQualifier()) &&
(E->getDecl() == E->getFoundDecl()) &&
nk == DeclarationName::Identifier &&
- !E->refersToEnclosingVariableOrCapture() && !E->isNonOdrUse()) {
+ !E->refersToEnclosingVariableOrCapture() && !E->isNonOdrUse() &&
+ !E->isImmediateEscalating()) {
AbbrevToUse = Writer.getDeclRefExprAbbrev();
}
@@ -1601,6 +1603,7 @@ void ASTStmtWriter::VisitCXXConstructExpr(CXXConstructExpr *E) {
Record.push_back(E->isStdInitListInitialization());
Record.push_back(E->requiresZeroInitialization());
Record.push_back(E->getConstructionKind()); // FIXME: stable encoding
+ Record.push_back(E->isImmediateEscalating());
Record.AddSourceLocation(E->getLocation());
Record.AddDeclRef(E->getConstructor());
Record.AddSourceRange(E->getParenOrBraceRange());
diff --git a/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp b/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp
index dfc32c9..c9a0ae9 100644
--- a/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp
+++ b/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp
@@ -1,21 +1,13 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++23 %s
-consteval int undefined(); // expected-note 4 {{declared here}}
+consteval int undefined(); // expected-note 2 {{declared here}}
void check_lambdas_unused(
- int a = []
- {
- // The body of a lambda is not a subexpression of the lambda
- // so this is immediately evaluated even if the parameter
- // is never used.
- return undefined(); // expected-error {{not a constant expression}} \
- // expected-note {{undefined function 'undefined'}}
- }(),
- int b = [](int no_error = undefined()) {
+ int a = [](int no_error = undefined()) {
return no_error;
}(0),
- int c = [](int defaulted = undefined()) {
+ int b = [](int defaulted = undefined()) {
return defaulted;
}()
) {}
@@ -40,8 +32,7 @@ int test_check_lambdas_used = check_lambdas_used();
struct UnusedInitWithLambda {
int a = [] {
- return undefined(); // expected-error {{not a constant expression}} \
- // expected-note {{undefined function 'undefined'}}
+ return undefined(); // never evaluated because immediate escalating
}();
// UnusedInitWithLambda is never constructed, so the initializer
// of b and undefined() are never evaluated.
@@ -50,22 +41,19 @@ struct UnusedInitWithLambda {
}();
};
-consteval int ub(int n) {
- return 0/n; // expected-note {{division}}
+consteval int ub(int n) { // expected-note {{declared here}}
+ return 0/n;
}
struct InitWithLambda {
- int b = [](int error = undefined()) { // expected-error {{not a constant expression}} \
- // expected-note {{declared here}} \
- // expected-note {{undefined function 'undefined'}}
+ int b = [](int error = undefined()) { // expected-error {{cannot take address of consteval function 'undefined' outside of an immediate invocation}}
return error;
- }(); // expected-note {{in the default initalizer of 'error'}}
- int c = [](int error = sizeof(undefined()) + ub(0)) { // expected-error {{'ub' is not a constant expression}} \
- // expected-note {{declared here}} \
- // expected-note {{in call to 'ub(0)}}
+ }();
+ int c = [](int error = sizeof(undefined()) + ub(0)) { // expected-error {{cannot take address of consteval function 'ub' outside of an immediate invocation}}
+
return error;
- }(); // expected-note {{in the default initalizer of 'error'}}
-} i; // expected-note {{in implicit default constructor}}
+ }();
+} i;
namespace ShouldNotCrash {
template
diff --git a/clang/test/SemaCXX/cxx2a-consteval.cpp b/clang/test/SemaCXX/cxx2a-consteval.cpp
index 7946107..d25eda9 100644
--- a/clang/test/SemaCXX/cxx2a-consteval.cpp
+++ b/clang/test/SemaCXX/cxx2a-consteval.cpp
@@ -250,14 +250,14 @@ auto l0 = [](int i) consteval {
return f(i);
};
-auto l1 = [](int i) constexpr {
-// expected-note@-1 {{declared here}}
+auto l1 = [](int i) constexpr { // expected-error{{cannot take address of immediate call operator}} \
+ // expected-note {{declared here}}
int t = f(i);
-// expected-error@-1 {{is not a constant expression}}
-// expected-note@-2 {{function parameter}}
- return f(0);
+ return f(0);
};
+int(*test)(int) = l1;
+
}
namespace std {
diff --git a/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp b/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
new file mode 100644
index 0000000..5e149dd
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
@@ -0,0 +1,166 @@
+// RUN: %clang_cc1 -std=c++2a -emit-llvm-only -Wno-unused-value %s -verify
+// RUN: %clang_cc1 -std=c++2b -emit-llvm-only -Wno-unused-value %s -verify
+
+consteval int id(int i) { return i; }
+constexpr char id(char c) { return c; }
+
+template
+constexpr int f(T t) { // expected-note {{declared here}}
+ return t + id(t); // expected-note 2{{'f' is an immediate function because its body contains a call to a consteval function 'id' and that call is not a constant expression}}
+}
+
+namespace examples {
+
+auto a = &f; // ok, f is not an immediate function
+auto b = &f; // expected-error {{cannot take address of immediate function 'f' outside of an immediate invocation}}
+
+static_assert(f(3) == 6); // ok
+
+template
+constexpr int g(T t) { // g is not an immediate function
+ return t + id(42); // because id(42) is already a constant
+}
+
+template
+constexpr bool is_not(T t, F f) {
+ return not f(t);
+}
+
+consteval bool is_even(int i) { return i % 2 == 0; }
+
+static_assert(is_not(5, is_even));
+
+int x = 0; // expected-note {{declared here}}
+
+template
+constexpr T h(T t = id(x)) { // expected-note {{read of non-const variable 'x' is not allowed in a constant expression}} \
+ // expected-note 2{{'hh' is an immediate function because its body contains a call to a consteval function 'id' and that call is not a constant expression}}
+ return t;
+}
+
+template
+constexpr T hh() { // hh is an immediate function
+ [[maybe_unused]] auto x = h();
+ return h();
+}
+
+int i = hh(); // expected-error {{call to immediate function 'examples::hh' is not a constant expression}} \
+ // expected-note {{in call to 'hh()'}}
+
+struct A {
+ int x;
+ int y = id(x);
+};
+
+template
+constexpr int k(int) {
+ return A(42).y;
+}
+
+}
+
+namespace nested {
+
+template
+constexpr int fdupe(T t) {
+ return id(t);
+}
+
+struct a {
+ constexpr a(int) { }
+};
+
+a aa(fdupe((f(7))));
+
+template
+constexpr int foo(T t); // expected-note {{declared here}}
+
+a bb(f(foo(7))); // expected-error{{call to immediate function 'f' is not a constant expression}} \
+ // expected-note{{undefined function 'foo' cannot be used in a constant expression}}
+
+}
+
+namespace e2{
+template
+constexpr int f(T t);
+auto a = &f;
+auto b = &f;
+}
+
+namespace forward_declare_constexpr{
+template
+constexpr int f(T t);
+
+auto a = &f;
+auto b = &f;
+
+template
+constexpr int f(T t) {
+ return id(0);
+}
+}
+
+namespace forward_declare_consteval{
+template
+constexpr int f(T t); // expected-note {{'f' defined here}}
+
+auto a = &f;
+auto b = &f; // expected-error {{immediate function 'f' used before it is defined}} \
+ // expected-note {{in instantiation of function template specialization}}
+
+template
+constexpr int f(T t) {
+ return id(t); // expected-note {{'f' is an immediate function because its body contains a call to a consteval function 'id' and that call is not a constant expression}}
+}
+}
+
+namespace constructors {
+consteval int f(int) {
+ return 0;
+}
+struct S {
+ constexpr S(auto i) {
+ f(i);
+ }
+};
+constexpr void g(auto i) {
+ [[maybe_unused]] S s{i};
+}
+void test() {
+ g(0);
+}
+}
+
+namespace aggregate {
+consteval int f(int);
+struct S{
+ int a = 0;
+ int b = f(a);
+};
+
+constexpr bool test(auto i) {
+ S s{i};
+ return s.b == 2 *i;
+}
+consteval int f(int i) {
+ return 2 * i;
+}
+
+void test() {
+ static_assert(test(42));
+}
+
+}
+
+namespace ConstevalConstructor{
+int x = 0; // expected-note {{declared here}}
+struct S {
+ consteval S(int) {};
+};
+constexpr int g(auto t) {
+ S s(t); // expected-note {{'g' is an immediate function because its body contains a call to a consteval constructor 'S' and that call is not a constant expression}}
+ return 0;
+}
+int i = g(x); // expected-error {{call to immediate function 'ConstevalConstructor::g' is not a constant expression}} \
+ // expected-note {{read of non-const variable 'x' is not allowed in a constant expression}}
+}
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 7300da5..9bcb02d5 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -363,7 +363,7 @@ C++23, informally referred to as C++26.
consteval needs to propagate up |
P2564R3 (DR) |
- No |
+ Clang 17 |
Lifetime extension in range-based for loops |
--
2.7.4