On Wed, 23 Jul 2025, Patrick Palka wrote:

> On Wed, 23 Jul 2025, Jason Merrill wrote:
> 
> > On 7/23/25 3:46 PM, Patrick Palka wrote:
> > > As a follow-up to r16-2448-g7590c14b53a762, this patch attempts to teach
> > > build_min_non_dep_op_overload how to rebuild all rewritten comparison
> > > operators, not just != -> == ones, so that we don't incorrectly repeat
> > > the unqualified name lookup at instantiation time.
> > 
> > Talking about mangling earlier made me wonder how we were handling
> > non-dependent operator expressions, and indeed it seems we get it wrong 
> > since
> > GCC 6:
> > 
> > struct A { };
> > A operator+(A,A);
> > template <class T>
> > void f(decltype(T(),A()+A())) { }
> > int main()
> > {
> >   f<int>(A()); // oops, mangles as operator+(A(),A()) instead of A()+A()
> > }
> > 
> > while clang and EDG corretly use the latter mangling.
> > 
> > With the current code I would think we could fix this by handling
> > CALL_EXPR_OPERATOR_SYNTAX in mangle.cc, but your patch (and indeed the 
> > earlier
> > one) would further obscure the original syntax.
> 
> Does this mean it's also incorrect to mangle the ordinary non-dependent f(0)
> call in:
> 
>     template<class T> void f(T);
> 
>     template<class T> decltype(T(),f(0)) g();
> 
>     int main() {
>       g<int>();
>     }
> 
> as f<int>(0) i.e. with an explicit template argument list even though it was
> written without one?  Clang mangles it as f<int>(0) too, not sure about EDG.
> This changed in GCC 12 with the non-dependent overload set pruning 
> optimization.
> 
> And does this have any declaration matching implications?  Say for
> 
>     struct A { };
> 
>     template<class T> int operator+(A,T);
>     template<class T> decltype(T(),A()+A()) f();
> 
>     A operator+(A,A);
>     template<class T> decltype(T(),A()+A()) f();
> 
>     int main() {
>       f<int>();
>     }
> 
> should we still reject the f<int>() call as ambiguous, or treat the second
> declaration as a redeclaration (since they have the same mangling?)  This 
> seems
> related to CWG1321 but for non-dependent calls.
> 
> > 
> > > While working on this I noticed we'll seemingly never create a rewritten
> > > operator expression that is in terms of a built-in operator, since we
> > > could have used a built-in operator directly in the first place, which
> > > simplifies things.  I think this also means the extract_call_expr
> > > handling of rewritten operators is wrong since it inspects for LT_EXPR,
> > > SPACESHIP_EXPR etc directly, so this patch just removes it in passing.
> > 
> > That code is not about rewriting in terms of a built-in operator, it was to
> > look through the operations added by the rewriting, e.g. TRUTH_NOT_EXPR for
> > operator!= to !(operator==) to find the actual call to the operator
> > underneath.
> 
> The TRUTH_NOT_EXPR case seems fine, but AFAICT the LT_EXPR, GT_EXPR etc cases
> are dead code because we'll never have an LT/GT/etc_EXPR of an operator<=> 
> call,
> since operator<=> must return std::strong/weak/partial_ordering which are 
> class
> types, and so 0 < (x <=> y) must always resolve to a user-defined operator< 
> etc.
> 
> Oh wait, that'll only be true after the rest of the patch is applied... 
> otherwise
> non-dependent templated rewritten operator expressions will indeed contain
> LT/GT/etc_EXPR.
> 
> > 
> > It does look like that's unnecessary now because build_new_op calls
> > extract_call_expr before adding those decorations, so I don't object to
> > removing it, but please make that a separate patch.
> 
> Sounds good.

Here's v3 which omits the extract_call_expr removal.

I'm not sure how or if we want to address the mangling concern.
With this patch we'll now at least our non-dependent operator
expression mangling will be consistent :)

-- >8 --

Subject: [PATCH] c++: more name lookup for non-dep rewritten cmp ops

gcc/cp/ChangeLog:

        * call.cc (build_new_op): If the selected candidate is
        rewritten, communicate the LOOKUP_REWRITTEN/REVERSED flags to
        the caller via the *overload out-parameter, and stop clearing
        *overload in that case.
        * tree.cc (build_min_non_dep_op_overload): Handle rebuilding all
        C++20 rewritten comparison operator expressions.

