forked from OSchip/llvm-project
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.
This commit is contained in:
parent
7a17f3ccd1
commit
6c29073efb
|
@ -6394,15 +6394,10 @@ public:
|
||||||
/// A diagnostic is emitted if it is not, false is returned, and
|
/// A diagnostic is emitted if it is not, false is returned, and
|
||||||
/// PossibleNonPrimary will be set to true if the failure might be due to a
|
/// PossibleNonPrimary will be set to true if the failure might be due to a
|
||||||
/// non-primary expression being used as an atomic constraint.
|
/// non-primary expression being used as an atomic constraint.
|
||||||
bool CheckConstraintExpression(Expr *CE, Token NextToken = Token(),
|
bool CheckConstraintExpression(const Expr *CE, Token NextToken = Token(),
|
||||||
bool *PossibleNonPrimary = nullptr,
|
bool *PossibleNonPrimary = nullptr,
|
||||||
bool IsTrailingRequiresClause = false);
|
bool IsTrailingRequiresClause = false);
|
||||||
|
|
||||||
/// Check whether the given type-dependent expression will be the name of a
|
|
||||||
/// function or another callable function-like entity (e.g. a function
|
|
||||||
// template or overload set) for any substitution.
|
|
||||||
bool IsDependentFunctionNameExpr(Expr *E);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Caches pairs of template-like decls whose associated constraints were
|
/// Caches pairs of template-like decls whose associated constraints were
|
||||||
/// checked for subsumption and whether or not the first's constraints did in
|
/// checked for subsumption and whether or not the first's constraints did in
|
||||||
|
|
|
@ -28,21 +28,47 @@
|
||||||
using namespace clang;
|
using namespace clang;
|
||||||
using namespace sema;
|
using namespace sema;
|
||||||
|
|
||||||
bool
|
namespace {
|
||||||
Sema::CheckConstraintExpression(Expr *ConstraintExpression, Token NextToken,
|
class LogicalBinOp {
|
||||||
bool *PossibleNonPrimary,
|
OverloadedOperatorKind Op = OO_None;
|
||||||
bool IsTrailingRequiresClause) {
|
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();
|
||||||
|
} else if (auto *OO = dyn_cast<CXXOperatorCallExpr>(E)) {
|
||||||
|
Op = OO->getOperator();
|
||||||
|
LHS = OO->getArg(0);
|
||||||
|
RHS = OO->getArg(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sema::CheckConstraintExpression(const Expr *ConstraintExpression,
|
||||||
|
Token NextToken, bool *PossibleNonPrimary,
|
||||||
|
bool IsTrailingRequiresClause) {
|
||||||
// C++2a [temp.constr.atomic]p1
|
// C++2a [temp.constr.atomic]p1
|
||||||
// ..E shall be a constant expression of type bool.
|
// ..E shall be a constant expression of type bool.
|
||||||
|
|
||||||
ConstraintExpression = ConstraintExpression->IgnoreParenImpCasts();
|
ConstraintExpression = ConstraintExpression->IgnoreParenImpCasts();
|
||||||
|
|
||||||
if (auto *BinOp = dyn_cast<BinaryOperator>(ConstraintExpression)) {
|
if (LogicalBinOp BO = ConstraintExpression) {
|
||||||
if (BinOp->getOpcode() == BO_LAnd || BinOp->getOpcode() == BO_LOr)
|
return CheckConstraintExpression(BO.getLHS(), NextToken,
|
||||||
return CheckConstraintExpression(BinOp->getLHS(), NextToken,
|
PossibleNonPrimary) &&
|
||||||
PossibleNonPrimary) &&
|
CheckConstraintExpression(BO.getRHS(), NextToken,
|
||||||
CheckConstraintExpression(BinOp->getRHS(), NextToken,
|
PossibleNonPrimary);
|
||||||
PossibleNonPrimary);
|
|
||||||
} else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpression))
|
} else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpression))
|
||||||
return CheckConstraintExpression(C->getSubExpr(), NextToken,
|
return CheckConstraintExpression(C->getSubExpr(), NextToken,
|
||||||
PossibleNonPrimary);
|
PossibleNonPrimary);
|
||||||
|
@ -60,7 +86,7 @@ Sema::CheckConstraintExpression(Expr *ConstraintExpression, Token NextToken,
|
||||||
(NextToken.is(tok::l_paren) &&
|
(NextToken.is(tok::l_paren) &&
|
||||||
(IsTrailingRequiresClause ||
|
(IsTrailingRequiresClause ||
|
||||||
(Type->isDependentType() &&
|
(Type->isDependentType() &&
|
||||||
IsDependentFunctionNameExpr(ConstraintExpression)) ||
|
isa<UnresolvedLookupExpr>(ConstraintExpression)) ||
|
||||||
Type->isFunctionType() ||
|
Type->isFunctionType() ||
|
||||||
Type->isSpecificBuiltinType(BuiltinType::Overload))) ||
|
Type->isSpecificBuiltinType(BuiltinType::Overload))) ||
|
||||||
// We have the following case:
|
// We have the following case:
|
||||||
|
@ -99,39 +125,37 @@ calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
|
||||||
AtomicEvaluator &&Evaluator) {
|
AtomicEvaluator &&Evaluator) {
|
||||||
ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
|
ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
|
||||||
|
|
||||||
if (auto *BO = dyn_cast<BinaryOperator>(ConstraintExpr)) {
|
if (LogicalBinOp BO = ConstraintExpr) {
|
||||||
if (BO->getOpcode() == BO_LAnd || BO->getOpcode() == BO_LOr) {
|
if (calculateConstraintSatisfaction(S, BO.getLHS(), Satisfaction,
|
||||||
if (calculateConstraintSatisfaction(S, BO->getLHS(), Satisfaction,
|
Evaluator))
|
||||||
Evaluator))
|
return true;
|
||||||
return true;
|
|
||||||
|
|
||||||
bool IsLHSSatisfied = Satisfaction.IsSatisfied;
|
bool IsLHSSatisfied = Satisfaction.IsSatisfied;
|
||||||
|
|
||||||
if (BO->getOpcode() == BO_LOr && IsLHSSatisfied)
|
if (BO.isOr() && IsLHSSatisfied)
|
||||||
// [temp.constr.op] p3
|
// [temp.constr.op] p3
|
||||||
// A disjunction is a constraint taking two operands. To determine if
|
// A disjunction is a constraint taking two operands. To determine if
|
||||||
// a disjunction is satisfied, the satisfaction of the first operand
|
// a disjunction is satisfied, the satisfaction of the first operand
|
||||||
// is checked. If that is satisfied, the disjunction is satisfied.
|
// is checked. If that is satisfied, the disjunction is satisfied.
|
||||||
// Otherwise, the disjunction is satisfied if and only if the second
|
// Otherwise, the disjunction is satisfied if and only if the second
|
||||||
// operand is satisfied.
|
// operand is satisfied.
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (BO->getOpcode() == BO_LAnd && !IsLHSSatisfied)
|
if (BO.isAnd() && !IsLHSSatisfied)
|
||||||
// [temp.constr.op] p2
|
// [temp.constr.op] p2
|
||||||
// A conjunction is a constraint taking two operands. To determine if
|
// A conjunction is a constraint taking two operands. To determine if
|
||||||
// a conjunction is satisfied, the satisfaction of the first operand
|
// a conjunction is satisfied, the satisfaction of the first operand
|
||||||
// is checked. If that is not satisfied, the conjunction is not
|
// is checked. If that is not satisfied, the conjunction is not
|
||||||
// satisfied. Otherwise, the conjunction is satisfied if and only if
|
// satisfied. Otherwise, the conjunction is satisfied if and only if
|
||||||
// the second operand is satisfied.
|
// the second operand is satisfied.
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return calculateConstraintSatisfaction(S, BO->getRHS(), Satisfaction,
|
return calculateConstraintSatisfaction(
|
||||||
std::forward<AtomicEvaluator>(Evaluator));
|
S, BO.getRHS(), Satisfaction, std::forward<AtomicEvaluator>(Evaluator));
|
||||||
}
|
} else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr)) {
|
||||||
}
|
|
||||||
else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr))
|
|
||||||
return calculateConstraintSatisfaction(S, C->getSubExpr(), Satisfaction,
|
return calculateConstraintSatisfaction(S, C->getSubExpr(), Satisfaction,
|
||||||
std::forward<AtomicEvaluator>(Evaluator));
|
std::forward<AtomicEvaluator>(Evaluator));
|
||||||
|
}
|
||||||
|
|
||||||
// An atomic constraint expression
|
// An atomic constraint expression
|
||||||
ExprResult SubstitutedAtomicExpr = Evaluator(ConstraintExpr);
|
ExprResult SubstitutedAtomicExpr = Evaluator(ConstraintExpr);
|
||||||
|
@ -725,19 +749,16 @@ NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
|
||||||
// - The normal form of an expression (E) is the normal form of E.
|
// - The normal form of an expression (E) is the normal form of E.
|
||||||
// [...]
|
// [...]
|
||||||
E = E->IgnoreParenImpCasts();
|
E = E->IgnoreParenImpCasts();
|
||||||
if (auto *BO = dyn_cast<const BinaryOperator>(E)) {
|
if (LogicalBinOp BO = E) {
|
||||||
if (BO->getOpcode() == BO_LAnd || BO->getOpcode() == BO_LOr) {
|
auto LHS = fromConstraintExpr(S, D, BO.getLHS());
|
||||||
auto LHS = fromConstraintExpr(S, D, BO->getLHS());
|
if (!LHS)
|
||||||
if (!LHS)
|
return None;
|
||||||
return None;
|
auto RHS = fromConstraintExpr(S, D, BO.getRHS());
|
||||||
auto RHS = fromConstraintExpr(S, D, BO->getRHS());
|
if (!RHS)
|
||||||
if (!RHS)
|
return None;
|
||||||
return None;
|
|
||||||
|
|
||||||
return NormalizedConstraint(
|
return NormalizedConstraint(S.Context, std::move(*LHS), std::move(*RHS),
|
||||||
S.Context, std::move(*LHS), std::move(*RHS),
|
BO.isAnd() ? CCK_Conjunction : CCK_Disjunction);
|
||||||
BO->getOpcode() == BO_LAnd ? CCK_Conjunction : CCK_Disjunction);
|
|
||||||
}
|
|
||||||
} else if (auto *CSE = dyn_cast<const ConceptSpecializationExpr>(E)) {
|
} else if (auto *CSE = dyn_cast<const ConceptSpecializationExpr>(E)) {
|
||||||
const NormalizedConstraint *SubNF;
|
const NormalizedConstraint *SubNF;
|
||||||
{
|
{
|
||||||
|
|
|
@ -18945,11 +18945,6 @@ ExprResult Sema::ActOnObjCAvailabilityCheckExpr(
|
||||||
ObjCAvailabilityCheckExpr(Version, AtLoc, RParen, Context.BoolTy);
|
ObjCAvailabilityCheckExpr(Version, AtLoc, RParen, Context.BoolTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sema::IsDependentFunctionNameExpr(Expr *E) {
|
|
||||||
assert(E->isTypeDependent());
|
|
||||||
return isa<UnresolvedLookupExpr>(E);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprResult Sema::CreateRecoveryExpr(SourceLocation Begin, SourceLocation End,
|
ExprResult Sema::CreateRecoveryExpr(SourceLocation Begin, SourceLocation End,
|
||||||
ArrayRef<Expr *> SubExprs, QualType T) {
|
ArrayRef<Expr *> SubExprs, QualType T) {
|
||||||
// FIXME: enable it for C++, RecoveryExpr is type-dependent to suppress
|
// FIXME: enable it for C++, RecoveryExpr is type-dependent to suppress
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
// RUN: %clang_cc1 -std=c++20 -verify %s
|
||||||
|
// RUN: %clang_cc1 -std=c++20 -verify %s -DDEPENDENT_OR
|
||||||
|
|
||||||
|
#ifdef DEPENDENT_OR
|
||||||
|
// This causes the || below to be a CXXOperatorCallExpr not a BinaryOperator.
|
||||||
|
struct A {}; bool operator||(A, A);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace PR45589 {
|
||||||
|
template<typename T> struct X { static constexpr bool value = T::value; }; // expected-error {{cannot be used prior to '::'}}
|
||||||
|
struct False { static constexpr bool value = false; };
|
||||||
|
struct True { static constexpr bool value = true; };
|
||||||
|
|
||||||
|
template<typename T> concept C = true;
|
||||||
|
|
||||||
|
template<bool B, typename T> constexpr int test = 0;
|
||||||
|
template<bool B, typename T> requires C<T> constexpr int test<B, T> = 1;
|
||||||
|
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}}
|
||||||
|
static_assert(test<true, False> == 2);
|
||||||
|
static_assert(test<true, True> == 2);
|
||||||
|
static_assert(test<true, char> == 2); // satisfaction of second term of || not considered
|
||||||
|
static_assert(test<false, False> == 1);
|
||||||
|
static_assert(test<false, True> == 2); // constraints are partially ordered
|
||||||
|
// FIXME: These diagnostics are excessive.
|
||||||
|
static_assert(test<false, char> == 1); // expected-note 2{{while}} expected-note 2{{during}}
|
||||||
|
}
|
Loading…
Reference in New Issue