diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -126,6 +126,7 @@ functions. Which include allowing non-literal types as return values and parameters, allow calling of non-constexpr functions and constructors. - Clang now supports `requires cplusplus23` for module maps. +- Implemented `P2564R3: consteval needs to propagate up `_. C++2c Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h --- 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 @@ -1659,6 +1659,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. @@ -1693,7 +1695,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 @@ -1705,12 +1707,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/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -1681,6 +1681,14 @@ getArgs()[Arg] = ArgExpr; } + bool isImmediateEscalating() const { + return CXXConstructExprBits.IsImmediateEscalating; + } + + void setIsImmediateEscalating(bool Set) { + CXXConstructExprBits.IsImmediateEscalating = Set; + } + SourceLocation getBeginLoc() const LLVM_READONLY; SourceLocation getEndLoc() const LLVM_READONLY; SourceRange getParenOrBraceRange() const { return ParenOrBraceRange; } diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h --- 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; @@ -827,6 +828,7 @@ unsigned StdInitListInitialization : 1; unsigned ZeroInitialization : 1; unsigned ConstructionKind : 3; + unsigned IsImmediateEscalating : 1; SourceLocation Loc; }; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2656,10 +2656,23 @@ "in C++14; add 'const' to avoid a change in behavior">, InGroup>; def err_invalid_consteval_take_address : Error< - "cannot take address of consteval %select{function|call operator of}1 %0 outside" + "cannot take address of %select{immediate|consteval}2 " + "%select{function|call operator of}1 %0 outside" " of an immediate invocation">; def err_invalid_consteval_call : Error< - "call to consteval function %q0 is not a constant expression">; + "call to %select{immediate|consteval}1 function " + "%q0 is not a constant expression">; + +def err_immediate_function_used_before_definition : Error< + "immediate function %0 used before it is defined">; + +def note_immediate_function_reason : Note< + "%0 is an immediate function because its body " + "%select{evaluates the address of %select{an immediate|a consteval}2 " + "function %1|contains a call to %select{an immediate|a consteval}2 " + "%select{function|constructor}4 %1 and that call is not a constant " + "expression}3">; + def note_invalid_consteval_initializer : Note< "in the default initalizer of %0">; def note_invalid_consteval_initializer_here : Note< diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h --- a/clang/include/clang/Sema/ScopeInfo.h +++ b/clang/include/clang/Sema/ScopeInfo.h @@ -172,6 +172,9 @@ /// in the function. One of co_return, co_await, or co_yield. unsigned char FirstCoroutineStmtKind : 2; + /// Whether we found an immediate-escalating expression. + bool FoundImmediateEscalatingExpression : 1; + /// First coroutine statement in the current function. /// (ex co_return, co_await, co_yield) SourceLocation FirstCoroutineStmtLoc; @@ -388,7 +391,8 @@ HasPotentialAvailabilityViolations(false), ObjCShouldCallSuper(false), ObjCIsDesignatedInit(false), ObjCWarnForNoDesignatedInitChain(false), ObjCIsSecondaryInit(false), ObjCWarnForNoInitDelegation(false), - NeedsCoroutineSuspends(true), ErrorTrap(Diag) {} + NeedsCoroutineSuspends(true), FoundImmediateEscalatingExpression(false), + ErrorTrap(Diag) {} virtual ~FunctionScopeInfo(); diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- 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 || @@ -6546,6 +6552,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, @@ -9159,6 +9172,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 @@ -7126,6 +7126,7 @@ E->getValueKind(), ToFoundD, ToResInfo, E->isNonOdrUse()); if (E->hadMultipleCandidates()) ToE->setHadMultipleCandidates(true); + ToE->setIsImmediateEscalating(E->isImmediateEscalating()); return ToE; } @@ -7943,12 +7944,14 @@ if (Error Err = ImportContainerChecked(E->arguments(), ToArgs)) return std::move(Err); - return CXXConstructExpr::Create( + CXXConstructExpr *ToE = CXXConstructExpr::Create( Importer.getToContext(), ToType, ToLocation, ToConstructor, E->isElidable(), ToArgs, E->hadMultipleCandidates(), E->isListInitialization(), E->isStdInitListInitialization(), E->requiresZeroInitialization(), E->getConstructionKind(), ToParenOrBraceRange); + ToE->setIsImmediateEscalating(E->isImmediateEscalating()); + return ToE; } ExpectedStmt ASTNodeImporter::VisitExprWithCleanups(ExprWithCleanups *E) { diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- 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/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -1136,6 +1136,7 @@ CXXConstructExprBits.StdInitListInitialization = StdInitListInitialization; CXXConstructExprBits.ZeroInitialization = ZeroInitialization; CXXConstructExprBits.ConstructionKind = ConstructKind; + CXXConstructExprBits.IsImmediateEscalating = false; CXXConstructExprBits.Loc = Loc; Stmt **TrailingArgs = getTrailingArgs(); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2163,13 +2163,12 @@ } } - if (auto *FD = dyn_cast_or_null(BaseVD)) { - if (FD->isConsteval()) { - Info.FFDiag(Loc, diag::note_consteval_address_accessible) - << !Type->isAnyPointerType(); - Info.Note(FD->getLocation(), diag::note_declared_at); - return false; - } + if (auto *FD = dyn_cast_or_null(BaseVD); + FD && FD->isImmediateFunction()) { + Info.FFDiag(Loc, diag::note_consteval_address_accessible) + << !Type->isAnyPointerType(); + Info.Note(FD->getLocation(), diag::note_declared_at); + return false; } // Check that the object is a global. Note that the fake 'this' object we @@ -2305,7 +2304,7 @@ const auto *FD = dyn_cast_or_null(Member); if (!FD) return true; - if (FD->isConsteval()) { + if (FD->isImmediateFunction()) { Info.FFDiag(Loc, diag::note_consteval_address_accessible) << /*pointer*/ 0; Info.Note(FD->getLocation(), diag::note_declared_at); return false; diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp --- a/clang/lib/AST/JSONNodeDumper.cpp +++ b/clang/lib/AST/JSONNodeDumper.cpp @@ -889,6 +889,7 @@ attributeOnlyIfTrue("explicitlyDeleted", FD->isDeletedAsWritten()); attributeOnlyIfTrue("constexpr", FD->isConstexpr()); attributeOnlyIfTrue("variadic", FD->isVariadic()); + attributeOnlyIfTrue("immediate", FD->isImmediateFunction()); if (FD->isDefaulted()) JOS.attribute("explicitlyDefaulted", @@ -1249,6 +1250,7 @@ case NOUR_Constant: JOS.attribute("nonOdrUseReason", "constant"); break; case NOUR_Discarded: JOS.attribute("nonOdrUseReason", "discarded"); break; } + attributeOnlyIfTrue("isImmediateEscalating", DRE->isImmediateEscalating()); } void JSONNodeDumper::VisitSYCLUniqueStableNameExpr( @@ -1408,6 +1410,7 @@ attributeOnlyIfTrue("initializer_list", CE->isStdInitListInitialization()); attributeOnlyIfTrue("zeroing", CE->requiresZeroInitialization()); attributeOnlyIfTrue("hadMultipleCandidates", CE->hadMultipleCandidates()); + attributeOnlyIfTrue("isImmediateEscalating", CE->isImmediateEscalating()); switch (CE->getConstructionKind()) { case CXXConstructExpr::CK_Complete: diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp --- 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"; } @@ -1046,6 +1048,8 @@ case NOUR_Constant: OS << " non_odr_use_constant"; break; case NOUR_Discarded: OS << " non_odr_use_discarded"; break; } + if (Node->isImmediateEscalating()) + OS << " immediate-escalating"; } void TextNodeDumper::VisitUnresolvedLookupExpr( @@ -1227,6 +1231,8 @@ OS << " std::initializer_list"; if (Node->requiresZeroInitialization()) OS << " zeroing"; + if (Node->isImmediateEscalating()) + OS << " immediate-escalating"; } void TextNodeDumper::VisitCXXBindTemporaryExpr( diff --git a/clang/lib/AST/VTableBuilder.cpp b/clang/lib/AST/VTableBuilder.cpp --- 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 @@ -4231,8 +4231,8 @@ bool ForVTable, bool DontDefer, ForDefinition_t IsForDefinition) { - assert(!cast(GD.getDecl())->isConsteval() && - "consteval function should never be emitted"); + assert(!cast(GD.getDecl())->isImmediateFunction() && + "an immediate function should never be emitted"); // If there was no specific requested type, just convert it now. if (!Ty) { const auto *FD = cast(GD.getDecl()); @@ -6333,9 +6333,8 @@ return; // Consteval function shouldn't be emitted. - if (auto *FD = dyn_cast(D)) - if (FD->isConsteval()) - return; + if (auto *FD = dyn_cast(D); FD && FD->isImmediateFunction()) + return; switch (D->getKind()) { case Decl::CXXConversion: diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp --- 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/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -3215,6 +3215,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 @@ -39,6 +39,7 @@ FirstReturnLoc = SourceLocation(); FirstCXXOrObjCTryLoc = SourceLocation(); FirstSEHTryLoc = SourceLocation(); + FoundImmediateEscalatingExpression = false; // Coroutine state FirstCoroutineStmtLoc = SourceLocation(); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -15198,6 +15198,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()) { @@ -15507,10 +15509,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->FoundImmediateEscalatingExpression); 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,88 @@ return true; } +bool Sema::CheckImmediateEscalatingFunctionDefinition( + FunctionDecl *FD, bool HasImmediateEscalatingExpression) { + if (!FD->hasBody() || !getLangOpts().CPlusPlus20 || + !FD->isImmediateEscalating()) + return true; + FD->setBodyContainsImmediateEscalatingExpressions( + HasImmediateEscalatingExpression); + if (HasImmediateEscalatingExpression) { + auto it = UndefinedButUsed.find(FD->getCanonicalDecl()); + if (it != UndefinedButUsed.end()) { + Diag(it->second, diag::err_immediate_function_used_before_definition) + << it->first; + Diag(FD->getLocation(), diag::note_defined_here) << FD; + if (FD->isImmediateFunction() && !FD->isConsteval()) + DiagnoseImmediateEscalatingReason(FD); + return false; + } + } + return true; +} + +void Sema::DiagnoseImmediateEscalatingReason(const FunctionDecl *FD) { + assert(FD->isImmediateEscalating() && !FD->isConsteval() && + "expected an immediate function"); + assert(FD->hasBody() && "expected the function to have a body"); + struct ImmediateEscalatingExpressionsVisitor + : public RecursiveASTVisitor { + using Base = RecursiveASTVisitor; + Sema &SemaRef; + const FunctionDecl *FD; + ImmediateEscalatingExpressionsVisitor(Sema &SemaRef, const FunctionDecl *FD) + : SemaRef(SemaRef), FD(FD) {} + + bool shouldVisitImplicitCode() const { return true; } + bool shouldVisitLambdaBody() const { return false; } + + bool TraverseCallExpr(CallExpr *E) { + if (const auto *DR = + dyn_cast(E->getCallee()->IgnoreImplicit()); + DR && DR->isImmediateEscalating()) { + SemaRef.Diag(E->getBeginLoc(), diag::note_immediate_function_reason) + << FD << E->getDirectCallee() << E->getDirectCallee()->isConsteval() + << /*Call*/ 1 << /*Function*/ 0 << E->getSourceRange(); + } + for (auto A : E->arguments()) { + getDerived().TraverseStmt(A); + } + return true; + } + bool VisitDeclRefExpr(DeclRefExpr *E) { + if (const auto *ReferencedFn = dyn_cast(E->getDecl()); + ReferencedFn && E->isImmediateEscalating()) { + SemaRef.Diag(E->getBeginLoc(), diag::note_immediate_function_reason) + << FD << ReferencedFn << ReferencedFn->isConsteval() + << /*Address*/ 0 << /*Function*/ 0 << E->getSourceRange(); + } + return true; + } + bool VisitCXXConstructExpr(CXXConstructExpr *E) { + CXXConstructorDecl *D = E->getConstructor(); + if (E->isImmediateEscalating()) { + SemaRef.Diag(E->getBeginLoc(), diag::note_immediate_function_reason) + << FD << D << D->isConsteval() << /*Call*/ 1 << /*Constructor*/ 1 + << E->getSourceRange(); + } + return true; + } + + // These nodes can never contain an immediate escalating expression, + // we can skip them to avoid unecessary work. + bool TraverseDecl(Decl *D) { + if (isa(D)) + return true; + return Base::TraverseDecl(D); + } + bool TraverseType(QualType T) { return true; } + bool VisitBlockExpr(BlockExpr *T) { return true; } + + } Visitor(*this, FD); + Visitor.TraverseStmt(FD->getBody()); +} + /// Get the class that is directly named by the current context. This is the /// class for which an unqualified-id in this scope could name a constructor /// or destructor. diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -6007,8 +6007,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; @@ -6019,13 +6020,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); } @@ -6104,7 +6113,7 @@ // scope is a function parameter scope of an immediate function. EnterExpressionEvaluationContext EvalContext( *this, - FD->isConsteval() + FD->isImmediateFunction() ? ExpressionEvaluationContext::ImmediateFunctionContext : ExpressionEvaluationContext::PotentiallyEvaluated, Param); @@ -6117,7 +6126,7 @@ // An immediate invocation that is not evaluated where it appears is // evaluated and checked for whether it is a constant expression at the // point where the enclosing initializer is used in a function call. - ImmediateCallVisitor V; + ImmediateCallVisitor V(getASTContext()); if (!NestedDefaultChecking) V.TraverseDecl(Param); if (V.HasImmediateCalls) { @@ -6198,10 +6207,17 @@ // evaluated and checked for whether it is a constant expression at the // point where the enclosing initializer is used in a [...] a constructor // definition, or an aggregate initialization. - ImmediateCallVisitor V; + ImmediateCallVisitor V(getASTContext()); if (!NestedDefaultChecking) V.TraverseDecl(Field); if (V.HasImmediateCalls) { + // C++23 [expr.const]/p15 + // An aggregate initialization is an immediate invocation + // if it evaluates a default member initializer that has a subexpression + // that is an immediate-escalating expression. + ExprEvalContexts.back().InImmediateFunctionContext |= + V.IsImmediateInvocation; + ExprEvalContexts.back().DelayedDefaultInitializationContext = {Loc, Field, CurContext}; ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer = @@ -17925,9 +17941,17 @@ ExprEvalContexts.back().InDiscardedStatement = ExprEvalContexts[ExprEvalContexts.size() - 2] .isDiscardedStatementContext(); + + // C++23 [expr.const]/p15 + // An expression or conversion is in an immediate function context if [...] + // it is a subexpression of a manifestly constant-evaluated expression or + // conversion. + const auto &Prev = ExprEvalContexts[ExprEvalContexts.size() - 2]; ExprEvalContexts.back().InImmediateFunctionContext = - ExprEvalContexts[ExprEvalContexts.size() - 2] - .isImmediateFunctionContext(); + Prev.isImmediateFunctionContext() || Prev.isConstantEvaluated(); + + ExprEvalContexts.back().InImmediateEscalatingFunctionContext = + Prev.InImmediateEscalatingFunctionContext; Cleanup.reset(); if (!MaybeODRUseExprs.empty()) @@ -18006,9 +18030,30 @@ } } +void Sema::MarkExpressionAsImmediateEscalating(Expr *E) { + assert(!FunctionScopes.empty() && "Expected a function scope"); + assert(getLangOpts().CPlusPlus20 && + ExprEvalContexts.back().InImmediateEscalatingFunctionContext && + "Cannot mark an immediate escalating expression outside of an " + "immediate escalating context"); + if (auto *Call = dyn_cast(E->IgnoreImplicit()); + Call && Call->getCallee()) { + if (auto *DeclRef = + dyn_cast(Call->getCallee()->IgnoreImplicit())) + DeclRef->setIsImmediateEscalating(true); + } else if (auto *Ctr = dyn_cast(E->IgnoreImplicit())) { + Ctr->setIsImmediateEscalating(true); + } else if (auto *DeclRef = dyn_cast(E->IgnoreImplicit())) { + DeclRef->setIsImmediateEscalating(true); + } else { + assert(false && "expected an immediately escalating expression"); + } + getCurFunction()->FoundImmediateEscalatingExpression = true; +} + ExprResult Sema::CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl) { if (isUnevaluatedContext() || !E.isUsable() || !Decl || - !Decl->isConsteval() || isConstantEvaluated() || + !Decl->isImmediateFunction() || isConstantEvaluated() || isCheckingDefaultArgumentOrInitializer() || RebuildingImmediateInvocation || isImmediateFunctionContext()) return E; @@ -18022,6 +18067,32 @@ dyn_cast(Call->getCallee()->IgnoreImplicit())) ExprEvalContexts.back().ReferenceToConsteval.erase(DeclRef); + // C++23 [expr.const]/p16 + // An expression or conversion is immediate-escalating if it is not initially + // in an immediate function context and it is [...] an immediate invocation + // that is not a constant expression and is not a subexpression of an + // immediate invocation. + APValue Cached; + auto CheckConstantExpressionAndKeepResult = [&]() { + llvm::SmallVector Notes; + Expr::EvalResult Eval; + Eval.Diag = &Notes; + bool Res = E.get()->EvaluateAsConstantExpr( + Eval, getASTContext(), ConstantExprKind::ImmediateInvocation); + if (Res && Notes.empty()) { + Cached = std::move(Eval.Val); + return true; + } + return false; + }; + + if (!E.get()->isValueDependent() && + ExprEvalContexts.back().InImmediateEscalatingFunctionContext && + !CheckConstantExpressionAndKeepResult()) { + MarkExpressionAsImmediateEscalating(E.get()); + return E; + } + E = MaybeCreateExprWithCleanups(E); ConstantExpr *Res = ConstantExpr::Create( @@ -18029,6 +18100,8 @@ ConstantExpr::getStorageKind(Decl->getReturnType().getTypePtr(), getASTContext()), /*IsImmediateInvocation*/ true); + if (Cached.hasValue()) + Res->MoveIntoResult(Cached, getASTContext()); /// Value-dependent constant expressions should not be immediately /// evaluated until they are instantiated. if (!Res->isValueDependent()) @@ -18056,14 +18129,17 @@ FD = Call->getConstructor(); else llvm_unreachable("unhandled decl kind"); - assert(FD && FD->isConsteval()); - SemaRef.Diag(CE->getBeginLoc(), diag::err_invalid_consteval_call) << FD; + assert(FD && FD->isImmediateFunction()); + SemaRef.Diag(CE->getBeginLoc(), diag::err_invalid_consteval_call) + << FD << FD->isConsteval(); if (auto Context = SemaRef.InnermostDeclarationWithDelayedImmediateInvocations()) { SemaRef.Diag(Context->Loc, diag::note_invalid_consteval_initializer) << Context->Decl; SemaRef.Diag(Context->Decl->getBeginLoc(), diag::note_declared_at); } + if (!FD->isConsteval()) + SemaRef.DiagnoseImmediateEscalatingReason(FD); for (auto &Note : Notes) SemaRef.Diag(Note.first, Note.second); return; @@ -18209,13 +18285,36 @@ if (!CE.getInt()) EvaluateAndDiagnoseImmediateInvocation(SemaRef, CE); for (auto *DR : Rec.ReferenceToConsteval) { - NamedDecl *ND = cast(DR->getDecl()); - if (auto *MD = llvm::dyn_cast(ND); + const auto *FD = cast(DR->getDecl()); + const NamedDecl *ND = FD; + if (const auto *MD = dyn_cast(ND); MD && (MD->isLambdaStaticInvoker() || isLambdaCallOperator(MD))) ND = MD->getParent(); - SemaRef.Diag(DR->getBeginLoc(), diag::err_invalid_consteval_take_address) - << ND << isa(ND); - SemaRef.Diag(ND->getLocation(), diag::note_declared_at); + + // C++23 [expr.const]/p16 + // An expression or conversion is immediate-escalating if it is not + // initially in an immediate function context and it is [...] a + // potentially-evaluated id-expression that denotes an immediate function + // that is not a subexpression of an immediate invocation. + bool ImmediateEscalating = false; + bool IsPotentiallyEvaluated = + Rec.Context == + Sema::ExpressionEvaluationContext::PotentiallyEvaluated || + Rec.Context == + Sema::ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed; + if (SemaRef.inTemplateInstantiation() && IsPotentiallyEvaluated) + ImmediateEscalating = Rec.InImmediateEscalatingFunctionContext; + + if (!Rec.InImmediateEscalatingFunctionContext || + (SemaRef.inTemplateInstantiation() && !ImmediateEscalating)) { + SemaRef.Diag(DR->getBeginLoc(), diag::err_invalid_consteval_take_address) + << ND << isa(ND) << FD->isConsteval(); + SemaRef.Diag(ND->getLocation(), diag::note_declared_at); + if (FD->isImmediateEscalating() && !FD->isConsteval()) + SemaRef.DiagnoseImmediateEscalatingReason(FD); + } else { + SemaRef.MarkExpressionAsImmediateEscalating(DR); + } } } @@ -20299,12 +20398,14 @@ !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 @@ -1421,6 +1421,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) @@ -4753,6 +4754,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 @@ -4390,6 +4390,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 = @@ -5011,6 +5017,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 @@ -595,6 +595,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(); @@ -1703,6 +1704,7 @@ E->CXXConstructExprBits.StdInitListInitialization = Record.readInt(); E->CXXConstructExprBits.ZeroInitialization = Record.readInt(); E->CXXConstructExprBits.ConstructionKind = Record.readInt(); + E->CXXConstructExprBits.IsImmediateEscalating = Record.readInt(); E->CXXConstructExprBits.Loc = readSourceLocation(); E->Constructor = readDeclAs(); E->ParenOrBraceRange = readSourceRange(); @@ -2933,12 +2935,14 @@ 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 @@ -613,6 +613,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(); @@ -624,7 +625,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(); } @@ -1601,6 +1603,7 @@ Record.push_back(E->isStdInitListInitialization()); Record.push_back(E->requiresZeroInitialization()); Record.push_back(E->getConstructionKind()); // FIXME: stable encoding + Record.push_back(E->isImmediateEscalating()); Record.AddSourceLocation(E->getLocation()); Record.AddDeclRef(E->getConstructor()); Record.AddSourceRange(E->getParenOrBraceRange()); diff --git a/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp b/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp --- 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,166 @@ +// RUN: %clang_cc1 -std=c++2a -emit-llvm-only -Wno-unused-value %s -verify +// RUN: %clang_cc1 -std=c++2b -emit-llvm-only -Wno-unused-value %s -verify + +consteval int id(int i) { return i; } +constexpr char id(char c) { return c; } + +template +constexpr int f(T t) { // expected-note {{declared here}} + return t + id(t); // expected-note 2{{'f' is an immediate function because its body contains a call to a consteval function 'id' and that call is not a constant expression}} +} + +namespace examples { + +auto a = &f; // ok, f is not an immediate function +auto b = &f; // expected-error {{cannot take address of immediate function 'f' outside of an immediate invocation}} + +static_assert(f(3) == 6); // ok + +template +constexpr int g(T t) { // g is not an immediate function + return t + id(42); // because id(42) is already a constant +} + +template +constexpr bool is_not(T t, F f) { + return not f(t); +} + +consteval bool is_even(int i) { return i % 2 == 0; } + +static_assert(is_not(5, is_even)); + +int x = 0; // expected-note {{declared here}} + +template +constexpr T h(T t = id(x)) { // expected-note {{read of non-const variable 'x' is not allowed in a constant expression}} \ + // expected-note 2{{'hh' is an immediate function because its body contains a call to a consteval function 'id' and that call is not a constant expression}} + return t; +} + +template +constexpr T hh() { // hh is an immediate function + [[maybe_unused]] auto x = h(); + return h(); +} + +int i = hh(); // expected-error {{call to immediate function 'examples::hh' is not a constant expression}} \ + // expected-note {{in call to 'hh()'}} + +struct A { + int x; + int y = id(x); +}; + +template +constexpr int k(int) { + return A(42).y; +} + +} + +namespace nested { + +template +constexpr int fdupe(T t) { + return id(t); +} + +struct a { + constexpr a(int) { } +}; + +a aa(fdupe((f(7)))); + +template +constexpr int foo(T t); // expected-note {{declared here}} + +a bb(f(foo(7))); // expected-error{{call to immediate function 'f' is not a constant expression}} \ + // expected-note{{undefined function 'foo' cannot be used in a constant expression}} + +} + +namespace e2{ +template +constexpr int f(T t); +auto a = &f; +auto b = &f; +} + +namespace forward_declare_constexpr{ +template +constexpr int f(T t); + +auto a = &f; +auto b = &f; + +template +constexpr int f(T t) { + return id(0); +} +} + +namespace forward_declare_consteval{ +template +constexpr int f(T t); // expected-note {{'f' defined here}} + +auto a = &f; +auto b = &f; // expected-error {{immediate function 'f' used before it is defined}} \ + // expected-note {{in instantiation of function template specialization}} + +template +constexpr int f(T t) { + return id(t); // expected-note {{'f' is an immediate function because its body contains a call to a consteval function 'id' and that call is not a constant expression}} +} +} + +namespace constructors { +consteval int f(int) { + return 0; +} +struct S { + constexpr S(auto i) { + f(i); + } +}; +constexpr void g(auto i) { + [[maybe_unused]] S s{i}; +} +void test() { + g(0); +} +} + +namespace aggregate { +consteval int f(int); +struct S{ + int a = 0; + int b = f(a); +}; + +constexpr bool test(auto i) { + S s{i}; + return s.b == 2 *i; +} +consteval int f(int i) { + return 2 * i; +} + +void test() { + static_assert(test(42)); +} + +} + +namespace ConstevalConstructor{ +int x = 0; // expected-note {{declared here}} +struct S { + consteval S(int) {}; +}; +constexpr int g(auto t) { + S s(t); // expected-note {{'g' is an immediate function because its body contains a call to a consteval constructor 'S' and that call is not a constant expression}} + return 0; +} +int i = g(x); // expected-error {{call to immediate function 'ConstevalConstructor::g' is not a constant expression}} \ + // expected-note {{read of non-const variable 'x' is not allowed in a constant expression}} +} diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html --- 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