From 99f9d4b1b6bd7f072a84a0b19f0397bfa50008cb Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Wed, 10 Aug 2016 17:58:23 -0400 Subject: [PATCH] Implement C++17 constexpr if. * cp-tree.h (IF_STMT_CONSTEXPR_P): New. * name-lookup.c (push_to_top_level, pop_from_top_level_1): Handle it. * parser.h (struct cp_parser): Add in_discarded_stmt field. * parser.c (cp_parser_selection_statement): Handle 'if constexpr'. (cp_parser_jump_statement): Avoid deducing from a discarded return. * pt.c (tsubst_expr): Only instantiate taken branch of constexpr if. * semantics.c (begin_if_stmt): Set the binding level this_entity. (finish_if_stmt_cond): Require the condition of a constexpr if to be constant. * decl.c (level_for_constexpr_if): New. (named_label_entry): Add in_constexpr_if field. (poplevel_named_label_1): Set it. (check_goto): Check it. (check_previous_goto_1): Check level_for_constexpr_if. From-SVN: r239338 --- gcc/cp/ChangeLog | 18 +++++++ gcc/cp/cp-tree.h | 4 +- gcc/cp/decl.c | 36 ++++++++++++- gcc/cp/parser.c | 59 ++++++++++++++++++++-- gcc/cp/parser.h | 4 ++ gcc/cp/pt.c | 12 +++-- gcc/cp/semantics.c | 11 +++- gcc/testsuite/g++.dg/cpp1z/constexpr-if1.C | 14 +++++ gcc/testsuite/g++.dg/cpp1z/constexpr-if2.C | 15 ++++++ gcc/testsuite/g++.dg/cpp1z/constexpr-if3.C | 13 +++++ gcc/testsuite/g++.dg/cpp1z/constexpr-if4.C | 11 ++++ gcc/testsuite/g++.dg/cpp1z/constexpr-if5.C | 14 +++++ gcc/testsuite/g++.dg/cpp1z/constexpr-if6.C | 14 +++++ gcc/testsuite/g++.dg/cpp1z/constexpr-if7.C | 14 +++++ gcc/testsuite/g++.dg/cpp1z/constexpr-if8.C | 14 +++++ gcc/testsuite/g++.dg/cpp1z/constexpr-if9.C | 11 ++++ 16 files changed, 253 insertions(+), 11 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if1.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if2.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if3.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if4.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if5.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if6.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if7.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if8.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if9.C diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index ca04d6438a4..647b3e65800 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,21 @@ +2016-08-10 Jason Merrill + + Implement C++17 constexpr if. + * cp-tree.h (IF_STMT_CONSTEXPR_P): New. + * name-lookup.c (push_to_top_level, pop_from_top_level_1): Handle it. + * parser.h (struct cp_parser): Add in_discarded_stmt field. + * parser.c (cp_parser_selection_statement): Handle 'if constexpr'. + (cp_parser_jump_statement): Avoid deducing from a discarded return. + * pt.c (tsubst_expr): Only instantiate taken branch of constexpr if. + * semantics.c (begin_if_stmt): Set the binding level this_entity. + (finish_if_stmt_cond): Require the condition of a + constexpr if to be constant. + * decl.c (level_for_constexpr_if): New. + (named_label_entry): Add in_constexpr_if field. + (poplevel_named_label_1): Set it. + (check_goto): Check it. + (check_previous_goto_1): Check level_for_constexpr_if. + 2016-08-09 Jason Merrill PR c++/68703 diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index f98b1c4d503..8a32f179ecd 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -145,6 +145,7 @@ operator == (const cp_expr &lhs, tree rhs) WILDCARD_PACK_P (in WILDCARD_DECL) BLOCK_OUTER_CURLY_BRACE_P (in BLOCK) FOLD_EXPR_MODOP_P (*_FOLD_EXPR) + IF_STMT_CONSTEXPR_P (IF_STMT) 1: IDENTIFIER_VIRTUAL_P (in IDENTIFIER_NODE) TI_PENDING_TEMPLATE_FLAG. TEMPLATE_PARMS_FOR_INLINE. @@ -4530,6 +4531,7 @@ more_aggr_init_expr_args_p (const aggr_init_expr_arg_iterator *iter) #define THEN_CLAUSE(NODE) TREE_OPERAND (IF_STMT_CHECK (NODE), 1) #define ELSE_CLAUSE(NODE) TREE_OPERAND (IF_STMT_CHECK (NODE), 2) #define IF_SCOPE(NODE) TREE_OPERAND (IF_STMT_CHECK (NODE), 3) +#define IF_STMT_CONSTEXPR_P(NODE) TREE_LANG_FLAG_0 (IF_STMT_CHECK (NODE)) /* WHILE_STMT accessors. These give access to the condition of the while statement and the body of the while statement, respectively. */ @@ -6303,7 +6305,7 @@ extern void add_decl_expr (tree); extern tree maybe_cleanup_point_expr_void (tree); extern tree finish_expr_stmt (tree); extern tree begin_if_stmt (void); -extern void finish_if_stmt_cond (tree, tree); +extern tree finish_if_stmt_cond (tree, tree); extern tree finish_then_clause (tree); extern void begin_else_clause (tree); extern void finish_else_clause (tree); diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c index 45286d0bf67..43cf3df36ba 100644 --- a/gcc/cp/decl.c +++ b/gcc/cp/decl.c @@ -218,6 +218,7 @@ struct GTY((for_user)) named_label_entry { bool in_catch_scope; bool in_omp_scope; bool in_transaction_scope; + bool in_constexpr_if; }; #define named_labels cp_function_chain->x_named_labels @@ -476,6 +477,16 @@ objc_mark_locals_volatile (void *enclosing_blk) } } +/* True if B is the level for the condition of a constexpr if. */ + +static bool +level_for_constexpr_if (cp_binding_level *b) +{ + return (b->kind == sk_cond && b->this_entity + && TREE_CODE (b->this_entity) == IF_STMT + && IF_STMT_CONSTEXPR_P (b->this_entity)); +} + /* Update data for defined and undefined labels when leaving a scope. */ int @@ -512,6 +523,10 @@ poplevel_named_label_1 (named_label_entry **slot, cp_binding_level *bl) case sk_transaction: ent->in_transaction_scope = true; break; + case sk_block: + if (level_for_constexpr_if (bl->level_chain)) + ent->in_constexpr_if = true; + break; default: break; } @@ -3047,7 +3062,7 @@ check_previous_goto_1 (tree decl, cp_binding_level* level, tree names, cp_binding_level *b; bool complained = false; int identified = 0; - bool saw_eh = false, saw_omp = false, saw_tm = false; + bool saw_eh = false, saw_omp = false, saw_tm = false, saw_cxif = false; if (exited_omp) { @@ -3132,6 +3147,20 @@ check_previous_goto_1 (tree decl, cp_binding_level* level, tree names, " enters synchronized or atomic statement"); saw_tm = true; } + if (!saw_cxif && b->kind == sk_block + && level_for_constexpr_if (b->level_chain)) + { + if (identified < 2) + { + complained = identify_goto (decl, input_location, locus, + DK_ERROR); + identified = 2; + } + if (complained) + inform (EXPR_LOCATION (b->level_chain->this_entity), + " enters constexpr if statement"); + saw_cxif = true; + } } return !identified; @@ -3200,10 +3229,11 @@ check_goto (tree decl) } if (ent->in_try_scope || ent->in_catch_scope || ent->in_transaction_scope + || ent->in_constexpr_if || ent->in_omp_scope || !vec_safe_is_empty (ent->bad_decls)) { diagnostic_t diag_kind = DK_PERMERROR; - if (ent->in_try_scope || ent->in_catch_scope + if (ent->in_try_scope || ent->in_catch_scope || ent->in_constexpr_if || ent->in_transaction_scope || ent->in_omp_scope) diag_kind = DK_ERROR; complained = identify_goto (decl, DECL_SOURCE_LOCATION (decl), @@ -3248,6 +3278,8 @@ check_goto (tree decl) inform (input_location, " enters catch block"); else if (ent->in_transaction_scope) inform (input_location, " enters synchronized or atomic statement"); + else if (ent->in_constexpr_if) + inform (input_location, " enters constexpr if statement"); } if (ent->in_omp_scope) diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 6db5e84475b..1b9359ed050 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -10899,6 +10899,18 @@ cp_parser_selection_statement (cp_parser* parser, bool *if_p, tree statement; tree condition; + bool cx = false; + if (keyword == RID_IF + && cp_lexer_next_token_is_keyword (parser->lexer, + RID_CONSTEXPR)) + { + cx = true; + cp_token *tok = cp_lexer_consume_token (parser->lexer); + if (cxx_dialect < cxx1z && !in_system_header_at (tok->location)) + pedwarn (tok->location, 0, "% only available " + "with -std=c++1z or -std=gnu++1z"); + } + /* Look for the `('. */ if (!cp_parser_require (parser, CPP_OPEN_PAREN, RT_OPEN_PAREN)) { @@ -10908,7 +10920,10 @@ cp_parser_selection_statement (cp_parser* parser, bool *if_p, /* Begin the selection-statement. */ if (keyword == RID_IF) - statement = begin_if_stmt (); + { + statement = begin_if_stmt (); + IF_STMT_CONSTEXPR_P (statement) = cx; + } else statement = begin_switch_stmt (); @@ -10925,7 +10940,7 @@ cp_parser_selection_statement (cp_parser* parser, bool *if_p, unsigned char in_statement; /* Add the condition. */ - finish_if_stmt_cond (condition, statement); + condition = finish_if_stmt_cond (condition, statement); if (warn_duplicated_cond) warn_duplicated_cond_add_or_warn (token->location, condition, @@ -10934,16 +10949,44 @@ cp_parser_selection_statement (cp_parser* parser, bool *if_p, /* Parse the then-clause. */ in_statement = parser->in_statement; parser->in_statement |= IN_IF_STMT; + + /* Outside a template, the non-selected branch of a constexpr + if is a 'discarded statement', i.e. unevaluated. */ + bool was_discarded = parser->in_discarded_stmt; + bool discard_then = (cx && !processing_template_decl + && integer_zerop (condition)); + if (discard_then) + { + parser->in_discarded_stmt = true; + ++c_inhibit_evaluation_warnings; + } + cp_parser_implicitly_scoped_statement (parser, &nested_if, guard_tinfo); + parser->in_statement = in_statement; finish_then_clause (statement); + if (discard_then) + { + THEN_CLAUSE (statement) = NULL_TREE; + parser->in_discarded_stmt = was_discarded; + --c_inhibit_evaluation_warnings; + } + /* If the next token is `else', parse the else-clause. */ if (cp_lexer_next_token_is_keyword (parser->lexer, RID_ELSE)) { + bool discard_else = (cx && !processing_template_decl + && integer_nonzerop (condition)); + if (discard_else) + { + parser->in_discarded_stmt = true; + ++c_inhibit_evaluation_warnings; + } + guard_tinfo = get_token_indent_info (cp_lexer_peek_token (parser->lexer)); /* Consume the `else' keyword. */ @@ -10993,6 +11036,13 @@ cp_parser_selection_statement (cp_parser* parser, bool *if_p, when we get back up to the parent if statement. */ if (if_p != NULL) *if_p = true; + + if (discard_else) + { + ELSE_CLAUSE (statement) = NULL_TREE; + parser->in_discarded_stmt = was_discarded; + --c_inhibit_evaluation_warnings; + } } else { @@ -11864,7 +11914,10 @@ cp_parser_jump_statement (cp_parser* parser) expression. */ expr = NULL_TREE; /* Build the return-statement. */ - statement = finish_return_stmt (expr); + if (current_function_auto_return_pattern && parser->in_discarded_stmt) + /* Don't deduce from a discarded return statement. */; + else + statement = finish_return_stmt (expr); /* Look for the final `;'. */ cp_parser_require (parser, CPP_SEMICOLON, RT_SEMICOLON); } diff --git a/gcc/cp/parser.h b/gcc/cp/parser.h index 2d3feb69157..6a52b12e813 100644 --- a/gcc/cp/parser.h +++ b/gcc/cp/parser.h @@ -336,6 +336,10 @@ struct GTY(()) cp_parser { a local class. */ bool in_function_body; + /* TRUE if we are parsing a C++17 discarded statement (the non-taken branch + of an if constexpr). */ + bool in_discarded_stmt; + /* Nonzero if we're processing a __transaction_atomic or __transaction_relaxed statement. */ unsigned char in_transaction; diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index 2638564612d..1ee5fd4b579 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -15387,12 +15387,18 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl, case IF_STMT: stmt = begin_if_stmt (); + IF_STMT_CONSTEXPR_P (stmt) = IF_STMT_CONSTEXPR_P (t); tmp = RECUR (IF_COND (t)); - finish_if_stmt_cond (tmp, stmt); - RECUR (THEN_CLAUSE (t)); + tmp = finish_if_stmt_cond (tmp, stmt); + if (IF_STMT_CONSTEXPR_P (t) && integer_zerop (tmp)) + /* Don't instantiate the THEN_CLAUSE. */; + else + RECUR (THEN_CLAUSE (t)); finish_then_clause (stmt); - if (ELSE_CLAUSE (t)) + if (IF_STMT_CONSTEXPR_P (t) && integer_nonzerop (tmp)) + /* Don't instantiate the ELSE_CLAUSE. */; + else if (ELSE_CLAUSE (t)) { begin_else_clause (stmt); RECUR (ELSE_CLAUSE (t)); diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c index bffdddbb965..a2e04f6b61d 100644 --- a/gcc/cp/semantics.c +++ b/gcc/cp/semantics.c @@ -715,6 +715,7 @@ begin_if_stmt (void) scope = do_pushlevel (sk_cond); r = build_stmt (input_location, IF_STMT, NULL_TREE, NULL_TREE, NULL_TREE, scope); + current_binding_level->this_entity = r; begin_cond (&IF_COND (r)); return r; } @@ -722,12 +723,18 @@ begin_if_stmt (void) /* Process the COND of an if-statement, which may be given by IF_STMT. */ -void +tree finish_if_stmt_cond (tree cond, tree if_stmt) { - finish_cond (&IF_COND (if_stmt), maybe_convert_cond (cond)); + cond = maybe_convert_cond (cond); + if (IF_STMT_CONSTEXPR_P (if_stmt) + && require_potential_rvalue_constant_expression (cond) + && !value_dependent_expression_p (cond)) + cond = cxx_constant_value (cond, NULL_TREE); + finish_cond (&IF_COND (if_stmt), cond); add_stmt (if_stmt); THEN_CLAUSE (if_stmt) = push_stmt_list (); + return cond; } /* Finish the then-clause of an if-statement, which may be given by diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if1.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if1.C new file mode 100644 index 00000000000..416d9ec7129 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if1.C @@ -0,0 +1,14 @@ +// Testcase from P0292R2 +// { dg-do compile { target c++11 } } +// { dg-options "" } + +template void g(T&& p, Rest&& ...rs) { + // ... handle p + if constexpr (sizeof...(rs) > 0) // { dg-warning "constexpr" "" { target c++14_down } } + g(rs...); // never instantiated with an empty argument list. +} + +int main() +{ + g(1,2,3); +} diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if2.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if2.C new file mode 100644 index 00000000000..7f3a5776c42 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if2.C @@ -0,0 +1,15 @@ +// { dg-do compile { target c++14 } } +// { dg-options "" } + +template struct Same; +template struct Same {}; + +auto f() +{ + if constexpr (sizeof(int)==3) // { dg-warning "constexpr" "" { target c++14_only } } + return 42; + else + return 42L; +} + +Same s; diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if3.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if3.C new file mode 100644 index 00000000000..1cc5780416f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if3.C @@ -0,0 +1,13 @@ +// Testcase from P0292R2 +// { dg-do link { target c++11 } } +// { dg-options "" } + +extern int x; // no definition of x required +int main() { + if constexpr (true) // { dg-warning "constexpr" "" { target c++14_down } } + return 0; + else if (x) + return x; + else + return -x; +} diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if4.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if4.C new file mode 100644 index 00000000000..612eff83b1e --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if4.C @@ -0,0 +1,11 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-w" } + +void f() +{ + goto l; // { dg-message "from here" } + if constexpr (false) // { dg-message "enters constexpr if" } + { + l:; // { dg-error "jump to label" } + } +} diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if5.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if5.C new file mode 100644 index 00000000000..69d03e3c8b8 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if5.C @@ -0,0 +1,14 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-w" } + +void f() +{ + if constexpr (false) // { dg-message "enters constexpr if" } + { + goto l; // { dg-message "from here" } + } + else + { + l:; // { dg-error "jump to label" } + } +} diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if6.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if6.C new file mode 100644 index 00000000000..87aeabc178d --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if6.C @@ -0,0 +1,14 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-w" } + +void f() +{ + if constexpr (false) + { + goto l; + l:; + } + else + { + } +} diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if7.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if7.C new file mode 100644 index 00000000000..64829ccf523 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if7.C @@ -0,0 +1,14 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-w" } + +void f() +{ + if constexpr (false) + { + l:; + goto l; + } + else + { + } +} diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if8.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if8.C new file mode 100644 index 00000000000..7efdc47070e --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if8.C @@ -0,0 +1,14 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-w" } + +void f() +{ + if constexpr (false) + { + l:; // { dg-error "jump to label" } + } + else + { + goto l; // { dg-message "from here" } + } +} diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if9.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if9.C new file mode 100644 index 00000000000..748278af8c9 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if9.C @@ -0,0 +1,11 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-w" } + +void f(int i) +{ + switch (i) + if constexpr (false) // { dg-message "enters constexpr if" } + { + case 42:; // { dg-error "jump to case label" } + } +} -- 2.30.2