From 873b760ae0f4a07efa73973b729bf0f20aa9661b Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Fri, 9 Aug 2024 12:43:44 -0700 Subject: [PATCH] Allow function parameter default arguments Closes #1189 I had been experimenting with not allowing default arguments for function parameters, in part because of the potential for creating order-dependent code; the way to find out whether they're necessary is to not support them and see if that leaves a usability hole. The result of the experiment is that it does leave a hole: There's persistent feedback that default arguments are often useful, and are actually necessary for a few cases including particularly `std::source_location` parameters. As for order independence, there are already ways to opt into creating potentially order-dependent code (such as by deduced return types which depend on function bodies). So I think it's time to enable default arguments, and Cpp2 is still order-independent by default. This example now works: my_function_name: ( fn: *const char = std::source_location::current().function_name() ) = { std::cout << "calling: (fn)$\n"; } main: (args) = { my_function_name(); } // On MSVC 2022, prints: // calling: int __cdecl main(const int,char **) // On GCC 14, prints: // calling: int main(int, char**) --- regression-tests/pure2-default-arguments.cpp2 | 19 ++++++ .../pure2-default-arguments.cpp.output | 4 ++ .../pure2-default-arguments.cpp.output | 66 +++++++++++++++++++ .../pure2-default-arguments.cpp.execution | 2 + .../pure2-default-arguments.cpp.execution | 2 + .../pure2-default-arguments.cpp.output | 1 + .../test-results/pure2-default-arguments.cpp | 53 +++++++++++++++ .../pure2-default-arguments.cpp2.output | 2 + regression-tests/test-results/version | 2 +- source/build.info | 2 +- source/parse.h | 21 +++--- source/to_cpp1.h | 14 +++- 12 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 regression-tests/pure2-default-arguments.cpp2 create mode 100644 regression-tests/test-results/clang-12-c++20/pure2-default-arguments.cpp.output create mode 100644 regression-tests/test-results/gcc-10-c++20/pure2-default-arguments.cpp.output create mode 100644 regression-tests/test-results/gcc-14-c++2b/pure2-default-arguments.cpp.execution create mode 100644 regression-tests/test-results/msvc-2022-c++latest/pure2-default-arguments.cpp.execution create mode 100644 regression-tests/test-results/msvc-2022-c++latest/pure2-default-arguments.cpp.output create mode 100644 regression-tests/test-results/pure2-default-arguments.cpp create mode 100644 regression-tests/test-results/pure2-default-arguments.cpp2.output diff --git a/regression-tests/pure2-default-arguments.cpp2 b/regression-tests/pure2-default-arguments.cpp2 new file mode 100644 index 000000000..7b4419486 --- /dev/null +++ b/regression-tests/pure2-default-arguments.cpp2 @@ -0,0 +1,19 @@ + +// Note: Using source_location requires GCC 11 or higher, +// Clang 16 or higher, MSVC 2019 16.10 or higher. +// Older compilers will emit failures for this test case. +my_function_name: ( + fn: *const char = std::source_location::current().function_name() + ) += { + std::cout << "calling: (fn)$\n"; +} + +f: (x: i32 = 0) = { std::cout << x; } + +main: (args) = { + my_function_name(); + f(); + f(1); + f(2); +} diff --git a/regression-tests/test-results/clang-12-c++20/pure2-default-arguments.cpp.output b/regression-tests/test-results/clang-12-c++20/pure2-default-arguments.cpp.output new file mode 100644 index 000000000..4812dc94c --- /dev/null +++ b/regression-tests/test-results/clang-12-c++20/pure2-default-arguments.cpp.output @@ -0,0 +1,4 @@ +pure2-default-arguments.cpp2:6:61: error: no member named 'source_location' in namespace 'std' + char const* fn = CPP2_UFCS_NONLOCAL(function_name)(std::source_location::current()) + ~~~~~^ +1 error generated. diff --git a/regression-tests/test-results/gcc-10-c++20/pure2-default-arguments.cpp.output b/regression-tests/test-results/gcc-10-c++20/pure2-default-arguments.cpp.output new file mode 100644 index 000000000..78fc177ba --- /dev/null +++ b/regression-tests/test-results/gcc-10-c++20/pure2-default-arguments.cpp.output @@ -0,0 +1,66 @@ +In file included from pure2-default-arguments.cpp:7: +../../../include/cpp2util.h:2086:28: error: local variable ‘obj’ may not appear in this context + 2086 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as<17>(x)), T >) { if (x.index() == 17) return operator_as<17>(x); } + | ^~~ +../../../include/cpp2util.h:2047:34: note: in definition of macro ‘CPP2_UFCS_IDENTITY’ + 2047 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return operator_as<3>(x); } + | ^~~~~~~~~~~ +../../../include/cpp2util.h:2086:15: note: in expansion of macro ‘CPP2_FORWARD’ + 2086 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as<17>(x)), T >) { if (x.index() == 17) return operator_as<17>(x); } + | ^~~~~~~~~~~~ +../../../include/cpp2util.h:2107:22: note: in expansion of macro ‘CPP2_UFCS_CONSTRAINT_ARG’ + 2107 | { return !x.has_value(); } + | ^~~~~~~~~ +../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ + 2137 | { return std::any_cast( x ); } + | ^ +pure2-default-arguments.cpp2:6:22: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ +../../../include/cpp2util.h:2086:92: error: local variable ‘params’ may not appear in this context + 2086 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as<17>(x)), T >) { if (x.index() == 17) return operator_as<17>(x); } + | ^~~~~~ +../../../include/cpp2util.h:2047:34: note: in definition of macro ‘CPP2_UFCS_IDENTITY’ + 2047 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return operator_as<3>(x); } + | ^~~~~~~~~~~ +../../../include/cpp2util.h:2086:79: note: in expansion of macro ‘CPP2_FORWARD’ + 2086 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as<17>(x)), T >) { if (x.index() == 17) return operator_as<17>(x); } + | ^~~~~~~~~~~~ +../../../include/cpp2util.h:2107:22: note: in expansion of macro ‘CPP2_UFCS_CONSTRAINT_ARG’ + 2107 | { return !x.has_value(); } + | ^~~~~~~~~ +../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ + 2137 | { return std::any_cast( x ); } + | ^ +pure2-default-arguments.cpp2:6:22: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ +../../../include/cpp2util.h:2087:74: error: local variable ‘obj’ may not appear in this context + 2087 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as<18>(x)), T >) { if (x.index() == 18) return operator_as<18>(x); } + | ^~~ +../../../include/cpp2util.h:2047:34: note: in definition of macro ‘CPP2_UFCS_IDENTITY’ + 2047 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return operator_as<3>(x); } + | ^~~~~~~~~~~ +../../../include/cpp2util.h:2087:61: note: in expansion of macro ‘CPP2_FORWARD’ + 2087 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as<18>(x)), T >) { if (x.index() == 18) return operator_as<18>(x); } + | ^~~~~~~~~~~~ +../../../include/cpp2util.h:2107:22: note: in expansion of macro ‘CPP2_UFCS_CONSTRAINT_ARG’ + 2107 | { return !x.has_value(); } + | ^~~~~~~~~ +../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ + 2137 | { return std::any_cast( x ); } + | ^ +pure2-default-arguments.cpp2:6:22: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ +../../../include/cpp2util.h:2087:93: error: local variable ‘params’ may not appear in this context + 2087 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as<18>(x)), T >) { if (x.index() == 18) return operator_as<18>(x); } + | ^~~~~~ +../../../include/cpp2util.h:2047:34: note: in definition of macro ‘CPP2_UFCS_IDENTITY’ + 2047 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return operator_as<3>(x); } + | ^~~~~~~~~~~ +../../../include/cpp2util.h:2087:80: note: in expansion of macro ‘CPP2_FORWARD’ + 2087 | if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as<18>(x)), T >) { if (x.index() == 18) return operator_as<18>(x); } + | ^~~~~~~~~~~~ +../../../include/cpp2util.h:2107:22: note: in expansion of macro ‘CPP2_UFCS_CONSTRAINT_ARG’ + 2107 | { return !x.has_value(); } + | ^~~~~~~~~ +../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ + 2137 | { return std::any_cast( x ); } + | ^ +pure2-default-arguments.cpp2:6:22: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ +pure2-default-arguments.cpp2:6:61: error: ‘std::source_location’ has not been declared diff --git a/regression-tests/test-results/gcc-14-c++2b/pure2-default-arguments.cpp.execution b/regression-tests/test-results/gcc-14-c++2b/pure2-default-arguments.cpp.execution new file mode 100644 index 000000000..0e56963ff --- /dev/null +++ b/regression-tests/test-results/gcc-14-c++2b/pure2-default-arguments.cpp.execution @@ -0,0 +1,2 @@ +calling: int main(int, char**) +012 \ No newline at end of file diff --git a/regression-tests/test-results/msvc-2022-c++latest/pure2-default-arguments.cpp.execution b/regression-tests/test-results/msvc-2022-c++latest/pure2-default-arguments.cpp.execution new file mode 100644 index 000000000..8f0b4095c --- /dev/null +++ b/regression-tests/test-results/msvc-2022-c++latest/pure2-default-arguments.cpp.execution @@ -0,0 +1,2 @@ +calling: int __cdecl main(const int,char **) +012 \ No newline at end of file diff --git a/regression-tests/test-results/msvc-2022-c++latest/pure2-default-arguments.cpp.output b/regression-tests/test-results/msvc-2022-c++latest/pure2-default-arguments.cpp.output new file mode 100644 index 000000000..f1128b42d --- /dev/null +++ b/regression-tests/test-results/msvc-2022-c++latest/pure2-default-arguments.cpp.output @@ -0,0 +1 @@ +pure2-default-arguments.cpp diff --git a/regression-tests/test-results/pure2-default-arguments.cpp b/regression-tests/test-results/pure2-default-arguments.cpp new file mode 100644 index 000000000..d8d417a0a --- /dev/null +++ b/regression-tests/test-results/pure2-default-arguments.cpp @@ -0,0 +1,53 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-default-arguments.cpp2" + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-default-arguments.cpp2" + +// Note: Using source_location requires GCC 11 or higher, +// Clang 16 or higher, MSVC 2019 16.10 or higher. +// Older compilers will emit failures for this test case. +#line 5 "pure2-default-arguments.cpp2" +auto my_function_name( + char const* fn = CPP2_UFCS_NONLOCAL(function_name)(std::source_location::current()) + ) -> void; + +#line 12 "pure2-default-arguments.cpp2" +auto f(cpp2::impl::in x = 0) -> void; + +auto main(int const argc_, char** argv_) -> int; + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-default-arguments.cpp2" + +#line 5 "pure2-default-arguments.cpp2" +auto my_function_name( + char const* fn + ) -> void +{ + std::cout << "calling: " + cpp2::to_string(fn) + "\n"; +} + +#line 12 "pure2-default-arguments.cpp2" +auto f(cpp2::impl::in x) -> void{std::cout << x; } + +#line 14 "pure2-default-arguments.cpp2" +auto main(int const argc_, char** argv_) -> int{ + auto const args = cpp2::make_args(argc_, argv_); +#line 15 "pure2-default-arguments.cpp2" + my_function_name(); + f(); + f(1); + f(2); +} + diff --git a/regression-tests/test-results/pure2-default-arguments.cpp2.output b/regression-tests/test-results/pure2-default-arguments.cpp2.output new file mode 100644 index 000000000..905239d13 --- /dev/null +++ b/regression-tests/test-results/pure2-default-arguments.cpp2.output @@ -0,0 +1,2 @@ +pure2-default-arguments.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/regression-tests/test-results/version b/regression-tests/test-results/version index 8c82ed8e3..ba18fe003 100644 --- a/regression-tests/test-results/version +++ b/regression-tests/test-results/version @@ -1,5 +1,5 @@ -cppfront compiler v0.7.2 Build 9804:1033 +cppfront compiler v0.7.2 Build 9809:1046 Copyright(c) Herb Sutter All rights reserved SPDX-License-Identifier: CC-BY-NC-ND-4.0 diff --git a/source/build.info b/source/build.info index f7c98cb74..ce3054eaa 100644 --- a/source/build.info +++ b/source/build.info @@ -1 +1 @@ -"9804:1033" \ No newline at end of file +"9809:1046" \ No newline at end of file diff --git a/source/parse.h b/source/parse.h index ea07039e3..282067125 100644 --- a/source/parse.h +++ b/source/parse.h @@ -2857,8 +2857,9 @@ struct declaration_node bool member_function_generation = true; // Cache some context - bool is_a_template_parameter = false; - bool is_a_parameter = false; + bool is_a_template_parameter = false; + bool is_a_parameter = false; + bool is_a_statement_parameter = false; // Constructor // @@ -2885,6 +2886,12 @@ struct declaration_node return is_a_parameter; } + auto is_statement_parameter() const + -> bool + { + return is_a_statement_parameter; + } + auto type_member_mark_for_removal() -> bool { @@ -8041,6 +8048,7 @@ class parser pos = start_pos; // backtrack return {}; } + n->declaration->is_a_statement_parameter = is_statement; // And some error checks // @@ -8107,15 +8115,6 @@ class parser return {}; } - if ( - !is_returns - && !is_statement - && n->declaration->initializer - ) - { - error("Cpp2 is currently exploring the path of not allowing default arguments - use overloading instead", false); - return {}; - } if (is_named && is_returns) { auto tok = n->name(); assert(tok); diff --git a/source/to_cpp1.h b/source/to_cpp1.h index 880d8cb52..bc66cd5d7 100644 --- a/source/to_cpp1.h +++ b/source/to_cpp1.h @@ -3291,14 +3291,18 @@ class cppfront ufcs_string += "_TEMPLATE"; } - // If we're in an object declaration (i.e., initializer) - // at namespace scope, use the _NONLOCAL version + // If we're in a namespace-scope object declaration (i.e., initializer) + // or in a default function argument, use the _NONLOCAL version // // Note: If there are other cases where code could execute // in a non-local scope where a capture-default for the UFCS // lambda would not be allowed, then add them here if ( current_declarations.back()->is_namespace() + || ( + current_declarations.back()->is_parameter() + && !current_declarations.back()->is_statement_parameter() + ) || ( current_declarations.back()->is_object() && current_declarations.back()->parent_is_namespace() @@ -4680,6 +4684,10 @@ class cppfront if ( !is_returns && n.declaration->initializer + && ( + is_statement + || printer.get_phase() != printer.phase2_func_defs + ) ) { auto guard = stack_element(current_declarations, &*n.declaration); @@ -4689,7 +4697,7 @@ class cppfront else { printer.print_cpp2( " = ", n.declaration->initializer->position() ); } - emit(*n.declaration->initializer, !is_statement); + emit(*n.declaration->initializer, false); if (is_statement) { printer.print_cpp2( "};", n.declaration->initializer->position() ); }