Skip to content

Commit 6c29073

Browse files
committed
PR45589: Properly decompose overloaded && and || operators in
constraint expressions. We create overloaded `&&` and `||` operators to hold the possible unqualified lookup results (if any) when the operands are dependent. We could avoid building these in some cases (we will never use the stored lookup results, and it would be better to not store them or perform the lookups), but in the general case we will probably still need to handle overloaded operators even with that optimization.
1 parent 7a17f3c commit 6c29073

File tree

4 files changed

+98
-61
lines changed

4 files changed

+98
-61
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6394,15 +6394,10 @@ class Sema final {
63946394
/// A diagnostic is emitted if it is not, false is returned, and
63956395
/// PossibleNonPrimary will be set to true if the failure might be due to a
63966396
/// non-primary expression being used as an atomic constraint.
6397-
bool CheckConstraintExpression(Expr *CE, Token NextToken = Token(),
6397+
bool CheckConstraintExpression(const Expr *CE, Token NextToken = Token(),
63986398
bool *PossibleNonPrimary = nullptr,
63996399
bool IsTrailingRequiresClause = false);
64006400

6401-
/// Check whether the given type-dependent expression will be the name of a
6402-
/// function or another callable function-like entity (e.g. a function
6403-
// template or overload set) for any substitution.
6404-
bool IsDependentFunctionNameExpr(Expr *E);
6405-
64066401
private:
64076402
/// Caches pairs of template-like decls whose associated constraints were
64086403
/// checked for subsumption and whether or not the first's constraints did in

clang/lib/Sema/SemaConcept.cpp

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,47 @@
2828
using namespace clang;
2929
using namespace sema;
3030

31-
bool
32-
Sema::CheckConstraintExpression(Expr *ConstraintExpression, Token NextToken,
33-
bool *PossibleNonPrimary,
34-
bool IsTrailingRequiresClause) {
31+
namespace {
32+
class LogicalBinOp {
33+
OverloadedOperatorKind Op = OO_None;
34+
const Expr *LHS = nullptr;
35+
const Expr *RHS = nullptr;
36+
37+
public:
38+
LogicalBinOp(const Expr *E) {
39+
if (auto *BO = dyn_cast<BinaryOperator>(E)) {
40+
Op = BinaryOperator::getOverloadedOperator(BO->getOpcode());
41+
LHS = BO->getLHS();
42+
RHS = BO->getRHS();
43+
} else if (auto *OO = dyn_cast<CXXOperatorCallExpr>(E)) {
44+
Op = OO->getOperator();
45+
LHS = OO->getArg(0);
46+
RHS = OO->getArg(1);
47+
}
48+
}
49+
50+
bool isAnd() const { return Op == OO_AmpAmp; }
51+
bool isOr() const { return Op == OO_PipePipe; }
52+
explicit operator bool() const { return isAnd() || isOr(); }
53+
54+
const Expr *getLHS() const { return LHS; }
55+
const Expr *getRHS() const { return RHS; }
56+
};
57+
}
58+
59+
bool Sema::CheckConstraintExpression(const Expr *ConstraintExpression,
60+
Token NextToken, bool *PossibleNonPrimary,
61+
bool IsTrailingRequiresClause) {
3562
// C++2a [temp.constr.atomic]p1
3663
// ..E shall be a constant expression of type bool.
3764

3865
ConstraintExpression = ConstraintExpression->IgnoreParenImpCasts();
3966

40-
if (auto *BinOp = dyn_cast<BinaryOperator>(ConstraintExpression)) {
41-
if (BinOp->getOpcode() == BO_LAnd || BinOp->getOpcode() == BO_LOr)
42-
return CheckConstraintExpression(BinOp->getLHS(), NextToken,
43-
PossibleNonPrimary) &&
44-
CheckConstraintExpression(BinOp->getRHS(), NextToken,
45-
PossibleNonPrimary);
67+
if (LogicalBinOp BO = ConstraintExpression) {
68+
return CheckConstraintExpression(BO.getLHS(), NextToken,
69+
PossibleNonPrimary) &&
70+
CheckConstraintExpression(BO.getRHS(), NextToken,
71+
PossibleNonPrimary);
4672
} else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpression))
4773
return CheckConstraintExpression(C->getSubExpr(), NextToken,
4874
PossibleNonPrimary);
@@ -60,7 +86,7 @@ Sema::CheckConstraintExpression(Expr *ConstraintExpression, Token NextToken,
6086
(NextToken.is(tok::l_paren) &&
6187
(IsTrailingRequiresClause ||
6288
(Type->isDependentType() &&
63-
IsDependentFunctionNameExpr(ConstraintExpression)) ||
89+
isa<UnresolvedLookupExpr>(ConstraintExpression)) ||
6490
Type->isFunctionType() ||
6591
Type->isSpecificBuiltinType(BuiltinType::Overload))) ||
6692
// We have the following case:
@@ -99,39 +125,37 @@ calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
99125
AtomicEvaluator &&Evaluator) {
100126
ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
101127

102-
if (auto *BO = dyn_cast<BinaryOperator>(ConstraintExpr)) {
103-
if (BO->getOpcode() == BO_LAnd || BO->getOpcode() == BO_LOr) {
104-
if (calculateConstraintSatisfaction(S, BO->getLHS(), Satisfaction,
105-
Evaluator))
106-
return true;
128+
if (LogicalBinOp BO = ConstraintExpr) {
129+
if (calculateConstraintSatisfaction(S, BO.getLHS(), Satisfaction,
130+
Evaluator))
131+
return true;
107132

108-
bool IsLHSSatisfied = Satisfaction.IsSatisfied;
133+
bool IsLHSSatisfied = Satisfaction.IsSatisfied;
109134

110-
if (BO->getOpcode() == BO_LOr && IsLHSSatisfied)
111-
// [temp.constr.op] p3
112-
// A disjunction is a constraint taking two operands. To determine if
113-
// a disjunction is satisfied, the satisfaction of the first operand
114-
// is checked. If that is satisfied, the disjunction is satisfied.
115-
// Otherwise, the disjunction is satisfied if and only if the second
116-
// operand is satisfied.
117-
return false;
135+
if (BO.isOr() && IsLHSSatisfied)
136+
// [temp.constr.op] p3
137+
// A disjunction is a constraint taking two operands. To determine if
138+
// a disjunction is satisfied, the satisfaction of the first operand
139+
// is checked. If that is satisfied, the disjunction is satisfied.
140+
// Otherwise, the disjunction is satisfied if and only if the second
141+
// operand is satisfied.
142+
return false;
118143

119-
if (BO->getOpcode() == BO_LAnd && !IsLHSSatisfied)
120-
// [temp.constr.op] p2
121-
// A conjunction is a constraint taking two operands. To determine if
122-
// a conjunction is satisfied, the satisfaction of the first operand
123-
// is checked. If that is not satisfied, the conjunction is not
124-
// satisfied. Otherwise, the conjunction is satisfied if and only if
125-
// the second operand is satisfied.
126-
return false;
144+
if (BO.isAnd() && !IsLHSSatisfied)
145+
// [temp.constr.op] p2
146+
// A conjunction is a constraint taking two operands. To determine if
147+
// a conjunction is satisfied, the satisfaction of the first operand
148+
// is checked. If that is not satisfied, the conjunction is not
149+
// satisfied. Otherwise, the conjunction is satisfied if and only if
150+
// the second operand is satisfied.
151+
return false;
127152

128-
return calculateConstraintSatisfaction(S, BO->getRHS(), Satisfaction,
129-
std::forward<AtomicEvaluator>(Evaluator));
130-
}
131-
}
132-
else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr))
153+
return calculateConstraintSatisfaction(
154+
S, BO.getRHS(), Satisfaction, std::forward<AtomicEvaluator>(Evaluator));
155+
} else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr)) {
133156
return calculateConstraintSatisfaction(S, C->getSubExpr(), Satisfaction,
134157
std::forward<AtomicEvaluator>(Evaluator));
158+
}
135159

