Skip to content

Reapply [Clang][C++26] Implement "Ordering of constraints involving fold expressions #99022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ C++2c Feature Support

- Implemented `P3144R2 Deleting a Pointer to an Incomplete Type Should be Ill-formed <https://wg21.link/P3144R2>`_.

- Implemented `P2963R3 Ordering of constraints involving fold expressions <https://wg21.link/P2963R3>`_.


Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Substitute template parameter pack, when it is not explicitly specified
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -14078,6 +14078,11 @@ class Sema final : public SemaBase {
const DeclarationNameInfo &NameInfo,
SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);

/// Collect the set of unexpanded parameter packs within the given
/// expression.
static void collectUnexpandedParameterPacks(
Expr *E, SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);

/// Invoked when parsing a template argument followed by an
/// ellipsis, which creates a pack expansion.
///
Expand Down
193 changes: 160 additions & 33 deletions clang/include/clang/Sema/SemaConcept.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
namespace clang {
class Sema;

struct AtomicConstraint {
enum { ConstraintAlignment = 8 };

struct alignas(ConstraintAlignment) AtomicConstraint {
const Expr *ConstraintExpr;
std::optional<ArrayRef<TemplateArgumentLoc>> ParameterMapping;

Expand Down Expand Up @@ -75,6 +77,28 @@ struct AtomicConstraint {
}
};

struct alignas(ConstraintAlignment) FoldExpandedConstraint;

using NormalFormConstraint =
llvm::PointerUnion<AtomicConstraint *, FoldExpandedConstraint *>;
struct NormalizedConstraint;
using NormalForm =
llvm::SmallVector<llvm::SmallVector<NormalFormConstraint, 2>, 4>;

// A constraint is in conjunctive normal form when it is a conjunction of
// clauses where each clause is a disjunction of atomic constraints. For atomic
// constraints A, B, and C, the constraint A  ∧ (B  ∨ C) is in conjunctive
// normal form.
NormalForm makeCNF(const NormalizedConstraint &Normalized);

// A constraint is in disjunctive normal form when it is a disjunction of
// clauses where each clause is a conjunction of atomic constraints. For atomic
// constraints A, B, and C, the disjunctive normal form of the constraint A
//  ∧ (B  ∨ C) is (A  ∧ B)  ∨ (A  ∧ C).
NormalForm makeDNF(const NormalizedConstraint &Normalized);

struct alignas(ConstraintAlignment) NormalizedConstraintPair;

/// \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.
Expand All @@ -83,30 +107,20 @@ struct NormalizedConstraint {

enum CompoundConstraintKind { CCK_Conjunction, CCK_Disjunction };

using CompoundConstraint = llvm::PointerIntPair<
std::pair<NormalizedConstraint, NormalizedConstraint> *, 1,
CompoundConstraintKind>;
using CompoundConstraint = llvm::PointerIntPair<NormalizedConstraintPair *, 1,
CompoundConstraintKind>;

llvm::PointerUnion<AtomicConstraint *, CompoundConstraint> Constraint;
llvm::PointerUnion<AtomicConstraint *, FoldExpandedConstraint *,
CompoundConstraint>
Constraint;

NormalizedConstraint(AtomicConstraint *C): Constraint{C} { };
NormalizedConstraint(FoldExpandedConstraint *C) : Constraint{C} {};

NormalizedConstraint(ASTContext &C, NormalizedConstraint LHS,
NormalizedConstraint RHS, CompoundConstraintKind Kind)
: Constraint{CompoundConstraint{
new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
std::move(LHS), std::move(RHS)}, Kind}} { };

NormalizedConstraint(ASTContext &C, const NormalizedConstraint &Other) {
if (Other.isAtomic()) {
Constraint = new (C) AtomicConstraint(*Other.getAtomicConstraint());
} else {
Constraint = CompoundConstraint(
new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
NormalizedConstraint(C, Other.getLHS()),
NormalizedConstraint(C, Other.getRHS())},
Other.getCompoundKind());
}
}
NormalizedConstraint RHS, CompoundConstraintKind Kind);

