Skip to content

Commit b330d80

Browse files
authored
Reapply [Clang][C++26] Implement "Ordering of constraints involving fold expressions (#99022)
Implement https://isocpp.org/files/papers/P2963R3.pdf
1 parent e316f19 commit b330d80

File tree

7 files changed

+862
-234
lines changed

7 files changed

+862
-234
lines changed

clang/docs/ReleaseNotes.rst

+3
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ C++2c Feature Support
276276

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

279+
- Implemented `P2963R3 Ordering of constraints involving fold expressions <https://wg21.link/P2963R3>`_.
280+
281+
279282
Resolutions to C++ Defect Reports
280283
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
281284
- Substitute template parameter pack, when it is not explicitly specified

clang/include/clang/Sema/Sema.h

+5
Original file line numberDiff line numberDiff line change
@@ -14078,6 +14078,11 @@ class Sema final : public SemaBase {
1407814078
const DeclarationNameInfo &NameInfo,
1407914079
SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
1408014080

14081+
/// Collect the set of unexpanded parameter packs within the given
14082+
/// expression.
14083+
static void collectUnexpandedParameterPacks(
14084+
Expr *E, SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
14085+
1408114086
/// Invoked when parsing a template argument followed by an
1408214087
/// ellipsis, which creates a pack expansion.
1408314088
///

clang/include/clang/Sema/SemaConcept.h

+160-33
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
namespace clang {
2727
class Sema;
2828

29-
struct AtomicConstraint {
29+
enum { ConstraintAlignment = 8 };
30+
31+
struct alignas(ConstraintAlignment) AtomicConstraint {
3032
const Expr *ConstraintExpr;
3133
std::optional<ArrayRef<TemplateArgumentLoc>> ParameterMapping;
3234

@@ -75,6 +77,28 @@ struct AtomicConstraint {
7577
}
7678
};
7779

80+
struct alignas(ConstraintAlignment) FoldExpandedConstraint;
81+
82+
using NormalFormConstraint =
83+
llvm::PointerUnion<AtomicConstraint *, FoldExpandedConstraint *>;
84+
struct NormalizedConstraint;
85+
using NormalForm =
86+
llvm::SmallVector<llvm::SmallVector<NormalFormConstraint, 2>, 4>;
87+
88+
// A constraint is in conjunctive normal form when it is a conjunction of
89+
// clauses where each clause is a disjunction of atomic constraints. For atomic
90+
// constraints A, B, and C, the constraint A  ∧ (B  ∨ C) is in conjunctive
91+
// normal form.
92+
NormalForm makeCNF(const NormalizedConstraint &Normalized);
93+
94+
// A constraint is in disjunctive normal form when it is a disjunction of
95+
// clauses where each clause is a conjunction of atomic constraints. For atomic
96+
// constraints A, B, and C, the disjunctive normal form of the constraint A
97+
//  ∧ (B  ∨ C) is (A  ∧ B)  ∨ (A  ∧ C).
98+
NormalForm makeDNF(const NormalizedConstraint &Normalized);
99+
100+
struct alignas(ConstraintAlignment) NormalizedConstraintPair;
101+
78102
/// \brief A normalized constraint, as defined in C++ [temp.constr.normal], is
79103
/// either an atomic constraint, a conjunction of normalized constraints or a
80104
/// disjunction of normalized constraints.
@@ -83,30 +107,20 @@ struct NormalizedConstraint {
83107

84108
enum CompoundConstraintKind { CCK_Conjunction, CCK_Disjunction };
85109

86-
using CompoundConstraint = llvm::PointerIntPair<
87-
std::pair<NormalizedConstraint, NormalizedConstraint> *, 1,
88-
CompoundConstraintKind>;
110+
using CompoundConstraint = llvm::PointerIntPair<NormalizedConstraintPair *, 1,
111+
CompoundConstraintKind>;
89112

90-
llvm::PointerUnion<AtomicConstraint *, CompoundConstraint> Constraint;
113+
llvm::PointerUnion<AtomicConstraint *, FoldExpandedConstraint *,
114+
CompoundConstraint>
115+
Constraint;
91116

92117
NormalizedConstraint(AtomicConstraint *C): Constraint{C} { };
118+
NormalizedConstraint(FoldExpandedConstraint *C) : Constraint{C} {};
119+
93120
NormalizedConstraint(ASTContext &C, NormalizedConstraint LHS,
94-
NormalizedConstraint RHS, CompoundConstraintKind Kind)
95-
: Constraint{CompoundConstraint{
96-
new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
97-
std::move(LHS), std::move(RHS)}, Kind}} { };
98-
99-
NormalizedConstraint(ASTContext &C, const NormalizedConstraint &Other) {
100-
if (Other.isAtomic()) {
101-
Constraint = new (C) AtomicConstraint(*Other.getAtomicConstraint());
102-
} else {
103-
Constraint = CompoundConstraint(
104-
new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
105-
NormalizedConstraint(C, Other.getLHS()),
106-
NormalizedConstraint(C, Other.getRHS())},
107-
Other.getCompoundKind());
108-
}
109-
}
121+
NormalizedConstraint RHS, CompoundConstraintKind Kind);
122+
123+
NormalizedConstraint(ASTContext &C, const NormalizedConstraint &Other);
110124
NormalizedConstraint(NormalizedConstraint &&Other):
111125
Constraint(Other.Constraint) {
112126
Other.Constraint = nullptr;
@@ -120,36 +134,149 @@ struct NormalizedConstraint {
120134
return *this;
121135
}
122136

123-
CompoundConstraintKind getCompoundKind() const {
124-
assert(!isAtomic() && "getCompoundKind called on atomic constraint.");
125-
return Constraint.get<CompoundConstraint>().getInt();
126-
}
127-
128137
bool isAtomic() const { return Constraint.is<AtomicConstraint *>(); }
129-
130-
NormalizedConstraint &getLHS() const {
131-
assert(!isAtomic() && "getLHS called on atomic constraint.");
132-
return Constraint.get<CompoundConstraint>().getPointer()->first;
138+
bool isFoldExpanded() const {
139+
return Constraint.is<FoldExpandedConstraint *>();
133140
}
141+
bool isCompound() const { return Constraint.is<CompoundConstraint>(); }
134142

135-
NormalizedConstraint &getRHS() const {
136-
assert(!isAtomic() && "getRHS called on atomic constraint.");
137-
return Constraint.get<CompoundConstraint>().getPointer()->second;
143+
CompoundConstraintKind getCompoundKind() const {
144+
assert(isCompound() && "getCompoundKind on a non-compound constraint..");
145+
return Constraint.get<CompoundConstraint>().getInt();
138146
}
139147

148+
NormalizedConstraint &getLHS() const;
149+
NormalizedConstraint &getRHS() const;
150+
140151
AtomicConstraint *getAtomicConstraint() const {
141152
assert(isAtomic() &&
142153
"getAtomicConstraint called on non-atomic constraint.");
143154
return Constraint.get<AtomicConstraint *>();
144155
}
145156

157+
FoldExpandedConstraint *getFoldExpandedConstraint() const {
158+
assert(isFoldExpanded() &&
159+
"getFoldExpandedConstraint called on non-fold-expanded constraint.");
160+
return Constraint.get<FoldExpandedConstraint *>();
161+
}
162+
146163
private:
147164
static std::optional<NormalizedConstraint>
148165
fromConstraintExprs(Sema &S, NamedDecl *D, ArrayRef<const Expr *> E);
149166
static std::optional<NormalizedConstraint>
150167
fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E);
151168
};
152169

170+
struct alignas(ConstraintAlignment) NormalizedConstraintPair {
171+
NormalizedConstraint LHS, RHS;
172+
};
173+
174+
struct alignas(ConstraintAlignment) FoldExpandedConstraint {
175+
enum class FoldOperatorKind { And, Or } Kind;
176+
NormalizedConstraint Constraint;
177+
const Expr *Pattern;
178+
179+
FoldExpandedConstraint(FoldOperatorKind K, NormalizedConstraint C,
180+
const Expr *Pattern)
181+
: Kind(K), Constraint(std::move(C)), Pattern(Pattern) {};
182+
183+
template <typename AtomicSubsumptionEvaluator>
184+
bool subsumes(const FoldExpandedConstraint &Other,
185+
const AtomicSubsumptionEvaluator &E) const;
186+
187+
static bool AreCompatibleForSubsumption(const FoldExpandedConstraint &A,
188+
const FoldExpandedConstraint &B);
189+
};
190+
191+
const NormalizedConstraint *getNormalizedAssociatedConstraints(
192+
Sema &S, NamedDecl *ConstrainedDecl,
193+
ArrayRef<const Expr *> AssociatedConstraints);
194+
195+
template <typename AtomicSubsumptionEvaluator>
196+
bool subsumes(const NormalForm &PDNF, const NormalForm &QCNF,
197+
const AtomicSubsumptionEvaluator &E) {
198+
// C++ [temp.constr.order] p2
199+
// Then, P subsumes Q if and only if, for every disjunctive clause Pi in the
200+
// disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in
201+
// the conjuctive normal form of Q, where [...]
202+
for (const auto &Pi : PDNF) {
203+
for (const auto &Qj : QCNF) {
204+
// C++ [temp.constr.order] p2
205+
// - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if
206+
// and only if there exists an atomic constraint Pia in Pi for which
207+
// there exists an atomic constraint, Qjb, in Qj such that Pia
208+
// subsumes Qjb.
209+
bool Found = false;
210+
for (NormalFormConstraint Pia : Pi) {
211+
for (NormalFormConstraint Qjb : Qj) {
212+
if (Pia.is<FoldExpandedConstraint *>() &&
213+
Qjb.is<FoldExpandedConstraint *>()) {
214+
if (Pia.get<FoldExpandedConstraint *>()->subsumes(
215+
*Qjb.get<FoldExpandedConstraint *>(), E)) {
216+
Found = true;
217+
break;
218+
}
219+
} else if (Pia.is<AtomicConstraint *>() &&
220+
Qjb.is<AtomicConstraint *>()) {
221+
if (E(*Pia.get<AtomicConstraint *>(),
222+
*Qjb.get<AtomicConstraint *>())) {
223+
Found = true;
224+
break;
225+
}
226+
}
227+
}
228+
if (Found)
229+
break;
230+
}
231+
if (!Found)
232+
return false;
233+
}
234+
}
235+
return true;
236+
}
237+
238+
template <typename AtomicSubsumptionEvaluator>
239+
bool subsumes(Sema &S, NamedDecl *DP, ArrayRef<const Expr *> P, NamedDecl *DQ,
240+
ArrayRef<const Expr *> Q, bool &Subsumes,
241+
const AtomicSubsumptionEvaluator &E) {
242+
// C++ [temp.constr.order] p2
243+
// In order to determine if a constraint P subsumes a constraint Q, P is
244+
// transformed into disjunctive normal form, and Q is transformed into
245+
// conjunctive normal form. [...]
246+
const NormalizedConstraint *PNormalized =
247+
getNormalizedAssociatedConstraints(S, DP, P);
248+
if (!PNormalized)
249+
return true;
250+
NormalForm PDNF = makeDNF(*PNormalized);
251+
252+
const NormalizedConstraint *QNormalized =
253+
getNormalizedAssociatedConstraints(S, DQ, Q);
254+
if (!QNormalized)
255+
return true;
256+
NormalForm QCNF = makeCNF(*QNormalized);
257+
258+
Subsumes = subsumes(PDNF, QCNF, E);
259+
return false;
260+
}
261+
262+
template <typename AtomicSubsumptionEvaluator>
263+
bool FoldExpandedConstraint::subsumes(
264+
const FoldExpandedConstraint &Other,
265+
const AtomicSubsumptionEvaluator &E) const {
266+
267+
// [C++26] [temp.constr.order]
268+
// a fold expanded constraint A subsumes another fold expanded constraint B if
269+
// they are compatible for subsumption, have the same fold-operator, and the
270+
// constraint of A subsumes that of B
271+
272+
if (Kind != Other.Kind || !AreCompatibleForSubsumption(*this, Other))
273+
return false;
274+
275+
NormalForm PDNF = makeDNF(this->Constraint);
276+
NormalForm QCNF = makeCNF(Other.Constraint);
277+
return clang::subsumes(PDNF, QCNF, E);
278+
}
279+
153280
} // clang
154281

155282
#endif // LLVM_CLANG_SEMA_SEMACONCEPT_H

0 commit comments

Comments
 (0)