aboutsummaryrefslogtreecommitdiff
path: root/gcc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc')
-rw-r--r--gcc/cp/call.cc20
-rw-r--r--gcc/cp/constexpr.cc5
-rw-r--r--gcc/cp/constraint.cc270
-rw-r--r--gcc/cp/cp-tree.h16
-rw-r--r--gcc/cp/error.cc14
-rw-r--r--gcc/cp/except.cc12
-rw-r--r--gcc/cp/method.cc221
-rw-r--r--gcc/cp/typeck.cc14
-rw-r--r--gcc/cp/typeck2.cc5
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C31
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C77
-rw-r--r--gcc/testsuite/g++.dg/diagnostic/static_assert5.C70
-rw-r--r--gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C27
-rw-r--r--gcc/testsuite/g++.dg/ext/is_assignable2.C36
-rw-r--r--gcc/testsuite/g++.dg/ext/is_constructible9.C60
-rw-r--r--gcc/testsuite/g++.dg/ext/is_convertible7.C29
-rw-r--r--gcc/testsuite/g++.dg/ext/is_destructible3.C65
-rw-r--r--gcc/testsuite/g++.dg/ext/is_invocable6.C45
-rw-r--r--gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C13
19 files changed, 844 insertions, 186 deletions
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index c925dd1..c76b15b 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 1a77954..3d52297 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 8d7aec3..d4a83e4 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 6810250..01112aa 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 eb2ff33..177f28f 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 a7f35e4..2c1ef4c 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 334c325..22a84b9 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 a604511..a66586d 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 45edd18..97852d3 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 3e87da4..90d859a 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 0000000..caad816
--- /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 0000000..16681b2
--- /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 0000000..14eea80
--- /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 0000000..b346d7b
--- /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 0000000..5448878
--- /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 0000000..b38fc04
--- /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 0000000..a8501d6
--- /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 0000000..64c5c76
--- /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 0000000..ac28121
--- /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" }