summaryrefslogtreecommitdiffstats
path: root/clang/lib/Sema/SemaConcept.cpp
diff options
context:
space:
mode:
authorSaar Raz <saar@raz.email>2019-12-18 20:59:01 +0200
committerSaar Raz <saar@raz.email>2019-12-18 21:01:31 +0200
commitfc0731b98a67c793862288f8ae334322666214dc (patch)
tree6b40608a727d7ddd36cf7afa6ac657f56804a2b1 /clang/lib/Sema/SemaConcept.cpp
parent406b6019cd2bd50924be11c634b058c01053fbd3 (diff)
downloadbcm5719-llvm-fc0731b98a67c793862288f8ae334322666214dc.tar.gz
bcm5719-llvm-fc0731b98a67c793862288f8ae334322666214dc.zip
[Concepts] Constrained partial specializations and function overloads.
Added support for constraint satisfaction checking and partial ordering of constraints in constrained partial specialization and function template overloads. Phabricator: D41910
Diffstat (limited to 'clang/lib/Sema/SemaConcept.cpp')
-rwxr-xr-x[-rw-r--r--]clang/lib/Sema/SemaConcept.cpp360
1 files changed, 360 insertions, 0 deletions
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index f917d9c7e5a..cd41000fa02 100644..100755
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -17,6 +17,7 @@
#include "clang/Sema/TemplateDeduction.h"
#include "clang/Sema/Template.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/AST/RecursiveASTVisitor.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PointerUnion.h"
using namespace clang;
@@ -414,4 +415,363 @@ void Sema::DiagnoseUnsatisfiedConstraint(
diagnoseUnsatisfiedConstraintExpr(*this, Pair.first, Pair.second, First);
First = false;
}
+}
+
+namespace {
+struct AtomicConstraint {
+ const Expr *ConstraintExpr;
+ llvm::Optional<llvm::SmallVector<TemplateArgumentLoc, 3>> ParameterMapping;
+
+ AtomicConstraint(Sema &S, const Expr *ConstraintExpr) :
+ ConstraintExpr(ConstraintExpr) { };
+
+ bool hasMatchingParameterMapping(ASTContext &C,
+ const AtomicConstraint &Other) const {
+ if (!ParameterMapping != !Other.ParameterMapping)
+ return false;
+ if (!ParameterMapping)
+ return true;
+ if (ParameterMapping->size() != Other.ParameterMapping->size())
+ return false;
+
+ for (unsigned I = 0, S = ParameterMapping->size(); I < S; ++I)
+ if (!C.getCanonicalTemplateArgument((*ParameterMapping)[I].getArgument())
+ .structurallyEquals(C.getCanonicalTemplateArgument(
+ (*Other.ParameterMapping)[I].getArgument())))
+ return false;
+ return true;
+ }
+
+ bool subsumes(ASTContext &C, const AtomicConstraint &Other) const {
+ // 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 [...]
+
+ // We do not actually substitute the parameter mappings into the
+ // constraint expressions, therefore the constraint expressions are
+ // the originals, and comparing them will suffice.
+ if (ConstraintExpr != Other.ConstraintExpr)
+ return false;
+
+ // Check that the parameter lists are identical
+ return hasMatchingParameterMapping(C, Other);
+ }
+};
+
+/// \brief A normalized constraint, as defined in C++ [temp.constr.normal], is
+/// either an atomic constraint, a conjunction of normalized constraints or a
+/// disjunction of normalized constraints.
+struct NormalizedConstraint {
+ enum CompoundConstraintKind { CCK_Conjunction, CCK_Disjunction };
+
+ using CompoundConstraint = llvm::PointerIntPair<
+ std::pair<NormalizedConstraint, NormalizedConstraint> *, 1,
+ CompoundConstraintKind>;
+
+ llvm::PointerUnion<AtomicConstraint *, CompoundConstraint> Constraint;
+
+ NormalizedConstraint(AtomicConstraint *C): Constraint{C} { };
+ NormalizedConstraint(ASTContext &C, NormalizedConstraint LHS,
+ NormalizedConstraint RHS, CompoundConstraintKind Kind)
+ : Constraint{CompoundConstraint{
+ new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{LHS,
+ RHS},
+ Kind}} { };
+
+ CompoundConstraintKind getCompoundKind() const {
+ assert(!isAtomic() && "getCompoundKind called on atomic constraint.");
+ return Constraint.get<CompoundConstraint>().getInt();
+ }
+
+ bool isAtomic() const { return Constraint.is<AtomicConstraint *>(); }
+
+ NormalizedConstraint &getLHS() const {
+ assert(!isAtomic() && "getLHS called on atomic constraint.");
+ return Constraint.get<CompoundConstraint>().getPointer()->first;
+ }
+
+ NormalizedConstraint &getRHS() const {
+ assert(!isAtomic() && "getRHS called on atomic constraint.");
+ return Constraint.get<CompoundConstraint>().getPointer()->second;
+ }
+
+ AtomicConstraint *getAtomicConstraint() const {
+ assert(isAtomic() &&
+ "getAtomicConstraint called on non-atomic constraint.");
+ return Constraint.get<AtomicConstraint *>();
+ }
+
+ static llvm::Optional<NormalizedConstraint>
+ fromConstraintExprs(Sema &S, NamedDecl *D, ArrayRef<const Expr *> E) {
+ assert(E.size() != 0);
+ auto First = fromConstraintExpr(S, D, E[0]);
+ if (E.size() == 1)
+ return First;
+ auto Second = fromConstraintExpr(S, D, E[1]);
+ if (!Second)
+ return llvm::Optional<NormalizedConstraint>{};
+ llvm::Optional<NormalizedConstraint> Conjunction;
+ Conjunction.emplace(S.Context, std::move(*First), std::move(*Second),
+ CCK_Conjunction);
+ for (unsigned I = 2; I < E.size(); ++I) {
+ auto Next = fromConstraintExpr(S, D, E[I]);
+ if (!Next)
+ return llvm::Optional<NormalizedConstraint>{};
+ NormalizedConstraint NewConjunction(S.Context, std::move(*Conjunction),
+ std::move(*Next), CCK_Conjunction);
+ *Conjunction = std::move(NewConjunction);
+ }
+ return Conjunction;
+ }
+
+private:
+ static llvm::Optional<NormalizedConstraint> fromConstraintExpr(Sema &S,
+ NamedDecl *D,
+ const Expr *E);
+};
+
+static bool substituteParameterMappings(Sema &S, NormalizedConstraint &N,
+ ConceptDecl *Concept, ArrayRef<TemplateArgument> TemplateArgs,
+ const ASTTemplateArgumentListInfo *ArgsAsWritten) {
+ if (!N.isAtomic()) {
+ if (substituteParameterMappings(S, N.getLHS(), Concept, TemplateArgs,
+ ArgsAsWritten))
+ return true;
+ return substituteParameterMappings(S, N.getRHS(), Concept, TemplateArgs,
+ ArgsAsWritten);
+ }
+ TemplateParameterList *TemplateParams = Concept->getTemplateParameters();
+
+ AtomicConstraint &Atomic = *N.getAtomicConstraint();
+ TemplateArgumentListInfo SubstArgs;
+ MultiLevelTemplateArgumentList MLTAL;
+ MLTAL.addOuterTemplateArguments(TemplateArgs);
+ if (!Atomic.ParameterMapping) {
+ llvm::SmallBitVector OccurringIndices;
+ S.MarkUsedTemplateParameters(Atomic.ConstraintExpr, /*OnlyDeduced=*/false,
+ /*Depth=*/0, OccurringIndices);
+ Atomic.ParameterMapping.emplace();
+ Atomic.ParameterMapping->reserve(OccurringIndices.size());
+ for (unsigned I = 0, C = TemplateParams->size(); I != C; ++I)
+ if (OccurringIndices[I])
+ Atomic.ParameterMapping->push_back(
+ S.getIdentityTemplateArgumentLoc(TemplateParams->begin()[I],
+ // Here we assume we do not support things like
+ // template<typename A, typename B>
+ // concept C = ...;
+ //
+ // template<typename... Ts> requires C<Ts...>
+ // struct S { };
+ // The above currently yields a diagnostic.
+ // We still might have default arguments for concept parameters.
+ ArgsAsWritten->NumTemplateArgs > I ?
+ ArgsAsWritten->arguments()[I].getLocation() :
+ SourceLocation()));
+ }
+ Sema::InstantiatingTemplate Inst(
+ S, ArgsAsWritten->arguments().front().getSourceRange().getBegin(),
+ Sema::InstantiatingTemplate::ParameterMappingSubstitution{}, Concept,
+ SourceRange(ArgsAsWritten->arguments()[0].getSourceRange().getBegin(),
+ ArgsAsWritten->arguments().back().getSourceRange().getEnd()));
+ if (S.SubstTemplateArguments(*Atomic.ParameterMapping, MLTAL, SubstArgs))
+ return true;
+ std::copy(SubstArgs.arguments().begin(), SubstArgs.arguments().end(),
+ N.getAtomicConstraint()->ParameterMapping->begin());
+ return false;
+}
+
+llvm::Optional<NormalizedConstraint>
+NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
+ 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();
+ if (auto *BO = dyn_cast<const BinaryOperator>(E)) {
+ if (BO->getOpcode() == BO_LAnd || BO->getOpcode() == BO_LOr) {
+ auto LHS = fromConstraintExpr(S, D, BO->getLHS());
+ if (!LHS)
+ return None;
+ auto RHS = fromConstraintExpr(S, D, BO->getRHS());
+ if (!RHS)
+ return None;
+
+ return NormalizedConstraint(
+ S.Context, *LHS, *RHS,
+ BO->getOpcode() == BO_LAnd ? CCK_Conjunction : CCK_Disjunction);
+ }
+ } else if (auto *CSE = dyn_cast<const ConceptSpecializationExpr>(E)) {
+ Optional<NormalizedConstraint> SubNF;
+ {
+ Sema::InstantiatingTemplate Inst(
+ S, CSE->getExprLoc(),
+ Sema::InstantiatingTemplate::ConstraintNormalization{}, D,
+ CSE->getSourceRange());
+ // 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.
+ // [...]
+ SubNF = fromConstraintExpr(S, CSE->getNamedConcept(),
+ CSE->getNamedConcept()->getConstraintExpr());
+ if (!SubNF)
+ return None;
+ }
+
+ if (substituteParameterMappings(
+ S, *SubNF, CSE->getNamedConcept(),
+ CSE->getTemplateArguments(), CSE->getTemplateArgsAsWritten()))
+ return None;
+
+ return SubNF;
+ }
+ return NormalizedConstraint{new (S.Context) AtomicConstraint(S, E)};
+}
+
+} // namespace
+
+using NormalForm =
+ llvm::SmallVector<llvm::SmallVector<AtomicConstraint *, 2>, 4>;
+
+static NormalForm makeCNF(const NormalizedConstraint &Normalized) {
+ if (Normalized.isAtomic())
+ return {{Normalized.getAtomicConstraint()}};
+
+ NormalForm LCNF = makeCNF(Normalized.getLHS());
+ NormalForm RCNF = makeCNF(Normalized.getRHS());
+ if (Normalized.getCompoundKind() == NormalizedConstraint::CCK_Conjunction) {
+ LCNF.reserve(LCNF.size() + RCNF.size());
+ while (!RCNF.empty())
+ LCNF.push_back(std::move(RCNF.pop_back_val()));
+ return LCNF;
+ }
+
+ // Disjunction
+ NormalForm Res;
+ Res.reserve(LCNF.size() * RCNF.size());
+ for (auto &LDisjunction : LCNF)
+ for (auto &RDisjunction : RCNF) {
+ NormalForm::value_type Combined;
+ Combined.reserve(LDisjunction.size() + RDisjunction.size());
+ std::copy(LDisjunction.begin(), LDisjunction.end(),
+ std::back_inserter(Combined));
+ std::copy(RDisjunction.begin(), RDisjunction.end(),
+ std::back_inserter(Combined));
+ Res.emplace_back(Combined);
+ }
+ return Res;
+}
+
+static NormalForm makeDNF(const NormalizedConstraint &Normalized) {
+ if (Normalized.isAtomic())
+ return {{Normalized.getAtomicConstraint()}};
+
+ NormalForm LDNF = makeDNF(Normalized.getLHS());
+ NormalForm RDNF = makeDNF(Normalized.getRHS());
+ if (Normalized.getCompoundKind() == NormalizedConstraint::CCK_Disjunction) {
+ LDNF.reserve(LDNF.size() + RDNF.size());
+ while (!RDNF.empty())
+ LDNF.push_back(std::move(RDNF.pop_back_val()));
+ return LDNF;
+ }
+
+ // Conjunction
+ NormalForm Res;
+ Res.reserve(LDNF.size() * RDNF.size());
+ for (auto &LConjunction : LDNF) {
+ for (auto &RConjunction : RDNF) {
+ NormalForm::value_type Combined;
+ Combined.reserve(LConjunction.size() + RConjunction.size());
+ std::copy(LConjunction.begin(), LConjunction.end(),
+ std::back_inserter(Combined));
+ std::copy(RConjunction.begin(), RConjunction.end(),
+ std::back_inserter(Combined));
+ Res.emplace_back(Combined);
+ }
+ }
+ return Res;
+}
+
+static bool subsumes(Sema &S, NamedDecl *DP, ArrayRef<const Expr *> P,
+ NamedDecl *DQ, ArrayRef<const Expr *> Q, bool &Subsumes) {
+ // C++ [temp.constr.order] p2
+ // In order to determine if a constraint P subsumes a constraint Q, P is
+ // transformed into disjunctive normal form, and Q is transformed into
+ // conjunctive normal form. [...]
+ auto PNormalized = NormalizedConstraint::fromConstraintExprs(S, DP, P);
+ if (!PNormalized)
+ return true;
+ const NormalForm PDNF = makeDNF(*PNormalized);
+
+ auto QNormalized = NormalizedConstraint::fromConstraintExprs(S, DQ, Q);
+ if (!QNormalized)
+ return true;
+ const NormalForm QCNF = makeCNF(*QNormalized);
+
+ // C++ [temp.constr.order] p2
+ // Then, P subsumes Q if and only if, for every disjunctive clause Pi in the
+ // disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in
+ // the conjuctive normal form of Q, where [...]
+ for (const auto &Pi : PDNF) {
+ for (const auto &Qj : QCNF) {
+ // C++ [temp.constr.order] p2
+ // - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if
+ // and only if there exists an atomic constraint Pia in Pi for which
+ // there exists an atomic constraint, Qjb, in Qj such that Pia
+ // subsumes Qjb.
+ bool Found = false;
+ for (const AtomicConstraint *Pia : Pi) {
+ for (const AtomicConstraint *Qjb : Qj) {
+ if (Pia->subsumes(S.Context, *Qjb)) {
+ Found = true;
+ break;
+ }
+ }
+ if (Found)
+ break;
+ }
+ if (!Found) {
+ Subsumes = false;
+ return false;
+ }
+ }
+ }
+ Subsumes = true;
+ return false;
+}
+
+bool Sema::IsAtLeastAsConstrained(NamedDecl *D1, ArrayRef<const Expr *> AC1,
+ NamedDecl *D2, ArrayRef<const Expr *> AC2,
+ bool &Result) {
+ 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<NamedDecl *, NamedDecl *> Key{D1, D2};
+ auto CacheEntry = SubsumptionCache.find(Key);
+ if (CacheEntry != SubsumptionCache.end()) {
+ Result = CacheEntry->second;
+ return false;
+ }
+ if (subsumes(*this, D1, AC1, D2, AC2, Result))
+ return true;
+ SubsumptionCache.try_emplace(Key, Result);
+ return false;
} \ No newline at end of file
OpenPOWER on IntegriCloud