136160
// An atomic constraint expression
137161
ExprResult SubstitutedAtomicExpr = Evaluator(ConstraintExpr);
@@ -725,19 +749,16 @@ NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
725749
// - The normal form of an expression (E) is the normal form of E.
726750
// [...]
727751
E = E->IgnoreParenImpCasts();
728-
if (auto *BO = dyn_cast<const BinaryOperator>(E)) {
729-
if (BO->getOpcode() == BO_LAnd || BO->getOpcode() == BO_LOr) {
730-
auto LHS = fromConstraintExpr(S, D, BO->getLHS());
731-
if (!LHS)
732-
return None;
733-
auto RHS = fromConstraintExpr(S, D, BO->getRHS());
734-
if (!RHS)
735-
return None;
752+
if (LogicalBinOp BO = E) {
753+
auto LHS = fromConstraintExpr(S, D, BO.getLHS());
754+
if (!LHS)
755+
return None;
756+
auto RHS = fromConstraintExpr(S, D, BO.getRHS());
757+
if (!RHS)
758+
return None;
736759

737-
return NormalizedConstraint(
738-
S.Context, std::move(*LHS), std::move(*RHS),
739-
BO->getOpcode() == BO_LAnd ? CCK_Conjunction : CCK_Disjunction);
740-
}
760+
return NormalizedConstraint(S.Context, std::move(*LHS), std::move(*RHS),
761+
BO.isAnd() ? CCK_Conjunction : CCK_Disjunction);
741762
} else if (auto *CSE = dyn_cast<const ConceptSpecializationExpr>(E)) {
742763
const NormalizedConstraint *SubNF;
743764
{

clang/lib/Sema/SemaExpr.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18945,11 +18945,6 @@ ExprResult Sema::ActOnObjCAvailabilityCheckExpr(
1894518945
ObjCAvailabilityCheckExpr(Version, AtLoc, RParen, Context.BoolTy);
1894618946
}
1894718947

18948-
bool Sema::IsDependentFunctionNameExpr(Expr *E) {
18949-
assert(E->isTypeDependent());
18950-
return isa<UnresolvedLookupExpr>(E);
18951-
}
18952-
1895318948
ExprResult Sema::CreateRecoveryExpr(SourceLocation Begin, SourceLocation End,
1895418949
ArrayRef<Expr *> SubExprs, QualType T) {
1895518950
// FIXME: enable it for C++, RecoveryExpr is type-dependent to suppress
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %clang_cc1 -std=c++20 -verify %s
2+
// RUN: %clang_cc1 -std=c++20 -verify %s -DDEPENDENT_OR
3+
4+
#ifdef DEPENDENT_OR
5+
// This causes the || below to be a CXXOperatorCallExpr not a BinaryOperator.
6+
struct A {}; bool operator||(A, A);
7+
#endif
8+
9+
namespace PR45589 {
10+
template<typename T> struct X { static constexpr bool value = T::value; }; // expected-error {{cannot be used prior to '::'}}
11+
struct False { static constexpr bool value = false; };
12+
struct True { static constexpr bool value = true; };
13+
14+
template<typename T> concept C = true;
15+
16+
template<bool B, typename T> constexpr int test = 0;
17+
template<bool B, typename T> requires C<T> constexpr int test<B, T> = 1;
18+
template<bool B, typename T> requires (B && C<T>) || (X<T>::value && C<T>) constexpr int test<B, T> = 2; // expected-error {{non-constant expression}} expected-note {{subexpression}} expected-note {{instantiation of}} expected-note {{while substituting}}
19+
static_assert(test<true, False> == 2);
20+
static_assert(test<true, True> == 2);
21+
static_assert(test<true, char> == 2); // satisfaction of second term of || not considered
22+
static_assert(test<false, False> == 1);
23+
static_assert(test<false, True> == 2); // constraints are partially ordered
24+
// FIXME: These diagnostics are excessive.
25+
static_assert(test<false, char> == 1); // expected-note 2{{while}} expected-note 2{{during}}
26+
}

0 commit comments

Comments
 (0)