Skip to content
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

[clang] Reapply Handle templated operators with reversed arguments #72213

Merged
merged 4 commits into from Jan 12, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions clang/docs/ReleaseNotes.rst
Expand Up @@ -37,6 +37,29 @@ These changes are ones which we think may surprise users when upgrading to
Clang |release| because of the opportunity they pose for disruption to existing
code bases.

- Fix a bug in reversed argument for templated operators.
This breaks code in C++20 which was previously accepted in C++17.
Clang did not properly diagnose such casese in C++20 before this change. Eg:

.. code-block:: cpp

struct P {};
template<class S> bool operator==(const P&, const S&);

struct A : public P {};
struct B : public P {};

// This equality is now ambiguous in C++20.
bool ambiguous(A a, B b) { return a == b; }

template<class S> bool operator!=(const P&, const S&);
// Ok. Found a matching operator!=.
bool fine(A a, B b) { return a == b; }

To reduce such widespread breakages, as an extension, Clang accepts this code
with an existing warning ``-Wambiguous-reversed-operator`` warning.
Fixes `GH <https://github.com/llvm/llvm-project/issues/53954>`_.

- The CMake variable ``GCC_INSTALL_PREFIX`` (which sets the default
``--gcc-toolchain=``) is deprecated and will be removed. Specify
``--gcc-install-dir=`` or ``--gcc-triple=`` in a `configuration file
Expand Down
46 changes: 34 additions & 12 deletions clang/lib/Sema/SemaOverload.cpp
Expand Up @@ -7723,9 +7723,19 @@ bool Sema::CheckNonDependentConversions(
++I) {
QualType ParamType = ParamTypes[I + Offset];
if (!ParamType->isDependentType()) {
unsigned ConvIdx = PO == OverloadCandidateParamOrder::Reversed
? 0
: (ThisConversions + I);
unsigned ConvIdx;
if (PO == OverloadCandidateParamOrder::Reversed) {
ConvIdx = Args.size() - 1 - I;
assert(Args.size() + ThisConversions == 2 &&
"number of args (including 'this') must be exactly 2 for "
"reversed order");
// For members, there would be only one arg 'Args[0]' whose ConvIdx
// would also be 0. 'this' got ConvIdx = 1 previously.
assert(!HasThisConversion || (ConvIdx == 0 && I == 0));
} else {
// For members, 'this' got ConvIdx = 0 previously.
ConvIdx = ThisConversions + I;
}
Conversions[ConvIdx]
= TryCopyInitialization(*this, Args[I], ParamType,
SuppressUserConversions,
Expand Down Expand Up @@ -10121,11 +10131,23 @@ getImplicitObjectParamType(ASTContext &Context, const FunctionDecl *F) {
return M->getFunctionObjectParameterReferenceType();
}

static bool haveSameParameterTypes(ASTContext &Context, const FunctionDecl *F1,
const FunctionDecl *F2) {
// As a Clang extension, allow ambiguity among F1 and F2 if they represent
// represent the same entity.
static bool allowAmbiguity(ASTContext &Context, const FunctionDecl *F1,
const FunctionDecl *F2) {
if (declaresSameEntity(F1, F2))
return true;

auto PT1 = F1->getPrimaryTemplate();
auto PT2 = F2->getPrimaryTemplate();
if (PT1 && PT2) {
if (declaresSameEntity(PT1, PT2) ||
usx95 marked this conversation as resolved.
Show resolved Hide resolved
declaresSameEntity(PT1->getInstantiatedFromMemberTemplate(),
PT2->getInstantiatedFromMemberTemplate()))
return true;
}
// TODO: It is not clear whether comparing parameters is necessary (i.e.
// different functions with same params). Consider removing this (as no test
// fail w/o it).
auto NextParam = [&](const FunctionDecl *F, unsigned &I, bool First) {
if (First) {
if (std::optional<QualType> T = getImplicitObjectParamType(Context, F))
Expand Down Expand Up @@ -10329,14 +10351,14 @@ bool clang::isBetterOverloadCandidate(
case ImplicitConversionSequence::Worse:
if (Cand1.Function && Cand2.Function &&
Cand1.isReversed() != Cand2.isReversed() &&
haveSameParameterTypes(S.Context, Cand1.Function, Cand2.Function)) {
allowAmbiguity(S.Context, Cand1.Function, Cand2.Function)) {
// Work around large-scale breakage caused by considering reversed
// forms of operator== in C++20:
//
// When comparing a function against a reversed function with the same
// parameter types, if we have a better conversion for one argument and
// a worse conversion for the other, the implicit conversion sequences
// are treated as being equally good.
// When comparing a function against a reversed function, if we have a
// better conversion for one argument and a worse conversion for the
// other, the implicit conversion sequences are treated as being equally
// good.
//
// This prevents a comparison function from being considered ambiguous
// with a reversed form that is written in the same way.
Expand Down Expand Up @@ -14526,7 +14548,7 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
llvm::SmallVector<FunctionDecl*, 4> AmbiguousWith;
for (OverloadCandidate &Cand : CandidateSet) {
if (Cand.Viable && Cand.Function && Cand.isReversed() &&
haveSameParameterTypes(Context, Cand.Function, FnDecl)) {
allowAmbiguity(Context, Cand.Function, FnDecl)) {
for (unsigned ArgIdx = 0; ArgIdx < 2; ++ArgIdx) {
if (CompareImplicitConversionSequences(
*this, OpLoc, Cand.Conversions[ArgIdx],
Expand Down
Expand Up @@ -324,6 +324,124 @@ bool x = X() == X(); // expected-warning {{ambiguous}}
}
} // namespace P2468R2

namespace GH53954{
namespace friend_template_1 {
struct P {
template <class T>
friend bool operator==(const P&, const T&) { return true; } // expected-note {{candidate}} \
// expected-note {{ambiguous candidate function with reversed arguments}}
};
struct A : public P {};
struct B : public P {};
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
}

namespace friend_template_2 {
struct P {
template <class T>
friend bool operator==(const T&, const P&) { return true; } // expected-note {{candidate}} \
// expected-note {{ambiguous candidate function with reversed arguments}}
};
struct A : public P {};
struct B : public P {};
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
}

namespace friend_template_class_template {
template<class S>
struct P {
template <class T>
friend bool operator==(const T&, const P&) { // expected-note 2 {{candidate}}
return true;
}
};
struct A : public P<int> {};
struct B : public P<bool> {};
bool check(A a, B b) { return a == b; } // expected-warning {{ambiguous}}
}

namespace friend_template_fixme {
// FIXME(GH70210): This should not rewrite operator== and definitely not a hard error.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just wanted to point out that this bug is worrying as it might break some code that's not easy to rewrite.
We worked around the problem in LLVM with 77f2ccb, but you could already see how the rewrite is complicated.

I don't think we should block on that, especially given that the issue you mention requires further progress from EWG and CWG. But I would keep a close eye for people complaining about this change.

struct P {
template <class T>
friend bool operator==(const T &, const P &) { return true; } // expected-note 2 {{candidate}}
template <class T>
friend bool operator!=(const T &, const P &) { return true; } // expected-note {{candidate}}
};
struct A : public P {};
struct B : public P {};
bool check(A a, B b) { return a != b; } // expected-error{{ambiguous}}
}

namespace member_template_1 {
struct P {
template<class S>
bool operator==(const S &) const; // expected-note {{candidate}} \
// expected-note {{ambiguous candidate function with reversed arguments}}
};
struct A : public P {};
struct B : public P {};
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
} // namespace member_template

namespace member_template_2{
template <typename T>
class Foo {
public:
template <typename U = T>
bool operator==(const Foo& other) const;
};
bool x = Foo<int>{} == Foo<int>{};
} // namespace template_member_opeqeq

namespace non_member_template_1 {
struct P {};
template<class S>
bool operator==(const P&, const S &); // expected-note {{candidate}} \
// expected-note {{ambiguous candidate function with reversed arguments}}

struct A : public P {};
struct B : public P {};
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}

template<class S> bool operator!=(const P&, const S &);
bool fine(A a, B b) { return a == b; } // Ok. Found a matching operator!=.
} // namespace non_member_template_1

namespace non_member_template_2 {
struct P {};
template<class S>
bool operator==(const S&, const P&); // expected-note {{candidate}} \
// expected-note {{ambiguous candidate function with reversed arguments}}

struct A : public P {};
struct B : public P {};
bool check(A a, B b) { return a == b; } // expected-warning {{use of overloaded operator '==' (with operand types 'A' and 'B') to be ambiguous}}
} // namespace non_member_template_2

namespace class_and_member_template {
template <class T>
struct S {
template <typename OtherT>
bool operator==(const OtherT &rhs); // expected-note {{candidate}} \
// expected-note {{reversed arguments}}
};
struct A : S<int> {};
struct B : S<bool> {};
bool x = A{} == B{}; // expected-warning {{ambiguous}}
} // namespace class_and_member_template

namespace ambiguous_case {
template <class T>
struct Foo {};
template <class T, class U> bool operator==(Foo<U>, Foo<T*>); // expected-note{{candidate}}
template <class T, class U> bool operator==(Foo<T*>, Foo<U>); // expected-note{{candidate}}

void test() {
Foo<int*>() == Foo<int*>(); // expected-error{{ambiguous}}
}
} // namespace ambiguous_case
} // namespace
namespace ADL_GH68901{
namespace test1 {
namespace A {
Expand Down