On Thu, Jul 24, 2025 at 09:02:24PM +1000, Nathaniel Shead wrote:
> On Tue, Jul 08, 2025 at 12:51:37PM -0400, Jason Merrill wrote:
> > I'm resistant to moving functions around unnecessarily, as it makes git
> > change tracking a lot harder.  Especially when it means moving a function to
> > a file that's already 4x the size of the one you're moving it from.
> > 
> > Instead, I would prefer to add maybe_diagnose_standard_trait to
> > constraint.cc.
> > 
> > v4 is OK with that adjustment.
> > 
> > Jason
> > 
> 
> Fair enough, adjusted.
> 
> I've been away a few weeks, and there were a number of merge conflicts I
> had to resolve first so posting again in case you'd like to re-review
> before I submit.
> 
> I also ran into an ICE for an adjusted libstdc++ testcase which I added
> a testcase for in 'is_destructible3', because it turns out that call
> resolution behaves differently with 'complain & tf_error' and will not
> always return 'error_mark_node', which comes up with e.g. access
> violations.
> 
> I suppose this is to enable further diagnostics to occur when not in a
> SFINAE context?  Rather than adjusting call.cc I've adjusted
> 'is_trivially_xible' and 'is_nothrow_xible' to handle this on that end
> instead, let me know if you have any other thoughts here.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
> 

Sorry, just realised I attached an old version of the patch there;
here's the real version.

-- >8 --

Currently, concept failures of standard type traits just report
'expression X<T> evaluates to false'.  However, many type traits are
actually defined in terms of compiler builtins; we can do better here.
For instance, 'is_constructible_v' could go on to explain why the type
is not constructible, or 'is_invocable_v' could list potential
candidates.

Apart from concept diagnostics, this is also useful when using such
traits in a 'static_assert' directly, so this patch also adjusts the
diagnostics in that context.

As a first step to supporting that we need to be able to map the
standard type traits to the builtins that they use.  Rather than adding
another list that would need to be kept up-to-date whenever a builtin is
added, this patch instead tries to detect any variable template defined
directly in terms of a TRAIT_EXPR.

This patch also adjusts 'diagnose_trait_expr' to provide more helpful
diagnostics for these cases.  Not all type traits have yet been updated,
this patch just updates those that seem particularly valuable or
straight-forward.  The function also gets moved to cp/semantics.cc to be
closer to 'trait_expr_value'.

Various other parts of the compiler are also adjusted here to assist in
making clear diagnostics, such as making more use of 'is_stub_object' to
refer to a type directly rather than in terms of 'std::declval<T>()'.
Additionally, since there are now more cases of nesting within a
'static_assert'ion I felt it was helpful for the experimental-nesting
mode to nest here as well.

        PR c++/117294
        PR c++/113854

gcc/cp/ChangeLog:

        * call.cc (implicit_conversion_error): Hide label when printing
        a stub object.
        (convert_like_internal): Likewise, and nest candidate
        diagnostics.
        * constexpr.cc (diagnose_failing_condition): Nest diagnostics,
        attempt to provide more helpful diagnostics for traits.
        * constraint.cc (satisfy_atom): Pass result before constant
        evaluation to diagnose_atomic_constraint.
        (diagnose_trait_expr): Adjust diagnostics for clarity and
        detail.
        (maybe_diagnose_standard_trait): New function.
        (diagnose_atomic_constraint): Attempt to provide more helpful
        diagnostics for more traits.
        * cp-tree.h (explain_not_noexcept): Declare new function.
        (is_trivially_xible): Add parameter.
        (is_nothrow_xible): Likewise.
        (is_xible): Likewise.
        (is_convertible): Likewise.
        (is_nothrow_convertible): Likewise.
        (diagnose_trait_expr): Declare new function.
        (maybe_diagnose_standard_trait): Declare new function.
        * error.cc (dump_type) <case TREE_VEC>: Handle trait types.
        * except.cc (explain_not_noexcept): New function.
        * method.cc (build_trait_object): Add complain parameter.
        (build_invoke): Propagate complain parameter.
        (assignable_expr): Add explain parameter to show diagnostics.
        (constructible_expr): Likewise.
        (destructible_expr): Likewise.
        (is_xible_helper): Replace trivial flag with explain flag,
        add diagnostics.
        (is_trivially_xible): New explain flag.
        (is_nothrow_xible): Likewise.
        (is_xible): Likewise.
        (is_convertible_helper): Add complain flag.
        (is_convertible): New explain flag.
        (is_nothrow_convertible): Likewise.
        * typeck.cc (cp_build_function_call_vec): Add handling for stub
        objects.
        (convert_arguments): Always return -1 on error.
        * typeck2.cc (cxx_readonly_error): Add handling for stub
        objects.

libstdc++-v3/ChangeLog:

        * testsuite/20_util/any/misc/any_cast_neg.cc: Adjust
        diagnostics.
        * testsuite/20_util/expected/illformed_neg.cc: Likewise.
        * testsuite/20_util/optional/monadic/or_else_neg.cc: Likewise.
        * testsuite/23_containers/array/creation/3_neg.cc: Likewise.
        * testsuite/24_iterators/range_generators/lwg3900.cc: Likewise.
        * testsuite/29_atomics/atomic/requirements/types_neg.cc:
        Likewise.
        * testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc:
        Likewise.
        * testsuite/std/format/arguments/args_neg.cc: Likewise.
        * testsuite/std/format/string_neg.cc: Likewise.

gcc/testsuite/ChangeLog:

        * g++.dg/cpp2a/concepts-traits3.C: Adjust diagnostics.
        * g++.dg/cpp2a/concepts-traits4.C: New test.
        * g++.dg/diagnostic/static_assert5.C: New test.
        * g++.dg/ext/has_virtual_destructor2.C: New test.
        * g++.dg/ext/is_assignable2.C: New test.
        * g++.dg/ext/is_constructible9.C: New test.
        * g++.dg/ext/is_convertible7.C: New test.
        * g++.dg/ext/is_destructible3.C: New test.
        * g++.dg/ext/is_invocable6.C: New test.
        * g++.dg/ext/is_virtual_base_of_diagnostic2.C: New test.

Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
Reviewed-by: Patrick Palka <ppa...@redhat.com>
Reviewed-by: Jason Merrill <ja...@redhat.com>
---
 gcc/cp/call.cc                                |  20 +-
 gcc/cp/constexpr.cc                           |   5 +
 gcc/cp/constraint.cc                          | 270 +++++++++++-------
 gcc/cp/cp-tree.h                              |  16 +-
 gcc/cp/error.cc                               |  14 +
 gcc/cp/except.cc                              |  12 +
 gcc/cp/method.cc                              | 221 ++++++++++----
 gcc/cp/typeck.cc                              |  14 +-
 gcc/cp/typeck2.cc                             |   5 +
 gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C |  31 +-
 gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C |  77 +++++
 .../g++.dg/diagnostic/static_assert5.C        |  70 +++++
 .../g++.dg/ext/has_virtual_destructor2.C      |  27 ++
 gcc/testsuite/g++.dg/ext/is_assignable2.C     |  36 +++
 gcc/testsuite/g++.dg/ext/is_constructible9.C  |  60 ++++
 gcc/testsuite/g++.dg/ext/is_convertible7.C    |  29 ++
 gcc/testsuite/g++.dg/ext/is_destructible3.C   |  65 +++++
 gcc/testsuite/g++.dg/ext/is_invocable6.C      |  45 +++
 .../ext/is_virtual_base_of_diagnostic2.C      |  13 +
 .../20_util/any/misc/any_cast_neg.cc          |   3 +
 .../20_util/expected/illformed_neg.cc         |   1 +
 .../20_util/optional/monadic/or_else_neg.cc   |   1 +
 .../23_containers/array/creation/3_neg.cc     |   2 +
 .../24_iterators/range_generators/lwg3900.cc  |   3 +-
 .../atomic/requirements/types_neg.cc          |   2 +
 .../stop_token/stop_callback/invocable_neg.cc |   1 +
 .../std/format/arguments/args_neg.cc          |   1 +
 .../testsuite/std/format/string_neg.cc        |   2 +
 28 files changed, 859 insertions(+), 187 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
 create mode 100644 gcc/testsuite/g++.dg/diagnostic/static_assert5.C
 create mode 100644 gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C
 create mode 100644 gcc/testsuite/g++.dg/ext/is_assignable2.C
 create mode 100644 gcc/testsuite/g++.dg/ext/is_constructible9.C
 create mode 100644 gcc/testsuite/g++.dg/ext/is_convertible7.C
 create mode 100644 gcc/testsuite/g++.dg/ext/is_destructible3.C
 create mode 100644 gcc/testsuite/g++.dg/ext/is_invocable6.C
 create mode 100644 gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index c925dd18ab4..c76b15bcef6 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -4927,6 +4927,11 @@ implicit_conversion_error (location_t loc, tree type, 
tree expr)
           && !CP_AGGREGATE_TYPE_P (type))
     error_at (loc, "designated initializers cannot be used with a "
              "non-aggregate type %qT", type);
