[clang] [Clang][C++26] Implement "Ordering of constraints involving fold expressions (PR #98160)

via cfe-commits cfe-commits at lists.llvm.org
Thu Jul 11 07:16:53 PDT 2024


https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/98160

>From ba2b6ab4fd0aa5a79a849d71f2857256f8fc1c73 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 9 Jul 2024 16:08:44 +0200
Subject: [PATCH 1/9] [Clang][C++26] Implement "Ordering of constraints
 involving fold expressions"

Implement https://isocpp.org/files/papers/P2963R3.pdf
---
 clang/docs/ReleaseNotes.rst             |   3 +
 clang/include/clang/Sema/Sema.h         |   5 +
 clang/include/clang/Sema/SemaConcept.h  | 142 +++++-
 clang/lib/Sema/SemaConcept.cpp          | 604 ++++++++++++++++--------
 clang/lib/Sema/SemaTemplateVariadic.cpp |   4 +
 clang/test/SemaCXX/cxx2c-fold-exprs.cpp | 239 ++++++++++
 clang/www/cxx_status.html               |   2 +-
 7 files changed, 781 insertions(+), 218 deletions(-)
 create mode 100644 clang/test/SemaCXX/cxx2c-fold-exprs.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 838cb69f647ee..7d0b2115f8e53 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -271,6 +271,9 @@ C++2c Feature Support
 
 - Implemented `P3144R2 Deleting a Pointer to an Incomplete Type Should be Ill-formed <https://wg21.link/P3144R2>`_.
 
+- Implemented `P2963R3 Ordering of constraints involving fold expressions <https://wg21.link/P2963R3>`_.
+
+
 Resolutions to C++ Defect Reports
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - Substitute template parameter pack, when it is not explicitly specified
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 75a80540dbcbf..4a07da6b7bfd9 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14062,6 +14062,11 @@ class Sema final : public SemaBase {
       const DeclarationNameInfo &NameInfo,
       SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
 
+  /// Collect the set of unexpanded parameter packs within the given
+  /// expression.
+  static void collectUnexpandedParameterPacks(
+      Expr *E, SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
+
   /// Invoked when parsing a template argument followed by an
   /// ellipsis, which creates a pack expansion.
   ///
diff --git a/clang/include/clang/Sema/SemaConcept.h b/clang/include/clang/Sema/SemaConcept.h
index 711443505174f..b6327e729768b 100644
--- a/clang/include/clang/Sema/SemaConcept.h
+++ b/clang/include/clang/Sema/SemaConcept.h
@@ -75,6 +75,17 @@ struct AtomicConstraint {
   }
 };
 
+struct FoldExpandedConstraint;
+
+using NormalFormConstraint =
+    llvm::PointerUnion<AtomicConstraint *, FoldExpandedConstraint *>;
+struct NormalizedConstraint;
+using NormalForm =
+    llvm::SmallVector<llvm::SmallVector<NormalFormConstraint, 2>, 4>;
+
+NormalForm makeCNF(const NormalizedConstraint &Normalized);
+NormalForm makeDNF(const NormalizedConstraint &Normalized);
+
 /// \brief A normalized constraint, as defined in C++ [temp.constr.normal], is
 /// either an atomic constraint, a conjunction of normalized constraints or a
 /// disjunction of normalized constraints.
@@ -87,26 +98,17 @@ struct NormalizedConstraint {
       std::pair<NormalizedConstraint, NormalizedConstraint> *, 1,
       CompoundConstraintKind>;
 
-  llvm::PointerUnion<AtomicConstraint *, CompoundConstraint> Constraint;
+  llvm::PointerUnion<AtomicConstraint *, FoldExpandedConstraint *,
+                     CompoundConstraint>
+      Constraint;
 
   NormalizedConstraint(AtomicConstraint *C): Constraint{C} { };
+  NormalizedConstraint(FoldExpandedConstraint *C) : Constraint{C} {};
+
   NormalizedConstraint(ASTContext &C, NormalizedConstraint LHS,
-                       NormalizedConstraint RHS, CompoundConstraintKind Kind)
-      : Constraint{CompoundConstraint{
-            new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
-                std::move(LHS), std::move(RHS)}, Kind}} { };
-
-  NormalizedConstraint(ASTContext &C, const NormalizedConstraint &Other) {
-    if (Other.isAtomic()) {
-      Constraint = new (C) AtomicConstraint(*Other.getAtomicConstraint());
-    } else {
-      Constraint = CompoundConstraint(
-          new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
-              NormalizedConstraint(C, Other.getLHS()),
-              NormalizedConstraint(C, Other.getRHS())},
-              Other.getCompoundKind());
-    }
-  }
+                       NormalizedConstraint RHS, CompoundConstraintKind Kind);
+
+  NormalizedConstraint(ASTContext &C, const NormalizedConstraint &Other);
   NormalizedConstraint(NormalizedConstraint &&Other):
       Constraint(Other.Constraint) {
     Other.Constraint = nullptr;
@@ -126,6 +128,9 @@ struct NormalizedConstraint {
   }
 
   bool isAtomic() const { return Constraint.is<AtomicConstraint *>(); }
+  bool isFoldExpanded() const {
+    return Constraint.is<FoldExpandedConstraint *>();
+  }
 
   NormalizedConstraint &getLHS() const {
     assert(!isAtomic() && "getLHS called on atomic constraint.");
@@ -143,6 +148,12 @@ struct NormalizedConstraint {
     return Constraint.get<AtomicConstraint *>();
   }
 
+  FoldExpandedConstraint *getFoldExpandedConstraint() const {
+    assert(isFoldExpanded() &&
+           "getFoldExpandedConstraint called on non-fold-expanded constraint.");
+    return Constraint.get<FoldExpandedConstraint *>();
+  }
+
 private:
   static std::optional<NormalizedConstraint>
   fromConstraintExprs(Sema &S, NamedDecl *D, ArrayRef<const Expr *> E);
@@ -150,6 +161,103 @@ struct NormalizedConstraint {
   fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E);
 };
 
+struct FoldExpandedConstraint {
+  enum class FoldOperatorKind { FoAnd, FoOr } Kind;
+  NormalizedConstraint Constraint;
+  const Expr *Pattern;
+
+  FoldExpandedConstraint(FoldOperatorKind K, NormalizedConstraint C,
+                         const Expr *Pattern)
+      : Kind(K), Constraint(std::move(C)), Pattern(Pattern) {};
+
+  template <typename AtomicSubsumptionEvaluator>
+  bool subsumes(const FoldExpandedConstraint &Other,
+                AtomicSubsumptionEvaluator E) const;
+
+  static bool AreSubsumptionElligible(const FoldExpandedConstraint &A,
+                                      const FoldExpandedConstraint &B);
+};
+
+const NormalizedConstraint *getNormalizedAssociatedConstraints(
+    Sema &S, NamedDecl *ConstrainedDecl,
+    ArrayRef<const Expr *> AssociatedConstraints);
+
+template <typename AtomicSubsumptionEvaluator>
+bool subsumes(const NormalForm &PDNF, const NormalForm &QCNF,
+              AtomicSubsumptionEvaluator E) {
+  // C++ [temp.constr.order] p2
+  //   Then, P subsumes Q if and only if, for every disjunctive clause Pi in the
+  //   disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in
+  //   the conjuctive normal form of Q, where [...]
+  for (const auto &Pi : PDNF) {
+    for (const auto &Qj : QCNF) {
+      // C++ [temp.constr.order] p2
+      //   - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if
+      //     and only if there exists an atomic constraint Pia in Pi for which
+      //     there exists an atomic constraint, Qjb, in Qj such that Pia
+      //     subsumes Qjb.
+      bool Found = false;
+      for (NormalFormConstraint Pia : Pi) {
+        for (NormalFormConstraint Qjb : Qj) {
+          if (Pia.is<FoldExpandedConstraint *>() &&
+              Qjb.is<FoldExpandedConstraint *>()) {
+            if (Pia.get<FoldExpandedConstraint *>()->subsumes(
+                    *Qjb.get<FoldExpandedConstraint *>(), E)) {
+              Found = true;
+              break;
+            }
+          } else if (Pia.is<AtomicConstraint *>() &&
+                     Qjb.is<AtomicConstraint *>()) {
+            if (E(*Pia.get<AtomicConstraint *>(),
+                  *Qjb.get<AtomicConstraint *>())) {
+              Found = true;
+              break;
+            }
+          }
+        }
+        if (Found)
+          break;
+      }
+      if (!Found)
+        return false;
+    }
+  }
+  return true;
+}
+
+template <typename AtomicSubsumptionEvaluator>
+bool subsumes(Sema &S, NamedDecl *DP, ArrayRef<const Expr *> P, NamedDecl *DQ,
+              ArrayRef<const Expr *> Q, bool &Subsumes,
+              AtomicSubsumptionEvaluator E) {
+  // C++ [temp.constr.order] p2
+  //   In order to determine if a constraint P subsumes a constraint Q, P is
+  //   transformed into disjunctive normal form, and Q is transformed into
+  //   conjunctive normal form. [...]
+  auto *PNormalized = getNormalizedAssociatedConstraints(S, DP, P);
+  if (!PNormalized)
+    return true;
+  const NormalForm PDNF = makeDNF(*PNormalized);
+
+  auto *QNormalized = getNormalizedAssociatedConstraints(S, DQ, Q);
+  if (!QNormalized)
+    return true;
+  const NormalForm QCNF = makeCNF(*QNormalized);
+
+  Subsumes = subsumes(PDNF, QCNF, E);
+  return false;
+}
+
+template <typename AtomicSubsumptionEvaluator>
+bool FoldExpandedConstraint::subsumes(const FoldExpandedConstraint &Other,
+                                      AtomicSubsumptionEvaluator E) const {
+  if (Kind != Other.Kind || !AreSubsumptionElligible(*this, Other))
+    return false;
+
+  const NormalForm PDNF = makeDNF(this->Constraint);
+  const NormalForm QCNF = makeCNF(Other.Constraint);
+  return clang::subsumes(PDNF, QCNF, E);
+}
+
 } // clang
 
 #endif // LLVM_CLANG_SEMA_SEMACONCEPT_H
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 202dd86c67f62..b86e2d51e380b 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -65,6 +65,7 @@ class LogicalBinOp {
 
   const Expr *getLHS() const { return LHS; }
   const Expr *getRHS() const { return RHS; }
+  OverloadedOperatorKind getOp() const { return Op; }
 
   ExprResult recreateBinOp(Sema &SemaRef, ExprResult LHS) const {
     return recreateBinOp(SemaRef, LHS, const_cast<Expr *>(getRHS()));
@@ -177,65 +178,160 @@ struct SatisfactionStackRAII {
 };
 } // namespace
 
-template <typename AtomicEvaluator>
+template <typename ConstraintEvaluator>
 static ExprResult
 calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
                                 ConstraintSatisfaction &Satisfaction,
-                                AtomicEvaluator &&Evaluator) {
-  ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
+                                ConstraintEvaluator &&Evaluator);
 
-  if (LogicalBinOp BO = ConstraintExpr) {
-    size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
-    ExprResult LHSRes = calculateConstraintSatisfaction(
-        S, BO.getLHS(), Satisfaction, Evaluator);
+template <typename ConstraintEvaluator>
+static ExprResult calculateConstraintSatisfaction(
+    Sema &S, const Expr *LHS, OverloadedOperatorKind Op, const Expr *RHS,
+    ConstraintSatisfaction &Satisfaction, ConstraintEvaluator &Evaluator) {
+  size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
 
-    if (LHSRes.isInvalid())
-      return ExprError();
+  ExprResult LHSRes =
+      calculateConstraintSatisfaction(S, LHS, Satisfaction, Evaluator);
 
-    bool IsLHSSatisfied = Satisfaction.IsSatisfied;
+  if (LHSRes.isInvalid())
+    return ExprError();
 
-    if (BO.isOr() && IsLHSSatisfied)
-      // [temp.constr.op] p3
-      //    A disjunction is a constraint taking two operands. To determine if
-      //    a disjunction is satisfied, the satisfaction of the first operand
-      //    is checked. If that is satisfied, the disjunction is satisfied.
-      //    Otherwise, the disjunction is satisfied if and only if the second
-      //    operand is satisfied.
-      // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
-      return LHSRes;
-
-    if (BO.isAnd() && !IsLHSSatisfied)
-      // [temp.constr.op] p2
-      //    A conjunction is a constraint taking two operands. To determine if
-      //    a conjunction is satisfied, the satisfaction of the first operand
-      //    is checked. If that is not satisfied, the conjunction is not
-      //    satisfied. Otherwise, the conjunction is satisfied if and only if
-      //    the second operand is satisfied.
-      // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
-      return LHSRes;
-
-    ExprResult RHSRes = calculateConstraintSatisfaction(
-        S, BO.getRHS(), Satisfaction, std::forward<AtomicEvaluator>(Evaluator));
-    if (RHSRes.isInvalid())
-      return ExprError();
+  bool IsLHSSatisfied = Satisfaction.IsSatisfied;
+
+  if (Op == clang::OO_PipePipe && IsLHSSatisfied)
+    // [temp.constr.op] p3
+    //    A disjunction is a constraint taking two operands. To determine if
+    //    a disjunction is satisfied, the satisfaction of the first operand
+    //    is checked. If that is satisfied, the disjunction is satisfied.
+    //    Otherwise, the disjunction is satisfied if and only if the second
+    //    operand is satisfied.
+    // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
+    return LHSRes;
+
+  if (Op == clang::OO_AmpAmp && !IsLHSSatisfied)
+    // [temp.constr.op] p2
+    //    A conjunction is a constraint taking two operands. To determine if
+    //    a conjunction is satisfied, the satisfaction of the first operand
+    //    is checked. If that is not satisfied, the conjunction is not
+    //    satisfied. Otherwise, the conjunction is satisfied if and only if
+    //    the second operand is satisfied.
+    // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
+    return LHSRes;
+
+  ExprResult RHSRes = calculateConstraintSatisfaction(
+      S, RHS, Satisfaction, std::forward<ConstraintEvaluator>(Evaluator));
+  if (RHSRes.isInvalid())
+    return ExprError();
+
+  bool IsRHSSatisfied = Satisfaction.IsSatisfied;
+  // Current implementation adds diagnostic information about the falsity
+  // of each false atomic constraint expression when it evaluates them.
+  // When the evaluation results to `false || true`, the information
+  // generated during the evaluation of left-hand side is meaningless
+  // because the whole expression evaluates to true.
+  // The following code removes the irrelevant diagnostic information.
+  // FIXME: We should probably delay the addition of diagnostic information
+  // until we know the entire expression is false.
+  if (Op == clang::OO_PipePipe && IsRHSSatisfied) {
+    auto EffectiveDetailEnd = Satisfaction.Details.begin();
+    std::advance(EffectiveDetailEnd, EffectiveDetailEndIndex);
+    Satisfaction.Details.erase(EffectiveDetailEnd, Satisfaction.Details.end());
+  }
 
+  if (!LHSRes.isUsable() || !RHSRes.isUsable())
+    return ExprEmpty();
+
+  return BinaryOperator::Create(S.Context, LHSRes.get(), RHSRes.get(),
+                                BinaryOperator::getOverloadedOpcode(Op),
+                                S.Context.BoolTy, VK_PRValue, OK_Ordinary,
+                                LHS->getBeginLoc(), FPOptionsOverride{});
+}
+
+template <typename ConstraintEvaluator>
+static ExprResult
+calculateConstraintSatisfaction(Sema &S, const CXXFoldExpr *FE,
+                                ConstraintSatisfaction &Satisfaction,
+                                ConstraintEvaluator &&Evaluator) {
+  bool Conjunction = FE->getOperator() == BinaryOperatorKind::BO_LAnd;
+  size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
+
+  ExprResult Out;
+  if (FE->isLeftFold() && FE->getInit()) {
+    Out = calculateConstraintSatisfaction(S, FE->getInit(), Satisfaction,
+                                          Evaluator);
+    if (Out.isInvalid())
+      return ExprError();
+    bool IsLHSSatisfied = Satisfaction.IsSatisfied;
+    if (Conjunction && !IsLHSSatisfied) {
+      return Out;
+    }
+    if (!Conjunction && IsLHSSatisfied) {
+      return Out;
+    }
+  }
+  std::optional<unsigned> NumExpansions =
+      Evaluator.EvaluateFoldExpandedConstraintSize(FE);
+  if (!NumExpansions)
+    return ExprError();
+  for (unsigned I = 0; I < *NumExpansions; I++) {
+    Sema::ArgumentPackSubstitutionIndexRAII SubstIndex(S, I);
+    ExprResult Res = calculateConstraintSatisfaction(S, FE->getPattern(),
+                                                     Satisfaction, Evaluator);
+    if (Res.isInvalid())
+      return ExprError();
     bool IsRHSSatisfied = Satisfaction.IsSatisfied;
-    // Current implementation adds diagnostic information about the falsity
-    // of each false atomic constraint expression when it evaluates them.
-    // When the evaluation results to `false || true`, the information
-    // generated during the evaluation of left-hand side is meaningless
-    // because the whole expression evaluates to true.
-    // The following code removes the irrelevant diagnostic information.
-    // FIXME: We should probably delay the addition of diagnostic information
-    // until we know the entire expression is false.
-    if (BO.isOr() && IsRHSSatisfied) {
+    if (!Conjunction && IsRHSSatisfied) {
       auto EffectiveDetailEnd = Satisfaction.Details.begin();
       std::advance(EffectiveDetailEnd, EffectiveDetailEndIndex);
       Satisfaction.Details.erase(EffectiveDetailEnd,
                                  Satisfaction.Details.end());
     }
+    if (Out.isUnset())
+      Out = Res;
+    else if (!Res.isUnset()) {
+      Out = BinaryOperator::Create(
+          S.Context, Out.get(), Res.get(), FE->getOperator(), S.Context.BoolTy,
+          VK_PRValue, OK_Ordinary, FE->getBeginLoc(), FPOptionsOverride{});
+    }
+    if (Conjunction && !IsRHSSatisfied)
+      return Out;
+    if (!Conjunction && IsRHSSatisfied)
+      return Out;
+  }
+
+  if (FE->isRightFold() && FE->getInit()) {
+    ExprResult Res = calculateConstraintSatisfaction(S, FE->getInit(),
+                                                     Satisfaction, Evaluator);
+    if (Out.isInvalid())
+      return ExprError();
+
+    if (Out.isUnset())
+      Out = Res;
+    else if (!Res.isUnset()) {
+      Out = BinaryOperator::Create(
+          S.Context, Out.get(), Res.get(), FE->getOperator(), S.Context.BoolTy,
+          VK_PRValue, OK_Ordinary, FE->getBeginLoc(), FPOptionsOverride{});
+    }
+  }
 
-    return BO.recreateBinOp(S, LHSRes, RHSRes);
+  if (Out.isUnset()) {
+    Satisfaction.IsSatisfied = Conjunction;
+    Out = S.BuildEmptyCXXFoldExpr(FE->getBeginLoc(), FE->getOperator());
+  }
+  return Out;
+}
+
+template <typename ConstraintEvaluator>
+static ExprResult
+calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
+                                ConstraintSatisfaction &Satisfaction,
+                                ConstraintEvaluator &&Evaluator) {
+  ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
+
+  if (LogicalBinOp BO = ConstraintExpr) {
+
+    return calculateConstraintSatisfaction(
+        S, BO.getLHS(), BO.getOp(), BO.getRHS(), Satisfaction, Evaluator);
   }
 
   if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr)) {
@@ -243,11 +339,19 @@ calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
     // evaluate these as if the cleanups didn't exist.
     return calculateConstraintSatisfaction(
         S, C->getSubExpr(), Satisfaction,
-        std::forward<AtomicEvaluator>(Evaluator));
+        std::forward<ConstraintEvaluator>(Evaluator));
+  }
+
+  if (auto *FE = dyn_cast<CXXFoldExpr>(ConstraintExpr);
+      FE && S.getLangOpts().CPlusPlus26 &&
+      (FE->getOperator() == BinaryOperatorKind::BO_LAnd ||
+       FE->getOperator() == BinaryOperatorKind::BO_LOr)) {
+    return calculateConstraintSatisfaction(S, FE, Satisfaction, Evaluator);
   }
 
   // An atomic constraint expression
