[clang] [Clang] Implement P2718R0 "Lifetime extension in range-based for loops" (PR #76361)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Dec 25 07:59:15 PST 2023
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: None (yronglin)
<details>
<summary>Changes</summary>
Implement P2718R0 "Lifetime extension in range-based for loops" (https://wg21.link/P2718R0)
Differential Revision: https://reviews.llvm.org/D153701
---
Patch is 43.09 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/76361.diff
10 Files Affected:
- (modified) clang/include/clang/Parse/Parser.h (+1-1)
- (modified) clang/include/clang/Sema/Sema.h (+33-1)
- (modified) clang/lib/Parse/ParseDecl.cpp (+19)
- (modified) clang/lib/Parse/ParseStmt.cpp (+1-1)
- (modified) clang/lib/Sema/SemaExpr.cpp (+18-4)
- (modified) clang/lib/Sema/SemaExprCXX.cpp (+26-20)
- (modified) clang/lib/Sema/SemaInit.cpp (+4)
- (modified) clang/lib/Sema/SemaStmt.cpp (+9-1)
- (added) clang/test/AST/ast-dump-for-range-lifetime.cpp (+355)
- (modified) clang/test/CXX/special/class.temporary/p6.cpp (+234)
``````````diff
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 2dbe090bd0932f..a467ff6157962c 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -2377,7 +2377,7 @@ class Parser : public CodeCompletionHandler {
struct ForRangeInit {
SourceLocation ColonLoc;
ExprResult RangeExpr;
-
+ SmallVector<MaterializeTemporaryExpr *, 8> 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 5e3b57ea33220b..250194f90d503c 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1338,6 +1338,12 @@ class Sema final {
/// context not already known to be immediately invoked.
llvm::SmallPtrSet<DeclRefExpr *, 4> ReferenceToConsteval;
+ /// P2718R0 - Lifetime extension in range-based for loops.
+ /// MaterializeTemporaryExprs in for-range-init expression which need to
+ /// extend lifetime. Add MaterializeTemporaryExpr* if the value of
+ /// IsInLifetimeExtendingContext is true.
+ SmallVector<MaterializeTemporaryExpr *, 8> ForRangeLifetimeExtendTemps;
+
/// \brief Describes whether we are in an expression constext which we have
/// to handle differently.
enum ExpressionKind {
@@ -1357,6 +1363,19 @@ class Sema final {
// VLAs).
bool InConditionallyConstantEvaluateContext = false;
+ /// Whether we are currently in a context in which temporaries must be
+ /// lifetime-extended (Eg. in a for-range initializer).
+ bool IsInLifetimeExtendingContext = false;
+
+ /// Whether we should materialize temporaries in discarded expressions.
+ ///
+ /// [C++23][class.temporary]/p2.6 when a prvalue that has type other than cv
+ /// void appears as a discarded-value expression ([expr.context]).
+ ///
+ /// We do not materialize temporaries by default in order to avoid creating
+ /// unnecessary temporary objects.
+ bool MaterializePRValueInDiscardedExpression = 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
@@ -5215,7 +5234,8 @@ class Sema final {
Stmt *LoopVar,
SourceLocation ColonLoc, Expr *Collection,
SourceLocation RParenLoc,
- BuildForRangeKind Kind);
+ BuildForRangeKind Kind,
+ ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
StmtResult BuildCXXForRangeStmt(SourceLocation ForLoc,
SourceLocation CoawaitLoc,
Stmt *InitStmt,
@@ -9956,6 +9976,18 @@ class Sema final {
return currentEvaluationContext().isImmediateFunctionContext();
}
+ bool isInLifetimeExtendingContext() const {
+ assert(!ExprEvalContexts.empty() &&
+ "Must be in an expression evaluation context");
+ return ExprEvalContexts.back().IsInLifetimeExtendingContext;
+ }
+
+ bool ShouldMaterializePRValueInDiscardedExpression() const {
+ assert(!ExprEvalContexts.empty() &&
+ "Must be in an expression evaluation context");
+ return ExprEvalContexts.back().MaterializePRValueInDiscardedExpression;
+ }
+
bool isCheckingDefaultArgumentOrInitializer() const {
const ExpressionEvaluationContextRecord &Ctx = currentEvaluationContext();
return (Ctx.Context ==
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index ed006f9d67de45..8b809aa9c3df26 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2312,12 +2312,31 @@ 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.IsInLifetimeExtendingContext = true;
+
+ // Materialize non-`cv void` prvalue temporaries in discarded
+ // expressions. These materialized temporaries may be lifetime-extented.
+ LastRecord.MaterializePRValueInDiscardedExpression = true;
+ }
+
if (getLangOpts().OpenMP)
Actions.startOpenMPCXXRangeFor();
if (Tok.is(tok::l_brace))
FRI->RangeExpr = ParseBraceInitializer();
else
FRI->RangeExpr = ParseExpression();
+ if (getLangOpts().CPlusPlus23)
+ 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 d0ff33bd1379ab..de883eb99765c9 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -2288,7 +2288,7 @@ 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);
+ T.getCloseLocation(), Sema::BFRK_Build, ForRangeInfo.LifetimeExtendTemps);
// Similarly, we need to do the semantic analysis for a for-range
// statement immediately in order to close over temporaries correctly.
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 960f513d1111b2..45f6f50e6f7385 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6256,7 +6256,7 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
assert(Param->hasDefaultArg() && "can't build nonexistent default arg");
bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();
-
+ bool IsInLifetimeExtendingContext = isInLifetimeExtendingContext();
std::optional<ExpressionEvaluationContextRecord::InitializationContext>
InitializationContext =
OutermostDeclarationWithDelayedImmediateInvocations();
@@ -6289,9 +6289,19 @@ 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 || IsInLifetimeExtendingContext) {
+ if (V.HasImmediateCalls)
+ ExprEvalContexts.back().DelayedDefaultInitializationContext = {
+ CallLoc, Param, CurContext};
+ // Pass down lifetime extendning flag, and collect temporaries in
+ // CreateMaterializeTemporaryExpr when we rewrite the call argument.
+ auto &LastRecord = ExprEvalContexts.back();
+ auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
+ LastRecord.IsInLifetimeExtendingContext = IsInLifetimeExtendingContext;
+
EnsureImmediateInvocationInDefaultArgs Immediate(*this);
ExprResult Res;
runWithSufficientStackSpace(CallLoc, [&] {
@@ -6305,6 +6315,10 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
if (Res.isInvalid())
return ExprError();
Init = Res.get();
+
+ // Collect MaterializeTemporaryExprs in the rewrited CXXDefaultArgExpr.
+ PrevRecord.ForRangeLifetimeExtendTemps.append(
+ LastRecord.ForRangeLifetimeExtendTemps);
}
}
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 4ae04358d5df7c..3590582fb5912e 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -8209,21 +8209,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
@@ -8244,11 +8229,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 (ShouldMaterializePRValueInDiscardedExpression() && 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 61d244f3bb9798..42e4d1eacce78d 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -8469,6 +8469,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 f0b03db690843a..759453f319e2b5 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2487,7 +2487,8 @@ StmtResult Sema::ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc,
SourceLocation CoawaitLoc, Stmt *InitStmt,
Stmt *First, SourceLocation ColonLoc,
Expr *Range, SourceLocation RParenLoc,
- BuildForRangeKind Kind) {
+ BuildForRangeKind Kind,
+ ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
// FIXME: recover in order to allow the body to be parsed.
if (!First)
return StmtError();
@@ -2539,6 +2540,12 @@ StmtResult Sema::ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc,
return StmtError();
}
+ if (getLangOpts().CPlusPlus23) {
+ auto Entity = InitializedEntity::InitializeVariable(RangeVar);
+ for (auto *MTE : LifetimeExtendTemps)
+ MTE->setExtendingDecl(RangeVar, Entity.allocateManglingNumber());
+ }
+
// Claim the type doesn't contain auto: we've already done the checking.
DeclGroupPtrTy RangeGroup =
BuildDeclaratorGroup(MutableArrayRef<Decl *>((Decl **)&RangeVar, 1));
@@ -2783,6 +2790,7 @@ StmtResult Sema::BuildCXXForRangeStmt(SourceLocation ForLoc,
LoopVar->setType(SubstAutoTypeDependent(LoopVar->getType()));
}
} else if (!BeginDeclStmt.get()) {
+
SourceLocation RangeLoc = RangeVar->getLocation();
const QualType RangeVarNonRefType = RangeVarType.getNonReferenceType();
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 00000000000000..dec2e29184526e
--- /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: | |-<<<NULL>>>
+ // 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 &)' <FunctionToPointerDecay>
+ // 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' <NoOp>
+ // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}})
+ // CHECK-NEXT: | | `-CallExpr {{.*}} 'A':'P2718R0::A'
+ // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'A (*)()' <FunctionToPointerDecay>
+ // 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: | |-<<<NULL>>>
+ // 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]' <FunctionToPointerDecay>
+ // 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 &)' <FunctionToPointerDecay>
+ // CHECK-NEXT: | | | `-DeclRefExpr {{.*}} 'const A *(const A &)' lvalue Function {{.*}} 'g' 'const A *(const A &)'
+ // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const A':'const P2718R0::A' lvalue <DerivedToBase (A)>
+ // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'const B':'const P2718R0::B' lvalue extended by Var {{.*}} '__range1' 'int (&)[3]'
+ // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const B':'const P2718R0::B' <NoOp>
+ // 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: |-<<<NULL>>>
+ // 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<void> <ToVoid>
+ // 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<void>(LockGuard()), v)
+ LockGuard guard;
+
+ // CHECK: -CXXForRangeStmt {{.*}}
+ // CHECK-NEXT: |-<<<NULL>>>
+ // 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' <ToVoid>
+ // 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: |-<<<NULL>>>
+ // 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() {}
+};
...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/76361
More information about the cfe-commits
mailing list