+  else if (is_stub_object (expr))
+    /* The expression is generated by a trait check, we don't have
+       a useful location to highlight the label.  */
+    error_at (loc, "could not convert %qH to %qI",
+             TREE_TYPE (expr), type);
   else
     {
       range_label_for_type_mismatch label (TREE_TYPE (expr), type);
@@ -8698,6 +8703,7 @@ convert_like_internal (conversion *convs, tree expr, tree 
fn, int argnum,
   diagnostic_t diag_kind;
   int flags;
   location_t loc = cp_expr_loc_or_input_loc (expr);
+  const bool stub_object_p = is_stub_object (expr);
 
   if (convs->bad_p && !(complain & tf_error))
     return error_mark_node;
@@ -8774,7 +8780,10 @@ convert_like_internal (conversion *convs, tree expr, 
tree fn, int argnum,
                                      "from %qH to %qI", TREE_TYPE (expr),
                                      totype);
              if (complained)
-               print_z_candidate (loc, N_("candidate is:"), t->cand);
+               {
+                 auto_diagnostic_nesting_level sentinel;
+                 print_z_candidate (loc, N_("candidate is:"), t->cand);
+               }
              expr = convert_like (t, expr, fn, argnum,
                                   /*issue_conversion_warnings=*/false,
                                   /*c_cast_p=*/false, /*nested_p=*/true,
@@ -8799,7 +8808,14 @@ convert_like_internal (conversion *convs, tree expr, 
tree fn, int argnum,
          else if (t->kind == ck_identity)
            break;
        }
-      if (!complained && expr != error_mark_node)
+      if (!complained && stub_object_p)
+       {
+         /* An error diagnosed within a trait, don't give extra labels.  */
+         error_at (loc, "invalid conversion from %qH to %qI",
+                   TREE_TYPE (expr), totype);
+         complained = 1;
+       }
+      else if (!complained && expr != error_mark_node)
        {
          range_label_for_type_mismatch label (TREE_TYPE (expr), totype);
          gcc_rich_location richloc (loc, &label, highlight_colors::percent_h);
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 1a77954b5ac..3d52297656f 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -2883,10 +2883,15 @@ diagnose_failing_condition (tree bad, location_t cloc, 
bool show_expr_p,
   if (TREE_CODE (bad) == CLEANUP_POINT_EXPR)
     bad = TREE_OPERAND (bad, 0);
 
+  auto_diagnostic_nesting_level sentinel;
+
   /* Actually explain the failure if this is a concept check or a
      requires-expression.  */
   if (concept_check_p (bad) || TREE_CODE (bad) == REQUIRES_EXPR)
     diagnose_constraints (cloc, bad, NULL_TREE);
+  /* Similarly if this is a standard trait.  */
+  else if (maybe_diagnose_standard_trait (cloc, bad))
+    ;
   else if (COMPARISON_CLASS_P (bad)
           && ARITHMETIC_TYPE_P (TREE_TYPE (TREE_OPERAND (bad, 0))))
     {
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 8d7aec337a8..d4a83e429e5 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -2490,10 +2490,11 @@ satisfy_atom (tree t, tree args, sat_info info)
   result = force_rvalue (result, info.complain);
   if (result == error_mark_node)
     return cache.save (inst_cache.save (error_mark_node));
+  tree substituted = result;
   if (!same_type_p (TREE_TYPE (result), boolean_type_node))
     {
       if (info.noisy ())
-       diagnose_atomic_constraint (t, args, result, info);
+       diagnose_atomic_constraint (t, args, substituted, info);
       return cache.save (inst_cache.save (error_mark_node));
     }
 
@@ -2511,7 +2512,7 @@ satisfy_atom (tree t, tree args, sat_info info)
     }
   result = satisfaction_value (result);
   if (result == boolean_false_node && info.diagnose_unsatisfaction_p ())
-    diagnose_atomic_constraint (t, args, result, info);
+    diagnose_atomic_constraint (t, args, substituted, info);
 
   return cache.save (inst_cache.save (result));
 }
@@ -3063,11 +3064,9 @@ get_constraint_error_location (tree t)
 
 /* Emit a diagnostic for a failed trait.  */
 
-static void
-diagnose_trait_expr (tree expr, tree args)
+void
+diagnose_trait_expr (location_t loc, tree expr, tree args)
 {
-  location_t loc = cp_expr_location (expr);
-
   /* Build a "fake" version of the instantiated trait, so we can
      get the instantiated types from result.  */
   ++processing_template_decl;
@@ -3076,218 +3075,246 @@ diagnose_trait_expr (tree expr, tree args)
 
   tree t1 = TRAIT_EXPR_TYPE1 (expr);
   tree t2 = TRAIT_EXPR_TYPE2 (expr);
-  if (t2 && TREE_CODE (t2) == TREE_VEC)
-    {
-      /* Convert the TREE_VEC of arguments into a TREE_LIST, since we can't
-        directly print a TREE_VEC but we can a TREE_LIST via the E format
-        specifier.  */
-      tree list = NULL_TREE;
-      for (tree t : tree_vec_range (t2))
-       list = tree_cons (NULL_TREE, t, list);
-      t2 = nreverse (list);
-    }
+
+  /* For traits intrinsically about the properties of user-defined types,
+     decl_loc will point to the declaration of that type.  */
+  location_t decl_loc = location_of (t1);
+  if (decl_loc == input_location)
+    decl_loc = loc;
+
   switch (TRAIT_EXPR_KIND (expr))
     {
     case CPTK_HAS_NOTHROW_ASSIGN:
-      inform (loc, "  %qT is not nothrow copy assignable", t1);
+      inform (decl_loc, "%qT is not nothrow copy assignable", t1);
       break;
     case CPTK_HAS_NOTHROW_CONSTRUCTOR:
-      inform (loc, "  %qT is not nothrow default constructible", t1);
+      inform (decl_loc, "%qT is not nothrow default constructible", t1);
       break;
     case CPTK_HAS_NOTHROW_COPY:
-      inform (loc, "  %qT is not nothrow copy constructible", t1);
+      inform (decl_loc, "%qT is not nothrow copy constructible", t1);
       break;
     case CPTK_HAS_TRIVIAL_ASSIGN:
-      inform (loc, "  %qT is not trivially copy assignable", t1);
+      inform (decl_loc, "%qT is not trivially copy assignable", t1);
       break;
     case CPTK_HAS_TRIVIAL_CONSTRUCTOR:
-      inform (loc, "  %qT is not trivially default constructible", t1);
+      inform (decl_loc, "%qT is not trivially default constructible", t1);
       break;
     case CPTK_HAS_TRIVIAL_COPY:
-      inform (loc, "  %qT is not trivially copy constructible", t1);
+      inform (decl_loc, "%qT is not trivially copy constructible", t1);
       break;
     case CPTK_HAS_TRIVIAL_DESTRUCTOR:
-      inform (loc, "  %qT is not trivially destructible", t1);
+      inform (decl_loc, "%qT is not trivially destructible", t1);
       break;
     case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
-      inform (loc, "  %qT does not have unique object representations", t1);
+      inform (decl_loc, "%qT does not have unique object representations", t1);
       break;
     case CPTK_HAS_VIRTUAL_DESTRUCTOR:
-      inform (loc, "  %qT does not have a virtual destructor", t1);
+      {
+       location_t dtor_loc = decl_loc;
+       if (NON_UNION_CLASS_TYPE_P (t1))
+         if (tree dtor = CLASSTYPE_DESTRUCTOR (t1))
+           dtor_loc = DECL_SOURCE_LOCATION (dtor);
+       inform (dtor_loc, "%qT does not have a virtual destructor", t1);
+      }
       break;
     case CPTK_IS_ABSTRACT:
-      inform (loc, "  %qT is not an abstract class", t1);
+      inform (decl_loc, "%qT is not an abstract class", t1);
       break;
     case CPTK_IS_AGGREGATE:
-      inform (loc, "  %qT is not an aggregate", t1);
+      inform (decl_loc, "%qT is not an aggregate", t1);
       break;
     case CPTK_IS_ARRAY:
-      inform (loc, "  %qT is not an array", t1);
+      inform (loc, "%qT is not an array", t1);
       break;
     case CPTK_IS_ASSIGNABLE:
-      inform (loc, "  %qT is not assignable from %qT", t1, t2);
+      inform (loc, "%qT is not assignable from %qT, because", t1, t2);
+      is_xible (MODIFY_EXPR, t1, t2, /*explain=*/true);
       break;
     case CPTK_IS_BASE_OF:
-      inform (loc, "  %qT is not a base of %qT", t1, t2);
+      inform (decl_loc, "%qT is not a base of %qT", t1, t2);
       break;
     case CPTK_IS_BOUNDED_ARRAY:
-      inform (loc, "  %qT is not a bounded array", t1);
+      inform (loc, "%qT is not a bounded array", t1);
       break;
     case CPTK_IS_CLASS:
-      inform (loc, "  %qT is not a class", t1);
+      inform (decl_loc, "%qT is not a class", t1);
       break;
     case CPTK_IS_CONST:
-      inform (loc, "  %qT is not a const type", t1);
+      inform (loc, "%qT is not a const type", t1);
       break;
     case CPTK_IS_CONSTRUCTIBLE:
-      if (!t2)
-       inform (loc, "  %qT is not default constructible", t1);
+      if (!TREE_VEC_LENGTH (t2))
+       inform (loc, "%qT is not default constructible, because", t1);
       else
-       inform (loc, "  %qT is not constructible from %qE", t1, t2);
+       inform (loc, "%qT is not constructible from %qT, because", t1, t2);
+      is_xible (INIT_EXPR, t1, t2, /*explain=*/true);
       break;
     case CPTK_IS_CONVERTIBLE:
-      inform (loc, "  %qT is not convertible from %qE", t2, t1);
+      /* The errors produced here all seem to mention "convertible" in the
+        diagnostic, so an extra inform here appears redundant.  */
+      is_convertible (t1, t2, /*explain=*/true);
       break;
     case CPTK_IS_DESTRUCTIBLE:
-      inform (loc, "  %qT is not destructible", t1);
+      inform (loc, "%qT is not destructible, because", t1);
+      is_xible (BIT_NOT_EXPR, t1, NULL_TREE, /*explain=*/true);
       break;
     case CPTK_IS_EMPTY:
-      inform (loc, "  %qT is not an empty class", t1);
+      inform (decl_loc, "%qT is not an empty class", t1);
       break;
     case CPTK_IS_ENUM:
-      inform (loc, "  %qT is not an enum", t1);
+      inform (decl_loc, "%qT is not an enum", t1);
       break;
     case CPTK_IS_FINAL:
-      inform (loc, "  %qT is not a final class", t1);
+      inform (decl_loc, "%qT is not a final class", t1);
       break;
     case CPTK_IS_FUNCTION:
-      inform (loc, "  %qT is not a function", t1);
+      inform (loc, "%qT is not a function", t1);
       break;
     case CPTK_IS_INVOCABLE:
-      if (!t2)
-       inform (loc, "  %qT is not invocable", t1);
-      else
-       inform (loc, "  %qT is not invocable by %qE", t1, t2);
+      {
+       if (!TREE_VEC_LENGTH (t2))
+         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);
+      }
       break;
     case CPTK_IS_LAYOUT_COMPATIBLE:
-      inform (loc, "  %qT is not layout compatible with %qT", t1, t2);
+      inform (loc, "%qT is not layout compatible with %qT", t1, t2);
       break;
     case CPTK_IS_LITERAL_TYPE:
-      inform (loc, "  %qT is not a literal type", t1);
+      inform (decl_loc, "%qT is not a literal type", t1);
       break;
     case CPTK_IS_MEMBER_FUNCTION_POINTER:
-      inform (loc, "  %qT is not a member function pointer", t1);
+      inform (loc, "%qT is not a member function pointer", t1);
       break;
     case CPTK_IS_MEMBER_OBJECT_POINTER:
-      inform (loc, "  %qT is not a member object pointer", t1);
+      inform (loc, "%qT is not a member object pointer", t1);
       break;
     case CPTK_IS_MEMBER_POINTER:
-      inform (loc, "  %qT is not a member pointer", t1);
+      inform (loc, "%qT is not a member pointer", t1);
       break;
     case CPTK_IS_NOTHROW_ASSIGNABLE:
-      inform (loc, "  %qT is not nothrow assignable from %qT", t1, t2);
+      inform (loc, "%qT is not nothrow assignable from %qT, because", t1, t2);
+      is_nothrow_xible (MODIFY_EXPR, t1, t2, /*explain=*/true);
       break;
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
-      if (!t2)
-       inform (loc, "  %qT is not nothrow default constructible", t1);
+      if (!TREE_VEC_LENGTH (t2))
+       inform (loc, "%qT is not nothrow default constructible, because", t1);
       else
-       inform (loc, "  %qT is not nothrow constructible from %qE", t1, t2);
+       inform (loc, "%qT is not nothrow constructible from %qT, because",
+               t1, t2);
+      is_nothrow_xible (INIT_EXPR, t1, t2, /*explain=*/true);
       break;
     case CPTK_IS_NOTHROW_CONVERTIBLE:
-      inform (loc, "  %qT is not nothrow convertible from %qE", t2, t1);
+      inform (loc, "%qT is not nothrow convertible from %qT, because", t1, t2);
+      is_nothrow_convertible (t1, t2, /*explain=*/true);
       break;
     case CPTK_IS_NOTHROW_DESTRUCTIBLE:
-      inform (loc, "  %qT is not nothrow destructible", t1);
+      inform (loc, "%qT is not nothrow destructible, because", t1);
+      is_nothrow_xible (BIT_NOT_EXPR, t1, NULL_TREE, /*explain=*/true);
       break;
     case CPTK_IS_NOTHROW_INVOCABLE:
-      if (!t2)
-       inform (loc, "  %qT is not nothrow invocable", t1);
-      else
-       inform (loc, "  %qT is not nothrow invocable by %qE", t1, t2);
+      {
+       if (!TREE_VEC_LENGTH (t2))
+         inform (loc, "%qT is not nothrow invocable, because", t1);
+       else
+         inform (loc, "%qT is not nothrow invocable by %qT, because", t1, t2);
+       tree call = build_invoke (t1, t2, tf_error);
+       if (call != error_mark_node)
+         explain_not_noexcept (call);
+      }
       break;
     case CPTK_IS_NOTHROW_RELOCATABLE:
-      inform (loc, "  %qT is not nothrow relocatable", t1);
+      inform (loc, "%qT is not nothrow relocatable", t1);
       break;
     case CPTK_IS_OBJECT:
-      inform (loc, "  %qT is not an object type", t1);
+      inform (loc, "%qT is not an object type", t1);
       break;
     case CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
-      inform (loc, "  %qT is not pointer-interconvertible base of %qT",
+      inform (decl_loc, "%qT is not a pointer-interconvertible base of %qT",
              t1, t2);
       break;
     case CPTK_IS_POD:
-      inform (loc, "  %qT is not a POD type", t1);
+      inform (loc, "%qT is not a POD type", t1);
       break;
     case CPTK_IS_POINTER:
-      inform (loc, "  %qT is not a pointer", t1);
+      inform (loc, "%qT is not a pointer", t1);
       break;
     case CPTK_IS_POLYMORPHIC:
-      inform (loc, "  %qT is not a polymorphic type", t1);
+      inform (decl_loc, "%qT is not a polymorphic type", t1);
       break;
     case CPTK_IS_REFERENCE:
-      inform (loc, "  %qT is not a reference", t1);
+      inform (loc, "%qT is not a reference", t1);
       break;
     case CPTK_IS_REPLACEABLE:
-      inform (loc, "  %qT is not replaceable", t1);
+      inform (loc, "%qT is not replaceable", t1);
       break;
     case CPTK_IS_SAME:
-      inform (loc, "  %qT is not the same as %qT", t1, t2);
+      inform (loc, "%q#T is not the same as %q#T", t1, t2);
       break;
     case CPTK_IS_SCOPED_ENUM:
-      inform (loc, "  %qT is not a scoped enum", t1);
+      inform (decl_loc, "%qT is not a scoped enum", t1);
       break;
     case CPTK_IS_STD_LAYOUT:
-      inform (loc, "  %qT is not an standard layout type", t1);
+      inform (decl_loc, "%qT is not a standard layout type", t1);
       break;
     case CPTK_IS_TRIVIAL:
-      inform (loc, "  %qT is not a trivial type", t1);
+      inform (decl_loc, "%qT is not a trivial type", t1);
       break;
     case CPTK_IS_TRIVIALLY_ASSIGNABLE:
-      inform (loc, "  %qT is not trivially assignable from %qT", t1, t2);
+      inform (loc, "%qT is not trivially assignable from %qT, because", t1, 
t2);
+      is_trivially_xible (MODIFY_EXPR, t1, t2, /*explain=*/true);
       break;
     case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
-      if (!t2)
-       inform (loc, "  %qT is not trivially default constructible", t1);
+      if (!TREE_VEC_LENGTH (t2))
+       inform (loc, "%qT is not trivially default constructible, because", t1);
       else
-       inform (loc, "  %qT is not trivially constructible from %qE", t1, t2);
+       inform (loc, "%qT is not trivially constructible from %qT, because",
+               t1, t2);
+      is_trivially_xible (INIT_EXPR, t1, t2, /*explain=*/true);
       break;
     case CPTK_IS_TRIVIALLY_COPYABLE:
-      inform (loc, "  %qT is not trivially copyable", t1);
+      inform (decl_loc, "%qT is not trivially copyable", t1);
       break;
     case CPTK_IS_TRIVIALLY_DESTRUCTIBLE:
-      inform (loc, "  %qT is not trivially destructible", t1);
+      inform (loc, "%qT is not trivially destructible, because", t1);
+      is_trivially_xible (BIT_NOT_EXPR, t1, NULL_TREE, /*explain=*/true);
       break;
     case CPTK_IS_TRIVIALLY_RELOCATABLE:
-      inform (loc, "  %qT is not trivially relocatable", t1);
+      inform (loc, "%qT is not trivially relocatable", t1);
       break;
     case CPTK_IS_UNBOUNDED_ARRAY:
-      inform (loc, "  %qT is not an unbounded array", t1);
+      inform (loc, "%qT is not an unbounded array", t1);
       break;
     case CPTK_IS_UNION:
-      inform (loc, "  %qT is not a union", t1);
+      inform (decl_loc, "%qT is not a union", t1);
       break;
     case CPTK_IS_VIRTUAL_BASE_OF:
-      inform (loc, "  %qT is not a virtual base of %qT", t1, t2);
+      inform (decl_loc, "%qT is not a virtual base of %qT", t1, t2);
+      if (CLASS_TYPE_P (t2))
+       inform (location_of (t2), "%qT declared here", t2);
       break;
     case CPTK_IS_VOLATILE:
-      inform (loc, "  %qT is not a volatile type", t1);
+      inform (loc, "%qT is not a volatile type", t1);
       break;
     case CPTK_RANK:
-      inform (loc, "  %qT cannot yield a rank", t1);
+      inform (loc, "%qT cannot yield a rank", t1);
       break;
     case CPTK_TYPE_ORDER:
-      inform (loc, "  %qT and %qT cannot be ordered", t1, t2);
+      inform (loc, "%qT and %qT cannot be ordered", t1, t2);
       break;
     case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
-      inform (loc, "  %qT is not a reference that binds to a temporary "
+      inform (loc, "%qT is not a reference that binds to a temporary "
              "object of type %qT (direct-initialization)", t1, t2);
       break;
     case CPTK_REF_CONVERTS_FROM_TEMPORARY:
-      inform (loc, "  %qT is not a reference that binds to a temporary "
+      inform (loc, "%qT is not a reference that binds to a temporary "
              "object of type %qT (copy-initialization)", t1, t2);
       break;
     case CPTK_IS_DEDUCIBLE:
-      inform (loc, "  %qD is not deducible from %qT", t1, t2);
+      inform (loc, "%qD is not deducible from %qT", t1, t2);
       break;
 #define DEFTRAIT_TYPE(CODE, NAME, ARITY) \
     case CPTK_##CODE:
@@ -3300,10 +3327,50 @@ diagnose_trait_expr (tree expr, tree args)
     }
 }
 
+/* Attempt to detect if this is a standard type trait, defined in terms
+   of a compiler builtin (above).  If so, this will allow us to provide
+   more helpful diagnostics.   */
+
+bool
+maybe_diagnose_standard_trait (location_t loc, tree expr)
+{
+  gcc_assert (TREE_CODE (expr) != TRAIT_EXPR);
+  expr = tree_strip_nop_conversions (expr);
+
+  /* TODO: in some cases it would be possible to provide more helpful
+     diagnostics for negations of traits, e.g. '!is_same_v<T1, T2>'.  */
+
+  tree args = NULL_TREE;
+  if (VAR_P (expr) && DECL_LANG_SPECIFIC (expr) && DECL_USE_TEMPLATE (expr))
+    {
+      tree tinfo = DECL_TEMPLATE_INFO (expr);
+      if (PRIMARY_TEMPLATE_P (TI_TEMPLATE (tinfo)) && TI_PARTIAL_INFO (tinfo))
+       tinfo = TI_PARTIAL_INFO (tinfo);
+      else if (DECL_TEMPLATE_SPECIALIZATION (expr))
+       /* In an explicit specialisation we no longer know what the original
+          initializer looked like.  */
+       tinfo = NULL_TREE;
+
+      if (tinfo)
+       {
+         expr = DECL_INITIAL (DECL_TEMPLATE_RESULT (TI_TEMPLATE (tinfo)));
+         args = TI_ARGS (tinfo);
+       }
+    }
+
+  if (TREE_CODE (expr) == TRAIT_EXPR)
+    {
+      diagnose_trait_expr (loc, expr, args);
+      return true;
+    }
+
+  return false;
+}
+
 /* Diagnose a substitution failure in the atomic constraint T using ARGS.  */
 
 static void
-diagnose_atomic_constraint (tree t, tree args, tree result, sat_info info)
+diagnose_atomic_constraint (tree t, tree args, tree substituted, sat_info info)
 {
   /* If the constraint is already ill-formed, we've previously diagnosed
      the reason.  We should still say why the constraints aren't satisfied.  */
@@ -3324,25 +3391,26 @@ diagnose_atomic_constraint (tree t, tree args, tree 
result, sat_info info)
   /* Generate better diagnostics for certain kinds of expressions.  */
   tree expr = ATOMIC_CONSTR_EXPR (t);
   STRIP_ANY_LOCATION_WRAPPER (expr);
-  switch (TREE_CODE (expr))
+
+  if (TREE_CODE (expr) == REQUIRES_EXPR)
     {
-    case TRAIT_EXPR:
-      diagnose_trait_expr (expr, args);
-      break;
-    case REQUIRES_EXPR:
       gcc_checking_assert (info.diagnose_unsatisfaction_p ());
       /* Clear in_decl before replaying the substitution to avoid emitting
         seemingly unhelpful "in declaration ..." notes that follow some
         substitution failure error messages.  */
       info.in_decl = NULL_TREE;
       tsubst_requires_expr (expr, args, info);
-      break;
-    default:
-      if (!same_type_p (TREE_TYPE (result), boolean_type_node))
-       error_at (loc, "constraint %qE has type %qT, not %<bool%>",
-                 t, TREE_TYPE (result));
+    }
+  else if (!same_type_p (TREE_TYPE (substituted), boolean_type_node))
+    error_at (loc, "constraint %qE has type %qT, not %<bool%>",
+             t, TREE_TYPE (substituted));
+  else
+    {
+      inform (loc, "the expression %qE evaluated to %<false%>", t);
+      if (TREE_CODE (expr) == TRAIT_EXPR)
+       diagnose_trait_expr (loc, expr, args);
       else
-       inform (loc, "the expression %qE evaluated to %<false%>", t);
+       maybe_diagnose_standard_trait (loc, substituted);
     }
 }
 
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 68102501569..01112aa894e 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7481,6 +7481,7 @@ extern int nothrow_libfn_p                        
(const_tree);
 extern void check_handlers                     (tree);
 extern tree finish_noexcept_expr               (tree, tsubst_flags_t);
 extern bool expr_noexcept_p                    (tree, tsubst_flags_t);
+extern void explain_not_noexcept               (tree);
 extern void perform_deferred_noexcept_checks   (void);
 extern bool nothrow_spec_p                     (const_tree);
 extern bool type_noexcept_p                    (const_tree);
@@ -7602,11 +7603,14 @@ extern void finish_thunk                        (tree);
 extern void use_thunk                          (tree, bool);
 extern bool trivial_fn_p                       (tree);
 extern tree forward_parm                       (tree);
-extern bool is_trivially_xible                 (enum tree_code, tree, tree);
-extern bool is_nothrow_xible                   (enum tree_code, tree, tree);
-extern bool is_xible                           (enum tree_code, tree, tree);
-extern bool is_convertible                     (tree, tree);
-extern bool is_nothrow_convertible             (tree, tree);
+extern bool is_trivially_xible                 (enum tree_code, tree, tree,
+                                                bool = false);
+extern bool is_nothrow_xible                   (enum tree_code, tree, tree,
+                                                bool = false);
+extern bool is_xible                           (enum tree_code, tree, tree,
+                                                bool = false);
+extern bool is_convertible                     (tree, tree, bool = false);
+extern bool is_nothrow_convertible             (tree, tree, bool = false);
 extern bool ref_xes_from_temporary             (tree, tree, bool);
 extern tree get_defaulted_eh_spec              (tree, tsubst_flags_t = 
tf_warning_or_error);
 extern bool maybe_explain_implicit_delete      (tree);
@@ -8923,6 +8927,8 @@ extern bool constraints_equivalent_p            (tree, 
tree);
 extern bool atomic_constraints_identical_p     (tree, tree);
 extern hashval_t iterative_hash_constraint      (tree, hashval_t);
 extern hashval_t hash_atomic_constraint         (tree);
+extern void diagnose_trait_expr                        (location_t, tree, 
tree);
+extern bool maybe_diagnose_standard_trait      (location_t, tree);
 extern void diagnose_constraints                (location_t, tree, tree);
 
 extern void note_failed_type_completion                (tree, tsubst_flags_t);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index eb2ff33ac30..177f28fa165 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -704,6 +704,20 @@ dump_type (cxx_pretty_printer *pp, tree t, int flags)
        }
       break;
 
+    case TREE_VEC:
+      {
+       /* A list of types used for a trait.  */
+       bool need_comma = false;
+       for (tree arg : tree_vec_range (t))
+         {
+           if (need_comma)
+             pp_separate_with_comma (pp);
+           dump_type (pp, arg, flags);
+           need_comma = true;
+         }
+      }
+      break;
+
     case TREE_LIST:
       /* A list of function parms.  */
       dump_parameters (pp, t, flags);
diff --git a/gcc/cp/except.cc b/gcc/cp/except.cc
index a7f35e4314d..2c1ef4c3af6 100644
--- a/gcc/cp/except.cc
+++ b/gcc/cp/except.cc
@@ -1218,6 +1218,18 @@ expr_noexcept_p (tree expr, tsubst_flags_t complain)
     return true;
 }
 
+/* Explain why EXPR is not noexcept.  */
+
+void explain_not_noexcept (tree expr)
+{
+  tree fn = cp_walk_tree_without_duplicates (&expr, check_noexcept_r, 0);
+  gcc_assert (fn);
+  if (DECL_P (fn))
+    inform (DECL_SOURCE_LOCATION (fn), "%qD is not %<noexcept%>", fn);
+  else
+    inform (location_of (fn), "%qT is not %<noexcept%>", TREE_TYPE (fn));
+}
+
 /* Return true iff SPEC is throw() or noexcept(true).  */
 
 bool
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index 334c325842f..22a84b9c11a 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -1928,8 +1928,8 @@ is_stub_object (tree expr)
 
 /* Build a std::declval<TYPE>() expression and return it.  */
 
-tree
-build_trait_object (tree type)
+static tree
+build_trait_object (tree type, tsubst_flags_t complain)
 {
   /* TYPE can't be a function with cv-/ref-qualifiers: std::declval is
      defined as
@@ -1942,7 +1942,11 @@ build_trait_object (tree type)
   if (FUNC_OR_METHOD_TYPE_P (type)
       && (type_memfn_quals (type) != TYPE_UNQUALIFIED
          || type_memfn_rqual (type) != REF_QUAL_NONE))
-    return error_mark_node;
+    {
+      if (complain & tf_error)
+       error ("object cannot have qualified function type %qT", type);
+      return error_mark_node;
+    }
 
   return build_stub_object (type);
 }
@@ -2046,13 +2050,16 @@ build_invoke (tree fn_type, const_tree arg_types, 
tsubst_flags_t complain)
            }
        }
 
-      tree datum_expr = build_trait_object (datum_type);
+      tree datum_expr = build_trait_object (datum_type, complain);
       if (!ptrmem_is_same_or_base_of_datum && !datum_is_refwrap)
        /* 1.3 & 1.6: Try to dereference datum_expr.  */
        datum_expr = build_x_indirect_ref (UNKNOWN_LOCATION, datum_expr,
                                           RO_UNARY_STAR, NULL_TREE, complain);
 
-      tree fn_expr = build_trait_object (fn_type);
+      if (error_operand_p (datum_expr))
+       return error_mark_node;
+
+      tree fn_expr = build_trait_object (fn_type, complain);
       ptrmem_expr = build_m_component_ref (datum_expr, fn_expr, complain);
 
       if (error_operand_p (ptrmem_expr))
@@ -2069,7 +2076,9 @@ build_invoke (tree fn_type, const_tree arg_types, 
tsubst_flags_t complain)
   for (int i = is_ptrmemfunc ? 1 : 0; i < TREE_VEC_LENGTH (arg_types); ++i)
     {
       tree arg_type = TREE_VEC_ELT (arg_types, i);
-      tree arg = build_trait_object (arg_type);
+      tree arg = build_trait_object (arg_type, complain);
+      if (error_operand_p (arg))
+       return error_mark_node;
       vec_safe_push (args, arg);
     }
 
@@ -2078,8 +2087,8 @@ build_invoke (tree fn_type, const_tree arg_types, 
tsubst_flags_t complain)
     invoke_expr = build_offset_ref_call_from_tree (ptrmem_expr, &args,
                                                   complain);
   else  /* 1.7.  */
-    invoke_expr = finish_call_expr (build_trait_object (fn_type), &args, false,
-                                   false, complain);
+    invoke_expr = finish_call_expr (build_trait_object (fn_type, complain),
+                                   &args, false, false, complain);
   return invoke_expr;
 }
 
@@ -2228,12 +2237,20 @@ check_nontriv (tree *tp, int *, void *)
 /* Return declval<T>() = declval<U>() treated as an unevaluated operand.  */
 
 static tree
-assignable_expr (tree to, tree from)
+assignable_expr (tree to, tree from, bool explain)
 {
   cp_unevaluated cp_uneval_guard;
-  to = build_trait_object (to);
-  from = build_trait_object (from);
-  tree r = cp_build_modify_expr (input_location, to, NOP_EXPR, from, tf_none);
+  tsubst_flags_t complain = explain ? tf_error : tf_none;
+
+  to = build_trait_object (to, complain);
+  if (to == error_mark_node)
+    return error_mark_node;
+
+  from = build_trait_object (from, complain);
+  if (from == error_mark_node)
+    return error_mark_node;
+
+  tree r = cp_build_modify_expr (input_location, to, NOP_EXPR, from, complain);
   return r;
 }
 
@@ -2245,10 +2262,11 @@ assignable_expr (tree to, tree from)
    Return something equivalent in well-formedness and triviality.  */
 
 static tree
-constructible_expr (tree to, tree from)
+constructible_expr (tree to, tree from, bool explain)
 {
   tree expr;
   cp_unevaluated cp_uneval_guard;
+  tsubst_flags_t complain = explain ? tf_error : tf_none;
   const int len = TREE_VEC_LENGTH (from);
   if (CLASS_TYPE_P (to))
     {
@@ -2260,14 +2278,14 @@ constructible_expr (tree to, tree from)
        to = cp_build_reference_type (to, /*rval*/false);
       tree ob = build_stub_object (to);
       if (len == 0)
-       expr = build_value_init (ctype, tf_none);
+       expr = build_value_init (ctype, complain);
       else
        {
          vec_alloc (args, len);
          for (tree arg : tree_vec_range (from))
            args->quick_push (build_stub_object (arg));
          expr = build_special_member_call (ob, complete_ctor_identifier, &args,
-                                           ctype, LOOKUP_NORMAL, tf_none);
+                                           ctype, LOOKUP_NORMAL, complain);
        }
       if (expr == error_mark_node)
        return error_mark_node;
@@ -2277,7 +2295,7 @@ constructible_expr (tree to, tree from)
        {
          tree dtor = build_special_member_call (ob, complete_dtor_identifier,
                                                 NULL, ctype, LOOKUP_NORMAL,
-                                                tf_none);
+                                                complain);
          if (dtor == error_mark_node)
            return error_mark_node;
          if (!TYPE_HAS_TRIVIAL_DESTRUCTOR (ctype))
@@ -2287,12 +2305,15 @@ constructible_expr (tree to, tree from)
   else
     {
       if (len == 0)
-       return build_value_init (strip_array_types (to), tf_none);
+       return build_value_init (strip_array_types (to), complain);
       if (len > 1)
        {
          if (cxx_dialect < cxx20)
-           /* Too many initializers.  */
-           return error_mark_node;
+           {
+             if (explain)
+               error ("too many initializers for non-class type %qT", to);
+             return error_mark_node;
+           }
 
          /* In C++20 this is well-formed:
               using T = int[2];
@@ -2313,9 +2334,11 @@ constructible_expr (tree to, tree from)
        }
       else
        from = build_stub_object (TREE_VEC_ELT (from, 0));
+
+      tree orig_from = from;
       expr = perform_direct_initialization_if_possible (to, from,
                                                        /*cast*/false,
-                                                       tf_none);
+                                                       complain);
       /* If t(e) didn't work, maybe t{e} will.  */
       if (expr == NULL_TREE
          && len == 1
@@ -2327,7 +2350,24 @@ constructible_expr (tree to, tree from)
          CONSTRUCTOR_IS_PAREN_INIT (from) = true;
          expr = perform_direct_initialization_if_possible (to, from,
                                                            /*cast*/false,
-                                                           tf_none);
+                                                           complain);
+       }
+
+      if (expr == NULL_TREE && explain)
+       {
+         if (len > 1)
+           error ("too many initializers for non-class type %qT", to);
+         else
+           {
+             /* Redo the implicit conversion for diagnostics.  */
+             int count = errorcount + warningcount;
+             perform_implicit_conversion_flags (to, orig_from, complain,
+                                                LOOKUP_NORMAL);
+             if (count == errorcount + warningcount)
+               /* The message may have been suppressed due to -w + 
-fpermissive,
+                  emit a generic response instead.  */
+               error ("the conversion is invalid");
+           }
        }
     }
   return expr;
@@ -2341,20 +2381,25 @@ constructible_expr (tree to, tree from)
    valid or error_mark_node if not.  */
 
 static tree
-destructible_expr (tree to)
+destructible_expr (tree to, bool explain)
 {
   cp_unevaluated cp_uneval_guard;
+  tsubst_flags_t complain = explain ? tf_error : tf_none;
   int flags = LOOKUP_NORMAL|LOOKUP_DESTRUCTOR;
   if (TYPE_REF_P (to))
     return void_node;
   if (!COMPLETE_TYPE_P (complete_type (to)))
-    return error_mark_node;
+    {
+      if (explain)
+       error_at (location_of (to), "%qT is incomplete", to);
+      return error_mark_node;
+    }
   to = strip_array_types (to);
   if (CLASS_TYPE_P (to))
     {
-      to = build_trait_object (to);
+      to = build_trait_object (to, complain);
       return build_delete (input_location, TREE_TYPE (to), to,
-                            sfk_complete_destructor, flags, 0, tf_none);
+                          sfk_complete_destructor, flags, 0, complain);
     }
   /* [expr.prim.id.dtor] If the id-expression names a pseudo-destructor, T
      shall be a scalar type.... */
@@ -2366,70 +2411,109 @@ destructible_expr (tree to)
 
 /* Returns a tree iff TO is assignable (if CODE is MODIFY_EXPR) or
    constructible (otherwise) from FROM, which is a single type for
-   assignment or a list of types for construction.  */
+   assignment or a list of types for construction.  If EXPLAIN is
+   set, emit a diagnostic explaining why the operation failed.  */
 
 static tree
-is_xible_helper (enum tree_code code, tree to, tree from, bool trivial)
+is_xible_helper (enum tree_code code, tree to, tree from, bool explain)
 {
   to = complete_type (to);
   deferring_access_check_sentinel acs (dk_no_deferred);
-  if (VOID_TYPE_P (to)
-      || (from && FUNC_OR_METHOD_TYPE_P (from)
-         && (TYPE_READONLY (from) || FUNCTION_REF_QUALIFIED (from))))
-    return error_mark_node;
+
+  if (VOID_TYPE_P (to))
+    {
+      if (explain)
+       error_at (location_of (to), "%qT is incomplete", to);
+      return error_mark_node;
+    }
+  if (from
+      && FUNC_OR_METHOD_TYPE_P (from)
+      && (TYPE_READONLY (from) || FUNCTION_REF_QUALIFIED (from)))
+    {
+      if (explain)
+       error ("%qT is a qualified function type", from);
+      return error_mark_node;
+    }
+
   tree expr;
   if (code == MODIFY_EXPR)
-    expr = assignable_expr (to, from);
+    expr = assignable_expr (to, from, explain);
   else if (code == BIT_NOT_EXPR)
-    expr = destructible_expr (to);
-  else if (trivial && TREE_VEC_LENGTH (from) > 1
-          && cxx_dialect < cxx20)
-    return error_mark_node; // only 0- and 1-argument ctors can be trivial
-                           // before C++20 aggregate paren init
+    expr = destructible_expr (to, explain);
   else if (TREE_CODE (to) == ARRAY_TYPE && !TYPE_DOMAIN (to))
-    return error_mark_node; // can't construct an array of unknown bound
+    {
+      if (explain)
+       error ("cannot construct an array of unknown bound");
+      return error_mark_node;
+    }
   else
-    expr = constructible_expr (to, from);
+    expr = constructible_expr (to, from, explain);
   return expr;
 }
 
 /* Returns true iff TO is trivially assignable (if CODE is MODIFY_EXPR) or
    constructible (otherwise) from FROM, which is a single type for
-   assignment or a list of types for construction.  */
+   assignment or a list of types for construction.  If EXPLAIN, diagnose
+   why we returned false.  */
 
 bool
-is_trivially_xible (enum tree_code code, tree to, tree from)
+is_trivially_xible (enum tree_code code, tree to, tree from,
+                   bool explain/*=false*/)
 {
-  tree expr = is_xible_helper (code, to, from, /*trivial*/true);
+  /* In some cases, when producing errors is_xible_helper may not return
+     error_mark_node, so check if it looks like we've already emitted any
+     diagnostics to ensure we don't do so multiple times.  */
+  int errs = errorcount + sorrycount;
+
+  tree expr = is_xible_helper (code, to, from, explain);
   if (expr == NULL_TREE || expr == error_mark_node)
     return false;
+
   tree nt = cp_walk_tree_without_duplicates (&expr, check_nontriv, NULL);
+  if (explain && errs == (errorcount + sorrycount))
+    {
+      gcc_assert (nt);
+      inform (location_of (nt), "%qE is non-trivial", nt);
+    }
   return !nt;
 }
 
 /* Returns true iff TO is nothrow assignable (if CODE is MODIFY_EXPR) or
    constructible (otherwise) from FROM, which is a single type for
-   assignment or a list of types for construction.  */
+   assignment or a list of types for construction.  If EXPLAIN, diagnose
+   why we returned false.  */
 
 bool
-is_nothrow_xible (enum tree_code code, tree to, tree from)
+is_nothrow_xible (enum tree_code code, tree to, tree from,
+                 bool explain/*=false*/)
 {
+  /* As with is_trivially_xible.  */
+  int errs = errorcount + sorrycount;
+
   ++cp_noexcept_operand;
-  tree expr = is_xible_helper (code, to, from, /*trivial*/false);
+  tree expr = is_xible_helper (code, to, from, explain);
   --cp_noexcept_operand;
   if (expr == NULL_TREE || expr == error_mark_node)
     return false;
-  return expr_noexcept_p (expr, tf_none);
+
+  bool is_noexcept = expr_noexcept_p (expr, tf_none);
+  if (explain && errs == (errorcount + sorrycount))
+    {
+      gcc_assert (!is_noexcept);
+      explain_not_noexcept (expr);
+    }
+  return is_noexcept;
 }
 
 /* Returns true iff TO is assignable (if CODE is MODIFY_EXPR) or
    constructible (otherwise) from FROM, which is a single type for
-   assignment or a list of types for construction.  */
+   assignment or a list of types for construction.  If EXPLAIN, diagnose
+   why we returned false.  */
 
 bool
-is_xible (enum tree_code code, tree to, tree from)
+is_xible (enum tree_code code, tree to, tree from, bool explain/*=false*/)
 {
-  tree expr = is_xible_helper (code, to, from, /*trivial*/false);
+  tree expr = is_xible_helper (code, to, from, explain);
   if (expr == error_mark_node)
     return false;
   return !!expr;
@@ -2454,7 +2538,7 @@ ref_xes_from_temporary (tree to, tree from, bool 
direct_init_p)
     return false;
   /* We don't check is_constructible<T, U>: if T isn't constructible
      from U, we won't be able to create a conversion.  */
-  tree val = build_trait_object (from);
+  tree val = build_trait_object (from, tf_none);
   if (val == error_mark_node)
     return false;
   if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
@@ -2463,25 +2547,36 @@ ref_xes_from_temporary (tree to, tree from, bool 
direct_init_p)
 }
 
 /* Worker for is_{,nothrow_}convertible.  Attempt to perform an implicit
-   conversion from FROM to TO and return the result.  */
+   conversion from FROM to TO and return the result.  If EXPLAIN, emit a
+   diagnostic about why the conversion failed.  */
 
 static tree
-is_convertible_helper (tree from, tree to)
+is_convertible_helper (tree from, tree to, bool explain)
 {
   if (VOID_TYPE_P (from) && VOID_TYPE_P (to))
     return integer_one_node;
   cp_unevaluated u;
-  tree expr = build_trait_object (from);
+  tsubst_flags_t complain = explain ? tf_error : tf_none;
+
   /* std::is_{,nothrow_}convertible test whether the imaginary function
      definition
 
        To test() { return std::declval<From>(); }
 
      is well-formed.  A function can't return a function.  */
-  if (FUNC_OR_METHOD_TYPE_P (to) || expr == error_mark_node)
+  if (FUNC_OR_METHOD_TYPE_P (to))
+    {
+      if (explain)
+       error ("%qT is a function type", to);
+      return error_mark_node;
+    }
+
+  tree expr = build_trait_object (from, complain);
+  if (expr == error_mark_node)
     return error_mark_node;
+
   deferring_access_check_sentinel acs (dk_no_deferred);
-  return perform_implicit_conversion (to, expr, tf_none);
+  return perform_implicit_conversion (to, expr, complain);
 }
 
 /* Return true if FROM can be converted to TO using implicit conversions,
@@ -2490,9 +2585,9 @@ is_convertible_helper (tree from, tree to)
    to either type" restriction.  */
 
 bool
-is_convertible (tree from, tree to)
+is_convertible (tree from, tree to, bool explain/*=false*/)
 {
-  tree expr = is_convertible_helper (from, to);
+  tree expr = is_convertible_helper (from, to, explain);
   if (expr == error_mark_node)
     return false;
   return !!expr;
@@ -2501,12 +2596,18 @@ is_convertible (tree from, tree to)
 /* Like is_convertible, but the conversion is also noexcept.  */
 
 bool
-is_nothrow_convertible (tree from, tree to)
+is_nothrow_convertible (tree from, tree to, bool explain/*=false*/)
 {
-  tree expr = is_convertible_helper (from, to);
+  tree expr = is_convertible_helper (from, to, explain);
   if (expr == NULL_TREE || expr == error_mark_node)
     return false;
-  return expr_noexcept_p (expr, tf_none);
+  bool is_noexcept = expr_noexcept_p (expr, tf_none);
+  if (explain)
+    {
+      gcc_assert (!is_noexcept);
+      explain_not_noexcept (expr);
+    }
+  return is_noexcept;
 }
 
 /* Categorize various special_function_kinds.  */
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index a604511db71..a66586dfad8 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -4530,7 +4530,11 @@ cp_build_function_call_vec (tree function, vec<tree, 
va_gc> **params,
     {
       if (complain & tf_error)
        {
-         if (!flag_diagnostics_show_caret)
+         if (is_stub_object (original))
+           error_at (input_location,
+                     "%qT cannot be used as a function",
+                     TREE_TYPE (original));
+         else if (!flag_diagnostics_show_caret)
            error_at (input_location,
                      "%qE cannot be used as a function", original);
          else if (DECL_P (original))
@@ -4672,12 +4676,8 @@ convert_arguments (tree typelist, vec<tree, va_gc> 
**values, tree fndecl,
       if (type == void_type_node)
        {
           if (complain & tf_error)
-            {
-             error_args_num (input_location, fndecl, /*too_many_p=*/true);
-              return i;
-            }
-          else
-            return -1;
+           error_args_num (input_location, fndecl, /*too_many_p=*/true);
+         return -1;
        }
 
       /* build_c_cast puts on a NOP_EXPR to make the result not an lvalue.
diff --git a/gcc/cp/typeck2.cc b/gcc/cp/typeck2.cc
index 45edd180173..97852d3c727 100644
--- a/gcc/cp/typeck2.cc
+++ b/gcc/cp/typeck2.cc
@@ -119,6 +119,11 @@ cxx_readonly_error (location_t loc, tree arg, enum 
lvalue_use errstring)
                          G_("increment of read-only reference %qD"),
                          G_("decrement of read-only reference %qD"),
                          TREE_OPERAND (arg, 0));
+  else if (is_stub_object (arg))
+    {
+      gcc_assert (errstring == lv_assign);
+      error_at (loc, "assignment to read-only type %qT", TREE_TYPE (arg));
+    }
   else
     readonly_error (loc, arg, errstring);
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C 
b/gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C
index 3e87da4611e..90d859a6c69 100644
--- a/gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C
@@ -1,49 +1,58 @@
 // PR c++/100474
 // { dg-do compile { target c++20 } }
 
-struct S { S() = delete; S(const S&); };
+struct S { S() = delete; S(const S&); };  // { dg-line S }
 
 template<class T>
 concept Aggregate = __is_aggregate(T);
-// { dg-message "'S' is not an aggregate" "" { target *-*-* } .-1  }
+// { dg-message "'S' is not an aggregate" "" { target *-*-* } S }
 
 template<class T>
 concept TriviallyCopyable = __is_trivially_copyable(T);
-// { dg-message "'S' is not trivially copyable" "" { target *-*-* } .-1  }
+// { dg-message "'S' is not trivially copyable" "" { target *-*-* } S }
 
 template<class T, class U>
 concept Assignable = __is_assignable(T, U);
-// { dg-message "'S' is not assignable from 'int'" "" { target *-*-* } .-1  }
+// { dg-message "'S' is not assignable from 'int', because" "" { target *-*-* 
} .-1  }
+// { dg-error "no match for 'operator='" "" { target *-*-* } .-2 }
 
 template<class T, class U>
 concept TriviallyAssignable = __is_trivially_assignable(T, U);
 // { dg-message "'S' is not trivially assignable from 'int'" "" { target *-*-* 
} .-1  }
+// { dg-error "no match for 'operator='" "" { target *-*-* } .-2 }
 
 template<class T, class U>
 concept NothrowAssignable = __is_nothrow_assignable(T, U);
 // { dg-message "'S' is not nothrow assignable from 'int'" "" { target *-*-* } 
.-1  }
+// { dg-error "no match for 'operator='" "" { target *-*-* } .-2 }
 
 template<class T, class... Args>
 concept Constructible = __is_constructible(T, Args...);
 // { dg-message "'S' is not default constructible" "" { target *-*-* } .-1  }
-// { dg-message "'S' is not constructible from 'int'" "" { target *-*-* } .-2  
}
-// { dg-message "'S' is not constructible from 'int, char'" "" { target *-*-* 
} .-3  }
+// { dg-error "use of deleted function 'S::S\\(\\)'" "" { target *-*-* } .-2 }
+// { dg-message "'S' is not constructible from 'int'" "" { target *-*-* } .-3  
}
+// { dg-message "'S' is not constructible from 'int, char'" "" { target *-*-* 
} .-4  }
+// { dg-error "no matching function for call to 'S::S" "" { target *-*-* } .-5 
}
 
 template<class T, class... Args>
 concept TriviallyConstructible = __is_trivially_constructible(T, Args...);
 // { dg-message "'S' is not trivially default constructible" "" { target *-*-* 
} .-1  }
-// { dg-message "'S' is not trivially constructible from 'int'" "" { target 
*-*-* } .-2  }
-// { dg-message "'S' is not trivially constructible from 'int, char'" "" { 
target *-*-* } .-3  }
+// { dg-error "use of deleted function 'S::S\\(\\)'" "" { target *-*-* } .-2 }
+// { dg-message "'S' is not trivially constructible from 'int'" "" { target 
*-*-* } .-3  }
+// { dg-message "'S' is not trivially constructible from 'int, char'" "" { 
target *-*-* } .-4  }
+// { dg-error "no matching function for call to 'S::S" "" { target *-*-* } .-5 
}
 
 template<class T, class... Args>
 concept NothrowConstructible = __is_nothrow_constructible(T, Args...);
 // { dg-message "'S' is not nothrow default constructible" "" { target *-*-* } 
.-1  }
-// { dg-message "'S' is not nothrow constructible from 'int'" "" { target 
*-*-* } .-2  }
-// { dg-message "'S' is not nothrow constructible from 'int, char'" "" { 
target *-*-* } .-3  }
+// { dg-error "use of deleted function 'S::S\\(\\)'" "" { target *-*-* } .-2 }
+// { dg-message "'S' is not nothrow constructible from 'int'" "" { target 
*-*-* } .-3  }
+// { dg-message "'S' is not nothrow constructible from 'int, char'" "" { 
target *-*-* } .-4  }
+// { dg-error "no matching function for call to 'S::S" "" { target *-*-* } .-5 
}
 
 template<class T>
 concept UniqueObjReps = __has_unique_object_representations(T);
-// { dg-message "'S' does not have unique object representations" "" { target 
*-*-* } .-1  }
+// { dg-message "'S' does not have unique object representations" "" { target 
*-*-* } S }
 
 static_assert(Aggregate<S>); // { dg-error "assert" }
 static_assert(TriviallyCopyable<S>); // { dg-error "assert" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C 
b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
new file mode 100644
index 00000000000..caad816bf3d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
@@ -0,0 +1,77 @@
+// PR c++/117294
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fconcepts-diagnostics-depth=2" }
+
+template <typename T> struct norm
+  { static constexpr bool value = __is_constructible(T); };
+template <typename T> constexpr bool norm_v = __is_constructible(T);
+
+template <typename T> struct part
+  { static constexpr bool value = __is_constructible(T); };
+template <typename T> struct part<T*>
+  { static constexpr bool value = false; };
+template <typename T> struct part<const T>
+  { static constexpr bool value = __is_same(T, void); };
+template <typename T> constexpr bool part_v = __is_constructible(T);
+template <typename T> constexpr bool part_v<T*> = false;
+template <typename T> constexpr bool part_v<const T> = __is_same(T, void);
+
+template <typename T> struct expl
+  { static constexpr bool value = __is_constructible(T); };
+template <> struct expl<int*>
+  { static constexpr bool value = false; };
+template <> struct expl<const int>
+  { static constexpr bool value = __is_same(int, void); };
+template <typename T> constexpr bool expl_v = __is_constructible(T);
+template <> constexpr bool expl_v<int*> = false;
+template <> constexpr bool expl_v<const int> = __is_same(int, void);
+
+template <typename T> concept test_norm = norm<T>::value;  // { dg-line norm }
+template <typename T> concept test_part = part<T>::value;  // { dg-line part }
+template <typename T> concept test_expl = expl<T>::value;  // { dg-line expl }
+template <typename T> concept test_norm_v = norm_v<T>;  // { dg-line norm_v }
+template <typename T> concept test_part_v = part_v<T>;  // { dg-line part_v }
+template <typename T> concept test_expl_v = expl_v<T>;  // { dg-line expl_v }
+
+static_assert(test_norm<void>);  // { dg-error "assert" }
+static_assert(test_part<void>);  // { dg-error "assert" }
+static_assert(test_expl<void>);  // { dg-error "assert" }
+static_assert(test_norm_v<void>);  // { dg-error "assert" }
+static_assert(test_part_v<void>);  // { dg-error "assert" }
+static_assert(test_expl_v<void>);  // { dg-error "assert" }
+// { dg-message "'void' is not default constructible" "" { target *-*-* } norm 
}
+// { dg-message "'void' is not default constructible" "" { target *-*-* } part 
}
+// { dg-message "'void' is not default constructible" "" { target *-*-* } expl 
}
+// { dg-message "'void' is not default constructible" "" { target *-*-* } 
norm_v }
+// { dg-message "'void' is not default constructible" "" { target *-*-* } 
part_v }
+// { dg-message "'void' is not default constructible" "" { target *-*-* } 
expl_v }
+// { dg-prune-output "'void' is incomplete" }
+
+static_assert(test_part<int*>);  // { dg-error "assert" }
+static_assert(test_expl<int*>);  // { dg-error "assert" }
+static_assert(test_part_v<int*>);  // { dg-error "assert" }
+static_assert(test_expl_v<int*>);  // { dg-error "assert" }
+// { dg-message ".with T = int\\*.. evaluated to .false." "" { target *-*-* } 
part }
+// { dg-message ".with T = int\\*.. evaluated to .false." "" { target *-*-* } 
expl }
+// { dg-message ".with T = int\\*.. evaluated to .false." "" { target *-*-* } 
part_v }
+// { dg-message ".with T = int\\*.. evaluated to .false." "" { target *-*-* } 
expl_v }
+
+static_assert(test_part<const int>);  // { dg-error "assert" }
+static_assert(test_part_v<const int>);  // { dg-error "assert" }
+// { dg-message "'int' is not the same as 'void'" "" { target *-*-* } part }
+// { dg-message "'int' is not the same as 'void'" "" { target *-*-* } part_v }
+
+struct S { S(int); };
+static_assert(requires { requires test_norm<S>; });  // { dg-error "assert" }
+static_assert(requires { requires test_part<S>; });  // { dg-error "assert" }
+static_assert(requires { requires test_expl<S>; });  // { dg-error "assert" }
+static_assert(requires { requires test_norm_v<S>; });  // { dg-error "assert" }
+static_assert(requires { requires test_part_v<S>; });  // { dg-error "assert" }
+static_assert(requires { requires test_expl_v<S>; });  // { dg-error "assert" }
+// { dg-message "'S' is not default constructible" "" { target *-*-* } norm }
+// { dg-message "'S' is not default constructible" "" { target *-*-* } part }
+// { dg-message "'S' is not default constructible" "" { target *-*-* } expl }
+// { dg-message "'S' is not default constructible" "" { target *-*-* } norm_v }
+// { dg-message "'S' is not default constructible" "" { target *-*-* } part_v }
+// { dg-message "'S' is not default constructible" "" { target *-*-* } expl_v }
+// { dg-prune-output "no matching function for call" }
diff --git a/gcc/testsuite/g++.dg/diagnostic/static_assert5.C 
b/gcc/testsuite/g++.dg/diagnostic/static_assert5.C
new file mode 100644
index 00000000000..16681b25e38
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/static_assert5.C
@@ -0,0 +1,70 @@
+// PR c++/117294
+// { dg-do compile { target c++14 } }
+
+template <typename T> struct norm
+  { static constexpr bool value = __is_constructible(T); };
+template <typename T> constexpr bool norm_v = __is_constructible(T);
+
+template <typename T> struct part
+  { static constexpr bool value = __is_constructible(T); };
+template <typename T> struct part<T*>
+  { static constexpr bool value = false; };
+template <typename T> struct part<const T>
+  { static constexpr bool value = __is_same(T, void); };
+template <typename T> constexpr bool part_v = __is_constructible(T);
+template <typename T> constexpr bool part_v<T*> = false;
+template <typename T> constexpr bool part_v<const T> = __is_same(T, void);
+
+template <typename T> struct expl
+  { static constexpr bool value = __is_constructible(T); };
+template <> struct expl<int*>
+  { static constexpr bool value = false; };
+template <> struct expl<const int>
+  { static constexpr bool value = __is_same(int, void); };
+template <typename T> constexpr bool expl_v = __is_constructible(T);
+template <> constexpr bool expl_v<int*> = false;
+template <> constexpr bool expl_v<const int> = __is_same(int, void);
+
+// === Primary template can give customised diagnostics when using traits
+static_assert(norm<void>::value);  // { dg-error "assert" }
+// { dg-message "'void' is not default constructible" "" { target *-*-* } .-1 }
+static_assert(part<void>::value);  // { dg-error "assert" }
+// { dg-message "'void' is not default constructible" "" { target *-*-* } .-1 }
+static_assert(expl<void>::value);  // { dg-error "assert" }
+// { dg-message "'void' is not default constructible" "" { target *-*-* } .-1 }
+static_assert(norm_v<void>);  // { dg-error "assert" }
+// { dg-message "'void' is not default constructible" "" { target *-*-* } .-1 }
+static_assert(part_v<void>);  // { dg-error "assert" }
+// { dg-message "'void' is not default constructible" "" { target *-*-* } .-1 }
+static_assert(expl_v<void>);  // { dg-error "assert" }
+// { dg-message "'void' is not default constructible" "" { target *-*-* } .-1 }
+
+// { dg-prune-output "'void' is incomplete" }
+
+
+// === Specialisations don't customise just because primary template had trait
+static_assert(part<int*>::value);  // { dg-error "assert" }
+// { dg-bogus "default constructible" "" { target *-*-* } .-1 }
+static_assert(expl<int*>::value);  // { dg-error "assert" }
+// { dg-bogus "default constructible" "" { target *-*-* } .-1 }
+static_assert(part_v<int*>);  // { dg-error "assert" }
+// { dg-bogus "default constructible" "" { target *-*-* } .-1 }
+static_assert(expl_v<int*>);  // { dg-error "assert" }
+// { dg-bogus "default constructible" "" { target *-*-* } .-1 }
+
+
+// === But partial specialisations actually using a trait can customise
+static_assert(part<const int>::value);  // { dg-error "assert" }
+// { dg-message "'int' is not the same as 'void'" "" { target *-*-* } .-1 }
+static_assert(part_v<const int>);  // { dg-error "assert" }
+// { dg-message "'int' is not the same as 'void'" "" { target *-*-* } .-1 }
+
+
+// === For these cases, we no longer know that the error was caused by the 
trait
+// === because it's been folded away before we process the failure.
+static_assert(expl<const int>::value);  // { dg-error "assert" }
+// { dg-bogus "because" "" { target *-*-* } .-1 }
+static_assert(expl_v<const int>);  // { dg-error "assert" }
+// { dg-bogus "because" "" { target *-*-* } .-1 }
+static_assert(__is_constructible(void));  // { dg-error "assert" }
+// { dg-bogus "because" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C 
b/gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C
new file mode 100644
index 00000000000..14eea8095d3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C
@@ -0,0 +1,27 @@
+// { dg-do compile { target c++11 } }
+
+template <typename T> struct has_virtual_destructor {
+  static constexpr bool value = __has_virtual_destructor(T);
+};
+
+static_assert(has_virtual_destructor<int>::value, "");  // { dg-error "assert" 
}
+// { dg-message "'int' does not have a virtual destructor" "" { target *-*-* } 
.-1 }
+
+struct A {};  // { dg-message "'A' does not have a virtual destructor" }
+static_assert(has_virtual_destructor<A>::value, "");  // { dg-error "assert" }
+
+struct B {
+  ~B();  // { dg-message "'B' does not have a virtual destructor" }
+};
+static_assert(has_virtual_destructor<B>::value, "");  // { dg-error "assert" }
+
+struct C {  // { dg-bogus "" }
+  virtual ~C();  // { dg-bogus "" }
+};
+static_assert(has_virtual_destructor<C[5]>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'C \\\[5\\\]' does not have a virtual destructor" "" { target 
*-*-* } .-1 }
+
+union U {  // { dg-message "'U' does not have a virtual destructor" }
+  ~U();
+};
+static_assert(has_virtual_destructor<U>::value, "");  // { dg-error "assert" }
diff --git a/gcc/testsuite/g++.dg/ext/is_assignable2.C 
b/gcc/testsuite/g++.dg/ext/is_assignable2.C
new file mode 100644
index 00000000000..b346d7b235d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_assignable2.C
@@ -0,0 +1,36 @@
+// { dg-do compile { target c++11 } }
+
+template <typename T>
+struct is_copy_assignable {
+  static constexpr bool value = __is_assignable(T&, const T&);
+};
+
+static_assert(is_copy_assignable<const int>::value, "");  // { dg-error 
"assert" }
+// { dg-error "assignment to read-only type 'const int'" "" { target *-*-* } 
.-1 }
+
+struct A {
+  void operator=(A) = delete;  // { dg-message "declared here" }
+};
+static_assert(is_copy_assignable<A>::value, "");  // { dg-error "assert" }
+// { dg-message "is not assignable" "" { target *-*-* } .-1 }
+// { dg-error "use of deleted function" "" { target *-*-* } .-2 }
+
+template <typename T>
+struct is_nothrow_copy_assignable {
+  static constexpr bool value = __is_nothrow_assignable(T&, const T&);
+};
+struct B {
+  void operator=(const B&);  // { dg-message "noexcept" }
+};
+static_assert(is_nothrow_copy_assignable<B>::value, "");  // { dg-error 
"assert" }
+// { dg-message "is not nothrow assignable" "" { target *-*-* } .-1 }
+
+template <typename T>
+struct is_trivially_copy_assignable {
+  static constexpr bool value = __is_trivially_assignable(T&, const T&);
+};
+struct C {
+  void operator=(const C&);  // { dg-message "non-trivial" }
+};
+static_assert(is_trivially_copy_assignable<C>::value, "");  // { dg-error 
"assert" }
+// { dg-message "is not trivially assignable" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/g++.dg/ext/is_constructible9.C 
b/gcc/testsuite/g++.dg/ext/is_constructible9.C
new file mode 100644
index 00000000000..5448878c122
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_constructible9.C
@@ -0,0 +1,60 @@
+// { dg-do compile { target c++11 } }
+
+template <typename T, typename... Args>
+struct is_constructible {
+  static constexpr bool value = __is_constructible(T, Args...);
+};
+
+static_assert(is_constructible<void>::value, "");  // { dg-error "assert" }
+// { dg-message "'void' is not default constructible, because" "" { target 
*-*-* } .-1 }
+// { dg-error "'void' is incomplete" "" { target *-*-* } .-2 }
+
+static_assert(is_constructible<int&, const int&>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'int&' is not constructible from 'const int&', because" "" { 
target *-*-* } .-1 }
+// { dg-error "discards qualifiers" "" { target *-*-* } .-2 }
+
+static_assert(is_constructible<int, int, int>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'int' is not constructible from 'int, int', because" "" { 
target *-*-* } .-1 }
+// { dg-error "too many initializers for non-class type 'int'" "" { target 
*-*-* } .-2 }
+
+struct A {
+  A(int);  // { dg-message "candidate" }
+};
+static_assert(is_constructible<A, int, int>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'A' is not constructible from 'int, int', because" "" { 
target *-*-* } .-1 }
+// { dg-error "no matching function for call to" "" { target *-*-* } .-2 }
+
+template <typename T, typename... Args>
+struct is_nothrow_constructible {
+  static constexpr bool value = __is_nothrow_constructible(T, Args...);
+};
+
+struct B {
+  B(int);  // { dg-message "candidate" }
+};
+static_assert(is_nothrow_constructible<B>::value, "");  // { dg-error "assert" 
}
+// { dg-message "'B' is not nothrow default constructible, because" "" { 
target *-*-* } .-1 }
+// { dg-error "no matching function for call to" "" { target *-*-* } .-2 }
+
+struct C {
+  C(int);  // { dg-message "noexcept" }
+};
+static_assert(is_nothrow_constructible<C, int>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'C' is not nothrow constructible from 'int', because" "" { 
target *-*-* } .-1 }
+
+template <typename T, typename... Args>
+struct is_trivially_constructible {
+  static constexpr bool value = __is_trivially_constructible(T, Args...);
+};
+
+struct D {
+  D();  // { dg-message "non-trivial" }
+};
+static_assert(is_trivially_constructible<D>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'D' is not trivially default constructible, because" "" { 
target *-*-* } .-1 }
+
+struct E {
+  operator int();  // { dg-message "non-trivial" }
+};
+static_assert(is_trivially_constructible<int, E>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'int' is not trivially constructible from 'E', because" "" { 
target *-*-* } .-1 }
diff --git a/gcc/testsuite/g++.dg/ext/is_convertible7.C 
b/gcc/testsuite/g++.dg/ext/is_convertible7.C
new file mode 100644
index 00000000000..b38fc042de9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_convertible7.C
@@ -0,0 +1,29 @@
+// { dg-do compile { target c++11 } }
+
+template <typename T, typename U>
+struct is_convertible {
+  static constexpr bool value = __is_convertible(T, U);
+};
+
+static_assert(is_convertible<int*, int>::value, "");  // { dg-error "assert" }
+// { dg-error "invalid conversion" "" { target *-*-* } .-1 }
+
+static_assert(is_convertible<int(), double (*)()>::value, "");  // { dg-error 
"assert" }
+// { dg-error "invalid conversion" "" { target *-*-* } .-1 }
+
+struct A {
+  explicit A(int);
+};
+static_assert(is_convertible<int, A>::value, "");  // { dg-error "assert" }
+// { dg-error "could not convert 'int' to 'A'" "" { target *-*-* } .-1 }
+
+template <typename T, typename U>
+struct is_nothrow_convertible {
+  static constexpr bool value = __is_nothrow_convertible(T, U);
+};
+
+struct B {
+  B(int);  // { dg-message "noexcept" }
+};
+static_assert(is_nothrow_convertible<int, B>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'int' is not nothrow convertible from 'B', because" "" { 
target *-*-* } .-1 }
diff --git a/gcc/testsuite/g++.dg/ext/is_destructible3.C 
b/gcc/testsuite/g++.dg/ext/is_destructible3.C
new file mode 100644
index 00000000000..a8501d637ab
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_destructible3.C
@@ -0,0 +1,65 @@
+// { dg-do compile { target c++11 } }
+
+template <typename T>
+struct is_destructible {
+  static constexpr bool value = __is_destructible(T);
+};
+
+static_assert(is_destructible<void>::value, "");  // { dg-error "assert" }
+// { dg-message "'void' is not destructible, because" "" { target *-*-* } .-1 }
+// { dg-error "'void' is incomplete" "" { target *-*-* } .-2 }
+
+struct A {
+  ~A() = delete;  // { dg-message "declared here" }
+};
+static_assert(is_destructible<A>::value, "");  // { dg-error "assert" }
+// { dg-message "'A' is not destructible, because" "" { target *-*-* } .-1 }
+// { dg-error "use of deleted function" "" { target *-*-* } .-2 }
+
+struct B {
+private:
+  ~B();  // { dg-message "declared private here" }
+};
+static_assert(is_destructible<B>::value, "");  // { dg-error "assert" }
+// { dg-message "'B' is not destructible, because" "" { target *-*-* } .-1 }
+// { dg-error "private within this context" "" { target *-*-* } .-2 }
+
+template <typename T>
+struct is_nothrow_destructible {
+  static constexpr bool value = __is_nothrow_destructible(T);
+};
+
+struct C {
+  ~C() noexcept(false);  // { dg-message "noexcept" }
+};
+static_assert(is_nothrow_destructible<C>::value, "");  // { dg-error "assert" }
+// { dg-message "'C' is not nothrow destructible, because" "" { target *-*-* } 
.-1 }
+
+struct D {
+private:
+  ~D() {}  // { dg-message "declared private here" }
+};
+static_assert(is_nothrow_destructible<D>::value, "");  // { dg-error "assert" }
+// { dg-message "'D' is not nothrow destructible, because" "" { target *-*-* } 
.-1 }
+// { dg-error "private within this context" "" { target *-*-* } .-2 }
+
+template <typename T>
+struct is_trivially_destructible {
+  static constexpr bool value = __is_trivially_destructible(T);
+};
+
+struct E {
+  ~E();
+};
+struct F { E d; };  // { dg-message "non-trivial" }
+static_assert(is_trivially_destructible<F>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'F' is not trivially destructible, because" "" { target *-*-* 
} .-1 }
+
+struct G {
+private:
+  ~G();  // { dg-message "declared private here" }
+};
+struct H { G g; };  // { dg-error "private within this context" }
+static_assert(is_trivially_destructible<H>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'H' is not trivially destructible, because" "" { target *-*-* 
} .-1 }
+// { dg-error "use of deleted function" "" { target *-*-* } .-2 }
diff --git a/gcc/testsuite/g++.dg/ext/is_invocable6.C 
b/gcc/testsuite/g++.dg/ext/is_invocable6.C
new file mode 100644
index 00000000000..64c5c760389
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_invocable6.C
@@ -0,0 +1,45 @@
+// { dg-do compile { target c++11 } }
+
+template <typename F, typename... Args>
+struct is_invocable {
+  static constexpr bool value = __is_invocable(F, Args...);
+};
+
+static_assert(is_invocable<int>::value, "");  // { dg-error "assert" }
+// { dg-message "'int' is not invocable, because" "" { target *-*-* } .-1 }
+// { dg-error "'int' cannot be used as a function" "" { target *-*-* } .-2 }
+
+static_assert(is_invocable<void(*)(), int>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'void \[^'\]*' is not invocable by 'int', because" "" { 
target *-*-* } .-1 }
+// { dg-error "too many arguments" "" { target *-*-* } .-2 }
+
+static_assert(is_invocable<void(void*), void() const>::value, "");  // { 
dg-error "assert" }
+// { dg-message "'void.void..' is not invocable by 'void.. const', because" "" 
{ target *-*-* } .-1 }
+// { dg-error "qualified function type" "" { target *-*-* } .-2 }
+
+struct A {};
+static_assert(is_invocable<const A&&, int, double>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'const A&&' is not invocable by 'int, double', because" "" { 
target *-*-* } .-1 }
+// { dg-error "no match for call to " "" { target *-*-* } .-2 }
+
+struct B {
+  void operator()() = delete;  // { dg-message "declared here" }
+};
+static_assert(is_invocable<B>::value, "");  // { dg-error "assert" }
+// { dg-message "'B' is not invocable, because" "" { target *-*-* } .-1 }
+// { dg-error "use of deleted function" "" { target *-*-* } .-2 }
+
+template <typename F, typename... Args>
+struct is_nothrow_invocable {
+  static constexpr bool value = __is_nothrow_invocable(F, Args...);
+};
+
+static_assert(is_nothrow_invocable<void(*)()>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'void \[^'\]*' is not nothrow invocable, because" "" { target 
*-*-* } .-1 }
+// { dg-message "'void \[^'\]*' is not 'noexcept'" "" { target *-*-* } .-2 }
+
+struct C {
+  int operator()(int, double) const;  // { dg-message "noexcept" }
+};
+static_assert(is_nothrow_invocable<const C&, int, int>::value, "");  // { 
dg-error "assert" }
+// { dg-message "'const C&' is not nothrow invocable by 'int, int', because" 
"" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C 
b/gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C
new file mode 100644
index 00000000000..ac28121d49f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C
@@ -0,0 +1,13 @@
+// { dg-do compile { target c++11 } }
+
+template <typename T, typename U>
+struct is_virtual_base_of {
+  static constexpr bool value = __builtin_is_virtual_base_of(T, U);
+};
+
+static_assert(is_virtual_base_of<int, int>::value, "");  // { dg-error 
"assert" }
+// { dg-message "'int' is not a virtual base of 'int'" "" { target *-*-* } .-1 
}
+
+struct A {};  // { dg-message "'A' is not a virtual base of 'B'" }
+struct B : A {};  // { dg-message "declared here" }
+static_assert(is_virtual_base_of<A, B>::value, "");  // { dg-error "assert" }
diff --git a/libstdc++-v3/testsuite/20_util/any/misc/any_cast_neg.cc 
b/libstdc++-v3/testsuite/20_util/any/misc/any_cast_neg.cc
index 9740e09ac5e..9e5c64cf3f6 100644
--- a/libstdc++-v3/testsuite/20_util/any/misc/any_cast_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/any/misc/any_cast_neg.cc
@@ -27,6 +27,7 @@ void test01()
   const any y(1);
   any_cast<int&>(y); // { dg-error "here" }
   // { dg-error "Template argument must be constructible from a const value" 
"" { target { *-*-* } } 0 }
+  // { dg-error "binding reference of type 'int&' to 'const int' discards 
qualifiers" "" { target { *-*-* } } 0 }
 }
 
 void test02()
@@ -34,6 +35,7 @@ void test02()
   any y(1);
   any_cast<int&&>(y); // { dg-error "here" }
   // { dg-error "Template argument must be constructible from an lvalue" "" { 
target { *-*-* } } 0 }
+  // { dg-error "cannot bind rvalue reference of type 'int&&' to lvalue of 
type 'int'" "" { target { *-*-* } } 0 }
 }
 
 void test03()
@@ -41,6 +43,7 @@ void test03()
   any y(1);
   any_cast<int&>(std::move(y)); // { dg-error "here" }
   // { dg-error "Template argument must be constructible from an rvalue" "" { 
target { *-*-* } } 0 }
+  // { dg-error "cannot bind non-const lvalue reference of type 'int&' to an 
rvalue of type 'int'" "" { target { *-*-* } } 0 }
 }
 
 // { dg-prune-output "invalid 'static_cast'" }
diff --git a/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc 
b/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc
index 69c13b48a22..69aa4a17960 100644
--- a/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc
@@ -13,6 +13,7 @@ test_unexpected()
   std::unexpected<void()> func(test_unexpected); // { dg-error "here" }
   // { dg-error "no matching function for call to" "" { target *-*-* } 0 }
   // { dg-error "invalidly declared function type" "" { target *-*-* } 0 }
+  // { dg-error "could not convert" "" { target *-*-* } 0 }
 
   // an array type,
   std::unexpected<int[2]> array(i); // { dg-error "here" }
diff --git a/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc 
b/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
index f5028c15bc3..12a67bbc118 100644
--- a/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
@@ -26,4 +26,5 @@ test02()
 
   std::optional<move_only> mo;
   mo.or_else([]{ return std::optional<move_only>{}; }); // { dg-error "no 
matching function" }
+  // { dg-error "use of deleted function" "" { target *-*-* } 0 }
 }
diff --git a/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc 
b/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc
index ae06302b952..133522809b1 100644
--- a/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc
+++ b/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc
@@ -54,3 +54,5 @@ test03()
 }
 
 // { dg-prune-output "static assertion failed" }
+// { dg-prune-output "use of deleted function" }
+// { dg-prune-output "could not convert" }
diff --git a/libstdc++-v3/testsuite/24_iterators/range_generators/lwg3900.cc 
b/libstdc++-v3/testsuite/24_iterators/range_generators/lwg3900.cc
index 957879e8862..08fd5c2ab1d 100644
--- a/libstdc++-v3/testsuite/24_iterators/range_generators/lwg3900.cc
+++ b/libstdc++-v3/testsuite/24_iterators/range_generators/lwg3900.cc
@@ -13,4 +13,5 @@ bar(std::allocator_arg_t, std::pmr::memory_resource& mr) // { 
dg-error "here" }
 }
 
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
-// { dg-error "no matching function .*memory_resource&" "" { target *-*-* } 0 }
+// { dg-error "could not convert 'const std::pmr::memory_resource'" "" { 
target *-*-* } 0 }
+// { dg-error "no matching function \[^\n\r\]*memory_resource&" "" { target 
*-*-* } 0 }
diff --git a/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc 
b/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc
index 5aa32433c7a..cfe44d255ca 100644
--- a/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc
+++ b/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc
@@ -20,6 +20,7 @@
 #include <atomic>
 
 std::atomic<const int> a; // { dg-error "here" }
+// { dg-error "assignment to read-only type" "" { target *-*-* } 0 }
 
 struct MoveOnly
 {
@@ -40,3 +41,4 @@ struct NoMove
 std::atomic<NoMove> c; // { dg-error "here" }
 
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
+// { dg-error "use of deleted function" "" { target *-*-* } 0 }
diff --git 
a/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc 
b/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc
index 93b31863650..2b2fce42d2f 100644
--- 
a/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc
+++ 
b/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc
@@ -32,3 +32,4 @@ test01(std::stop_token& tok, F& f)
 }
 
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
+// { dg-error "no match for call" "" { target *-*-* } 0 }
diff --git a/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc 
b/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc
index ded56fe63ab..83c7b22d081 100644
--- a/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc
+++ b/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc
@@ -42,3 +42,4 @@ void test_const_arg()
 }
 
 // { dg-prune-output "no matching function for call to .*::basic_format_arg<" }
+// { dg-prune-output "use of deleted function" }
diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc 
b/libstdc++-v3/testsuite/std/format/string_neg.cc
index 09cc9a25b3e..acae88eaf71 100644
--- a/libstdc++-v3/testsuite/std/format/string_neg.cc
+++ b/libstdc++-v3/testsuite/std/format/string_neg.cc
@@ -8,3 +8,5 @@ auto s = std::format(" {9} "); // { dg-error "call to consteval 
function" }
 struct X { };
 std::format_string<X> str(""); // { dg-error "here" }
 // { dg-error "std::formatter must be specialized" "" { target *-*-* } 0 }
+
+// { dg-prune-output "use of deleted function" }
-- 
2.47.0


Reply via email to