https://gcc.gnu.org/g:85ba6b0eb100999a3e34a7731fc5977a139f117e
commit r16-9051-g85ba6b0eb100999a3e34a7731fc5977a139f117e Author: Patrick Palka <[email protected]> Date: Wed Jun 3 18:03:14 2026 -0400 c++: unchecked 'A op B' constraint-expression [PR125490] Here the nonsensical constraint-expression f() == 42 isn't getting rejected ahead of time because when parsing we pass no_toplevel_fold_p=true to cp_parser_binary_expression, which makes it use build_min instead of build_x_binary_op, effectively bypassing the actual processing of the operator expression until satisfaction time. This flag was introduced by r145014 to avoid undesirable folding / canonicalization of OpenMP for loop conditions, and that's still the only other user of the flag today. But I think cp_parser_constraint_expression doesn't need to use this flag at all because constraint-expression parsing is always templated and we (generally) don't fold templated trees. I think the flag is intended to be used only by OpenMP parsing routines. Note that without this patch we'd go on to reject the constraint-expression at satisfaction time, so this first testcase is effectively IFNDR / QoI. But this change isn't limited to IFNDR cases, we need to process the operator expression at parse time for sake of correct unqualified lookup as in the second testcase. PR c++/125490 gcc/cp/ChangeLog: * parser.cc (cp_parser_constraint_expression): Pass no_toplevel_fold_p=false instead of =true to cp_parser_binary_expression. gcc/testsuite/ChangeLog: * g++.dg/cpp2a/concepts-pr125490.C: New test. * g++.dg/cpp2a/concepts-pr125490a.C: New test. Reviewed-by: Jason Merrill <[email protected]> (cherry picked from commit f7990da34872cfb448d4d1341714508904cb666c) Diff: --- gcc/cp/parser.cc | 2 +- gcc/testsuite/g++.dg/cpp2a/concepts-pr125490.C | 10 ++++++++++ gcc/testsuite/g++.dg/cpp2a/concepts-pr125490a.C | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index cc3ee315436a..f44450519ae3 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -34725,7 +34725,7 @@ cp_parser_constraint_expression (cp_parser *parser) { processing_constraint_expression_sentinel parsing_constraint; ++processing_template_decl; - cp_expr expr = cp_parser_binary_expression (parser, false, true, + cp_expr expr = cp_parser_binary_expression (parser, false, false, PREC_NOT_OPERATOR, NULL); --processing_template_decl; if (check_for_bare_parameter_packs (expr)) diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-pr125490.C b/gcc/testsuite/g++.dg/cpp2a/concepts-pr125490.C new file mode 100644 index 000000000000..a33416b232d1 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-pr125490.C @@ -0,0 +1,10 @@ +// PR c++/125490 +// { dg-do compile { target c++20 } } + +void f(); + +template<class T> +concept C = f() == 42; // { dg-error "invalid operands" } + +template<class T> +concept D = requires { requires f() == 42; }; // { dg-error "invalid operands" } diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-pr125490a.C b/gcc/testsuite/g++.dg/cpp2a/concepts-pr125490a.C new file mode 100644 index 000000000000..b1208fea7c96 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-pr125490a.C @@ -0,0 +1,15 @@ +// PR c++/125490 +// { dg-do compile { target c++20 } } + +namespace N { + struct A { }; +} + +constexpr bool operator==(N::A, auto) { return true; } // #1 + +template<class T> +concept C = T{} == T{}; + +constexpr bool operator==(N::A, N::A) { return false; } // #2 + +static_assert(C<N::A>); // must only consider #1
