https://github.com/avikivity created
https://github.com/llvm/llvm-project/pull/220234
09231ed02384 ("[clang] Delay dependent co_return promise calls") made
Sema::BuildCoreturnStmt return early for a type-dependent operand, before
reaching the ActOnFinishFullExpr() call that every other path performs.
Inside a lambda, a reference to a variable of an enclosing function is not
captured immediately: DoMarkPotentialCapture() only records it via
LambdaScopeInfo::addPotentialCapture(), and relies on ActOnFinishFullExpr() to
turn it into an actual capture and odr-use. Skipping that on the dependent path
silently drops the pending capture, so a variable mentioned only in the operand
of a dependent co_return never gets a capture field. When the generic lambda's
call operator is later instantiated from another context, Sema tries to capture
the variable again and rejects valid code with "reference to local variable 'p'
declared in enclosing function".
Finish the full-expression on the dependent path as well.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Fixes #220233
>From 86cc1fe1d1f7f4f654237773ca5ed652ab076799 Mon Sep 17 00:00:00 2001
From: Avi Kivity <[email protected]>
Date: Tue, 1 Sep 2026 15:25:40 +0300
Subject: [PATCH] [clang] Fix lost lambda capture in dependent co_return
operand
09231ed02384 ("[clang] Delay dependent co_return promise calls") made
Sema::BuildCoreturnStmt return early for a type-dependent operand, before
reaching the ActOnFinishFullExpr() call that every other path performs.
Inside a lambda, a reference to a variable of an enclosing function is not
captured immediately: DoMarkPotentialCapture() only records it via
LambdaScopeInfo::addPotentialCapture(), and relies on ActOnFinishFullExpr()
to turn it into an actual capture and odr-use. Skipping that on the
dependent path silently drops the pending capture, so a variable mentioned
only in the operand of a dependent co_return never gets a capture field.
When the generic lambda's call operator is later instantiated from another
context, Sema tries to capture the variable again and rejects valid code
with "reference to local variable 'p' declared in enclosing function".
Finish the full-expression on the dependent path as well.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Fixes #220233
---
clang/lib/Sema/SemaCoroutine.cpp | 10 +++++--
clang/test/SemaCXX/coreturn.cpp | 47 ++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp
index b46efba09500f..d22260b887415 100644
--- a/clang/lib/Sema/SemaCoroutine.cpp
+++ b/clang/lib/Sema/SemaCoroutine.cpp
@@ -1058,9 +1058,15 @@ StmtResult Sema::BuildCoreturnStmt(SourceLocation Loc,
Expr *E,
// A type-dependent operand can init to either void or non-void.
// Delay selecting return_void or return_value until template init
// rebuilds the co_return statement with the operand type.
- if (E && !isa<InitListExpr>(E) && E->isTypeDependent())
+ if (E && !isa<InitListExpr>(E) && E->isTypeDependent()) {
+ // Still finish the full-expression, so that potential captures in the
+ // operand are turned into actual captures of the enclosing lambda.
+ ExprResult FE = ActOnFinishFullExpr(E, /*DiscardedValue=*/false);
+ if (FE.isInvalid())
+ return StmtError();
return new (Context)
- CoreturnStmt(Loc, E, /*PromiseCall=*/nullptr, IsImplicit);
+ CoreturnStmt(Loc, FE.get(), /*PromiseCall=*/nullptr, IsImplicit);
+ }
VarDecl *Promise = FSI->CoroutinePromise;
ExprResult PC;
diff --git a/clang/test/SemaCXX/coreturn.cpp b/clang/test/SemaCXX/coreturn.cpp
index 8fb2506848c3d..8ed78d6f0b9a2 100644
--- a/clang/test/SemaCXX/coreturn.cpp
+++ b/clang/test/SemaCXX/coreturn.cpp
@@ -164,3 +164,50 @@ coro f(Ctx &ctx) {
void use(Ctx &ctx) { f<int>(ctx); }
}
+
+namespace dependent_coreturn_lambda_capture {
+// A variable of the enclosing function referenced only from the type-dependent
+// operand of a co_return must still be captured by the enclosing lambda.
+struct coro_void {
+ struct promise_type {
+ coro_void get_return_object();
+ suspend_never initial_suspend();
+ suspend_never final_suspend() noexcept;
+ void unhandled_exception();
+ void return_void();
+ };
+};
+
+struct coro_value {
+ struct promise_type {
+ coro_value get_return_object();
+ suspend_never initial_suspend();
+ suspend_never final_suspend() noexcept;
+ void unhandled_exception();
+ void return_value(int);
+ };
+};
+
+template <typename T> void use_void(int &, T);
+template <typename T> int use_value(int &, T);
+
+// Instantiates the generic lambda from a context that no longer has the
+// enclosing function's scopes on the stack.
+template <typename F> void call(F f) {
+ int t = 0;
+ f(t);
+}
+
+void captured_by_dependent_coreturn(int p) {
+ call([&](auto &t) -> coro_void {
+ if (t)
+ co_return;
+ co_return use_void(p, t);
+ });
+ call([&](auto &t) -> coro_value {
+ if (t)
+ co_return 0;
+ co_return use_value(p, t);
+ });
+}
+} // namespace dependent_coreturn_lambda_capture
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits