[clang] [Clang] Support initializing structured bindings from an array with direct-list-initialization (PR #102581)

Yanzuo Liu via cfe-commits cfe-commits at lists.llvm.org
Tue Aug 27 02:43:07 PDT 2024


https://github.com/zwuis updated https://github.com/llvm/llvm-project/pull/102581

>From 9d5d8d99db6f7fa0b6973fe55582de9d34740b19 Mon Sep 17 00:00:00 2001
From: Yanzuo Liu <zwuis at outlook.com>
Date: Fri, 9 Aug 2024 15:45:40 +0800
Subject: [PATCH 1/5] Support non-reference structured bindings with braced
 array as initializer

---
 clang/docs/ReleaseNotes.rst                |  2 +
 clang/include/clang/Sema/Initialization.h  |  4 ++
 clang/lib/Sema/SemaInit.cpp                | 82 +++++++++++++++++-----
 clang/test/SemaCXX/cxx1z-decomposition.cpp | 31 +++++++-
 4 files changed, 99 insertions(+), 20 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7beef7be0e6a53..4e725ce4770e99 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -211,6 +211,8 @@ Bug Fixes to C++ Support
 - Clang now preserves the unexpanded flag in a lambda transform used for pack expansion. (#GH56852), (#GH85667),
   (#GH99877).
 - Fixed a bug when diagnosing ambiguous explicit specializations of constrained member functions.
+- Clang now correctly handle non-reference structured bindings with braced
+  array as initializer. (#GH31813)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Sema/Initialization.h b/clang/include/clang/Sema/Initialization.h
index 4b876db436b48c..dfe3d5ff0a252c 100644
--- a/clang/include/clang/Sema/Initialization.h
+++ b/clang/include/clang/Sema/Initialization.h
@@ -1384,6 +1384,10 @@ class InitializationSequence {
 
   void AddParenthesizedListInitStep(QualType T);
 
+  /// Only used when initializing non-reference structured bindings from braced
+  /// array. Unwrap the initializer list to get the array for array copy.
+  void AddUnwrapInitListAtFirst(InitListExpr *Syntactic);
+
   /// Add steps to unwrap a initializer list for a reference around a
   /// single element and rewrap it at the end.
   void RewrapReferenceInitList(QualType T, InitListExpr *Syntactic);
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 2666e60c0dd67c..5884955838006e 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -4091,6 +4091,15 @@ void InitializationSequence::AddParenthesizedListInitStep(QualType T) {
   Steps.push_back(S);
 }
 
+void InitializationSequence::AddUnwrapInitListAtFirst(InitListExpr *Syntactic) {
+  assert(Syntactic->getNumInits() == 1 &&
+         "Can only unwrap trivial init lists.");
+  Step S;
+  S.Kind = SK_UnwrapInitList;
+  S.Type = Syntactic->getInit(0)->getType();
+  Steps.insert(Steps.begin(), S);
+}
+
 void InitializationSequence::RewrapReferenceInitList(QualType T,
                                                      InitListExpr *Syntactic) {
   assert(Syntactic->getNumInits() == 1 &&
@@ -4167,6 +4176,33 @@ static void MaybeProduceObjCObject(Sema &S,
   }
 }
 
+/// Initialize an array from another array
+static void TryArrayCopy(Sema &S, const InitializationKind &Kind,
+                         const InitializedEntity &Entity, Expr *Initializer,
+                         QualType DestType, InitializationSequence &Sequence,
+                         bool TreatUnavailableAsInvalid) {
+  // If source is a prvalue, use it directly.
+  if (Initializer->isPRValue()) {
+    Sequence.AddArrayInitStep(DestType, /*IsGNUExtension*/ false);
+    return;
+  }
+
+  // Emit element-at-a-time copy loop.
+  InitializedEntity Element =
+      InitializedEntity::InitializeElement(S.Context, 0, Entity);
+  QualType InitEltT =
+      S.Context.getAsArrayType(Initializer->getType())->getElementType();
+  OpaqueValueExpr OVE(Initializer->getExprLoc(), InitEltT,
+                      Initializer->getValueKind(),
+                      Initializer->getObjectKind());
+  Expr *OVEAsExpr = &OVE;
+  Sequence.InitializeFrom(S, Element, Kind, OVEAsExpr,
+                          /*TopLevelOfInitList*/ false,
+                          TreatUnavailableAsInvalid);
+  if (Sequence)
+    Sequence.AddArrayInitLoopStep(Entity.getType(), InitEltT);
+}
+
 static void TryListInitialization(Sema &S,
                                   const InitializedEntity &Entity,
                                   const InitializationKind &Kind,
@@ -4775,6 +4811,31 @@ static void TryListInitialization(Sema &S,
     }
     if (const ArrayType *DestAT = S.Context.getAsArrayType(DestType)) {
       Expr *SubInit[1] = {InitList->getInit(0)};
+
+      // C++17 [dcl.struct.bind]p1:
+      // ... If the assignment-expression in the initializer has array type A
+      // and no ref-qualifier is present, e has type cv A and each element is
+      // copy-initialized or direct-initialized from the corresponding element
+      // of the assignment-expression as specified by the form of the
+      // initializer.
+      //
+      // This is a special case not following list-initialization.
+      if (isa<ConstantArrayType>(DestAT) &&
+          Entity.getKind() == InitializedEntity::EK_Variable &&
+          isa<DecompositionDecl>(Entity.getDecl())) {
+        assert(
+            S.Context.hasSameUnqualifiedType(SubInit[0]->getType(), DestType) &&
+            "Deduced to other type?");
+        TryArrayCopy(S,
+                     InitializationKind::CreateCopy(Kind.getLocation(),
+                                                    InitList->getLBraceLoc()),
+                     Entity, SubInit[0], DestType, Sequence,
+                     TreatUnavailableAsInvalid);
+        if (Sequence)
+          Sequence.AddUnwrapInitListAtFirst(InitList);
+        return;
+      }
+
       if (!isa<VariableArrayType>(DestAT) &&
           IsStringInit(SubInit[0], DestAT, S.Context) == SIF_None) {
         InitializationKind SubKind =
@@ -6460,25 +6521,8 @@ void InitializationSequence::InitializeFrom(Sema &S,
         S.Context.hasSameUnqualifiedType(Initializer->getType(),
                                          Entity.getType()) &&
         canPerformArrayCopy(Entity)) {
-      // If source is a prvalue, use it directly.
-      if (Initializer->isPRValue()) {
-        AddArrayInitStep(DestType, /*IsGNUExtension*/false);
-        return;
-      }
-
-      // Emit element-at-a-time copy loop.
-      InitializedEntity Element =
-          InitializedEntity::InitializeElement(S.Context, 0, Entity);
-      QualType InitEltT =
-          Context.getAsArrayType(Initializer->getType())->getElementType();
-      OpaqueValueExpr OVE(Initializer->getExprLoc(), InitEltT,
-                          Initializer->getValueKind(),
-                          Initializer->getObjectKind());
-      Expr *OVEAsExpr = &OVE;
-      InitializeFrom(S, Element, Kind, OVEAsExpr, TopLevelOfInitList,
-                     TreatUnavailableAsInvalid);
-      if (!Failed())
-        AddArrayInitLoopStep(Entity.getType(), InitEltT);
+      TryArrayCopy(S, Kind, Entity, Initializer, DestType, *this,
+                   TreatUnavailableAsInvalid);
       return;
     }
 
diff --git a/clang/test/SemaCXX/cxx1z-decomposition.cpp b/clang/test/SemaCXX/cxx1z-decomposition.cpp
index 19c730303625ee..f05a962a3b3d21 100644
--- a/clang/test/SemaCXX/cxx1z-decomposition.cpp
+++ b/clang/test/SemaCXX/cxx1z-decomposition.cpp
@@ -198,4 +198,33 @@ namespace lambdas {
   }
 }
 
-// FIXME: by-value array copies
+namespace by_value_array_copy {
+  struct explicit_copy {
+    explicit_copy() = default; // expected-note 2{{candidate constructor not viable: requires 0 arguments, but 1 was provided}}
+    explicit explicit_copy(const explicit_copy&) = default; // expected-note 2{{explicit constructor is not a candidate}}
+  };
+
+  constexpr int direct_initialization_for_elements() {
+    int arr[3]{1, 2, 3};
+    auto [a1, b1, c1](arr);
+    explicit_copy ec_arr[2];
+    auto [a2, b2](ec_arr);
+    arr[0]--;
+    return a1 + b1 + c1 + arr[0];
+  }
+  static_assert(direct_initialization_for_elements() == 6);
+
+  void copy_initialization_for_elements() {
+    int arr[2]{1, 2};
+    auto [a1, b1] = arr;
+    auto [a2, b2]{arr}; // GH31813
+    explicit_copy ec_arr[2];
+    auto [a3, b3] = ec_arr; // expected-error {{no matching constructor for initialization of 'explicit_copy[2]'}}
+    auto [a4, b4]{ec_arr}; // expected-error {{no matching constructor for initialization of 'explicit_copy[2]'}}
+
+    // Test prvalue
+    using T = explicit_copy[2];
+    auto [a5, b5] = T{};
+    auto [a6, b6]{T{}};
+  }
+} // namespace by_value_array_copy

>From ad648f0744f8622d27ad03b15db34c1c0a94e67f Mon Sep 17 00:00:00 2001
From: Yanzuo Liu <zwuis at outlook.com>
Date: Sat, 24 Aug 2024 08:44:46 +0800
Subject: [PATCH 2/5] Modify comment, release note and name of function

---
 clang/docs/ReleaseNotes.rst               | 4 ++--
 clang/include/clang/Sema/Initialization.h | 7 ++++---
 clang/lib/Sema/SemaInit.cpp               | 4 ++--
 3 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 4e725ce4770e99..e46833716add4f 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -211,8 +211,8 @@ Bug Fixes to C++ Support
 - Clang now preserves the unexpanded flag in a lambda transform used for pack expansion. (#GH56852), (#GH85667),
   (#GH99877).
 - Fixed a bug when diagnosing ambiguous explicit specializations of constrained member functions.
-- Clang now correctly handle non-reference structured bindings with braced
-  array as initializer. (#GH31813)
+- Clang now correctly handle initializing array-typed structured bindings with
+  direct-list-initialization. (#GH31813)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Sema/Initialization.h b/clang/include/clang/Sema/Initialization.h
index dfe3d5ff0a252c..1affb19f360c00 100644
--- a/clang/include/clang/Sema/Initialization.h
+++ b/clang/include/clang/Sema/Initialization.h
@@ -1384,9 +1384,10 @@ class InitializationSequence {
 
   void AddParenthesizedListInitStep(QualType T);
 
-  /// Only used when initializing non-reference structured bindings from braced
-  /// array. Unwrap the initializer list to get the array for array copy.
-  void AddUnwrapInitListAtFirst(InitListExpr *Syntactic);
+  /// Only used when initializing array-typed structured bindings with
+  /// direct-list-initialization. Unwrap the initializer list to get the array
+  /// for array copy.
+  void AddUnwrapInitListAtTheBeginning(InitListExpr *Syntactic);
 
   /// Add steps to unwrap a initializer list for a reference around a
   /// single element and rewrap it at the end.
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 5884955838006e..a3d4a6329d0f40 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -4817,7 +4817,7 @@ static void TryListInitialization(Sema &S,
       // and no ref-qualifier is present, e has type cv A and each element is
       // copy-initialized or direct-initialized from the corresponding element
       // of the assignment-expression as specified by the form of the
-      // initializer.
+      // initializer. ...
       //
       // This is a special case not following list-initialization.
       if (isa<ConstantArrayType>(DestAT) &&
@@ -4832,7 +4832,7 @@ static void TryListInitialization(Sema &S,
                      Entity, SubInit[0], DestType, Sequence,
                      TreatUnavailableAsInvalid);
         if (Sequence)
-          Sequence.AddUnwrapInitListAtFirst(InitList);
+          Sequence.AddUnwrapInitListAtTheBeginning(InitList);
         return;
       }
 

>From a3c334780e7b8fe537fe4ae641cbae868da8e1be Mon Sep 17 00:00:00 2001
From: Yanzuo Liu <zwuis at outlook.com>
Date: Sat, 24 Aug 2024 09:57:52 +0800
Subject: [PATCH 3/5] Fix build

---
 clang/lib/Sema/SemaInit.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index b16d9ae45065bd..41b0b908375534 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -4091,7 +4091,8 @@ void InitializationSequence::AddParenthesizedListInitStep(QualType T) {
   Steps.push_back(S);
 }
 
-void InitializationSequence::AddUnwrapInitListAtFirst(InitListExpr *Syntactic) {
+void InitializationSequence::AddUnwrapInitListAtTheBeginning(
+    InitListExpr *Syntactic) {
   assert(Syntactic->getNumInits() == 1 &&
          "Can only unwrap trivial init lists.");
   Step S;

>From 9415a5068dc620cf9633033af904d831e47880c4 Mon Sep 17 00:00:00 2001
From: Yanzuo Liu <zwuis at outlook.com>
Date: Tue, 27 Aug 2024 17:09:30 +0800
Subject: [PATCH 4/5] Improve wording in release notes, comment and name of
 function (suggestions from cor3ntin)

---
 clang/docs/ReleaseNotes.rst               | 3 +--
 clang/include/clang/Sema/Initialization.h | 4 ++--
 clang/lib/Sema/SemaInit.cpp               | 4 ++--
 3 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c3022d11f88d47..ba4dd746bc619b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -302,8 +302,7 @@ Bug Fixes to C++ Support
   template depth than the friend function template. (#GH98258)
 - Clang now rebuilds the template parameters of out-of-line declarations and specializations in the context
   of the current instantiation in all cases.
-- Clang now correctly handle initializing array-typed structured bindings with
-  direct-list-initialization. (#GH31813)
+- Clang now correctly handles direct-list-initialization of a structured bindings from an array. (#GH31813)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Sema/Initialization.h b/clang/include/clang/Sema/Initialization.h
index 1affb19f360c00..0455e1fa5016bb 100644
--- a/clang/include/clang/Sema/Initialization.h
+++ b/clang/include/clang/Sema/Initialization.h
@@ -1384,10 +1384,10 @@ class InitializationSequence {
 
   void AddParenthesizedListInitStep(QualType T);
 
-  /// Only used when initializing array-typed structured bindings with
+  /// Only used when initializing structured bindings from an array with
   /// direct-list-initialization. Unwrap the initializer list to get the array
   /// for array copy.
-  void AddUnwrapInitListAtTheBeginning(InitListExpr *Syntactic);
+  void AddUnwrapInitListInitStep(InitListExpr *Syntactic);
 
   /// Add steps to unwrap a initializer list for a reference around a
   /// single element and rewrap it at the end.
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 41b0b908375534..5a19a3505454ca 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -4091,7 +4091,7 @@ void InitializationSequence::AddParenthesizedListInitStep(QualType T) {
   Steps.push_back(S);
 }
 
-void InitializationSequence::AddUnwrapInitListAtTheBeginning(
+void InitializationSequence::AddUnwrapInitListInitStep(
     InitListExpr *Syntactic) {
   assert(Syntactic->getNumInits() == 1 &&
          "Can only unwrap trivial init lists.");
@@ -4833,7 +4833,7 @@ static void TryListInitialization(Sema &S,
                      Entity, SubInit[0], DestType, Sequence,
                      TreatUnavailableAsInvalid);
         if (Sequence)
-          Sequence.AddUnwrapInitListAtTheBeginning(InitList);
+          Sequence.AddUnwrapInitListInitStep(InitList);
         return;
       }
 

>From 6923d279a81ef4d61ad01c49b3503700e2973959 Mon Sep 17 00:00:00 2001
From: Yanzuo Liu <zwuis at outlook.com>
Date: Tue, 27 Aug 2024 17:34:34 +0800
Subject: [PATCH 5/5] Improve test cases (suggestion from cor3ntin)

---
 clang/test/SemaCXX/cxx1z-decomposition.cpp | 27 ++++++++++++++--------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/clang/test/SemaCXX/cxx1z-decomposition.cpp b/clang/test/SemaCXX/cxx1z-decomposition.cpp
index f05a962a3b3d21..a8914fe4e9cd82 100644
--- a/clang/test/SemaCXX/cxx1z-decomposition.cpp
+++ b/clang/test/SemaCXX/cxx1z-decomposition.cpp
@@ -205,26 +205,33 @@ namespace by_value_array_copy {
   };
 
   constexpr int direct_initialization_for_elements() {
-    int arr[3]{1, 2, 3};
-    auto [a1, b1, c1](arr);
     explicit_copy ec_arr[2];
-    auto [a2, b2](ec_arr);
+    auto [a1, b1](ec_arr);
+
+    int arr[3]{1, 2, 3};
+    auto [a2, b2, c2](arr);
     arr[0]--;
-    return a1 + b1 + c1 + arr[0];
+    return a2 + b2 + c2 + arr[0];
   }
   static_assert(direct_initialization_for_elements() == 6);
 
-  void copy_initialization_for_elements() {
-    int arr[2]{1, 2};
+  constexpr int copy_initialization_for_elements() {
+    int arr[2]{4, 5};
     auto [a1, b1] = arr;
     auto [a2, b2]{arr}; // GH31813
+    arr[0] = 0;
+    return a1 + b1 + a2 + b2 + arr[0];
+  }
+  static_assert(copy_initialization_for_elements() == 18);
+
+  void copy_initialization_for_elements_with_explicit_copy_ctor() {
     explicit_copy ec_arr[2];
-    auto [a3, b3] = ec_arr; // expected-error {{no matching constructor for initialization of 'explicit_copy[2]'}}
-    auto [a4, b4]{ec_arr}; // expected-error {{no matching constructor for initialization of 'explicit_copy[2]'}}
+    auto [a1, b1] = ec_arr; // expected-error {{no matching constructor for initialization of 'explicit_copy[2]'}}
+    auto [a2, b2]{ec_arr}; // expected-error {{no matching constructor for initialization of 'explicit_copy[2]'}}
 
     // Test prvalue
     using T = explicit_copy[2];
-    auto [a5, b5] = T{};
-    auto [a6, b6]{T{}};
+    auto [a3, b3] = T{};
+    auto [a4, b4]{T{}};
   }
 } // namespace by_value_array_copy



More information about the cfe-commits mailing list