Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? -- >8 -- This patch implements consteval blocks, as specified by P2996. They aren't very useful without define_aggregate, but having a reviewed implementation on trunk would be great.
consteval {} can be anywhere where a member-declaration or block-declaration can be. The expression corresponding to it is: [] -> void static consteval compound-statement () and it must be a constant expression. I've used cp_parser_lambda_expression to take care of most of the parsing. Since a consteval block can find itself in a template, we need a vehicle to carry the block for instantiation. Rather than inventing a new tree, I'm using STMT_EXPR. A consteval block can't return a value but that is checked by virtue of the lambda having a void return type. PR c++/120775 gcc/cp/ChangeLog: * class.cc (maybe_add_class_template_decl_list): Update comment. * cp-tree.h (CONSTEVAL_BLOCK_P): Define. (evaluate_consteval_block): Declare. * parser.cc (cp_parser_lambda_expression): New parameter for consteval blocks. Use it. (cp_parser_lambda_declarator_opt): Likewise. (cp_parser_consteval_block): New. (cp_parser_block_declaration): Handle consteval blocks. (cp_parser_member_declaration): Likewise. * pt.cc (instantiate_class_template): tsubst_expr consteval blocks. (evaluate_consteval_block): New. (tsubst_expr) <case STMT_EXPR>: Evaluate consteval blocks. gcc/testsuite/ChangeLog: * g++.dg/cpp26/consteval-block1.C: New test. * g++.dg/cpp26/consteval-block2.C: New test. * g++.dg/cpp26/consteval-block3.C: New test. * g++.dg/cpp26/consteval-block4.C: New test. * g++.dg/cpp26/consteval-block5.C: New test. * g++.dg/cpp26/consteval-block6.C: New test. * g++.dg/cpp26/consteval-block7.C: New test. --- gcc/cp/class.cc | 3 +- gcc/cp/cp-tree.h | 6 + gcc/cp/parser.cc | 124 ++++++++++++++---- gcc/cp/pt.cc | 31 +++++ gcc/testsuite/g++.dg/cpp26/consteval-block1.C | 92 +++++++++++++ gcc/testsuite/g++.dg/cpp26/consteval-block2.C | 49 +++++++ gcc/testsuite/g++.dg/cpp26/consteval-block3.C | 41 ++++++ gcc/testsuite/g++.dg/cpp26/consteval-block4.C | 41 ++++++ gcc/testsuite/g++.dg/cpp26/consteval-block5.C | 70 ++++++++++ gcc/testsuite/g++.dg/cpp26/consteval-block6.C | 108 +++++++++++++++ gcc/testsuite/g++.dg/cpp26/consteval-block7.C | 12 ++ 11 files changed, 553 insertions(+), 24 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp26/consteval-block1.C create mode 100644 gcc/testsuite/g++.dg/cpp26/consteval-block2.C create mode 100644 gcc/testsuite/g++.dg/cpp26/consteval-block3.C create mode 100644 gcc/testsuite/g++.dg/cpp26/consteval-block4.C create mode 100644 gcc/testsuite/g++.dg/cpp26/consteval-block5.C create mode 100644 gcc/testsuite/g++.dg/cpp26/consteval-block6.C create mode 100644 gcc/testsuite/g++.dg/cpp26/consteval-block7.C diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc index f5d20e5c539..63207b6ba8a 100644 --- a/gcc/cp/class.cc +++ b/gcc/cp/class.cc @@ -3403,7 +3403,8 @@ finish_struct_anon (tree t) a typedef (TYPE_DECL) or a member class template (TEMPLATE_DECL) When FRIEND_P is nonzero, T is either a friend class (RECORD_TYPE, TEMPLATE_DECL) or a friend function - (FUNCTION_DECL, TEMPLATE_DECL). */ + (FUNCTION_DECL, TEMPLATE_DECL). This function also adds other + entities, such as STATIC_ASSERT or STMT_EXPR. */ void maybe_add_class_template_decl_list (tree type, tree t, int friend_p) diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 68102501569..52629d1aeb5 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -474,6 +474,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (in ATOMIC_CONSTR) STATIC_INIT_DECOMP_BASE_P (in the TREE_LIST for {static,tls}_aggregates) MUST_NOT_THROW_THROW_P (in MUST_NOT_THROW_EXPR) + CONSTEVAL_BLOCK_P (in STMT_EXPR) 2: IDENTIFIER_KIND_BIT_2 (in IDENTIFIER_NODE) ICS_THIS_FLAG (in _CONV) DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL) @@ -767,6 +768,10 @@ typedef struct ptrmem_cst * ptrmem_cst_t; #define STMT_EXPR_NO_SCOPE(NODE) \ TREE_LANG_FLAG_0 (STMT_EXPR_CHECK (NODE)) +/* True if this statement-expression represents a C++26 consteval block. */ +#define CONSTEVAL_BLOCK_P(NODE) \ + TREE_LANG_FLAG_1 (STMT_EXPR_CHECK (NODE)) + #define COND_EXPR_IS_VEC_DELETE(NODE) \ TREE_LANG_FLAG_0 (COND_EXPR_CHECK (NODE)) @@ -7962,6 +7967,7 @@ extern tree add_to_template_args (tree, tree); extern tree add_outermost_template_args (tree, tree); extern tree add_extra_args (tree, tree, tsubst_flags_t, tree); extern tree build_extra_args (tree, tree, tsubst_flags_t); +extern void evaluate_consteval_block (tree); /* in rtti.cc */ /* A vector of all tinfo decls that haven't been emitted yet. */ diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 0d9ed2ea82b..569ed939093 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -2576,11 +2576,11 @@ static cp_expr cp_parser_constant_expression static cp_expr cp_parser_builtin_offsetof (cp_parser *); static cp_expr cp_parser_lambda_expression - (cp_parser *); + (cp_parser *, bool = false); static void cp_parser_lambda_introducer (cp_parser *, tree); static bool cp_parser_lambda_declarator_opt - (cp_parser *, tree); + (cp_parser *, tree, bool = false); static void cp_parser_lambda_body (cp_parser *, tree); @@ -11739,10 +11739,14 @@ cp_parser_trait (cp_parser* parser, const cp_trait* trait) lambda-introducer < template-parameter-list > requires-clause [opt] lambda-declarator [opt] compound-statement + If CONSTEVAL_BLOCK_P is true, we are parsing a consteval block, which + is syntactic sugar for a consteval lambda. + Returns a representation of the expression. */ static cp_expr -cp_parser_lambda_expression (cp_parser* parser) +cp_parser_lambda_expression (cp_parser* parser, + bool consteval_block_p/*=false*/) { tree lambda_expr = build_lambda_expr (); tree type; @@ -11794,9 +11798,12 @@ cp_parser_lambda_expression (cp_parser* parser) it now. */ push_deferring_access_checks (dk_no_deferred); - cp_parser_lambda_introducer (parser, lambda_expr); - if (cp_parser_error_occurred (parser)) - return error_mark_node; + if (!consteval_block_p) + { + cp_parser_lambda_introducer (parser, lambda_expr); + if (cp_parser_error_occurred (parser)) + return error_mark_node; + } { /* OK, this is a bit tricksy. cp_parser_requires_expression sets @@ -11868,7 +11875,8 @@ cp_parser_lambda_expression (cp_parser* parser) if (cp_parser_start_tentative_firewall (parser)) start = token; - ok &= cp_parser_lambda_declarator_opt (parser, lambda_expr); + ok &= cp_parser_lambda_declarator_opt (parser, lambda_expr, + consteval_block_p); if (ok && cp_parser_error_occurred (parser)) ok = false; @@ -12251,10 +12259,13 @@ cp_parser_lambda_introducer (cp_parser* parser, tree lambda_expr) decl-specifier-seq [opt] noexcept-specifier [opt] attribute-specifier-seq [opt] trailing-return-type [opt] - LAMBDA_EXPR is the current representation of the lambda expression. */ + LAMBDA_EXPR is the current representation of the lambda expression. + If CONSTEVAL_BLOCK_P is true, we are parsing a consteval block, which + is syntactic sugar for a consteval lambda. */ static bool -cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr) +cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr, + bool consteval_block_p/*=false*/) { /* 5.1.1.4 of the standard says: If a lambda-expression does not include a lambda-declarator, it is as if @@ -12357,6 +12368,18 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr) CP_PARSER_FLAGS_ONLY_MUTABLE_OR_CONSTEXPR, &lambda_specs, &declares_class_or_enum); + /* [dcl.pre] For a consteval-block-declaration D, the expression E + corresponding to D is: + [] -> void static consteval compound-statement () + Make it so. */ + if (consteval_block_p) + { + return_type = void_type_node; + lambda_specs.storage_class = sc_static; + set_and_check_decl_spec_loc (&lambda_specs, ds_consteval, + cp_lexer_peek_token (parser->lexer)); + } + if (omitted_parms_loc && lambda_specs.any_specifiers_p) { pedwarn (omitted_parms_loc, OPT_Wc__23_extensions, @@ -16294,6 +16317,46 @@ cp_parser_toplevel_declaration (cp_parser* parser) cp_parser_declaration (parser, NULL_TREE); } +/* Parse a consteval-block-declaration. + + consteval-block-declaration: + consteval compound-statement + + If MEMBER_P, this consteval block is a member declaration. */ + +static void +cp_parser_consteval_block (cp_parser *parser, bool member_p) +{ + /* Consume the 'consteval'. */ + cp_lexer_consume_token (parser->lexer); + + /* We know the next token is '{'. Let cp_parser_lambda_body handle it. */ + cp_expr lam = cp_parser_lambda_expression (parser, + /*consteval_block_p=*/true); + if (!cp_parser_error_occurred (parser)) + { + releasing_vec args; + tree call = finish_call_expr (lam, &args, + /*disallow_virtual=*/false, + /*koenig_p=*/false, + tf_warning_or_error); + if (!instantiation_dependent_expression_p (call)) + evaluate_consteval_block (call); + else + { + tree expr = begin_stmt_expr (); + expr = finish_stmt_expr_expr (call, expr); + expr = finish_stmt_expr (expr, false); + CONSTEVAL_BLOCK_P (expr) = true; + if (member_p) + maybe_add_class_template_decl_list (current_class_type, expr, + /*friend_p=*/0); + else + add_stmt (expr); + } + } +} + /* Parse a block-declaration. block-declaration: @@ -16301,18 +16364,18 @@ cp_parser_toplevel_declaration (cp_parser* parser) asm-definition namespace-alias-definition using-declaration + using-enum-declaration using-directive + static_assert-declaration + consteval-block-declaration + alias-declaration + opaque-enum-declaration GNU Extension: block-declaration: __extension__ block-declaration - C++0x Extension: - - block-declaration: - static_assert-declaration - If STATEMENT_P is TRUE, then this block-declaration is occurring as part of a declaration-statement. */ @@ -16389,6 +16452,10 @@ cp_parser_block_declaration (cp_parser *parser, /* If the next token is `static_assert' we have a static assertion. */ else if (token1->keyword == RID_STATIC_ASSERT) cp_parser_static_assert (parser, /*member_p=*/false); + else if (cxx_dialect >= cxx26 + && token1->keyword == RID_CONSTEVAL + && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_BRACE)) + cp_parser_consteval_block (parser, /*member_p=*/false); else { size_t attr_idx = cp_parser_skip_std_attribute_spec_seq (parser, 1); @@ -28824,12 +28891,20 @@ cp_parser_member_specification_opt (cp_parser* parser) /* Parse a member-declaration. member-declaration: - decl-specifier-seq [opt] member-declarator-list [opt] ; - function-definition ; [opt] - :: [opt] nested-name-specifier template [opt] unqualified-id ; + attribute-specifier-seq [opt] decl-specifier-seq [opt] + member-declarator-list [opt] ; + function-definition + friend-type-declaration using-declaration + using-enum-declaration + static_assert-declaration + consteval-block-declaration template-declaration + explicit-specialization + deduction-guide alias-declaration + opaque-enum-declaration + empty-declaration member-declarator-list: member-declarator @@ -28848,12 +28923,7 @@ cp_parser_member_specification_opt (cp_parser* parser) member-declarator: declarator attributes [opt] pure-specifier [opt] declarator attributes [opt] constant-initializer [opt] - identifier [opt] attributes [opt] : constant-expression - - C++0x Extensions: - - member-declaration: - static_assert-declaration */ + identifier [opt] attributes [opt] : constant-expression */ static void cp_parser_member_declaration (cp_parser* parser) @@ -28952,6 +29022,14 @@ cp_parser_member_declaration (cp_parser* parser) return; } + if (cxx_dialect >= cxx26 + && cp_lexer_next_token_is_keyword (parser->lexer, RID_CONSTEVAL) + && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_BRACE)) + { + cp_parser_consteval_block (parser, /*member_p=*/true); + return; + } + parser->colon_corrects_to_scope_p = false; cp_omp_declare_simd_data odsd; diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index ca8d61dbca1..4baf9a7011b 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -12864,6 +12864,10 @@ instantiate_class_template (tree type) /* Build new TYPE_FIELDS. */ if (TREE_CODE (t) == STATIC_ASSERT) tsubst_stmt (t, args, tf_warning_or_error, NULL_TREE); + /* Consteval block. */ + else if (TREE_CODE (t) == STMT_EXPR + && CONSTEVAL_BLOCK_P (t)) + tsubst_expr (t, args, tf_warning_or_error, NULL_TREE); else if (TREE_CODE (t) != CONST_DECL) { tree r; @@ -20695,6 +20699,26 @@ tsubst_call_args (tree t, tree args, tsubst_flags_t complain, } } +/* Evaluate BLOCK, which is a C++26 consteval block. */ + +void +evaluate_consteval_block (tree block) +{ + if (TREE_CODE (block) == CLEANUP_POINT_EXPR) + block = TREE_OPERAND (block, 0); + if (TREE_CODE (block) == EXPR_STMT) + block = TREE_OPERAND (block, 0); + /* We have no object to initialize here and finish_call_expr will + return something like + (void) TARGET_EXPR <D.2675, {}>, <lambda()>::operator() () + which cxx_eval_outermost_constant_expr would not evaluate. So + extract the operator() which will get evaluated since it's an + immediate function. */ + if (TREE_CODE (block) == COMPOUND_EXPR) + block = TREE_OPERAND (block, 1); + cxx_constant_value (block); +} + /* Like tsubst but deals with expressions and performs semantic analysis. */ @@ -22522,6 +22546,13 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl) if (empty_expr_stmt_p (stmt_expr)) stmt_expr = void_node; + if (CONSTEVAL_BLOCK_P (t)) + { + evaluate_consteval_block (stmt_expr); + /* We are done with this block. */ + stmt_expr = void_node; + } + RETURN (stmt_expr); } diff --git a/gcc/testsuite/g++.dg/cpp26/consteval-block1.C b/gcc/testsuite/g++.dg/cpp26/consteval-block1.C new file mode 100644 index 00000000000..c9461e40de7 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/consteval-block1.C @@ -0,0 +1,92 @@ +// { dg-do compile { target c++26 } } +// Test consteval blocks, as specified by P2996. + +constexpr int fn () { return 42; } +struct M { + static consteval void foo () {} +}; + +consteval { } +consteval { fn (); } +consteval { M::foo (); } +consteval { auto x = fn (); return; } +consteval { + [](int i) { return i; }(5); +} +auto lam = [] { }; +consteval { lam (); } + +struct S { + consteval { } +}; + +struct S2 { + consteval { fn(); } +}; + +class C { + consteval { } +}; + +class C2 { + consteval { M::foo (); } +}; + +union U { + consteval { } +}; + +template<typename> +struct TS { + consteval { } +}; + +template<typename... Ts> +struct TS2 { + consteval { + (Ts::foo (), ...); + } +}; + +TS2<M> ts2; + +void +g () +{ + consteval { } +} + +template<typename> +void +tg () +{ + consteval { } +} + +/* __func__ won't be set. Make sure we don't crash. */ +consteval { __func__; } +struct F { + consteval { __func__; } +}; +template<typename> +struct TF { + consteval { __func__; } +}; + +void die (); +constexpr int +bar (int i) +{ + if (i != 42) + die (); + return 0; +} + +void +foo () +{ + constexpr int r = 42; + consteval { + bar (r); + } +} diff --git a/gcc/testsuite/g++.dg/cpp26/consteval-block2.C b/gcc/testsuite/g++.dg/cpp26/consteval-block2.C new file mode 100644 index 00000000000..895fcb63867 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/consteval-block2.C @@ -0,0 +1,49 @@ +// { dg-do compile { target c++26 } } +// Test consteval blocks, as specified by P2996. + +void fn (); + +consteval { fn (); } // { dg-error "call to non-.constexpr. function" } +consteval { return 42; } // { dg-error "return-statement with a value" } + +struct S { + consteval { + fn (); // { dg-error "call to non-.constexpr. function" } + } + consteval { + return 42; // { dg-error "return-statement with a value" } + } +}; + +template<typename T> +constexpr void foo (T t) { return t; } // { dg-error "return-statement with a value" } + +template<int N> +struct R { + consteval { foo (N); } +}; + +R<1> r; + +template<typename T> +constexpr void foo2 (T t) { return t; } // { dg-error "return-statement with a value" } + +template<int N> +void +f () +{ + consteval { foo2 (1); } +} + +constexpr int bar (int) { return 0; } + +void +g () +{ + f<1>(); + + int r = 42; + consteval { + bar (r); // { dg-error ".r. is not captured" } + } +} diff --git a/gcc/testsuite/g++.dg/cpp26/consteval-block3.C b/gcc/testsuite/g++.dg/cpp26/consteval-block3.C new file mode 100644 index 00000000000..c1221c3019f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/consteval-block3.C @@ -0,0 +1,41 @@ +// { dg-do compile { target c++26 } } +// Test consteval blocks, as specified by P2996. +// Test that we actually evaluate the consteval block. + +void bar () { } + +template<int N> +constexpr void +fn () +{ + if (N > 0) + bar (); // { dg-error "call to non-.constexpr. function" } +} + +template<int N> +struct S { + consteval { fn<N>(); } // { dg-error "called in a constant expression" } +}; + +S<1> s; + +template<int N> +constexpr void +fn2 () +{ + if (N > 0) + bar (); // { dg-error "call to non-.constexpr. function" } +} + +template<int N> +void +g () +{ + consteval { fn2<N>(); } // { dg-error "called in a constant expression" } +} + +void +f () +{ + g<1>(); +} diff --git a/gcc/testsuite/g++.dg/cpp26/consteval-block4.C b/gcc/testsuite/g++.dg/cpp26/consteval-block4.C new file mode 100644 index 00000000000..be95e170ee1 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/consteval-block4.C @@ -0,0 +1,41 @@ +// { dg-do compile { target c++26 } } +// Test consteval blocks, as specified by P2996. +// Test that we actually evaluate the consteval block. + +void bar () { } + +template<int N> +constexpr void +fn () +{ + if (N > 0) + bar (); +} + +template<int N> +struct S { + consteval { fn<N>(); } +}; + +S<0> s; + +template<int N> +constexpr void +fn2 () +{ + if (N > 0) + bar (); +} + +template<int N> +void +g () +{ + consteval { fn2<N>(); } +} + +void +f () +{ + g<0>(); +} diff --git a/gcc/testsuite/g++.dg/cpp26/consteval-block5.C b/gcc/testsuite/g++.dg/cpp26/consteval-block5.C new file mode 100644 index 00000000000..462cebe6cc9 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/consteval-block5.C @@ -0,0 +1,70 @@ +// { dg-do compile { target c++26 } } +// Test consteval blocks, as specified by P2996. + +void bar () { } + +template<int N> +constexpr void +fn () +{ + if (N > 0) + bar (); +} + +template<typename> +struct S { + consteval { fn<1>(); } +}; + +template<> +struct S<int> { + consteval { fn<0>(); } +}; + +S<int> s1; + +template<typename T> +struct S<T*> { + consteval { fn<0>(); } +}; + +S<int *> s2; + +template<typename T, int N> +struct W { + consteval { T t; fn<N - 1>(); } +}; + +template<typename T> +struct W<T, 0> { + consteval { T t; fn<0>(); } +}; + +template<> +struct W<char, 0> { + consteval { fn<0>(); } +}; + +W<int, 0> w1; +W<int, 1> w2; +W<char, 0> w3; + +template<typename> +void +f () +{ + consteval { fn<1>(); } +} + +template<> +void +f<int> () +{ + consteval { fn<0>(); } +} + +void +g () +{ + f<int> (); +} diff --git a/gcc/testsuite/g++.dg/cpp26/consteval-block6.C b/gcc/testsuite/g++.dg/cpp26/consteval-block6.C new file mode 100644 index 00000000000..ca90b3e376f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/consteval-block6.C @@ -0,0 +1,108 @@ +// { dg-do compile { target c++26 } } +// Test consteval blocks, as specified by P2996. + +void die () {} + +template<int N> +constexpr void +fn () +{ + if (N > 0) + die (); +} + +template<int N> +void +fn2 () +{ + struct S { + consteval { + fn<N>(); + } + }; +} + +template<int N> +struct A { + struct B { + consteval { + fn<N>(); + } + }; + template<int M> + struct C { + consteval { + fn<N + M>(); + } + }; +}; + +template<int N> +struct D { + constexpr static int i = 0; + struct E { + consteval { + fn<i>(); + } + }; +}; + +A<0>::B b; +A<0>::C<0> c; +D<0>::E e; + +void +f () +{ + fn2<0>(); +} + +static constexpr int j = 0; +const int x = 0; + +consteval { + fn<j>(); + consteval { + fn<j + j>(); + consteval { + fn<j + j + j>(); + consteval { + fn<j + j + x>(); + consteval { + fn<j + x>(); + } + } + } + } +} + +struct R { constexpr R() {} }; + +template<int N> +constexpr auto X = N; + +consteval { + R{}; + constexpr auto x = 0; + fn<x>(); + fn<X<0>>(); + if consteval + { + fn<j>(); + } + else + { + die (); + } +} + +template<typename T> +struct G { + consteval { + using U = T[3]; + U arr{}; + int i = arr[2]; + } +}; + +G<int> g; diff --git a/gcc/testsuite/g++.dg/cpp26/consteval-block7.C b/gcc/testsuite/g++.dg/cpp26/consteval-block7.C new file mode 100644 index 00000000000..231682f8adf --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/consteval-block7.C @@ -0,0 +1,12 @@ +// { dg-do compile { target c++26 } } +// Test consteval blocks, as specified by P2996. + +consteval { + template <class T> // { dg-error "template declaration cannot appear at block scope" } + struct X { }; + + template <class T> // { dg-error "template declaration cannot appear at block scope" } + concept C = true; + + return; // OK +} base-commit: 39ba01fe70c77e70a00ed65a1e6bac8350479f4d -- 2.50.1