| //===-- SemaConcept.cpp - Semantic Analysis for Constraints and Concepts --===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file implements semantic analysis for C++ constraints and concepts. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/Sema/SemaConcept.h" |
| #include "TreeTransform.h" |
| #include "clang/AST/ASTConcept.h" |
| #include "clang/AST/ASTLambda.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/ExprConcepts.h" |
| #include "clang/AST/RecursiveASTVisitor.h" |
| #include "clang/Basic/OperatorPrecedence.h" |
| #include "clang/Sema/EnterExpressionEvaluationContext.h" |
| #include "clang/Sema/Initialization.h" |
| #include "clang/Sema/Overload.h" |
| #include "clang/Sema/ScopeInfo.h" |
| #include "clang/Sema/Sema.h" |
| #include "clang/Sema/SemaInternal.h" |
| #include "clang/Sema/Template.h" |
| #include "clang/Sema/TemplateDeduction.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/PointerUnion.h" |
| #include "llvm/ADT/StringExtras.h" |
| #include "llvm/Support/SaveAndRestore.h" |
| |
| using namespace clang; |
| using namespace sema; |
| |
| namespace { |
| class LogicalBinOp { |
| SourceLocation Loc; |
| OverloadedOperatorKind Op = OO_None; |
| const Expr *LHS = nullptr; |
| const Expr *RHS = nullptr; |
| |
| public: |
| LogicalBinOp(const Expr *E) { |
| if (auto *BO = dyn_cast<BinaryOperator>(E)) { |
| Op = BinaryOperator::getOverloadedOperator(BO->getOpcode()); |
| LHS = BO->getLHS(); |
| RHS = BO->getRHS(); |
| Loc = BO->getExprLoc(); |
| } else if (auto *OO = dyn_cast<CXXOperatorCallExpr>(E)) { |
| // If OO is not || or && it might not have exactly 2 arguments. |
| if (OO->getNumArgs() == 2) { |
| Op = OO->getOperator(); |
| LHS = OO->getArg(0); |
| RHS = OO->getArg(1); |
| Loc = OO->getOperatorLoc(); |
| } |
| } |
| } |
| |
| bool isAnd() const { return Op == OO_AmpAmp; } |
| bool isOr() const { return Op == OO_PipePipe; } |
| explicit operator bool() const { return isAnd() || isOr(); } |
| |
| 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())); |
| } |
| |
| ExprResult recreateBinOp(Sema &SemaRef, ExprResult LHS, |
| ExprResult RHS) const { |
| assert((isAnd() || isOr()) && "Not the right kind of op?"); |
| assert((!LHS.isInvalid() && !RHS.isInvalid()) && "not good expressions?"); |
| |
| if (!LHS.isUsable() || !RHS.isUsable()) |
| return ExprEmpty(); |
| |
| // We should just be able to 'normalize' these to the builtin Binary |
| // Operator, since that is how they are evaluated in constriant checks. |
| return BinaryOperator::Create(SemaRef.Context, LHS.get(), RHS.get(), |
| BinaryOperator::getOverloadedOpcode(Op), |
| SemaRef.Context.BoolTy, VK_PRValue, |
| OK_Ordinary, Loc, FPOptionsOverride{}); |
| } |
| }; |
| } // namespace |
| |
| bool Sema::CheckConstraintExpression(const Expr *ConstraintExpression, |
| Token NextToken, bool *PossibleNonPrimary, |
| bool IsTrailingRequiresClause) { |
| // C++2a [temp.constr.atomic]p1 |
| // ..E shall be a constant expression of type bool. |
| |
| ConstraintExpression = ConstraintExpression->IgnoreParenImpCasts(); |
| |
| if (LogicalBinOp BO = ConstraintExpression) { |
| return CheckConstraintExpression(BO.getLHS(), NextToken, |
| PossibleNonPrimary) && |
| CheckConstraintExpression(BO.getRHS(), NextToken, |
| PossibleNonPrimary); |
| } else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpression)) |
| return CheckConstraintExpression(C->getSubExpr(), NextToken, |
| PossibleNonPrimary); |
| |
| QualType Type = ConstraintExpression->getType(); |
| |
| auto CheckForNonPrimary = [&] { |
| if (!PossibleNonPrimary) |
| return; |
| |
| *PossibleNonPrimary = |
| // We have the following case: |
| // template<typename> requires func(0) struct S { }; |
| // The user probably isn't aware of the parentheses required around |
| // the function call, and we're only going to parse 'func' as the |
| // primary-expression, and complain that it is of non-bool type. |
| // |
| // However, if we're in a lambda, this might also be: |
| // []<typename> requires var () {}; |
| // Which also looks like a function call due to the lambda parentheses, |
| // but unlike the first case, isn't an error, so this check is skipped. |
| (NextToken.is(tok::l_paren) && |
| (IsTrailingRequiresClause || |
| (Type->isDependentType() && |
| isa<UnresolvedLookupExpr>(ConstraintExpression) && |
| !dyn_cast_if_present<LambdaScopeInfo>(getCurFunction())) || |
| Type->isFunctionType() || |
| Type->isSpecificBuiltinType(BuiltinType::Overload))) || |
| // We have the following case: |
| // template<typename T> requires size_<T> == 0 struct S { }; |
| // The user probably isn't aware of the parentheses required around |
| // the binary operator, and we're only going to parse 'func' as the |
| // first operand, and complain that it is of non-bool type. |
| getBinOpPrecedence(NextToken.getKind(), |
| /*GreaterThanIsOperator=*/true, |
| getLangOpts().CPlusPlus11) > prec::LogicalAnd; |
| }; |
| |
| // An atomic constraint! |
| if (ConstraintExpression->isTypeDependent()) { |
| CheckForNonPrimary(); |
| return true; |
| } |
| |
| if (!Context.hasSameUnqualifiedType(Type, Context.BoolTy)) { |
| Diag(ConstraintExpression->getExprLoc(), |
| diag::err_non_bool_atomic_constraint) |
| << Type << ConstraintExpression->getSourceRange(); |
| CheckForNonPrimary(); |
| return false; |
| } |
| |
| if (PossibleNonPrimary) |
| *PossibleNonPrimary = false; |
| return true; |
| } |
| |
| namespace { |
| struct SatisfactionStackRAII { |
| Sema &SemaRef; |
| bool Inserted = false; |
| SatisfactionStackRAII(Sema &SemaRef, const NamedDecl *ND, |
| const llvm::FoldingSetNodeID &FSNID) |
| : SemaRef(SemaRef) { |
| if (ND) { |
| SemaRef.PushSatisfactionStackEntry(ND, FSNID); |
| Inserted = true; |
| } |
| } |
| ~SatisfactionStackRAII() { |
| if (Inserted) |
| SemaRef.PopSatisfactionStackEntry(); |
| } |
| }; |
| } // namespace |
| |
| static bool DiagRecursiveConstraintEval( |
| Sema &S, llvm::FoldingSetNodeID &ID, const NamedDecl *Templ, const Expr *E, |
| const MultiLevelTemplateArgumentList *MLTAL = nullptr) { |
| E->Profile(ID, S.Context, /*Canonical=*/true); |
| if (MLTAL) { |
| for (const auto &List : *MLTAL) |
| for (const auto &TemplateArg : List.Args) |
| S.Context.getCanonicalTemplateArgument(TemplateArg) |
| .Profile(ID, S.Context); |
| } |
| if (S.SatisfactionStackContains(Templ, ID)) { |
| S.Diag(E->getExprLoc(), diag::err_constraint_depends_on_self) |
| << E << E->getSourceRange(); |
| return true; |
| } |
| return false; |
| } |
| |
| // Figure out the to-translation-unit depth for this function declaration for |
| // the purpose of seeing if they differ by constraints. This isn't the same as |
| // getTemplateDepth, because it includes already instantiated parents. |
| static unsigned |
| CalculateTemplateDepthForConstraints(Sema &S, const NamedDecl *ND, |
| bool SkipForSpecialization = false) { |
| MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs( |
| ND, ND->getLexicalDeclContext(), /*Final=*/false, |
| /*Innermost=*/std::nullopt, |
| /*RelativeToPrimary=*/true, |
| /*Pattern=*/nullptr, |
| /*ForConstraintInstantiation=*/true, SkipForSpecialization); |
| return MLTAL.getNumLevels(); |
| } |
| |
| namespace { |
| class AdjustConstraintDepth : public TreeTransform<AdjustConstraintDepth> { |
| unsigned TemplateDepth = 0; |
| |
| public: |
| using inherited = TreeTransform<AdjustConstraintDepth>; |
| AdjustConstraintDepth(Sema &SemaRef, unsigned TemplateDepth) |
| : inherited(SemaRef), TemplateDepth(TemplateDepth) {} |
| |
| using inherited::TransformTemplateTypeParmType; |
| QualType TransformTemplateTypeParmType(TypeLocBuilder &TLB, |
| TemplateTypeParmTypeLoc TL, bool) { |
| const TemplateTypeParmType *T = TL.getTypePtr(); |
| |
| TemplateTypeParmDecl *NewTTPDecl = nullptr; |
| if (TemplateTypeParmDecl *OldTTPDecl = T->getDecl()) |
| NewTTPDecl = cast_or_null<TemplateTypeParmDecl>( |
| TransformDecl(TL.getNameLoc(), OldTTPDecl)); |
| |
| QualType Result = getSema().Context.getTemplateTypeParmType( |
| T->getDepth() + TemplateDepth, T->getIndex(), T->isParameterPack(), |
| NewTTPDecl); |
| TemplateTypeParmTypeLoc NewTL = TLB.push<TemplateTypeParmTypeLoc>(Result); |
| NewTL.setNameLoc(TL.getNameLoc()); |
| return Result; |
| } |
| |
| bool AlreadyTransformed(QualType T) { |
| if (T.isNull()) |
| return true; |
| |
| if (T->isInstantiationDependentType() || T->isVariablyModifiedType() || |
| T->containsUnexpandedParameterPack()) |
| return false; |
| return true; |
| } |
| }; |
| } // namespace |
| |
| namespace { |
| |
| // FIXME: Convert it to DynamicRecursiveASTVisitor |
| class HashParameterMapping : public RecursiveASTVisitor<HashParameterMapping> { |
| using inherited = RecursiveASTVisitor<HashParameterMapping>; |
| friend inherited; |
| |
| Sema &SemaRef; |
| const MultiLevelTemplateArgumentList &TemplateArgs; |
| llvm::FoldingSetNodeID &ID; |
| llvm::SmallVector<TemplateArgument, 10> UsedTemplateArgs; |
| |
| UnsignedOrNone OuterPackSubstIndex; |
| |
| bool shouldVisitTemplateInstantiations() const { return true; } |
| |
| public: |
| HashParameterMapping(Sema &SemaRef, |
| const MultiLevelTemplateArgumentList &TemplateArgs, |
| llvm::FoldingSetNodeID &ID, |
| UnsignedOrNone OuterPackSubstIndex) |
| : SemaRef(SemaRef), TemplateArgs(TemplateArgs), ID(ID), |
| OuterPackSubstIndex(OuterPackSubstIndex) {} |
| |
| bool VisitTemplateTypeParmType(TemplateTypeParmType *T) { |
| // A lambda expression can introduce template parameters that don't have |
| // corresponding template arguments yet. |
| if (T->getDepth() >= TemplateArgs.getNumLevels()) |
| return true; |
| |
| // There might not be a corresponding template argument before substituting |
| // into the parameter mapping, e.g. a sizeof... expression. |
| if (!TemplateArgs.hasTemplateArgument(T->getDepth(), T->getIndex())) |
| return true; |
| |
| TemplateArgument Arg = TemplateArgs(T->getDepth(), T->getIndex()); |
| |
| if (T->isParameterPack() && SemaRef.ArgPackSubstIndex) { |
| assert(Arg.getKind() == TemplateArgument::Pack && |
| "Missing argument pack"); |
| |
| Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg); |
| } |
| |
| UsedTemplateArgs.push_back( |
| SemaRef.Context.getCanonicalTemplateArgument(Arg)); |
| return true; |
| } |
| |
| bool VisitDeclRefExpr(DeclRefExpr *E) { |
| NamedDecl *D = E->getDecl(); |
| NonTypeTemplateParmDecl *NTTP = dyn_cast<NonTypeTemplateParmDecl>(D); |
| if (!NTTP) |
| return TraverseDecl(D); |
| |
| if (NTTP->getDepth() >= TemplateArgs.getNumLevels()) |
| return true; |
| |
| if (!TemplateArgs.hasTemplateArgument(NTTP->getDepth(), NTTP->getIndex())) |
| return true; |
| |
| TemplateArgument Arg = TemplateArgs(NTTP->getDepth(), NTTP->getPosition()); |
| if (NTTP->isParameterPack() && SemaRef.ArgPackSubstIndex) { |
| assert(Arg.getKind() == TemplateArgument::Pack && |
| "Missing argument pack"); |
| Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg); |
| } |
| |
| UsedTemplateArgs.push_back( |
| SemaRef.Context.getCanonicalTemplateArgument(Arg)); |
| return true; |
| } |
| |
| bool VisitTypedefType(TypedefType *TT) { |
| return inherited::TraverseType(TT->desugar()); |
| } |
| |
| bool TraverseDecl(Decl *D) { |
| if (auto *VD = dyn_cast<ValueDecl>(D)) { |
| if (auto *Var = dyn_cast<VarDecl>(VD)) |
| TraverseStmt(Var->getInit()); |
| return TraverseType(VD->getType()); |
| } |
| |
| return inherited::TraverseDecl(D); |
| } |
| |
| bool TraverseCallExpr(CallExpr *CE) { |
| inherited::TraverseStmt(CE->getCallee()); |
| |
| for (Expr *Arg : CE->arguments()) |
| inherited::TraverseStmt(Arg); |
| |
| return true; |
| } |
| |
| bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier = true) { |
| // We don't care about TypeLocs. So traverse Types instead. |
| return TraverseType(TL.getType().getCanonicalType(), TraverseQualifier); |
| } |
| |
| bool TraverseTagType(const TagType *T, bool TraverseQualifier) { |
| // T's parent can be dependent while T doesn't have any template arguments. |
| // We should have already traversed its qualifier. |
| // FIXME: Add an assert to catch cases where we failed to profile the |
| // concept. |
| return true; |
| } |
| |
| bool TraverseInjectedClassNameType(InjectedClassNameType *T, |
| bool TraverseQualifier) { |
| return TraverseTemplateArguments(T->getTemplateArgs(SemaRef.Context)); |
| } |
| |
| bool TraverseTemplateArgument(const TemplateArgument &Arg) { |
| if (!Arg.containsUnexpandedParameterPack() || Arg.isPackExpansion()) { |
| // Act as if we are fully expanding this pack, if it is a PackExpansion. |
| Sema::ArgPackSubstIndexRAII _1(SemaRef, std::nullopt); |
| llvm::SaveAndRestore<UnsignedOrNone> _2(OuterPackSubstIndex, |
| std::nullopt); |
| return inherited::TraverseTemplateArgument(Arg); |
| } |
| |
| Sema::ArgPackSubstIndexRAII _1(SemaRef, OuterPackSubstIndex); |
| return inherited::TraverseTemplateArgument(Arg); |
| } |
| |
| bool TraverseSizeOfPackExpr(SizeOfPackExpr *SOPE) { |
| return TraverseDecl(SOPE->getPack()); |
| } |
| |
| bool VisitSubstNonTypeTemplateParmExpr(SubstNonTypeTemplateParmExpr *E) { |
| return inherited::TraverseStmt(E->getReplacement()); |
| } |
| |
| void VisitConstraint(const NormalizedConstraintWithParamMapping &Constraint) { |
| if (!Constraint.hasParameterMapping()) { |
| for (const auto &List : TemplateArgs) |
| for (const TemplateArgument &Arg : List.Args) |
| SemaRef.Context.getCanonicalTemplateArgument(Arg).Profile( |
| ID, SemaRef.Context); |
| return; |
| } |
| |
| llvm::ArrayRef<TemplateArgumentLoc> Mapping = |
| Constraint.getParameterMapping(); |
| for (auto &ArgLoc : Mapping) { |
| TemplateArgument Canonical = |
| SemaRef.Context.getCanonicalTemplateArgument(ArgLoc.getArgument()); |
| // We don't want sugars to impede the profile of cache. |
| UsedTemplateArgs.push_back(Canonical); |
| TraverseTemplateArgument(Canonical); |
| } |
| |
| for (auto &Used : UsedTemplateArgs) { |
| llvm::FoldingSetNodeID R; |
| Used.Profile(R, SemaRef.Context); |
| ID.AddNodeID(R); |
| } |
| } |
| }; |
| |
| class ConstraintSatisfactionChecker { |
| Sema &S; |
| const NamedDecl *Template; |
| SourceLocation TemplateNameLoc; |
| UnsignedOrNone PackSubstitutionIndex; |
| |
| ConstraintSatisfaction &Satisfaction; |
| |
| private: |
| ExprResult |
| EvaluateAtomicConstraint(const Expr *AtomicExpr, |
| const MultiLevelTemplateArgumentList &MLTAL); |
| |
| UnsignedOrNone EvaluateFoldExpandedConstraintSize( |
| const FoldExpandedConstraint &FE, |
| const MultiLevelTemplateArgumentList &MLTAL); |
| |
| // XXX: It is SLOW! Use it very carefully. |
| std::optional<MultiLevelTemplateArgumentList> SubstitutionInTemplateArguments( |
| const NormalizedConstraintWithParamMapping &Constraint, |
| MultiLevelTemplateArgumentList MLTAL, |
| llvm::SmallVector<TemplateArgument> &SubstitutedOuterMost); |
| |
| ExprResult EvaluateSlow(const AtomicConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL); |
| |
| ExprResult Evaluate(const AtomicConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL); |
| |
| ExprResult EvaluateSlow(const FoldExpandedConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL); |
| |
| ExprResult Evaluate(const FoldExpandedConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL); |
| |
| ExprResult EvaluateSlow(const ConceptIdConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL, |
| unsigned int Size); |
| |
| ExprResult Evaluate(const ConceptIdConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL); |
| |
| ExprResult Evaluate(const CompoundConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL); |
| |
| public: |
| ConstraintSatisfactionChecker(Sema &SemaRef, const NamedDecl *Template, |
| SourceLocation TemplateNameLoc, |
| UnsignedOrNone PackSubstitutionIndex, |
| ConstraintSatisfaction &Satisfaction) |
| : S(SemaRef), Template(Template), TemplateNameLoc(TemplateNameLoc), |
| PackSubstitutionIndex(PackSubstitutionIndex), |
| Satisfaction(Satisfaction) {} |
| |
| ExprResult Evaluate(const NormalizedConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL); |
| }; |
| |
| StringRef allocateStringFromConceptDiagnostic(const Sema &S, |
| const PartialDiagnostic Diag) { |
| SmallString<128> DiagString; |
| DiagString = ": "; |
| Diag.EmitToString(S.getDiagnostics(), DiagString); |
| return S.getASTContext().backupStr(DiagString); |
| } |
| |
| } // namespace |
| |
| ExprResult ConstraintSatisfactionChecker::EvaluateAtomicConstraint( |
| const Expr *AtomicExpr, const MultiLevelTemplateArgumentList &MLTAL) { |
| EnterExpressionEvaluationContext ConstantEvaluated( |
| S, Sema::ExpressionEvaluationContext::ConstantEvaluated, |
| Sema::ReuseLambdaContextDecl); |
| |
| llvm::FoldingSetNodeID ID; |
| if (Template && |
| DiagRecursiveConstraintEval(S, ID, Template, AtomicExpr, &MLTAL)) { |
| Satisfaction.IsSatisfied = false; |
| Satisfaction.ContainsErrors = true; |
| return ExprEmpty(); |
| } |
| SatisfactionStackRAII StackRAII(S, Template, ID); |
| |
| // Atomic constraint - substitute arguments and check satisfaction. |
| ExprResult SubstitutedExpression = const_cast<Expr *>(AtomicExpr); |
| { |
| TemplateDeductionInfo Info(TemplateNameLoc); |
| Sema::InstantiatingTemplate Inst( |
| S, AtomicExpr->getBeginLoc(), |
| Sema::InstantiatingTemplate::ConstraintSubstitution{}, |
| // FIXME: improve const-correctness of InstantiatingTemplate |
| const_cast<NamedDecl *>(Template), Info, AtomicExpr->getSourceRange()); |
| if (Inst.isInvalid()) |
| return ExprError(); |
| |
| // 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: 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. |
| Satisfaction.Details.emplace_back( |
| new (S.Context) ConstraintSubstitutionDiagnostic{ |
| SubstDiag.first, |
| allocateStringFromConceptDiagnostic(S, SubstDiag.second)}); |
| Satisfaction.IsSatisfied = false; |
| return ExprEmpty(); |
| } |
| } |
| |
| 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; |
| } |
| |
| std::optional<MultiLevelTemplateArgumentList> |
| ConstraintSatisfactionChecker::SubstitutionInTemplateArguments( |
| const NormalizedConstraintWithParamMapping &Constraint, |
| MultiLevelTemplateArgumentList MLTAL, |
| llvm::SmallVector<TemplateArgument> &SubstitutedOuterMost) { |
| |
| if (!Constraint.hasParameterMapping()) |
| return std::move(MLTAL); |
| |
| TemplateDeductionInfo Info(Constraint.getBeginLoc()); |
| Sema::InstantiatingTemplate Inst( |
| S, Constraint.getBeginLoc(), |
| Sema::InstantiatingTemplate::ConstraintSubstitution{}, |
| // FIXME: improve const-correctness of InstantiatingTemplate |
| const_cast<NamedDecl *>(Template), Info, Constraint.getSourceRange()); |
| if (Inst.isInvalid()) |
| return std::nullopt; |
| |
| Sema::SFINAETrap Trap(S); |
| |
| TemplateArgumentListInfo SubstArgs; |
| Sema::ArgPackSubstIndexRAII SubstIndex( |
| S, Constraint.getPackSubstitutionIndex() |
| ? Constraint.getPackSubstitutionIndex() |
| : PackSubstitutionIndex); |
| |
| if (S.SubstTemplateArgumentsInParameterMapping( |
| Constraint.getParameterMapping(), Constraint.getBeginLoc(), MLTAL, |
| SubstArgs, /*BuildPackExpansionTypes=*/true)) { |
| Satisfaction.IsSatisfied = false; |
| return std::nullopt; |
| } |
| |
| Sema::CheckTemplateArgumentInfo CTAI; |
| auto *TD = const_cast<TemplateDecl *>( |
| cast<TemplateDecl>(Constraint.getConstraintDecl())); |
| if (S.CheckTemplateArgumentList(TD, Constraint.getUsedTemplateParamList(), |
| TD->getLocation(), SubstArgs, |
| /*DefaultArguments=*/{}, |
| /*PartialTemplateArgs=*/false, CTAI)) |
| return std::nullopt; |
| const NormalizedConstraint::OccurenceList &Used = |
| Constraint.mappingOccurenceList(); |
| // The empty MLTAL situation should only occur when evaluating non-dependent |
| // constraints. |
| if (MLTAL.getNumSubstitutedLevels()) |
| SubstitutedOuterMost = |
| llvm::to_vector_of<TemplateArgument>(MLTAL.getOutermost()); |
| unsigned Offset = 0; |
| for (unsigned I = 0, MappedIndex = 0; I < Used.size(); I++) { |
| TemplateArgument Arg; |
| if (Used[I]) |
| Arg = S.Context.getCanonicalTemplateArgument( |
| CTAI.SugaredConverted[MappedIndex++]); |
| if (I < SubstitutedOuterMost.size()) { |
| SubstitutedOuterMost[I] = Arg; |
| Offset = I + 1; |
| } else { |
| SubstitutedOuterMost.push_back(Arg); |
| Offset = SubstitutedOuterMost.size(); |
| } |
| } |
| if (Offset < SubstitutedOuterMost.size()) |
| SubstitutedOuterMost.erase(SubstitutedOuterMost.begin() + Offset); |
| |
| MultiLevelTemplateArgumentList SubstitutedTemplateArgs; |
| SubstitutedTemplateArgs.addOuterTemplateArguments(TD, SubstitutedOuterMost, |
| /*Final=*/false); |
| return std::move(SubstitutedTemplateArgs); |
| } |
| |
| ExprResult ConstraintSatisfactionChecker::EvaluateSlow( |
| const AtomicConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL) { |
| |
| llvm::SmallVector<TemplateArgument> SubstitutedOuterMost; |
| std::optional<MultiLevelTemplateArgumentList> SubstitutedArgs = |
| SubstitutionInTemplateArguments(Constraint, MLTAL, SubstitutedOuterMost); |
| if (!SubstitutedArgs) { |
| Satisfaction.IsSatisfied = false; |
| return ExprEmpty(); |
| } |
| |
| Sema::ArgPackSubstIndexRAII SubstIndex(S, PackSubstitutionIndex); |
| ExprResult SubstitutedAtomicExpr = EvaluateAtomicConstraint( |
| Constraint.getConstraintExpr(), *SubstitutedArgs); |
| |
| if (SubstitutedAtomicExpr.isInvalid()) |
| return ExprError(); |
| |
| if (SubstitutedAtomicExpr.isUnset()) |
| // Evaluator has decided satisfaction without yielding an expression. |
| return ExprEmpty(); |
| |
| // We don't have the ability to evaluate this, since it contains a |
| // RecoveryExpr, so we want to fail overload resolution. Otherwise, |
| // we'd potentially pick up a different overload, and cause confusing |
| // diagnostics. SO, add a failure detail that will cause us to make this |
| // overload set not viable. |
| if (SubstitutedAtomicExpr.get()->containsErrors()) { |
| Satisfaction.IsSatisfied = false; |
| Satisfaction.ContainsErrors = true; |
| |
| PartialDiagnostic Msg = S.PDiag(diag::note_constraint_references_error); |
| Satisfaction.Details.emplace_back( |
| new (S.Context) ConstraintSubstitutionDiagnostic{ |
| SubstitutedAtomicExpr.get()->getBeginLoc(), |
| allocateStringFromConceptDiagnostic(S, Msg)}); |
| return SubstitutedAtomicExpr; |
| } |
| |
| if (SubstitutedAtomicExpr.get()->isValueDependent()) { |
| Satisfaction.IsSatisfied = true; |
| Satisfaction.ContainsErrors = false; |
| return SubstitutedAtomicExpr; |
| } |
| |
| EnterExpressionEvaluationContext ConstantEvaluated( |
| S, Sema::ExpressionEvaluationContext::ConstantEvaluated); |
| SmallVector<PartialDiagnosticAt, 2> EvaluationDiags; |
| Expr::EvalResult EvalResult; |
| EvalResult.Diag = &EvaluationDiags; |
| if (!SubstitutedAtomicExpr.get()->EvaluateAsConstantExpr(EvalResult, |
| S.Context) || |
| !EvaluationDiags.empty()) { |
| // C++2a [temp.constr.atomic]p1 |
| // ...E shall be a constant expression of type bool. |
| S.Diag(SubstitutedAtomicExpr.get()->getBeginLoc(), |
| diag::err_non_constant_constraint_expression) |
| << SubstitutedAtomicExpr.get()->getSourceRange(); |
| for (const PartialDiagnosticAt &PDiag : EvaluationDiags) |
| S.Diag(PDiag.first, PDiag.second); |
| return ExprError(); |
| } |
| |
| assert(EvalResult.Val.isInt() && |
| "evaluating bool expression didn't produce int"); |
| Satisfaction.IsSatisfied = EvalResult.Val.getInt().getBoolValue(); |
| if (!Satisfaction.IsSatisfied) |
| Satisfaction.Details.emplace_back(SubstitutedAtomicExpr.get()); |
| |
| return SubstitutedAtomicExpr; |
| } |
| |
| ExprResult ConstraintSatisfactionChecker::Evaluate( |
| const AtomicConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL) { |
| |
| unsigned Size = Satisfaction.Details.size(); |
| llvm::FoldingSetNodeID ID; |
| UnsignedOrNone OuterPackSubstIndex = |
| Constraint.getPackSubstitutionIndex() |
| ? Constraint.getPackSubstitutionIndex() |
| : PackSubstitutionIndex; |
| |
| ID.AddPointer(Constraint.getConstraintExpr()); |
| ID.AddInteger(OuterPackSubstIndex.toInternalRepresentation()); |
| HashParameterMapping(S, MLTAL, ID, OuterPackSubstIndex) |
| .VisitConstraint(Constraint); |
| |
| if (auto Iter = S.UnsubstitutedConstraintSatisfactionCache.find(ID); |
| Iter != S.UnsubstitutedConstraintSatisfactionCache.end()) { |
| auto &Cached = Iter->second.Satisfaction; |
| Satisfaction.ContainsErrors = Cached.ContainsErrors; |
| Satisfaction.IsSatisfied = Cached.IsSatisfied; |
| Satisfaction.Details.insert(Satisfaction.Details.begin() + Size, |
| Cached.Details.begin(), Cached.Details.end()); |
| return Iter->second.SubstExpr; |
| } |
| |
| ExprResult E = EvaluateSlow(Constraint, MLTAL); |
| |
| UnsubstitutedConstraintSatisfactionCacheResult Cache; |
| Cache.Satisfaction.ContainsErrors = Satisfaction.ContainsErrors; |
| Cache.Satisfaction.IsSatisfied = Satisfaction.IsSatisfied; |
| std::copy(Satisfaction.Details.begin() + Size, Satisfaction.Details.end(), |
| std::back_inserter(Cache.Satisfaction.Details)); |
| Cache.SubstExpr = E; |
| S.UnsubstitutedConstraintSatisfactionCache.insert({ID, std::move(Cache)}); |
| |
| return E; |
| } |
| |
| UnsignedOrNone |
| ConstraintSatisfactionChecker::EvaluateFoldExpandedConstraintSize( |
| const FoldExpandedConstraint &FE, |
| const MultiLevelTemplateArgumentList &MLTAL) { |
| |
| // We should ignore errors in the presence of packs of different size. |
| Sema::SFINAETrap Trap(S); |
| |
| Expr *Pattern = const_cast<Expr *>(FE.getPattern()); |
| |
| SmallVector<UnexpandedParameterPack, 2> Unexpanded; |
| S.collectUnexpandedParameterPacks(Pattern, Unexpanded); |
| assert(!Unexpanded.empty() && "Pack expansion without parameter packs?"); |
| bool Expand = true; |
| bool RetainExpansion = false; |
| UnsignedOrNone NumExpansions(std::nullopt); |
| if (S.CheckParameterPacksForExpansion( |
| Pattern->getExprLoc(), Pattern->getSourceRange(), Unexpanded, MLTAL, |
| /*FailOnPackProducingTemplates=*/false, Expand, RetainExpansion, |
| NumExpansions) || |
| !Expand || RetainExpansion) |
| return std::nullopt; |
| |
| if (NumExpansions && S.getLangOpts().BracketDepth < *NumExpansions) { |
| S.Diag(Pattern->getExprLoc(), |
| clang::diag::err_fold_expression_limit_exceeded) |
| << *NumExpansions << S.getLangOpts().BracketDepth |
| << Pattern->getSourceRange(); |
| S.Diag(Pattern->getExprLoc(), diag::note_bracket_depth); |
| return std::nullopt; |
| } |
| return NumExpansions; |
| } |
| |
| ExprResult ConstraintSatisfactionChecker::EvaluateSlow( |
| const FoldExpandedConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL) { |
| |
| bool Conjunction = Constraint.getFoldOperator() == |
| FoldExpandedConstraint::FoldOperatorKind::And; |
| unsigned EffectiveDetailEndIndex = Satisfaction.Details.size(); |
| |
| llvm::SmallVector<TemplateArgument> SubstitutedOuterMost; |
| // FIXME: Is PackSubstitutionIndex correct? |
| llvm::SaveAndRestore _(PackSubstitutionIndex, S.ArgPackSubstIndex); |
| std::optional<MultiLevelTemplateArgumentList> SubstitutedArgs = |
| SubstitutionInTemplateArguments( |
| static_cast<const NormalizedConstraintWithParamMapping &>(Constraint), |
| MLTAL, SubstitutedOuterMost); |
| if (!SubstitutedArgs) { |
| Satisfaction.IsSatisfied = false; |
| return ExprError(); |
| } |
| |
| ExprResult Out; |
| UnsignedOrNone NumExpansions = |
| EvaluateFoldExpandedConstraintSize(Constraint, *SubstitutedArgs); |
| if (!NumExpansions) |
| return ExprEmpty(); |
| |
| if (*NumExpansions == 0) { |
| Satisfaction.IsSatisfied = Conjunction; |
| return ExprEmpty(); |
| } |
| |
| for (unsigned I = 0; I < *NumExpansions; I++) { |
| Sema::ArgPackSubstIndexRAII SubstIndex(S, I); |
| Satisfaction.IsSatisfied = false; |
| Satisfaction.ContainsErrors = false; |
| ExprResult Expr = |
| ConstraintSatisfactionChecker(S, Template, TemplateNameLoc, |
| UnsignedOrNone(I), Satisfaction) |
| .Evaluate(Constraint.getNormalizedPattern(), *SubstitutedArgs); |
| if (Expr.isUsable()) { |
| if (Out.isUnset()) |
| Out = Expr; |
| else |
| Out = BinaryOperator::Create(S.Context, Out.get(), Expr.get(), |
| Conjunction ? BinaryOperatorKind::BO_LAnd |
| : BinaryOperatorKind::BO_LOr, |
| S.Context.BoolTy, VK_PRValue, OK_Ordinary, |
| Constraint.getBeginLoc(), |
| FPOptionsOverride{}); |
| } else { |
| assert(!Satisfaction.IsSatisfied); |
| } |
| if (!Conjunction && Satisfaction.IsSatisfied) { |
| Satisfaction.Details.erase(Satisfaction.Details.begin() + |
| EffectiveDetailEndIndex, |
| Satisfaction.Details.end()); |
| break; |
| } |
| if (Satisfaction.IsSatisfied != Conjunction) |
| return Out; |
| } |
| |
| return Out; |
| } |
| |
| ExprResult ConstraintSatisfactionChecker::Evaluate( |
| const FoldExpandedConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL) { |
| |
| llvm::FoldingSetNodeID ID; |
| ID.AddPointer(Constraint.getPattern()); |
| HashParameterMapping(S, MLTAL, ID, std::nullopt).VisitConstraint(Constraint); |
| |
| if (auto Iter = S.UnsubstitutedConstraintSatisfactionCache.find(ID); |
| Iter != S.UnsubstitutedConstraintSatisfactionCache.end()) { |
| |
| auto &Cached = Iter->second.Satisfaction; |
| Satisfaction.ContainsErrors = Cached.ContainsErrors; |
| Satisfaction.IsSatisfied = Cached.IsSatisfied; |
| Satisfaction.Details.insert(Satisfaction.Details.end(), |
| Cached.Details.begin(), Cached.Details.end()); |
| return Iter->second.SubstExpr; |
| } |
| |
| unsigned Size = Satisfaction.Details.size(); |
| |
| ExprResult E = EvaluateSlow(Constraint, MLTAL); |
| UnsubstitutedConstraintSatisfactionCacheResult Cache; |
| Cache.Satisfaction.ContainsErrors = Satisfaction.ContainsErrors; |
| Cache.Satisfaction.IsSatisfied = Satisfaction.IsSatisfied; |
| std::copy(Satisfaction.Details.begin() + Size, Satisfaction.Details.end(), |
| std::back_inserter(Cache.Satisfaction.Details)); |
| Cache.SubstExpr = E; |
| S.UnsubstitutedConstraintSatisfactionCache.insert({ID, std::move(Cache)}); |
| return E; |
| } |
| |
| ExprResult ConstraintSatisfactionChecker::EvaluateSlow( |
| const ConceptIdConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL, unsigned Size) { |
| const ConceptReference *ConceptId = Constraint.getConceptId(); |
| |
| llvm::SmallVector<TemplateArgument> SubstitutedOuterMost; |
| std::optional<MultiLevelTemplateArgumentList> SubstitutedArgs = |
| SubstitutionInTemplateArguments(Constraint, MLTAL, SubstitutedOuterMost); |
| |
| if (!SubstitutedArgs) { |
| Satisfaction.IsSatisfied = false; |
| // FIXME: diagnostics? |
| return ExprError(); |
| } |
| |
| Sema::SFINAETrap Trap(S); |
| Sema::ArgPackSubstIndexRAII SubstIndex( |
| S, Constraint.getPackSubstitutionIndex() |
| ? Constraint.getPackSubstitutionIndex() |
| : PackSubstitutionIndex); |
| |
| const ASTTemplateArgumentListInfo *Ori = |
| ConceptId->getTemplateArgsAsWritten(); |
| TemplateDeductionInfo Info(TemplateNameLoc); |
| Sema::InstantiatingTemplate _( |
| S, TemplateNameLoc, Sema::InstantiatingTemplate::ConstraintSubstitution{}, |
| const_cast<NamedDecl *>(Template), Info, Constraint.getSourceRange()); |
| |
| TemplateArgumentListInfo OutArgs(Ori->LAngleLoc, Ori->RAngleLoc); |
| if (S.SubstTemplateArguments(Ori->arguments(), *SubstitutedArgs, OutArgs) || |
| Trap.hasErrorOccurred()) { |
| Satisfaction.IsSatisfied = false; |
| if (!Trap.hasErrorOccurred()) |
| return ExprError(); |
| |
| PartialDiagnosticAt SubstDiag{SourceLocation(), |
| PartialDiagnostic::NullDiagnostic()}; |
| Info.takeSFINAEDiagnostic(SubstDiag); |
| // FIXME: 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. |
| Satisfaction.Details.insert( |
| Satisfaction.Details.begin() + Size, |
| new (S.Context) ConstraintSubstitutionDiagnostic{ |
| SubstDiag.first, |
| allocateStringFromConceptDiagnostic(S, SubstDiag.second)}); |
| return ExprError(); |
| } |
| |
| CXXScopeSpec SS; |
| SS.Adopt(ConceptId->getNestedNameSpecifierLoc()); |
| |
| ExprResult SubstitutedConceptId = S.CheckConceptTemplateId( |
| SS, ConceptId->getTemplateKWLoc(), ConceptId->getConceptNameInfo(), |
| ConceptId->getFoundDecl(), ConceptId->getNamedConcept(), &OutArgs, |
| /*DoCheckConstraintSatisfaction=*/false); |
| |
| if (SubstitutedConceptId.isInvalid() || Trap.hasErrorOccurred()) |
| return ExprError(); |
| |
| if (Size != Satisfaction.Details.size()) { |
| Satisfaction.Details.insert( |
| Satisfaction.Details.begin() + Size, |
| UnsatisfiedConstraintRecord( |
| SubstitutedConceptId.getAs<ConceptSpecializationExpr>() |
| ->getConceptReference())); |
| } |
| return SubstitutedConceptId; |
| } |
| |
| ExprResult ConstraintSatisfactionChecker::Evaluate( |
| const ConceptIdConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL) { |
| |
| const ConceptReference *ConceptId = Constraint.getConceptId(); |
| |
| UnsignedOrNone OuterPackSubstIndex = |
| Constraint.getPackSubstitutionIndex() |
| ? Constraint.getPackSubstitutionIndex() |
| : PackSubstitutionIndex; |
| |
| Sema::InstantiatingTemplate InstTemplate( |
| S, ConceptId->getBeginLoc(), |
| Sema::InstantiatingTemplate::ConstraintsCheck{}, |
| ConceptId->getNamedConcept(), |
| // We may have empty template arguments when checking non-dependent |
| // nested constraint expressions. |
| // In such cases, non-SFINAE errors would have already been diagnosed |
| // during parameter mapping substitution, so the instantiating template |
| // arguments are less useful here. |
| MLTAL.getNumSubstitutedLevels() ? MLTAL.getInnermost() |
| : ArrayRef<TemplateArgument>{}, |
| Constraint.getSourceRange()); |
| if (InstTemplate.isInvalid()) |
| return ExprError(); |
| |
| unsigned Size = Satisfaction.Details.size(); |
| |
| ExprResult E = Evaluate(Constraint.getNormalizedConstraint(), MLTAL); |
| |
| if (!E.isUsable()) { |
| Satisfaction.Details.insert(Satisfaction.Details.begin() + Size, ConceptId); |
| return E; |
| } |
| |
| // ConceptIdConstraint is only relevant for diagnostics, |
| // so if the normalized constraint is satisfied, we should not |
| // substitute into the constraint. |
| if (Satisfaction.IsSatisfied) |
| return E; |
| |
| llvm::FoldingSetNodeID ID; |
| ID.AddPointer(Constraint.getConceptId()); |
| ID.AddInteger(OuterPackSubstIndex.toInternalRepresentation()); |
| HashParameterMapping(S, MLTAL, ID, OuterPackSubstIndex) |
| .VisitConstraint(Constraint); |
| |
| if (auto Iter = S.UnsubstitutedConstraintSatisfactionCache.find(ID); |
| Iter != S.UnsubstitutedConstraintSatisfactionCache.end()) { |
| |
| auto &Cached = Iter->second.Satisfaction; |
| Satisfaction.ContainsErrors = Cached.ContainsErrors; |
| Satisfaction.IsSatisfied = Cached.IsSatisfied; |
| Satisfaction.Details.insert(Satisfaction.Details.begin() + Size, |
| Cached.Details.begin(), Cached.Details.end()); |
| return Iter->second.SubstExpr; |
| } |
| |
| ExprResult CE = EvaluateSlow(Constraint, MLTAL, Size); |
| if (CE.isInvalid()) |
| return E; |
| UnsubstitutedConstraintSatisfactionCacheResult Cache; |
| Cache.Satisfaction.ContainsErrors = Satisfaction.ContainsErrors; |
| Cache.Satisfaction.IsSatisfied = Satisfaction.IsSatisfied; |
| std::copy(Satisfaction.Details.begin() + Size, Satisfaction.Details.end(), |
| std::back_inserter(Cache.Satisfaction.Details)); |
| Cache.SubstExpr = CE; |
| S.UnsubstitutedConstraintSatisfactionCache.insert({ID, std::move(Cache)}); |
| return CE; |
| } |
| |
| ExprResult ConstraintSatisfactionChecker::Evaluate( |
| const CompoundConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL) { |
| |
| unsigned EffectiveDetailEndIndex = Satisfaction.Details.size(); |
| |
| bool Conjunction = |
| Constraint.getCompoundKind() == NormalizedConstraint::CCK_Conjunction; |
| |
| ExprResult LHS = Evaluate(Constraint.getLHS(), MLTAL); |
| |
| if (Conjunction && (!Satisfaction.IsSatisfied || Satisfaction.ContainsErrors)) |
| return LHS; |
| |
| if (!Conjunction && LHS.isUsable() && Satisfaction.IsSatisfied && |
| !Satisfaction.ContainsErrors) |
| return LHS; |
| |
| Satisfaction.ContainsErrors = false; |
| Satisfaction.IsSatisfied = false; |
| |
| ExprResult RHS = Evaluate(Constraint.getRHS(), MLTAL); |
| |
| if (RHS.isUsable() && Satisfaction.IsSatisfied && |
| !Satisfaction.ContainsErrors) |
| Satisfaction.Details.erase(Satisfaction.Details.begin() + |
| EffectiveDetailEndIndex, |
| Satisfaction.Details.end()); |
| |
| if (!LHS.isUsable()) |
| return RHS; |
| |
| if (!RHS.isUsable()) |
| return LHS; |
| |
| return BinaryOperator::Create(S.Context, LHS.get(), RHS.get(), |
| Conjunction ? BinaryOperatorKind::BO_LAnd |
| : BinaryOperatorKind::BO_LOr, |
| S.Context.BoolTy, VK_PRValue, OK_Ordinary, |
| Constraint.getBeginLoc(), FPOptionsOverride{}); |
| } |
| |
| ExprResult ConstraintSatisfactionChecker::Evaluate( |
| const NormalizedConstraint &Constraint, |
| const MultiLevelTemplateArgumentList &MLTAL) { |
| switch (Constraint.getKind()) { |
| case NormalizedConstraint::ConstraintKind::Atomic: |
| return Evaluate(static_cast<const AtomicConstraint &>(Constraint), MLTAL); |
| |
| case NormalizedConstraint::ConstraintKind::FoldExpanded: |
| return Evaluate(static_cast<const FoldExpandedConstraint &>(Constraint), |
| MLTAL); |
| |
| case NormalizedConstraint::ConstraintKind::ConceptId: |
| return Evaluate(static_cast<const ConceptIdConstraint &>(Constraint), |
| MLTAL); |
| |
| case NormalizedConstraint::ConstraintKind::Compound: |
| return Evaluate(static_cast<const CompoundConstraint &>(Constraint), MLTAL); |
| } |
| llvm_unreachable("Unknown ConstraintKind enum"); |
| } |
| |
| static bool CheckConstraintSatisfaction( |
| Sema &S, const NamedDecl *Template, |
| ArrayRef<AssociatedConstraint> AssociatedConstraints, |
| const MultiLevelTemplateArgumentList &TemplateArgsLists, |
| SourceRange TemplateIDRange, ConstraintSatisfaction &Satisfaction, |
| Expr **ConvertedExpr, const ConceptReference *TopLevelConceptId = nullptr) { |
| |
| if (ConvertedExpr) |
| *ConvertedExpr = nullptr; |
| |
| if (AssociatedConstraints.empty()) { |
| Satisfaction.IsSatisfied = true; |
| return false; |
| } |
| |
| if (TemplateArgsLists.isAnyArgInstantiationDependent()) { |
| // No need to check satisfaction for dependent constraint expressions. |
| Satisfaction.IsSatisfied = true; |
| return false; |
| } |
| |
| llvm::ArrayRef<TemplateArgument> Args; |
| if (TemplateArgsLists.getNumLevels() != 0) |
| Args = TemplateArgsLists.getInnermost(); |
| |
| std::optional<Sema::InstantiatingTemplate> SynthesisContext; |
| if (!TopLevelConceptId) { |
| SynthesisContext.emplace(S, TemplateIDRange.getBegin(), |
| Sema::InstantiatingTemplate::ConstraintsCheck{}, |
| const_cast<NamedDecl *>(Template), Args, |
| TemplateIDRange); |
| } |
| |
| const NormalizedConstraint *C = |
| S.getNormalizedAssociatedConstraints(Template, AssociatedConstraints); |
| if (!C) { |
| Satisfaction.IsSatisfied = false; |
| return true; |
| } |
| |
| if (TopLevelConceptId) |
| C = ConceptIdConstraint::Create(S.getASTContext(), TopLevelConceptId, |
| const_cast<NormalizedConstraint *>(C), |
| Template, /*CSE=*/nullptr, |
| S.ArgPackSubstIndex); |
| |
| ExprResult Res = |
| ConstraintSatisfactionChecker(S, Template, TemplateIDRange.getBegin(), |
| S.ArgPackSubstIndex, Satisfaction) |
| .Evaluate(*C, TemplateArgsLists); |
| |
| if (Res.isInvalid()) |
| return true; |
| |
| if (Res.isUsable() && ConvertedExpr) |
| *ConvertedExpr = Res.get(); |
| |
| return false; |
| } |
| |
| bool Sema::CheckConstraintSatisfaction( |
| ConstrainedDeclOrNestedRequirement Entity, |
| ArrayRef<AssociatedConstraint> AssociatedConstraints, |
| const MultiLevelTemplateArgumentList &TemplateArgsLists, |
| SourceRange TemplateIDRange, ConstraintSatisfaction &OutSatisfaction, |
| const ConceptReference *TopLevelConceptId, Expr **ConvertedExpr) { |
| if (AssociatedConstraints.empty()) { |
| OutSatisfaction.IsSatisfied = true; |
| return false; |
| } |
| const auto *Template = Entity.dyn_cast<const NamedDecl *>(); |
| if (!Template) { |
| return ::CheckConstraintSatisfaction( |
| *this, nullptr, AssociatedConstraints, TemplateArgsLists, |
| TemplateIDRange, OutSatisfaction, ConvertedExpr, TopLevelConceptId); |
| } |
| // Invalid templates could make their way here. Substituting them could result |
| // in dependent expressions. |
| if (Template->isInvalidDecl()) { |
| OutSatisfaction.IsSatisfied = false; |
| return true; |
| } |
| |
| // A list of the template argument list flattened in a predictible manner for |
| // the purposes of caching. The ConstraintSatisfaction type is in AST so it |
| // has no access to the MultiLevelTemplateArgumentList, so this has to happen |
| // here. |
| llvm::SmallVector<TemplateArgument, 4> FlattenedArgs; |
| for (auto List : TemplateArgsLists) |
| for (const TemplateArgument &Arg : List.Args) |
| FlattenedArgs.emplace_back(Context.getCanonicalTemplateArgument(Arg)); |
| |
| const NamedDecl *Owner = Template; |
| if (TopLevelConceptId) |
| Owner = TopLevelConceptId->getNamedConcept(); |
| |
| llvm::FoldingSetNodeID ID; |
| ConstraintSatisfaction::Profile(ID, Context, Owner, FlattenedArgs); |
| void *InsertPos; |
| if (auto *Cached = SatisfactionCache.FindNodeOrInsertPos(ID, InsertPos)) { |
| OutSatisfaction = *Cached; |
| return false; |
| } |
| |
| auto Satisfaction = |
| std::make_unique<ConstraintSatisfaction>(Owner, FlattenedArgs); |
| if (::CheckConstraintSatisfaction( |
| *this, Template, AssociatedConstraints, TemplateArgsLists, |
| TemplateIDRange, *Satisfaction, ConvertedExpr, TopLevelConceptId)) { |
| OutSatisfaction = std::move(*Satisfaction); |
| return true; |
| } |
| |
| if (auto *Cached = SatisfactionCache.FindNodeOrInsertPos(ID, InsertPos)) { |
| // The evaluation of this constraint resulted in us trying to re-evaluate it |
| // recursively. This isn't really possible, except we try to form a |
| // RecoveryExpr as a part of the evaluation. If this is the case, just |
| // return the 'cached' version (which will have the same result), and save |
| // ourselves the extra-insert. If it ever becomes possible to legitimately |
| // recursively check a constraint, we should skip checking the 'inner' one |
| // above, and replace the cached version with this one, as it would be more |
| // specific. |
| OutSatisfaction = *Cached; |
| return false; |
| } |
| |
| // Else we can simply add this satisfaction to the list. |
| OutSatisfaction = *Satisfaction; |
| // We cannot use InsertPos here because CheckConstraintSatisfaction might have |
| // invalidated it. |
| // Note that entries of SatisfactionCache are deleted in Sema's destructor. |
| SatisfactionCache.InsertNode(Satisfaction.release()); |
| return false; |
| } |
| |
| static const ExprResult |
| SubstituteConceptsInConstrainExpression(Sema &S, const NamedDecl *D, |
| const ConceptSpecializationExpr *CSE, |
| UnsignedOrNone SubstIndex) { |
| |
| // [C++2c] [temp.constr.normal] |
| // Otherwise, to form CE, any non-dependent concept template argument Ai |
| // is substituted into the constraint-expression of C. |
| // If any such substitution results in an invalid concept-id, |
| // the program is ill-formed; no diagnostic is required. |
| |
| ConceptDecl *Concept = CSE->getNamedConcept()->getCanonicalDecl(); |
| Sema::ArgPackSubstIndexRAII _(S, SubstIndex); |
| |
| const ASTTemplateArgumentListInfo *ArgsAsWritten = |
| CSE->getTemplateArgsAsWritten(); |
| if (llvm::none_of( |
| ArgsAsWritten->arguments(), [&](const TemplateArgumentLoc &ArgLoc) { |
| return !ArgLoc.getArgument().isDependent() && |
| ArgLoc.getArgument().isConceptOrConceptTemplateParameter(); |
| })) { |
| return Concept->getConstraintExpr(); |
| } |
| |
| MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs( |
| Concept, Concept->getLexicalDeclContext(), |
| /*Final=*/false, CSE->getTemplateArguments(), |
| /*RelativeToPrimary=*/true, |
| /*Pattern=*/nullptr, |
| /*ForConstraintInstantiation=*/true); |
| return S.SubstConceptTemplateArguments(CSE, Concept->getConstraintExpr(), |
| MLTAL); |
| } |
| |
| bool Sema::CheckConstraintSatisfaction( |
| const ConceptSpecializationExpr *ConstraintExpr, |
| ConstraintSatisfaction &Satisfaction) { |
| |
| ExprResult Res = SubstituteConceptsInConstrainExpression( |
| *this, nullptr, ConstraintExpr, ArgPackSubstIndex); |
| if (!Res.isUsable()) |
| return true; |
| |
| llvm::SmallVector<AssociatedConstraint, 1> Constraints; |
| Constraints.emplace_back(Res.get()); |
| |
| MultiLevelTemplateArgumentList MLTAL(ConstraintExpr->getNamedConcept(), |
| ConstraintExpr->getTemplateArguments(), |
| true); |
| |
| return CheckConstraintSatisfaction( |
| ConstraintExpr->getNamedConcept(), Constraints, MLTAL, |
| ConstraintExpr->getSourceRange(), Satisfaction, |
| ConstraintExpr->getConceptReference()); |
| } |
| |
| bool Sema::SetupConstraintScope( |
| FunctionDecl *FD, std::optional<ArrayRef<TemplateArgument>> TemplateArgs, |
| const MultiLevelTemplateArgumentList &MLTAL, |
| LocalInstantiationScope &Scope) { |
| assert(!isLambdaCallOperator(FD) && |
| "Use LambdaScopeForCallOperatorInstantiationRAII to handle lambda " |
| "instantiations"); |
| if (FD->isTemplateInstantiation() && FD->getPrimaryTemplate()) { |
| FunctionTemplateDecl *PrimaryTemplate = FD->getPrimaryTemplate(); |
| InstantiatingTemplate Inst( |
| *this, FD->getPointOfInstantiation(), |
| Sema::InstantiatingTemplate::ConstraintsCheck{}, PrimaryTemplate, |
| TemplateArgs ? *TemplateArgs : ArrayRef<TemplateArgument>{}, |
| SourceRange()); |
| if (Inst.isInvalid()) |
| return true; |
| |
| // addInstantiatedParametersToScope creates a map of 'uninstantiated' to |
| // 'instantiated' parameters and adds it to the context. For the case where |
| // this function is a template being instantiated NOW, we also need to add |
| // the list of current template arguments to the list so that they also can |
| // be picked out of the map. |
| if (auto *SpecArgs = FD->getTemplateSpecializationArgs()) { |
| MultiLevelTemplateArgumentList JustTemplArgs(FD, SpecArgs->asArray(), |
| /*Final=*/false); |
| if (addInstantiatedParametersToScope( |
| FD, PrimaryTemplate->getTemplatedDecl(), Scope, JustTemplArgs)) |
| return true; |
| } |
| |
| // If this is a member function, make sure we get the parameters that |
| // reference the original primary template. |
| if (FunctionTemplateDecl *FromMemTempl = |
| PrimaryTemplate->getInstantiatedFromMemberTemplate()) { |
| if (addInstantiatedParametersToScope(FD, FromMemTempl->getTemplatedDecl(), |
| Scope, MLTAL)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| if (FD->getTemplatedKind() == FunctionDecl::TK_MemberSpecialization || |
| FD->getTemplatedKind() == FunctionDecl::TK_DependentNonTemplate) { |
| FunctionDecl *InstantiatedFrom = |
| FD->getTemplatedKind() == FunctionDecl::TK_MemberSpecialization |
| ? FD->getInstantiatedFromMemberFunction() |
| : FD->getInstantiatedFromDecl(); |
| |
| InstantiatingTemplate Inst( |
| *this, FD->getPointOfInstantiation(), |
| Sema::InstantiatingTemplate::ConstraintsCheck{}, InstantiatedFrom, |
| TemplateArgs ? *TemplateArgs : ArrayRef<TemplateArgument>{}, |
| SourceRange()); |
| if (Inst.isInvalid()) |
| return true; |
| |
| // Case where this was not a template, but instantiated as a |
| // child-function. |
| if (addInstantiatedParametersToScope(FD, InstantiatedFrom, Scope, MLTAL)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // This function collects all of the template arguments for the purposes of |
| // constraint-instantiation and checking. |
| std::optional<MultiLevelTemplateArgumentList> |
| Sema::SetupConstraintCheckingTemplateArgumentsAndScope( |
| FunctionDecl *FD, std::optional<ArrayRef<TemplateArgument>> TemplateArgs, |
| LocalInstantiationScope &Scope) { |
| MultiLevelTemplateArgumentList MLTAL; |
| |
| // Collect the list of template arguments relative to the 'primary' template. |
| // We need the entire list, since the constraint is completely uninstantiated |
| // at this point. |
| MLTAL = |
| getTemplateInstantiationArgs(FD, FD->getLexicalDeclContext(), |
| /*Final=*/false, /*Innermost=*/std::nullopt, |
| /*RelativeToPrimary=*/true, |
| /*Pattern=*/nullptr, |
| /*ForConstraintInstantiation=*/true); |
| // Lambdas are handled by LambdaScopeForCallOperatorInstantiationRAII. |
| if (isLambdaCallOperator(FD)) |
| return MLTAL; |
| if (SetupConstraintScope(FD, TemplateArgs, MLTAL, Scope)) |
| return std::nullopt; |
| |
| return MLTAL; |
| } |
| |
| bool Sema::CheckFunctionConstraints(const FunctionDecl *FD, |
| ConstraintSatisfaction &Satisfaction, |
| SourceLocation UsageLoc, |
| bool ForOverloadResolution) { |
| // Don't check constraints if the function is dependent. Also don't check if |
| // this is a function template specialization, as the call to |
| // CheckFunctionTemplateConstraints after this will check it |
| // better. |
| if (FD->isDependentContext() || |
| FD->getTemplatedKind() == |
| FunctionDecl::TK_FunctionTemplateSpecialization) { |
| Satisfaction.IsSatisfied = true; |
| return false; |
| } |
| |
| // A lambda conversion operator has the same constraints as the call operator |
| // and constraints checking relies on whether we are in a lambda call operator |
| // (and may refer to its parameters), so check the call operator instead. |
| // Note that the declarations outside of the lambda should also be |
| // considered. Turning on the 'ForOverloadResolution' flag results in the |
| // LocalInstantiationScope not looking into its parents, but we can still |
| // access Decls from the parents while building a lambda RAII scope later. |
| if (const auto *MD = dyn_cast<CXXConversionDecl>(FD); |
| MD && isLambdaConversionOperator(const_cast<CXXConversionDecl *>(MD))) |
| return CheckFunctionConstraints(MD->getParent()->getLambdaCallOperator(), |
| Satisfaction, UsageLoc, |
| /*ShouldAddDeclsFromParentScope=*/true); |
| |
| DeclContext *CtxToSave = const_cast<FunctionDecl *>(FD); |
| |
| while (isLambdaCallOperator(CtxToSave) || FD->isTransparentContext()) { |
| if (isLambdaCallOperator(CtxToSave)) |
| CtxToSave = CtxToSave->getParent()->getParent(); |
| else |
| CtxToSave = CtxToSave->getNonTransparentContext(); |
| } |
| |
| ContextRAII SavedContext{*this, CtxToSave}; |
| LocalInstantiationScope Scope(*this, !ForOverloadResolution); |
| std::optional<MultiLevelTemplateArgumentList> MLTAL = |
| SetupConstraintCheckingTemplateArgumentsAndScope( |
| const_cast<FunctionDecl *>(FD), {}, Scope); |
| |
| if (!MLTAL) |
| return true; |
| |
| Qualifiers ThisQuals; |
| CXXRecordDecl *Record = nullptr; |
| if (auto *Method = dyn_cast<CXXMethodDecl>(FD)) { |
| ThisQuals = Method->getMethodQualifiers(); |
| Record = const_cast<CXXRecordDecl *>(Method->getParent()); |
| } |
| CXXThisScopeRAII ThisScope(*this, Record, ThisQuals, Record != nullptr); |
| |
| LambdaScopeForCallOperatorInstantiationRAII LambdaScope( |
| *this, const_cast<FunctionDecl *>(FD), *MLTAL, Scope, |
| ForOverloadResolution); |
| |
| return CheckConstraintSatisfaction( |
| FD, FD->getTrailingRequiresClause(), *MLTAL, |
| SourceRange(UsageLoc.isValid() ? UsageLoc : FD->getLocation()), |
| Satisfaction); |
| } |
| |
| static const Expr *SubstituteConstraintExpressionWithoutSatisfaction( |
| Sema &S, const Sema::TemplateCompareNewDeclInfo &DeclInfo, |
| const Expr *ConstrExpr) { |
| MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs( |
| DeclInfo.getDecl(), DeclInfo.getDeclContext(), /*Final=*/false, |
| /*Innermost=*/std::nullopt, |
| /*RelativeToPrimary=*/true, |
| /*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true, |
| /*SkipForSpecialization*/ false); |
| |
| if (MLTAL.getNumSubstitutedLevels() == 0) |
| return ConstrExpr; |
| |
| Sema::SFINAETrap SFINAE(S); |
| |
| Sema::InstantiatingTemplate Inst( |
| S, DeclInfo.getLocation(), |
| Sema::InstantiatingTemplate::ConstraintNormalization{}, |
| const_cast<NamedDecl *>(DeclInfo.getDecl()), SourceRange{}); |
| if (Inst.isInvalid()) |
| return nullptr; |
| |
| // Set up a dummy 'instantiation' scope in the case of reference to function |
| // parameters that the surrounding function hasn't been instantiated yet. Note |
| // this may happen while we're comparing two templates' constraint |
| // equivalence. |
| std::optional<LocalInstantiationScope> ScopeForParameters; |
| if (const NamedDecl *ND = DeclInfo.getDecl(); |
| ND && ND->isFunctionOrFunctionTemplate()) { |
| ScopeForParameters.emplace(S, /*CombineWithOuterScope=*/true); |
| const FunctionDecl *FD = ND->getAsFunction(); |
| if (FunctionTemplateDecl *Template = FD->getDescribedFunctionTemplate(); |
| Template && Template->getInstantiatedFromMemberTemplate()) |
| FD = Template->getInstantiatedFromMemberTemplate()->getTemplatedDecl(); |
| for (auto *PVD : FD->parameters()) { |
| if (ScopeForParameters->getInstantiationOfIfExists(PVD)) |
| continue; |
| if (!PVD->isParameterPack()) { |
| ScopeForParameters->InstantiatedLocal(PVD, PVD); |
| continue; |
| } |
| // This is hacky: we're mapping the parameter pack to a size-of-1 argument |
| // to avoid building SubstTemplateTypeParmPackTypes for |
| // PackExpansionTypes. The SubstTemplateTypeParmPackType node would |
| // otherwise reference the AssociatedDecl of the template arguments, which |
| // is, in this case, the template declaration. |
| // |
| // However, as we are in the process of comparing potential |
| // re-declarations, the canonical declaration is the declaration itself at |
| // this point. So if we didn't expand these packs, we would end up with an |
| // incorrect profile difference because we will be profiling the |
| // canonical types! |
| // |
| // FIXME: Improve the "no-transform" machinery in FindInstantiatedDecl so |
| // that we can eliminate the Scope in the cases where the declarations are |
| // not necessarily instantiated. It would also benefit the noexcept |
| // specifier comparison. |
| ScopeForParameters->MakeInstantiatedLocalArgPack(PVD); |
| ScopeForParameters->InstantiatedLocalPackArg(PVD, PVD); |
| } |
| } |
| |
| std::optional<Sema::CXXThisScopeRAII> ThisScope; |
| |
| // See TreeTransform::RebuildTemplateSpecializationType. A context scope is |
| // essential for having an injected class as the canonical type for a template |
| // specialization type at the rebuilding stage. This guarantees that, for |
| // out-of-line definitions, injected class name types and their equivalent |
| // template specializations can be profiled to the same value, which makes it |
| // possible that e.g. constraints involving C<Class<T>> and C<Class> are |
| // perceived identical. |
| std::optional<Sema::ContextRAII> ContextScope; |
| const DeclContext *DC = [&] { |
| if (!DeclInfo.getDecl()) |
| return DeclInfo.getDeclContext(); |
| return DeclInfo.getDecl()->getFriendObjectKind() |
| ? DeclInfo.getLexicalDeclContext() |
| : DeclInfo.getDeclContext(); |
| }(); |
| if (auto *RD = dyn_cast<CXXRecordDecl>(DC)) { |
| ThisScope.emplace(S, const_cast<CXXRecordDecl *>(RD), Qualifiers()); |
| ContextScope.emplace(S, const_cast<DeclContext *>(cast<DeclContext>(RD)), |
| /*NewThisContext=*/false); |
| } |
| EnterExpressionEvaluationContext UnevaluatedContext( |
| S, Sema::ExpressionEvaluationContext::Unevaluated, |
| Sema::ReuseLambdaContextDecl); |
| ExprResult SubstConstr = S.SubstConstraintExprWithoutSatisfaction( |
| const_cast<clang::Expr *>(ConstrExpr), MLTAL); |
| if (SFINAE.hasErrorOccurred() || !SubstConstr.isUsable()) |
| return nullptr; |
| return SubstConstr.get(); |
| } |
| |
| bool Sema::AreConstraintExpressionsEqual(const NamedDecl *Old, |
| const Expr *OldConstr, |
| const TemplateCompareNewDeclInfo &New, |
| const Expr *NewConstr) { |
| if (OldConstr == NewConstr) |
| return true; |
| // C++ [temp.constr.decl]p4 |
| if (Old && !New.isInvalid() && !New.ContainsDecl(Old) && |
| Old->getLexicalDeclContext() != New.getLexicalDeclContext()) { |
| if (const Expr *SubstConstr = |
| SubstituteConstraintExpressionWithoutSatisfaction(*this, Old, |
| OldConstr)) |
| OldConstr = SubstConstr; |
| else |
| return false; |
| if (const Expr *SubstConstr = |
| SubstituteConstraintExpressionWithoutSatisfaction(*this, New, |
| NewConstr)) |
| NewConstr = SubstConstr; |
| else |
| return false; |
| } |
| |
| llvm::FoldingSetNodeID ID1, ID2; |
| OldConstr->Profile(ID1, Context, /*Canonical=*/true); |
| NewConstr->Profile(ID2, Context, /*Canonical=*/true); |
| return ID1 == ID2; |
| } |
| |
| bool Sema::FriendConstraintsDependOnEnclosingTemplate(const FunctionDecl *FD) { |
| assert(FD->getFriendObjectKind() && "Must be a friend!"); |
| |
| // The logic for non-templates is handled in ASTContext::isSameEntity, so we |
| // don't have to bother checking 'DependsOnEnclosingTemplate' for a |
| // non-function-template. |
| assert(FD->getDescribedFunctionTemplate() && |
| "Non-function templates don't need to be checked"); |
| |
| SmallVector<AssociatedConstraint, 3> ACs; |
| FD->getDescribedFunctionTemplate()->getAssociatedConstraints(ACs); |
| |
| unsigned OldTemplateDepth = CalculateTemplateDepthForConstraints(*this, FD); |
| for (const AssociatedConstraint &AC : ACs) |
| if (ConstraintExpressionDependsOnEnclosingTemplate(FD, OldTemplateDepth, |
| AC.ConstraintExpr)) |
| return true; |
| |
| return false; |
| } |
| |
| bool Sema::EnsureTemplateArgumentListConstraints( |
| TemplateDecl *TD, const MultiLevelTemplateArgumentList &TemplateArgsLists, |
| SourceRange TemplateIDRange) { |
| ConstraintSatisfaction Satisfaction; |
| llvm::SmallVector<AssociatedConstraint, 3> AssociatedConstraints; |
| TD->getAssociatedConstraints(AssociatedConstraints); |
| if (CheckConstraintSatisfaction(TD, AssociatedConstraints, TemplateArgsLists, |
| TemplateIDRange, Satisfaction)) |
| return true; |
| |
| if (!Satisfaction.IsSatisfied) { |
| SmallString<128> TemplateArgString; |
| TemplateArgString = " "; |
| TemplateArgString += getTemplateArgumentBindingsText( |
| TD->getTemplateParameters(), TemplateArgsLists.getInnermost().data(), |
| TemplateArgsLists.getInnermost().size()); |
| |
| Diag(TemplateIDRange.getBegin(), |
| diag::err_template_arg_list_constraints_not_satisfied) |
| << (int)getTemplateNameKindForDiagnostics(TemplateName(TD)) << TD |
| << TemplateArgString << TemplateIDRange; |
| DiagnoseUnsatisfiedConstraint(Satisfaction); |
| return true; |
| } |
| return false; |
| } |
| |
| static bool CheckFunctionConstraintsWithoutInstantiation( |
| Sema &SemaRef, SourceLocation PointOfInstantiation, |
| FunctionTemplateDecl *Template, ArrayRef<TemplateArgument> TemplateArgs, |
| ConstraintSatisfaction &Satisfaction) { |
| SmallVector<AssociatedConstraint, 3> TemplateAC; |
| Template->getAssociatedConstraints(TemplateAC); |
| if (TemplateAC.empty()) { |
| Satisfaction.IsSatisfied = true; |
| return false; |
| } |
| |
| LocalInstantiationScope Scope(SemaRef); |
| |
| FunctionDecl *FD = Template->getTemplatedDecl(); |
| // Collect the list of template arguments relative to the 'primary' |
| // template. We need the entire list, since the constraint is completely |
| // uninstantiated at this point. |
| |
| MultiLevelTemplateArgumentList MLTAL; |
| { |
| // getTemplateInstantiationArgs uses this instantiation context to find out |
| // template arguments for uninstantiated functions. |
| // We don't want this RAII object to persist, because there would be |
| // otherwise duplicate diagnostic notes. |
| Sema::InstantiatingTemplate Inst( |
| SemaRef, PointOfInstantiation, |
| Sema::InstantiatingTemplate::ConstraintsCheck{}, Template, TemplateArgs, |
| PointOfInstantiation); |
| if (Inst.isInvalid()) |
| return true; |
| MLTAL = SemaRef.getTemplateInstantiationArgs( |
| /*D=*/FD, FD, |
| /*Final=*/false, /*Innermost=*/{}, /*RelativeToPrimary=*/true, |
| /*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true); |
| } |
| |
| Sema::ContextRAII SavedContext(SemaRef, FD); |
| return SemaRef.CheckConstraintSatisfaction( |
| Template, TemplateAC, MLTAL, PointOfInstantiation, Satisfaction); |
| } |
| |
| bool Sema::CheckFunctionTemplateConstraints( |
| SourceLocation PointOfInstantiation, FunctionDecl *Decl, |
| ArrayRef<TemplateArgument> TemplateArgs, |
| ConstraintSatisfaction &Satisfaction) { |
| // In most cases we're not going to have constraints, so check for that first. |
| FunctionTemplateDecl *Template = Decl->getPrimaryTemplate(); |
| |
| if (!Template) |
| return ::CheckFunctionConstraintsWithoutInstantiation( |
| *this, PointOfInstantiation, Decl->getDescribedFunctionTemplate(), |
| TemplateArgs, Satisfaction); |
| |
| // Note - code synthesis context for the constraints check is created |
| // inside CheckConstraintsSatisfaction. |
| SmallVector<AssociatedConstraint, 3> TemplateAC; |
| Template->getAssociatedConstraints(TemplateAC); |
| if (TemplateAC.empty()) { |
| Satisfaction.IsSatisfied = true; |
| return false; |
| } |
| |
| // Enter the scope of this instantiation. We don't use |
| // PushDeclContext because we don't have a scope. |
| Sema::ContextRAII savedContext(*this, Decl); |
| LocalInstantiationScope Scope(*this); |
| |
| std::optional<MultiLevelTemplateArgumentList> MLTAL = |
| SetupConstraintCheckingTemplateArgumentsAndScope(Decl, TemplateArgs, |
| Scope); |
| |
| if (!MLTAL) |
| return true; |
| |
| Qualifiers ThisQuals; |
| CXXRecordDecl *Record = nullptr; |
| if (auto *Method = dyn_cast<CXXMethodDecl>(Decl)) { |
| ThisQuals = Method->getMethodQualifiers(); |
| Record = Method->getParent(); |
| } |
| |
| CXXThisScopeRAII ThisScope(*this, Record, ThisQuals, Record != nullptr); |
| LambdaScopeForCallOperatorInstantiationRAII LambdaScope(*this, Decl, *MLTAL, |
| Scope); |
| |
| return CheckConstraintSatisfaction(Template, TemplateAC, *MLTAL, |
| PointOfInstantiation, Satisfaction); |
| } |
| |
| static void diagnoseUnsatisfiedRequirement(Sema &S, |
| concepts::ExprRequirement *Req, |
| bool First) { |
| assert(!Req->isSatisfied() && |
| "Diagnose() can only be used on an unsatisfied requirement"); |
| switch (Req->getSatisfactionStatus()) { |
| case concepts::ExprRequirement::SS_Dependent: |
| llvm_unreachable("Diagnosing a dependent requirement"); |
| break; |
| case concepts::ExprRequirement::SS_ExprSubstitutionFailure: { |
| auto *SubstDiag = Req->getExprSubstitutionDiagnostic(); |
| if (!SubstDiag->DiagMessage.empty()) |
| S.Diag(SubstDiag->DiagLoc, |
| diag::note_expr_requirement_expr_substitution_error) |
| << (int)First << SubstDiag->SubstitutedEntity |
| << SubstDiag->DiagMessage; |
| else |
| S.Diag(SubstDiag->DiagLoc, |
| diag::note_expr_requirement_expr_unknown_substitution_error) |
| << (int)First << SubstDiag->SubstitutedEntity; |
| break; |
| } |
| case concepts::ExprRequirement::SS_NoexceptNotMet: |
| S.Diag(Req->getNoexceptLoc(), diag::note_expr_requirement_noexcept_not_met) |
| << (int)First << Req->getExpr(); |
| break; |
| case concepts::ExprRequirement::SS_TypeRequirementSubstitutionFailure: { |
| auto *SubstDiag = |
| Req->getReturnTypeRequirement().getSubstitutionDiagnostic(); |
| if (!SubstDiag->DiagMessage.empty()) |
| S.Diag(SubstDiag->DiagLoc, |
| diag::note_expr_requirement_type_requirement_substitution_error) |
| << (int)First << SubstDiag->SubstitutedEntity |
| << SubstDiag->DiagMessage; |
| else |
| S.Diag( |
| SubstDiag->DiagLoc, |
| diag:: |
| note_expr_requirement_type_requirement_unknown_substitution_error) |
| << (int)First << SubstDiag->SubstitutedEntity; |
| break; |
| } |
| case concepts::ExprRequirement::SS_ConstraintsNotSatisfied: { |
| ConceptSpecializationExpr *ConstraintExpr = |
| Req->getReturnTypeRequirementSubstitutedConstraintExpr(); |
| S.DiagnoseUnsatisfiedConstraint(ConstraintExpr); |
| break; |
| } |
| case concepts::ExprRequirement::SS_Satisfied: |
| llvm_unreachable("We checked this above"); |
| } |
| } |
| |
| static void diagnoseUnsatisfiedRequirement(Sema &S, |
| concepts::TypeRequirement *Req, |
| bool First) { |
| assert(!Req->isSatisfied() && |
| "Diagnose() can only be used on an unsatisfied requirement"); |
| switch (Req->getSatisfactionStatus()) { |
| case concepts::TypeRequirement::SS_Dependent: |
| llvm_unreachable("Diagnosing a dependent requirement"); |
| return; |
| case concepts::TypeRequirement::SS_SubstitutionFailure: { |
| auto *SubstDiag = Req->getSubstitutionDiagnostic(); |
| if (!SubstDiag->DiagMessage.empty()) |
| S.Diag(SubstDiag->DiagLoc, diag::note_type_requirement_substitution_error) |
| << (int)First << SubstDiag->SubstitutedEntity |
| << SubstDiag->DiagMessage; |
| else |
| S.Diag(SubstDiag->DiagLoc, |
| diag::note_type_requirement_unknown_substitution_error) |
| << (int)First << SubstDiag->SubstitutedEntity; |
| return; |
| } |
| default: |
| llvm_unreachable("Unknown satisfaction status"); |
| return; |
| } |
| } |
| |
| static void diagnoseUnsatisfiedConceptIdExpr(Sema &S, |
| const ConceptReference *Concept, |
| SourceLocation Loc, bool First) { |
| if (Concept->getTemplateArgsAsWritten()->NumTemplateArgs == 1) { |
| S.Diag( |
| Loc, |
| diag:: |
| note_single_arg_concept_specialization_constraint_evaluated_to_false) |
| << (int)First |
| << Concept->getTemplateArgsAsWritten()->arguments()[0].getArgument() |
| << Concept->getNamedConcept(); |
| } else { |
| S.Diag(Loc, diag::note_concept_specialization_constraint_evaluated_to_false) |
| << (int)First << Concept; |
| } |
| } |
| |
| static void diagnoseUnsatisfiedConstraintExpr( |
| Sema &S, const UnsatisfiedConstraintRecord &Record, SourceLocation Loc, |
| bool First, concepts::NestedRequirement *Req = nullptr); |
| |
| static void DiagnoseUnsatisfiedConstraint( |
| Sema &S, ArrayRef<UnsatisfiedConstraintRecord> Records, SourceLocation Loc, |
| bool First = true, concepts::NestedRequirement *Req = nullptr) { |
| for (auto &Record : Records) { |
| diagnoseUnsatisfiedConstraintExpr(S, Record, Loc, First, Req); |
| Loc = {}; |
| First = isa<const ConceptReference *>(Record); |
| } |
| } |
| |
| static void diagnoseUnsatisfiedRequirement(Sema &S, |
| concepts::NestedRequirement *Req, |
| bool First) { |
| DiagnoseUnsatisfiedConstraint(S, Req->getConstraintSatisfaction().records(), |
| Req->hasInvalidConstraint() |
| ? SourceLocation() |
| : Req->getConstraintExpr()->getExprLoc(), |
| First, Req); |
| } |
| |
| static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S, |
| const Expr *SubstExpr, |
| bool First) { |
| SubstExpr = SubstExpr->IgnoreParenImpCasts(); |
| if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(SubstExpr)) { |
| switch (BO->getOpcode()) { |
| // These two cases will in practice only be reached when using fold |
| // expressions with || and &&, since otherwise the || and && will have been |
| // broken down into atomic constraints during satisfaction checking. |
| case BO_LOr: |
| // Or evaluated to false - meaning both RHS and LHS evaluated to false. |
| diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getLHS(), First); |
| diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(), |
| /*First=*/false); |
| return; |
| case BO_LAnd: { |
| bool LHSSatisfied = |
| BO->getLHS()->EvaluateKnownConstInt(S.Context).getBoolValue(); |
| if (LHSSatisfied) { |
| // LHS is true, so RHS must be false. |
| diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(), First); |
| return; |
| } |
| // LHS is false |
| diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getLHS(), First); |
| |
| // RHS might also be false |
| bool RHSSatisfied = |
| BO->getRHS()->EvaluateKnownConstInt(S.Context).getBoolValue(); |
| if (!RHSSatisfied) |
| diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(), |
| /*First=*/false); |
| return; |
| } |
| case BO_GE: |
| case BO_LE: |
| case BO_GT: |
| case BO_LT: |
| case BO_EQ: |
| case BO_NE: |
| if (BO->getLHS()->getType()->isIntegerType() && |
| BO->getRHS()->getType()->isIntegerType()) { |
| Expr::EvalResult SimplifiedLHS; |
| Expr::EvalResult SimplifiedRHS; |
| BO->getLHS()->EvaluateAsInt(SimplifiedLHS, S.Context, |
| Expr::SE_NoSideEffects, |
| /*InConstantContext=*/true); |
| BO->getRHS()->EvaluateAsInt(SimplifiedRHS, S.Context, |
| Expr::SE_NoSideEffects, |
| /*InConstantContext=*/true); |
| if (!SimplifiedLHS.Diag && !SimplifiedRHS.Diag) { |
| S.Diag(SubstExpr->getBeginLoc(), |
| diag::note_atomic_constraint_evaluated_to_false_elaborated) |
| << (int)First << SubstExpr |
| << toString(SimplifiedLHS.Val.getInt(), 10) |
| << BinaryOperator::getOpcodeStr(BO->getOpcode()) |
| << toString(SimplifiedRHS.Val.getInt(), 10); |
| return; |
| } |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } else if (auto *RE = dyn_cast<RequiresExpr>(SubstExpr)) { |
| // FIXME: RequiresExpr should store dependent diagnostics. |
| for (concepts::Requirement *Req : RE->getRequirements()) |
| if (!Req->isDependent() && !Req->isSatisfied()) { |
| if (auto *E = dyn_cast<concepts::ExprRequirement>(Req)) |
| diagnoseUnsatisfiedRequirement(S, E, First); |
| else if (auto *T = dyn_cast<concepts::TypeRequirement>(Req)) |
| diagnoseUnsatisfiedRequirement(S, T, First); |
| else |
| diagnoseUnsatisfiedRequirement( |
| S, cast<concepts::NestedRequirement>(Req), First); |
| break; |
| } |
| return; |
| } else if (auto *CSE = dyn_cast<ConceptSpecializationExpr>(SubstExpr)) { |
| // Drill down concept ids treated as atomic constraints |
| S.DiagnoseUnsatisfiedConstraint(CSE, First); |
| return; |
| } else if (auto *TTE = dyn_cast<TypeTraitExpr>(SubstExpr); |
| TTE && TTE->getTrait() == clang::TypeTrait::BTT_IsDeducible) { |
| assert(TTE->getNumArgs() == 2); |
| S.Diag(SubstExpr->getSourceRange().getBegin(), |
| diag::note_is_deducible_constraint_evaluated_to_false) |
| << TTE->getArg(0)->getType() << TTE->getArg(1)->getType(); |
| return; |
| } |
| |
| S.Diag(SubstExpr->getSourceRange().getBegin(), |
| diag::note_atomic_constraint_evaluated_to_false) |
| << (int)First << SubstExpr; |
| S.DiagnoseTypeTraitDetails(SubstExpr); |
| } |
| |
| static void diagnoseUnsatisfiedConstraintExpr( |
| Sema &S, const UnsatisfiedConstraintRecord &Record, SourceLocation Loc, |
| bool First, concepts::NestedRequirement *Req) { |
| if (auto *Diag = |
| Record |
| .template dyn_cast<const ConstraintSubstitutionDiagnostic *>()) { |
| if (Req) |
| S.Diag(Diag->first, diag::note_nested_requirement_substitution_error) |
| << (int)First << Req->getInvalidConstraintEntity() << Diag->second; |
| else |
| S.Diag(Diag->first, diag::note_substituted_constraint_expr_is_ill_formed) |
| << Diag->second; |
| return; |
| } |
| if (const auto *Concept = dyn_cast<const ConceptReference *>(Record)) { |
| if (Loc.isInvalid()) |
| Loc = Concept->getBeginLoc(); |
| diagnoseUnsatisfiedConceptIdExpr(S, Concept, Loc, First); |
| return; |
| } |
| diagnoseWellFormedUnsatisfiedConstraintExpr( |
| S, cast<const class Expr *>(Record), First); |
| } |
| |
| void Sema::DiagnoseUnsatisfiedConstraint( |
| const ConstraintSatisfaction &Satisfaction, SourceLocation Loc, |
| bool First) { |
| |
| assert(!Satisfaction.IsSatisfied && |
| "Attempted to diagnose a satisfied constraint"); |
| ::DiagnoseUnsatisfiedConstraint(*this, Satisfaction.Details, Loc, First); |
| } |
| |
| void Sema::DiagnoseUnsatisfiedConstraint( |
| const ConceptSpecializationExpr *ConstraintExpr, bool First) { |
| |
| const ASTConstraintSatisfaction &Satisfaction = |
| ConstraintExpr->getSatisfaction(); |
| |
| assert(!Satisfaction.IsSatisfied && |
| "Attempted to diagnose a satisfied constraint"); |
| |
| ::DiagnoseUnsatisfiedConstraint(*this, Satisfaction.records(), |
| ConstraintExpr->getBeginLoc(), First); |
| } |
| |
| namespace { |
| |
| class SubstituteParameterMappings { |
| Sema &SemaRef; |
| |
| const MultiLevelTemplateArgumentList *MLTAL; |
| const ASTTemplateArgumentListInfo *ArgsAsWritten; |
| |
| bool InFoldExpr; |
| |
| SubstituteParameterMappings(Sema &SemaRef, |
| const MultiLevelTemplateArgumentList *MLTAL, |
| const ASTTemplateArgumentListInfo *ArgsAsWritten, |
| bool InFoldExpr) |
| : SemaRef(SemaRef), MLTAL(MLTAL), ArgsAsWritten(ArgsAsWritten), |
| InFoldExpr(InFoldExpr) {} |
| |
| void buildParameterMapping(NormalizedConstraintWithParamMapping &N); |
| |
| bool substitute(NormalizedConstraintWithParamMapping &N); |
| |
| bool substitute(ConceptIdConstraint &CC); |
| |
| public: |
| SubstituteParameterMappings(Sema &SemaRef, bool InFoldExpr = false) |
| : SemaRef(SemaRef), MLTAL(nullptr), ArgsAsWritten(nullptr), |
| InFoldExpr(InFoldExpr) {} |
| |
| bool substitute(NormalizedConstraint &N); |
| }; |
| |
| void SubstituteParameterMappings::buildParameterMapping( |
| NormalizedConstraintWithParamMapping &N) { |
| TemplateParameterList *TemplateParams = |
| cast<TemplateDecl>(N.getConstraintDecl())->getTemplateParameters(); |
| |
| llvm::SmallBitVector OccurringIndices(TemplateParams->size()); |
| llvm::SmallBitVector OccurringIndicesForSubsumption(TemplateParams->size()); |
| |
| if (N.getKind() == NormalizedConstraint::ConstraintKind::Atomic) { |
| SemaRef.MarkUsedTemplateParameters( |
| static_cast<AtomicConstraint &>(N).getConstraintExpr(), |
| /*OnlyDeduced=*/false, |
| /*Depth=*/0, OccurringIndices); |
| |
| SemaRef.MarkUsedTemplateParametersForSubsumptionParameterMapping( |
| static_cast<AtomicConstraint &>(N).getConstraintExpr(), |
| /*Depth=*/0, OccurringIndicesForSubsumption); |
| |
| } else if (N.getKind() == |
| NormalizedConstraint::ConstraintKind::FoldExpanded) { |
| SemaRef.MarkUsedTemplateParameters( |
| static_cast<FoldExpandedConstraint &>(N).getPattern(), |
| /*OnlyDeduced=*/false, |
| /*Depth=*/0, OccurringIndices); |
| } else if (N.getKind() == NormalizedConstraint::ConstraintKind::ConceptId) { |
| auto *Args = static_cast<ConceptIdConstraint &>(N) |
| .getConceptId() |
| ->getTemplateArgsAsWritten(); |
| if (Args) |
| SemaRef.MarkUsedTemplateParameters(Args->arguments(), |
| /*Depth=*/0, OccurringIndices); |
| } |
| TemplateArgumentLoc *TempArgs = |
| new (SemaRef.Context) TemplateArgumentLoc[OccurringIndices.count()]; |
| llvm::SmallVector<NamedDecl *> UsedParams; |
| for (unsigned I = 0, J = 0, C = TemplateParams->size(); I != C; ++I) { |
| SourceLocation Loc = ArgsAsWritten->NumTemplateArgs > I |
| ? ArgsAsWritten->arguments()[I].getLocation() |
| : SourceLocation(); |
| // FIXME: Investigate why we couldn't always preserve the SourceLoc. We |
| // can't assert Loc.isValid() now. |
| if (OccurringIndices[I]) { |
| NamedDecl *Param = TemplateParams->begin()[I]; |
| new (&(TempArgs)[J]) TemplateArgumentLoc( |
| SemaRef.getIdentityTemplateArgumentLoc(Param, Loc)); |
| UsedParams.push_back(Param); |
| J++; |
| } |
| } |
| auto *UsedList = TemplateParameterList::Create( |
| SemaRef.Context, TemplateParams->getTemplateLoc(), |
| TemplateParams->getLAngleLoc(), UsedParams, |
| /*RAngleLoc=*/SourceLocation(), |
| /*RequiresClause=*/nullptr); |
| unsigned Size = OccurringIndices.count(); |
| N.updateParameterMapping( |
| std::move(OccurringIndices), std::move(OccurringIndicesForSubsumption), |
| MutableArrayRef<TemplateArgumentLoc>{TempArgs, Size}, UsedList); |
| } |
| |
| bool SubstituteParameterMappings::substitute( |
| NormalizedConstraintWithParamMapping &N) { |
| if (!N.hasParameterMapping()) |
| buildParameterMapping(N); |
| |
| SourceLocation InstLocBegin, InstLocEnd; |
| llvm::ArrayRef Arguments = ArgsAsWritten->arguments(); |
| if (Arguments.empty()) { |
| InstLocBegin = ArgsAsWritten->getLAngleLoc(); |
| InstLocEnd = ArgsAsWritten->getRAngleLoc(); |
| } else { |
| auto SR = Arguments[0].getSourceRange(); |
| InstLocBegin = SR.getBegin(); |
| InstLocEnd = SR.getEnd(); |
| } |
| Sema::InstantiatingTemplate Inst( |
| SemaRef, InstLocBegin, |
| Sema::InstantiatingTemplate::ParameterMappingSubstitution{}, |
| const_cast<NamedDecl *>(N.getConstraintDecl()), |
| {InstLocBegin, InstLocEnd}); |
| if (Inst.isInvalid()) |
| return true; |
| |
| // TransformTemplateArguments is unable to preserve the source location of a |
| // pack. The SourceLocation is necessary for the instantiation location. |
| // FIXME: The BaseLoc will be used as the location of the pack expansion, |
| // which is wrong. |
| TemplateArgumentListInfo SubstArgs; |
| if (SemaRef.SubstTemplateArgumentsInParameterMapping( |
| N.getParameterMapping(), N.getBeginLoc(), *MLTAL, SubstArgs, |
| /*BuildPackExpansionTypes=*/!InFoldExpr)) |
| return true; |
| Sema::CheckTemplateArgumentInfo CTAI; |
| auto *TD = |
| const_cast<TemplateDecl *>(cast<TemplateDecl>(N.getConstraintDecl())); |
| if (SemaRef.CheckTemplateArgumentList(TD, N.getUsedTemplateParamList(), |
| TD->getLocation(), SubstArgs, |
| /*DefaultArguments=*/{}, |
| /*PartialTemplateArgs=*/false, CTAI)) |
| return true; |
| |
| TemplateArgumentLoc *TempArgs = |
| new (SemaRef.Context) TemplateArgumentLoc[CTAI.SugaredConverted.size()]; |
| |
| for (unsigned I = 0; I < CTAI.SugaredConverted.size(); ++I) { |
| SourceLocation Loc; |
| // If this is an empty pack, we have no corresponding SubstArgs. |
| if (I < SubstArgs.size()) |
| Loc = SubstArgs.arguments()[I].getLocation(); |
| |
| TempArgs[I] = SemaRef.getTrivialTemplateArgumentLoc( |
| CTAI.SugaredConverted[I], QualType(), Loc); |
| } |
| |
| MutableArrayRef<TemplateArgumentLoc> Mapping(TempArgs, |
| CTAI.SugaredConverted.size()); |
| N.updateParameterMapping(N.mappingOccurenceList(), |
| N.mappingOccurenceListForSubsumption(), Mapping, |
| N.getUsedTemplateParamList()); |
| return false; |
| } |
| |
| bool SubstituteParameterMappings::substitute(ConceptIdConstraint &CC) { |
| assert(CC.getConstraintDecl() && MLTAL && ArgsAsWritten); |
| |
| if (substitute(static_cast<NormalizedConstraintWithParamMapping &>(CC))) |
| return true; |
| |
| auto *CSE = CC.getConceptSpecializationExpr(); |
| assert(CSE); |
| assert(!CC.getBeginLoc().isInvalid()); |
| |
| SourceLocation InstLocBegin, InstLocEnd; |
| if (llvm::ArrayRef Arguments = ArgsAsWritten->arguments(); |
| Arguments.empty()) { |
| InstLocBegin = ArgsAsWritten->getLAngleLoc(); |
| InstLocEnd = ArgsAsWritten->getRAngleLoc(); |
| } else { |
| auto SR = Arguments[0].getSourceRange(); |
| InstLocBegin = SR.getBegin(); |
| InstLocEnd = SR.getEnd(); |
| } |
| // This is useful for name lookup across modules; see Sema::getLookupModules. |
| Sema::InstantiatingTemplate Inst( |
| SemaRef, InstLocBegin, |
| Sema::InstantiatingTemplate::ParameterMappingSubstitution{}, |
| const_cast<NamedDecl *>(CC.getConstraintDecl()), |
| {InstLocBegin, InstLocEnd}); |
| if (Inst.isInvalid()) |
| return true; |
| |
| TemplateArgumentListInfo Out; |
| // TransformTemplateArguments is unable to preserve the source location of a |
| // pack. The SourceLocation is necessary for the instantiation location. |
| // FIXME: The BaseLoc will be used as the location of the pack expansion, |
| // which is wrong. |
| const ASTTemplateArgumentListInfo *ArgsAsWritten = |
| CSE->getTemplateArgsAsWritten(); |
| if (SemaRef.SubstTemplateArgumentsInParameterMapping( |
| ArgsAsWritten->arguments(), CC.getBeginLoc(), *MLTAL, Out, |
| /*BuildPackExpansionTypes=*/!InFoldExpr)) |
| return true; |
| Sema::CheckTemplateArgumentInfo CTAI; |
| if (SemaRef.CheckTemplateArgumentList(CSE->getNamedConcept(), |
| CSE->getConceptNameInfo().getLoc(), Out, |
| /*DefaultArgs=*/{}, |
| /*PartialTemplateArgs=*/false, CTAI, |
| /*UpdateArgsWithConversions=*/false)) |
| return true; |
| auto TemplateArgs = *MLTAL; |
| TemplateArgs.replaceOutermostTemplateArguments(CSE->getNamedConcept(), |
| CTAI.SugaredConverted); |
| return SubstituteParameterMappings(SemaRef, &TemplateArgs, ArgsAsWritten, |
| InFoldExpr) |
| .substitute(CC.getNormalizedConstraint()); |
| } |
| |
| bool SubstituteParameterMappings::substitute(NormalizedConstraint &N) { |
| switch (N.getKind()) { |
| case NormalizedConstraint::ConstraintKind::Atomic: { |
| if (!MLTAL) { |
| assert(!ArgsAsWritten); |
| return false; |
| } |
| return substitute(static_cast<NormalizedConstraintWithParamMapping &>(N)); |
| } |
| case NormalizedConstraint::ConstraintKind::FoldExpanded: { |
| auto &FE = static_cast<FoldExpandedConstraint &>(N); |
| if (!MLTAL) { |
| llvm::SaveAndRestore _1(InFoldExpr, true); |
| assert(!ArgsAsWritten); |
| return substitute(FE.getNormalizedPattern()); |
| } |
| Sema::ArgPackSubstIndexRAII _(SemaRef, std::nullopt); |
| substitute(static_cast<NormalizedConstraintWithParamMapping &>(FE)); |
| return SubstituteParameterMappings(SemaRef, /*InFoldExpr=*/true) |
| .substitute(FE.getNormalizedPattern()); |
| } |
| case NormalizedConstraint::ConstraintKind::ConceptId: { |
| auto &CC = static_cast<ConceptIdConstraint &>(N); |
| if (MLTAL) { |
| assert(ArgsAsWritten); |
| return substitute(CC); |
| } |
| assert(!ArgsAsWritten); |
| const ConceptSpecializationExpr *CSE = CC.getConceptSpecializationExpr(); |
| ConceptDecl *Concept = CSE->getNamedConcept(); |
| MultiLevelTemplateArgumentList MLTAL = SemaRef.getTemplateInstantiationArgs( |
| Concept, Concept->getLexicalDeclContext(), |
| /*Final=*/true, CSE->getTemplateArguments(), |
| /*RelativeToPrimary=*/true, |
| /*Pattern=*/nullptr, |
| /*ForConstraintInstantiation=*/true); |
| |
| return SubstituteParameterMappings( |
| SemaRef, &MLTAL, CSE->getTemplateArgsAsWritten(), InFoldExpr) |
| .substitute(CC.getNormalizedConstraint()); |
| } |
| case NormalizedConstraint::ConstraintKind::Compound: { |
| auto &Compound = static_cast<CompoundConstraint &>(N); |
| if (substitute(Compound.getLHS())) |
| return true; |
| return substitute(Compound.getRHS()); |
| } |
| } |
| llvm_unreachable("Unknown ConstraintKind enum"); |
| } |
| |
| } // namespace |
| |
| NormalizedConstraint *NormalizedConstraint::fromAssociatedConstraints( |
| Sema &S, const NamedDecl *D, ArrayRef<AssociatedConstraint> ACs) { |
| assert(ACs.size() != 0); |
| auto *Conjunction = |
| fromConstraintExpr(S, D, ACs[0].ConstraintExpr, ACs[0].ArgPackSubstIndex); |
| if (!Conjunction) |
| return nullptr; |
| for (unsigned I = 1; I < ACs.size(); ++I) { |
| auto *Next = fromConstraintExpr(S, D, ACs[I].ConstraintExpr, |
| ACs[I].ArgPackSubstIndex); |
| if (!Next) |
| return nullptr; |
| Conjunction = CompoundConstraint::CreateConjunction(S.getASTContext(), |
| Conjunction, Next); |
| } |
| return Conjunction; |
| } |
| |
| NormalizedConstraint *NormalizedConstraint::fromConstraintExpr( |
| Sema &S, const NamedDecl *D, const Expr *E, UnsignedOrNone SubstIndex) { |
| assert(E != nullptr); |
| |
| // C++ [temp.constr.normal]p1.1 |
| // [...] |
| // - The normal form of an expression (E) is the normal form of E. |
| // [...] |
| E = E->IgnoreParenImpCasts(); |
| |
| llvm::FoldingSetNodeID ID; |
| if (D && DiagRecursiveConstraintEval(S, ID, D, E)) { |
| return nullptr; |
| } |
| SatisfactionStackRAII StackRAII(S, D, ID); |
| |
| // C++2a [temp.param]p4: |
| // [...] If T is not a pack, then E is E', otherwise E is (E' && ...). |
| // Fold expression is considered atomic constraints per current wording. |
| // See http://cplusplus.github.io/concepts-ts/ts-active.html#28 |
| |
| if (LogicalBinOp BO = E) { |
| auto *LHS = fromConstraintExpr(S, D, BO.getLHS(), SubstIndex); |
| if (!LHS) |
| return nullptr; |
| auto *RHS = fromConstraintExpr(S, D, BO.getRHS(), SubstIndex); |
| if (!RHS) |
| return nullptr; |
| |
| return CompoundConstraint::Create( |
| S.Context, LHS, BO.isAnd() ? CCK_Conjunction : CCK_Disjunction, RHS); |
| } else if (auto *CSE = dyn_cast<const ConceptSpecializationExpr>(E)) { |
| NormalizedConstraint *SubNF; |
| { |
| Sema::InstantiatingTemplate Inst( |
| S, CSE->getExprLoc(), |
| Sema::InstantiatingTemplate::ConstraintNormalization{}, |
| // FIXME: improve const-correctness of InstantiatingTemplate |
| const_cast<NamedDecl *>(D), CSE->getSourceRange()); |
| if (Inst.isInvalid()) |
| return nullptr; |
| // C++ [temp.constr.normal]p1.1 |
| // [...] |
| // The normal form of an id-expression of the form C<A1, A2, ..., AN>, |
| // where C names a concept, is the normal form of the |
| // constraint-expression of C, after substituting A1, A2, ..., AN for C’s |
| // respective template parameters in the parameter mappings in each atomic |
| // constraint. If any such substitution results in an invalid type or |
| // expression, the program is ill-formed; no diagnostic is required. |
| // [...] |
| |
| // Use canonical declarations to merge ConceptDecls across |
| // different modules. |
| ConceptDecl *CD = CSE->getNamedConcept()->getCanonicalDecl(); |
| |
| ExprResult Res = |
| SubstituteConceptsInConstrainExpression(S, D, CSE, SubstIndex); |
| if (!Res.isUsable()) |
| return nullptr; |
| |
| SubNF = NormalizedConstraint::fromAssociatedConstraints( |
| S, CD, AssociatedConstraint(Res.get(), SubstIndex)); |
| |
| if (!SubNF) |
| return nullptr; |
| } |
| |
| return ConceptIdConstraint::Create(S.getASTContext(), |
| CSE->getConceptReference(), SubNF, D, |
| CSE, SubstIndex); |
| |
| } else if (auto *FE = dyn_cast<const CXXFoldExpr>(E); |
| FE && S.getLangOpts().CPlusPlus26 && |
| (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 |
| : FoldExpandedConstraint::FoldOperatorKind::Or; |
| |
| if (FE->getInit()) { |
| auto *LHS = fromConstraintExpr(S, D, FE->getLHS(), SubstIndex); |
| auto *RHS = fromConstraintExpr(S, D, FE->getRHS(), SubstIndex); |
| if (!LHS || !RHS) |
| return nullptr; |
| |
| if (FE->isRightFold()) |
| LHS = FoldExpandedConstraint::Create(S.getASTContext(), |
| FE->getPattern(), D, Kind, LHS); |
| else |
| RHS = FoldExpandedConstraint::Create(S.getASTContext(), |
| FE->getPattern(), D, Kind, RHS); |
| |
| return CompoundConstraint::Create( |
| S.getASTContext(), LHS, |
| (FE->getOperator() == BinaryOperatorKind::BO_LAnd ? CCK_Conjunction |
| : CCK_Disjunction), |
| RHS); |
| } |
| auto *Sub = fromConstraintExpr(S, D, FE->getPattern(), SubstIndex); |
| if (!Sub) |
| return nullptr; |
| return FoldExpandedConstraint::Create(S.getASTContext(), FE->getPattern(), |
| D, Kind, Sub); |
| } |
| return AtomicConstraint::Create(S.getASTContext(), E, D, SubstIndex); |
| } |
| |
| const NormalizedConstraint *Sema::getNormalizedAssociatedConstraints( |
| ConstrainedDeclOrNestedRequirement ConstrainedDeclOrNestedReq, |
| ArrayRef<AssociatedConstraint> AssociatedConstraints) { |
| if (!ConstrainedDeclOrNestedReq) { |
| auto *Normalized = NormalizedConstraint::fromAssociatedConstraints( |
| *this, nullptr, AssociatedConstraints); |
| if (!Normalized || |
| SubstituteParameterMappings(*this).substitute(*Normalized)) |
| return nullptr; |
| |
| return Normalized; |
| } |
| |
| // FIXME: ConstrainedDeclOrNestedReq is never a NestedRequirement! |
| const NamedDecl *ND = |
| ConstrainedDeclOrNestedReq.dyn_cast<const NamedDecl *>(); |
| auto CacheEntry = NormalizationCache.find(ConstrainedDeclOrNestedReq); |
| if (CacheEntry == NormalizationCache.end()) { |
| auto *Normalized = NormalizedConstraint::fromAssociatedConstraints( |
| *this, ND, AssociatedConstraints); |
| CacheEntry = |
| NormalizationCache.try_emplace(ConstrainedDeclOrNestedReq, Normalized) |
| .first; |
| if (!Normalized || |
| SubstituteParameterMappings(*this).substitute(*Normalized)) |
| return nullptr; |
| } |
| return CacheEntry->second; |
| } |
| |
| 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.getPattern()), |
| APacks); |
| Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(B.getPattern()), |
| BPacks); |
| |
| for (const UnexpandedParameterPack &APack : APacks) { |
| auto ADI = getDepthAndIndex(APack); |
| if (!ADI) |
| continue; |
| auto It = llvm::find_if(BPacks, [&](const UnexpandedParameterPack &BPack) { |
| return getDepthAndIndex(BPack) == ADI; |
| }); |
| if (It != BPacks.end()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool Sema::IsAtLeastAsConstrained(const NamedDecl *D1, |
| MutableArrayRef<AssociatedConstraint> AC1, |
| const NamedDecl *D2, |
| MutableArrayRef<AssociatedConstraint> AC2, |
| bool &Result) { |
| #ifndef NDEBUG |
| if (const auto *FD1 = dyn_cast<FunctionDecl>(D1)) { |
| auto IsExpectedEntity = [](const FunctionDecl *FD) { |
| FunctionDecl::TemplatedKind Kind = FD->getTemplatedKind(); |
| return Kind == FunctionDecl::TK_NonTemplate || |
| Kind == FunctionDecl::TK_FunctionTemplate; |
| }; |
| const auto *FD2 = dyn_cast<FunctionDecl>(D2); |
| assert(IsExpectedEntity(FD1) && FD2 && IsExpectedEntity(FD2) && |
| "use non-instantiated function declaration for constraints partial " |
| "ordering"); |
| } |
| #endif |
| |
| if (AC1.empty()) { |
| Result = AC2.empty(); |
| return false; |
| } |
| if (AC2.empty()) { |
| // TD1 has associated constraints and TD2 does not. |
| Result = true; |
| return false; |
| } |
| |
| std::pair<const NamedDecl *, const NamedDecl *> Key{D1, D2}; |
| auto CacheEntry = SubsumptionCache.find(Key); |
| if (CacheEntry != SubsumptionCache.end()) { |
| Result = CacheEntry->second; |
| return false; |
| } |
| |
| unsigned Depth1 = CalculateTemplateDepthForConstraints(*this, D1, true); |
| unsigned Depth2 = CalculateTemplateDepthForConstraints(*this, D2, true); |
| |
| for (size_t I = 0; I != AC1.size() && I != AC2.size(); ++I) { |
| if (Depth2 > Depth1) { |
| AC1[I].ConstraintExpr = |
| AdjustConstraintDepth(*this, Depth2 - Depth1) |
| .TransformExpr(const_cast<Expr *>(AC1[I].ConstraintExpr)) |
| .get(); |
| } else if (Depth1 > Depth2) { |
| AC2[I].ConstraintExpr = |
| AdjustConstraintDepth(*this, Depth1 - Depth2) |
| .TransformExpr(const_cast<Expr *>(AC2[I].ConstraintExpr)) |
| .get(); |
| } |
| } |
| |
| SubsumptionChecker SC(*this); |
| std::optional<bool> Subsumes = SC.Subsumes(D1, AC1, D2, AC2); |
| if (!Subsumes) { |
| // Normalization failed |
| return true; |
| } |
| Result = *Subsumes; |
| SubsumptionCache.try_emplace(Key, *Subsumes); |
| return false; |
| } |
| |
| bool Sema::MaybeEmitAmbiguousAtomicConstraintsDiagnostic( |
| const NamedDecl *D1, ArrayRef<AssociatedConstraint> AC1, |
| const NamedDecl *D2, ArrayRef<AssociatedConstraint> AC2) { |
| if (isSFINAEContext()) |
| // No need to work here because our notes would be discarded. |
| return false; |
| |
| if (AC1.empty() || AC2.empty()) |
| return false; |
| |
| const Expr *AmbiguousAtomic1 = nullptr, *AmbiguousAtomic2 = nullptr; |
| auto IdenticalExprEvaluator = [&](const AtomicConstraint &A, |
| const AtomicConstraint &B) { |
| if (!A.hasMatchingParameterMapping(Context, B)) |
| return false; |
| const Expr *EA = A.getConstraintExpr(), *EB = B.getConstraintExpr(); |
| if (EA == EB) |
| return true; |
| |
| // Not the same source level expression - are the expressions |
| // identical? |
| llvm::FoldingSetNodeID IDA, IDB; |
| EA->Profile(IDA, Context, /*Canonical=*/true); |
| EB->Profile(IDB, Context, /*Canonical=*/true); |
| if (IDA != IDB) |
| return false; |
| |
| AmbiguousAtomic1 = EA; |
| AmbiguousAtomic2 = EB; |
| return true; |
| }; |
| |
| { |
| // The subsumption checks might cause diagnostics |
| SFINAETrap Trap(*this); |
| auto *Normalized1 = getNormalizedAssociatedConstraints(D1, AC1); |
| if (!Normalized1) |
| return false; |
| |
| auto *Normalized2 = getNormalizedAssociatedConstraints(D2, AC2); |
| if (!Normalized2) |
| return false; |
| |
| SubsumptionChecker SC(*this); |
| |
| bool Is1AtLeastAs2Normally = SC.Subsumes(Normalized1, Normalized2); |
| bool Is2AtLeastAs1Normally = SC.Subsumes(Normalized2, Normalized1); |
| |
| SubsumptionChecker SC2(*this, IdenticalExprEvaluator); |
| bool Is1AtLeastAs2 = SC2.Subsumes(Normalized1, Normalized2); |
| bool Is2AtLeastAs1 = SC2.Subsumes(Normalized2, Normalized1); |
| |
| if (Is1AtLeastAs2 == Is1AtLeastAs2Normally && |
| Is2AtLeastAs1 == Is2AtLeastAs1Normally) |
| // Same result - no ambiguity was caused by identical atomic expressions. |
| return false; |
| } |
| // A different result! Some ambiguous atomic constraint(s) caused a difference |
| assert(AmbiguousAtomic1 && AmbiguousAtomic2); |
| |
| Diag(AmbiguousAtomic1->getBeginLoc(), diag::note_ambiguous_atomic_constraints) |
| << AmbiguousAtomic1->getSourceRange(); |
| Diag(AmbiguousAtomic2->getBeginLoc(), |
| diag::note_ambiguous_atomic_constraints_similar_expression) |
| << AmbiguousAtomic2->getSourceRange(); |
| return true; |
| } |
| |
| // |
| // |
| // ------------------------ Subsumption ----------------------------------- |
| // |
| // |
| SubsumptionChecker::SubsumptionChecker(Sema &SemaRef, |
| SubsumptionCallable Callable) |
| : SemaRef(SemaRef), Callable(Callable), NextID(1) {} |
| |
| uint16_t SubsumptionChecker::getNewLiteralId() { |
| assert((unsigned(NextID) + 1 < std::numeric_limits<uint16_t>::max()) && |
| "too many constraints!"); |
| return NextID++; |
| } |
| |
| auto SubsumptionChecker::find(const AtomicConstraint *Ori) -> Literal { |
| auto &Elems = AtomicMap[Ori->getConstraintExpr()]; |
| // C++ [temp.constr.order] p2 |
| // - an atomic constraint A subsumes another atomic constraint B |
| // if and only if the A and B are identical [...] |
| // |
| // C++ [temp.constr.atomic] p2 |
| // Two atomic constraints are identical if they are formed from the |
| // same expression and the targets of the parameter mappings are |
| // equivalent according to the rules for expressions [...] |
| |
| // Because subsumption of atomic constraints is an identity |
| // relationship that does not require further analysis |
| // We cache the results such that if an atomic constraint literal |
| // subsumes another, their literal will be the same |
| |
| llvm::FoldingSetNodeID ID; |
| ID.AddBoolean(Ori->hasParameterMapping()); |
| if (Ori->hasParameterMapping()) { |
| const auto &Mapping = Ori->getParameterMapping(); |
| const NormalizedConstraint::OccurenceList &Indexes = |
| Ori->mappingOccurenceListForSubsumption(); |
| for (auto [Idx, TAL] : llvm::enumerate(Mapping)) { |
| if (Indexes[Idx]) |
| SemaRef.getASTContext() |
| .getCanonicalTemplateArgument(TAL.getArgument()) |
| .Profile(ID, SemaRef.getASTContext()); |
| } |
| } |
| auto It = Elems.find(ID); |
| if (It == Elems.end()) { |
| It = Elems |
| .insert({ID, |
| MappedAtomicConstraint{ |
| Ori, {getNewLiteralId(), Literal::Atomic}}}) |
| .first; |
| ReverseMap[It->second.ID.Value] = Ori; |
| } |
| return It->getSecond().ID; |
| } |
| |
| auto SubsumptionChecker::find(const FoldExpandedConstraint *Ori) -> Literal { |
| auto &Elems = FoldMap[Ori->getPattern()]; |
| |
| FoldExpendedConstraintKey K; |
| K.Kind = Ori->getFoldOperator(); |
| |
| auto It = llvm::find_if(Elems, [&K](const FoldExpendedConstraintKey &Other) { |
| return K.Kind == Other.Kind; |
| }); |
| if (It == Elems.end()) { |
| K.ID = {getNewLiteralId(), Literal::FoldExpanded}; |
| It = Elems.insert(Elems.end(), std::move(K)); |
| ReverseMap[It->ID.Value] = Ori; |
| } |
| return It->ID; |
| } |
| |
| auto SubsumptionChecker::CNF(const NormalizedConstraint &C) -> CNFFormula { |
| return SubsumptionChecker::Normalize<CNFFormula>(C); |
| } |
| auto SubsumptionChecker::DNF(const NormalizedConstraint &C) -> DNFFormula { |
| return SubsumptionChecker::Normalize<DNFFormula>(C); |
| } |
| |
| /// |
| /// \brief SubsumptionChecker::Normalize |
| /// |
| /// Normalize a formula to Conjunctive Normal Form or |
| /// Disjunctive normal form. |
| /// |
| /// Each Atomic (and Fold Expanded) constraint gets represented by |
| /// a single id to reduce space. |
| /// |
| /// To minimize risks of exponential blow up, if two atomic |
| /// constraints subsumes each other (same constraint and mapping), |
| /// they are represented by the same literal. |
| /// |
| template <typename FormulaType> |
| FormulaType SubsumptionChecker::Normalize(const NormalizedConstraint &NC) { |
| FormulaType Res; |
| |
| auto Add = [&, this](Clause C) { |
| // Sort each clause and remove duplicates for faster comparisons. |
| llvm::sort(C); |
| C.erase(llvm::unique(C), C.end()); |
| AddUniqueClauseToFormula(Res, std::move(C)); |
| }; |
| |
| switch (NC.getKind()) { |
| case NormalizedConstraint::ConstraintKind::Atomic: |
| return {{find(&static_cast<const AtomicConstraint &>(NC))}}; |
| |
| case NormalizedConstraint::ConstraintKind::FoldExpanded: |
| return {{find(&static_cast<const FoldExpandedConstraint &>(NC))}}; |
| |
| case NormalizedConstraint::ConstraintKind::ConceptId: |
| return Normalize<FormulaType>( |
| static_cast<const ConceptIdConstraint &>(NC).getNormalizedConstraint()); |
| |
| case NormalizedConstraint::ConstraintKind::Compound: { |
| const auto &Compound = static_cast<const CompoundConstraint &>(NC); |
| FormulaType Left, Right; |
| SemaRef.runWithSufficientStackSpace(SourceLocation(), [&] { |
| Left = Normalize<FormulaType>(Compound.getLHS()); |
| Right = Normalize<FormulaType>(Compound.getRHS()); |
| }); |
| |
| if (Compound.getCompoundKind() == FormulaType::Kind) { |
| Res = std::move(Left); |
| Res.reserve(Left.size() + Right.size()); |
| std::for_each(std::make_move_iterator(Right.begin()), |
| std::make_move_iterator(Right.end()), Add); |
| return Res; |
| } |
| |
| Res.reserve(Left.size() * Right.size()); |
| for (const auto <ransform : Left) { |
| for (const auto &RTransform : Right) { |
| Clause Combined; |
| Combined.reserve(LTransform.size() + RTransform.size()); |
| llvm::copy(LTransform, std::back_inserter(Combined)); |
| llvm::copy(RTransform, std::back_inserter(Combined)); |
| Add(std::move(Combined)); |
| } |
| } |
| return Res; |
| } |
| } |
| llvm_unreachable("Unknown ConstraintKind enum"); |
| } |
| |
| void SubsumptionChecker::AddUniqueClauseToFormula(Formula &F, Clause C) { |
| for (auto &Other : F) { |
| if (llvm::equal(C, Other)) |
| return; |
| } |
| F.push_back(C); |
| } |
| |
| std::optional<bool> SubsumptionChecker::Subsumes( |
| const NamedDecl *DP, ArrayRef<AssociatedConstraint> P, const NamedDecl *DQ, |
| ArrayRef<AssociatedConstraint> Q) { |
| const NormalizedConstraint *PNormalized = |
| SemaRef.getNormalizedAssociatedConstraints(DP, P); |
| if (!PNormalized) |
| return std::nullopt; |
| |
| const NormalizedConstraint *QNormalized = |
| SemaRef.getNormalizedAssociatedConstraints(DQ, Q); |
| if (!QNormalized) |
| return std::nullopt; |
| |
| return Subsumes(PNormalized, QNormalized); |
| } |
| |
| bool SubsumptionChecker::Subsumes(const NormalizedConstraint *P, |
| const NormalizedConstraint *Q) { |
| |
| DNFFormula DNFP = DNF(*P); |
| CNFFormula CNFQ = CNF(*Q); |
| return Subsumes(DNFP, CNFQ); |
| } |
| |
| bool SubsumptionChecker::Subsumes(const DNFFormula &PDNF, |
| const CNFFormula &QCNF) { |
| 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. |
| if (!DNFSubsumes(Pi, Qj)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool SubsumptionChecker::DNFSubsumes(const Clause &P, const Clause &Q) { |
| |
| return llvm::any_of(P, [&](Literal LP) { |
| return llvm::any_of(Q, [this, LP](Literal LQ) { return Subsumes(LP, LQ); }); |
| }); |
| } |
| |
| bool SubsumptionChecker::Subsumes(const FoldExpandedConstraint *A, |
| const FoldExpandedConstraint *B) { |
| std::pair<const FoldExpandedConstraint *, const FoldExpandedConstraint *> Key{ |
| A, B}; |
| |
| auto It = FoldSubsumptionCache.find(Key); |
| if (It == FoldSubsumptionCache.end()) { |
| // C++ [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. |
| bool DoesSubsume = |
| A->getFoldOperator() == B->getFoldOperator() && |
| FoldExpandedConstraint::AreCompatibleForSubsumption(*A, *B) && |
| Subsumes(&A->getNormalizedPattern(), &B->getNormalizedPattern()); |
| It = FoldSubsumptionCache.try_emplace(std::move(Key), DoesSubsume).first; |
| } |
| return It->second; |
| } |
| |
| bool SubsumptionChecker::Subsumes(Literal A, Literal B) { |
| if (A.Kind != B.Kind) |
| return false; |
| switch (A.Kind) { |
| case Literal::Atomic: |
| if (!Callable) |
| return A.Value == B.Value; |
| return Callable( |
| *static_cast<const AtomicConstraint *>(ReverseMap[A.Value]), |
| *static_cast<const AtomicConstraint *>(ReverseMap[B.Value])); |
| case Literal::FoldExpanded: |
| return Subsumes( |
| static_cast<const FoldExpandedConstraint *>(ReverseMap[A.Value]), |
| static_cast<const FoldExpandedConstraint *>(ReverseMap[B.Value])); |
| } |
| llvm_unreachable("unknown literal kind"); |
| } |