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

via cfe-commits cfe-commits at lists.llvm.org
Tue Jul 9 07:11:29 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: cor3ntin (cor3ntin)

<details>
<summary>Changes</summary>

Implement https://isocpp.org/files/papers/P2963R3.pdf

---

Patch is 50.57 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/98160.diff


7 Files Affected:

- (modified) clang/docs/ReleaseNotes.rst (+3) 
- (modified) clang/include/clang/Sema/Sema.h (+5) 
- (modified) clang/include/clang/Sema/SemaConcept.h (+125-17) 
- (modified) clang/lib/Sema/SemaConcept.cpp (+404-200) 
- (modified) clang/lib/Sema/SemaTemplateVariadic.cpp (+4) 
- (added) clang/test/SemaCXX/cxx2c-fold-exprs.cpp (+239) 
- (modified) clang/www/cxx_status.html (+1-1) 


``````````diff
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...
[truncated]

``````````

</details>


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


More information about the cfe-commits mailing list