https://gcc.gnu.org/g:bd5597156ca0c7d6fb50c6fe92a7abe6717cb2b5

commit r15-9186-gbd5597156ca0c7d6fb50c6fe92a7abe6717cb2b5
Author: Jason Merrill <ja...@redhat.com>
Date:   Tue Apr 1 13:04:05 2025 -0400

    c++: operator!= rewriting and arg-dep lookup
    
    When considering an op== as a rewrite target, we need to disqualify it if
    there is a matching op!= in the same scope.  But add_candidates was assuming
    that we could use the same set of op!= for all op==, which is wrong if
    arg-dep lookup finds op== in multiple namespaces.
    
    This broke 20_util/optional/relops/constrained.cc if the order of the ADL
    set changed.
    
    gcc/cp/ChangeLog:
    
            * call.cc (add_candidates): Re-lookup ne_fns if we move into
            another namespace.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp2a/spaceship-rewrite6.C: New test.

Diff:
---
 gcc/cp/call.cc                                  | 27 +++++++++++++++++---
 gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite6.C | 33 +++++++++++++++++++++++++
 2 files changed, 57 insertions(+), 3 deletions(-)

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index d8533623c015..6caac8963cc9 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -6673,6 +6673,7 @@ add_candidates (tree fns, tree first_arg, const vec<tree, 
va_gc> *args,
   bool check_list_ctor = false;
   bool check_converting = false;
   unification_kind_t strict;
+  tree ne_context = NULL_TREE;
   tree ne_fns = NULL_TREE;
 
   if (!fns)
@@ -6719,6 +6720,7 @@ add_candidates (tree fns, tree first_arg, const vec<tree, 
va_gc> *args,
       tree ne_name = ovl_op_identifier (false, NE_EXPR);
       if (DECL_CLASS_SCOPE_P (fn))
        {
+         ne_context = DECL_CONTEXT (fn);
          ne_fns = lookup_fnfields (TREE_TYPE ((*args)[0]), ne_name,
                                    1, tf_none);
          if (ne_fns == error_mark_node || ne_fns == NULL_TREE)
@@ -6728,8 +6730,9 @@ add_candidates (tree fns, tree first_arg, const vec<tree, 
va_gc> *args,
        }
       else
        {
-         tree context = decl_namespace_context (fn);
-         ne_fns = lookup_qualified_name (context, ne_name, LOOK_want::NORMAL,
+         ne_context = decl_namespace_context (fn);
+         ne_fns = lookup_qualified_name (ne_context, ne_name,
+                                         LOOK_want::NORMAL,
                                          /*complain*/false);
          if (ne_fns == error_mark_node
              || !is_overloaded_fn (ne_fns))
@@ -6828,8 +6831,26 @@ add_candidates (tree fns, tree first_arg, const 
vec<tree, va_gc> *args,
 
       /* When considering reversed operator==, if there's a corresponding
         operator!= in the same scope, it's not a rewrite target.  */
-      if (ne_fns)
+      if (ne_context)
        {
+         if (TREE_CODE (ne_context) == NAMESPACE_DECL)
+           {
+             /* With argument-dependent lookup, fns can span multiple
+                namespaces; make sure we look in the fn's namespace for a
+                corresponding operator!=.  */
+             tree fn_ns = decl_namespace_context (fn);
+             if (fn_ns != ne_context)
+               {
+                 ne_context = fn_ns;
+                 tree ne_name = ovl_op_identifier (false, NE_EXPR);
+                 ne_fns = lookup_qualified_name (ne_context, ne_name,
+                                                 LOOK_want::NORMAL,
+                                                 /*complain*/false);
+                 if (ne_fns == error_mark_node
+                     || !is_overloaded_fn (ne_fns))
+                   ne_fns = NULL_TREE;
+               }
+           }
          bool found = false;
          for (lkp_iterator ne (ne_fns); !found && ne; ++ne)
            if (0 && !ne.using_p ()
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite6.C 
b/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite6.C
new file mode 100644
index 000000000000..0ec74e891024
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite6.C
@@ -0,0 +1,33 @@
+// { dg-do compile { target c++20 } }
+
+// We wrongly considered D to be ne_comparable because we were looking for a
+// corresponding op!= for N::op== in ::, because ::op== happened to be the
+// first thing in the lookup set.
+
+template<bool, typename _Tp = void>
+struct enable_if;
+
+template<typename _Tp>
+struct enable_if<true, _Tp>
+{ typedef _Tp type; };
+
+template <class T, class U> struct A { };
+
+namespace N {
+  struct X { };
+  template <class T> auto operator== (const A<T,X>&, const A<T,X>&)
+    -> typename enable_if<sizeof(T() == T()), bool>::type;
+  template <class T> auto operator!= (const A<T,X>&, const A<T,X>&)
+    -> typename enable_if<sizeof(T() != T()), bool>::type;
+}
+
+template<typename T, typename U = T>
+concept ne_comparable
+= requires (const A<T,N::X>& t, const A<U,N::X>& u) {
+  t != u;
+};
+
+struct D { };
+int operator==(D, D);
+bool operator!=(D, D) = delete;
+static_assert( ! ne_comparable<D> );

Reply via email to