On Tue, 29 Jul 2025, Nathaniel Shead wrote:

> Tested on x86_64-pc-linux-gnu, OK for trunk if full bootstrap+regtest
> passes?
> 
> -- >8 --
> 
> For the sake of determining if there are other errors in user code to
> report early, many trait functions don't return error_mark_node if not
> called in a SFINAE context (i.e., tf_error is set).  This patch removes
> some assumptions on this behaviour I'd made when improving diagnostics
> of builtin traits.
> 
>       PR c++/121291
> 
> gcc/cp/ChangeLog:
> 
>       * constraint.cc (diagnose_trait_expr): Remove assumption about
>       failures returning error_mark_node.
>       * method.cc (build_invoke): Adjust comment.
>       (is_nothrow_convertible): Check for errorcount changes.
> 
> gcc/testsuite/ChangeLog:
> 
>       * g++.dg/ext/is_invocable7.C: New test.
>       * g++.dg/ext/is_nothrow_convertible5.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
> ---
>  gcc/cp/constraint.cc                          |  9 +++++---
>  gcc/cp/method.cc                              |  9 ++++++--
>  gcc/testsuite/g++.dg/ext/is_invocable7.C      | 21 +++++++++++++++++++
>  .../g++.dg/ext/is_nothrow_convertible5.C      | 15 +++++++++++++
>  4 files changed, 49 insertions(+), 5 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/ext/is_invocable7.C
>  create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_convertible5.C
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index d4a83e429e5..94238c9e8f1 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -3176,8 +3176,7 @@ diagnose_trait_expr (location_t loc, tree expr, tree 
> args)
>         inform (loc, "%qT is not invocable, because", t1);
>       else
>         inform (loc, "%qT is not invocable by %qT, because", t1, t2);
> -     tree call = build_invoke (t1, t2, tf_error);
> -     gcc_assert (call == error_mark_node);
> +     build_invoke (t1, t2, tf_error);
>        }
>        break;
>      case CPTK_IS_LAYOUT_COMPATIBLE:
> @@ -3221,8 +3220,12 @@ diagnose_trait_expr (location_t loc, tree expr, tree 
> args)
>         inform (loc, "%qT is not nothrow invocable, because", t1);
>       else
>         inform (loc, "%qT is not nothrow invocable by %qT, because", t1, t2);
> +     /* We can't rely on build_invoke returning error_mark_node, because
> +        when passing tf_error in some cases a valid call may be returned
> +        anyway.  */
> +     int errs = errorcount + sorrycount;
>       tree call = build_invoke (t1, t2, tf_error);
> -     if (call != error_mark_node)
> +     if (call != error_mark_node && errs == errorcount + sorrycount)
>         explain_not_noexcept (call);

What do you think about just checking expr_noexcept_p here instead (or
removing the assert in explain_not_noexcept)?  So that if a call is not
nothrow invocable because the selected candidate is private _and_
because it's not noexcept, we'd report both errors.  I see that
tsubst_compound_requirement already behaves this way.

>        }
>        break;
> diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> index 62f8d80c781..cb2ea8db18d 100644
> --- a/gcc/cp/method.cc
> +++ b/gcc/cp/method.cc
> @@ -1952,7 +1952,8 @@ build_trait_object (tree type, tsubst_flags_t complain)
>  }
>  
>  /* [func.require] Build an expression of INVOKE(FN_TYPE, ARG_TYPES...).  If 
> the
> -   given is not invocable, returns error_mark_node.  */
> +   given is not invocable, returns error_mark_node, unless COMPLAIN includes
> +   tf_error.  */
>  
>  tree
>  build_invoke (tree fn_type, const_tree arg_types, tsubst_flags_t complain)
> @@ -2598,11 +2599,15 @@ is_convertible (tree from, tree to, bool 
> explain/*=false*/)
>  bool
>  is_nothrow_convertible (tree from, tree to, bool explain/*=false*/)
>  {
> +  /* as with is_nothrow_xible.  */
> +  int errs = errorcount + sorrycount;
> +
>    tree expr = is_convertible_helper (from, to, explain);
>    if (expr == NULL_TREE || expr == error_mark_node)
>      return false;
> +
>    bool is_noexcept = expr_noexcept_p (expr, tf_none);
> -  if (explain)
> +  if (explain && errs == (errorcount + sorrycount))
>      {
>        gcc_assert (!is_noexcept);
>        explain_not_noexcept (expr);
> diff --git a/gcc/testsuite/g++.dg/ext/is_invocable7.C 
> b/gcc/testsuite/g++.dg/ext/is_invocable7.C
> new file mode 100644
> index 00000000000..5c852fc8428
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_invocable7.C
> @@ -0,0 +1,21 @@
> +// PR c++/121291
> +// { dg-do compile { target c++17 } }
> +
> +template <typename T>
> +constexpr bool is_invocable = __is_invocable(T);
> +
> +template <typename T>
> +constexpr bool is_nothrow_invocable = __is_nothrow_invocable(T);
> +
> +struct S {
> +private:
> +  int operator()() noexcept;  // { dg-message "here" }
> +};
> +
> +static_assert(is_invocable<S>);  // { dg-error "assert" }
> +// { dg-message "not invocable" "" { target *-*-* } .-1 }
> +// { dg-error "private within this context" "" { target *-*-* } .-2 }
> +
> +static_assert(is_nothrow_invocable<S>);  // { dg-error "assert" }
> +// { dg-message "not nothrow invocable" "" { target *-*-* } .-1 }
> +// { dg-error "private within this context" "" { target *-*-* } .-2 }
> diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_convertible5.C 
> b/gcc/testsuite/g++.dg/ext/is_nothrow_convertible5.C
> new file mode 100644
> index 00000000000..0ce8fb82191
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_nothrow_convertible5.C
> @@ -0,0 +1,15 @@
> +// PR c++/121291
> +// { dg-do compile { target c++17 } }
> +
> +template <typename T, typename U>
> +constexpr bool is_nothrow_convertible = __is_nothrow_convertible(T, U);
> +
> +struct A {};
> +struct B {
> +private:
> +  operator A() noexcept;  // { dg-message "here" }
> +};
> +
> +static_assert(is_nothrow_convertible<B, A>);  // { dg-error "assert" }
> +// { dg-message "not nothrow convertible" "" { target *-*-* } .-1 }
> +// { dg-error "private within this context" "" { target *-*-* } .-2 }
> -- 
> 2.47.0
> 
> 

Reply via email to