gcc/testsuite/ChangeLog:

        * g++.dg/lookup/operator-8.C: Remove XFAILs and properly
        suppress all -Wunused-result warnings.
---
 gcc/cp/call.cc                           | 16 +++--
 gcc/cp/tree.cc                           | 85 ++++++++++++++++++++----
 gcc/testsuite/g++.dg/lookup/operator-8.C | 16 +++--
 3 files changed, 92 insertions(+), 25 deletions(-)

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index c925dd18ab41..825d171fdeb0 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -7486,7 +7486,16 @@ build_new_op (const op_location_t &loc, enum tree_code 
code, int flags,
       else if (TREE_CODE (cand->fn) == FUNCTION_DECL)
        {
          if (overload)
-           *overload = cand->fn;
+           {
+             if (cand->rewritten ())
+               /* build_min_non_dep_op_overload needs to know whether the
+                  candidate is rewritten/reversed.  */
+               *overload = build_tree_list (build_int_cst (integer_type_node,
+                                                           cand->flags),
+                                            cand->fn);
+             else
+               *overload = cand->fn;
+           }
 
          if (resolve_args (arglist, complain) == NULL)
            result = error_mark_node;
@@ -7535,11 +7544,6 @@ build_new_op (const op_location_t &loc, enum tree_code 
code, int flags,
          /* If this was a C++20 rewritten comparison, adjust the result.  */
          if (cand->rewritten ())
            {
-             /* FIXME build_min_non_dep_op_overload can't handle rewrites.  */
-             if (code == NE_EXPR && !cand->reversed ())
-               /* It can handle != rewritten to == though.  */;
-             else if (overload)
-               *overload = NULL_TREE;
              switch (code)
                {
                case EQ_EXPR:
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index c260efb7f6ba..50659c2de8be 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -3696,7 +3696,58 @@ build_min_non_dep_op_overload (enum tree_code op,
   int nargs, expected_nargs;
   tree fn, call, obj = NULL_TREE;
 
-  bool negated = (TREE_CODE (non_dep) == TRUTH_NOT_EXPR);
+  releasing_vec args;
+  va_start (p, overload);
+
+  bool negated = false, rewritten = false, reversed = false;
+  if (cxx_dialect >= cxx20 && TREE_CODE (overload) == TREE_LIST)
+    {
+      /* Handle rebuilding a C++20 rewritten comparison operator expression,
+        e.g. !(x == y), y <=> x, (x <=> y) @ 0, etc, that resolved to a call
+        to a user-defined operator<=>/==.  */
+      gcc_checking_assert (TREE_CODE_CLASS (op) == tcc_comparison
+                          || op == SPACESHIP_EXPR);
+      int flags = TREE_INT_CST_LOW (TREE_PURPOSE (overload));
+      if (TREE_CODE (non_dep) == TRUTH_NOT_EXPR)
+       {
+         negated = true;
+         non_dep = TREE_OPERAND (non_dep, 0);
+       }
+      if (flags & LOOKUP_REWRITTEN)
+       rewritten = true;
+      if (flags & LOOKUP_REVERSED)
+       reversed = true;
+      if (rewritten
+         && DECL_OVERLOADED_OPERATOR_IS (TREE_VALUE (overload),
+                                         SPACESHIP_EXPR))
+       {
+         /* Handle (x <=> y) @ 0 and 0 @ (y <=> x) by recursing to first
+            rebuild the <=>.  Note that both OVERLOAD and the provided 
arguments
+            in this case correspond to the selected operator<=>.  */
+
+         tree spaceship_non_dep = CALL_EXPR_ARG (non_dep, reversed ? 1 : 0);
+         gcc_checking_assert (TREE_CODE (spaceship_non_dep) == CALL_EXPR);
+         tree spaceship_op0 = va_arg (p, tree);
+         tree spaceship_op1 = va_arg (p, tree);
+         if (reversed)
+           std::swap (spaceship_op0, spaceship_op1);
+
+         /* Push the correct arguments for the operator OP expression, and set
+            OVERLOAD appropriately.  */
+         tree op0 = build_min_non_dep_op_overload (SPACESHIP_EXPR,
+                                                   spaceship_non_dep,
+                                                   TREE_VALUE (overload),
+                                                   spaceship_op0,
+                                                   spaceship_op1);
+         tree op1 = CALL_EXPR_ARG (non_dep, reversed ? 0 : 1);
+         gcc_checking_assert (integer_zerop (op1));
+         vec_safe_push (args, op0);
+         vec_safe_push (args, op1);
+         overload = CALL_EXPR_FN (non_dep);
+       }
+      else
+       overload = TREE_VALUE (overload);
+    }
   non_dep = extract_call_expr (non_dep);
 
   nargs = call_expr_nargs (non_dep);
@@ -3717,32 +3768,40 @@ build_min_non_dep_op_overload (enum tree_code op,
     expected_nargs += 1;
   gcc_assert (nargs == expected_nargs);
 
-  releasing_vec args;
-  va_start (p, overload);
-
   if (!DECL_OBJECT_MEMBER_FUNCTION_P (overload))
     {
       fn = overload;
-      if (op == ARRAY_REF)
-       obj = va_arg (p, tree);
-      for (int i = 0; i < nargs; i++)
+      if (vec_safe_length (args) != 0)
+       /* The correct arguments were already pushed above.  */
+       gcc_checking_assert (rewritten);
+      else
        {
-         tree arg = va_arg (p, tree);
-         vec_safe_push (args, arg);
+         if (op == ARRAY_REF)
+           obj = va_arg (p, tree);
+         for (int i = 0; i < nargs; i++)
+           {
+             tree arg = va_arg (p, tree);
+             vec_safe_push (args, arg);
+           }
        }
+      if (reversed)
+       std::swap ((*args)[0], (*args)[1]);
     }
   else
     {
+      gcc_checking_assert (vec_safe_length (args) == 0);
       tree object = va_arg (p, tree);
-      tree binfo = TYPE_BINFO (TREE_TYPE (object));
-      tree method = build_baselink (binfo, binfo, overload, NULL_TREE);
-      fn = build_min (COMPONENT_REF, TREE_TYPE (overload),
-                     object, method, NULL_TREE);
       for (int i = 0; i < nargs; i++)
        {
          tree arg = va_arg (p, tree);
          vec_safe_push (args, arg);
        }
+      if (reversed)
+       std::swap (object, (*args)[0]);
+      tree binfo = TYPE_BINFO (TREE_TYPE (object));
+      tree method = build_baselink (binfo, binfo, overload, NULL_TREE);
+      fn = build_min (COMPONENT_REF, TREE_TYPE (overload),
+                     object, method, NULL_TREE);
     }
 
   va_end (p);
diff --git a/gcc/testsuite/g++.dg/lookup/operator-8.C 
b/gcc/testsuite/g++.dg/lookup/operator-8.C
index 7fe6a57061bd..32d432dd8432 100644
--- a/gcc/testsuite/g++.dg/lookup/operator-8.C
+++ b/gcc/testsuite/g++.dg/lookup/operator-8.C
@@ -16,12 +16,16 @@ struct A {
 template<class T>
 void f() {
   A a;
-  (void)(a != 0);         // We only handle this simple case, after PR121179
-  (void)(0 != a);         // { dg-bogus "deleted" "" { xfail *-*-* } }
-  (void)(a < 0, 0 < a);   // { dg-bogus "deleted" "" { xfail *-*-* } }
-  (void)(a <= 0, 0 <= a); // { dg-bogus "deleted" "" { xfail *-*-* } }
-  (void)(a > 0, 0 > a);   // { dg-bogus "deleted" "" { xfail *-*-* } }
-  (void)(a >= 0, 0 >= a); // { dg-bogus "deleted" "" { xfail *-*-* } }
+  (void)(a != 0);
+  (void)(0 != a);
+  (void)(a < 0);
+  (void)(0 < a);
+  (void)(a <= 0);
+  (void)(0 <= a);
+  (void)(a > 0);
+  (void)(0 > a);
+  (void)(a >= 0);
+  (void)(0 >= a);
 }
 
 // These later-declared namespace-scope overloads shouldn't be considered
-- 
2.50.1.319.g90c0775e97

Reply via email to