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 }


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to