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,18 @@ return getConstexprKind() == ConstexprSpecKind::Consteval; } + void setBodyContainsImmediateEscalatingExpressions(bool Set) { + FunctionDeclBits.BodyContainsImmediateEscalatingExpression = Set; + } + + bool BodyContainsImmediateEscalatingExpressions() const { + return FunctionDeclBits.BodyContainsImmediateEscalatingExpression; + } + + bool isImmediateEscalating() const; + + 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. 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->isConsteval(); + S.ExprEvalContexts.back().InImmediateEscalatingFunctionContext = + 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,11 @@ /// invocation. ExprResult CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl); + bool CheckImmediateEscalatingExpression(Expr *E); + + bool CheckImmediateEscalatingFunctionDefinition( + FunctionDecl *FD, Expr *ImmediateEscalatingExpression); + bool CompleteConstructorCall(CXXConstructorDecl *Constructor, QualType DeclInitType, MultiExprArg ArgsPtr, SourceLocation Loc, @@ -9148,6 +9159,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, @@ -9732,6 +9746,12 @@ return ExprEvalContexts.back().isImmediateFunctionContext(); } + bool isImmediateEscalatingFunctionContext() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + return ExprEvalContexts.back().InImmediateEscalatingFunctionContext; + } + bool isCheckingDefaultArgumentOrInitializer() const { assert(!ExprEvalContexts.empty() && "Must be in an expression evaluation context"); 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,43 @@ 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. + auto 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 (auto *MD = dyn_cast(this); + MD && MD->isLambdaStaticInvoker()) + return MD->getParent()->getLambdaCallOperator()->isImmediateFunction(); + + if (isImmediateEscalating() && BodyContainsImmediateEscalatingExpressions()) + return true; + return false; +} + bool FunctionDecl::isMain() const { const TranslationUnitDecl *tunit = dyn_cast(getDeclContext()->getRedeclContext()); 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 = + 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,32 @@ return true; } +bool Sema::CheckImmediateEscalatingFunctionDefinition( + FunctionDecl *FD, Expr *ImmediateEscalatingExpression) { + if (!FD->hasBody() || !FD->isImmediateEscalating() || + !getLangOpts().CPlusPlus20) + return true; + FD->setBodyContainsImmediateEscalatingExpressions( + ImmediateEscalatingExpression != nullptr); + if (ImmediateEscalatingExpression) { + 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 (auto *CE = llvm::dyn_cast( + ImmediateEscalatingExpression->IgnoreImplicit())) { + Diag(CE->getBeginLoc(), diag::note_immediate_function_reason) + << FD << CE->getDirectCallee() + << CE->getDirectCallee()->isConsteval() << 1 + << CE->getSourceRange(); + } + return false; + } + } + return true; +} + /// 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,18 @@ } } +bool Sema::CheckImmediateEscalatingExpression(Expr *E) { + if (!LangOpts.CPlusPlus20 || + !ExprEvalContexts.back().InImmediateEscalatingFunctionContext || + FunctionScopes.empty()) + return false; + FunctionScopes.back()->ImmediateEscalatingExpression = E; + return true; +} + 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 +17974,18 @@ 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. + if (!E.get()->isValueDependent() && + !E.get()->isCXX11ConstantExpr(getASTContext()) && + ExprEvalContexts.back().InImmediateEscalatingFunctionContext && + CheckImmediateEscalatingExpression(E.get())) { + return E; + } + E = MaybeCreateExprWithCleanups(E); ConstantExpr *Res = ConstantExpr::Create( @@ -17975,7 +18020,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()) { @@ -18128,13 +18173,32 @@ if (!CE.getInt()) EvaluateAndDiagnoseImmediateInvocation(SemaRef, CE); for (auto *DR : Rec.ReferenceToConsteval) { + bool IsConsteval = cast(DR->getDecl())->isConsteval(); NamedDecl *ND = cast(DR->getDecl()); if (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 = SemaRef.CheckImmediateEscalatingExpression(DR); + + if (!Rec.InImmediateEscalatingFunctionContext || + (SemaRef.inTemplateInstantiation() && !ImmediateEscalating)) { + SemaRef.Diag(DR->getBeginLoc(), diag::err_invalid_consteval_take_address) + << ND << isa(ND) << IsConsteval; + SemaRef.Diag(ND->getLocation(), diag::note_declared_at); + } } } @@ -20212,12 +20276,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() && + !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 = + 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,11 @@ DeduceReturnType(Specialization, Info.getLocation(), false)) return TDK_MiscellaneousDeductionFailure; + if (IsAddressOfFunction && 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 +5007,35 @@ 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); }); + } + if (CallOp->isInvalidDecl()) + return true; + return false; + } + + 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/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,130 @@ + +// 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); +} +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}} + 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