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 @@ -1353,6 +1353,21 @@ bool IsCurrentlyCheckingDefaultArgumentOrInitializer = false; + /// Whether we are currently checking C++ for-range-init variable. + /// Eg. Parsing the init expression or extending the lifetime of temporaries + /// in the init expression. + bool IsCheckingCXXForRangeInitVariable = false; + + /// Whether we should materialize temporary the non `cv void` prvalue in + /// discard statement. + /// + /// [6.7.7.2.6] when a prvalue that has type other than cv void appears as a + /// discarded-value expression ([expr.context]). + /// + /// We do not materialize temporay by default in order to avoid creating + /// unnecessary temporary objects. + bool MaterializePRValueInDiscardStatement = 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 @@ -9835,6 +9850,18 @@ return ExprEvalContexts.back().isImmediateFunctionContext(); } + bool isCheckingCXXForRangeInitVariable() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + return ExprEvalContexts.back().IsCheckingCXXForRangeInitVariable; + } + + bool isMaterializePRValueInDiscardStatement() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + return ExprEvalContexts.back().MaterializePRValueInDiscardStatement; + } + bool isCheckingDefaultArgumentOrInitializer() const { assert(!ExprEvalContexts.empty() && "Must be in an expression evaluation context"); diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2238,6 +2238,23 @@ 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.IsCheckingCXXForRangeInitVariable = true; + + // Materialize non-`cv void` prvalue temporaries in discard statement + // during parsing. These materialized temporaries may be extented + // lifetime. + LastRecord.MaterializePRValueInDiscardStatement = true; + } + if (getLangOpts().OpenMP) Actions.startOpenMPCXXRangeFor(); if (Tok.is(tok::l_brace)) 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 @@ -6240,9 +6240,13 @@ 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 || isCheckingCXXForRangeInitVariable()) { + if (V.HasImmediateCalls) + ExprEvalContexts.back().DelayedDefaultInitializationContext = { + CallLoc, Param, CurContext}; EnsureImmediateInvocationInDefaultArgs Immediate(*this); ExprResult Res; runWithSufficientStackSpace(CallLoc, [&] { @@ -18166,7 +18170,10 @@ ExprEvalContexts.back().InImmediateEscalatingFunctionContext = Prev.InImmediateEscalatingFunctionContext; - + ExprEvalContexts.back().IsCheckingCXXForRangeInitVariable = + Prev.IsCheckingCXXForRangeInitVariable; + ExprEvalContexts.back().MaterializePRValueInDiscardStatement = + Prev.MaterializePRValueInDiscardStatement; Cleanup.reset(); if (!MaybeODRUseExprs.empty()) std::swap(MaybeODRUseExprs, ExprEvalContexts.back().SavedMaybeODRUseExprs); diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -8185,21 +8185,6 @@ 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 @@ -8220,11 +8205,33 @@ // 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 temporay 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 (isMaterializePRValueInDiscardStatement() && + 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 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -7357,12 +7357,13 @@ }); } -static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path, +static void visitLocalsRetainedByInitializer(Sema &S, IndirectLocalPath &Path, Expr *Init, LocalVisitor Visit, bool RevisitSubinits, bool EnableLifetimeWarnings); -static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path, +static void visitLocalsRetainedByReferenceBinding(Sema &S, + IndirectLocalPath &Path, Expr *Init, ReferenceKind RK, LocalVisitor Visit, bool EnableLifetimeWarnings); @@ -7448,7 +7449,7 @@ return false; } -static void handleGslAnnotatedTypes(IndirectLocalPath &Path, Expr *Call, +static void handleGslAnnotatedTypes(Sema &S, IndirectLocalPath &Path, Expr *Call, LocalVisitor Visit) { auto VisitPointerArg = [&](const Decl *D, Expr *Arg, bool Value) { // We are not interested in the temporary base objects of gsl Pointers: @@ -7469,11 +7470,11 @@ : IndirectLocalPathEntry::GslReferenceInit, Arg, D}); if (Arg->isGLValue()) - visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding, + visitLocalsRetainedByReferenceBinding(S, Path, Arg, RK_ReferenceBinding, Visit, /*EnableLifetimeWarnings=*/true); else - visitLocalsRetainedByInitializer(Path, Arg, Visit, true, + visitLocalsRetainedByInitializer(S, Path, Arg, Visit, true, /*EnableLifetimeWarnings=*/true); Path.pop_back(); }; @@ -7544,7 +7545,7 @@ return false; } -static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call, +static void visitLifetimeBoundArguments(Sema &S, IndirectLocalPath &Path, Expr *Call, LocalVisitor Visit) { const FunctionDecl *Callee; ArrayRef Args; @@ -7571,29 +7572,31 @@ auto VisitLifetimeBoundArg = [&](const Decl *D, Expr *Arg) { Path.push_back({IndirectLocalPathEntry::LifetimeBoundCall, Arg, D}); if (Arg->isGLValue()) - visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding, + visitLocalsRetainedByReferenceBinding(S, Path, Arg, RK_ReferenceBinding, Visit, /*EnableLifetimeWarnings=*/false); else - visitLocalsRetainedByInitializer(Path, Arg, Visit, true, + visitLocalsRetainedByInitializer(S, Path, Arg, Visit, true, /*EnableLifetimeWarnings=*/false); Path.pop_back(); }; - if (ObjectArg && implicitObjectParamIsLifetimeBound(Callee)) + if (ObjectArg && (implicitObjectParamIsLifetimeBound(Callee) || + S.isCheckingCXXForRangeInitVariable())) VisitLifetimeBoundArg(Callee, ObjectArg); for (unsigned I = 0, N = std::min(Callee->getNumParams(), Args.size()); I != N; ++I) { - if (Callee->getParamDecl(I)->hasAttr()) + if (Callee->getParamDecl(I)->hasAttr() || + S.isCheckingCXXForRangeInitVariable()) VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]); } } /// Visit the locals that would be reachable through a reference bound to the /// glvalue expression \c Init. -static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path, +static void visitLocalsRetainedByReferenceBinding(Sema &S, IndirectLocalPath &Path, Expr *Init, ReferenceKind RK, LocalVisitor Visit, bool EnableLifetimeWarnings) { @@ -7615,8 +7618,16 @@ // Step over any subobject adjustments; we may have a materialized // temporary inside them. - Init = const_cast(Init->skipRValueSubobjectAdjustments()); + SmallVector CommaLHS; + SmallVector Adjustments; + Init = const_cast( + Init->skipRValueSubobjectAdjustments(CommaLHS, Adjustments)); + llvm::for_each(CommaLHS, [&](const Expr *LHS) { + visitLocalsRetainedByReferenceBinding(S, Path, const_cast(LHS), + RK_ReferenceBinding, Visit, + EnableLifetimeWarnings); + }); // Per current approach for DR1376, look through casts to reference type // when performing lifetime extension. if (CastExpr *CE = dyn_cast(Init)) @@ -7633,7 +7644,7 @@ else // We can't lifetime extend through this but we might still find some // retained temporaries. - return visitLocalsRetainedByInitializer(Path, Init, Visit, true, + return visitLocalsRetainedByInitializer(S, Path, Init, Visit, true, EnableLifetimeWarnings); } @@ -7644,18 +7655,24 @@ {IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()}); Init = DIE->getExpr(); } + + if (auto *DAE = dyn_cast(Init)) { + Path.push_back( + {IndirectLocalPathEntry::DefaultInit, DAE, DAE->getParam()}); + Init = DAE->getExpr(); + } } while (Init != Old); if (auto *MTE = dyn_cast(Init)) { if (Visit(Path, Local(MTE), RK)) - visitLocalsRetainedByInitializer(Path, MTE->getSubExpr(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, MTE->getSubExpr(), Visit, true, EnableLifetimeWarnings); } if (isa(Init)) { if (EnableLifetimeWarnings) - handleGslAnnotatedTypes(Path, Init, Visit); - return visitLifetimeBoundArguments(Path, Init, Visit); + handleGslAnnotatedTypes(S, Path, Init, Visit); + return visitLifetimeBoundArguments(S, Path, Init, Visit); } switch (Init->getStmtClass()) { @@ -7674,7 +7691,7 @@ break; } else if (VD->getInit() && !isVarOnPath(Path, VD)) { Path.push_back({IndirectLocalPathEntry::VarInit, DRE, VD}); - visitLocalsRetainedByReferenceBinding(Path, VD->getInit(), + visitLocalsRetainedByReferenceBinding(S, Path, VD->getInit(), RK_ReferenceBinding, Visit, EnableLifetimeWarnings); } @@ -7688,13 +7705,13 @@ // handling all sorts of rvalues passed to a unary operator. const UnaryOperator *U = cast(Init); if (U->getOpcode() == UO_Deref) - visitLocalsRetainedByInitializer(Path, U->getSubExpr(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, U->getSubExpr(), Visit, true, EnableLifetimeWarnings); break; } case Stmt::OMPArraySectionExprClass: { - visitLocalsRetainedByInitializer(Path, + visitLocalsRetainedByInitializer(S, Path, cast(Init)->getBase(), Visit, true, EnableLifetimeWarnings); break; @@ -7704,10 +7721,10 @@ case Stmt::BinaryConditionalOperatorClass: { auto *C = cast(Init); if (!C->getTrueExpr()->getType()->isVoidType()) - visitLocalsRetainedByReferenceBinding(Path, C->getTrueExpr(), RK, Visit, + visitLocalsRetainedByReferenceBinding(S, Path, C->getTrueExpr(), RK, Visit, EnableLifetimeWarnings); if (!C->getFalseExpr()->getType()->isVoidType()) - visitLocalsRetainedByReferenceBinding(Path, C->getFalseExpr(), RK, Visit, + visitLocalsRetainedByReferenceBinding(S, Path, C->getFalseExpr(), RK, Visit, EnableLifetimeWarnings); break; } @@ -7721,7 +7738,7 @@ /// Visit the locals that would be reachable through an object initialized by /// the prvalue expression \c Init. -static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path, +static void visitLocalsRetainedByInitializer(Sema &S, IndirectLocalPath &Path, Expr *Init, LocalVisitor Visit, bool RevisitSubinits, bool EnableLifetimeWarnings) { @@ -7757,19 +7774,19 @@ // initializer. Path.push_back({IndirectLocalPathEntry::LValToRVal, CE}); return visitLocalsRetainedByReferenceBinding( - Path, Init, RK_ReferenceBinding, + S, Path, Init, RK_ReferenceBinding, [&](IndirectLocalPath &Path, Local L, ReferenceKind RK) -> bool { if (auto *DRE = dyn_cast(L)) { auto *VD = dyn_cast(DRE->getDecl()); if (VD && VD->getType().isConstQualified() && VD->getInit() && !isVarOnPath(Path, VD)) { Path.push_back({IndirectLocalPathEntry::VarInit, DRE, VD}); - visitLocalsRetainedByInitializer(Path, VD->getInit(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, VD->getInit(), Visit, true, EnableLifetimeWarnings); } } else if (auto *MTE = dyn_cast(L)) { if (MTE->getType().isConstQualified()) - visitLocalsRetainedByInitializer(Path, MTE->getSubExpr(), Visit, + visitLocalsRetainedByInitializer(S, Path, MTE->getSubExpr(), Visit, true, EnableLifetimeWarnings); } return false; @@ -7802,7 +7819,7 @@ // Model array-to-pointer decay as taking the address of the array // lvalue. Path.push_back({IndirectLocalPathEntry::AddressOf, CE}); - return visitLocalsRetainedByReferenceBinding(Path, CE->getSubExpr(), + return visitLocalsRetainedByReferenceBinding(S, Path, CE->getSubExpr(), RK_ReferenceBinding, Visit, EnableLifetimeWarnings); @@ -7818,7 +7835,7 @@ // initializing an initializer_list object from the array extends the // lifetime of the array exactly like binding a reference to a temporary. if (auto *ILE = dyn_cast(Init)) - return visitLocalsRetainedByReferenceBinding(Path, ILE->getSubExpr(), + return visitLocalsRetainedByReferenceBinding(S, Path, ILE->getSubExpr(), RK_StdInitializerList, Visit, EnableLifetimeWarnings); @@ -7830,13 +7847,13 @@ return; if (ILE->isTransparent()) - return visitLocalsRetainedByInitializer(Path, ILE->getInit(0), Visit, + return visitLocalsRetainedByInitializer(S, Path, ILE->getInit(0), Visit, RevisitSubinits, EnableLifetimeWarnings); if (ILE->getType()->isArrayType()) { for (unsigned I = 0, N = ILE->getNumInits(); I != N; ++I) - visitLocalsRetainedByInitializer(Path, ILE->getInit(I), Visit, + visitLocalsRetainedByInitializer(S, Path, ILE->getInit(I), Visit, RevisitSubinits, EnableLifetimeWarnings); return; @@ -7850,13 +7867,13 @@ // bound to temporaries, those temporaries are also lifetime-extended. if (RD->isUnion() && ILE->getInitializedFieldInUnion() && ILE->getInitializedFieldInUnion()->getType()->isReferenceType()) - visitLocalsRetainedByReferenceBinding(Path, ILE->getInit(0), + visitLocalsRetainedByReferenceBinding(S, Path, ILE->getInit(0), RK_ReferenceBinding, Visit, EnableLifetimeWarnings); else { unsigned Index = 0; for (; Index < RD->getNumBases() && Index < ILE->getNumInits(); ++Index) - visitLocalsRetainedByInitializer(Path, ILE->getInit(Index), Visit, + visitLocalsRetainedByInitializer(S, Path, ILE->getInit(Index), Visit, RevisitSubinits, EnableLifetimeWarnings); for (const auto *I : RD->fields()) { @@ -7866,14 +7883,14 @@ continue; Expr *SubInit = ILE->getInit(Index); if (I->getType()->isReferenceType()) - visitLocalsRetainedByReferenceBinding(Path, SubInit, + visitLocalsRetainedByReferenceBinding(S, Path, SubInit, RK_ReferenceBinding, Visit, EnableLifetimeWarnings); else // This might be either aggregate-initialization of a member or // initialization of a std::initializer_list object. Regardless, // we should recursively lifetime-extend that initializer. - visitLocalsRetainedByInitializer(Path, SubInit, Visit, + visitLocalsRetainedByInitializer(S, Path, SubInit, Visit, RevisitSubinits, EnableLifetimeWarnings); ++Index; @@ -7895,10 +7912,10 @@ if (Cap.capturesVariable()) Path.push_back({IndirectLocalPathEntry::LambdaCaptureInit, E, &Cap}); if (E->isGLValue()) - visitLocalsRetainedByReferenceBinding(Path, E, RK_ReferenceBinding, + visitLocalsRetainedByReferenceBinding(S, Path, E, RK_ReferenceBinding, Visit, EnableLifetimeWarnings); else - visitLocalsRetainedByInitializer(Path, E, Visit, true, + visitLocalsRetainedByInitializer(S, Path, E, Visit, true, EnableLifetimeWarnings); if (Cap.capturesVariable()) Path.pop_back(); @@ -7913,7 +7930,7 @@ Expr *Arg = MTE->getSubExpr(); Path.push_back({IndirectLocalPathEntry::TemporaryCopy, Arg, CCE->getConstructor()}); - visitLocalsRetainedByInitializer(Path, Arg, Visit, true, + visitLocalsRetainedByInitializer(S, Path, Arg, Visit, true, /*EnableLifetimeWarnings*/false); Path.pop_back(); } @@ -7922,8 +7939,8 @@ if (isa(Init) || isa(Init)) { if (EnableLifetimeWarnings) - handleGslAnnotatedTypes(Path, Init, Visit); - return visitLifetimeBoundArguments(Path, Init, Visit); + handleGslAnnotatedTypes(S, Path, Init, Visit); + return visitLifetimeBoundArguments(S, Path, Init, Visit); } switch (Init->getStmtClass()) { @@ -7939,7 +7956,7 @@ return; Path.push_back({IndirectLocalPathEntry::AddressOf, UO}); - visitLocalsRetainedByReferenceBinding(Path, UO->getSubExpr(), + visitLocalsRetainedByReferenceBinding(S, Path, UO->getSubExpr(), RK_ReferenceBinding, Visit, EnableLifetimeWarnings); } @@ -7954,10 +7971,10 @@ break; if (BO->getLHS()->getType()->isPointerType()) - visitLocalsRetainedByInitializer(Path, BO->getLHS(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, BO->getLHS(), Visit, true, EnableLifetimeWarnings); else if (BO->getRHS()->getType()->isPointerType()) - visitLocalsRetainedByInitializer(Path, BO->getRHS(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, BO->getRHS(), Visit, true, EnableLifetimeWarnings); break; } @@ -7968,10 +7985,10 @@ // In C++, we can have a throw-expression operand, which has 'void' type // and isn't interesting from a lifetime perspective. if (!C->getTrueExpr()->getType()->isVoidType()) - visitLocalsRetainedByInitializer(Path, C->getTrueExpr(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, C->getTrueExpr(), Visit, true, EnableLifetimeWarnings); if (!C->getFalseExpr()->getType()->isVoidType()) - visitLocalsRetainedByInitializer(Path, C->getFalseExpr(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, C->getFalseExpr(), Visit, true, EnableLifetimeWarnings); break; } @@ -8125,6 +8142,12 @@ return false; } + if (isCheckingCXXForRangeInitVariable()) { + MTE->setExtendingDecl(ExtendingEntity->getDecl(), + ExtendingEntity->allocateManglingNumber()); + return true; + } + switch (shouldLifetimeExtendThroughPath(Path)) { case PathLifetimeKind::Extend: // Update the storage duration of the materialized temporary. @@ -8330,11 +8353,11 @@ diag::warn_dangling_lifetime_pointer, SourceLocation()); llvm::SmallVector Path; if (Init->isGLValue()) - visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding, - TemporaryVisitor, + visitLocalsRetainedByReferenceBinding(*this, Path, Init, + RK_ReferenceBinding, TemporaryVisitor, EnableLifetimeWarnings); else - visitLocalsRetainedByInitializer(Path, Init, TemporaryVisitor, false, + visitLocalsRetainedByInitializer(*this, Path, Init, TemporaryVisitor, false, EnableLifetimeWarnings); } 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 @@ -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" @@ -2779,6 +2780,17 @@ LoopVar->setType(SubstAutoTypeDependent(LoopVar->getType())); } } else if (!BeginDeclStmt.get()) { + // P2718R0 - Lifetime extension in range-based for loops. + if (getLangOpts().CPlusPlus23) { + EnterExpressionEvaluationContext RangeVarContext( + *this, ExpressionEvaluationContext::PotentiallyEvaluated); + auto &LastRecord = ExprEvalContexts.back(); + LastRecord.IsCheckingCXXForRangeInitVariable = true; + Cleanup = LastRecord.ParentCleanup; + auto Entity = InitializedEntity::InitializeVariable(RangeVar); + checkInitializerLifetime(Entity, RangeVar->getInit()); + } + SourceLocation RangeLoc = RangeVar->getLocation(); const QualType RangeVarNonRefType = RangeVarType.getNonReferenceType(); diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -8494,9 +8494,23 @@ if (Init.isInvalid()) return StmtError(); - StmtResult Range = getDerived().TransformStmt(S->getRangeStmt()); - if (Range.isInvalid()) - return StmtError(); + StmtResult Range = S->getRangeStmt(); + { + EnterExpressionEvaluationContext RangeVarContext( + getSema(), Sema::ExpressionEvaluationContext::PotentiallyEvaluated, + /*LambdaContextDecl=*/nullptr, + Sema::ExpressionEvaluationContextRecord::EK_Other, + getSema().getLangOpts().CPlusPlus23); + if (getSema().getLangOpts().CPlusPlus23) { + auto &LastRecord = getSema().ExprEvalContexts.back(); + LastRecord.IsCheckingCXXForRangeInitVariable = true; + getSema().Cleanup = LastRecord.ParentCleanup; + } + + Range = getDerived().TransformStmt(S->getRangeStmt()); + if (Range.isInvalid()) + return StmtError(); + } StmtResult Begin = getDerived().TransformStmt(S->getBeginStmt()); if (Begin.isInvalid()) 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 --- /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]':'int[3]' lvalue + // CHECK-NEXT: | | `-CallExpr {{.*}} 'int[3]':'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':'const P2718R0::A' lvalue + // CHECK-NEXT: | `-CallExpr {{.*}} 'const P2718R0::A':'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':'const P2718R0::A' lvalue extended by Var {{.*}} '__range1' 'const P2718R0::A &' + // CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'const P2718R0::A':'const P2718R0::A' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'P2718R0::A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CallExpr {{.*}} 'P2718R0::A':'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]':'int[3]' lvalue + // CHECK-NEXT: | `-CallExpr {{.*}} 'int[3]':'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':'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':'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':'const P2718R0::LockGuard' lvalue ',' + // CHECK-NEXT: | | |-CallExpr {{.*}} 'const P2718R0::LockGuard':'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':'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':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'P2718R0::A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CallExpr {{.*}} 'P2718R0::A':'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 --- 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 namespace std { typedef decltype(sizeof(int)) size_t; @@ -238,3 +239,236 @@ // CHECK: call {{.*}}dtor // CHECK: } } + +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-CXX23: void @_ZN7P2718R05test1Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + 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-CXX23: void @_ZN7P2718R05test2Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01BD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + for (auto e : f(g(B()))) + bar(e); +} + +// Test discard statement. +struct LockGuard { + LockGuard() {} + ~LockGuard() {} +}; + +void test3() { + int v[] = {42, 17, 13}; + + // CHECK-CXX23: void @_ZN7P2718R05test3Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R09LockGuardD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + for ([[maybe_unused]] int x : static_cast(LockGuard()), v) + LockGuard guard; + + // CHECK-CXX23: for.cond.cleanup11: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R09LockGuardD1Ev( + // CHECK-CXX23-NEXT: br label %for.end17 + for ([[maybe_unused]] int x : (void)LockGuard(), v) + LockGuard guard; + + // CHECK-CXX23: for.cond.cleanup27: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R09LockGuardD1Ev( + // CHECK-CXX23-NEXT: br label %for.end33 + for ([[maybe_unused]] int x : LockGuard(), v) + LockGuard guard; +} + +// Test default arg +int (&default_arg_fn(const A & = A()))[3]; +void test4() { + + // CHECK-CXX23: void @_ZN7P2718R05test4Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + for (auto e : default_arg_fn()) + bar(e); +} + +struct DefaultA { + DefaultA() {} + ~DefaultA() {} +}; + +A foo(const A&, const DefaultA &Default = DefaultA()) { + return A(); +} + +void test5() { + // CHECK-CXX23: void @_ZN7P2718R05test5Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R08DefaultAD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R08DefaultAD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R08DefaultAD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + 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() { + // CHECK-CXX23: void @_ZN7P2718R05test6Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01CD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R08DefaultAD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01CD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R08DefaultAD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01CD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R08DefaultAD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01CD1Ev( + // CHECK-CXX23: br label %for.end + for (auto e : C(0, C(0, C(0, C())))) + bar(e); +} + +// Test member call +void test7() { + // CHECK-CXX23: void @_ZN7P2718R05test7Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + 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-CXX23: void @_ZN7P2718R05test8Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + for (auto e : df1(dg())) + sum += e; +} + +template int (&df2(const T *))[3]; +const A *dg2(const A &); + +void test9() { + // CHECK-CXX23: void @_ZN7P2718R05test9Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01BD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + for (auto e : df2(dg2(B()))) + bar(e); +} + +// Test discard statement && dependent context +void test10() { + int v[] = {42, 17, 13}; + + // CHECK-CXX23: void @_ZN7P2718R06test10Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R09LockGuardD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + for ([[maybe_unused]] int x : static_cast(df1(LockGuard())), v) + LockGuard guard; + + // CHECK-CXX23: for.cond.cleanup12: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R09LockGuardD1Ev( + // CHECK-CXX23-NEXT: br label %for.inc16 + for ([[maybe_unused]] int x : (void)df1(LockGuard()), v) + LockGuard guard; + + // CHECK-CXX23: for.cond.cleanup31: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R09LockGuardD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R09LockGuardD1Ev( + // CHECK-CXX23-NEXT: br label %for.end37 + 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() { + // CHECK-CXX23: void @_ZN7P2718R06test11Ev() + // CHECK-CXX23-NEXT: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + for (auto e : default_arg_fn2()) + bar(e); +} + +template A foo2(const T&, const DefaultA &Default = DefaultA()); + +void test12() { + // CHECK-CXX23: void @_ZN7P2718R06test12Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R08DefaultAD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R08DefaultAD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R08DefaultAD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + for (auto e : default_arg_fn2(foo2(foo2(foo2(A()))))) + bar(e); +} + +// Test member call && dependent context +void test13() { + + // CHECK-CXX23: void @_ZN7P2718R06test13Ev() + // CHECK-CXX23: for.cond.cleanup: + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: call void @_ZN7P2718R01AD1Ev( + // CHECK-CXX23-NEXT: br label %for.end + for (auto e : dg().r().g().r().g().r().g()) + bar(e); +} +} // namespace P2718R0 +