[clang] 008e7bf - [C++20] Add consteval-specific semantic for functions
via cfe-commits
cfe-commits at lists.llvm.org
Tue Feb 4 11:38:41 PST 2020
Author: Tyker
Date: 2020-02-04T20:38:32+01:00
New Revision: 008e7bf92343b8bd6ebade5b3ddcfe4bb4e29f8d
URL: https://github.com/llvm/llvm-project/commit/008e7bf92343b8bd6ebade5b3ddcfe4bb4e29f8d
DIFF: https://github.com/llvm/llvm-project/commit/008e7bf92343b8bd6ebade5b3ddcfe4bb4e29f8d.diff
LOG: [C++20] Add consteval-specific semantic for functions
Summary:
Changes:
- Calls to consteval function are now evaluated in constant context but IR is still generated for them.
- Add diagnostic for taking address of a consteval function in non-constexpr context.
- Add diagnostic for address of consteval function accessible at runtime.
- Add tests
Reviewers: rsmith, aaron.ballman
Reviewed By: rsmith
Subscribers: mgrang, riccibruno, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D63960
Added:
Modified:
clang/include/clang/AST/Expr.h
clang/include/clang/AST/Stmt.h
clang/include/clang/Basic/DiagnosticASTKinds.td
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Sema/Sema.h
clang/lib/AST/Expr.cpp
clang/lib/AST/ExprConstant.cpp
clang/lib/Sema/SemaDecl.cpp
clang/lib/Sema/SemaDeclCXX.cpp
clang/lib/Sema/SemaExpr.cpp
clang/lib/Sema/SemaLambda.cpp
clang/lib/Sema/SemaOverload.cpp
clang/test/SemaCXX/cxx2a-consteval.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 16956c27a114..5d9174f6e627 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -693,7 +693,8 @@ class Expr : public ValueStmt {
/// Evaluate an expression that is required to be a constant expression.
bool EvaluateAsConstantExpr(EvalResult &Result, ConstExprUsage Usage,
- const ASTContext &Ctx) const;
+ const ASTContext &Ctx,
+ bool InPlace = false) const;
/// If the current Expr is a pointer, this will try to statically
/// determine the number of bytes available where the pointer is pointing.
@@ -1008,7 +1009,7 @@ class ConstantExpr final
"invalid accessor");
return *getTrailingObjects<APValue>();
}
- const APValue &APValueResult() const {
+ APValue &APValueResult() const {
return const_cast<ConstantExpr *>(this)->APValueResult();
}
@@ -1022,7 +1023,8 @@ class ConstantExpr final
static ConstantExpr *Create(const ASTContext &Context, Expr *E,
const APValue &Result);
static ConstantExpr *Create(const ASTContext &Context, Expr *E,
- ResultStorageKind Storage = RSK_None);
+ ResultStorageKind Storage = RSK_None,
+ bool IsImmediateInvocation = false);
static ConstantExpr *CreateEmpty(const ASTContext &Context,
ResultStorageKind StorageKind,
EmptyShell Empty);
@@ -1053,8 +1055,11 @@ class ConstantExpr final
ResultStorageKind getResultStorageKind() const {
return static_cast<ResultStorageKind>(ConstantExprBits.ResultKind);
}
+ bool isImmediateInvocation() const {
+ return ConstantExprBits.IsImmediateInvocation;
+ }
APValue getAPValueResult() const;
- const APValue &getResultAsAPValue() const { return APValueResult(); }
+ APValue &getResultAsAPValue() const { return APValueResult(); }
llvm::APSInt getResultAsAPSInt() const;
// Iterators
child_range children() { return child_range(&SubExpr, &SubExpr+1); }
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 0b29857c37e3..24c1abb2be54 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -347,6 +347,9 @@ class alignas(void *) Stmt {
/// When ResultKind == RSK_APValue. Wether the ASTContext will cleanup the
/// destructor on the trail-allocated APValue.
unsigned HasCleanup : 1;
+
+ /// Whether this ConstantExpr was created for immediate invocation.
+ unsigned IsImmediateInvocation : 1;
};
class PredefinedExprBitfields {
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 95505b00344f..c0b737b08a72 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -57,6 +57,9 @@ def note_constexpr_non_global : Note<
def note_constexpr_dynamic_alloc : Note<
"%select{pointer|reference}0 to %select{|subobject of }1"
"heap-allocated object is not a constant expression">;
+def note_consteval_address_accessible : Note<
+ "%select{pointer|reference}0 to a consteval declaration "
+ "is not a constant expression">;
def note_constexpr_uninitialized : Note<
"%select{|sub}0object of type %1 is not initialized">;
def note_constexpr_subobject_declared_here : Note<
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 0d42f886f379..504c9057107b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2409,6 +2409,13 @@ def warn_cxx14_compat_constexpr_not_const : Warning<
"'constexpr' non-static member function will not be implicitly 'const' "
"in C++14; add 'const' to avoid a change in behavior">,
InGroup<DiagGroup<"constexpr-not-const">>;
+def err_invalid_consteval_take_address : Error<
+ "cannot take address of consteval function %0 outside"
+ " of an immediate invocation">;
+def err_invalid_consteval_call : Error<
+ "call to consteval function '%q0' is not a constant expression">;
+def err_invalid_consteval_decl_kind : Error<
+ "%0 cannot be declared consteval">;
def err_invalid_constexpr : Error<
"%select{function parameter|typedef}0 "
"cannot be %sub{select_constexpr_spec_kind}1">;
@@ -2506,7 +2513,7 @@ def warn_cxx17_compat_constexpr_local_var_no_init : Warning<
"is incompatible with C++ standards before C++20">,
InGroup<CXXPre2aCompat>, DefaultIgnore;
def ext_constexpr_function_never_constant_expr : ExtWarn<
- "constexpr %select{function|constructor}0 never produces a "
+ "%select{constexpr|consteval}1 %select{function|constructor}0 never produces a "
"constant expression">, InGroup<DiagGroup<"invalid-constexpr">>, DefaultError;
def err_attr_cond_never_constant_expr : Error<
"%0 attribute expression never produces a constant expression">;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index dcc1c433aa73..1d953c8fa65f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -837,6 +837,11 @@ class Sema final {
}
};
+ /// Whether the AST is currently being rebuilt to correct immediate
+ /// invocations. Immediate invocation candidates and references to consteval
+ /// functions aren't tracked when this is set.
+ bool RebuildingImmediateInvocation = false;
+
/// Used to change context to isConstantEvaluated without pushing a heavy
/// ExpressionEvaluationContextRecord object.
bool isConstantEvaluatedOverride;
@@ -1048,6 +1053,8 @@ class Sema final {
PotentiallyEvaluatedIfUsed
};
+ using ImmediateInvocationCandidate = llvm::PointerIntPair<ConstantExpr *, 1>;
+
/// Data structure used to record current or nested
/// expression evaluation contexts.
struct ExpressionEvaluationContextRecord {
@@ -1094,6 +1101,13 @@ class Sema final {
/// they are not discarded-value expressions nor unevaluated operands.
SmallVector<Expr*, 2> VolatileAssignmentLHSs;
+ /// Set of candidates for starting an immediate invocation.
+ llvm::SmallVector<ImmediateInvocationCandidate, 4> ImmediateInvocationCandidates;
+
+ /// Set of DeclRefExprs referencing a consteval function when used in a
+ /// context not already known to be immediately invoked.
+ llvm::SmallPtrSet<DeclRefExpr *, 4> ReferenceToConsteval;
+
/// \brief Describes whether we are in an expression constext which we have
/// to handle
diff erently.
enum ExpressionKind {
@@ -5522,6 +5536,10 @@ class Sema final {
/// it simply returns the passed in expression.
ExprResult MaybeBindToTemporary(Expr *E);
+ /// Wrap the expression in a ConstantExpr if it is a potential immediate
+ /// invocation.
+ ExprResult CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl);
+
bool CompleteConstructorCall(CXXConstructorDecl *Constructor,
MultiExprArg ArgsPtr,
SourceLocation Loc,
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 51d29aef3267..0b6b1e39183c 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -280,7 +280,8 @@ ConstantExpr::ConstantExpr(Expr *subexpr, ResultStorageKind StorageKind)
}
ConstantExpr *ConstantExpr::Create(const ASTContext &Context, Expr *E,
- ResultStorageKind StorageKind) {
+ ResultStorageKind StorageKind,
+ bool IsImmediateInvocation) {
assert(!isa<ConstantExpr>(E));
AssertResultStorageKind(StorageKind);
unsigned Size = totalSizeToAlloc<APValue, uint64_t>(
@@ -288,6 +289,8 @@ ConstantExpr *ConstantExpr::Create(const ASTContext &Context, Expr *E,
StorageKind == ConstantExpr::RSK_Int64);
void *Mem = Context.Allocate(Size, alignof(ConstantExpr));
ConstantExpr *Self = new (Mem) ConstantExpr(E, StorageKind);
+ Self->ConstantExprBits.IsImmediateInvocation =
+ IsImmediateInvocation;
return Self;
}
@@ -317,7 +320,7 @@ ConstantExpr *ConstantExpr::CreateEmpty(const ASTContext &Context,
}
void ConstantExpr::MoveIntoResult(APValue &Value, const ASTContext &Context) {
- assert(getStorageKind(Value) == ConstantExprBits.ResultKind &&
+ assert(getStorageKind(Value) <= ConstantExprBits.ResultKind &&
"Invalid storage for this value kind");
ConstantExprBits.APValueKind = Value.getKind();
switch (ConstantExprBits.ResultKind) {
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index c79973507323..f045e5f6cbad 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2005,6 +2005,17 @@ static bool CheckLValueConstantExpression(EvalInfo &Info, SourceLocation Loc,
APValue::LValueBase Base = LVal.getLValueBase();
const SubobjectDesignator &Designator = LVal.getLValueDesignator();
+ if (auto *VD = LVal.getLValueBase().dyn_cast<const ValueDecl *>()) {
+ if (auto *FD = dyn_cast<FunctionDecl>(VD)) {
+ if (FD->isConsteval()) {
+ 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
// manufacture when checking potential constant expressions is conservatively
// assumed to be global here.
@@ -2114,6 +2125,11 @@ static bool CheckMemberPointerConstantExpression(EvalInfo &Info,
const auto *FD = dyn_cast_or_null<CXXMethodDecl>(Member);
if (!FD)
return true;
+ if (FD->isConsteval()) {
+ Info.FFDiag(Loc, diag::note_consteval_address_accessible) << /*pointer*/ 0;
+ Info.Note(FD->getLocation(), diag::note_declared_at);
+ return false;
+ }
return Usage == Expr::EvaluateForMangling || FD->isVirtual() ||
!FD->hasAttr<DLLImportAttr>();
}
@@ -3548,7 +3564,13 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
APValue *BaseVal = nullptr;
QualType BaseType = getType(LVal.Base);
- if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl*>()) {
+ if (const ConstantExpr *CE =
+ dyn_cast_or_null<ConstantExpr>(LVal.Base.dyn_cast<const Expr *>())) {
+ /// Nested immediate invocation have been previously removed so if we found
+ /// a ConstantExpr it can only be the EvaluatingDecl.
+ assert(CE->isImmediateInvocation() && CE == Info.EvaluatingDecl);
+ BaseVal = Info.EvaluatingDeclValue;
+ } else if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl *>()) {
// In C++98, const, non-volatile integers initialized with ICEs are ICEs.
// In C++11, constexpr, non-volatile variables initialized with constant
// expressions are constant expressions too. Inside constexpr functions,
@@ -7369,6 +7391,8 @@ class LValueExprEvaluatorBase
// from the AST (FIXME).
// * A MaterializeTemporaryExpr that has static storage duration, with no
// CallIndex, for a lifetime-extended temporary.
+// * The ConstantExpr that is currently being evaluated during evaluation of an
+// immediate invocation.
// plus an offset in bytes.
//===----------------------------------------------------------------------===//
namespace {
@@ -13827,7 +13851,7 @@ bool Expr::EvaluateAsLValue(EvalResult &Result, const ASTContext &Ctx,
}
bool Expr::EvaluateAsConstantExpr(EvalResult &Result, ConstExprUsage Usage,
- const ASTContext &Ctx) const {
+ const ASTContext &Ctx, bool InPlace) const {
assert(!isValueDependent() &&
"Expression evaluator can't be called on a dependent expression.");
@@ -13835,7 +13859,14 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, ConstExprUsage Usage,
EvalInfo Info(Ctx, Result, EM);
Info.InConstantContext = true;
- if (!::Evaluate(Result.Val, Info, this) || Result.HasSideEffects)
+ if (InPlace) {
+ Info.setEvaluatingDecl(this, Result.Val);
+ LValue LVal;
+ LVal.set(this);
+ if (!::EvaluateInPlace(Result.Val, Info, LVal, this) ||
+ Result.HasSideEffects)
+ return false;
+ } else if (!::Evaluate(Result.Val, Info, this) || Result.HasSideEffects)
return false;
if (!Info.discardCleanups())
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 2175361dab9a..41c5119fe573 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -8930,9 +8930,24 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
// C++11 [dcl.constexpr]p3: functions declared constexpr are required to
// be either constructors or to return a literal type. Therefore,
// destructors cannot be declared constexpr.
- if (isa<CXXDestructorDecl>(NewFD) && !getLangOpts().CPlusPlus2a) {
+ if (isa<CXXDestructorDecl>(NewFD) &&
+ (!getLangOpts().CPlusPlus2a || ConstexprKind == CSK_consteval)) {
Diag(D.getDeclSpec().getConstexprSpecLoc(), diag::err_constexpr_dtor)
<< ConstexprKind;
+ NewFD->setConstexprKind(getLangOpts().CPlusPlus2a ? CSK_unspecified : CSK_constexpr);
+ }
+ // C++20 [dcl.constexpr]p2: An allocation function, or a
+ // deallocation function shall not be declared with the consteval
+ // specifier.
+ if (ConstexprKind == CSK_consteval &&
+ (NewFD->getOverloadedOperator() == OO_New ||
+ NewFD->getOverloadedOperator() == OO_Array_New ||
+ NewFD->getOverloadedOperator() == OO_Delete ||
+ NewFD->getOverloadedOperator() == OO_Array_Delete)) {
+ Diag(D.getDeclSpec().getConstexprSpecLoc(),
+ diag::err_invalid_consteval_decl_kind)
+ << NewFD;
+ NewFD->setConstexprKind(CSK_constexpr);
}
}
@@ -13649,7 +13664,9 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
// Do not push if it is a lambda because one is already pushed when building
// the lambda in ActOnStartOfLambdaDefinition().
if (!isLambdaCallOperator(FD))
- PushExpressionEvaluationContext(ExprEvalContexts.back().Context);
+ PushExpressionEvaluationContext(
+ FD->isConsteval() ? ExpressionEvaluationContext::ConstantEvaluated
+ : ExprEvalContexts.back().Context);
// Check for defining attributes before the check for redefinition.
if (const auto *Attr = FD->getAttr<AliasAttr>()) {
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 389b78dfcf85..abbc34c88719 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -2306,7 +2306,7 @@ static bool CheckConstexprFunctionBody(Sema &SemaRef, const FunctionDecl *Dcl,
!Expr::isPotentialConstantExpr(Dcl, Diags)) {
SemaRef.Diag(Dcl->getLocation(),
diag::ext_constexpr_function_never_constant_expr)
- << isa<CXXConstructorDecl>(Dcl);
+ << isa<CXXConstructorDecl>(Dcl) << Dcl->isConsteval();
for (size_t I = 0, N = Diags.size(); I != N; ++I)
SemaRef.Diag(Diags[I].first, Diags[I].second);
// Don't return false here: we allow this for compatibility in
@@ -7083,7 +7083,9 @@ bool Sema::CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD,
// If a function is explicitly defaulted on its first declaration, it is
// implicitly considered to be constexpr if the implicit declaration
// would be.
- MD->setConstexprKind(Constexpr ? CSK_constexpr : CSK_unspecified);
+ MD->setConstexprKind(
+ Constexpr ? (MD->isConsteval() ? CSK_consteval : CSK_constexpr)
+ : CSK_unspecified);
if (!Type->hasExceptionSpec()) {
// C++2a [except.spec]p3:
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 2380a6b8d67f..ffe72c983569 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -46,6 +46,7 @@
#include "clang/Sema/SemaInternal.h"
#include "clang/Sema/Template.h"
#include "llvm/Support/ConvertUTF.h"
+#include "llvm/Support/SaveAndRestore.h"
using namespace clang;
using namespace sema;
@@ -6150,7 +6151,7 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl,
return ExprError();
}
- return MaybeBindToTemporary(TheCall);
+ return CheckForImmediateInvocation(MaybeBindToTemporary(TheCall), FDecl);
}
ExprResult
@@ -15276,6 +15277,167 @@ void Sema::CheckUnusedVolatileAssignment(Expr *E) {
}
}
+ExprResult Sema::CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl) {
+ if (!E.isUsable() || !Decl || !Decl->isConsteval() || isConstantEvaluated() ||
+ RebuildingImmediateInvocation)
+ return E;
+
+ /// Opportunistically remove the callee from ReferencesToConsteval if we can.
+ /// It's OK if this fails; we'll also remove this in
+ /// HandleImmediateInvocations, but catching it here allows us to avoid
+ /// walking the AST looking for it in simple cases.
+ if (auto *Call = dyn_cast<CallExpr>(E.get()->IgnoreImplicit()))
+ if (auto *DeclRef =
+ dyn_cast<DeclRefExpr>(Call->getCallee()->IgnoreImplicit()))
+ ExprEvalContexts.back().ReferenceToConsteval.erase(DeclRef);
+
+ E = MaybeCreateExprWithCleanups(E);
+
+ ConstantExpr *Res = ConstantExpr::Create(
+ getASTContext(), E.get(),
+ ConstantExpr::getStorageKind(E.get()->getType().getTypePtr(),
+ getASTContext()),
+ /*IsImmediateInvocation*/ true);
+ ExprEvalContexts.back().ImmediateInvocationCandidates.emplace_back(Res, 0);
+ return Res;
+}
+
+static void EvaluateAndDiagnoseImmediateInvocation(
+ Sema &SemaRef, Sema::ImmediateInvocationCandidate Candidate) {
+ llvm::SmallVector<PartialDiagnosticAt, 8> Notes;
+ Expr::EvalResult Eval;
+ Eval.Diag = &Notes;
+ ConstantExpr *CE = Candidate.getPointer();
+ if (!CE->EvaluateAsConstantExpr(Eval, Expr::EvaluateForCodeGen,
+ SemaRef.getASTContext(), true)) {
+ Expr *InnerExpr = CE->getSubExpr()->IgnoreImplicit();
+ FunctionDecl *FD = nullptr;
+ if (auto *Call = dyn_cast<CallExpr>(InnerExpr))
+ FD = cast<FunctionDecl>(Call->getCalleeDecl());
+ else if (auto *Call = dyn_cast<CXXConstructExpr>(InnerExpr))
+ FD = Call->getConstructor();
+ else
+ llvm_unreachable("unhandled decl kind");
+ assert(FD->isConsteval());
+ SemaRef.Diag(CE->getBeginLoc(), diag::err_invalid_consteval_call) << FD;
+ for (auto &Note : Notes)
+ SemaRef.Diag(Note.first, Note.second);
+ return;
+ }
+ CE->MoveIntoResult(Eval.Val, SemaRef.getASTContext());
+}
+
+static void RemoveNestedImmediateInvocation(
+ Sema &SemaRef, Sema::ExpressionEvaluationContextRecord &Rec,
+ SmallVector<Sema::ImmediateInvocationCandidate, 4>::reverse_iterator It) {
+ struct ComplexRemove : TreeTransform<ComplexRemove> {
+ using Base = TreeTransform<ComplexRemove>;
+ llvm::SmallPtrSetImpl<DeclRefExpr *> &DRSet;
+ SmallVector<Sema::ImmediateInvocationCandidate, 4> &IISet;
+ SmallVector<Sema::ImmediateInvocationCandidate, 4>::reverse_iterator
+ CurrentII;
+ ComplexRemove(Sema &SemaRef, llvm::SmallPtrSetImpl<DeclRefExpr *> &DR,
+ SmallVector<Sema::ImmediateInvocationCandidate, 4> &II,
+ SmallVector<Sema::ImmediateInvocationCandidate,
+ 4>::reverse_iterator Current)
+ : Base(SemaRef), DRSet(DR), IISet(II), CurrentII(Current) {}
+ void RemoveImmediateInvocation(ConstantExpr* E) {
+ auto It = std::find_if(CurrentII, IISet.rend(),
+ [E](Sema::ImmediateInvocationCandidate Elem) {
+ return Elem.getPointer() == E;
+ });
+ assert(It != IISet.rend() &&
+ "ConstantExpr marked IsImmediateInvocation should "
+ "be present");
+ It->setInt(1); // Mark as deleted
+ }
+ ExprResult TransformConstantExpr(ConstantExpr *E) {
+ if (!E->isImmediateInvocation())
+ return Base::TransformConstantExpr(E);
+ RemoveImmediateInvocation(E);
+ return Base::TransformExpr(E->getSubExpr());
+ }
+ /// Base::TransfromCXXOperatorCallExpr doesn't traverse the callee so
+ /// we need to remove its DeclRefExpr from the DRSet.
+ ExprResult TransformCXXOperatorCallExpr(CXXOperatorCallExpr *E) {
+ DRSet.erase(cast<DeclRefExpr>(E->getCallee()->IgnoreImplicit()));
+ return Base::TransformCXXOperatorCallExpr(E);
+ }
+ /// Base::TransformInitializer skip ConstantExpr so we need to visit them
+ /// here.
+ ExprResult TransformInitializer(Expr *Init, bool NotCopyInit) {
+ if (!Init)
+ return Init;
+ /// ConstantExpr are the first layer of implicit node to be removed so if
+ /// Init isn't a ConstantExpr, no ConstantExpr will be skipped.
+ if (auto *CE = dyn_cast<ConstantExpr>(Init))
+ if (CE->isImmediateInvocation())
+ RemoveImmediateInvocation(CE);
+ return Base::TransformInitializer(Init, NotCopyInit);
+ }
+ ExprResult TransformDeclRefExpr(DeclRefExpr *E) {
+ DRSet.erase(E);
+ return E;
+ }
+ bool AlwaysRebuild() { return false; }
+ bool ReplacingOriginal() { return true; }
+ } Transformer(SemaRef, Rec.ReferenceToConsteval,
+ Rec.ImmediateInvocationCandidates, It);
+ ExprResult Res = Transformer.TransformExpr(It->getPointer()->getSubExpr());
+ assert(Res.isUsable());
+ Res = SemaRef.MaybeCreateExprWithCleanups(Res);
+ It->getPointer()->setSubExpr(Res.get());
+}
+
+static void
+HandleImmediateInvocations(Sema &SemaRef,
+ Sema::ExpressionEvaluationContextRecord &Rec) {
+ if ((Rec.ImmediateInvocationCandidates.size() == 0 &&
+ Rec.ReferenceToConsteval.size() == 0) ||
+ SemaRef.RebuildingImmediateInvocation)
+ return;
+
+ /// When we have more then 1 ImmediateInvocationCandidates we need to check
+ /// for nested ImmediateInvocationCandidates. when we have only 1 we only
+ /// need to remove ReferenceToConsteval in the immediate invocation.
+ if (Rec.ImmediateInvocationCandidates.size() > 1) {
+
+ /// Prevent sema calls during the tree transform from adding pointers that
+ /// are already in the sets.
+ llvm::SaveAndRestore<bool> DisableIITracking(
+ SemaRef.RebuildingImmediateInvocation, true);
+
+ /// Prevent diagnostic during tree transfrom as they are duplicates
+ Sema::TentativeAnalysisScope DisableDiag(SemaRef);
+
+ for (auto It = Rec.ImmediateInvocationCandidates.rbegin();
+ It != Rec.ImmediateInvocationCandidates.rend(); It++)
+ if (!It->getInt())
+ RemoveNestedImmediateInvocation(SemaRef, Rec, It);
+ } else if (Rec.ImmediateInvocationCandidates.size() == 1 &&
+ Rec.ReferenceToConsteval.size()) {
+ struct SimpleRemove : RecursiveASTVisitor<SimpleRemove> {
+ llvm::SmallPtrSetImpl<DeclRefExpr *> &DRSet;
+ SimpleRemove(llvm::SmallPtrSetImpl<DeclRefExpr *> &S) : DRSet(S) {}
+ bool VisitDeclRefExpr(DeclRefExpr *E) {
+ DRSet.erase(E);
+ return DRSet.size();
+ }
+ } Visitor(Rec.ReferenceToConsteval);
+ Visitor.TraverseStmt(
+ Rec.ImmediateInvocationCandidates.front().getPointer()->getSubExpr());
+ }
+ for (auto CE : Rec.ImmediateInvocationCandidates)
+ if (!CE.getInt())
+ EvaluateAndDiagnoseImmediateInvocation(SemaRef, CE);
+ for (auto DR : Rec.ReferenceToConsteval) {
+ auto *FD = cast<FunctionDecl>(DR->getDecl());
+ SemaRef.Diag(DR->getBeginLoc(), diag::err_invalid_consteval_take_address)
+ << FD;
+ SemaRef.Diag(FD->getLocation(), diag::note_declared_at);
+ }
+}
+
void Sema::PopExpressionEvaluationContext() {
ExpressionEvaluationContextRecord& Rec = ExprEvalContexts.back();
unsigned NumTypos = Rec.NumTypos;
@@ -15309,6 +15471,7 @@ void Sema::PopExpressionEvaluationContext() {
}
WarnOnPendingNoDerefs(Rec);
+ HandleImmediateInvocations(*this, Rec);
// Warn on any volatile-qualified simple-assignments that are not discarded-
// value expressions nor unevaluated operands (those cases get removed from
@@ -17037,6 +17200,11 @@ void Sema::MarkDeclRefReferenced(DeclRefExpr *E, const Expr *Base) {
if (Method->isVirtual() &&
!Method->getDevirtualizedMethod(Base, getLangOpts().AppleKext))
OdrUse = false;
+
+ if (auto *FD = dyn_cast<FunctionDecl>(E->getDecl()))
+ if (!isConstantEvaluated() && FD->isConsteval() &&
+ !RebuildingImmediateInvocation)
+ ExprEvalContexts.back().ReferenceToConsteval.insert(E);
MarkExprReferenced(*this, E->getLocation(), E->getDecl(), E, OdrUse);
}
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index ae89b146c409..51f643e6431e 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1234,7 +1234,9 @@ void Sema::ActOnStartOfLambdaDefinition(LambdaIntroducer &Intro,
// Enter a new evaluation context to insulate the lambda from any
// cleanups from the enclosing full-expression.
PushExpressionEvaluationContext(
- ExpressionEvaluationContext::PotentiallyEvaluated);
+ LSI->CallOperator->isConsteval()
+ ? ExpressionEvaluationContext::ConstantEvaluated
+ : ExpressionEvaluationContext::PotentiallyEvaluated);
}
void Sema::ActOnLambdaError(SourceLocation StartLoc, Scope *CurScope,
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index a244868d5260..00542bf25006 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -13005,8 +13005,7 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
if (CheckFunctionCall(FnDecl, TheCall,
FnDecl->getType()->castAs<FunctionProtoType>()))
return ExprError();
-
- return MaybeBindToTemporary(TheCall);
+ return CheckForImmediateInvocation(MaybeBindToTemporary(TheCall), FnDecl);
} else {
// We matched a built-in operator. Convert the arguments, then
// break out so that we will build the appropriate built-in
@@ -13397,7 +13396,7 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
if (Best->RewriteKind != CRK_None)
R = new (Context) CXXRewrittenBinaryOperator(R.get(), IsReversed);
- return R;
+ return CheckForImmediateInvocation(R, FnDecl);
} else {
// We matched a built-in operator. Convert the arguments, then
// break out so that we will build the appropriate built-in
@@ -14060,7 +14059,8 @@ Sema::BuildCallToMemberFunction(Scope *S, Expr *MemExprE,
MemExpr->getMemberLoc());
}
- return MaybeBindToTemporary(TheCall);
+ return CheckForImmediateInvocation(MaybeBindToTemporary(TheCall),
+ TheCall->getMethodDecl());
}
/// BuildCallToObjectOfClassType - Build a call to an object of class
@@ -14341,7 +14341,7 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
if (CheckFunctionCall(Method, TheCall, Proto))
return true;
- return MaybeBindToTemporary(TheCall);
+ return CheckForImmediateInvocation(MaybeBindToTemporary(TheCall), Method);
}
/// BuildOverloadedArrowExpr - Build a call to an overloaded @c operator->
@@ -14536,7 +14536,7 @@ ExprResult Sema::BuildLiteralOperatorCall(LookupResult &R,
if (CheckFunctionCall(FD, UDL, nullptr))
return ExprError();
- return MaybeBindToTemporary(UDL);
+ return CheckForImmediateInvocation(MaybeBindToTemporary(UDL), FD);
}
/// Build a call to 'begin' or 'end' for a C++11 for-range statement. If the
diff --git a/clang/test/SemaCXX/cxx2a-consteval.cpp b/clang/test/SemaCXX/cxx2a-consteval.cpp
index 3b52ed4409ee..0be995bc7992 100644
--- a/clang/test/SemaCXX/cxx2a-consteval.cpp
+++ b/clang/test/SemaCXX/cxx2a-consteval.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -std=c++2a -fsyntax-only %s -verify
+// RUN: %clang_cc1 -std=c++2a -fsyntax-only -Wno-unused-value %s -verify
namespace basic_sema {
@@ -12,6 +12,7 @@ consteval constexpr int f2(int i) {
}
constexpr auto l_eval = [](int i) consteval {
+// expected-note at -1+ {{declared here}}
return i;
};
@@ -23,11 +24,12 @@ constexpr consteval int f3(int i) {
struct A {
consteval int f1(int i) const {
+// expected-note at -1 {{declared here}}
return i;
}
consteval A(int i);
consteval A() = default;
- consteval ~A() = default;
+ consteval ~A() = default; // expected-error {{destructor cannot be declared consteval}}
};
consteval struct B {}; // expected-error {{struct cannot be marked consteval}}
@@ -51,14 +53,329 @@ struct C {
struct D {
C c;
consteval D() = default; // expected-error {{cannot be consteval}}
- consteval ~D() = default; // expected-error {{cannot be consteval}}
+ consteval ~D() = default; // expected-error {{destructor cannot be declared consteval}}
};
-struct E : C { // expected-note {{here}}
- consteval ~E() {} // expected-error {{cannot be declared consteval because base class 'basic_sema::C' does not have a constexpr destructor}}
+struct E : C {
+ consteval ~E() {} // expected-error {{cannot be declared consteval}}
};
}
consteval int main() { // expected-error {{'main' is not allowed to be declared consteval}}
return 0;
}
+
+consteval int f_eval(int i) {
+// expected-note at -1+ {{declared here}}
+ return i;
+}
+
+namespace taking_address {
+
+using func_type = int(int);
+
+func_type* p1 = (&f_eval);
+// expected-error at -1 {{take address}}
+func_type* p7 = __builtin_addressof(f_eval);
+// expected-error at -1 {{take address}}
+
+auto p = f_eval;
+// expected-error at -1 {{take address}}
+
+auto m1 = &basic_sema::A::f1;
+// expected-error at -1 {{take address}}
+auto l1 = &decltype(basic_sema::l_eval)::operator();
+// expected-error at -1 {{take address}}
+
+consteval int f(int i) {
+// expected-note at -1+ {{declared here}}
+ return i;
+}
+
+auto ptr = &f;
+// expected-error at -1 {{take address}}
+
+auto f1() {
+ return &f;
+// expected-error at -1 {{take address}}
+}
+
+}
+
+namespace invalid_function {
+using size_t = unsigned long;
+struct A {
+ consteval void *operator new(size_t count);
+ // expected-error at -1 {{'operator new' cannot be declared consteval}}
+ consteval void *operator new[](size_t count);
+ // expected-error at -1 {{'operator new[]' cannot be declared consteval}}
+ consteval void operator delete(void* ptr);
+ // expected-error at -1 {{'operator delete' cannot be declared consteval}}
+ consteval void operator delete[](void* ptr);
+ // expected-error at -1 {{'operator delete[]' cannot be declared consteval}}
+ consteval ~A() {}
+ // expected-error at -1 {{destructor cannot be declared consteval}}
+};
+
+}
+
+namespace nested {
+consteval int f() {
+ return 0;
+}
+
+consteval int f1(...) {
+ return 1;
+}
+
+enum E {};
+
+using T = int(&)();
+
+consteval auto operator+ (E, int(*a)()) {
+ return 0;
+}
+
+void d() {
+ auto i = f1(E() + &f);
+}
+
+auto l0 = [](auto) consteval {
+ return 0;
+};
+
+int i0 = l0(&f1);
+
+int i1 = f1(l0(4));
+
+int i2 = f1(&f1, &f1, &f1, &f1, &f1, &f1, &f1);
+
+int i3 = f1(f1(f1(&f1, &f1), f1(&f1, &f1), f1(f1(&f1, &f1), &f1)));
+
+}
+
+namespace user_defined_literal {
+
+consteval int operator"" _test(unsigned long long i) {
+// expected-note at -1+ {{declared here}}
+ return 0;
+}
+
+int i = 0_test;
+
+auto ptr = &operator"" _test;
+// expected-error at -1 {{take address}}
+
+consteval auto operator"" _test1(unsigned long long i) {
+ return &f_eval;
+}
+
+auto i1 = 0_test1; // expected-error {{is not a constant expression}}
+// expected-note at -1 {{is not a constant expression}}
+
+}
+
+namespace return_address {
+
+consteval int f() {
+// expected-note at -1 {{declared here}}
+ return 0;
+}
+
+consteval int(*ret1(int i))() {
+ return &f;
+}
+
+auto ptr = ret1(0);
+// expected-error at -1 {{is not a constant expression}}
+// expected-note at -2 {{pointer to a consteval}}
+
+struct A {
+ consteval int f(int) {
+ // expected-note at -1+ {{declared here}}
+ return 0;
+ }
+};
+
+using mem_ptr_type = int (A::*)(int);
+
+template<mem_ptr_type ptr>
+struct C {};
+
+C<&A::f> c;
+// expected-error at -1 {{is not a constant expression}}
+// expected-note at -2 {{pointer to a consteval}}
+
+consteval mem_ptr_type ret2() {
+ return &A::f;
+}
+
+C<ret2()> c1;
+// expected-error at -1 {{is not a constant expression}}
+// expected-note at -2 {{pointer to a consteval}}
+
+}
+
+namespace context {
+
+int g_i;
+// expected-note at -1 {{declared here}}
+
+consteval int f(int) {
+ return 0;
+}
+
+constexpr int c_i = 0;
+
+int t1 = f(g_i);
+// expected-error at -1 {{is not a constant expression}}
+// expected-note at -2 {{read of non-const variable}}
+int t3 = f(c_i);
+
+constexpr int f_c(int i) {
+// expected-note at -1 {{declared here}}
+ int t = f(i);
+// expected-error at -1 {{is not a constant expression}}
+// expected-note at -2 {{read of non-const variable}}
+ return f(0);
+}
+
+consteval int f_eval(int i) {
+ return f(i);
+}
+
+auto l0 = [](int i) consteval {
+ return f(i);
+};
+
+auto l1 = [](int i) constexpr {
+// expected-note at -1 {{declared here}}
+ int t = f(i);
+// expected-error at -1 {{is not a constant expression}}
+// expected-note at -2 {{read of non-const variable}}
+ return f(0);
+};
+
+}
+
+namespace temporaries {
+
+struct A {
+ consteval int ret_i() const { return 0; }
+ consteval A ret_a() const { return A{}; }
+ constexpr ~A() { }
+};
+
+consteval int by_value_a(A a) { return a.ret_i(); }
+
+consteval int const_a_ref(const A &a) {
+ return a.ret_i();
+}
+
+consteval int rvalue_ref(const A &&a) {
+ return a.ret_i();
+}
+
+consteval const A &to_lvalue_ref(const A &&a) {
+ return a;
+}
+
+void test() {
+ constexpr A a {};
+ { int k = A().ret_i(); }
+ { A k = A().ret_a(); }
+ { A k = to_lvalue_ref(A()); }// expected-error {{is not a constant expression}}
+ // expected-note at -1 {{is not a constant expression}} expected-note at -1 {{temporary created here}}
+ { A k = to_lvalue_ref(A().ret_a()); } // expected-error {{is not a constant expression}}
+ // expected-note at -1 {{is not a constant expression}} expected-note at -1 {{temporary created here}}
+ { int k = A().ret_a().ret_i(); }
+ { int k = by_value_a(A()); }
+ { int k = const_a_ref(A()); }
+ { int k = const_a_ref(a); }
+ { int k = rvalue_ref(A()); }
+ { int k = rvalue_ref(static_cast<const A&&>(a)); }
+ { int k = const_a_ref(A().ret_a()); }
+ { int k = const_a_ref(to_lvalue_ref(A().ret_a())); }
+ { int k = const_a_ref(to_lvalue_ref(static_cast<const A&&>(a))); }
+ { int k = by_value_a(A().ret_a()); }
+ { int k = by_value_a(to_lvalue_ref(static_cast<const A&&>(a))); }
+ { int k = (A().ret_a(), A().ret_i()); }
+ { int k = (const_a_ref(A().ret_a()), A().ret_i()); }//
+}
+
+}
+
+namespace alloc {
+
+struct A {
+ int* p = new int(42);
+ // expected-note at -1+ {{heap allocation performed here}}
+ consteval int ret_i() const { return p ? *p : 0; }
+ consteval A ret_a() const { return A{}; }
+ constexpr ~A() { delete p; }
+};
+
+consteval int by_value_a(A a) { return a.ret_i(); }
+
+consteval int const_a_ref(const A &a) {
+ return a.ret_i();
+}
+
+consteval int rvalue_ref(const A &&a) {
+ return a.ret_i();
+}
+
+consteval const A &to_lvalue_ref(const A &&a) {
+ return a;
+}
+
+void test() {
+ constexpr A a{ nullptr };
+ { int k = A().ret_i(); }
+ { A k = A().ret_a(); } // expected-error {{is not a constant expression}}
+ // expected-note at -1 {{is not a constant expression}}
+ { A k = to_lvalue_ref(A()); } // expected-error {{is not a constant expression}}
+ // expected-note at -1 {{is not a constant expression}} expected-note at -1 {{temporary created here}}
+ { A k = to_lvalue_ref(A().ret_a()); } // expected-error {{is not a constant expression}}
+ // expected-note at -1 {{is not a constant expression}} expected-note at -1 {{temporary created here}}
+ { int k = A().ret_a().ret_i(); }
+ { int k = by_value_a(A()); }
+ { int k = const_a_ref(A()); }
+ { int k = const_a_ref(a); }
+ { int k = rvalue_ref(A()); }
+ { int k = rvalue_ref(static_cast<const A&&>(a)); }
+ { int k = const_a_ref(A().ret_a()); }
+ { int k = const_a_ref(to_lvalue_ref(A().ret_a())); }
+ { int k = const_a_ref(to_lvalue_ref(static_cast<const A&&>(a))); }
+ { int k = by_value_a(A().ret_a()); }
+ { int k = by_value_a(to_lvalue_ref(static_cast<const A&&>(a))); }
+ { int k = (A().ret_a(), A().ret_i()); }// expected-error {{is not a constant expression}}
+ // expected-note at -1 {{is not a constant expression}}
+ { int k = (const_a_ref(A().ret_a()), A().ret_i()); }
+}
+
+}
+
+namespace self_referencing {
+
+struct S {
+ S* ptr = nullptr;
+ constexpr S(int i) : ptr(this) {
+ if (this == ptr && i)
+ ptr = nullptr;
+ }
+ constexpr ~S() {}
+};
+
+consteval S f(int i) {
+ return S(i);
+}
+
+void test() {
+ S s(1);
+ s = f(1);
+ s = f(0); // expected-error {{is not a constant expression}}
+ // expected-note at -1 {{is not a constant expression}} expected-note at -1 {{temporary created here}}
+}
+
+}
More information about the cfe-commits
mailing list