Skip to content

[clang] Implement CWG2611 #133747

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

[clang] Implement CWG2611 #133747

wants to merge 3 commits into from

Conversation

offsetof
Copy link
Contributor

CWG2611 "Missing parentheses in expansion of fold-expression could cause syntactic reinterpretation"

Fixes #57396

CWG2611 "Missing parentheses in expansion of fold-expression could cause
syntactic reinterpretation"
@offsetof offsetof requested a review from Endilll as a code owner March 31, 2025 16:43
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Mar 31, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 31, 2025

@llvm/pr-subscribers-clang

Author: None (offsetof)

Changes

CWG2611 "Missing parentheses in expansion of fold-expression could cause syntactic reinterpretation"

Fixes #57396


Full diff: https://github.com/llvm/llvm-project/pull/133747.diff

6 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/lib/Sema/TreeTransform.h (+12-3)
  • (modified) clang/test/CXX/drs/cwg26xx.cpp (+23)
  • (modified) clang/test/CXX/temp/temp.param/p10-2a.cpp (+4-4)
  • (modified) clang/test/SemaTemplate/instantiate-requires-expr.cpp (+10-10)
  • (modified) clang/www/cxx_dr_status.html (+1-1)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index daad01919ecd4..51b575f1b15d1 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -110,6 +110,7 @@ Resolutions to C++ Defect Reports
   two releases. The improvements to template template parameter matching implemented
   in the previous release, as described in P3310 and P3579, made this flag unnecessary.
 
+- Implemented `CWG2611 Missing parentheses in expansion of fold-expression could cause syntactic reinterpretation <https://cplusplus.github.io/CWG/issues/2611>`_
 - Implemented `CWG2918 Consideration of constraints for address of overloaded `
   `function <https://cplusplus.github.io/CWG/issues/2918.html>`_
 
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 8fdb2cf6dce6c..aa23da298864f 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -16441,14 +16441,23 @@ TreeTransform<Derived>::TransformCXXFoldExpr(CXXFoldExpr *E) {
       return true;
   }
 
-  if (ParenExpr *PE = dyn_cast_or_null<ParenExpr>(Result.get()))
-    PE->setIsProducedByFoldExpansion();
-
   // If we had no init and an empty pack, and we're not retaining an expansion,
   // then produce a fallback value or error.
   if (Result.isUnset())
     return getDerived().RebuildEmptyCXXFoldExpr(E->getEllipsisLoc(),
                                                 E->getOperator());
+
+  // Wrap the result in a ParenExpr if it isn't already one (see CWG2611).
+  ParenExpr *PE = dyn_cast<ParenExpr>(Result.get());
+  if (!PE) {
+    Result = getDerived().RebuildParenExpr(Result.get(), E->getLParenLoc(),
+                                           E->getRParenLoc());
+    if (Result.isInvalid())
+      return true;
+    PE = cast<ParenExpr>(Result.get());
+  }
+  PE->setIsProducedByFoldExpansion();
+
   return Result;
 }
 
diff --git a/clang/test/CXX/drs/cwg26xx.cpp b/clang/test/CXX/drs/cwg26xx.cpp
index 3eb70583b6026..5bb955dedd766 100644
--- a/clang/test/CXX/drs/cwg26xx.cpp
+++ b/clang/test/CXX/drs/cwg26xx.cpp
@@ -35,6 +35,29 @@ namespace std {
   template<typename T> T declval();
 } // namespace std
 
