From 861d4af8d82819a857e360949545651adf33a264 Mon Sep 17 00:00:00 2001 From: Andrew Sutton Date: Wed, 27 Nov 2019 15:23:02 +0000 Subject: [PATCH] re PR c++/92236 ([concepts] Explain non-satisfaction in static_assert) 2019-11-27 Andrew Sutton PR c++/92236 Defer evaluation of concept checks so that static assertions can emit more detailed diagnostics. gcc/cp/ * constexpr.c (cxx_eval_call_expression): Handle concept checks. (cxx_eval_constant_expression): Diagnose misuse of function concepts as template-id expressions. Follow the usual return path for results. (cxx_eval_outermost_constant_expr): Avoid calling cp_get_callee_fndecl_nofold for function concepts. * constraint.cc (build_function_check): Fully type the concept check so that we don't ICE in conversions. * cp-gimplify.c (cp_genericize_r) [CALL_EXPR]: Handle concept checks. [TEMPLATE_ID_EXPR] Likewise. * cvt.c (convert_to_void): Always evaluate concept checks so we don't accidentally ignore them. Substitution during satisfaction can make a program ill-formed (example in g++.dg/cpp2a/concepts6.C). * pt.c (tsubst_copy_and_build): [CALL_EXPR]: Don't evaluate concepts. [TEMPLATE_ID_EXPR]: Likewise. * semantics.c (finish_call_expr): Don't evaluate concepts. (finish_id_expression_1): Likewise. (finish_static_assert): Preserve the original condition so we can diagnose concept errors when a check returns false. gcc/testsuite/ * g++.dg/cpp2a/concepts-iconv1.C: Update diagnostics. * g++.dg/cpp2a/concepts-requires5.C: Likewise. * g++.dg/cpp2a/concepts6.C: New test. From-SVN: r278775 --- gcc/cp/ChangeLog | 24 ++++++++++++ gcc/cp/constexpr.c | 38 ++++++++++++++----- gcc/cp/constraint.cc | 2 + gcc/cp/cp-gimplify.c | 16 ++++++++ gcc/cp/cvt.c | 6 +++ gcc/cp/pt.c | 11 ------ gcc/cp/semantics.c | 20 +++++----- gcc/testsuite/ChangeLog | 7 ++++ gcc/testsuite/g++.dg/cpp2a/concepts-iconv1.C | 8 ++-- .../g++.dg/cpp2a/concepts-requires5.C | 4 +- 10 files changed, 99 insertions(+), 37 deletions(-) diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index 36302e43aca..c3e66d4e40f 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,27 @@ +2019-11-27 Andrew Sutton + + PR c++/92236 + Defer evaluation of concept checks so that static assertions can + emit more detailed diagnostics. + * constexpr.c (cxx_eval_call_expression): Handle concept checks. + (cxx_eval_constant_expression): Diagnose misuse of function concepts + as template-id expressions. Follow the usual return path for results. + (cxx_eval_outermost_constant_expr): Avoid calling + cp_get_callee_fndecl_nofold for function concepts. + * constraint.cc (build_function_check): Fully type the concept check + so that we don't ICE in conversions. + * cp-gimplify.c (cp_genericize_r) [CALL_EXPR]: Handle concept checks. + [TEMPLATE_ID_EXPR] Likewise. + * cvt.c (convert_to_void): Always evaluate concept checks so we don't + accidentally ignore them. Substitution during satisfaction can make + a program ill-formed (example in g++.dg/cpp2a/concepts6.C). + * pt.c (tsubst_copy_and_build): [CALL_EXPR]: Don't evaluate concepts. + [TEMPLATE_ID_EXPR]: Likewise. + * semantics.c (finish_call_expr): Don't evaluate concepts. + (finish_id_expression_1): Likewise. + (finish_static_assert): Preserve the original condition so we can + diagnose concept errors when a check returns false. + 2019-11-27 Andrew Sutton PR c++/92439 diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 32d929b82f3..ee3ccb9691c 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -1672,6 +1672,10 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, bool lval, bool *non_constant_p, bool *overflow_p) { + /* Handle concept checks separately. */ + if (concept_check_p (t)) + return evaluate_concept_check (t, tf_warning_or_error); + location_t loc = cp_expr_loc_or_input_loc (t); tree fun = get_function_named_in_call (t); constexpr_call new_call @@ -5645,14 +5649,26 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, { /* We can evaluate template-id that refers to a concept only if the template arguments are non-dependent. */ - if (!concept_definition_p (TREE_OPERAND (t, 0))) + tree id = unpack_concept_check (t); + tree tmpl = TREE_OPERAND (id, 0); + if (!concept_definition_p (tmpl)) internal_error ("unexpected template-id %qE", t); + if (function_concept_p (tmpl)) + { + if (!ctx->quiet) + error_at (cp_expr_loc_or_input_loc (t), + "function concept must be called"); + r = error_mark_node; + break; + } + if (!processing_template_decl) - return evaluate_concept_check (t, tf_warning_or_error); + r = evaluate_concept_check (t, tf_warning_or_error); else *non_constant_p = true; - return t; + + break; } case ASM_EXPR: @@ -5809,12 +5825,16 @@ cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant, || TREE_CODE (t) == AGGR_INIT_EXPR || TREE_CODE (t) == TARGET_EXPR)) { - tree x = t; - if (TREE_CODE (x) == TARGET_EXPR) - x = TARGET_EXPR_INITIAL (x); - tree fndecl = cp_get_callee_fndecl_nofold (x); - if (fndecl && DECL_IMMEDIATE_FUNCTION_P (fndecl)) - is_consteval = true; + /* For non-concept checks, determine if it is consteval. */ + if (!concept_check_p (t)) + { + tree x = t; + if (TREE_CODE (x) == TARGET_EXPR) + x = TARGET_EXPR_INITIAL (x); + tree fndecl = cp_get_callee_fndecl_nofold (x); + if (fndecl && DECL_IMMEDIATE_FUNCTION_P (fndecl)) + is_consteval = true; + } } if (AGGREGATE_TYPE_P (type) || VECTOR_TYPE_P (type)) { diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc index 9967b1ef996..fadbe7c8ac0 100644 --- a/gcc/cp/constraint.cc +++ b/gcc/cp/constraint.cc @@ -1263,6 +1263,7 @@ build_function_check (tree tmpl, tree args, tsubst_flags_t /*complain*/) ++processing_template_decl; vec *fargs = make_tree_vector (); tree call = build_min_nt_call_vec (id, fargs); + TREE_TYPE (call) = boolean_type_node; release_tree_vector (fargs); --processing_template_decl; @@ -1397,6 +1398,7 @@ build_constrained_parameter (tree cnc, tree proto, tree args) Note that the constraints are neither reduced nor decomposed. That is done only after the requires clause has been parsed (or not). */ + tree finish_shorthand_constraint (tree decl, tree constr) { diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c index 60766978c71..7942afa7ece 100644 --- a/gcc/cp/cp-gimplify.c +++ b/gcc/cp/cp-gimplify.c @@ -1622,6 +1622,15 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data) break; case CALL_EXPR: + /* Evaluate function concept checks instead of treating them as + normal functions. */ + if (concept_check_p (stmt)) + { + *stmt_p = evaluate_concept_check (stmt, tf_warning_or_error); + * walk_subtrees = 0; + break; + } + if (!wtd->no_sanitize_p && sanitize_flags_p ((SANITIZE_NULL | SANITIZE_ALIGNMENT | SANITIZE_VPTR))) @@ -1679,6 +1688,13 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data) TARGET_EXPR_NO_ELIDE (stmt) = 1; break; + case TEMPLATE_ID_EXPR: + gcc_assert (concept_check_p (stmt)); + /* Emit the value of the concept check. */ + *stmt_p = evaluate_concept_check (stmt, tf_warning_or_error); + walk_subtrees = 0; + break; + default: if (IS_TYPE_OR_DECL_P (stmt)) *walk_subtrees = 0; diff --git a/gcc/cp/cvt.c b/gcc/cp/cvt.c index e67b32ea3b0..d916e39ee90 100644 --- a/gcc/cp/cvt.c +++ b/gcc/cp/cvt.c @@ -1135,6 +1135,12 @@ convert_to_void (tree expr, impl_conv_void implicit, tsubst_flags_t complain) error_at (loc, "pseudo-destructor is not called"); return error_mark_node; } + + /* Explicitly evaluate void-converted concept checks since their + satisfaction may produce ill-formed programs. */ + if (concept_check_p (expr)) + expr = evaluate_concept_check (expr, tf_warning_or_error); + if (VOID_TYPE_P (TREE_TYPE (expr))) return expr; switch (TREE_CODE (expr)) diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index eb907c56385..3eed27bb426 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -18860,12 +18860,6 @@ tsubst_copy_and_build (tree t, if (function_concept_p (TREE_OPERAND (id, 0))) RETURN (id); - /* Evaluate the concept, if needed. */ - tree args = TREE_OPERAND (id, 1); - if (!uses_template_parms (args) - && !processing_constraint_expression_p ()) - RETURN (evaluate_concept_check (check, complain)); - RETURN (check); } @@ -19650,11 +19644,6 @@ tsubst_copy_and_build (tree t, /* Ensure the result is wrapped as a call expression. */ ret = build_concept_check (tmpl, args, tf_warning_or_error); - - /* Possibly evaluate the check if it is non-dependent. */ - if (!uses_template_parms (args) - && !processing_constraint_expression_p ()) - ret = evaluate_concept_check (ret, complain); } else ret = finish_call_expr (function, &call_args, diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c index 6c4785c6858..4a5479cb4b4 100644 --- a/gcc/cp/semantics.c +++ b/gcc/cp/semantics.c @@ -2605,10 +2605,6 @@ finish_call_expr (tree fn, vec **args, bool disallow_virtual, /* Ensure the result is wrapped as a call expression. */ result = build_concept_check (tmpl, args, tf_warning_or_error); - - /* Evaluate the check if it is non-dependent. */ - if (!uses_template_parms (args)) - result = evaluate_concept_check (result, complain); } else if (is_overloaded_fn (fn)) { @@ -3890,13 +3886,8 @@ finish_id_expression_1 (tree id_expression, } else if (concept_check_p (decl)) { - /* If this is a standard or variable concept check, potentially - evaluate it. Function concepts need to be called as functions, - so don't try evaluating them here. */ - tree tmpl = TREE_OPERAND (decl, 0); - tree args = TREE_OPERAND (decl, 1); - if (!function_concept_p (tmpl) && !uses_template_parms (args)) - decl = evaluate_concept_check (decl, tf_warning_or_error); + /* Nothing more to do. All of the analysis for concept checks + is done by build_conept_id, called from the parser. */ } else if (scope) { @@ -9564,6 +9555,9 @@ finish_static_assert (tree condition, tree message, location_t location, return; } + /* Save the condition in case it was a concept check. */ + tree orig_condition = condition; + /* Fold the expression and convert it to a boolean value. */ condition = perform_implicit_conversion_flags (boolean_type_node, condition, complain, LOOKUP_NORMAL); @@ -9590,6 +9584,10 @@ finish_static_assert (tree condition, tree message, location_t location, else error ("static assertion failed: %s", TREE_STRING_POINTER (message)); + + /* Actually explain the failure if this is a concept check. */ + if (concept_check_p (orig_condition)) + diagnose_constraints (location, orig_condition, NULL_TREE); } else if (condition && condition != error_mark_node) { diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 9b22c91ce69..aca3864b184 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,10 @@ +2019-11-27 Andrew Sutton + + PR c++/92236 + * g++.dg/cpp2a/concepts-iconv1.C: Update diagnostics. + * g++.dg/cpp2a/concepts-requires5.C: Likewise. + * g++.dg/cpp2a/concepts6.C: New test. + 2019-11-27 Andrew Sutton PR c++/92439 diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-iconv1.C b/gcc/testsuite/g++.dg/cpp2a/concepts-iconv1.C index cc2ce7e321d..4d521c30748 100644 --- a/gcc/testsuite/g++.dg/cpp2a/concepts-iconv1.C +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-iconv1.C @@ -7,16 +7,16 @@ int foo(int x) { return x; } - + template concept C1 = requires (T x) { - {foo(x)} -> Same; + {foo(x)} -> Same; // { dg-error "placeholder constraints" } }; template concept C2 = requires (T x) { - {foo(x)} -> Same; + {foo(x)} -> Same; // { dg-error "placeholder constraints" } }; - + static_assert( C1 ); // { dg-error "assert" } static_assert( C2 ); // { dg-error "assert" } diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-requires5.C b/gcc/testsuite/g++.dg/cpp2a/concepts-requires5.C index fe37ed48e79..133d29e45a4 100644 --- a/gcc/testsuite/g++.dg/cpp2a/concepts-requires5.C +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-requires5.C @@ -33,13 +33,13 @@ int driver_1() // Test implicit conversion requirements template -concept ConvertibleTo = requires(T& t) { {t} -> U&; }; +concept ConvertibleTo = requires(T& t) { {t} -> U&; }; // { dg-error "inaccessible" } struct B { }; class D : /*private*/ B { }; void driver_2() { - static_assert(ConvertibleTo()); // { dg-error "cannot be used as a function" } + static_assert(ConvertibleTo()); // { dg-error "cannot call" } static_assert(ConvertibleTo); // { dg-error "static assertion failed" } } -- 2.30.2