diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 99971bf10922b..bac6c7162a45b 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -70,6 +70,9 @@ C++20 Feature Support C++23 Feature Support ^^^^^^^^^^^^^^^^^^^^^ +- Implemented `P2718R0: Lifetime extension in range-based for loops `_. Also + materialize temporary object which is a prvalue in discarded-value expression. + C++2c Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index baad6155b7c63..4d09d8ba95e32 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2402,7 +2402,7 @@ class Parser : public CodeCompletionHandler { struct ForRangeInit { SourceLocation ColonLoc; ExprResult RangeExpr; - + SmallVector LifetimeExtendTemps; bool ParsedForRangeDecl() { return !ColonLoc.isInvalid(); } }; struct ForRangeInfo : ForRangeInit { diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index b5946e3f3178f..59eab0185ae63 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1342,6 +1342,12 @@ class Sema final { /// context not already known to be immediately invoked. llvm::SmallPtrSet ReferenceToConsteval; + /// P2718R0 - Lifetime extension in range-based for loops. + /// MaterializeTemporaryExprs in for-range-init expressions which need to + /// extend lifetime. Add MaterializeTemporaryExpr* if the value of + /// InLifetimeExtendingContext is true. + SmallVector ForRangeLifetimeExtendTemps; + /// \brief Describes whether we are in an expression constext which we have /// to handle differently. enum ExpressionKind { @@ -1361,6 +1367,39 @@ class Sema final { // VLAs). bool InConditionallyConstantEvaluateContext = false; + /// Whether we are currently in a context in which all temporaries must be + /// lifetime-extended, even if they're not bound to a reference (for + /// example, in a for-range initializer). + bool InLifetimeExtendingContext = false; + + /// Whether we are currently in a context in which all temporaries must be + /// materialized. + /// + /// [class.temporary]/p2: + /// The materialization of a temporary object is generally delayed as long + /// as possible in order to avoid creating unnecessary temporary objects. + /// + /// Temporary objects are materialized: + /// (2.1) when binding a reference to a prvalue ([dcl.init.ref], + /// [expr.type.conv], [expr.dynamic.cast], [expr.static.cast], + /// [expr.const.cast], [expr.cast]), + /// + /// (2.2) when performing member access on a class prvalue ([expr.ref], + /// [expr.mptr.oper]), + /// + /// (2.3) when performing an array-to-pointer conversion or subscripting + /// on an array prvalue ([conv.array], [expr.sub]), + /// + /// (2.4) when initializing an object of type + /// std​::​initializer_list from a braced-init-list + /// ([dcl.init.list]), + /// + /// (2.5) for certain unevaluated operands ([expr.typeid], [expr.sizeof]) + /// + /// (2.6) when a prvalue that has type other than cv void appears as a + /// discarded-value expression ([expr.context]). + bool InMaterializeTemporaryObjectContext = false; + // When evaluating immediate functions in the initializer of a default // argument or default member initializer, this is the declaration whose // default initializer is being evaluated and the location of the call @@ -5253,22 +5292,17 @@ class Sema final { BFRK_Check }; - StmtResult ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc, - SourceLocation CoawaitLoc, - Stmt *InitStmt, - Stmt *LoopVar, - SourceLocation ColonLoc, Expr *Collection, - SourceLocation RParenLoc, - BuildForRangeKind Kind); - StmtResult BuildCXXForRangeStmt(SourceLocation ForLoc, - SourceLocation CoawaitLoc, - Stmt *InitStmt, - SourceLocation ColonLoc, - Stmt *RangeDecl, Stmt *Begin, Stmt *End, - Expr *Cond, Expr *Inc, - Stmt *LoopVarDecl, - SourceLocation RParenLoc, - BuildForRangeKind Kind); + StmtResult ActOnCXXForRangeStmt( + Scope *S, SourceLocation ForLoc, SourceLocation CoawaitLoc, + Stmt *InitStmt, Stmt *LoopVar, SourceLocation ColonLoc, Expr *Collection, + SourceLocation RParenLoc, BuildForRangeKind Kind, + ArrayRef LifetimeExtendTemps = {}); + StmtResult BuildCXXForRangeStmt( + SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt, + SourceLocation ColonLoc, Stmt *RangeDecl, Stmt *Begin, Stmt *End, + Expr *Cond, Expr *Inc, Stmt *LoopVarDecl, SourceLocation RParenLoc, + BuildForRangeKind Kind, + ArrayRef LifetimeExtendTemps = {}); StmtResult FinishCXXForRangeStmt(Stmt *ForRange, Stmt *Body); StmtResult ActOnGotoStmt(SourceLocation GotoLoc, @@ -10010,6 +10044,18 @@ class Sema final { return currentEvaluationContext().isImmediateFunctionContext(); } + bool isInLifetimeExtendingContext() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + return ExprEvalContexts.back().InLifetimeExtendingContext; + } + + bool isInMaterializeTemporaryObjectContext() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + return ExprEvalContexts.back().InMaterializeTemporaryObjectContext; + } + bool isCheckingDefaultArgumentOrInitializer() const { const ExpressionEvaluationContextRecord &Ctx = currentEvaluationContext(); return (Ctx.Context == @@ -10049,6 +10095,32 @@ class Sema final { return Res; } + /// keepInLifetimeExtendingContext - Pull down InLifetimeExtendingContext + /// flag from previous context. + void keepInLifetimeExtendingContext() { + if (ExprEvalContexts.size() > 2 && + ExprEvalContexts[ExprEvalContexts.size() - 2] + .InLifetimeExtendingContext) { + auto &LastRecord = ExprEvalContexts.back(); + auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2]; + LastRecord.InLifetimeExtendingContext = + PrevRecord.InLifetimeExtendingContext; + } + } + + /// keepInMaterializeTemporaryObjectContext - Pull down + /// InMaterializeTemporaryObjectContext flag from previous context. + void keepInMaterializeTemporaryObjectContext() { + if (ExprEvalContexts.size() > 2 && + ExprEvalContexts[ExprEvalContexts.size() - 2] + .InMaterializeTemporaryObjectContext) { + auto &LastRecord = ExprEvalContexts.back(); + auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2]; + LastRecord.InMaterializeTemporaryObjectContext = + PrevRecord.InMaterializeTemporaryObjectContext; + } + } + /// RAII class used to determine whether SFINAE has /// trapped any errors that occur during template argument /// deduction. diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 1b91c86f91398..877e205e2e9bf 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -643,7 +643,9 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, : "200704"); Builder.defineMacro("__cpp_constexpr_in_decltype", "201711L"); Builder.defineMacro("__cpp_range_based_for", - LangOpts.CPlusPlus17 ? "201603L" : "200907"); + LangOpts.CPlusPlus23 ? "202211L" + : LangOpts.CPlusPlus17 ? "201603L" + : "200907"); Builder.defineMacro("__cpp_static_assert", LangOpts.CPlusPlus26 ? "202306L" : LangOpts.CPlusPlus17 ? "201411L" diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 7ea0dccd1c724..bcbe2d9c635a6 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2312,12 +2312,38 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, bool IsForRangeLoop = false; if (TryConsumeToken(tok::colon, FRI->ColonLoc)) { IsForRangeLoop = true; + EnterExpressionEvaluationContext ForRangeInitContext( + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, + /*LambdaContextDecl=*/nullptr, + Sema::ExpressionEvaluationContextRecord::EK_Other, + getLangOpts().CPlusPlus23); + + // P2718R0 - Lifetime extension in range-based for loops. + if (getLangOpts().CPlusPlus23) { + auto &LastRecord = Actions.ExprEvalContexts.back(); + LastRecord.InLifetimeExtendingContext = true; + + // Materialize non-`cv void` prvalue temporaries in discarded + // expressions. These materialized temporaries may be lifetime-extented. + LastRecord.InMaterializeTemporaryObjectContext = true; + } + if (getLangOpts().OpenMP) Actions.startOpenMPCXXRangeFor(); if (Tok.is(tok::l_brace)) FRI->RangeExpr = ParseBraceInitializer(); else FRI->RangeExpr = ParseExpression(); + + // Before c++23, ForRangeLifetimeExtendTemps should be empty. + assert( + getLangOpts().CPlusPlus23 || + Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps.empty()); + + // Move the collected materialized temporaries into ForRangeInit before + // ForRangeInitContext exit. + FRI->LifetimeExtendTemps = std::move( + Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps); } Decl *ThisDecl = Actions.ActOnDeclarator(getCurScope(), D); diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index d0ff33bd1379a..76a3fa8f2627d 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -2288,11 +2288,11 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) { ForRangeStmt = Actions.ActOnCXXForRangeStmt( getCurScope(), ForLoc, CoawaitLoc, FirstPart.get(), ForRangeInfo.LoopVar.get(), ForRangeInfo.ColonLoc, CorrectedRange.get(), - T.getCloseLocation(), Sema::BFRK_Build); - - // Similarly, we need to do the semantic analysis for a for-range - // statement immediately in order to close over temporaries correctly. + T.getCloseLocation(), Sema::BFRK_Build, + ForRangeInfo.LifetimeExtendTemps); } else if (ForEach) { + // Similarly, we need to do the semantic analysis for a for-range + // statement immediately in order to close over temporaries correctly. ForEachStmt = Actions.ActOnObjCForCollectionStmt(ForLoc, FirstPart.get(), Collection.get(), diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 2f1ddfb215116..abe300ecc5431 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -6259,7 +6259,7 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc, assert(Param->hasDefaultArg() && "can't build nonexistent default arg"); bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); - + bool InLifetimeExtendingContext = isInLifetimeExtendingContext(); std::optional InitializationContext = OutermostDeclarationWithDelayedImmediateInvocations(); @@ -6292,9 +6292,17 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc, ImmediateCallVisitor V(getASTContext()); if (!NestedDefaultChecking) V.TraverseDecl(Param); - if (V.HasImmediateCalls) { - ExprEvalContexts.back().DelayedDefaultInitializationContext = { - CallLoc, Param, CurContext}; + + // Rewrite the call argument that was created from the corresponding + // parameter's default argument. + if (V.HasImmediateCalls || InLifetimeExtendingContext) { + if (V.HasImmediateCalls) + ExprEvalContexts.back().DelayedDefaultInitializationContext = { + CallLoc, Param, CurContext}; + // Pass down lifetime extending flag, and collect temporaries in + // CreateMaterializeTemporaryExpr when we rewrite the call argument. + keepInLifetimeExtendingContext(); + keepInMaterializeTemporaryObjectContext(); EnsureImmediateInvocationInDefaultArgs Immediate(*this); ExprResult Res; runWithSufficientStackSpace(CallLoc, [&] { @@ -18655,6 +18663,16 @@ void Sema::PopExpressionEvaluationContext() { } } + // Append the collected materialized temporaries into previous context before + // exit if the previous also is a lifetime extending context. + auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2]; + if (getLangOpts().CPlusPlus23 && isInLifetimeExtendingContext() && + PrevRecord.InLifetimeExtendingContext && !ExprEvalContexts.empty()) { + auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2]; + PrevRecord.ForRangeLifetimeExtendTemps.append( + Rec.ForRangeLifetimeExtendTemps); + } + WarnOnPendingNoDerefs(Rec); HandleImmediateInvocations(*this, Rec); diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 51c61886739bb..2b695fd43eac0 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -8222,21 +8222,6 @@ ExprResult Sema::IgnoredValueConversions(Expr *E) { E = result.get(); } - // C99 6.3.2.1: - // [Except in specific positions,] an lvalue that does not have - // array type is converted to the value stored in the - // designated object (and is no longer an lvalue). - if (E->isPRValue()) { - // In C, function designators (i.e. expressions of function type) - // are r-values, but we still want to do function-to-pointer decay - // on them. This is both technically correct and convenient for - // some clients. - if (!getLangOpts().CPlusPlus && E->getType()->isFunctionType()) - return DefaultFunctionArrayConversion(E); - - return E; - } - if (getLangOpts().CPlusPlus) { // The C++11 standard defines the notion of a discarded-value expression; // normally, we don't need to do anything to handle it, but if it is a @@ -8257,11 +8242,32 @@ ExprResult Sema::IgnoredValueConversions(Expr *E) { // If the expression is a prvalue after this optional conversion, the // temporary materialization conversion is applied. // - // We skip this step: IR generation is able to synthesize the storage for - // itself in the aggregate case, and adding the extra node to the AST is - // just clutter. - // FIXME: We don't emit lifetime markers for the temporaries due to this. - // FIXME: Do any other AST consumers care about this? + // We do not materialize temporaries by default in order to avoid creating + // unnecessary temporary objects. If we skip this step, IR generation is + // able to synthesize the storage for itself in the aggregate case, and + // adding the extra node to the AST is just clutter. + if (isInMaterializeTemporaryObjectContext() && getLangOpts().CPlusPlus17 && + E->isPRValue() && !E->getType()->isVoidType()) { + ExprResult Res = TemporaryMaterializationConversion(E); + if (Res.isInvalid()) + return E; + E = Res.get(); + } + return E; + } + + // C99 6.3.2.1: + // [Except in specific positions,] an lvalue that does not have + // array type is converted to the value stored in the + // designated object (and is no longer an lvalue). + if (E->isPRValue()) { + // In C, function designators (i.e. expressions of function type) + // are r-values, but we still want to do function-to-pointer decay + // on them. This is both technically correct and convenient for + // some clients. + if (!getLangOpts().CPlusPlus && E->getType()->isFunctionType()) + return DefaultFunctionArrayConversion(E); + return E; } diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 457fa377355a9..b6de06464cd6f 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -8491,6 +8491,10 @@ Sema::CreateMaterializeTemporaryExpr(QualType T, Expr *Temporary, // are done in both CreateMaterializeTemporaryExpr and MaybeBindToTemporary, // but there may be a chance to merge them. Cleanup.setExprNeedsCleanups(false); + if (isInLifetimeExtendingContext()) { + auto &Record = ExprEvalContexts.back(); + Record.ForRangeLifetimeExtendTemps.push_back(MTE); + } return MTE; } diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 5d5a29b825ae7..5ab2534492113 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -27,6 +27,7 @@ #include "clang/AST/TypeOrdering.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Preprocessor.h" +#include "clang/Sema/EnterExpressionEvaluationContext.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Ownership.h" @@ -2489,11 +2490,11 @@ static bool ObjCEnumerationCollection(Expr *Collection) { /// /// The body of the loop is not available yet, since it cannot be analysed until /// we have determined the type of the for-range-declaration. -StmtResult Sema::ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc, - SourceLocation CoawaitLoc, Stmt *InitStmt, - Stmt *First, SourceLocation ColonLoc, - Expr *Range, SourceLocation RParenLoc, - BuildForRangeKind Kind) { +StmtResult Sema::ActOnCXXForRangeStmt( + Scope *S, SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt, + Stmt *First, SourceLocation ColonLoc, Expr *Range, SourceLocation RParenLoc, + BuildForRangeKind Kind, + ArrayRef LifetimeExtendTemps) { // FIXME: recover in order to allow the body to be parsed. if (!First) return StmtError(); @@ -2557,7 +2558,8 @@ StmtResult Sema::ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc, StmtResult R = BuildCXXForRangeStmt( ForLoc, CoawaitLoc, InitStmt, ColonLoc, RangeDecl.get(), /*BeginStmt=*/nullptr, /*EndStmt=*/nullptr, - /*Cond=*/nullptr, /*Inc=*/nullptr, DS, RParenLoc, Kind); + /*Cond=*/nullptr, /*Inc=*/nullptr, DS, RParenLoc, Kind, + LifetimeExtendTemps); if (R.isInvalid()) { ActOnInitializerError(LoopVar); return StmtError(); @@ -2747,13 +2749,12 @@ static StmtResult RebuildForRangeWithDereference(Sema &SemaRef, Scope *S, } /// BuildCXXForRangeStmt - Build or instantiate a C++11 for-range statement. -StmtResult Sema::BuildCXXForRangeStmt(SourceLocation ForLoc, - SourceLocation CoawaitLoc, Stmt *InitStmt, - SourceLocation ColonLoc, Stmt *RangeDecl, - Stmt *Begin, Stmt *End, Expr *Cond, - Expr *Inc, Stmt *LoopVarDecl, - SourceLocation RParenLoc, - BuildForRangeKind Kind) { +StmtResult Sema::BuildCXXForRangeStmt( + SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt, + SourceLocation ColonLoc, Stmt *RangeDecl, Stmt *Begin, Stmt *End, + Expr *Cond, Expr *Inc, Stmt *LoopVarDecl, SourceLocation RParenLoc, + BuildForRangeKind Kind, + ArrayRef LifetimeExtendTemps) { // FIXME: This should not be used during template instantiation. We should // pick up the set of unqualified lookup results for the != and + operators // in the initial parse. @@ -2813,6 +2814,14 @@ StmtResult Sema::BuildCXXForRangeStmt(SourceLocation ForLoc, diag::err_for_range_incomplete_type)) return StmtError(); + // P2718R0 - Lifetime extension in range-based for loops. + if (getLangOpts().CPlusPlus23 && !LifetimeExtendTemps.empty()) { + InitializedEntity Entity = + InitializedEntity::InitializeVariable(RangeVar); + for (auto *MTE : LifetimeExtendTemps) + MTE->setExtendingDecl(RangeVar, Entity.allocateManglingNumber()); + } + // Build auto __begin = begin-expr, __end = end-expr. // Divide by 2, since the variables are in the inner scope (loop body). const auto DepthStr = std::to_string(S->getDepth() / 2); diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index fbc8572ea0e0f..fcb27a880290b 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -5440,6 +5440,8 @@ void Sema::InstantiateVariableInitializer( EnterExpressionEvaluationContext Evaluated( *this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, Var); + keepInLifetimeExtendingContext(); + keepInMaterializeTemporaryObjectContext(); // Instantiate the initializer. ExprResult Init; diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 2922924e550d0..e252c3e2ffc0e 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -2550,12 +2550,11 @@ class TreeTransform { /// /// By default, performs semantic analysis to build the new statement. /// Subclasses may override this routine to provide different behavior. - StmtResult RebuildCXXForRangeStmt(SourceLocation ForLoc, - SourceLocation CoawaitLoc, Stmt *Init, - SourceLocation ColonLoc, Stmt *Range, - Stmt *Begin, Stmt *End, Expr *Cond, - Expr *Inc, Stmt *LoopVar, - SourceLocation RParenLoc) { + StmtResult RebuildCXXForRangeStmt( + SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *Init, + SourceLocation ColonLoc, Stmt *Range, Stmt *Begin, Stmt *End, Expr *Cond, + Expr *Inc, Stmt *LoopVar, SourceLocation RParenLoc, + ArrayRef LifetimeExtendTemps) { // If we've just learned that the range is actually an Objective-C // collection, treat this as an Objective-C fast enumeration loop. if (DeclStmt *RangeStmt = dyn_cast(Range)) { @@ -2581,9 +2580,9 @@ class TreeTransform { } } - return getSema().BuildCXXForRangeStmt(ForLoc, CoawaitLoc, Init, ColonLoc, - Range, Begin, End, Cond, Inc, LoopVar, - RParenLoc, Sema::BFRK_Rebuild); + return getSema().BuildCXXForRangeStmt( + ForLoc, CoawaitLoc, Init, ColonLoc, Range, Begin, End, Cond, Inc, + LoopVar, RParenLoc, Sema::BFRK_Rebuild, LifetimeExtendTemps); } /// Build a new C++0x range-based for statement. @@ -4151,6 +4150,8 @@ ExprResult TreeTransform::TransformInitializer(Expr *Init, getSema(), EnterExpressionEvaluationContext::InitList, Construct->isListInitialization()); + getSema().keepInLifetimeExtendingContext(); + getSema().keepInLifetimeExtendingContext(); SmallVector NewArgs; bool ArgChanged = false; if (getDerived().TransformExprs(Construct->getArgs(), Construct->getNumArgs(), @@ -8656,6 +8657,21 @@ StmtResult TreeTransform::TransformCXXTryStmt(CXXTryStmt *S) { template StmtResult TreeTransform::TransformCXXForRangeStmt(CXXForRangeStmt *S) { + EnterExpressionEvaluationContext ForRangeInitContext( + getSema(), Sema::ExpressionEvaluationContext::PotentiallyEvaluated, + /*LambdaContextDecl=*/nullptr, + Sema::ExpressionEvaluationContextRecord::EK_Other, + getSema().getLangOpts().CPlusPlus23); + + // P2718R0 - Lifetime extension in range-based for loops. + if (getSema().getLangOpts().CPlusPlus23) { + auto &LastRecord = getSema().ExprEvalContexts.back(); + LastRecord.InLifetimeExtendingContext = true; + + // Materialize non-`cv void` prvalue temporaries in discarded + // expressions. These materialized temporaries may be lifetime-extented. + LastRecord.InMaterializeTemporaryObjectContext = true; + } StmtResult Init = S->getInit() ? getDerived().TransformStmt(S->getInit()) : StmtResult(); if (Init.isInvalid()) @@ -8665,6 +8681,12 @@ TreeTransform::TransformCXXForRangeStmt(CXXForRangeStmt *S) { if (Range.isInvalid()) return StmtError(); + // Before c++23, ForRangeLifetimeExtendTemps should be empty. + assert(getSema().getLangOpts().CPlusPlus23 || + getSema().ExprEvalContexts.back().ForRangeLifetimeExtendTemps.empty()); + auto ForRangeLifetimeExtendTemps = + getSema().ExprEvalContexts.back().ForRangeLifetimeExtendTemps; + StmtResult Begin = getDerived().TransformStmt(S->getBeginStmt()); if (Begin.isInvalid()) return StmtError(); @@ -8701,13 +8723,10 @@ TreeTransform::TransformCXXForRangeStmt(CXXForRangeStmt *S) { Cond.get() != S->getCond() || Inc.get() != S->getInc() || LoopVar.get() != S->getLoopVarStmt()) { - NewStmt = getDerived().RebuildCXXForRangeStmt(S->getForLoc(), - S->getCoawaitLoc(), Init.get(), - S->getColonLoc(), Range.get(), - Begin.get(), End.get(), - Cond.get(), - Inc.get(), LoopVar.get(), - S->getRParenLoc()); + NewStmt = getDerived().RebuildCXXForRangeStmt( + S->getForLoc(), S->getCoawaitLoc(), Init.get(), S->getColonLoc(), + Range.get(), Begin.get(), End.get(), Cond.get(), Inc.get(), + LoopVar.get(), S->getRParenLoc(), ForRangeLifetimeExtendTemps); if (NewStmt.isInvalid() && LoopVar.get() != S->getLoopVarStmt()) { // Might not have attached any initializer to the loop variable. getSema().ActOnInitializerError( @@ -8723,13 +8742,10 @@ TreeTransform::TransformCXXForRangeStmt(CXXForRangeStmt *S) { // Body has changed but we didn't rebuild the for-range statement. Rebuild // it now so we have a new statement to attach the body to. if (Body.get() != S->getBody() && NewStmt.get() == S) { - NewStmt = getDerived().RebuildCXXForRangeStmt(S->getForLoc(), - S->getCoawaitLoc(), Init.get(), - S->getColonLoc(), Range.get(), - Begin.get(), End.get(), - Cond.get(), - Inc.get(), LoopVar.get(), - S->getRParenLoc()); + NewStmt = getDerived().RebuildCXXForRangeStmt( + S->getForLoc(), S->getCoawaitLoc(), Init.get(), S->getColonLoc(), + Range.get(), Begin.get(), End.get(), Cond.get(), Inc.get(), + LoopVar.get(), S->getRParenLoc(), ForRangeLifetimeExtendTemps); if (NewStmt.isInvalid()) return StmtError(); } diff --git a/clang/test/AST/ast-dump-for-range-lifetime.cpp b/clang/test/AST/ast-dump-for-range-lifetime.cpp new file mode 100644 index 0000000000000..dec2e29184526 --- /dev/null +++ b/clang/test/AST/ast-dump-for-range-lifetime.cpp @@ -0,0 +1,355 @@ +// RUN: %clang_cc1 -std=c++23 -triple x86_64-linux-gnu -fcxx-exceptions -ast-dump %s \ +// RUN: | FileCheck -strict-whitespace %s + +namespace P2718R0 { + +// Test basic +struct A { + int a[3] = {1, 2, 3}; + A() {} + ~A() {} + const int *begin() const { return a; } + const int *end() const { return a + 3; } + A& r() { return *this; } + A g() { return A(); } +}; + +A g() { return A(); } +const A &f1(const A &t) { return t; } + +void test1() { + [[maybe_unused]] int sum = 0; + // CHECK: FunctionDecl {{.*}} test1 'void ()' + // CHECK: | `-CXXForRangeStmt {{.*}} + // CHECK-NEXT: | |-<<>> + // CHECK-NEXT: | |-DeclStmt {{.*}} + // CHECK-NEXT: | | `-VarDecl {{.*}} implicit used __range1 'const A &' cinit + // CHECK-NEXT: | | `-ExprWithCleanups {{.*}} 'const A':'const P2718R0::A' lvalue + // CHECK-NEXT: | | `-CallExpr {{.*}} 'const A':'const P2718R0::A' lvalue + // CHECK-NEXT: | | |-ImplicitCastExpr {{.*}} 'const A &(*)(const A &)' + // CHECK-NEXT: | | | `-DeclRefExpr {{.*}} 'const A &(const A &)' lvalue Function {{.*}} 'f1' 'const A &(const A &)' + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'const A':'const P2718R0::A' lvalue extended by Var {{.*}} '__range1' 'const A &' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const A':'const P2718R0::A' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CallExpr {{.*}} 'A':'P2718R0::A' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'A (*)()' + // CHECK-NEXT: | | `-DeclRefExpr {{.*}} 'A ()' lvalue Function {{.*}} 'g' 'A ()' + for (auto e : f1(g())) + sum += e; +} + +struct B : A {}; +int (&f(const A *))[3]; +const A *g(const A &); +void bar(int) {} + +void test2() { + // CHECK: FunctionDecl {{.*}} test2 'void ()' + // CHECK: | `-CXXForRangeStmt {{.*}} + // CHECK-NEXT: | |-<<>> + // CHECK-NEXT: | |-DeclStmt {{.*}} + // CHECK-NEXT: | | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | | `-CallExpr {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | | |-ImplicitCastExpr {{.*}} 'int (&(*)(const A *))[3]' + // CHECK-NEXT: | | | `-DeclRefExpr {{.*}} 'int (&(const A *))[3]' lvalue Function {{.*}} 'f' 'int (&(const A *))[3]' + // CHECK-NEXT: | | `-CallExpr {{.*}} 'const A *' + // CHECK-NEXT: | | |-ImplicitCastExpr {{.*}} 'const A *(*)(const A &)' + // CHECK-NEXT: | | | `-DeclRefExpr {{.*}} 'const A *(const A &)' lvalue Function {{.*}} 'g' 'const A *(const A &)' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const A':'const P2718R0::A' lvalue + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'const B':'const P2718R0::B' lvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const B':'const P2718R0::B' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'B':'P2718R0::B' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'B':'P2718R0::B' 'void () noexcept(false)' zeroing + for (auto e : f(g(B()))) + bar(e); +} + +// Test discard statement. +struct LockGuard { + LockGuard() {} + ~LockGuard() {} +}; + +void test3() { + int v[] = {42, 17, 13}; + + // CHECK: FunctionDecl {{.*}} test3 'void ()' + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-BinaryOperator {{.*}} 'int[3]' lvalue ',' + // CHECK-NEXT: | |-CXXStaticCastExpr {{.*}} 'void' static_cast + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' xvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' 'void ()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int[3]' lvalue Var {{.*}} 'v' 'int[3]' + for ([[maybe_unused]] int x : static_cast(LockGuard()), v) + LockGuard guard; + + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-BinaryOperator {{.*}} 'int[3]' lvalue ',' + // CHECK-NEXT: | |-CStyleCastExpr {{.*}} 'void' + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' xvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' 'void ()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int[3]' lvalue Var {{.*}} 'v' 'int[3]' + for ([[maybe_unused]] int x : (void)LockGuard(), v) + LockGuard guard; + + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-BinaryOperator {{.*}} 'int[3]' lvalue ',' + // CHECK-NEXT: | |-MaterializeTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' xvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' 'void ()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int[3]' lvalue Var {{.*}} 'v' 'int[3]' + for ([[maybe_unused]] int x : LockGuard(), v) + LockGuard guard; +} + +// Test default arg +int (&default_arg_fn(const A & = A()))[3]; +void test4() { + + // CHECK: FunctionDecl {{.*}} test4 'void ()' + // FIXME: Should dump CXXDefaultArgExpr->getExpr() if CXXDefaultArgExpr has been rewrited? + for (auto e : default_arg_fn()) + bar(e); +} + +struct DefaultA { + DefaultA() {} + ~DefaultA() {} +}; + +A foo(const A&, const DefaultA &Default = DefaultA()) { + return A(); +} + +void test5() { + for (auto e : default_arg_fn(foo(foo(foo(A()))))) + bar(e); +} + +struct C : public A { + C() {} + C(int, const C &, const DefaultA & = DefaultA()) {} +}; + +void test6() { + for (auto e : C(0, C(0, C(0, C())))) + bar(e); +} + +// Test member call +void test7() { + // CHECK: FunctionDecl {{.*}} test7 'void ()' + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'A &&' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'A':'P2718R0::A' xvalue + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .g {{.*}} + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .r {{.*}} + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .g {{.*}} + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .r {{.*}} + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .g {{.*}} + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .r {{.*}} + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CallExpr {{.*}} 'A':'P2718R0::A' + // CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'A (*)()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'A ()' lvalue Function {{.*}} 'g' 'A ()' + for (auto e : g().r().g().r().g().r().g()) + bar(e); +} + +// Test basic && dependent context +template T dg() { return T(); } +template const T &df1(const T &t) { return t; } + +void test8() { + [[maybe_unused]] int sum = 0; + // CHECK: FunctionDecl {{.*}} test8 'void ()' + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'const P2718R0::A &' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'const P2718R0::A' lvalue + // CHECK-NEXT: | `-CallExpr {{.*}} 'const P2718R0::A' lvalue + // CHECK-NEXT: | |-ImplicitCastExpr {{.*}} 'const P2718R0::A &(*)(const P2718R0::A &)' + // CHECK-NEXT: | | `-DeclRefExpr {{.*}} 'const P2718R0::A &(const P2718R0::A &)' lvalue Function {{.*}} 'df1' 'const P2718R0::A &(const P2718R0::A &)' (FunctionTemplate {{.*}} 'df1') + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'const P2718R0::A' lvalue extended by Var {{.*}} '__range1' 'const P2718R0::A &' + // CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'const P2718R0::A' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CallExpr {{.*}} 'P2718R0::A' + // CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'P2718R0::A (*)()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'P2718R0::A ()' lvalue Function {{.*}} 'dg' 'P2718R0::A ()' (FunctionTemplate {{.*}} 'dg') + for (auto e : df1(dg())) + sum += e; +} + +template int (&df2(const T *))[3]; +const A *dg2(const A &); + +void test9() { + // CHECK: FunctionDecl {{.*}} test9 'void ()' + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-CallExpr {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | |-ImplicitCastExpr {{.*}} 'int (&(*)(const P2718R0::A *))[3]' + // CHECK-NEXT: | | `-DeclRefExpr {{.*}} 'int (&(const P2718R0::A *))[3]' lvalue Function {{.*}} 'df2' 'int (&(const P2718R0::A *))[3]' (FunctionTemplate {{.*}} 'df2') + // CHECK-NEXT: | `-CallExpr {{.*}} 'const A *' + // CHECK-NEXT: | |-ImplicitCastExpr {{.*}} 'const A *(*)(const A &)' + // CHECK-NEXT: | | `-DeclRefExpr {{.*}} 'const A *(const A &)' lvalue Function {{.*}} 'dg2' 'const A *(const A &)' + // CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'const A':'const P2718R0::A' lvalue + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'const B':'const P2718R0::B' lvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'const B':'const P2718R0::B' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'B':'P2718R0::B' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CXXTemporaryObjectExpr {{.*}} 'B':'P2718R0::B' 'void () noexcept(false)' zeroing + for (auto e : df2(dg2(B()))) + bar(e); +} + +// Test discard statement && dependent context +void test10() { + int v[] = {42, 17, 13}; + + // CHECK: FunctionDecl {{.*}} test10 'void ()' + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-BinaryOperator {{.*}} 'int[3]' lvalue ',' + // CHECK-NEXT: | |-CXXStaticCastExpr {{.*}} 'void' static_cast + // CHECK-NEXT: | | `-CallExpr {{.*}} 'const P2718R0::LockGuard' lvalue + // CHECK-NEXT: | | |-ImplicitCastExpr {{.*}} 'const P2718R0::LockGuard &(*)(const P2718R0::LockGuard &)' + // CHECK-NEXT: | | | `-DeclRefExpr {{.*}} 'const P2718R0::LockGuard &(const P2718R0::LockGuard &)' lvalue Function {{.*}} 'df1' 'const P2718R0::LockGuard &(const P2718R0::LockGuard &)' (FunctionTemplate {{.*}} 'df1') + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'const LockGuard':'const P2718R0::LockGuard' lvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const LockGuard':'const P2718R0::LockGuard' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' 'void ()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int[3]' lvalue Var {{.*}} 'v' 'int[3]' + for ([[maybe_unused]] int x : static_cast(df1(LockGuard())), v) + LockGuard guard; + + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-BinaryOperator {{.*}} 'int[3]' lvalue ',' + // CHECK-NEXT: | |-CStyleCastExpr {{.*}} 'void' + // CHECK-NEXT: | | `-CallExpr {{.*}} 'const P2718R0::LockGuard' lvalue + // CHECK-NEXT: | | |-ImplicitCastExpr {{.*}} 'const P2718R0::LockGuard &(*)(const P2718R0::LockGuard &)' + // CHECK-NEXT: | | | `-DeclRefExpr {{.*}} 'const P2718R0::LockGuard &(const P2718R0::LockGuard &)' lvalue Function {{.*}} 'df1' 'const P2718R0::LockGuard &(const P2718R0::LockGuard &)' (FunctionTemplate {{.*}} 'df1') + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'const LockGuard':'const P2718R0::LockGuard' lvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const LockGuard':'const P2718R0::LockGuard' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' 'void ()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int[3]' lvalue Var {{.*}} 'v' 'int[3]' + for ([[maybe_unused]] int x : (void)df1(LockGuard()), v) + LockGuard guard; + + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-BinaryOperator {{.*}} 'int[3]' lvalue ',' + // CHECK-NEXT: | |-BinaryOperator {{.*}} 'const P2718R0::LockGuard' lvalue ',' + // CHECK-NEXT: | | |-CallExpr {{.*}} 'const P2718R0::LockGuard' lvalue + // CHECK-NEXT: | | | |-ImplicitCastExpr {{.*}} 'const P2718R0::LockGuard &(*)(const P2718R0::LockGuard &)' + // CHECK-NEXT: | | | | `-DeclRefExpr {{.*}} 'const P2718R0::LockGuard &(const P2718R0::LockGuard &)' lvalue Function {{.*}} 'df1' 'const P2718R0::LockGuard &(const P2718R0::LockGuard &)' (FunctionTemplate {{.*}} 'df1') + // CHECK-NEXT: | | | `-MaterializeTemporaryExpr {{.*}} 'const LockGuard':'const P2718R0::LockGuard' lvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | | `-ImplicitCastExpr {{.*}} 'const LockGuard':'const P2718R0::LockGuard' + // CHECK-NEXT: | | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | | `-CXXTemporaryObjectExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' 'void ()' + // CHECK-NEXT: | | `-CallExpr {{.*}} 'const P2718R0::LockGuard' lvalue + // CHECK-NEXT: | | |-ImplicitCastExpr {{.*}} 'const P2718R0::LockGuard &(*)(const P2718R0::LockGuard &)' + // CHECK-NEXT: | | | `-DeclRefExpr {{.*}} 'const P2718R0::LockGuard &(const P2718R0::LockGuard &)' lvalue Function {{.*}} 'df1' 'const P2718R0::LockGuard &(const P2718R0::LockGuard &)' (FunctionTemplate {{.*}} 'df1') + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'const LockGuard':'const P2718R0::LockGuard' lvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const LockGuard':'const P2718R0::LockGuard' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' 'void ()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int[3]' lvalue Var {{.*}} 'v' 'int[3]' + for ([[maybe_unused]] int x : df1(LockGuard()), df1(LockGuard()), v) + LockGuard guard; +} + +// Test default argument && dependent context +template int (&default_arg_fn2(const T & = T()))[3]; +void test11() { + for (auto e : default_arg_fn2()) + bar(e); +} + +template A foo2(const T&, const DefaultA &Default = DefaultA()); + +void test12() { + for (auto e : default_arg_fn2(foo2(foo2(foo2(A()))))) + bar(e); +} + +// Test member call && dependent context +void test13() { + + // CHECK: FunctionDecl {{.*}} test13 'void ()' + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'A &&' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'A':'P2718R0::A' xvalue + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .g {{.*}} + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .r {{.*}} + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .g {{.*}} + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .r {{.*}} + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .g {{.*}} + // CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue + // CHECK-NEXT: | `-MemberExpr {{.*}} '' .r {{.*}} + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CallExpr {{.*}} 'P2718R0::A' + // CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'P2718R0::A (*)()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'P2718R0::A ()' lvalue Function {{.*}} 'dg' 'P2718R0::A ()' (FunctionTemplate {{.*}} 'dg') + for (auto e : dg().r().g().r().g().r().g()) + bar(e); +} +} // namespace P2718R0 diff --git a/clang/test/CXX/special/class.temporary/p6.cpp b/clang/test/CXX/special/class.temporary/p6.cpp index 077385fb7aaaf..5554363cc69ab 100644 --- a/clang/test/CXX/special/class.temporary/p6.cpp +++ b/clang/test/CXX/special/class.temporary/p6.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -std=c++17 %s -triple x86_64-linux-gnu -emit-llvm -o - | FileCheck %s --implicit-check-not='call{{.*}}dtor' +// RUN: %clang_cc1 -std=c++23 %s -triple x86_64-linux-gnu -emit-llvm -o - | FileCheck %s --check-prefixes=CHECK-CXX23,CHECK-CXX23-NEXT,CHECK-CXX23-LABEL namespace std { typedef decltype(sizeof(int)) size_t; @@ -9,7 +10,36 @@ namespace std { size_t size; initializer_list() : begin(nullptr), size(0) {} }; -} + + template + struct list { + list() {} + ~list() {} + E *begin(); + E *end(); + const E *begin() const; + const E *end() const; + }; + + template + struct vector { + vector() {} + vector(std::initializer_list) {} + ~vector() {} + E *begin(); + E *end(); + const E *begin() const; + const E *end() const; + }; + + template + struct lock_guard { + lock_guard(T) {} + ~lock_guard() {} + }; + + struct mutex {}; +} // namespace std void then(); @@ -238,3 +268,282 @@ void init_capture_init_list() { // CHECK: call {{.*}}dtor // CHECK: } } + +namespace P2718R0 { +namespace basic { +template using T2 = std::list; +template const T2 &f1_temp(const T2 &t) { return t; } +template const T2 &f2_temp(T2 t) { return t; } +template T2 g_temp() { return T2{}; } + +template +void foo_dependent_context1() { + // CHECK-CXX23: void @_ZN7P2718R05basic22foo_dependent_context1IiEEvv() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + for (auto e : f1_temp(g_temp())) {} // OK, lifetime of return value of g() extended +} + +template +void foo_dependent_context2() { + // CHECK-CXX23: void @_ZN7P2718R05basic22foo_dependent_context2IiEEvv() + // CHECK-CXX23-NEXT: entry: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R05basic6g_tempIiEESt4listIT_Ev( + // CHECK-CXX23-NEXT: call {{.*}} @_ZN7P2718R05basic7f2_tempIiEERKSt4listIT_ES4_( + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + // CHECK-CXX23: call {{.*}} @_ZNKSt4listIiE5beginEv( + // CHECK-CXX23: call {{.*}} @_ZNKSt4listIiE3endEv( + for (auto e : f2_temp(g_temp())) {} // undefined behavior +} + +template void foo_dependent_context1(); +template void foo_dependent_context2(); +} // namespace basic + +namespace discard_value_expression { +template +void f_dependent_context1() { + std::vector v = { 42, 17, 13 }; + std::mutex m; + // CHECK-CXX23: void @_ZN7P2718R024discard_value_expression20f_dependent_context1IiEEvv() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt10lock_guardISt5mutexED1Ev( + for (T x : std::lock_guard(m), v) // lock released in C++ 2023 + std::lock_guard guard(m); // OK in C++ 2023, now deadlocks +} + +template +void f_dependent_context2() { + std::vector v = { 42, 17, 13 }; + std::mutex m; + // CHECK-CXX23: void @_ZN7P2718R024discard_value_expression20f_dependent_context2IiEEvv() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt10lock_guardISt5mutexED1Ev( + for (T x : (void)std::lock_guard(m), v) // lock released in C++ 2023 + std::lock_guard guard(m); // OK in C++ 2023, now deadlocks +} + +template +void f_dependent_context3() { + std::vector v = { 42, 17, 13 }; + std::mutex m; + // CHECK-CXX23: void @_ZN7P2718R024discard_value_expression20f_dependent_context3IiEEvv() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt10lock_guardISt5mutexED1Ev( + for (T x : static_cast(std::lock_guard(m)), v) // lock released in C++ 2023 + std::lock_guard guard(m); // OK in C++ 2023, now deadlocks +} + +template void f_dependent_context1(); +template void f_dependent_context2(); +template void f_dependent_context3(); +} // namespace discard_value_expression + +namespace member_call { +template +struct ListWrapper { + std::list list; + ListWrapper() {} + ~ListWrapper() {} + const T *begin() const { return list.begin(); } + const T *end() const { return list.end(); } + ListWrapper& r() { return *this; } + ListWrapper g() { return ListWrapper(); } +}; + +template +ListWrapper g_temp() { return ListWrapper{}; } + +template +void member_call_dependent_context() { + // CHECK-CXX23: void @_ZN7P2718R011member_call29member_call_dependent_contextIiEEvv() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev( + for (auto e : g_temp().r().g().r().g().r().g()) {} +} + +template void member_call_dependent_context(); +} // namespace member_call + +namespace default_arg { +template +struct DefaultArg { + DefaultArg() {} + DefaultArg(int) {} + ~DefaultArg() {} +}; + +template +struct C2 : public std::list { + C2() {} + C2(int, const C2 &, const DefaultArg &Default = DefaultArg{}) {} +}; + +template +std::list temp_foo(const std::list&, const DefaultArg &Default = DefaultArg{}) { + return std::list{}; +} + +template +void default_arg_dependent_context1() { + // CHECK-CXX23: void @_ZN7P2718R011default_arg30default_arg_dependent_context1IiEEvv() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + for (auto e : temp_foo(std::list{})) {} +} + +template +void default_arg_dependent_context2() { + // CHECK-CXX23: void @_ZN7P2718R011default_arg30default_arg_dependent_context2IiEEvv() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + for (auto e : temp_foo(temp_foo(std::list{}))) {} +} + +template +void default_arg_dependent_context3() { + // CHECK-CXX23: void @_ZN7P2718R011default_arg30default_arg_dependent_context3IiEEvv() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg2C2IiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg2C2IiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg2C2IiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg2C2IiED1Ev( + + for (auto e : C2(0, C2(0, C2(0, C2())))) {} +} + +template void default_arg_dependent_context1(); +template void default_arg_dependent_context2(); +template void default_arg_dependent_context3(); +} // namespace default_arg + +namespace basic { +using T = std::list; +const T& f1(const T& t) { return t; } +const T& f2(T t) { return t; } +T g() { return T{}; } + +void foo1() { + // CHECK-CXX23: void @_ZN7P2718R05basic4foo1Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + for (auto e : f1(g())) {} // OK, lifetime of return value of g() extended +} + +void foo2() { + // CHECK-CXX23: void @_ZN7P2718R05basic4foo2Ev() + // CHECK-CXX23-NEXT: call void @_ZN7P2718R05basic1gEv( + // CHECK-CXX23-NEXT: call {{.*}} @_ZN7P2718R05basic2f2ESt4listIiE( + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + for (auto e : f2(g())) {} // undefined behavior +} +} // namespace basic + +namespace discard_value_expression { +void f1() { + std::vector v = { 42, 17, 13 }; + std::mutex m; + // CHECK-CXX23: void @_ZN7P2718R024discard_value_expression2f1Ev() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt10lock_guardISt5mutexED1Ev( + for (int x : std::lock_guard(m), v) // lock released in C++ 2023 + std::lock_guard guard(m); // OK in C++ 2023, now deadlocks +} + +void f2() { + std::vector v = { 42, 17, 13 }; + std::mutex m; + // CHECK-CXX23: void @_ZN7P2718R024discard_value_expression2f2Ev() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt10lock_guardISt5mutexED1Ev( + for (int x : (void)std::lock_guard(m), v) // lock released in C++ 2023 + std::lock_guard guard(m); // OK in C++ 2023, now deadlocks +} + +void f3() { + std::vector v = { 42, 17, 13 }; + std::mutex m; + // CHECK-CXX23: void @_ZN7P2718R024discard_value_expression2f3Ev() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt10lock_guardISt5mutexED1Ev( + for (int x : static_cast(std::lock_guard(m)), v) // lock released in C++ 2023 + std::lock_guard guard(m); // OK in C++ 2023, now deadlocks +} +} // namespace discard_value_expression + +namespace member_call { +using A = ListWrapper; + +A g() { return A(); } +const A &f1(const A &t) { return t; } + +void member_call() { + // CHECK-CXX23: void @_ZN7P2718R011member_call11member_callEv() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev( + for (auto e : g().r().g().r().g().r().g()) {} +} +} // namespace member_call + +namespace default_arg { +using A = std::list; +using DefaultA = DefaultArg; +struct C : public A { + C() {} + C(int, const C &, const DefaultA & = DefaultA()) {} +}; + +A foo(const A&, const DefaultA &Default = DefaultA()) { + return A(); +} + +int (&some_func(const A & = A{}))[3]; + +void default_arg1() { + // CHECK-CXX23: void @_ZN7P2718R011default_arg12default_arg1Ev() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + for (auto e : some_func()) {} +} + +void default_arg2() { + // CHECK-CXX23: void @_ZN7P2718R011default_arg12default_arg2Ev() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZNSt4listIiED1Ev( + for (auto e : some_func(foo(foo(A())))) {} +} + +void default_arg3() { + // CHECK-CXX23: void @_ZN7P2718R011default_arg12default_arg3Ev() + // CHECK-CXX23-LABEL: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg1CD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg1CD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg1CD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg10DefaultArgIiED1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R011default_arg1CD1Ev( + for (auto e : C(0, C(0, C(0, C())))) {} +} +} // namespace default_arg +} // namespace P2718R0 + diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp index eb0e615f9470b..2650a3a82252b 100644 --- a/clang/test/Lexer/cxx-features.cpp +++ b/clang/test/Lexer/cxx-features.cpp @@ -309,7 +309,7 @@ #error "wrong value for __cpp_constexpr" #endif -#if check(range_based_for, 0, 200907, 200907, 201603, 201603, 201603, 201603) +#if check(range_based_for, 0, 200907, 200907, 201603, 201603, 202211, 202211) #error "wrong value for __cpp_range_based_for" #endif diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index a3c176d02129c..301f141b2f2b6 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -429,7 +429,7 @@

C++23 implementation status

Lifetime extension in range-based for loops
P2718R0 - No + Clang 19