diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -128,6 +128,7 @@ non-constexpr functions and constructors. - Clang now supports `requires cplusplus23` for module maps. - Implemented `P2564R3: consteval needs to propagate up `_. +- Implemented `P2718R0: Lifetime extension in range-based for loops `_. C++2c Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1047,6 +1047,10 @@ /// for-range statement. unsigned CXXForRangeDecl : 1; + /// Whether this variable is the for-range-initializer in a for-range + /// statement. + unsigned CXXForRangeInitializer : 1; + /// Whether this variable is the for-in loop declaration in Objective-C. unsigned ObjCForDecl : 1; @@ -1477,6 +1481,17 @@ NonParmVarDeclBits.CXXForRangeDecl = FRD; } + bool isCXXForRangeInitializer() const { + return isa(this) || !isImplicit() + ? false + : NonParmVarDeclBits.CXXForRangeInitializer; + } + + void setCXXForRangeInitializer(bool FRI) { + assert(!isa(this) && isImplicit()); + NonParmVarDeclBits.CXXForRangeInitializer = FRI; + } + /// Determine whether this variable is a for-loop declaration for a /// for-in statement in Objective-C. bool isObjCForDecl() const { 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 @@ -6919,7 +6919,8 @@ } ExprResult ActOnFinishFullExpr(Expr *Expr, SourceLocation CC, bool DiscardedValue, bool IsConstexpr = false, - bool IsTemplateArgument = false); + bool IsTemplateArgument = false, + bool IsForRangeInit = false); StmtResult ActOnFinishFullStmt(Stmt *Stmt); // Marks SS invalid if it represents an incomplete type. diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -13390,9 +13390,10 @@ // struct T { S a, b; } t = { Temp(), Temp() } // // we should destroy the first Temp before constructing the second. - ExprResult Result = - ActOnFinishFullExpr(Init, VDecl->getLocation(), - /*DiscardedValue*/ false, VDecl->isConstexpr()); + ExprResult Result = ActOnFinishFullExpr( + Init, VDecl->getLocation(), + /*DiscardedValue*/ false, VDecl->isConstexpr(), + /*IsTemplateArgument*/ false, VDecl->isCXXForRangeInitializer()); if (Result.isInvalid()) { VDecl->setInvalidDecl(); return; 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 @@ -8807,7 +8807,8 @@ ExprResult Sema::ActOnFinishFullExpr(Expr *FE, SourceLocation CC, bool DiscardedValue, bool IsConstexpr, - bool IsTemplateArgument) { + bool IsTemplateArgument, + bool IsForRangeInit) { ExprResult FullExpr = FE; if (!FullExpr.get()) @@ -8890,13 +8891,28 @@ // - Teach the handful of places that iterate over FunctionScopes to // stop at the outermost enclosing lexical scope." DeclContext *DC = CurContext; - while (DC && isa(DC)) + while (isa_and_nonnull(DC)) DC = DC->getParent(); const bool IsInLambdaDeclContext = isLambdaCallOperator(DC); if (IsInLambdaDeclContext && CurrentLSI && CurrentLSI->hasPotentialCaptures() && !FullExpr.isInvalid()) CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(FE, CurrentLSI, *this); + // [P2718R0] Lifetime extension in range-based for loops. + // + // 6.7.7 [class.temporary] p5: + // There are four contexts in which temporaries are destroyed at a different + // point than the end of the full-expression. + // + // 6.7.7 [class.temporary] p6: + // The fourth context is when a temporary object other than a function + // parameter object is created in the for-range-initializer of a range-based + // for statement. If such a temporary object would otherwise be destroyed at + // the end of the for-range-initializer full-expression, the object persists + // for the lifetime of the reference initialized by the for-range-initializer. + if (getLangOpts().CPlusPlus20 && IsForRangeInit) + return FullExpr; + return MaybeCreateExprWithCleanups(FullExpr); } 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 @@ -2529,6 +2529,7 @@ VarDecl *RangeVar = BuildForRangeVarDecl(*this, RangeLoc, Context.getAutoRRefDeductType(), std::string("__range") + DepthStr); + RangeVar->setCXXForRangeInitializer(true); if (FinishForRangeVarDecl(*this, RangeVar, Range, RangeLoc, diag::err_for_range_deduction_failure)) { ActOnInitializerError(LoopVar); diff --git a/clang/test/CodeGenCXX/for-range-temporaries.cpp b/clang/test/CodeGenCXX/for-range-temporaries.cpp --- a/clang/test/CodeGenCXX/for-range-temporaries.cpp +++ b/clang/test/CodeGenCXX/for-range-temporaries.cpp @@ -1,9 +1,10 @@ -// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -UDESUGAR %s | FileCheck %s -// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -DDESUGAR %s | FileCheck %s -// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -UDESUGAR -DTEMPLATE %s | FileCheck %s -// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -DDESUGAR -DTEMPLATE %s | FileCheck %s -// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -UDESUGAR -DTEMPLATE -DDEPENDENT %s | FileCheck %s -// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -DDESUGAR -DTEMPLATE -DDEPENDENT %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -UDESUGAR %s | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NO-EXT-LIFETIME +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -DDESUGAR %s | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NO-EXT-LIFETIME +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -UDESUGAR -DTEMPLATE %s | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NO-EXT-LIFETIME +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -DDESUGAR -DTEMPLATE %s | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NO-EXT-LIFETIME +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -UDESUGAR -DTEMPLATE -DDEPENDENT %s | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NO-EXT-LIFETIME +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++11 -emit-llvm -o - -DDESUGAR -DTEMPLATE -DDEPENDENT %s | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NO-EXT-LIFETIME +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -std=c++20 -emit-llvm -o - -UDESUGAR %s | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-EXT-LIFETIME struct A { A(); @@ -103,8 +104,8 @@ // CHECK: call void @_ZN1BC1Ev( // CHECK: call void @_ZN1CC1ERK1B( // CHECK: call void @_ZN1DC1ERK1C( -// CHECK: call void @_ZN1CD1Ev( -// CHECK: call void @_ZN1BD1Ev( +// CHECK-NO-EXT-LIFETIME: call void @_ZN1CD1Ev( +// CHECK-NO-EXT-LIFETIME: call void @_ZN1BD1Ev( // CHECK: call void @_ZN1DC1ERKS_( // CHECK: call void @_Z5begin1D( // CHECK: call void @_ZN1DD1Ev( @@ -122,6 +123,8 @@ // CHECK: [[CLEANUP]]: // CHECK: call void @_ZN1ED1Ev( // CHECK: call void @_ZN1ED1Ev( +// CHECK-EXT-LIFETIME: call void @_ZN1CD1Ev( +// CHECK-EXT-LIFETIME: call void @_ZN1BD1Ev( // In for-range: // call void @_ZN1DD1Ev( // CHECK: br label %[[END:.*]] @@ -142,5 +145,6 @@ // CHECK: [[END]]: // In desugared version: // call void @_ZN1DD1Ev( +// CHECK-EXT-LIFETIME: call void @_ZN1DD1Ev( // CHECK: call void @_ZN1AD1Ev( // CHECK: ret void diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -413,7 +413,7 @@ Lifetime extension in range-based for loops P2718R0 - No + Clang 17