diff options
Diffstat (limited to 'gcc')
-rw-r--r-- | gcc/cp/call.cc | 20 | ||||
-rw-r--r-- | gcc/cp/constexpr.cc | 5 | ||||
-rw-r--r-- | gcc/cp/constraint.cc | 270 | ||||
-rw-r--r-- | gcc/cp/cp-tree.h | 16 | ||||
-rw-r--r-- | gcc/cp/error.cc | 14 | ||||
-rw-r--r-- | gcc/cp/except.cc | 12 | ||||
-rw-r--r-- | gcc/cp/method.cc | 221 | ||||
-rw-r--r-- | gcc/cp/typeck.cc | 14 | ||||
-rw-r--r-- | gcc/cp/typeck2.cc | 5 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C | 31 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C | 77 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/diagnostic/static_assert5.C | 70 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C | 27 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/ext/is_assignable2.C | 36 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/ext/is_constructible9.C | 60 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/ext/is_convertible7.C | 29 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/ext/is_destructible3.C | 65 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/ext/is_invocable6.C | 45 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C | 13 |
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" } |