Skip to content

Commit

Permalink
[C++20][Clang] P2468R2 The Equality Operator You Are Looking For
Browse files Browse the repository at this point in the history
Implement
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2468r2.html.

Primarily we now accept
```
template<typename T> struct CRTPBase {
  bool operator==(const T&) const;
  bool operator!=(const T&) const;
};
struct CRTP : CRTPBase<CRTP> {};
bool cmp_crtp = CRTP() == CRTP();
bool cmp_crtp2 = CRTP() != CRTP();
```

Differential Revision: https://reviews.llvm.org/D134529
  • Loading branch information
usx95 committed Oct 6, 2022
1 parent 956f7f2 commit 38b9d31
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 64 deletions.
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Expand Up @@ -415,6 +415,7 @@ C++20 Feature Support
name is found via ordinary lookup so typedefs are found.
- Implemented `P0634r3 <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0634r3.html>`_,
which removes the requirement for the ``typename`` keyword in certain contexts.
- Implemented The Equality Operator You Are Looking For (`P2468 <http://wg21.link/p2468r2>`_).

C++2b Feature Support
^^^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -4703,6 +4703,8 @@ def ext_ovl_ambiguous_oper_binary_reversed : ExtWarn<
def note_ovl_ambiguous_oper_binary_reversed_self : Note<
"ambiguity is between a regular call to this operator and a call with the "
"argument order reversed">;
def note_ovl_ambiguous_eqeq_reversed_self_non_const : Note<
"mark 'operator==' as const or add a matching 'operator!=' to resolve the ambiguity">;
def note_ovl_ambiguous_oper_binary_selected_candidate : Note<
"candidate function with non-reversed arguments">;
def note_ovl_ambiguous_oper_binary_reversed_candidate : Note<
Expand Down
23 changes: 14 additions & 9 deletions clang/include/clang/Sema/Overload.h
Expand Up @@ -977,12 +977,16 @@ class Sema;
/// functions to a candidate set.
struct OperatorRewriteInfo {
OperatorRewriteInfo()
: OriginalOperator(OO_None), AllowRewrittenCandidates(false) {}
OperatorRewriteInfo(OverloadedOperatorKind Op, bool AllowRewritten)
: OriginalOperator(Op), AllowRewrittenCandidates(AllowRewritten) {}
: OriginalOperator(OO_None), OpLoc(), AllowRewrittenCandidates(false) {}
OperatorRewriteInfo(OverloadedOperatorKind Op, SourceLocation OpLoc,
bool AllowRewritten)
: OriginalOperator(Op), OpLoc(OpLoc),
AllowRewrittenCandidates(AllowRewritten) {}

/// The original operator as written in the source.
OverloadedOperatorKind OriginalOperator;
/// The source location of the operator.
SourceLocation OpLoc;
/// Whether we should include rewritten candidates in the overload set.
bool AllowRewrittenCandidates;

Expand Down Expand Up @@ -1018,22 +1022,23 @@ class Sema;
CRK = OverloadCandidateRewriteKind(CRK | CRK_Reversed);
return CRK;
}

/// Determines whether this operator could be implemented by a function
/// with reversed parameter order.
bool isReversible() {
return AllowRewrittenCandidates && OriginalOperator &&
(getRewrittenOverloadedOperator(OriginalOperator) != OO_None ||
shouldAddReversed(OriginalOperator));
allowsReversed(OriginalOperator));
}

/// Determine whether we should consider looking for and adding reversed
/// candidates for operator Op.
bool shouldAddReversed(OverloadedOperatorKind Op);
/// Determine whether reversing parameter order is allowed for operator
/// Op.
bool allowsReversed(OverloadedOperatorKind Op);

/// Determine whether we should add a rewritten candidate for \p FD with
/// reversed parameter order.
bool shouldAddReversed(ASTContext &Ctx, const FunctionDecl *FD);
/// \param OriginalArgs are the original non reversed arguments.
bool shouldAddReversed(Sema &S, ArrayRef<Expr *> OriginalArgs,
FunctionDecl *FD);
};

