[clang] 98e83ad - [Clang] Fix coro_await_elidable breaking with parenthesized expressions (#178495)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 2 08:44:16 PST 2026
Author: Yuxuan Chen
Date: 2026-02-02T08:44:11-08:00
New Revision: 98e83adc0104025b6127a128124d7d5079c4e4cb
URL: https://github.com/llvm/llvm-project/commit/98e83adc0104025b6127a128124d7d5079c4e4cb
DIFF: https://github.com/llvm/llvm-project/commit/98e83adc0104025b6127a128124d7d5079c4e4cb.diff
LOG: [Clang] Fix coro_await_elidable breaking with parenthesized expressions (#178495)
The `applySafeElideContext` function used `IgnoreImplicit()` to find the
underlying CallExpr, but this didn't strip `ParenExpr` nodes. When code
like `co_await (fn(leaf()))` was parsed, the operand was wrapped in a
`ParenExpr`, causing safe elide attribution to fail in Clang stage.
This fix chains `IgnoreImplicit()->IgnoreParens()->IgnoreImplicit()` to
handle both orderings of implicit nodes and parentheses in the AST.
Fixes https://github.com/llvm/llvm-project/issues/178256. Except that
`IgnoreParen()` should be a better suggestion than
`IgnoreParenImpCast()`.
Added:
Modified:
clang/lib/Sema/SemaCoroutine.cpp
clang/test/CodeGenCoroutines/coro-await-elidable.cpp
Removed:
################################################################################
diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp
index c0aba832dba94..90af7340c4614 100644
--- a/clang/lib/Sema/SemaCoroutine.cpp
+++ b/clang/lib/Sema/SemaCoroutine.cpp
@@ -18,6 +18,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/AST/IgnoreExpr.h"
#include "clang/AST/StmtCXX.h"
#include "clang/Basic/Builtins.h"
#include "clang/Lex/Preprocessor.h"
@@ -841,7 +842,11 @@ static bool isAttributedCoroAwaitElidable(const QualType &QT) {
}
static void applySafeElideContext(Expr *Operand) {
- auto *Call = dyn_cast<CallExpr>(Operand->IgnoreImplicit());
+ // Strip both implicit nodes and parentheses to find the underlying CallExpr.
+ // The AST may have these in either order, so we apply both transformations
+ // iteratively until reaching a fixed point.
+ auto *Call = dyn_cast<CallExpr>(IgnoreExprNodes(
+ Operand, IgnoreImplicitSingleStep, IgnoreParensSingleStep));
if (!Call || !Call->isPRValue())
return;
diff --git a/clang/test/CodeGenCoroutines/coro-await-elidable.cpp b/clang/test/CodeGenCoroutines/coro-await-elidable.cpp
index deb19b4a50043..71c56f310b344 100644
--- a/clang/test/CodeGenCoroutines/coro-await-elidable.cpp
+++ b/clang/test/CodeGenCoroutines/coro-await-elidable.cpp
@@ -124,4 +124,54 @@ Task<int> elidableWithPackRecursive() {
co_return co_await sumAll(addTasks(returnSame(1), returnSame(2)), returnSame(3));
}
+// Test that parenthesized expressions don't break HALO
+// CHECK-LABEL: define{{.*}} @_Z14withParenthesev{{.*}} {
+Task<int> withParenthese() {
+ // CHECK: call void @_Z6calleev(ptr {{.*}}) #[[ELIDE_SAFE]]
+ co_return co_await (callee());
+}
+
+// Test nested parentheses
+// CHECK-LABEL: define{{.*}} @_Z20withNestedParenthesev{{.*}} {
+Task<int> withNestedParenthese() {
+ // CHECK: call void @_Z6calleev(ptr {{.*}}) #[[ELIDE_SAFE]]
+ co_return co_await ((callee()));
+}
+
+// Test parentheses with elidable argument
+// CHECK-LABEL: define{{.*}} @_Z21withParenArgsElidablev{{.*}} {
+Task<int> withParenArgsElidable() {
+ // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2) #[[ELIDE_SAFE]]
+ // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3){{$}}
+ co_return co_await (addTasks(returnSame(2), returnSame(3)));
+}
+
+// Test parentheses around elidable argument expressions
+// CHECK-LABEL: define{{.*}} @_Z24withParenInsideArgsFirstv{{.*}} {
+Task<int> withParenInsideArgsFirst() {
+ // Argument wrapped in parens should still be elidable
+ // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 4) #[[ELIDE_SAFE]]
+ // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 5){{$}}
+ co_return co_await addTasks((returnSame(4)), returnSame(5));
+}
+
+// CHECK-LABEL: define{{.*}} @_Z25withParenInsideArgsSecondv{{.*}} {
+Task<int> withParenInsideArgsSecond() {
+ // Both arguments wrapped in parens, first should still be elidable
+ // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 6) #[[ELIDE_SAFE]]
+ // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 7){{$}}
+ co_return co_await addTasks((returnSame(6)), (returnSame(7)));
+}
+
+// Test operator overloading scenario (like the `operator|` case)
+Task<int> operator|(int, [[clang::coro_await_elidable_argument]] Task<int> &&t) {
+ co_return co_await t;
+}
+
+// CHECK-LABEL: define{{.*}} @_Z15withOperatorOldv{{.*}} {
+Task<int> withOperatorOld() {
+ // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 8) #[[ELIDE_SAFE]]
+ co_return co_await (0 | returnSame(8));
+}
+
// CHECK: attributes #[[ELIDE_SAFE]] = { coro_elide_safe }
More information about the cfe-commits
mailing list