diff mbox

C++ PATCH for C++17 constexpr if

Message ID CADzB+2nNudHWKqVsTjrxhHuf2gn_ZC=2gHtUtoiGy+EXAxVbWw@mail.gmail.com
State New
Headers show

Commit Message

Jason Merrill Aug. 10, 2016, 9:57 p.m. UTC
This patch implements the C++17 constexpr if feature.  The primary use
is in templates, where the non-taken branch of the constexpr
if-statement is not instantiated.  But it can also be used outside of
templates, where the branches are parsed and discarded, and discarded
return statements are not used for return type deduction.

constexpr if is also active with a pedwarn in C++11 and C++14.

Tested x86_64-pc-linux-gnu, applying to trunk.
commit 504a8c704153edc559e7108321892e7809cdcc2f
Author: Jason Merrill <jason@redhat.com>
Date:   Wed Aug 10 14:26:01 2016 -0400

    	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.
diff mbox

Patch

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index f98b1c4..8a32f17 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 45286d0..43cf3df 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 6db5e84..1b9359e 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, "%<if constexpr%> 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 2d3feb6..6a52b12 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 2638564..1ee5fd4 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 bffdddb..a2e04f6 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 0000000..416d9ec
--- /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<typename T, typename ... Rest> 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 0000000..7f3a577
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if2.C
@@ -0,0 +1,15 @@ 
+// { dg-do compile { target c++14 } }
+// { dg-options "" }
+
+template <class,class> struct Same;
+template <class T> struct Same<T,T> {};
+
+auto f()
+{
+  if constexpr (sizeof(int)==3) // { dg-warning "constexpr" "" { target c++14_only } }
+    return 42;
+  else
+    return 42L;
+}
+
+Same<decltype(f()), long> 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 0000000..1cc5780
--- /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 0000000..612eff8
--- /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 0000000..69d03e3
--- /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 0000000..87aeabc
--- /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 0000000..64829cc
--- /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 0000000..7efdc47
--- /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 0000000..748278a
--- /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" }
+      }
+}