private:
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Sema/SemaDeclCXX.cpp
Expand Up @@ -7879,7 +7879,8 @@ class DefaultedComparisonAnalyzer
OverloadCandidateSet CandidateSet(
FD->getLocation(), OverloadCandidateSet::CSK_Operator,
OverloadCandidateSet::OperatorRewriteInfo(
OO, /*AllowRewrittenCandidates=*/!SpaceshipCandidates));
OO, FD->getLocation(),
/*AllowRewrittenCandidates=*/!SpaceshipCandidates));

/// C++2a [class.compare.default]p1 [P2002R0]:
/// [...] the defaulted function itself is never a candidate for overload
Expand Down
136 changes: 119 additions & 17 deletions clang/lib/Sema/SemaOverload.cpp
Expand Up @@ -12,14 +12,17 @@

#include "clang/AST/ASTContext.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DependenceFlags.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeOrdering.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/OperatorKinds.h"
#include "clang/Basic/PartialDiagnostic.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
Expand All @@ -34,6 +37,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/Casting.h"
#include <algorithm>
#include <cstdlib>

Expand Down Expand Up @@ -890,22 +894,99 @@ llvm::Optional<unsigned> DeductionFailureInfo::getCallArgIndex() {
}
}

bool OverloadCandidateSet::OperatorRewriteInfo::shouldAddReversed(
static bool FunctionsCorrespond(ASTContext &Ctx, const FunctionDecl *X,
const FunctionDecl *Y) {
if (!X || !Y)
return false;
if (X->getNumParams() != Y->getNumParams())
return false;
for (unsigned I = 0; I < X->getNumParams(); ++I)
if (!Ctx.hasSameUnqualifiedType(X->getParamDecl(I)->getType(),
Y->getParamDecl(I)->getType()))
return false;
if (auto *FTX = X->getDescribedFunctionTemplate()) {
auto *FTY = Y->getDescribedFunctionTemplate();
if (!FTY)
return false;
if (!Ctx.isSameTemplateParameterList(FTX->getTemplateParameters(),
FTY->getTemplateParameters()))
return false;
}
return true;
}

static bool shouldAddReversedEqEq(Sema &S, SourceLocation OpLoc,
Expr *FirstOperand, FunctionDecl *EqFD) {
assert(EqFD->getOverloadedOperator() ==
OverloadedOperatorKind::OO_EqualEqual);
// C++2a [over.match.oper]p4:
// A non-template function or function template F named operator== is a
// rewrite target with first operand o unless a search for the name operator!=
// in the scope S from the instantiation context of the operator expression
// finds a function or function template that would correspond
// ([basic.scope.scope]) to F if its name were operator==, where S is the
// scope of the class type of o if F is a class member, and the namespace
// scope of which F is a member otherwise. A function template specialization
// named operator== is a rewrite target if its function template is a rewrite
// target.
DeclarationName NotEqOp = S.Context.DeclarationNames.getCXXOperatorName(
OverloadedOperatorKind::OO_ExclaimEqual);
if (auto *MD = dyn_cast<CXXMethodDecl>(EqFD)) {
// If F is a class member, search scope is class type of first operand.
QualType RHS = FirstOperand->getType();
auto *RHSRec = RHS->getAs<RecordType>();
if (!RHSRec)
return true;
LookupResult Members(S, NotEqOp, OpLoc,
Sema::LookupNameKind::LookupMemberName);
S.LookupQualifiedName(Members, RHSRec->getDecl());
Members.suppressDiagnostics();
for (NamedDecl *Op : Members)
if (FunctionsCorrespond(S.Context, EqFD, Op->getAsFunction()))
return false;
return true;
}
// Otherwise the search scope is the namespace scope of which F is a member.
LookupResult NonMembers(S, NotEqOp, OpLoc,
Sema::LookupNameKind::LookupOperatorName);
S.LookupName(NonMembers,
S.getScopeForContext(EqFD->getEnclosingNamespaceContext()));
NonMembers.suppressDiagnostics();
for (NamedDecl *Op : NonMembers) {
auto *FD = Op->getAsFunction();
if(auto* UD = dyn_cast<UsingShadowDecl>(Op))
FD = UD->getUnderlyingDecl()->getAsFunction();
if (FunctionsCorrespond(S.Context, EqFD, FD) &&
declaresSameEntity(cast<Decl>(EqFD->getDeclContext()),
cast<Decl>(Op->getDeclContext())))
return false;
}
return true;
}

bool OverloadCandidateSet::OperatorRewriteInfo::allowsReversed(
OverloadedOperatorKind Op) {
if (!AllowRewrittenCandidates)
return false;
return Op == OO_EqualEqual || Op == OO_Spaceship;
}

bool OverloadCandidateSet::OperatorRewriteInfo::shouldAddReversed(
ASTContext &Ctx, const FunctionDecl *FD) {
if (!shouldAddReversed(FD->getDeclName().getCXXOverloadedOperator()))
Sema &S, ArrayRef<Expr *> OriginalArgs, FunctionDecl *FD) {
auto Op = FD->getOverloadedOperator();
if (!allowsReversed(Op))
return false;
if (Op == OverloadedOperatorKind::OO_EqualEqual) {
assert(OriginalArgs.size() == 2);
if (!shouldAddReversedEqEq(
S, OpLoc, /*FirstOperand in reversed args*/ OriginalArgs[1], FD))
return false;
}
// Don't bother adding a reversed candidate that can never be a better
// match than the non-reversed version.
return FD->getNumParams() != 2 ||
!Ctx.hasSameUnqualifiedType(FD->getParamDecl(0)->getType(),
FD->getParamDecl(1)->getType()) ||
!S.Context.hasSameUnqualifiedType(FD->getParamDecl(0)->getType(),
FD->getParamDecl(1)->getType()) ||
FD->hasAttr<EnableIfAttr>();
}

Expand Down Expand Up @@ -7749,7 +7830,7 @@ void Sema::AddNonMemberOperatorCandidates(
if (FunTmpl) {
AddTemplateOverloadCandidate(FunTmpl, F.getPair(), ExplicitTemplateArgs,
FunctionArgs, CandidateSet);
if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD))
if (CandidateSet.getRewriteInfo().shouldAddReversed(*this, Args, FD))
AddTemplateOverloadCandidate(
FunTmpl, F.getPair(), ExplicitTemplateArgs,
{FunctionArgs[1], FunctionArgs[0]}, CandidateSet, false, false,
Expand All @@ -7758,7 +7839,7 @@ void Sema::AddNonMemberOperatorCandidates(
if (ExplicitTemplateArgs)
continue;
AddOverloadCandidate(FD, F.getPair(), FunctionArgs, CandidateSet);
if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD))
if (CandidateSet.getRewriteInfo().shouldAddReversed(*this, Args, FD))
AddOverloadCandidate(FD, F.getPair(),
{FunctionArgs[1], FunctionArgs[0]}, CandidateSet,
false, false, true, false, ADLCallKind::NotADL,
Expand Down Expand Up @@ -7809,12 +7890,17 @@ void Sema::AddMemberOperatorCandidates(OverloadedOperatorKind Op,
Operators.suppressDiagnostics();

for (LookupResult::iterator Oper = Operators.begin(),
OperEnd = Operators.end();
Oper != OperEnd;
++Oper)
OperEnd = Operators.end();
Oper != OperEnd; ++Oper) {
if (Oper->getAsFunction() &&
PO == OverloadCandidateParamOrder::Reversed &&
!CandidateSet.getRewriteInfo().shouldAddReversed(
*this, {Args[1], Args[0]}, Oper->getAsFunction()))
continue;
AddMethodCandidate(Oper.getPair(), Args[0]->getType(),
Args[0]->Classify(Context), Args.slice(1),
CandidateSet, /*SuppressUserConversion=*/false, PO);
}
}
}