+namespace cwg2611 { // cwg2611: 21
+#if __cpp_fold_expressions >= 201603
+template<class> class R {};
+template<class... Ts> auto a(Ts... xs) -> R<decltype((xs + ...))>;
+template<class... Ts> auto b(Ts... xs) -> R<decltype((..., xs))>;
+template<class... Ts> auto c(Ts... xs, int i = 0) -> R<decltype((xs * ... * i))>;
+template<class... Ts> auto d(Ts... xs, int i = 0) -> R<decltype((i ^ ... ^ xs))>;
+R<int&> i = a(1);
+R<int&> j = b(1);
+R<int&> k = c();
+R<int&> l = d();
+
+template<int... Is> constexpr int w = 0 * (Is + ...);
+template<int... Is> constexpr int x = 0 * (... + Is);
+template<int... Is> constexpr int y = 0 * (Is + ... + 1);
+template<int... Is> constexpr int z = 0 * (1 + ... + Is);
+static_assert(w<1, 2> == 0);
+static_assert(x<1, 2> == 0);
+static_assert(y<1, 2> == 0);
+static_assert(z<1, 2> == 0);
+#endif
+} // namespace cwg2611
+
 namespace cwg2621 { // cwg2621: sup 2877
 #if __cplusplus >= 202002L
 enum class E { a };
diff --git a/clang/test/CXX/temp/temp.param/p10-2a.cpp b/clang/test/CXX/temp/temp.param/p10-2a.cpp
index 4f5fdd3b4809a..281c66bf9da90 100644
--- a/clang/test/CXX/temp/temp.param/p10-2a.cpp
+++ b/clang/test/CXX/temp/temp.param/p10-2a.cpp
@@ -27,10 +27,10 @@ using b4 = B<short, char>; // expected-error {{constraints not satisfied for ali
 
 template<typename... T>
 concept C3 = (sizeof(T) + ...) == 12;
-// expected-note@-1 {{because 'sizeof(char[11]) == 12' (11 == 12) evaluated to false}}
-// expected-note@-2 {{because 'sizeof(char[10]) == 12' (10 == 12) evaluated to false}}
-// expected-note@-3 3{{because 'sizeof(int) == 12' (4 == 12) evaluated to false}}
-// expected-note@-4 6{{because 'sizeof(short) == 12' (2 == 12) evaluated to false}}
+// expected-note@-1 {{because '(sizeof(char[11])) == 12' (11 == 12) evaluated to false}}
+// expected-note@-2 {{because '(sizeof(char[10])) == 12' (10 == 12) evaluated to false}}
+// expected-note@-3 3{{because '(sizeof(int)) == 12' (4 == 12) evaluated to false}}
+// expected-note@-4 6{{because '(sizeof(short)) == 12' (2 == 12) evaluated to false}}
 
 template<C3 T1, C3 T2, C3 T3>
 // expected-note@-1 {{because 'char[11]' does not satisfy 'C3'}}
diff --git a/clang/test/SemaTemplate/instantiate-requires-expr.cpp b/clang/test/SemaTemplate/instantiate-requires-expr.cpp
index a1f5456156a06..2930c5a2eb05d 100644
--- a/clang/test/SemaTemplate/instantiate-requires-expr.cpp
+++ b/clang/test/SemaTemplate/instantiate-requires-expr.cpp
@@ -35,7 +35,7 @@ using r1i2 = r1<char>; // expected-error {{constraints not satisfied for class t
 
 template<typename... Ts> requires
 false_v<requires (Ts... ts) {requires ((sizeof(ts) == 2) && ...);}>
-// expected-note@-1 {{because 'false_v<requires (short ts, unsigned short ts) { requires (sizeof (ts) == 2) && (sizeof (ts) == 2); }>'}}
+// expected-note@-1 {{because 'false_v<requires (short ts, unsigned short ts) { requires ((sizeof (ts) == 2) && (sizeof (ts) == 2)); }>'}}
 // expected-note@-2 {{because 'false_v<requires (short ts) { requires (sizeof (ts) == 2); }>' evaluated to false}}
 struct r2 {};
 
@@ -44,8 +44,8 @@ using r2i2 = r2<short>; // expected-error {{constraints not satisfied for class
 
 template<typename... Ts> requires
 false_v<(requires (Ts ts) {requires sizeof(ts) != 0;} && ...)>
-// expected-note@-1 {{because 'false_v<requires (short ts) { requires sizeof (ts) != 0; } && requires (unsigned short ts) { requires sizeof (ts) != 0; }>' evaluated to false}}
-// expected-note@-2 {{because 'false_v<requires (short ts) { requires sizeof (ts) != 0; }>' evaluated to false}}
+// expected-note@-1 {{because 'false_v<(requires (short ts) { requires sizeof (ts) != 0; } && requires (unsigned short ts) { requires sizeof (ts) != 0; })>' evaluated to false}}
+// expected-note@-2 {{because 'false_v<(requires (short ts) { requires sizeof (ts) != 0; })>' evaluated to false}}
 struct r3 {};
 
 using r3i1 = r3<short, unsigned short>; // expected-error {{constraints not satisfied for class template 'r3' [with Ts = <short, unsigned short>]}}
@@ -93,19 +93,19 @@ namespace type_requirement {
   // Parameter pack inside expr
   template<typename... Ts> requires
   false_v<(requires { typename Ts::type; } && ...)>
-  // expected-note@-1 {{because 'false_v<requires { typename identity<short>::type; } && requires { typename identity<int>::type; } && requires { <<error-type>>; }>' evaluated to false}}
+  // expected-note@-1 {{because 'false_v<(requires { typename identity<short>::type; } && requires { typename identity<int>::type; } && requires { <<error-type>>; })>' evaluated to false}}
   struct r5 {};
 
   using r5i = r5<identity<short>, identity<int>, short>; // expected-error{{constraints not satisfied for class template 'r5' [with Ts = <identity<short>, identity<int>, short>]}}
   template<typename... Ts> requires
-  false_v<(requires { typename void_t<Ts>; } && ...)> // expected-note{{because 'false_v<requires { typename void_t<int>; } && requires { typename void_t<short>; }>' evaluated to false}}
+  false_v<(requires { typename void_t<Ts>; } && ...)> // expected-note{{because 'false_v<(requires { typename void_t<int>; } && requires { typename void_t<short>; })>' evaluated to false}}
   struct r6 {};
 
   using r6i = r6<int, short>; // expected-error{{constraints not satisfied for class template 'r6' [with Ts = <int, short>]}}
 
   template<typename... Ts> requires
   false_v<(requires { typename Ts::template aaa<Ts>; } && ...)>
-  // expected-note@-1 {{because 'false_v<requires { <<error-type>>; } && requires { <<error-type>>; }>' evaluated to false}}
+  // expected-note@-1 {{because 'false_v<(requires { <<error-type>>; } && requires { <<error-type>>; })>' evaluated to false}}
   struct r7 {};
 
   using r7i = r7<int, A>; // expected-error{{constraints not satisfied for class template 'r7' [with Ts = <int, type_requirement::A>]}}
@@ -160,7 +160,7 @@ namespace expr_requirement {
 
   template<typename... Ts> requires
   false_v<(requires { { 0 } noexcept -> C1<Ts>; } && ...)>
-  // expected-note@-1 {{because 'false_v<requires { { 0 } noexcept -> C1<int>; } && requires { { 0 } noexcept -> C1<unsigned int>; }>' evaluated to false}}
+  // expected-note@-1 {{because 'false_v<(requires { { 0 } noexcept -> C1<int>; } && requires { { 0 } noexcept -> C1<unsigned int>; })>' evaluated to false}}
   struct r3 {};
 
   using r3i = r3<int, unsigned int>; // expected-error{{constraints not satisfied for class template 'r3' [with Ts = <int, unsigned int>]}}
@@ -199,7 +199,7 @@ namespace nested_requirement {
   // Parameter pack inside expr
   template<typename... Ts> requires
   false_v<(requires { requires sizeof(Ts) == 0; } && ...)>
-  // expected-note@-1 {{because 'false_v<requires { requires sizeof(int) == 0; } && requires { requires sizeof(short) == 0; }>' evaluated to false}}
+  // expected-note@-1 {{because 'false_v<(requires { requires sizeof(int) == 0; } && requires { requires sizeof(short) == 0; })>' evaluated to false}}
   struct r2 {};
 
   using r2i = r2<int, short>; // expected-error{{constraints not satisfied for class template 'r2' [with Ts = <int, short>]}}
@@ -208,14 +208,14 @@ namespace nested_requirement {
 // Parameter pack inside multiple requirements
 template<typename... Ts> requires
 false_v<(requires { requires sizeof(Ts) == 0; sizeof(Ts); } && ...)>
-// expected-note@-1 {{because 'false_v<requires { requires sizeof(int) == 0; sizeof(Ts); } && requires { requires sizeof(short) == 0; sizeof(Ts); }>' evaluated to false}}
+// expected-note@-1 {{because 'false_v<(requires { requires sizeof(int) == 0; sizeof(Ts); } && requires { requires sizeof(short) == 0; sizeof(Ts); })>' evaluated to false}}
 struct r4 {};
 
 using r4i = r4<int, short>; // expected-error{{constraints not satisfied for class template 'r4' [with Ts = <int, short>]}}
 
 template<typename... Ts> requires
 false_v<(requires(Ts t) { requires sizeof(t) == 0; t++; } && ...)>
-// expected-note@-1 {{because 'false_v<requires (int t) { requires sizeof (t) == 0; t++; } && requires (short t) { requires sizeof (t) == 0; t++; }>' evaluated to false}}
+// expected-note@-1 {{because 'false_v<(requires (int t) { requires sizeof (t) == 0; t++; } && requires (short t) { requires sizeof (t) == 0; t++; })>' evaluated to false}}
 struct r5 {};
 
 using r5i = r5<int, short>; // expected-error{{constraints not satisfied for class template 'r5' [with Ts = <int, short>]}}
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 138f12facf0ad..55d16a5da0174 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -15513,7 +15513,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
     <td><a href="https://cplusplus.github.io/CWG/issues/2611.html">2611</a></td>
     <td>C++23</td>
     <td>Missing parentheses in expansion of fold-expression could cause syntactic reinterpretation</td>
-    <td class="unknown" align="center">Unknown</td>
+    <td class="unreleased" align="center">Clang 21</td>
   </tr>
   <tr id="2612">
     <td><a href="https://cplusplus.github.io/CWG/issues/2612.html">2612</a></td>

@@ -35,6 +35,29 @@ namespace std {
template<typename T> T declval();
} // namespace std

namespace cwg2611 { // cwg2611: 21
#if __cpp_fold_expressions >= 201603
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#if __cpp_fold_expressions >= 201603
#if __cplusplus >= 201703L

@Endilll Endilll requested a review from cor3ntin April 1, 2025 04:07
Replace feature-test macro with `__cplusplus`
@offsetof offsetof requested a review from Endilll April 2, 2025 17:12
// Wrap the result in a ParenExpr if it isn't already one (see CWG2611).
ParenExpr *PE = dyn_cast<ParenExpr>(Result.get());
if (!PE) {
Result = getDerived().RebuildParenExpr(Result.get(), E->getLParenLoc(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would create the Paren Expr directly here, we don't need to do all of the work RebuildParenExpr is doing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks; updated.

Comment on lines +39 to +49
#if __cplusplus >= 201703L
template<class> class R {};
template<class... Ts> auto a(Ts... xs) -> R<decltype((xs + ...))>;
template<class... Ts> auto b(Ts... xs) -> R<decltype((..., xs))>;
template<class... Ts> auto c(Ts... xs, int i = 0) -> R<decltype((xs * ... * i))>;
template<class... Ts> auto d(Ts... xs, int i = 0) -> R<decltype((i ^ ... ^ xs))>;
R<int&> i = a(1);
R<int&> j = b(1);
R<int&> k = c();
R<int&> l = d();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add __is_same_as tests as in the issue, that's the (only) tests we get wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These testcases are checking the same thing as the one in the issue (if I understand correctly what you're referring to). Could you clarify what you think is missing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CWG 2611 decltype and fold-expressions
4 participants