NormalizedConstraint(ASTContext &C, const NormalizedConstraint &Other);
NormalizedConstraint(NormalizedConstraint &&Other):
Constraint(Other.Constraint) {
Other.Constraint = nullptr;
Expand All @@ -120,36 +134,149 @@ struct NormalizedConstraint {
return *this;
}

CompoundConstraintKind getCompoundKind() const {
assert(!isAtomic() && "getCompoundKind called on atomic constraint.");
return Constraint.get<CompoundConstraint>().getInt();
}

bool isAtomic() const { return Constraint.is<AtomicConstraint *>(); }

NormalizedConstraint &getLHS() const {
assert(!isAtomic() && "getLHS called on atomic constraint.");
return Constraint.get<CompoundConstraint>().getPointer()->first;
bool isFoldExpanded() const {
return Constraint.is<FoldExpandedConstraint *>();
}
bool isCompound() const { return Constraint.is<CompoundConstraint>(); }

NormalizedConstraint &getRHS() const {
assert(!isAtomic() && "getRHS called on atomic constraint.");
return Constraint.get<CompoundConstraint>().getPointer()->second;
CompoundConstraintKind getCompoundKind() const {
assert(isCompound() && "getCompoundKind on a non-compound constraint..");
return Constraint.get<CompoundConstraint>().getInt();
}

NormalizedConstraint &getLHS() const;
NormalizedConstraint &getRHS() const;

AtomicConstraint *getAtomicConstraint() const {
assert(isAtomic() &&
"getAtomicConstraint called on non-atomic constraint.");
return Constraint.get<AtomicConstraint *>();
}

FoldExpandedConstraint *getFoldExpandedConstraint() const {
assert(isFoldExpanded() &&
"getFoldExpandedConstraint called on non-fold-expanded constraint.");
return Constraint.get<FoldExpandedConstraint *>();
}

private:
static std::optional<NormalizedConstraint>
fromConstraintExprs(Sema &S, NamedDecl *D, ArrayRef<const Expr *> E);
static std::optional<NormalizedConstraint>
fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E);
};

struct alignas(ConstraintAlignment) NormalizedConstraintPair {
NormalizedConstraint LHS, RHS;
};

struct alignas(ConstraintAlignment) FoldExpandedConstraint {
enum class FoldOperatorKind { And, Or } Kind;
NormalizedConstraint Constraint;
const Expr *Pattern;

FoldExpandedConstraint(FoldOperatorKind K, NormalizedConstraint C,
const Expr *Pattern)
: Kind(K), Constraint(std::move(C)), Pattern(Pattern) {};

template <typename AtomicSubsumptionEvaluator>
bool subsumes(const FoldExpandedConstraint &Other,
const AtomicSubsumptionEvaluator &E) const;

static bool AreCompatibleForSubsumption(const FoldExpandedConstraint &A,
const FoldExpandedConstraint &B);
};

const NormalizedConstraint *getNormalizedAssociatedConstraints(
Sema &S, NamedDecl *ConstrainedDecl,
ArrayRef<const Expr *> AssociatedConstraints);

template <typename AtomicSubsumptionEvaluator>
bool subsumes(const NormalForm &PDNF, const NormalForm &QCNF,
const AtomicSubsumptionEvaluator &E) {
// C++ [temp.constr.order] p2
// Then, P subsumes Q if and only if, for every disjunctive clause Pi in the
// disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in
// the conjuctive normal form of Q, where [...]
for (const auto &Pi : PDNF) {
for (const auto &Qj : QCNF) {
// C++ [temp.constr.order] p2
// - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if
// and only if there exists an atomic constraint Pia in Pi for which
// there exists an atomic constraint, Qjb, in Qj such that Pia
// subsumes Qjb.
bool Found = false;
for (NormalFormConstraint Pia : Pi) {
for (NormalFormConstraint Qjb : Qj) {
if (Pia.is<FoldExpandedConstraint *>() &&
Qjb.is<FoldExpandedConstraint *>()) {
if (Pia.get<FoldExpandedConstraint *>()->subsumes(
*Qjb.get<FoldExpandedConstraint *>(), E)) {
Found = true;
break;
}
} else if (Pia.is<AtomicConstraint *>() &&
Qjb.is<AtomicConstraint *>()) {
if (E(*Pia.get<AtomicConstraint *>(),
*Qjb.get<AtomicConstraint *>())) {
Found = true;
break;
}
}
}
if (Found)
break;
}
if (!Found)
return false;
}
}
return true;
}

template <typename AtomicSubsumptionEvaluator>
bool subsumes(Sema &S, NamedDecl *DP, ArrayRef<const Expr *> P, NamedDecl *DQ,
ArrayRef<const Expr *> Q, bool &Subsumes,
const AtomicSubsumptionEvaluator &E) {
// C++ [temp.constr.order] p2
// In order to determine if a constraint P subsumes a constraint Q, P is
// transformed into disjunctive normal form, and Q is transformed into
// conjunctive normal form. [...]
const NormalizedConstraint *PNormalized =
getNormalizedAssociatedConstraints(S, DP, P);
if (!PNormalized)
return true;
NormalForm PDNF = makeDNF(*PNormalized);

const NormalizedConstraint *QNormalized =
getNormalizedAssociatedConstraints(S, DQ, Q);
if (!QNormalized)
return true;
NormalForm QCNF = makeCNF(*QNormalized);

Subsumes = subsumes(PDNF, QCNF, E);
return false;
}

template <typename AtomicSubsumptionEvaluator>
bool FoldExpandedConstraint::subsumes(
const FoldExpandedConstraint &Other,
const AtomicSubsumptionEvaluator &E) const {

// [C++26] [temp.constr.order]
// a fold expanded constraint A subsumes another fold expanded constraint B if
// they are compatible for subsumption, have the same fold-operator, and the
// constraint of A subsumes that of B

if (Kind != Other.Kind || !AreCompatibleForSubsumption(*this, Other))
return false;

NormalForm PDNF = makeDNF(this->Constraint);
NormalForm QCNF = makeCNF(Other.Constraint);
return clang::subsumes(PDNF, QCNF, E);
}

} // clang

#endif // LLVM_CLANG_SEMA_SEMACONCEPT_H
Loading