Expand Down Expand Up @@ -9510,7 +9596,7 @@ Sema::AddArgumentDependentLookupCandidates(DeclarationName Name,
FD, FoundDecl, Args, CandidateSet, /*SuppressUserConversions=*/false,
PartialOverloading, /*AllowExplicit=*/true,
/*AllowExplicitConversion=*/false, ADLCallKind::UsesADL);
if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD)) {
if (CandidateSet.getRewriteInfo().shouldAddReversed(*this, Args, FD)) {
AddOverloadCandidate(
FD, FoundDecl, {Args[1], Args[0]}, CandidateSet,
/*SuppressUserConversions=*/false, PartialOverloading,
Expand All @@ -9524,7 +9610,7 @@ Sema::AddArgumentDependentLookupCandidates(DeclarationName Name,
/*SuppressUserConversions=*/false, PartialOverloading,
/*AllowExplicit=*/true, ADLCallKind::UsesADL);
if (CandidateSet.getRewriteInfo().shouldAddReversed(
Context, FTD->getTemplatedDecl())) {
*this, Args, FTD->getTemplatedDecl())) {
AddTemplateOverloadCandidate(
FTD, FoundDecl, ExplicitTemplateArgs, {Args[1], Args[0]},
CandidateSet, /*SuppressUserConversions=*/false, PartialOverloading,
Expand Down Expand Up @@ -13637,14 +13723,14 @@ void Sema::LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet,

// Add operator candidates that are member functions.
AddMemberOperatorCandidates(Op, OpLoc, Args, CandidateSet);
if (CandidateSet.getRewriteInfo().shouldAddReversed(Op))
if (CandidateSet.getRewriteInfo().allowsReversed(Op))
AddMemberOperatorCandidates(Op, OpLoc, {Args[1], Args[0]}, CandidateSet,
OverloadCandidateParamOrder::Reversed);

// In C++20, also add any rewritten member candidates.
if (ExtraOp) {
AddMemberOperatorCandidates(ExtraOp, OpLoc, Args, CandidateSet);
if (CandidateSet.getRewriteInfo().shouldAddReversed(ExtraOp))
if (CandidateSet.getRewriteInfo().allowsReversed(ExtraOp))
AddMemberOperatorCandidates(ExtraOp, OpLoc, {Args[1], Args[0]},
CandidateSet,
OverloadCandidateParamOrder::Reversed);
Expand Down Expand Up @@ -13775,9 +13861,9 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);

// Build the overload set.
OverloadCandidateSet CandidateSet(
OpLoc, OverloadCandidateSet::CSK_Operator,
OverloadCandidateSet::OperatorRewriteInfo(Op, AllowRewrittenCandidates));
OverloadCandidateSet CandidateSet(OpLoc, OverloadCandidateSet::CSK_Operator,
OverloadCandidateSet::OperatorRewriteInfo(
Op, OpLoc, AllowRewrittenCandidates));
if (DefaultedFn)
CandidateSet.exclude(DefaultedFn);
LookupOverloadedBinOp(CandidateSet, Op, Fns, Args, PerformADL);
Expand Down Expand Up @@ -13852,6 +13938,22 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
if (AmbiguousWithSelf) {
Diag(FnDecl->getLocation(),
diag::note_ovl_ambiguous_oper_binary_reversed_self);
// Mark member== const or provide matching != to disallow reversed
// args. Eg.
// struct S { bool operator==(const S&); };
// S()==S();
if (auto *MD = dyn_cast<CXXMethodDecl>(FnDecl))
if (Op == OverloadedOperatorKind::OO_EqualEqual &&
!MD->isConst() &&
Context.hasSameUnqualifiedType(
MD->getThisObjectType(),
MD->getParamDecl(0)->getType().getNonReferenceType()) &&
Context.hasSameUnqualifiedType(MD->getThisObjectType(),
Args[0]->getType()) &&
Context.hasSameUnqualifiedType(MD->getThisObjectType(),
Args[1]->getType()))
Diag(FnDecl->getLocation(),
diag::note_ovl_ambiguous_eqeq_reversed_self_non_const);
} else {
Diag(FnDecl->getLocation(),
diag::note_ovl_ambiguous_oper_binary_selected_candidate);
Expand Down

0 comments on commit 38b9d31

Please sign in to comment.