[clang] [Clang] Implement CWG 2628 "Implicit deduction guides should propagate constraints" (PR #111143)

Younan Zhang via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 4 05:41:45 PDT 2024


https://github.com/zyn0217 created https://github.com/llvm/llvm-project/pull/111143

Closes https://github.com/llvm/llvm-project/issues/98592


>From 567177e658ede96a59a22af14c9472e232391487 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Fri, 4 Oct 2024 20:22:59 +0800
Subject: [PATCH] [Clang] Implement CWG 2628 "Implicit deduction guides should
 propagate constraints"

---
 clang/docs/ReleaseNotes.rst                   |   3 +
 clang/lib/Sema/SemaTemplateDeductionGuide.cpp |  70 ++++++--
 clang/test/CXX/drs/cwg26xx.cpp                |  19 +-
 clang/test/SemaTemplate/deduction-guide.cpp   | 163 ++++++++++++++++++
 clang/www/cxx_dr_status.html                  |   2 +-
 5 files changed, 227 insertions(+), 30 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 6e7a5fb76b602b..a88c00ea038091 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -215,6 +215,9 @@ Resolutions to C++ Defect Reports
 - Clang now allows trailing requires clause on explicit deduction guides.
   (`CWG2707: Deduction guides cannot have a trailing requires-clause <https://cplusplus.github.io/CWG/issues/2707.html>`_).
 
+- Respect constructor constraints during CTAD.
+  (`CWG2628: Implicit deduction guides should propagate constraints <https://cplusplus.github.io/CWG/issues/2628.html>`_).
+
 C Language Changes
 ------------------
 
diff --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
index 545da21183c3c4..dc9eb1b83b03fe 100644
--- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
+++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
@@ -195,12 +195,14 @@ class ExtractTypeForDeductionGuide
 // A deduction guide can be either a template or a non-template function
 // declaration. If \p TemplateParams is null, a non-template function
 // declaration will be created.
-NamedDecl *buildDeductionGuide(
-    Sema &SemaRef, TemplateDecl *OriginalTemplate,
-    TemplateParameterList *TemplateParams, CXXConstructorDecl *Ctor,
-    ExplicitSpecifier ES, TypeSourceInfo *TInfo, SourceLocation LocStart,
-    SourceLocation Loc, SourceLocation LocEnd, bool IsImplicit,
-    llvm::ArrayRef<TypedefNameDecl *> MaterializedTypedefs = {}) {
+NamedDecl *
+buildDeductionGuide(Sema &SemaRef, TemplateDecl *OriginalTemplate,
+                    TemplateParameterList *TemplateParams,
+                    CXXConstructorDecl *Ctor, ExplicitSpecifier ES,
+                    TypeSourceInfo *TInfo, SourceLocation LocStart,
+                    SourceLocation Loc, SourceLocation LocEnd, bool IsImplicit,
+                    llvm::ArrayRef<TypedefNameDecl *> MaterializedTypedefs = {},
+                    Expr *FunctionTrailingRC = nullptr) {
   DeclContext *DC = OriginalTemplate->getDeclContext();
   auto DeductionGuideName =
       SemaRef.Context.DeclarationNames.getCXXDeductionGuideName(
@@ -211,9 +213,9 @@ NamedDecl *buildDeductionGuide(
       TInfo->getTypeLoc().castAs<FunctionProtoTypeLoc>().getParams();
 
   // Build the implicit deduction guide template.
-  auto *Guide =
-      CXXDeductionGuideDecl::Create(SemaRef.Context, DC, LocStart, ES, Name,
-                                    TInfo->getType(), TInfo, LocEnd, Ctor);
+  auto *Guide = CXXDeductionGuideDecl::Create(
+      SemaRef.Context, DC, LocStart, ES, Name, TInfo->getType(), TInfo, LocEnd,
+      Ctor, DeductionCandidate::Normal, FunctionTrailingRC);
   Guide->setImplicit(IsImplicit);
   Guide->setParams(Params);
 
@@ -355,10 +357,11 @@ struct ConvertConstructorToDeductionGuideTransform {
     //       template arguments) of the constructor, if any.
     TemplateParameterList *TemplateParams =
         SemaRef.GetTemplateParameterList(Template);
+    SmallVector<TemplateArgument, 16> Depth1Args;
+    Expr *OuterRC = TemplateParams->getRequiresClause();
     if (FTD) {
       TemplateParameterList *InnerParams = FTD->getTemplateParameters();
       SmallVector<NamedDecl *, 16> AllParams;
-      SmallVector<TemplateArgument, 16> Depth1Args;
       AllParams.reserve(TemplateParams->size() + InnerParams->size());
       AllParams.insert(AllParams.begin(), TemplateParams->begin(),
                        TemplateParams->end());
@@ -391,7 +394,7 @@ struct ConvertConstructorToDeductionGuideTransform {
               /*EvaluateConstraint=*/false);
         }
 
-        assert(NewParam->getTemplateDepth() == 0 &&
+        assert(getDepthAndIndex(NewParam).first == 0 &&
                "Unexpected template parameter depth");
 
         AllParams.push_back(NewParam);
@@ -407,10 +410,11 @@ struct ConvertConstructorToDeductionGuideTransform {
         Args.addOuterRetainedLevel();
         if (NestedPattern)
           Args.addOuterRetainedLevels(NestedPattern->getTemplateDepth());
-        ExprResult E = SemaRef.SubstExpr(InnerRC, Args);
-        if (E.isInvalid())
+        ExprResult E =
+            SemaRef.SubstConstraintExprWithoutSatisfaction(InnerRC, Args);
+        if (!E.isUsable())
           return nullptr;
-        RequiresClause = E.getAs<Expr>();
+        RequiresClause = E.get();
       }
 
       TemplateParams = TemplateParameterList::Create(
@@ -446,10 +450,46 @@ struct ConvertConstructorToDeductionGuideTransform {
       return nullptr;
     TypeSourceInfo *NewTInfo = TLB.getTypeSourceInfo(SemaRef.Context, NewType);
 
+    // At this point, the function parameters are already 'instantiated' in the
+    // current scope. Substitute into the constructor's trailing
+    // requires-clause, if any.
+    Expr *FunctionTrailingRC = nullptr;
+    if (Expr *RC = CD->getTrailingRequiresClause()) {
+      MultiLevelTemplateArgumentList Args;
+      Args.setKind(TemplateSubstitutionKind::Rewrite);
+      Args.addOuterTemplateArguments(Depth1Args);
+      Args.addOuterRetainedLevel();
+      if (NestedPattern)
+        Args.addOuterRetainedLevels(NestedPattern->getTemplateDepth());
+      ExprResult E = SemaRef.SubstConstraintExprWithoutSatisfaction(RC, Args);
+      if (!E.isUsable())
+        return nullptr;
+      FunctionTrailingRC = E.get();
+    }
+
+    // C++ [over.match.class.deduct]p1:
+    // If C is defined, for each constructor of C, a function template with
+    // the following properties:
+    // [...]
+    // - The associated constraints are the conjunction of the associated
+    // constraints of C and the associated constraints of the constructor, if
+    // any.
+    if (OuterRC) {
+      // The outer template parameters are not transformed, so their
+      // associated constraints don't need substitution.
+      if (!FunctionTrailingRC)
+        FunctionTrailingRC = OuterRC;
+      else
+        FunctionTrailingRC = BinaryOperator::Create(
+            SemaRef.Context, /*lhs=*/OuterRC, /*rhs=*/FunctionTrailingRC,
+            BO_LAnd, SemaRef.Context.BoolTy, VK_PRValue, OK_Ordinary,
+            TemplateParams->getTemplateLoc(), FPOptionsOverride());
+    }
+
     return buildDeductionGuide(
         SemaRef, Template, TemplateParams, CD, CD->getExplicitSpecifier(),
         NewTInfo, CD->getBeginLoc(), CD->getLocation(), CD->getEndLoc(),
-        /*IsImplicit=*/true, MaterializedTypedefs);
+        /*IsImplicit=*/true, MaterializedTypedefs, FunctionTrailingRC);
   }
 
   /// Build a deduction guide with the specified parameter types.
diff --git a/clang/test/CXX/drs/cwg26xx.cpp b/clang/test/CXX/drs/cwg26xx.cpp
index 63a954c803b77a..0d297b33e88bd9 100644
--- a/clang/test/CXX/drs/cwg26xx.cpp
+++ b/clang/test/CXX/drs/cwg26xx.cpp
@@ -132,27 +132,18 @@ struct E {
 #endif
 } // namespace cwg2627
 
-namespace cwg2628 { // cwg2628: no
-                   // this was reverted for the 16.x release
-                   // due to regressions, see the issue for more details:
-                   // https://github.com/llvm/llvm-project/issues/60777
+namespace cwg2628 { // cwg2628: 20
 #if __cplusplus >= 202002L
 template <bool A = false, bool B = false>
 struct foo {
-  // The expected notes below should be removed when cwg2628 is fully implemented again
-  constexpr foo() requires (!A && !B) = delete; // #cwg2628-ctor-1
-  constexpr foo() requires (A || B) = delete; //  #cwg2628-ctor-2
+  constexpr foo() requires (!A && !B) = delete; // #cwg2628-ctor
+  constexpr foo() requires (A || B) = delete;
 };
 
 void f() {
-  // The FIXME's below should be the expected errors when cwg2628 is
-  // fully implemented again.
   foo fooable; // #cwg2628-fooable
-  // since-cxx20-error at -1 {{ambiguous deduction for template arguments of 'foo'}}
-  //   since-cxx20-note@#cwg2628-ctor-1 {{candidate function [with A = false, B = false]}}
-  //   since-cxx20-note@#cwg2628-ctor-2 {{candidate function [with A = false, B = false]}}
-  // FIXME-since-cxx20-error@#cwg2628-fooable {{call to deleted}} 
-  //   FIXME-since-cxx20-note@#cwg2628-ctor {{marked deleted here}} 
+  // since-cxx20-error@#cwg2628-fooable {{call to deleted}} 
+  //   since-cxx20-note@#cwg2628-ctor {{marked deleted here}} 
 }
 #endif
 }
diff --git a/clang/test/SemaTemplate/deduction-guide.cpp b/clang/test/SemaTemplate/deduction-guide.cpp
index d03c783313dd71..1e7b4d80be53f1 100644
--- a/clang/test/SemaTemplate/deduction-guide.cpp
+++ b/clang/test/SemaTemplate/deduction-guide.cpp
@@ -478,3 +478,166 @@ A a{.f1 = {1}};
 // CHECK-NEXT:     `-DeclRefExpr {{.+}} <col:10> 'int' NonTypeTemplateParm {{.+}} 'N' 'int'
 
 } // namespace GH83368
+
+namespace GH60777 {
+
+template <typename... Ts> constexpr bool True() { return true; }
+
+template <typename T>
+  requires(sizeof(T) == 4)
+struct A {
+  template <typename... Ts>
+    requires(sizeof...(Ts) == 0)
+  A(T val, Ts... tail)
+    requires(True<Ts...>())
+  {}
+};
+
+A a(42);
+
+// `requires (sizeof(T) == 4)` goes into the deduction guide together with
+// `requires (True<Ts...>())`, while `requires(sizeof...(Ts) == 0)` goes into
+// the template parameter list of the synthesized declaration.
+
+// CHECK-LABEL: Dumping GH60777::<deduction guide for A>:
+// CHECK-NEXT: FunctionTemplateDecl 0x{{.+}} <{{.+}}> {{.+}} implicit <deduction guide for A>
+// CHECK-NEXT: |-TemplateTypeParmDecl 0x{{.+}} <{{.+}}> col:20 referenced typename depth 0 index 0 T
+// CHECK-NEXT: |-TemplateTypeParmDecl 0x{{.+}} <{{.+}}> col:25 typename depth 0 index 1 ... Ts
+// CHECK-NEXT: |-ParenExpr 0x{{.+}} <{{.+}}> 'bool'
+// CHECK-NEXT: | `-BinaryOperator 0x{{.+}} <{{.+}}> 'bool' '=='
+// CHECK-NEXT: |   |-SizeOfPackExpr 0x{{.+}} <{{.+}}> 'unsigned long' 0x{{.+}} Ts
+// CHECK-NEXT: |   | `-TemplateArgument type 'Ts...':'type-parameter-0-1...'
+// CHECK-NEXT: |   |   `-PackExpansionType 0x{{.+}} 'Ts...' dependent
+// CHECK-NEXT: |   |     `-TemplateTypeParmType 0x{{.+}} 'Ts' dependent contains_unexpanded_pack depth 0 index 1 pack
+// CHECK-NEXT: |   |       `-TemplateTypeParm 0x{{.+}} 'Ts'
+// CHECK-NEXT: |   `-ImplicitCastExpr 0x{{.+}} <{{.+}}> 'unsigned long' <IntegralCast>
+// CHECK-NEXT: |     `-IntegerLiteral 0x{{.+}} <{{.+}}> 'int' 0
+// CHECK-NEXT: |-CXXDeductionGuideDecl 0x{{.+}} <{{.+}}> line:{{.+}} implicit <deduction guide for A> 'auto (T, Ts...) -> A<T>'
+// CHECK-NEXT: | |-ParmVarDecl 0x{{.+}} <{{.+}}> col:{{.+}} val 'T'
+// CHECK-NEXT: | |-ParmVarDecl 0x{{.+}} <{{.+}}> col:{{.+}} tail 'Ts...' pack
+// CHECK-NEXT: | `-BinaryOperator 0x{{.+}} <{{.+}}> 'bool' '&&'
+// CHECK-NEXT: |   |-ParenExpr 0x{{.+}} <{{.+}}> 'bool'
+// CHECK-NEXT: |   | `-BinaryOperator 0x{{.+}} <{{.+}}> 'bool' '=='
+// CHECK-NEXT: |   |   |-UnaryExprOrTypeTraitExpr 0x{{.+}} <{{.+}}> 'unsigned long' sizeof 'T'
+// CHECK-NEXT: |   |   `-ImplicitCastExpr 0x{{.+}} <{{.+}}> 'unsigned long' <IntegralCast>
+// CHECK-NEXT: |   |     `-IntegerLiteral 0x{{.+}} <{{.+}}> 'int' 4
+// CHECK-NEXT: |   `-ParenExpr 0x{{.+}} <{{.+}}> '<dependent type>'
+// CHECK-NEXT: |     `-CallExpr 0x{{.+}} <{{.+}}> '<dependent type>'
+// CHECK-NEXT: |       `-UnresolvedLookupExpr 0x{{.+}} <col:14, col:24> '<dependent type>' lvalue (ADL) = 'True' 0x{{.+}}
+// CHECK-NEXT: |         `-TemplateArgument type 'Ts...':'type-parameter-0-1...'
+// CHECK-NEXT: |           `-PackExpansionType 0x{{.+}} 'Ts...' dependent
+// CHECK-NEXT: |             `-TemplateTypeParmType 0x{{.+}} 'Ts' dependent contains_unexpanded_pack depth 0 index 1 pack
+// CHECK-NEXT: |               `-TemplateTypeParm 0x{{.+}} 'Ts'
+
+template <typename T>
+struct B {
+  template <typename... Ts>
+  B(T val, Ts... tail)
+    requires(True<tail...>())
+  {}
+};
+
+B b(42, 43);
+// expected-error at -1 {{no viable constructor}} \
+//   expected-note at -6 {{constraints not satisfied}} \
+//   expected-note at -5 {{because substituted constraint expression is ill-formed}} \
+//   expected-note at -6 {{implicit deduction guide declared as 'template <typename T, typename ...Ts> B(T val, Ts ...tail) -> B<T> requires (True<tail...>())'}} \
+//   expected-note at -8 {{function template not viable}} \
+//   expected-note at -8 {{implicit deduction guide declared as 'template <typename T> B(B<T>) -> B<T>'}}
+
+} // namespace GH60777
+
+// Examples from @hokein.
+namespace GH98592 {
+
+template <class T> concept True = true;
+double arr3[3];
+
+template <class T>
+struct X {
+  const int size;
+  template <class U>
+  constexpr X(T, U(&)[3]) requires True<T> : size(sizeof(T)) {}
+};
+
+template <typename T, typename U>
+X(T, U (&)[3]) -> X<U>;
+
+constexpr X x(3, arr3);
+
+// The synthesized deduction guide is more constrained than the explicit one.
+static_assert(x.size == 4);
+
+// CHECK-LABEL: Dumping GH98592::<deduction guide for X>:
+// CHECK-NEXT: FunctionTemplateDecl 0x{{.+}} <{{.+}}> col:13 implicit <deduction guide for X>
+// CHECK-NEXT: |-TemplateTypeParmDecl 0x{{.+}} <{{.+}}> col:17 referenced class depth 0 index 0 T
+// CHECK-NEXT: |-TemplateTypeParmDecl 0x{{.+}} <{{.+}}> col:19 class depth 0 index 1 U
+// CHECK-NEXT: |-CXXDeductionGuideDecl 0x{{.+}} <{{.+}}> col:13 implicit <deduction guide for X> 'auto (T, U (&)[3]) -> X<T>'
+// CHECK-NEXT: | |-ParmVarDecl 0x{{.+}} <col:15> col:16 'T'
+// CHECK-NEXT: | |-ParmVarDecl 0x{{.+}} <col:18, col:24> col:21 'U (&)[3]'
+// CHECK-NEXT: | `-ConceptSpecializationExpr 0x{{.+}} <col:36, col:42> 'bool' Concept 0x{{.+}} 'True'
+// CHECK-NEXT: |   |-ImplicitConceptSpecializationDecl 0x{{.+}} <{{.+}}> col:28
+// CHECK-NEXT: |   | `-TemplateArgument type 'type-parameter-0-0'
+// CHECK-NEXT: |   |   `-TemplateTypeParmType 0x{{.+}} 'type-parameter-0-0' dependent depth 0 index 0
+// CHECK-NEXT: |   `-TemplateArgument <{{.+}}> type 'T':'type-parameter-0-0'
+// CHECK-NEXT: |     `-TemplateTypeParmType 0x{{.+}} 'T' dependent depth 0 index 0
+// CHECK-NEXT: |       `-TemplateTypeParm 0x{{.+}} 'T'
+// CHECK-NEXT: `-CXXDeductionGuideDecl 0x{{.+}} <col:3, col:63> col:13 implicit used <deduction guide for X> 'auto (int, double (&)[3]) -> GH98592::X<int>' implicit_instantiation
+// CHECK-NEXT:   |-TemplateArgument type 'int'
+// CHECK-NEXT:   | `-BuiltinType 0x{{.+}} 'int'
+// CHECK-NEXT:   |-TemplateArgument type 'double'
+// CHECK-NEXT:   | `-BuiltinType 0x{{.+}} 'double'
+// CHECK-NEXT:   |-ParmVarDecl 0x{{.+}} <col:15> col:16 'int'
+// CHECK-NEXT:   |-ParmVarDecl 0x{{.+}} <col:18, col:24> col:21 'double (&)[3]'
+// CHECK-NEXT:   `-ConceptSpecializationExpr 0x{{.+}} <col:36, col:42> 'bool' Concept 0x{{.+}} 'True'
+// CHECK-NEXT:     |-ImplicitConceptSpecializationDecl 0x{{.+}} <{{.+}}> col:28
+// CHECK-NEXT:     | `-TemplateArgument type 'type-parameter-0-0'
+// CHECK-NEXT:     |   `-TemplateTypeParmType 0x{{.+}} 'type-parameter-0-0' dependent depth 0 index 0
+// CHECK-NEXT:     `-TemplateArgument <{{.+}}> type 'T':'type-parameter-0-0'
+// CHECK-NEXT:       `-TemplateTypeParmType 0x{{.+}} 'T' dependent depth 0 index 0
+// CHECK-NEXT:         `-TemplateTypeParm 0x{{.+}} 'T'
+
+template <class T> requires True<T> struct Y {
+  const int size;
+  template <class U>
+  constexpr Y(T, U(&)[3]) : size(sizeof(T)) {}
+};
+
+template <typename T, typename U> Y(T, U (&)[3]) -> Y<U>;
+
+constexpr Y y(3, arr3);
+
+// Likewise, the synthesized deduction guide should be preferred
+// according to [over.match.class.deduct]p1.
+static_assert(y.size == 4);
+
+// Dumping GH98592::<deduction guide for Y>:
+// FunctionTemplateDecl 0x{{.+}} <{{.+}}> col:13 implicit <deduction guide for Y>
+// |-TemplateTypeParmDecl 0x{{.+}} <{{.+}}> col:17 referenced class depth 0 index 0 T
+// |-TemplateTypeParmDecl 0x{{.+}} <{{.+}}> col:19 class depth 0 index 1 U
+// |-CXXDeductionGuideDecl 0x{{.+}} <{{.+}}> col:13 implicit <deduction guide for Y> 'auto (T, U (&)[3]) -> Y<T>'
+// | |-ParmVarDecl 0x{{.+}} <col:15> col:16 'T'
+// | |-ParmVarDecl 0x{{.+}} <col:18, col:24> col:21 'U (&)[3]'
+// | `-ConceptSpecializationExpr 0x{{.+}} <{{.+}}> 'bool' Concept 0x{{.+}} 'True'
+// |   |-ImplicitConceptSpecializationDecl 0x{{.+}} <{{.+}}> col:28
+// |   | `-TemplateArgument type 'type-parameter-0-0'
+// |   |   `-TemplateTypeParmType 0x{{.+}} 'type-parameter-0-0' dependent depth 0 index 0
+// |   `-TemplateArgument <{{.+}}> type 'T':'type-parameter-0-0'
+// |     `-TemplateTypeParmType 0x{{.+}} 'T' dependent depth 0 index 0
+// |       `-TemplateTypeParm 0x{{.+}} 'T'
+// `-CXXDeductionGuideDecl 0x{{.+}} <{{.+}}> col:13 implicit used <deduction guide for Y> 'auto (int, double (&)[3]) -> GH98592::Y<int>' implicit_instantiation
+//   |-TemplateArgument type 'int'
+//   | `-BuiltinType 0x{{.+}} 'int'
+//   |-TemplateArgument type 'double'
+//   | `-BuiltinType 0x{{.+}} 'double'
+//   |-ParmVarDecl 0x{{.+}} <col:15> col:16 'int'
+//   |-ParmVarDecl 0x{{.+}} <col:18, col:24> col:21 'double (&)[3]'
+//   `-ConceptSpecializationExpr 0x{{.+}} <{{.+}}> 'bool' Concept 0x{{.+}} 'True'
+//     |-ImplicitConceptSpecializationDecl 0x{{.+}} <{{.+}}> col:28
+//     | `-TemplateArgument type 'type-parameter-0-0'
+//     |   `-TemplateTypeParmType 0x{{.+}} 'type-parameter-0-0' dependent depth 0 index 0
+//     `-TemplateArgument <{{.+}}> type 'T':'type-parameter-0-0'
+//       `-TemplateTypeParmType 0x{{.+}} 'T' dependent depth 0 index 0
+//         `-TemplateTypeParm 0x{{.+}} 'T'
+
+} // namespce GH98592
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index ba63106ccc3875..185fb6a94f55a6 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -15615,7 +15615,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
     <td><a href="https://cplusplus.github.io/CWG/issues/2628.html">2628</a></td>
     <td>DRWP</td>
     <td>Implicit deduction guides should propagate constraints</td>
-    <td class="none" align="center">No</td>
+    <td class="unreleased" align="center">Clang 20</td>
   </tr>
   <tr id="2629">
     <td><a href="https://cplusplus.github.io/CWG/issues/2629.html">2629</a></td>



More information about the cfe-commits mailing list