https://gcc.gnu.org/g:7590c14b53a762ab30f5026148bd1cb9cf142264
commit r16-2448-g7590c14b53a762ab30f5026148bd1cb9cf142264 Author: Patrick Palka <ppa...@redhat.com> Date: Wed Jul 23 08:38:12 2025 -0400 c++: name lookup for non-dep rewritten != expr [PR121179] Here we're incorrectly rejecting the modules testcase (reduced from a std module example): $ cat 121179_a.C export module foo; enum class E { x }; bool operator==(E, int); export template<class T> void f() { E::x != 0; } $ cat 121179_b.C import foo; template void f<int>(); $ g++ -fmodules 121179_*.C In module foo, imported at 121179_b.C:1: 121179_a.C: In instantiation of ‘void f@foo() [with T = int]’: 121179_b.C:3:9: required from here 121179_a.C:9:8: error: no match for ‘operator!=’ (operand types are ‘E@foo’ and ‘int’) This is ultimately because our non-dependent rewritten operator expression handling throws away the result of unqualified lookup at template parse time, and so we have to repeat the lookup at instantiation time which fails because the operator== isn't exported. This is a known deficiency, but it's easy enough to narrowly fix this for simple != to == rewrites by making build_min_non_dep_op_overload look through logical negation. PR c++/121179 gcc/cp/ChangeLog: * call.cc (build_new_op): Don't clear *overload for a simple != to == rewrite. * tree.cc (build_min_non_dep_op_overload): Handle TRUTH_NOT_EXPR appearing in a rewritten operator expression. gcc/testsuite/ChangeLog: * g++.dg/lookup/operator-8.C: Strengthen test and remove one XFAIL. Reviewed-by: Jason Merrill <ja...@redhat.com> Diff: --- gcc/cp/call.cc | 4 +++- gcc/cp/tree.cc | 3 +++ gcc/testsuite/g++.dg/lookup/operator-8.C | 9 ++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index 37ad0a977c22..c925dd18ab41 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -7536,7 +7536,9 @@ build_new_op (const op_location_t &loc, enum tree_code code, int flags, if (cand->rewritten ()) { /* FIXME build_min_non_dep_op_overload can't handle rewrites. */ - if (overload) + if (code == NE_EXPR && !cand->reversed ()) + /* It can handle != rewritten to == though. */; + else if (overload) *overload = NULL_TREE; switch (code) { diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index a7b890884abd..c260efb7f6ba 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -3696,6 +3696,7 @@ 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); non_dep = extract_call_expr (non_dep); nargs = call_expr_nargs (non_dep); @@ -3753,6 +3754,8 @@ build_min_non_dep_op_overload (enum tree_code op, CALL_EXPR_ORDERED_ARGS (call_expr) = CALL_EXPR_ORDERED_ARGS (non_dep); CALL_EXPR_REVERSE_ARGS (call_expr) = CALL_EXPR_REVERSE_ARGS (non_dep); + if (negated) + call = build_min (TRUTH_NOT_EXPR, boolean_type_node, call); if (obj) return keep_unused_object_arg (call, obj, overload); return call; diff --git a/gcc/testsuite/g++.dg/lookup/operator-8.C b/gcc/testsuite/g++.dg/lookup/operator-8.C index 64d8a97cdd00..7fe6a57061bd 100644 --- a/gcc/testsuite/g++.dg/lookup/operator-8.C +++ b/gcc/testsuite/g++.dg/lookup/operator-8.C @@ -16,7 +16,8 @@ struct A { template<class T> void f() { A a; - (void)(a != 0, 0 != a); // { dg-bogus "deleted" "" { xfail *-*-* } } + (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 *-*-* } } @@ -31,4 +32,10 @@ bool operator<=(A, int) = delete; bool operator>(A, int) = delete; bool operator>=(A, int) = delete; +bool operator!=(int, A) = delete; +bool operator<(int, A) = delete; +bool operator<=(int, A) = delete; +bool operator>(int, A) = delete; +bool operator>=(int, A) = delete; + template void f<int>();