https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/98160
>From bbb0f550b72c317f02e572c2e8edf2e4df0c49bd Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@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 | 602 ++++++++++++++++-------- clang/lib/Sema/SemaTemplateVariadic.cpp | 4 + clang/test/SemaCXX/cxx2c-fold-exprs.cpp | 239 ++++++++++ clang/www/cxx_status.html | 2 +- 7 files changed, 780 insertions(+), 217 deletions(-) create mode 100644 clang/test/SemaCXX/cxx2c-fold-exprs.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 5dc0f8b7e0bbb..a63a9d60789c5 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -278,6 +278,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 6be6f6725e5b7..1113f687e69a8 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -14051,6 +14051,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 54891150da20f..930e539abe8a5 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(); @@ -334,91 +438,132 @@ 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( - 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( + 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( @@ -534,13 +679,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(); } @@ -1235,6 +1388,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, @@ -1313,6 +1473,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) { @@ -1387,17 +1574,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) { @@ -1423,10 +1675,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) { @@ -1453,60 +1708,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, @@ -1559,10 +1760,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; @@ -1619,10 +1821,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 5c0c1012ab47fcf92e66d50665400cfe0eec443b Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@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 930e539abe8a5..6edddad06bb62 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); @@ -446,7 +443,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); @@ -533,7 +530,7 @@ static ExprResult calculateConstraintSatisfaction( } std::optional<unsigned> - EvaluateFoldExpandedConstraintSize(const CXXFoldExpr *FE) { + EvaluateFoldExpandedConstraintSize(const CXXFoldExpr *FE) const { Expr *Pattern = FE->getPattern(); SmallVector<UnexpandedParameterPack, 2> Unexpanded; @@ -682,12 +679,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; } }; @@ -1581,8 +1578,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()); @@ -1610,23 +1607,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 232def4c61c1f30d004848d5b2bb20543f5b8586 Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@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 6edddad06bb62..b296c348ce15f 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -1607,7 +1607,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 7a9c8f2b7794d9a9479ba11e0fd2fa9b903a9921 Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@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 b296c348ce15f..a00069ff1589a 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -1576,6 +1576,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 @@ -1609,6 +1611,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 7c5551560bc71467da0dde654318be09cc69e66e Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@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 0f2b7818aea2120ad4f67ca94da1be38d520d7cc Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@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 a00069ff1589a..7b9a764496a9f 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -1602,6 +1602,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 4761ace6f22366dfe24e5e307ee36e0bdc3b9735 Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@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 1ee609a21617047a0945810dea093335da5d4620 Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@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 7b9a764496a9f..70562a327dcaa 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -1397,13 +1397,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 ced5a727a6fb39ace4f393ce174f72de8ce67ba1 Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@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.."); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits