diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -108,6 +108,7 @@ longer have to be constexpr compatible but rather support a less restricted requirements for constexpr functions. Which include allowing non-literal types as return values and parameters, allow calling of non-constexpr functions and constructors. +- 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 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -2378,6 +2378,21 @@ 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 --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -1656,6 +1656,8 @@ /// 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. @@ -1690,7 +1692,7 @@ }; /// 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 @@ -1702,12 +1704,12 @@ /// 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 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -1436,6 +1436,14 @@ 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/Stmt.h b/clang/include/clang/AST/Stmt.h --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -384,6 +384,7 @@ unsigned HadMultipleCandidates : 1; unsigned RefersToEnclosingVariableOrCapture : 1; unsigned NonOdrUseReason : 2; + unsigned IsImmediateEscalating : 1; /// The location of the declaration name itself. SourceLocation Loc; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2656,10 +2656,21 @@ "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">; + +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 " + "function %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 --- a/clang/include/clang/Sema/ScopeInfo.h +++ b/clang/include/clang/Sema/ScopeInfo.h @@ -236,6 +236,9 @@ /// The set of GNU address of label extension "&&label". llvm::SmallVector AddrLabels; + /// Whether we found an immediate-escalating expression. + Expr *ImmediateEscalatingExpression = nullptr; + public: /// Represents a simple identification of a weak object. /// diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1066,9 +1066,13 @@ 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 @@ /// 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 @@ // an immediate function context, so they need to be tracked independently. bool InDiscardedStatement; bool InImmediateFunctionContext; + bool InImmediateEscalatingFunctionContext; bool IsCurrentlyCheckingDefaultArgumentOrInitializer = false; @@ -1356,7 +1361,8 @@ : 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 || @@ -6527,6 +6533,13 @@ /// 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, @@ -9148,6 +9161,9 @@ 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 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -7107,6 +7107,7 @@ E->getValueKind(), ToFoundD, ToResInfo, E->isNonOdrUse()); if (E->hadMultipleCandidates()) ToE->setHadMultipleCandidates(true); + ToE->setIsImmediateEscalating(E->isImmediateEscalating()); return ToE; } diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -3002,6 +3002,7 @@ 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 @@ 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 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -622,6 +622,8 @@ 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 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -492,6 +492,7 @@ DeclRefExprBits.RefersToEnclosingVariableOrCapture = RefersToEnclosingVariableOrCapture; DeclRefExprBits.NonOdrUseReason = NOUR; + DeclRefExprBits.IsImmediateEscalating = false; DeclRefExprBits.Loc = L; setDependence(computeDependence(this, Ctx)); } @@ -529,6 +530,7 @@ getTrailingObjects()->initializeFrom( TemplateKWLoc); } + DeclRefExprBits.IsImmediateEscalating = false; DeclRefExprBits.HadMultipleCandidates = 0; setDependence(computeDependence(this, Ctx)); } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2164,7 +2164,7 @@ } if (auto *FD = dyn_cast_or_null(BaseVD)) { - if (FD->isConsteval()) { + if (FD->isImmediateFunction()) { Info.FFDiag(Loc, diag::note_consteval_address_accessible) << !Type->isAnyPointerType(); Info.Note(FD->getLocation(), diag::note_declared_at); @@ -2305,7 +2305,7 @@ 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/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -283,6 +283,8 @@ OS << " constexpr"; if (FD->isConsteval()) OS << " consteval"; + else if (FD->isImmediateFunction()) + OS << " immediate"; if (FD->isMultiVersion()) OS << " multiversion"; } diff --git a/clang/lib/AST/VTableBuilder.cpp b/clang/lib/AST/VTableBuilder.cpp --- a/clang/lib/AST/VTableBuilder.cpp +++ b/clang/lib/AST/VTableBuilder.cpp @@ -2259,7 +2259,7 @@ 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 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -4211,7 +4211,7 @@ bool ForVTable, bool DontDefer, ForDefinition_t IsForDefinition) { - assert(!cast(GD.getDecl())->isConsteval() && + assert(!cast(GD.getDecl())->isImmediateFunction() && "consteval function should never be emitted"); // If there was no specific requested type, just convert it now. if (!Ty) { @@ -6309,7 +6309,7 @@ // Consteval function shouldn't be emitted. if (auto *FD = dyn_cast(D)) - if (FD->isConsteval()) + if (FD->isImmediateFunction()) return; switch (D->getKind()) { diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -683,7 +683,7 @@ // 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/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -24,7 +24,6 @@ #include "clang/Sema/EnterExpressionEvaluationContext.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/ParsedTemplate.h" -#include "clang/Sema/Scope.h" #include "clang/Sema/SemaDiagnostic.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallString.h" @@ -2493,6 +2492,8 @@ Diag(ConsumeToken(), diag::err_default_special_members) << getLangOpts().CPlusPlus20; } else { + EnterExpressionEvaluationContext Ctx( + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated); InitializerScopeRAII InitScope(*this, D, ThisDecl); if (Tok.is(tok::code_completion)) { diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -3195,6 +3195,7 @@ ? 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 --- a/clang/lib/Sema/ScopeInfo.cpp +++ b/clang/lib/Sema/ScopeInfo.cpp @@ -57,6 +57,7 @@ Blocks.clear(); ByrefBlockVars.clear(); AddrLabels.clear(); + ImmediateEscalatingExpression = nullptr; } static const NamedDecl *getBestPropertyDecl(const ObjCPropertyRefExpr *PropE) { diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -15183,6 +15183,8 @@ // 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()) { @@ -15492,10 +15494,11 @@ // 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->ImmediateEscalatingExpression); if (getLangOpts().CPlusPlus14) { if (!FD->isInvalidDecl() && Body && !FD->isDependentContext() && diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -2437,6 +2437,74 @@ 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 { + 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 = + llvm::dyn_cast(E->getCallee()->IgnoreImplicit()); + DR && DR->isImmediateEscalating()) { + SemaRef.Diag(E->getBeginLoc(), diag::note_immediate_function_reason) + << FD << E->getDirectCallee() << E->getDirectCallee()->isConsteval() + << 1 << 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() << 0 + << E->getSourceRange(); + } + return true; + } + + bool TraverseDecl(Decl *) { return true; } + + 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 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5923,8 +5923,9 @@ // 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; @@ -5935,13 +5936,21 @@ } 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); } @@ -6020,7 +6029,7 @@ // scope is a function parameter scope of an immediate function. EnterExpressionEvaluationContext EvalContext( *this, - FD->isConsteval() + FD->isImmediateFunction() ? ExpressionEvaluationContext::ImmediateFunctionContext : ExpressionEvaluationContext::PotentiallyEvaluated, Param); @@ -6033,7 +6042,7 @@ // 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) { @@ -6114,10 +6123,17 @@ // 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 = @@ -17844,9 +17860,17 @@ 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()) @@ -17925,9 +17949,26 @@ } } +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())) { + if (auto *DeclRef = + dyn_cast(Call->getCallee()->IgnoreImplicit())) + DeclRef->setIsImmediateEscalating(true); + } else if (auto *DeclRef = dyn_cast(E->IgnoreImplicit())) { + DeclRef->setIsImmediateEscalating(true); + } + + getCurFunction()->ImmediateEscalatingExpression = E; +} + ExprResult Sema::CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl) { if (isUnevaluatedContext() || !E.isUsable() || !Decl || - !Decl->isConsteval() || isConstantEvaluated() || + !Decl->isImmediateFunction() || isConstantEvaluated() || isCheckingDefaultArgumentOrInitializer() || RebuildingImmediateInvocation || isImmediateFunctionContext()) return E; @@ -17941,6 +17982,32 @@ 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( @@ -17948,6 +18015,8 @@ 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()) @@ -17975,7 +18044,7 @@ FD = Call->getConstructor(); else llvm_unreachable("unhandled decl kind"); - assert(FD && FD->isConsteval()); + assert(FD && FD->isImmediateFunction()); SemaRef.Diag(CE->getBeginLoc(), diag::err_invalid_consteval_call) << FD; if (auto Context = SemaRef.InnermostDeclarationWithDelayedImmediateInvocations()) { @@ -17983,6 +18052,8 @@ << 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; @@ -18128,13 +18199,36 @@ 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 = cast(DR->getDecl()); + if (const auto *MD = llvm::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); + } } } @@ -20212,12 +20306,14 @@ !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 --- a/clang/lib/Sema/SemaLambda.cpp +++ b/clang/lib/Sema/SemaLambda.cpp @@ -1422,6 +1422,10 @@ 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 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -932,11 +932,12 @@ } 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) @@ -4743,6 +4744,7 @@ 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 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -4382,6 +4382,12 @@ 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 = @@ -5002,6 +5008,33 @@ 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 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -597,6 +597,7 @@ 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(); @@ -2934,12 +2935,14 @@ 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 --- a/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/clang/lib/Serialization/ASTWriterDecl.cpp @@ -580,7 +580,7 @@ } 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::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 @@ 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 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -610,6 +610,7 @@ 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(); @@ -621,7 +622,8 @@ 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(); } diff --git a/clang/test/CodeGenCXX/cxx20-consteval-crash.cpp b/clang/test/CodeGenCXX/cxx20-consteval-crash.cpp --- a/clang/test/CodeGenCXX/cxx20-consteval-crash.cpp +++ b/clang/test/CodeGenCXX/cxx20-consteval-crash.cpp @@ -17,7 +17,7 @@ // This code would previously cause a crash. struct X { int val; }; consteval X g() { return {0}; } -void f() { g(); } +void f() { (void)g(); } // CHECK: define dso_local void @_ZN7PR514841fEv() #1 { // CHECK: entry: diff --git a/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp b/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp --- 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 @@ 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 @@ }(); }; -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 --- a/clang/test/SemaCXX/cxx2a-consteval.cpp +++ b/clang/test/SemaCXX/cxx2a-consteval.cpp @@ -250,14 +250,14 @@ 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 --- /dev/null +++ b/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp @@ -0,0 +1,131 @@ + +// 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; } + +namespace examples { + +template +constexpr int f(T t) { // expected-note {{declared here}} + return t + 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}} +} +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 {{'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 + return h(); +} + +int i = hh(); // expected-error {{call to consteval 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 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)); +} + +} diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -363,7 +363,7 @@ consteval needs to propagate up P2564R3 (DR) - No + Clang 17 Lifetime extension in range-based for loops