https://gcc.gnu.org/g:2f15787f2e1a3afe2c2ad93d4eb0d3c1f73c8fbd
commit r15-105-g2f15787f2e1a3afe2c2ad93d4eb0d3c1f73c8fbd Author: Jakub Jelinek <ja...@redhat.com> Date: Thu May 2 09:34:31 2024 +0200 c++: Implement C++26 P2573R2 - = delete("should have a reason"); [PR114458] The following patch implements the C++26 P2573R2 = delete("should have a reason"); paper. I've tried to avoid increasing compile time memory for it when it isn't used (e.g. by adding a new lang_decl tree member), so the reason is represented as STRING_CST in DECL_INITIAL (which normally is for DECL_DELETED_FN error_mark_node) and to differentiate this delete("reason") initializer from some bogus attempt to initialize a function with "reason" using the RID_DELETE identifier as TREE_TYPE of the STRING_CST, as nothing needs to care about the type of the reason. If preferred it could be say TREE_LIST with the reason STRING_CST and RID_DELETE identifier or something similar instead, but that would need more compile time memory when it is used. 2024-05-02 Jakub Jelinek <ja...@redhat.com> PR c++/114458 gcc/c-family/ * c-cppbuiltin.cc (c_cpp_builtins): Predefine __cpp_deleted_function=202403L for C++26. gcc/cp/ChangeLog * parser.cc (cp_parser_pure_specifier): Implement C++26 P2573R2 - = delete("should have a reason");. Parse deleted-function-body. * decl.cc (duplicate_decls): Copy DECL_INITIAL from DECL_DELETED_FN olddecl to newdecl if it is a STRING_CST. (cp_finish_decl): Handle deleted init with a reason. * decl2.cc: Include "escaped_string.h". (grokfield): Handle deleted init with a reason. (mark_used): Emit DECL_DELETED_FN reason in the message if any. * cp-tree.h (DECL_DELETED_FN): Document representation of = delete("reason") on a DECL. gcc/testsuite/ * g++.dg/cpp26/feat-cxx26.C (__cpp_deleted_function): Add test. * g++.dg/cpp26/delete-reason1.C: New test. * g++.dg/cpp26/delete-reason2.C: New test. * g++.dg/parse/error65.C (f1): Adjust expected diagnostics. Diff: --- gcc/c-family/c-cppbuiltin.cc | 1 + gcc/cp/cp-tree.h | 5 +++- gcc/cp/decl.cc | 13 ++++++--- gcc/cp/decl2.cc | 23 +++++++++++++--- gcc/cp/parser.cc | 21 +++++++++++++++ gcc/testsuite/g++.dg/cpp26/delete-reason1.C | 41 +++++++++++++++++++++++++++++ gcc/testsuite/g++.dg/cpp26/delete-reason2.C | 20 ++++++++++++++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C | 6 +++++ gcc/testsuite/g++.dg/parse/error65.C | 3 +-- 9 files changed, 124 insertions(+), 9 deletions(-) diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc index 0a927b28836..b6f25e4db3c 100644 --- a/gcc/c-family/c-cppbuiltin.cc +++ b/gcc/c-family/c-cppbuiltin.cc @@ -1092,6 +1092,7 @@ c_cpp_builtins (cpp_reader *pfile) cpp_define (pfile, "__cpp_static_assert=202306L"); cpp_define (pfile, "__cpp_placeholder_variables=202306L"); cpp_define (pfile, "__cpp_structured_bindings=202403L"); + cpp_define (pfile, "__cpp_deleted_function=202403L"); } if (flag_concepts) { diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 5d1bd6ba493..933504b4821 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -4477,7 +4477,10 @@ get_vec_init_expr (tree t) && DECL_DECLARED_CONSTEXPR_P (NODE) \ && DECL_CLASS_SCOPE_P (NODE))) -/* Nonzero if DECL was declared with '= delete'. */ +/* Nonzero if DECL was declared with '= delete'. + = delete("reason") is represented in addition to this flag by DECL_INITIAL + being STRING_CST with the reason and TREE_TYPE of the STRING_CST the + RID_DELETE IDENTIFIER_NODE. */ #define DECL_DELETED_FN(DECL) \ (LANG_DECL_FN_CHECK (DECL)->min.base.threadprivate_or_deleted_p) diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index de0c02a39ee..378311c0f04 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -2420,6 +2420,10 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) "previous declaration of %qD", olddecl); } DECL_DELETED_FN (newdecl) |= DECL_DELETED_FN (olddecl); + if (DECL_DELETED_FN (olddecl) + && DECL_INITIAL (olddecl) + && TREE_CODE (DECL_INITIAL (olddecl)) == STRING_CST) + DECL_INITIAL (newdecl) = DECL_INITIAL (olddecl); } } @@ -8597,17 +8601,20 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p, if (init && TREE_CODE (decl) == FUNCTION_DECL) { tree clone; - if (init == ridpointers[(int)RID_DELETE]) + if (init == ridpointers[(int)RID_DELETE] + || (TREE_CODE (init) == STRING_CST + && TREE_TYPE (init) == ridpointers[(int)RID_DELETE])) { /* FIXME check this is 1st decl. */ DECL_DELETED_FN (decl) = 1; DECL_DECLARED_INLINE_P (decl) = 1; - DECL_INITIAL (decl) = error_mark_node; + DECL_INITIAL (decl) + = TREE_CODE (init) == STRING_CST ? init : error_mark_node; FOR_EACH_CLONE (clone, decl) { DECL_DELETED_FN (clone) = 1; DECL_DECLARED_INLINE_P (clone) = 1; - DECL_INITIAL (clone) = error_mark_node; + DECL_INITIAL (clone) = DECL_INITIAL (decl); } init = NULL_TREE; } diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc index 1f84878b2b9..6913efa5355 100644 --- a/gcc/cp/decl2.cc +++ b/gcc/cp/decl2.cc @@ -50,6 +50,7 @@ along with GCC; see the file COPYING3. If not see #include "asan.h" #include "optabs-query.h" #include "omp-general.h" +#include "escaped_string.h" /* Id for dumping the raw trees. */ int raw_dump_id; @@ -1038,7 +1039,10 @@ grokfield (const cp_declarator *declarator, init = NULL_TREE; int initialized; - if (init == ridpointers[(int)RID_DELETE]) + if (init == ridpointers[(int)RID_DELETE] + || (init + && TREE_CODE (init) == STRING_CST + && TREE_TYPE (init) == ridpointers[(int)RID_DELETE])) initialized = SD_DELETED; else if (init == ridpointers[(int)RID_DEFAULT]) initialized = SD_DEFAULTED; @@ -1123,10 +1127,14 @@ grokfield (const cp_declarator *declarator, { if (TREE_CODE (value) == FUNCTION_DECL) { - if (init == ridpointers[(int)RID_DELETE]) + if (init == ridpointers[(int)RID_DELETE] + || (TREE_CODE (init) == STRING_CST + && TREE_TYPE (init) == ridpointers[(int)RID_DELETE])) { DECL_DELETED_FN (value) = 1; DECL_DECLARED_INLINE_P (value) = 1; + if (TREE_CODE (init) == STRING_CST) + DECL_INITIAL (value) = init; } else if (init == ridpointers[(int)RID_DEFAULT]) { @@ -5912,7 +5920,16 @@ mark_used (tree decl, tsubst_flags_t complain /* = tf_warning_or_error */) sorry ("converting lambda that uses %<...%> to function pointer"); else if (complain & tf_error) { - error ("use of deleted function %qD", decl); + if (DECL_INITIAL (decl) + && TREE_CODE (DECL_INITIAL (decl)) == STRING_CST) + { + escaped_string msg; + msg.escape (TREE_STRING_POINTER (DECL_INITIAL (decl))); + error ("use of deleted function %qD: %s", + decl, (const char *) msg); + } + else + error ("use of deleted function %qD", decl); if (!maybe_explain_implicit_delete (decl)) inform (DECL_SOURCE_LOCATION (decl), "declared here"); } diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index a2bc6f69000..7c3cfcfcf4b 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -28634,6 +28634,27 @@ cp_parser_pure_specifier (cp_parser* parser) || token->keyword == RID_DELETE) { maybe_warn_cpp0x (CPP0X_DEFAULTED_DELETED); + if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_PAREN)) + { + if (cxx_dialect >= cxx11 && cxx_dialect < cxx26) + pedwarn (cp_lexer_peek_token (parser->lexer)->location, + OPT_Wc__26_extensions, + "%<delete%> reason only available with " + "%<-std=c++2c%> or %<-std=gnu++2c%>"); + + /* Consume the `('. */ + matching_parens parens; + parens.consume_open (parser); + tree reason = cp_parser_unevaluated_string_literal (parser); + /* Consume the `)'. */ + parens.require_close (parser); + if (TREE_CODE (reason) == STRING_CST) + { + TREE_TYPE (reason) = token->u.value; + return reason; + } + } + return token->u.value; } diff --git a/gcc/testsuite/g++.dg/cpp26/delete-reason1.C b/gcc/testsuite/g++.dg/cpp26/delete-reason1.C new file mode 100644 index 00000000000..3a097f57d07 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/delete-reason1.C @@ -0,0 +1,41 @@ +// P2573R2 = delete("should have a reason"); +// { dg-do compile { target c++11 } } +// { dg-options "" } + +void foo () = delete ("reason"); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } + // { dg-message "declared here" "" { target *-*-* } .-1 } +struct S { + void bar () = delete ("another reason"); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } +}; // { dg-message "declared here" "" { target *-*-* } .-1 } +int baz (int) = delete ("yet another reason"); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } +int baz (int); // { dg-message "declared here" } +template <typename T> +void qux (T) = delete ("some other reason"); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } + // { dg-message "declared here" "" { target *-*-* } .-1 } +template <typename T> +struct U { + U () = delete ("my reasons"); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } + U (int); // { dg-message "declared here" "" { target *-*-* } .-1 } + ~U () = delete ("your reasons"); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } +}; // { dg-message "declared here" "" { target *-*-* } .-1 } +template <> +void qux (long long) = delete; // { dg-message "declared here" } +template <typename T> +void corge (T) = delete; // { dg-message "declared here" } +template <> +void corge (double) = delete ("their reasons"); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } + // { dg-message "declared here" "" { target *-*-* } .-1 } + +void +test (U<int> &x) +{ + foo (); // { dg-error "use of deleted function 'void foo\\\(\\\)': reason" } + S{}.bar (); // { dg-error "use of deleted function 'void S::bar\\\(\\\)': another reason" } + baz (0); // { dg-error "use of deleted function 'int baz\\\(int\\\)': yet another reason" } + qux (0L); // { dg-error "use of deleted function 'void qux\\\(T\\\) \\\[with T = long int\\\]': some other reason" } + qux (0LL); // { dg-error "use of deleted function 'void qux\\\(T\\\) \\\[with T = long long int\\\]'" } + U<long> u; // { dg-error "use of deleted function 'U<T>::U\\\(\\\) \\\[with T = long int\\\]': my reasons" } + // { dg-error "use of deleted function 'U<T>::~U\\\(\\\) \\\[with T = long int\\\]': your reasons" "" { target *-*-* } .-1 } + corge (0); // { dg-error "use of deleted function 'void corge\\\(T\\\) \\\[with T = int\\\]'" } + corge (0.0); // { dg-error "use of deleted function 'void corge\\\(T\\\) \\\[with T = double\\\]': their reasons" } +} diff --git a/gcc/testsuite/g++.dg/cpp26/delete-reason2.C b/gcc/testsuite/g++.dg/cpp26/delete-reason2.C new file mode 100644 index 00000000000..3bd6e158307 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/delete-reason2.C @@ -0,0 +1,20 @@ +// P2573R2 = delete("should have a reason"); +// { dg-do compile { target c++11 } } +// { dg-options "" } + +void foo () = delete (; // { dg-warning "'delete' reason only available with" "" { target c++23_down } } + // { dg-error "expected string-literal before ';' token" "" { target *-*-* } .-1 } + // { dg-error "expected '\\\)' before ';' token" "" { target *-*-* } .-2 } +void bar () = delete (); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } + // { dg-error "expected string-literal before '\\\)' token" "" { target *-*-* } .-1 } +void baz () = delete (0); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } + // { dg-error "expected string-literal before numeric constant" "" { target *-*-* } .-1 } + // { dg-error "expected '\\\)' before numeric constant" "" { target *-*-* } .-2 } + // { dg-error "expected ',' or ';' before numeric constant" "" { target *-*-* } .-3 } +void qux () = delete (L""); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } + // { dg-error "a wide string is invalid in this context before '\\\)' token" "" { target *-*-* } .-1 } +void corge () = delete (u8""); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } + // { dg-error "a wide string is invalid in this context before '\\\)' token" "" { target *-*-* } .-1 } +void garply () = delete ("something" + 0); // { dg-warning "'delete' reason only available with" "" { target c++23_down } } + // { dg-error "expected '\\\)' before '\\\+' token" "" { target *-*-* } .-1 } + // { dg-error "expected ',' or ';' before '\\\+' token" "" { target *-*-* } .-2 } diff --git a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C index 204f8bac47d..de66dcccd02 100644 --- a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C +++ b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C @@ -609,3 +609,9 @@ #elif __cpp_placeholder_variables != 202306 # error "__cpp_placeholder_variables != 202306" #endif + +#ifndef __cpp_deleted_function +# error "__cpp_deleted_function" +#elif __cpp_deleted_function != 202403 +# error "__cpp_deleted_function != 202403" +#endif diff --git a/gcc/testsuite/g++.dg/parse/error65.C b/gcc/testsuite/g++.dg/parse/error65.C index d9e0a4bfbcb..9031c9d591f 100644 --- a/gcc/testsuite/g++.dg/parse/error65.C +++ b/gcc/testsuite/g++.dg/parse/error65.C @@ -1,8 +1,7 @@ // PR c++/111840 // { dg-do compile { target c++11 } } -// NB: =delete("reason") may be allowed via P2573. -int f1() = delete("should have a reason"); // { dg-error "expected" } +int f1() = delete("should have a reason"); // { dg-error "'delete' reason only available with" "" { target c++23_down } } int f2() = delete[""]; // { dg-error "expected" } int f3() = delete{""}; // { dg-error "expected" } int f4() = delete""; // { dg-error "expected" }