diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 879721c71add3..a1143e14562e3 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -56,6 +56,10 @@ C/C++ Language Potentially Breaking Changes array members for structs that contain them. This change is more consistent with the behavior of GCC. +- Fixed a bug in finding matching `operator!=` while adding reversed `operator==` as + outlined in "The Equality Operator You Are Looking For" (`P2468 `_). + Fixes (`#68901: `_). + C++ Specific Potentially Breaking Changes ----------------------------------------- - Clang won't search for coroutine_traits in std::experimental namespace any more. diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index a3d9abb153778..aef8dc58a48db 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -957,18 +957,13 @@ static bool shouldAddReversedEqEq(Sema &S, SourceLocation OpLoc, 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(Op)) - FD = UD->getUnderlyingDecl()->getAsFunction(); - if (FunctionsCorrespond(S.Context, EqFD, FD) && - declaresSameEntity(cast(EqFD->getDeclContext()), - cast(Op->getDeclContext()))) + for (NamedDecl *Op : EqFD->getEnclosingNamespaceContext()->lookup(NotEqOp)) { + auto *NotEqFD = Op->getAsFunction(); + if (auto *UD = dyn_cast(Op)) + NotEqFD = UD->getUnderlyingDecl()->getAsFunction(); + if (FunctionsCorrespond(S.Context, EqFD, NotEqFD) && S.isVisible(NotEqFD) && + declaresSameEntity(cast(EqFD->getEnclosingNamespaceContext()), + cast(Op->getLexicalDeclContext()))) return false; } return true; diff --git a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p2468R2.cppm b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p2468R2.cppm new file mode 100644 index 0000000000000..05891618991da --- /dev/null +++ b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p2468R2.cppm @@ -0,0 +1,24 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: split-file %s %t +// +// RUN: %clang_cc1 -std=c++20 %t/A.cppm -emit-module-interface -o %t/A.pcm +// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t -I%t %t/p2468r2.cpp -verify + +//--- A.cppm +module; +export module A; +export { +namespace NS { +struct S {}; +bool operator==(S, int); +} // namespace NS +} + +namespace NS { bool operator!=(S, int); } // Not visible. + + +//--- p2468r2.cpp +// expected-no-diagnostics +import A; +bool x = 0 == NS::S(); // Ok. operator!= from module A is not visible. diff --git a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp index 5c6804eb7726b..016eaf7f52876 100644 --- a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp +++ b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp @@ -324,6 +324,170 @@ bool x = X() == X(); // expected-warning {{ambiguous}} } } // namespace P2468R2 +namespace GH53954{ +namespace friend_template_1 { +struct P { + template + friend bool operator==(const P&, const T&); // 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 + friend bool operator==(const T&, 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 member_template { +struct P { + template + 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 non_member_template_1 { +struct P {}; +template +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 +bool operator!=(const P&, const S &); +bool fine(A a, B b) { return a == b; } // Ok. Found a matching operator!=. +} +} + + +namespace ADL_GH68901{ +namespace test1 { +namespace A { +struct S {}; +bool operator==(S, int); // expected-note {{no known conversion from 'int' to 'S' for 1st argument}} +bool a = 0 == A::S(); // Ok. Operator!= not visible. +bool operator!=(S, int); +} // namespace A +bool a = 0 == A::S(); // expected-error {{invalid operands to binary expression ('int' and 'A::S')}} +} // namespace test1 + +namespace test2 { +namespace B { +struct Derived {}; +struct Base : Derived {}; + +bool operator==(Derived& a, Base& b); +bool operator!=(Derived& a, Base& b); +} // namespace B + +bool foo() { + B::Base a,b; + return a == b; +} +} // namespace test2 + + +namespace template_ { +namespace ns { +template struct A {}; +template struct B : A {}; + +template bool operator==(B, A); // expected-note {{candidate template ignored: could not match 'B' against 'A'}} +template bool operator!=(B, A); +} + +void test() { + ns::A a; + ns::B b; + a == b; // expected-error {{invalid operands to binary expression}} +} +} // namespace test3 + +namespace using_not_eq { +namespace A { +struct S {}; +namespace B { +bool operator!=(S, int); +} +bool operator==(S, int); // expected-note {{candidate}} +using B::operator!=; +} // namespace A +bool a = 0 == A::S(); // expected-error {{invalid operands to binary expression}} +} // namespace reversed_lookup_not_like_ADL + +namespace using_eqeq { +namespace A { +struct S {}; +namespace B { +bool operator==(S, int); // expected-note {{candidate}} +bool operator!=(S, int); +} +using B::operator==; +} // namespace A +bool a = 0 == A::S(); // expected-error {{invalid operands to binary expression}} +} + +} //namespace ADL_GH68901 + +namespace function_scope_operator_eqeq { +// For non-members, we always lookup for matching operator!= in the namespace scope of +// operator== (and not in the scope of operator==). +struct X { operator int(); }; +namespace test1{ +bool h(X x) { + bool operator==(X, int); // expected-note {{reversed}} + return x == x; // expected-warning {{ambiguous}} +} + +bool g(X x) { + bool operator==(X, int); // expected-note {{reversed}} + bool operator!=(X, int); + return x == x; // expected-warning {{ambiguous}} +} +} // namespace test1 + +namespace test2 { +bool operator!=(X, int); + +bool h(X x) { + bool operator==(X, int); + return x == x; +} + +bool i(X x) { + bool operator==(X, int); + bool operator!=(X, int); + return x == x; +} +} // namespace test2 +} // namespace function_scope_operator_eqeq + +namespace non_member_template_2 { +struct P {}; +template +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}} +} + #else // NO_ERRORS namespace problem_cases {