-  ExprResult SubstitutedAtomicExpr = Evaluator(ConstraintExpr);
+  ExprResult SubstitutedAtomicExpr =
+      Evaluator.EvaluateAtomicConstraint(ConstraintExpr);
 
   if (SubstitutedAtomicExpr.isInvalid())
     return ExprError();
@@ -336,92 +440,133 @@ static ExprResult calculateConstraintSatisfaction(
     Sema &S, const NamedDecl *Template, SourceLocation TemplateNameLoc,
     const MultiLevelTemplateArgumentList &MLTAL, const Expr *ConstraintExpr,
     ConstraintSatisfaction &Satisfaction) {
-  return calculateConstraintSatisfaction(
-      S, ConstraintExpr, Satisfaction, [&](const Expr *AtomicExpr) {
-        EnterExpressionEvaluationContext ConstantEvaluated(
-            S, Sema::ExpressionEvaluationContext::ConstantEvaluated,
-            Sema::ReuseLambdaContextDecl);
-
-        // Atomic constraint - substitute arguments and check satisfaction.
-        ExprResult SubstitutedExpression;
-        {
-          TemplateDeductionInfo Info(TemplateNameLoc);
-          Sema::InstantiatingTemplate Inst(S, AtomicExpr->getBeginLoc(),
-              Sema::InstantiatingTemplate::ConstraintSubstitution{},
-              const_cast<NamedDecl *>(Template), Info,
-              AtomicExpr->getSourceRange());
-          if (Inst.isInvalid())
+
+  struct ConstraintEvaluator {
+    Sema &S;
+    const NamedDecl *Template;
+    SourceLocation TemplateNameLoc;
+    const MultiLevelTemplateArgumentList &MLTAL;
+    ConstraintSatisfaction &Satisfaction;
+
+    ExprResult EvaluateAtomicConstraint(const Expr *AtomicExpr) {
+      EnterExpressionEvaluationContext ConstantEvaluated(
+          S, Sema::ExpressionEvaluationContext::ConstantEvaluated,
+          Sema::ReuseLambdaContextDecl);
+
+      // Atomic constraint - substitute arguments and check satisfaction.
+      ExprResult SubstitutedExpression;
+      {
+        TemplateDeductionInfo Info(TemplateNameLoc);
+        Sema::InstantiatingTemplate Inst(
+            S, AtomicExpr->getBeginLoc(),
+            Sema::InstantiatingTemplate::ConstraintSubstitution{},
+            const_cast<NamedDecl *>(Template), Info,
+            AtomicExpr->getSourceRange());
+        if (Inst.isInvalid())
+          return ExprError();
+
+        llvm::FoldingSetNodeID ID;
+        if (Template &&
+            DiagRecursiveConstraintEval(S, ID, Template, AtomicExpr, MLTAL)) {
+          Satisfaction.IsSatisfied = false;
+          Satisfaction.ContainsErrors = true;
+          return ExprEmpty();
+        }
+
+        SatisfactionStackRAII StackRAII(S, Template, ID);
+
+        // We do not want error diagnostics escaping here.
+        Sema::SFINAETrap Trap(S);
+        SubstitutedExpression =
+            S.SubstConstraintExpr(const_cast<Expr *>(AtomicExpr), MLTAL);
+
+        if (SubstitutedExpression.isInvalid() || Trap.hasErrorOccurred()) {
+          // C++2a [temp.constr.atomic]p1
+          //   ...If substitution results in an invalid type or expression, the
+          //   constraint is not satisfied.
+          if (!Trap.hasErrorOccurred())
+            // A non-SFINAE error has occurred as a result of this
+            // substitution.
             return ExprError();
 
-          llvm::FoldingSetNodeID ID;
-          if (Template &&
-              DiagRecursiveConstraintEval(S, ID, Template, AtomicExpr, MLTAL)) {
-            Satisfaction.IsSatisfied = false;
-            Satisfaction.ContainsErrors = true;
-            return ExprEmpty();
-          }
-
-          SatisfactionStackRAII StackRAII(S, Template, ID);
-
-          // We do not want error diagnostics escaping here.
-          Sema::SFINAETrap Trap(S);
-          SubstitutedExpression =
-              S.SubstConstraintExpr(const_cast<Expr *>(AtomicExpr), MLTAL);
-
-          if (SubstitutedExpression.isInvalid() || Trap.hasErrorOccurred()) {
-            // C++2a [temp.constr.atomic]p1
-            //   ...If substitution results in an invalid type or expression, the
-            //   constraint is not satisfied.
-            if (!Trap.hasErrorOccurred())
-              // A non-SFINAE error has occurred as a result of this
-              // substitution.
-              return ExprError();
-
-            PartialDiagnosticAt SubstDiag{SourceLocation(),
-                                          PartialDiagnostic::NullDiagnostic()};
-            Info.takeSFINAEDiagnostic(SubstDiag);
-            // FIXME: Concepts: This is an unfortunate consequence of there
-            //  being no serialization code for PartialDiagnostics and the fact
-            //  that serializing them would likely take a lot more storage than
-            //  just storing them as strings. We would still like, in the
-            //  future, to serialize the proper PartialDiagnostic as serializing
-            //  it as a string defeats the purpose of the diagnostic mechanism.
-            SmallString<128> DiagString;
-            DiagString = ": ";
-            SubstDiag.second.EmitToString(S.getDiagnostics(), DiagString);
-            unsigned MessageSize = DiagString.size();
-            char *Mem = new (S.Context) char[MessageSize];
-            memcpy(Mem, DiagString.c_str(), MessageSize);
-            Satisfaction.Details.emplace_back(
-                AtomicExpr,
-                new (S.Context) ConstraintSatisfaction::SubstitutionDiagnostic{
-                        SubstDiag.first, StringRef(Mem, MessageSize)});
-            Satisfaction.IsSatisfied = false;
-            return ExprEmpty();
-          }
+          PartialDiagnosticAt SubstDiag{SourceLocation(),
+                                        PartialDiagnostic::NullDiagnostic()};
+          Info.takeSFINAEDiagnostic(SubstDiag);
+          // FIXME: Concepts: This is an unfortunate consequence of there
+          //  being no serialization code for PartialDiagnostics and the fact
+          //  that serializing them would likely take a lot more storage than
+          //  just storing them as strings. We would still like, in the
+          //  future, to serialize the proper PartialDiagnostic as serializing
+          //  it as a string defeats the purpose of the diagnostic mechanism.
+          SmallString<128> DiagString;
+          DiagString = ": ";
+          SubstDiag.second.EmitToString(S.getDiagnostics(), DiagString);
+          unsigned MessageSize = DiagString.size();
+          char *Mem = new (S.Context) char[MessageSize];
+          memcpy(Mem, DiagString.c_str(), MessageSize);
+          Satisfaction.Details.emplace_back(
+              AtomicExpr,
+              new (S.Context) ConstraintSatisfaction::SubstitutionDiagnostic{
+                  SubstDiag.first, StringRef(Mem, MessageSize)});
+          Satisfaction.IsSatisfied = false;
+          return ExprEmpty();
         }
+      }
 
-        if (!S.CheckConstraintExpression(SubstitutedExpression.get()))
-          return ExprError();
+      if (!S.CheckConstraintExpression(SubstitutedExpression.get()))
+        return ExprError();
+
+      // [temp.constr.atomic]p3: To determine if an atomic constraint is
+      // satisfied, the parameter mapping and template arguments are first
+      // substituted into its expression.  If substitution results in an
+      // invalid type or expression, the constraint is not satisfied.
+      // Otherwise, the lvalue-to-rvalue conversion is performed if necessary,
+      // and E shall be a constant expression of type bool.
+      //
+      // Perform the L to R Value conversion if necessary. We do so for all
+      // non-PRValue categories, else we fail to extend the lifetime of
+      // temporaries, and that fails the constant expression check.
+      if (!SubstitutedExpression.get()->isPRValue())
+        SubstitutedExpression = ImplicitCastExpr::Create(
+            S.Context, SubstitutedExpression.get()->getType(),
+            CK_LValueToRValue, SubstitutedExpression.get(),
+            /*BasePath=*/nullptr, VK_PRValue, FPOptionsOverride());
+
+      return SubstitutedExpression;
+    }
 
-        // [temp.constr.atomic]p3: To determine if an atomic constraint is
-        // satisfied, the parameter mapping and template arguments are first
-        // substituted into its expression.  If substitution results in an
-        // invalid type or expression, the constraint is not satisfied.
-        // Otherwise, the lvalue-to-rvalue conversion is performed if necessary,
-        // and E shall be a constant expression of type bool.
-        //
-        // Perform the L to R Value conversion if necessary. We do so for all
-        // non-PRValue categories, else we fail to extend the lifetime of
-        // temporaries, and that fails the constant expression check.
-        if (!SubstitutedExpression.get()->isPRValue())
-          SubstitutedExpression = ImplicitCastExpr::Create(
-              S.Context, SubstitutedExpression.get()->getType(),
-              CK_LValueToRValue, SubstitutedExpression.get(),
-              /*BasePath=*/nullptr, VK_PRValue, FPOptionsOverride());
-
-        return SubstitutedExpression;
-      });
+    std::optional<unsigned>
+    EvaluateFoldExpandedConstraintSize(const CXXFoldExpr *FE) {
+      Expr *Pattern = FE->getPattern();
+
+      SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+      S.collectUnexpandedParameterPacks(Pattern, Unexpanded);
+      assert(!Unexpanded.empty() && "Pack expansion without parameter packs?");
+      bool Expand = true;
+      bool RetainExpansion = false;
+      std::optional<unsigned> OrigNumExpansions = FE->getNumExpansions(),
+                              NumExpansions = OrigNumExpansions;
+      if (S.CheckParameterPacksForExpansion(
+              FE->getEllipsisLoc(), Pattern->getSourceRange(), Unexpanded,
+              MLTAL, Expand, RetainExpansion, NumExpansions) ||
+          !Expand || RetainExpansion)
+        return std::nullopt;
+
+      if (NumExpansions && S.getLangOpts().BracketDepth < NumExpansions) {
+        S.Diag(FE->getEllipsisLoc(),
+               clang::diag::err_fold_expression_limit_exceeded)
+            << *NumExpansions << S.getLangOpts().BracketDepth
+            << FE->getSourceRange();
+        S.Diag(FE->getEllipsisLoc(), diag::note_bracket_depth);
+        return std::nullopt;
+      }
+      return NumExpansions;
+    }
+  };
+
+  return calculateConstraintSatisfaction(
+      S, ConstraintExpr, Satisfaction,
+      ConstraintEvaluator{S, Template, TemplateNameLoc, MLTAL, Satisfaction});
 }
 
 static bool CheckConstraintSatisfaction(
@@ -537,13 +682,21 @@ bool Sema::CheckConstraintSatisfaction(
 
 bool Sema::CheckConstraintSatisfaction(const Expr *ConstraintExpr,
                                        ConstraintSatisfaction &Satisfaction) {
-  return calculateConstraintSatisfaction(
-             *this, ConstraintExpr, Satisfaction,
-             [this](const Expr *AtomicExpr) -> ExprResult {
-               // We only do this to immitate lvalue-to-rvalue conversion.
-               return PerformContextuallyConvertToBool(
-                   const_cast<Expr *>(AtomicExpr));
-             })
+
+  struct ConstraintEvaluator {
+    Sema &S;
+    ExprResult EvaluateAtomicConstraint(const Expr *AtomicExpr) {
+      return S.PerformContextuallyConvertToBool(const_cast<Expr *>(AtomicExpr));
+    }
+
+    std::optional<unsigned>
+    EvaluateFoldExpandedConstraintSize(const CXXFoldExpr *FE) {
+      return 0;
+    }
+  };
+
+  return calculateConstraintSatisfaction(*this, ConstraintExpr, Satisfaction,
+                                         ConstraintEvaluator{*this})
       .isInvalid();
 }
 
@@ -1238,6 +1391,13 @@ Sema::getNormalizedAssociatedConstraints(
   return CacheEntry->second;
 }
 
+const NormalizedConstraint *clang::getNormalizedAssociatedConstraints(
+    Sema &S, NamedDecl *ConstrainedDecl,
+    ArrayRef<const Expr *> AssociatedConstraints) {
+  return S.getNormalizedAssociatedConstraints(ConstrainedDecl,
+                                              AssociatedConstraints);
+}
+
 static bool
 substituteParameterMappings(Sema &S, NormalizedConstraint &N,
                             ConceptDecl *Concept,
@@ -1316,6 +1476,33 @@ static bool substituteParameterMappings(Sema &S, NormalizedConstraint &N,
                                      CSE->getTemplateArgsAsWritten());
 }
 
+NormalizedConstraint::NormalizedConstraint(ASTContext &C,
+                                           NormalizedConstraint LHS,
+                                           NormalizedConstraint RHS,
+                                           CompoundConstraintKind Kind)
+    : Constraint{CompoundConstraint{
+          new(C) std::pair<NormalizedConstraint, NormalizedConstraint>{
+              std::move(LHS), std::move(RHS)},
+          Kind}} {}
+
+NormalizedConstraint::NormalizedConstraint(ASTContext &C,
+                                           const NormalizedConstraint &Other) {
+  if (Other.isAtomic()) {
+    Constraint = new (C) AtomicConstraint(*Other.getAtomicConstraint());
+  } else if (Other.isFoldExpanded()) {
+    Constraint = new (C) FoldExpandedConstraint(
+        Other.getFoldExpandedConstraint()->Kind,
+        NormalizedConstraint(C, Other.getFoldExpandedConstraint()->Constraint),
+        Other.getFoldExpandedConstraint()->Pattern);
+  } else {
+    Constraint = CompoundConstraint(
+        new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
+            NormalizedConstraint(C, Other.getLHS()),
+            NormalizedConstraint(C, Other.getRHS())},
+        Other.getCompoundKind());
+  }
+}
+
 std::optional<NormalizedConstraint>
 NormalizedConstraint::fromConstraintExprs(Sema &S, NamedDecl *D,
                                           ArrayRef<const Expr *> E) {
@@ -1390,17 +1577,82 @@ NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
       return std::nullopt;
 
     return New;
+  } else if (auto *FE = dyn_cast<const CXXFoldExpr>(E);
+             FE && S.getLangOpts().CPlusPlus26 &&
+             (FE->getOperator() == BinaryOperatorKind::BO_LAnd ||
+              FE->getOperator() == BinaryOperatorKind::BO_LOr)) {
+
+    FoldExpandedConstraint::FoldOperatorKind Kind =
+        FE->getOperator() == BinaryOperatorKind::BO_LAnd
+            ? FoldExpandedConstraint::FoldOperatorKind::FoAnd
+            : FoldExpandedConstraint::FoldOperatorKind::FoOr;
+
+    if (FE->getInit()) {
+      auto LHS = fromConstraintExpr(S, D, FE->getLHS());
+      auto RHS = fromConstraintExpr(S, D, FE->getRHS());
+      if (!LHS || !RHS)
+        return std::nullopt;
+
+      if (FE->isRightFold())
+        RHS = NormalizedConstraint{new (S.Context) FoldExpandedConstraint{
+            Kind, std::move(*RHS), FE->getPattern()}};
+      else
+        LHS = NormalizedConstraint{new (S.Context) FoldExpandedConstraint{
+            Kind, std::move(*LHS), FE->getPattern()}};
+
+      return NormalizedConstraint(
+          S.Context, std::move(*LHS), std::move(*RHS),
+          FE->getOperator() == BinaryOperatorKind::BO_LAnd ? CCK_Conjunction
+                                                           : CCK_Disjunction);
+    }
+    auto Sub = fromConstraintExpr(S, D, FE->getPattern());
+    return NormalizedConstraint{new (S.Context) FoldExpandedConstraint{
+        Kind, std::move(*Sub), FE->getPattern()}};
   }
+
   return NormalizedConstraint{new (S.Context) AtomicConstraint(S, E)};
 }
 
-using NormalForm =
-    llvm::SmallVector<llvm::SmallVector<AtomicConstraint *, 2>, 4>;
+/*static void CollectConstraintParams(const AtomicConstraint& C,
+SmallVectorImpl<UnexpandedParameterPack> & Packs) {
+  Sema::collectUnexpandedParameterPacks(const_cast<Expr*>(C.ConstraintExpr),
+Packs);
+}
 
-static NormalForm makeCNF(const NormalizedConstraint &Normalized) {
+static void CollectConstraintParams(const NormalizedConstraint& C,
+SmallVectorImpl<UnexpandedParameterPack> & Packs) { if(C.isAtomic())
+    CollectConstraintParams(*C.getAtomicConstraint(), Packs);
+  else {
+    CollectConstraintParams(C.getLHS(), Packs);
+    CollectConstraintParams(C.getRHS(), Packs);
+  }
+}
+*/
+
+bool FoldExpandedConstraint::AreSubsumptionElligible(
+    const FoldExpandedConstraint &A, const FoldExpandedConstraint &B) {
+  llvm::SmallVector<UnexpandedParameterPack> APacks, BPacks;
+  Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(A.Pattern), APacks);
+  Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(B.Pattern), BPacks);
+
+  for (const UnexpandedParameterPack &APack : APacks) {
+    std::pair<unsigned, unsigned> DepthAndIndex = getDepthAndIndex(APack);
+    auto it = llvm::find_if(BPacks, [&](const UnexpandedParameterPack &BPack) {
+      return getDepthAndIndex(BPack) == DepthAndIndex;
+    });
+    if (it != BPacks.end())
+      return true;
+  }
+  return false;
+}
+
+NormalForm clang::makeCNF(const NormalizedConstraint &Normalized) {
   if (Normalized.isAtomic())
     return {{Normalized.getAtomicConstraint()}};
 
+  else if (Normalized.isFoldExpanded())
+    return {{Normalized.getFoldExpandedConstraint()}};
+
   NormalForm LCNF = makeCNF(Normalized.getLHS());
   NormalForm RCNF = makeCNF(Normalized.getRHS());
   if (Normalized.getCompoundKind() == NormalizedConstraint::CCK_Conjunction) {
@@ -1426,10 +1678,13 @@ static NormalForm makeCNF(const NormalizedConstraint &Normalized) {
   return Res;
 }
 
-static NormalForm makeDNF(const NormalizedConstraint &Normalized) {
+NormalForm clang::makeDNF(const NormalizedConstraint &Normalized) {
   if (Normalized.isAtomic())
     return {{Normalized.getAtomicConstraint()}};
 
+  else if (Normalized.isFoldExpanded())
+    return {{Normalized.getFoldExpandedConstraint()}};
+
   NormalForm LDNF = makeDNF(Normalized.getLHS());
   NormalForm RDNF = makeDNF(Normalized.getRHS());
   if (Normalized.getCompoundKind() == NormalizedConstraint::CCK_Disjunction) {
@@ -1456,60 +1711,6 @@ static NormalForm makeDNF(const NormalizedConstraint &Normalized) {
   return Res;
 }
 
-template<typename AtomicSubsumptionEvaluator>
-static bool subsumes(const NormalForm &PDNF, const NormalForm &QCNF,
-                     AtomicSubsumptionEvaluator E) {
-  // C++ [temp.constr.order] p2
-  //   Then, P subsumes Q if and only if, for every disjunctive clause Pi in the
-  //   disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in
-  //   the conjuctive normal form of Q, where [...]
-  for (const auto &Pi : PDNF) {
-    for (const auto &Qj : QCNF) {
-      // C++ [temp.constr.order] p2
-      //   - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if
-      //     and only if there exists an atomic constraint Pia in Pi for which
-      //     there exists an atomic constraint, Qjb, in Qj such that Pia
-      //     subsumes Qjb.
-      bool Found = false;
-      for (const AtomicConstraint *Pia : Pi) {
-        for (const AtomicConstraint *Qjb : Qj) {
-          if (E(*Pia, *Qjb)) {
-            Found = true;
-            break;
-          }
-        }
-        if (Found)
-          break;
-      }
-      if (!Found)
-        return false;
-    }
-  }
-  return true;
-}
-
-template<typename AtomicSubsumptionEvaluator>
-static bool subsumes(Sema &S, NamedDecl *DP, ArrayRef<const Expr *> P,
-                     NamedDecl *DQ, ArrayRef<const Expr *> Q, bool &Subsumes,
-                     AtomicSubsumptionEvaluator E) {
-  // C++ [temp.constr.order] p2
-  //   In order to determine if a constraint P subsumes a constraint Q, P is
-  //   transformed into disjunctive normal form, and Q is transformed into
-  //   conjunctive normal form. [...]
-  auto *PNormalized = S.getNormalizedAssociatedConstraints(DP, P);
-  if (!PNormalized)
-    return true;
-  const NormalForm PDNF = makeDNF(*PNormalized);
-
-  auto *QNormalized = S.getNormalizedAssociatedConstraints(DQ, Q);
-  if (!QNormalized)
-    return true;
-  const NormalForm QCNF = makeCNF(*QNormalized);
-
-  Subsumes = subsumes(PDNF, QCNF, E);
-  return false;
-}
-
 bool Sema::IsAtLeastAsConstrained(NamedDecl *D1,
                                   MutableArrayRef<const Expr *> AC1,
                                   NamedDecl *D2,
@@ -1562,10 +1763,11 @@ bool Sema::IsAtLeastAsConstrained(NamedDecl *D1,
     }
   }
 
-  if (subsumes(*this, D1, AC1, D2, AC2, Result,
-        [this] (const AtomicConstraint &A, const AtomicConstraint &B) {
-          return A.subsumes(Context, B);
-        }))
+  if (clang::subsumes(
+          *this, D1, AC1, D2, AC2, Result,
+          [this](const AtomicConstraint &A, const AtomicConstraint &B) {
+            return A.subsumes(Context, B);
+          }))
     return true;
   SubsumptionCache.try_emplace(Key, Result);
   return false;
@@ -1622,10 +1824,12 @@ bool Sema::MaybeEmitAmbiguousAtomicConstraintsDiagnostic(NamedDecl *D1,
     const NormalForm DNF2 = makeDNF(*Normalized2);
     const NormalForm CNF2 = makeCNF(*Normalized2);
 
-    bool Is1AtLeastAs2Normally = subsumes(DNF1, CNF2, NormalExprEvaluator);
-    bool Is2AtLeastAs1Normally = subsumes(DNF2, CNF1, NormalExprEvaluator);
-    bool Is1AtLeastAs2 = subsumes(DNF1, CNF2, IdenticalExprEvaluator);
-    bool Is2AtLeastAs1 = subsumes(DNF2, CNF1, IdenticalExprEvaluator);
+    bool Is1AtLeastAs2Normally =
+        clang::subsumes(DNF1, CNF2, NormalExprEvaluator);
+    bool Is2AtLeastAs1Normally =
+        clang::subsumes(DNF2, CNF1, NormalExprEvaluator);
+    bool Is1AtLeastAs2 = clang::subsumes(DNF1, CNF2, IdenticalExprEvaluator);
+    bool Is2AtLeastAs1 = clang::subsumes(DNF2, CNF1, IdenticalExprEvaluator);
     if (Is1AtLeastAs2 == Is1AtLeastAs2Normally &&
         Is2AtLeastAs1 == Is2AtLeastAs1Normally)
       // Same result - no ambiguity was caused by identical atomic expressions.
diff --git a/clang/lib/Sema/SemaTemplateVariadic.cpp b/clang/lib/Sema/SemaTemplateVariadic.cpp
index 6df7f2223d267..3d4ccaf68c700 100644
--- a/clang/lib/Sema/SemaTemplateVariadic.cpp
+++ b/clang/lib/Sema/SemaTemplateVariadic.cpp
@@ -566,6 +566,10 @@ void Sema::collectUnexpandedParameterPacks(
     .TraverseDeclarationNameInfo(NameInfo);
 }
 
+void Sema::collectUnexpandedParameterPacks(
+    Expr *E, SmallVectorImpl<UnexpandedParameterPack> &Unexpanded) {
+  CollectUnexpandedParameterPacksVisitor(Unexpanded).TraverseStmt(E);
+}
 
 ParsedTemplateArgument
 Sema::ActOnPackExpansion(const ParsedTemplateArgument &Arg,
diff --git a/clang/test/SemaCXX/cxx2c-fold-exprs.cpp b/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
new file mode 100644
index 0000000000000..d267f334aedd6
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
@@ -0,0 +1,239 @@
+// RUN: %clang_cc1 -std=c++2c -verify %s
+
+template <class T> concept A = true;
+template <class T> concept C = A<T> && true;
+template <class T> concept D = A<T> && __is_same(T, int);
+
+
+template <class T> requires (A<T>)
+constexpr int f(T) { return 0; };
+template <class... T> requires (C<T> && ...)
+constexpr int f(T...) { return 1; };
+
+static_assert(f(0) == 0);
+static_assert(f(1) == 0);
+
+
+template <class... T> requires (A<T> && ...)
+constexpr int g(T...) { return 0; };
+template <class... T> requires (C<T> && ...)
+constexpr int g(T...) { return 1; };
+
+static_assert(g(0) == 1);
+static_assert(g() == 1);
+static_assert(g(1, 2) == 1);
+
+
+
+template <class... T> requires (A<T> && ...)
+constexpr int h(T...) { return 0; }; // expected-note {{candidate}}
+template <class... T> requires (C<T> || ...)
+constexpr int h(T...) { return 1; }; // expected-note {{candidate}}
+
+static_assert(h(0) == 1); // expected-error {{call to 'h' is ambiguous}}
+
+template <class... T> requires (A<T> || ...)
+constexpr int i(T...) { return 0; }; // expected-note {{candidate}}
+template <class... T> requires (C<T> && ...)
+constexpr int i(T...) { return 1; }; // expected-note {{candidate}}
+
+static_assert(i(0) == 1); // expected-error {{call to 'i' is ambiguous}}
+
+
+template <class... T> requires (A<T> || ... || true)
+constexpr int j(T...) { return 0; };
+template <class... T> requires (C<T> && ... && true)
+constexpr int j(T...) { return 1; };
+
+static_assert(j(0) == 1);
+static_assert(j() == 1);
+
+
+
+template <class... T> requires (A<T> || ...)
+constexpr int k(T...) { return 0; }; // expected-note {{candidate template ignored: constraints not satisfied [with T = <>]}}
+template <class... T> requires (C<T> || ...)
+constexpr int k(T...) { return 1; }; // expected-note {{candidate template ignored: constraints not satisfied [with T = <>]}}
+
+static_assert(k(0) == 1);
+static_assert(k() == 0); // expected-error {{no matching function for call to 'k'}}
+static_assert(k(1, 2) == 1);
+
+
+consteval int terse(A auto...) {return 1;}
+consteval int terse(D auto...) {return 2;}
+
+static_assert(terse() == 2);
+static_assert(terse(0, 0) == 2);
+static_assert(terse(0L, 0) == 1);
+
+template <A... T>
+consteval int tpl_head(A auto...) {return 1;}
+template <D... T>
+consteval int tpl_head(D auto...) {return 2;}
+
+static_assert(tpl_head() == 2);
+static_assert(tpl_head(0, 0) == 2);
+static_assert(tpl_head(0L, 0) == 1);
+
+
+namespace equivalence {
+
+template <typename... T>
+struct S {
+    template <typename... U>
+    void f() requires (A<U> && ...);
+    template <typename... U>
+    void f() requires (C<U> && ...);
+
+    template <typename... U>
+    void g() requires (A<T> && ...);
+    template <typename... U>
+    void g() requires (C<T> && ...);
+
+    template <typename... U>
+    void h() requires (A<U> && ...); // expected-note {{candidate}}
+    template <typename... U>
+    void h() requires (C<T> && ...); // expected-note {{candidate}}
+};
+
+void test() {
+    S<int>{}.f<int>();
+    S<int>{}.g<int>();
+    S<int>{}.h<int>(); // expected-error {{call to member function 'h' is ambiguous}}
+}
+
+
+}
+
+namespace substitution {
+    struct S {
+    using type = int;
+};
+
+template <typename... T>
+consteval int And1() requires (C<typename T::type> && ...) { // #and1
+    return 1;
+}
+
+template <typename T, typename... U>
+consteval int And2() requires (C<typename U::type> && ... && C<typename T::type>) { // #and2
+    return 2;
+}
+
+template <typename T, typename... U>
+consteval int And3() requires (C<typename T::type> && ... && C<typename U::type>) { // #and3
+    return 3;
+}
+
+template <typename... T>
+consteval int Or1() requires (C<typename T::type> || ...) { // #or1
+    return 1;
+}
+
+template <typename T, typename... U>
+consteval int Or2() requires (C<typename U::type> || ... || C<typename T::type>) {  // #or2
+    return 2;
+}
+
+template <typename T, typename... U>
+consteval int Or3() requires (C<typename T::type> || ... || C<typename U::type>) {  // #or3
+    return 3;
+}
+
+static_assert(And1<>() == 1);
+static_assert(And1<S>() == 1);
+static_assert(And1<S, S>() == 1);
+static_assert(And1<int>() == 1); // expected-error {{no matching function for call to 'And1'}}
+                                 // expected-note@#and1 {{candidate template ignored: constraints not satisfied}}
+                                 // expected-note@#and1 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And1<S, int>() == 1); // expected-error {{no matching function for call to 'And1'}}
+                                   // expected-note@#and1 {{candidate template ignored: constraints not satisfied}}
+                                   // expected-note@#and1 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And1<int, S>() == 1); // expected-error {{no matching function for call to 'And1'}}
+                                   // expected-note@#and1 {{candidate template ignored: constraints not satisfied}}
+                                   // expected-note@#and1 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And2<S>() == 2);
+static_assert(And2<S, S>() == 2);
+static_assert(And2<int>() == 2);
+
+static_assert(And2<int, int>() == 2);  // expected-error {{no matching function for call to 'And2'}}
+                                      // expected-note@#and2 {{candidate template ignored: constraints not satisfied}}
+                                     // expected-note@#and2 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And2<S, int>() == 2); // expected-error {{no matching function for call to 'And2'}}
+                                   // expected-note@#and2 {{candidate template ignored: constraints not satisfied}}
+                                   // expected-note@#and2 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And2<int, S>() == 2); // expected-error {{no matching function for call to 'And2'}}
+                                   // expected-note@#and2 {{candidate template ignored: constraints not satisfied}}
+                                   // expected-note@#and2 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And3<S>() == 3);
+static_assert(And3<S, S>() == 3);
+static_assert(And3<int>() == 3);   // expected-error {{no matching function for call to 'And3'}}
+                                   // expected-note@#and3 {{candidate template ignored: constraints not satisfied}}
+                                   // expected-note@#and3 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And3<int, int>() == 3);  // expected-error {{no matching function for call to 'And3'}}
+                                      // expected-note@#and3 {{candidate template ignored: constraints not satisfied}}
+                                     // expected-note@#and3 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And3<S, int>() == 3); // expected-error {{no matching function for call to 'And3'}}
+                                   // expected-note@#and3 {{candidate template ignored: constraints not satisfied}}
+                                   // expected-note@#and3 {{because substituted constraint expression is ill-formed}}
+
+static_assert(And3<int, S>() == 3); // expected-error {{no matching function for call to 'And3'}}
+                                   // expected-note@#and3 {{candidate template ignored: constraints not satisfied}}
+                                   // expected-note@#and3 {{because substituted constraint expression is ill-formed}}
+
+
+static_assert(Or1<>() == 1); // expected-error {{no matching function for call to 'Or1'}}
+                             // expected-note@#or1 {{candidate template ignored: constraints not satisfied}}
+static_assert(Or1<S>() == 1);
+static_assert(Or1<int, S>() == 1);
+static_assert(Or1<S, int>() == 1);
+static_assert(Or1<S, S>() == 1);
+static_assert(Or1<int>() == 1); // expected-error {{no matching function for call to 'Or1'}}
+                                // expected-note@#or1 {{candidate template ignored: constraints not satisfied}} \
+                                // expected-note@#or1 {{because substituted constraint expression is ill-formed}}
+
+
+static_assert(Or2<S>() == 2);
+static_assert(Or2<int, S>() == 2);
+static_assert(Or2<S, int>() == 2);
+static_assert(Or2<S, S>() == 2);
+static_assert(Or2<int>() == 2); // expected-error {{no matching function for call to 'Or2'}}
+                                // expected-note@#or2 {{candidate template ignored: constraints not satisfied}} \
+                                // expected-note@#or2 {{because substituted constraint expression is ill-formed}}
+
+static_assert(Or3<S>() == 3);
+static_assert(Or3<int, S>() == 3);
+static_assert(Or3<S, int>() == 3);
+static_assert(Or3<S, S>() == 3);
+static_assert(Or3<int>() == 3); // expected-error {{no matching function for call to 'Or3'}}
+                                // expected-note@#or3 {{candidate template ignored: constraints not satisfied}} \
+                                // expected-note@#or3 {{because substituted constraint expression is ill-formed}}
+}
+
+namespace bool_conversion_break {
+
+template <typename ...V> struct A;
+struct Thingy {
+    static constexpr int compare(const Thingy&) {return 1;}
+};
+template <typename ...T, typename ...U>
+void f(A<T ...> *, A<U ...> *) // expected-note {{candidate template ignored: failed template argument deduction}}
+requires (T::compare(U{}) && ...); // expected-error {{atomic constraint must be of type 'bool' (found 'int')}}
+
+void g() {
+    A<Thingy, Thingy> *ap;
+    f(ap, ap); // expected-error{{no matching function for call to 'f'}} \
+               // expected-note {{while checking constraint satisfaction}} \
+               // expected-note {{in instantiation of function template specialization}}
+}
+
+}
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 27e2213e54caa..a6ded8be3ae9e 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -218,7 +218,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
  <tr>
   <td>Ordering of constraints involving fold expressions</td>
   <td><a href="https://wg21.link/P2963R3">P2963R3</a></td>
-  <td class="none" align="center">No</td>
+  <td class="unreleased" align="center">Clang 19</td>
  </tr>
  <tr>
   <td>Structured binding declaration as a condition</td>

>From f698f20a40cfa9f28789ae107835ce077dd34b04 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 9 Jul 2024 18:44:07 +0200
Subject: [PATCH 2/9] Address Aarons's feedback

---
 clang/include/clang/Sema/SemaConcept.h | 38 +++++++++-----
 clang/lib/Sema/SemaConcept.cpp         | 73 ++++++++++----------------
 2 files changed, 52 insertions(+), 59 deletions(-)

diff --git a/clang/include/clang/Sema/SemaConcept.h b/clang/include/clang/Sema/SemaConcept.h
index b6327e729768b..81861ea84c6b2 100644
--- a/clang/include/clang/Sema/SemaConcept.h
+++ b/clang/include/clang/Sema/SemaConcept.h
@@ -83,7 +83,16 @@ struct NormalizedConstraint;
 using NormalForm =
     llvm::SmallVector<llvm::SmallVector<NormalFormConstraint, 2>, 4>;
 
+// A constraint is in conjunctive normal form when it is a conjunction of
+// clauses where each clause is a disjunction of atomic constraints. For atomic
+// constraints A, B, and C, the constraint A  ∧ (B  ∨ C) is in conjunctive
+// normal form.
 NormalForm makeCNF(const NormalizedConstraint &Normalized);
+
+// A constraint is in disjunctive normal form when it is a disjunction of
+// clauses where each clause is a conjunction of atomic constraints. For atomic
+// constraints A, B, and C, the disjunctive normal form of the constraint A
+//  ∧ (B  ∨ C) is (A  ∧ B)  ∨ (A  ∧ C).
 NormalForm makeDNF(const NormalizedConstraint &Normalized);
 
 /// \brief A normalized constraint, as defined in C++ [temp.constr.normal], is
@@ -162,7 +171,7 @@ struct NormalizedConstraint {
 };
 
 struct FoldExpandedConstraint {
-  enum class FoldOperatorKind { FoAnd, FoOr } Kind;
+  enum class FoldOperatorKind { And, Or } Kind;
   NormalizedConstraint Constraint;
   const Expr *Pattern;
 
@@ -172,10 +181,10 @@ struct FoldExpandedConstraint {
 
   template <typename AtomicSubsumptionEvaluator>
   bool subsumes(const FoldExpandedConstraint &Other,
-                AtomicSubsumptionEvaluator E) const;
+                const AtomicSubsumptionEvaluator &E) const;
 
-  static bool AreSubsumptionElligible(const FoldExpandedConstraint &A,
-                                      const FoldExpandedConstraint &B);
+  static bool AreSubsumptionEligible(const FoldExpandedConstraint &A,
+                                     const FoldExpandedConstraint &B);
 };
 
 const NormalizedConstraint *getNormalizedAssociatedConstraints(
@@ -184,7 +193,7 @@ const NormalizedConstraint *getNormalizedAssociatedConstraints(
 
 template <typename AtomicSubsumptionEvaluator>
 bool subsumes(const NormalForm &PDNF, const NormalForm &QCNF,
-              AtomicSubsumptionEvaluator E) {
+              const AtomicSubsumptionEvaluator &E) {
   // C++ [temp.constr.order] p2
   //   Then, P subsumes Q if and only if, for every disjunctive clause Pi in the
   //   disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in
@@ -228,29 +237,32 @@ bool subsumes(const NormalForm &PDNF, const NormalForm &QCNF,
 template <typename AtomicSubsumptionEvaluator>
 bool subsumes(Sema &S, NamedDecl *DP, ArrayRef<const Expr *> P, NamedDecl *DQ,
               ArrayRef<const Expr *> Q, bool &Subsumes,
-              AtomicSubsumptionEvaluator E) {
+              const AtomicSubsumptionEvaluator &E) {
   // C++ [temp.constr.order] p2
   //   In order to determine if a constraint P subsumes a constraint Q, P is
   //   transformed into disjunctive normal form, and Q is transformed into
   //   conjunctive normal form. [...]
-  auto *PNormalized = getNormalizedAssociatedConstraints(S, DP, P);
+  const NormalizedConstraint *PNormalized =
+      getNormalizedAssociatedConstraints(S, DP, P);
   if (!PNormalized)
     return true;
-  const NormalForm PDNF = makeDNF(*PNormalized);
+  NormalForm PDNF = makeDNF(*PNormalized);
 
-  auto *QNormalized = getNormalizedAssociatedConstraints(S, DQ, Q);
+  const NormalizedConstraint *QNormalized =
+      getNormalizedAssociatedConstraints(S, DQ, Q);
   if (!QNormalized)
     return true;
-  const NormalForm QCNF = makeCNF(*QNormalized);
+  NormalForm QCNF = makeCNF(*QNormalized);
 
   Subsumes = subsumes(PDNF, QCNF, E);
   return false;
 }
 
 template <typename AtomicSubsumptionEvaluator>
-bool FoldExpandedConstraint::subsumes(const FoldExpandedConstraint &Other,
-                                      AtomicSubsumptionEvaluator E) const {
-  if (Kind != Other.Kind || !AreSubsumptionElligible(*this, Other))
+bool FoldExpandedConstraint::subsumes(
+    const FoldExpandedConstraint &Other,
+    const AtomicSubsumptionEvaluator &E) const {
+  if (Kind != Other.Kind || !AreSubsumptionEligible(*this, Other))
     return false;
 
   const NormalForm PDNF = makeDNF(this->Constraint);
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index b86e2d51e380b..70bf24d06f49b 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -182,12 +182,14 @@ template <typename ConstraintEvaluator>
 static ExprResult
 calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
                                 ConstraintSatisfaction &Satisfaction,
-                                ConstraintEvaluator &&Evaluator);
+                                const ConstraintEvaluator &Evaluator);
 
 template <typename ConstraintEvaluator>
-static ExprResult calculateConstraintSatisfaction(
-    Sema &S, const Expr *LHS, OverloadedOperatorKind Op, const Expr *RHS,
-    ConstraintSatisfaction &Satisfaction, ConstraintEvaluator &Evaluator) {
+static ExprResult
+calculateConstraintSatisfaction(Sema &S, const Expr *LHS,
+                                OverloadedOperatorKind Op, const Expr *RHS,
+                                ConstraintSatisfaction &Satisfaction,
+                                const ConstraintEvaluator &Evaluator) {
   size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
 
   ExprResult LHSRes =
@@ -218,8 +220,8 @@ static ExprResult calculateConstraintSatisfaction(
     // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
     return LHSRes;
 
-  ExprResult RHSRes = calculateConstraintSatisfaction(
-      S, RHS, Satisfaction, std::forward<ConstraintEvaluator>(Evaluator));
+  ExprResult RHSRes =
+      calculateConstraintSatisfaction(S, RHS, Satisfaction, Evaluator);
   if (RHSRes.isInvalid())
     return ExprError();
 
@@ -251,7 +253,7 @@ template <typename ConstraintEvaluator>
 static ExprResult
 calculateConstraintSatisfaction(Sema &S, const CXXFoldExpr *FE,
                                 ConstraintSatisfaction &Satisfaction,
-                                ConstraintEvaluator &&Evaluator) {
+                                const ConstraintEvaluator &Evaluator) {
   bool Conjunction = FE->getOperator() == BinaryOperatorKind::BO_LAnd;
   size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
 
@@ -261,13 +263,13 @@ calculateConstraintSatisfaction(Sema &S, const CXXFoldExpr *FE,
                                           Evaluator);
     if (Out.isInvalid())
       return ExprError();
-    bool IsLHSSatisfied = Satisfaction.IsSatisfied;
-    if (Conjunction && !IsLHSSatisfied) {
-      return Out;
-    }
-    if (!Conjunction && IsLHSSatisfied) {
+
+    // If the first clause of a conjunction is not satisfied,
+    // or if the first clause of a disjection is satisfied,
+    // we have established satisfaction of the whole constraint
+    // and we should not continue further.
+    if (Conjunction != Satisfaction.IsSatisfied)
       return Out;
-    }
   }
   std::optional<unsigned> NumExpansions =
       Evaluator.EvaluateFoldExpandedConstraintSize(FE);
@@ -293,9 +295,7 @@ calculateConstraintSatisfaction(Sema &S, const CXXFoldExpr *FE,
           S.Context, Out.get(), Res.get(), FE->getOperator(), S.Context.BoolTy,
           VK_PRValue, OK_Ordinary, FE->getBeginLoc(), FPOptionsOverride{});
     }
-    if (Conjunction && !IsRHSSatisfied)
-      return Out;
-    if (!Conjunction && IsRHSSatisfied)
+    if (Conjunction != IsRHSSatisfied)
       return Out;
   }
 
@@ -325,21 +325,18 @@ template <typename ConstraintEvaluator>
 static ExprResult
 calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
                                 ConstraintSatisfaction &Satisfaction,
-                                ConstraintEvaluator &&Evaluator) {
+                                const ConstraintEvaluator &Evaluator) {
   ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
 
-  if (LogicalBinOp BO = ConstraintExpr) {
-
+  if (LogicalBinOp BO = ConstraintExpr)
     return calculateConstraintSatisfaction(
         S, BO.getLHS(), BO.getOp(), BO.getRHS(), Satisfaction, Evaluator);
-  }
 
   if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr)) {
     // These aren't evaluated, so we don't care about cleanups, so we can just
     // evaluate these as if the cleanups didn't exist.
-    return calculateConstraintSatisfaction(
-        S, C->getSubExpr(), Satisfaction,
-        std::forward<ConstraintEvaluator>(Evaluator));
+    return calculateConstraintSatisfaction(S, C->getSubExpr(), Satisfaction,
+                                           Evaluator);
   }
 
   if (auto *FE = dyn_cast<CXXFoldExpr>(ConstraintExpr);
@@ -448,7 +445,7 @@ static ExprResult calculateConstraintSatisfaction(
     const MultiLevelTemplateArgumentList &MLTAL;
     ConstraintSatisfaction &Satisfaction;
 
-    ExprResult EvaluateAtomicConstraint(const Expr *AtomicExpr) {
+    ExprResult EvaluateAtomicConstraint(const Expr *AtomicExpr) const {
       EnterExpressionEvaluationContext ConstantEvaluated(
           S, Sema::ExpressionEvaluationContext::ConstantEvaluated,
           Sema::ReuseLambdaContextDecl);
@@ -536,7 +533,7 @@ static ExprResult calculateConstraintSatisfaction(
     }
 
     std::optional<unsigned>
-    EvaluateFoldExpandedConstraintSize(const CXXFoldExpr *FE) {
+    EvaluateFoldExpandedConstraintSize(const CXXFoldExpr *FE) const {
       Expr *Pattern = FE->getPattern();
 
       SmallVector<UnexpandedParameterPack, 2> Unexpanded;
@@ -685,12 +682,12 @@ bool Sema::CheckConstraintSatisfaction(const Expr *ConstraintExpr,
 
   struct ConstraintEvaluator {
     Sema &S;
-    ExprResult EvaluateAtomicConstraint(const Expr *AtomicExpr) {
+    ExprResult EvaluateAtomicConstraint(const Expr *AtomicExpr) const {
       return S.PerformContextuallyConvertToBool(const_cast<Expr *>(AtomicExpr));
     }
 
     std::optional<unsigned>
-    EvaluateFoldExpandedConstraintSize(const CXXFoldExpr *FE) {
+    EvaluateFoldExpandedConstraintSize(const CXXFoldExpr *FE) const {
       return 0;
     }
   };
@@ -1584,8 +1581,8 @@ NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
 
     FoldExpandedConstraint::FoldOperatorKind Kind =
         FE->getOperator() == BinaryOperatorKind::BO_LAnd
-            ? FoldExpandedConstraint::FoldOperatorKind::FoAnd
-            : FoldExpandedConstraint::FoldOperatorKind::FoOr;
+            ? FoldExpandedConstraint::FoldOperatorKind::And
+            : FoldExpandedConstraint::FoldOperatorKind::Or;
 
     if (FE->getInit()) {
       auto LHS = fromConstraintExpr(S, D, FE->getLHS());
@@ -1613,23 +1610,7 @@ NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
   return NormalizedConstraint{new (S.Context) AtomicConstraint(S, E)};
 }
 
-/*static void CollectConstraintParams(const AtomicConstraint& C,
-SmallVectorImpl<UnexpandedParameterPack> & Packs) {
-  Sema::collectUnexpandedParameterPacks(const_cast<Expr*>(C.ConstraintExpr),
-Packs);
-}
-
-static void CollectConstraintParams(const NormalizedConstraint& C,
-SmallVectorImpl<UnexpandedParameterPack> & Packs) { if(C.isAtomic())
-    CollectConstraintParams(*C.getAtomicConstraint(), Packs);
-  else {
-    CollectConstraintParams(C.getLHS(), Packs);
-    CollectConstraintParams(C.getRHS(), Packs);
-  }
-}
-*/
-
-bool FoldExpandedConstraint::AreSubsumptionElligible(
+bool FoldExpandedConstraint::AreSubsumptionEligible(
     const FoldExpandedConstraint &A, const FoldExpandedConstraint &B) {
   llvm::SmallVector<UnexpandedParameterPack> APacks, BPacks;
   Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(A.Pattern), APacks);

>From 911de61971f4351f41d2018e6398f0c8402419cc Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 9 Jul 2024 18:49:15 +0200
Subject: [PATCH 3/9] s/AreSubsumptionEligible/AreCompatibleForSubsumption to
 use the correct standard term

---
 clang/include/clang/Sema/SemaConcept.h | 6 +++---
 clang/lib/Sema/SemaConcept.cpp         | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/Sema/SemaConcept.h b/clang/include/clang/Sema/SemaConcept.h
index 81861ea84c6b2..b16bd59ee257a 100644
--- a/clang/include/clang/Sema/SemaConcept.h
+++ b/clang/include/clang/Sema/SemaConcept.h
@@ -183,8 +183,8 @@ struct FoldExpandedConstraint {
   bool subsumes(const FoldExpandedConstraint &Other,
                 const AtomicSubsumptionEvaluator &E) const;
 
-  static bool AreSubsumptionEligible(const FoldExpandedConstraint &A,
-                                     const FoldExpandedConstraint &B);
+  static bool AreCompatibleForSubsumption(const FoldExpandedConstraint &A,
+                                          const FoldExpandedConstraint &B);
 };
 
 const NormalizedConstraint *getNormalizedAssociatedConstraints(
@@ -262,7 +262,7 @@ template <typename AtomicSubsumptionEvaluator>
 bool FoldExpandedConstraint::subsumes(
     const FoldExpandedConstraint &Other,
     const AtomicSubsumptionEvaluator &E) const {
-  if (Kind != Other.Kind || !AreSubsumptionEligible(*this, Other))
+  if (Kind != Other.Kind || !AreCompatibleForSubsumption(*this, Other))
     return false;
 
   const NormalForm PDNF = makeDNF(this->Constraint);
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 70bf24d06f49b..4e8b7a4f5b2bb 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -1610,7 +1610,7 @@ NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
   return NormalizedConstraint{new (S.Context) AtomicConstraint(S, E)};
 }
 
-bool FoldExpandedConstraint::AreSubsumptionEligible(
+bool FoldExpandedConstraint::AreCompatibleForSubsumption(
     const FoldExpandedConstraint &A, const FoldExpandedConstraint &B) {
   llvm::SmallVector<UnexpandedParameterPack> APacks, BPacks;
   Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(A.Pattern), APacks);

>From e300b50e880a90668c1aeeae1a8955e8b02d842a Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 9 Jul 2024 19:13:54 +0200
Subject: [PATCH 4/9] Add comments

---
 clang/include/clang/Sema/SemaConcept.h | 6 ++++++
 clang/lib/Sema/SemaConcept.cpp         | 7 +++++++
 2 files changed, 13 insertions(+)

diff --git a/clang/include/clang/Sema/SemaConcept.h b/clang/include/clang/Sema/SemaConcept.h
index b16bd59ee257a..8ec74eea9d3b0 100644
--- a/clang/include/clang/Sema/SemaConcept.h
+++ b/clang/include/clang/Sema/SemaConcept.h
@@ -262,6 +262,12 @@ template <typename AtomicSubsumptionEvaluator>
 bool FoldExpandedConstraint::subsumes(
     const FoldExpandedConstraint &Other,
     const AtomicSubsumptionEvaluator &E) const {
+
+  // [C++26] [temp.constr.order]
+  // a fold expanded constraint A subsumes another fold expanded constraint B if
+  // they are compatible for subsumption, have the same fold-operator, and the
+  // constraint of A subsumes that of B
+
   if (Kind != Other.Kind || !AreCompatibleForSubsumption(*this, Other))
     return false;
 
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 4e8b7a4f5b2bb..33f01526b507b 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -1579,6 +1579,8 @@ NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
              (FE->getOperator() == BinaryOperatorKind::BO_LAnd ||
               FE->getOperator() == BinaryOperatorKind::BO_LOr)) {
 
+    // Normalize fold expressions in C++26.
+
     FoldExpandedConstraint::FoldOperatorKind Kind =
         FE->getOperator() == BinaryOperatorKind::BO_LAnd
             ? FoldExpandedConstraint::FoldOperatorKind::And
@@ -1612,6 +1614,11 @@ NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
 
 bool FoldExpandedConstraint::AreCompatibleForSubsumption(
     const FoldExpandedConstraint &A, const FoldExpandedConstraint &B) {
+
+  // [C++26] [temp.constr.fold]
+  // Two fold expanded constraints are compatible for subsumption
+  // if their respective constraints both contain an equivalent unexpanded pack.
+
   llvm::SmallVector<UnexpandedParameterPack> APacks, BPacks;
   Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(A.Pattern), APacks);
   Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(B.Pattern), BPacks);

>From 860bbe99a619c15f58a5822628c39e4b6eebf1c9 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 9 Jul 2024 19:27:15 +0200
Subject: [PATCH 5/9] Add nested pack tests

---
 clang/test/SemaCXX/cxx2c-fold-exprs.cpp | 38 +++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/clang/test/SemaCXX/cxx2c-fold-exprs.cpp b/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
index d267f334aedd6..1e0bc7bcfb4e7 100644
--- a/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
+++ b/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
@@ -237,3 +237,41 @@ void g() {
 }
 
 }
+
+namespace nested {
+
+template <typename... T>
+struct S {
+    template <typename... U>
+    consteval static int f()
+        requires ((A<T> && ...) && ... && A<U> ) {
+            return 1;
+    }
+
+    template <typename... U>
+    consteval static int f()
+        requires ((C<T> && ...) && ... && C<U> ) {
+            return 2;
+    }
+
+    template <typename... U>
+    consteval static int g() // #nested-ambiguous-g1
+        requires ((A<T> && ...) && ... && A<U> ) {
+            return 1;
+    }
+
+    template <typename... U>
+    consteval static int g() // #nested-ambiguous-g2
+        requires ((C<U> && ...) && ... && C<T> ) {
+            return 2;
+    }
+};
+
+static_assert(S<int>::f<int>() == 2);
+
+static_assert(S<int>::g<int>() == 2); // expected-error {{call to 'g' is ambiguous}}
+                                      // expected-note@#nested-ambiguous-g1 {{candidate}}
+                                      // expected-note@#nested-ambiguous-g2 {{candidate}}
+
+
+}

>From dbca4522c73a71783648c5019b884af56c171755 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Wed, 10 Jul 2024 11:51:34 +0200
Subject: [PATCH 6/9] fix crash

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

diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 33f01526b507b..1b200f2201fdb 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -1605,6 +1605,8 @@ NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
                                                            : CCK_Disjunction);
     }
     auto Sub = fromConstraintExpr(S, D, FE->getPattern());
+    if (!Sub)
+      return std::nullopt;
     return NormalizedConstraint{new (S.Context) FoldExpandedConstraint{
         Kind, std::move(*Sub), FE->getPattern()}};
   }

>From 80fa92c45983cd1104d05b4abb034da49f135f05 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Wed, 10 Jul 2024 15:25:39 +0200
Subject: [PATCH 7/9] remove top level const

---
 clang/include/clang/Sema/SemaConcept.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Sema/SemaConcept.h b/clang/include/clang/Sema/SemaConcept.h
index 8ec74eea9d3b0..d66a3537f68ea 100644
--- a/clang/include/clang/Sema/SemaConcept.h
+++ b/clang/include/clang/Sema/SemaConcept.h
@@ -271,8 +271,8 @@ bool FoldExpandedConstraint::subsumes(
   if (Kind != Other.Kind || !AreCompatibleForSubsumption(*this, Other))
     return false;
 
-  const NormalForm PDNF = makeDNF(this->Constraint);
-  const NormalForm QCNF = makeCNF(Other.Constraint);
+  NormalForm PDNF = makeDNF(this->Constraint);
+  NormalForm QCNF = makeCNF(Other.Constraint);
   return clang::subsumes(PDNF, QCNF, E);
 }
 

>From e701c0a7e942d3513688479fbd73163124612143 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 11 Jul 2024 15:45:19 +0200
Subject: [PATCH 8/9] fix libc++ crash

---
 clang/include/clang/Sema/SemaConcept.h | 15 ++++++++-------
 clang/lib/Sema/SemaConcept.cpp         | 11 ++++++++++-
 2 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/clang/include/clang/Sema/SemaConcept.h b/clang/include/clang/Sema/SemaConcept.h
index d66a3537f68ea..3617592477930 100644
--- a/clang/include/clang/Sema/SemaConcept.h
+++ b/clang/include/clang/Sema/SemaConcept.h
@@ -131,23 +131,24 @@ struct NormalizedConstraint {
     return *this;
   }
 
-  CompoundConstraintKind getCompoundKind() const {
-    assert(!isAtomic() && "getCompoundKind called on atomic constraint.");
-    return Constraint.get<CompoundConstraint>().getInt();
-  }
-
   bool isAtomic() const { return Constraint.is<AtomicConstraint *>(); }
   bool isFoldExpanded() const {
     return Constraint.is<FoldExpandedConstraint *>();
   }
+  bool isCompound() const { return Constraint.is<CompoundConstraint *>(); }
+
+  CompoundConstraintKind getCompoundKind() const {
+    assert(isCompound() && "getCompoundKind on a non-compound constraint..");
+    return Constraint.get<CompoundConstraint>().getInt();
+  }
 
   NormalizedConstraint &getLHS() const {
-    assert(!isAtomic() && "getLHS called on atomic constraint.");
+    assert(isCompound() && "getLHS called on a non-compound constraint.");
     return Constraint.get<CompoundConstraint>().getPointer()->first;
   }
 
   NormalizedConstraint &getRHS() const {
-    assert(!isAtomic() && "getRHS called on atomic constraint.");
+    assert(isCompound() && "getRHS called on a non-compound constraint.");
     return Constraint.get<CompoundConstraint>().getPointer()->second;
   }
 
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 1b200f2201fdb..b42329cd9d366 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -1400,13 +1400,22 @@ substituteParameterMappings(Sema &S, NormalizedConstraint &N,
                             ConceptDecl *Concept,
                             const MultiLevelTemplateArgumentList &MLTAL,
                             const ASTTemplateArgumentListInfo *ArgsAsWritten) {
-  if (!N.isAtomic()) {
+
+  if (N.isCompound()) {
     if (substituteParameterMappings(S, N.getLHS(), Concept, MLTAL,
                                     ArgsAsWritten))
       return true;
     return substituteParameterMappings(S, N.getRHS(), Concept, MLTAL,
                                        ArgsAsWritten);
   }
+
+  if (N.isFoldExpanded()) {
+    Sema::ArgumentPackSubstitutionIndexRAII _(S, -1);
+    return substituteParameterMappings(
+        S, N.getFoldExpandedConstraint()->Constraint, Concept, MLTAL,
+        ArgsAsWritten);
+  }
+
   TemplateParameterList *TemplateParams = Concept->getTemplateParameters();
 
   AtomicConstraint &Atomic = *N.getAtomicConstraint();

>From bfbf9451016cffff06e8bbfeacdb4091d001b9f3 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 11 Jul 2024 16:16:33 +0200
Subject: [PATCH 9/9] fix build

---
 clang/include/clang/Sema/SemaConcept.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Sema/SemaConcept.h b/clang/include/clang/Sema/SemaConcept.h
index 3617592477930..8fb7dd6838e57 100644
--- a/clang/include/clang/Sema/SemaConcept.h
+++ b/clang/include/clang/Sema/SemaConcept.h
@@ -135,7 +135,7 @@ struct NormalizedConstraint {
   bool isFoldExpanded() const {
     return Constraint.is<FoldExpandedConstraint *>();
   }
-  bool isCompound() const { return Constraint.is<CompoundConstraint *>(); }
+  bool isCompound() const { return Constraint.is<CompoundConstraint>(); }
 
   CompoundConstraintKind getCompoundKind() const {
     assert(isCompound() && "getCompoundKind on a non-compound constraint..");



More information about the cfe-commits mailing list