From a9a6a882c80b62cbc5819373720322a4cb4c008d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johel=20Ernesto=20Guerrero=20Pe=C3=B1a?= Date: Thu, 22 Jun 2023 22:58:23 -0400 Subject: [PATCH] feat: accept function-type after type-id --- ...2-bugfix-for-multi-token-type-prvalue.cpp2 | 1 + .../pure2-function-type-id-2-error.cpp2 | 3 + .../pure2-function-type-id-3-error.cpp2 | 3 + .../pure2-function-type-id-4-error.cpp2 | 1 + .../pure2-function-type-id-5-error.cpp2 | 3 + regression-tests/pure2-function-type-id.cpp2 | 88 ++++++++ ...for-multi-token-type-prvalue.cpp.execution | 0 ...ix-for-multi-token-type-prvalue.cpp.output | 0 .../pure2-function-type-id.cpp.execution | 0 .../gcc-13/pure2-function-type-id.cpp.output | 0 ...e2-bugfix-for-multi-token-type-prvalue.cpp | 24 +++ ...x-for-multi-token-type-prvalue.cpp2.output | 2 + ...pure2-function-type-id-2-error.cpp2.output | 3 + ...pure2-function-type-id-3-error.cpp2.output | 3 + ...pure2-function-type-id-4-error.cpp2.output | 5 + ...pure2-function-type-id-5-error.cpp2.output | 3 + .../test-results/pure2-function-type-id.cpp | 135 +++++++++++++ .../pure2-function-type-id.cpp2.output | 2 + source/cppfront.cpp | 189 +++++++++++++----- source/parse.h | 162 +++++++++++---- source/sema.h | 3 +- 21 files changed, 538 insertions(+), 92 deletions(-) create mode 100644 regression-tests/pure2-bugfix-for-multi-token-type-prvalue.cpp2 create mode 100644 regression-tests/pure2-function-type-id-2-error.cpp2 create mode 100644 regression-tests/pure2-function-type-id-3-error.cpp2 create mode 100644 regression-tests/pure2-function-type-id-4-error.cpp2 create mode 100644 regression-tests/pure2-function-type-id-5-error.cpp2 create mode 100644 regression-tests/pure2-function-type-id.cpp2 create mode 100644 regression-tests/test-results/gcc-13/pure2-bugfix-for-multi-token-type-prvalue.cpp.execution create mode 100644 regression-tests/test-results/gcc-13/pure2-bugfix-for-multi-token-type-prvalue.cpp.output create mode 100644 regression-tests/test-results/gcc-13/pure2-function-type-id.cpp.execution create mode 100644 regression-tests/test-results/gcc-13/pure2-function-type-id.cpp.output create mode 100644 regression-tests/test-results/pure2-bugfix-for-multi-token-type-prvalue.cpp create mode 100644 regression-tests/test-results/pure2-bugfix-for-multi-token-type-prvalue.cpp2.output create mode 100644 regression-tests/test-results/pure2-function-type-id-2-error.cpp2.output create mode 100644 regression-tests/test-results/pure2-function-type-id-3-error.cpp2.output create mode 100644 regression-tests/test-results/pure2-function-type-id-4-error.cpp2.output create mode 100644 regression-tests/test-results/pure2-function-type-id-5-error.cpp2.output create mode 100644 regression-tests/test-results/pure2-function-type-id.cpp create mode 100644 regression-tests/test-results/pure2-function-type-id.cpp2.output diff --git a/regression-tests/pure2-bugfix-for-multi-token-type-prvalue.cpp2 b/regression-tests/pure2-bugfix-for-multi-token-type-prvalue.cpp2 new file mode 100644 index 0000000000..9b8b170443 --- /dev/null +++ b/regression-tests/pure2-bugfix-for-multi-token-type-prvalue.cpp2 @@ -0,0 +1 @@ +main: (args) = { _ = :* int = args.argc&; } diff --git a/regression-tests/pure2-function-type-id-2-error.cpp2 b/regression-tests/pure2-function-type-id-2-error.cpp2 new file mode 100644 index 0000000000..f0193c2561 --- /dev/null +++ b/regression-tests/pure2-function-type-id-2-error.cpp2 @@ -0,0 +1,3 @@ +main: () = { + f: * (x: i32) throws = :(_) = {}; +} diff --git a/regression-tests/pure2-function-type-id-3-error.cpp2 b/regression-tests/pure2-function-type-id-3-error.cpp2 new file mode 100644 index 0000000000..c73d49800b --- /dev/null +++ b/regression-tests/pure2-function-type-id-3-error.cpp2 @@ -0,0 +1,3 @@ +main: () = { + f: * () [[pre: true]] = :() = {}; +} diff --git a/regression-tests/pure2-function-type-id-4-error.cpp2 b/regression-tests/pure2-function-type-id-4-error.cpp2 new file mode 100644 index 0000000000..db87fb328d --- /dev/null +++ b/regression-tests/pure2-function-type-id-4-error.cpp2 @@ -0,0 +1 @@ +main: () = { f: * () -> (x: i32); } diff --git a/regression-tests/pure2-function-type-id-5-error.cpp2 b/regression-tests/pure2-function-type-id-5-error.cpp2 new file mode 100644 index 0000000000..fc3870b41d --- /dev/null +++ b/regression-tests/pure2-function-type-id-5-error.cpp2 @@ -0,0 +1,3 @@ +main: () = { + f: * (_) throws = :(_) = {}; +} diff --git a/regression-tests/pure2-function-type-id.cpp2 b/regression-tests/pure2-function-type-id.cpp2 new file mode 100644 index 0000000000..8423c07105 --- /dev/null +++ b/regression-tests/pure2-function-type-id.cpp2 @@ -0,0 +1,88 @@ +main: () = { + postfix_operators(); + + // Variables with type of a mix of `*`/`const` to `() throws -> void`. + f0: * () throws = :() = {}; + f1: const * () throws = f0; + f2: * const () throws = f0; + f3: const * const () throws = f0; + + i: i32 = 0; + i0: * i32 = i&; + i1: const * i32 = i0; + i2: * const i32 = i0; + i3: const * const i32 = i0; + + // Assert consistent '*'/'const' with non-function type variables. + static_assert((std::is_const_v) == std::is_const_v); + static_assert((std::is_const_v) == std::is_const_v); + static_assert((std::is_const_v) == std::is_const_v); + + // Variables with various kinds of parameter. + f4: * (_: i32) throws = :(x: i32) = {}; + f5: * (_: std::any) throws = :(x: std::any) = {}; + f6: * (move _: i32) throws = :(move x: i32) = {}; + f7: * (out _: i32) throws = :(copy x) = {}; + + // In alternative. + [[assert: inspect f0 -> bool { + is () = (std::terminate(), false); + is () throws = (std::terminate(), false); + is * () throws = true; + is _ = false; + }]] + [[assert: inspect f0* -> bool { + is () throws = true; + is _ = false; + }]] + + // As block variable. + (f: * () throws = f0) { } + + // As local function parameter. + _ = :(f: * () throws) = {}; + _ = :(f: * () throws -> * () throws) = {}; + _ = :(f: * () throws -> * () throws -> * () throws) = {}; + + // In local function return type. + _ = :() -> * () throws = nullptr; + _ = :() -> * () throws -> * () throws = nullptr; + + // Without `throws`. + _ = :* (copy _: std::string_view, copy _: CPP2_MESSAGE_PARAM) = cpp2::report_and_terminate; + + // As template argument. + _ = :std::type_identity_t<* () throws> = f0; +} + +// As non-local function parameter. +g: (f: * () throws) = { } +g: (f: * () throws -> * () throws) = { } +// As template parameter. +g: () = { } +g: * () throws> () = { } + +// In non-local function return type. +g1: () -> * () throws = nullptr; +g2: () -> * () throws -> * () throws = nullptr; + +// clang-format off +// Test case from #343. +f2: () -> std::function<(_: std::string) throws -> std::string> = { + return :(s: std::string) -> std::string = { return s + " World!"; }; +} + +// Adapted from . + f: (x: i32) -> * (_: i32) throws -> std::string = +:(x: i32) -> std::string = ""; +postfix_operators: () = { + [[assert: f is (_: i32) throws -> * (_: i32) throws -> std::string]] + // / | | + // / | | + [[assert: f(42) is * (_: i32) throws -> std::string]] + // ________________/ | + // / | + [[assert: f(42)* is (_: i32) throws -> std::string]] + // _______________/ + // / + [[assert: (f(42)*)(1) is std::string]] +} // clang-format on diff --git a/regression-tests/test-results/gcc-13/pure2-bugfix-for-multi-token-type-prvalue.cpp.execution b/regression-tests/test-results/gcc-13/pure2-bugfix-for-multi-token-type-prvalue.cpp.execution new file mode 100644 index 0000000000..e69de29bb2 diff --git a/regression-tests/test-results/gcc-13/pure2-bugfix-for-multi-token-type-prvalue.cpp.output b/regression-tests/test-results/gcc-13/pure2-bugfix-for-multi-token-type-prvalue.cpp.output new file mode 100644 index 0000000000..e69de29bb2 diff --git a/regression-tests/test-results/gcc-13/pure2-function-type-id.cpp.execution b/regression-tests/test-results/gcc-13/pure2-function-type-id.cpp.execution new file mode 100644 index 0000000000..e69de29bb2 diff --git a/regression-tests/test-results/gcc-13/pure2-function-type-id.cpp.output b/regression-tests/test-results/gcc-13/pure2-function-type-id.cpp.output new file mode 100644 index 0000000000..e69de29bb2 diff --git a/regression-tests/test-results/pure2-bugfix-for-multi-token-type-prvalue.cpp b/regression-tests/test-results/pure2-bugfix-for-multi-token-type-prvalue.cpp new file mode 100644 index 0000000000..f5759d408e --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-multi-token-type-prvalue.cpp @@ -0,0 +1,24 @@ + +#define CPP2_USE_MODULES Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-bugfix-for-multi-token-type-prvalue.cpp2" +auto main(int const argc_, char const* const* const argv_) -> int; + + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-bugfix-for-multi-token-type-prvalue.cpp2" +auto main(int const argc_, char const* const* const argv_) -> int{ + auto args = cpp2::make_args(argc_, argv_); +#line 1 "pure2-bugfix-for-multi-token-type-prvalue.cpp2" +(void) std::type_identity_t{&args.argc}; } + diff --git a/regression-tests/test-results/pure2-bugfix-for-multi-token-type-prvalue.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-multi-token-type-prvalue.cpp2.output new file mode 100644 index 0000000000..5db34ad57e --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-multi-token-type-prvalue.cpp2.output @@ -0,0 +1,2 @@ +pure2-bugfix-for-multi-token-type-prvalue.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/regression-tests/test-results/pure2-function-type-id-2-error.cpp2.output b/regression-tests/test-results/pure2-function-type-id-2-error.cpp2.output new file mode 100644 index 0000000000..35c6c3147b --- /dev/null +++ b/regression-tests/test-results/pure2-function-type-id-2-error.cpp2.output @@ -0,0 +1,3 @@ +pure2-function-type-id-2-error.cpp2... +pure2-function-type-id-2-error.cpp2(2,9): error: the parameter of a function type must be named '_' + diff --git a/regression-tests/test-results/pure2-function-type-id-3-error.cpp2.output b/regression-tests/test-results/pure2-function-type-id-3-error.cpp2.output new file mode 100644 index 0000000000..1b84486b8f --- /dev/null +++ b/regression-tests/test-results/pure2-function-type-id-3-error.cpp2.output @@ -0,0 +1,3 @@ +pure2-function-type-id-3-error.cpp2... +pure2-function-type-id-3-error.cpp2(2,11): error: a function type can't have contracts + diff --git a/regression-tests/test-results/pure2-function-type-id-4-error.cpp2.output b/regression-tests/test-results/pure2-function-type-id-4-error.cpp2.output new file mode 100644 index 0000000000..bf48e2caea --- /dev/null +++ b/regression-tests/test-results/pure2-function-type-id-4-error.cpp2.output @@ -0,0 +1,5 @@ +pure2-function-type-id-4-error.cpp2... +pure2-function-type-id-4-error.cpp2(1,25): error: a function type can't have an anonymous return type +pure2-function-type-id-4-error.cpp2(1,14): error: f - variable must be initialized on every branch path + ==> program violates initialization safety guarantee - see previous errors + diff --git a/regression-tests/test-results/pure2-function-type-id-5-error.cpp2.output b/regression-tests/test-results/pure2-function-type-id-5-error.cpp2.output new file mode 100644 index 0000000000..6b1c02fe86 --- /dev/null +++ b/regression-tests/test-results/pure2-function-type-id-5-error.cpp2.output @@ -0,0 +1,3 @@ +pure2-function-type-id-5-error.cpp2... +pure2-function-type-id-5-error.cpp2(2,9): error: function type parameter must have a type + diff --git a/regression-tests/test-results/pure2-function-type-id.cpp b/regression-tests/test-results/pure2-function-type-id.cpp new file mode 100644 index 0000000000..9af02867f2 --- /dev/null +++ b/regression-tests/test-results/pure2-function-type-id.cpp @@ -0,0 +1,135 @@ + +#define CPP2_USE_MODULES Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-function-type-id.cpp2" +auto main() -> int; + + +#line 58 "pure2-function-type-id.cpp2" +// As non-local function parameter. +auto g(auto (* f)() -> void) -> void; +auto g(auto (* f)() -> auto (*)() -> void) -> void; +// As template parameter. +template void> auto g() -> void; +template auto (*)() -> void> auto g() -> void; + +// In non-local function return type. +[[nodiscard]] auto g1() -> auto (*)() -> void; +[[nodiscard]] auto g2() -> auto (*)() -> auto (*)() -> void; + +// clang-format off +// Test case from #343. +[[nodiscard]] auto f2() -> std::function) -> std::string>; + + +#line 75 "pure2-function-type-id.cpp2" +// Adapted from . + [[nodiscard]] auto f(cpp2::in x) -> auto (*)(cpp2::in) -> std::string; +auto postfix_operators() -> void; + + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-function-type-id.cpp2" +auto main() -> int{ + postfix_operators(); + + // Variables with type of a mix of `*`/`const` to `() throws -> void`. + std::type_identity_t void> f0 {[]() -> void{}}; + std::type_identity_t void> const f1 {f0}; + std::type_identity_t void> f2 {f0}; + std::type_identity_t void> const f3 {f0}; + + cpp2::i32 i {0}; + cpp2::i32* i0 {&i}; + cpp2::i32* const i1 {i0}; + cpp2::i32 const* i2 {i0}; + cpp2::i32 const* const i3 {i0}; + + // Assert consistent '*'/'const' with non-function type variables. + static_assert((std::is_const_v)==std::is_const_v); + static_assert((std::is_const_v)==std::is_const_v); + static_assert((std::is_const_v)==std::is_const_v); + + // Variables with various kinds of parameter. + std::type_identity_t) -> void> f4 {[](cpp2::in x) -> void{}}; + std::type_identity_t) -> void> f5 {[](cpp2::in x) -> void{}}; + std::type_identity_t void> f6 {[](cpp2::i32&& x) -> void{}}; + std::type_identity_t) -> void> f7 {[](auto x) -> void{}}; + + // In alternative. + cpp2::Default.expects([&] () -> bool { auto&& __expr = f0; + if (cpp2::is void>(__expr)) { if constexpr( requires{std::terminate(), false;} ) if constexpr( std::is_convertible_v ) return std::terminate(), false; else return bool{}; else return bool{}; } + else if (cpp2::is void>(__expr)) { if constexpr( requires{std::terminate(), false;} ) if constexpr( std::is_convertible_v ) return std::terminate(), false; else return bool{}; else return bool{}; } + else if (cpp2::is void>(__expr)) { if constexpr( requires{true;} ) if constexpr( std::is_convertible_v ) return true; else return bool{}; else return bool{}; } + else return false; } + (), ""); + cpp2::Default.expects([&] () -> bool { auto&& __expr = *cpp2::assert_not_null(f0); + if (cpp2::is void>(__expr)) { if constexpr( requires{true;} ) if constexpr( std::is_convertible_v ) return true; else return bool{}; else return bool{}; } + else return false; } + (), ""); +{ +auto (* f)() -> void = f0; + + // As block variable. +#line 40 "pure2-function-type-id.cpp2" + {} +} + + // As local function parameter. +#line 43 "pure2-function-type-id.cpp2" + (void) [](auto (* f)() -> void) -> void{}; + (void) [](auto (* f)() -> auto (*)() -> void) -> void{}; + (void) [](auto (* f)() -> auto (*)() -> auto (*)() -> void) -> void{}; + + // In local function return type. + (void) []() -> auto (*)() -> void { return nullptr; }; + (void) []() -> auto (*)() -> auto (*)() -> void { return nullptr; }; + + // Without `throws`. + (void) std::type_identity_t void>{cpp2::report_and_terminate}; + + // As template argument. + (void) std::type_identity_t void>{std::move(f0)}; +} + +#line 59 "pure2-function-type-id.cpp2" +auto g(auto (* f)() -> void) -> void{} +auto g(auto (* f)() -> auto (*)() -> void) -> void{} + +template void> auto g() -> void{} +template auto (*)() -> void> auto g() -> void{} + +#line 66 "pure2-function-type-id.cpp2" +[[nodiscard]] auto g1() -> auto (*)() -> void { return nullptr; } +[[nodiscard]] auto g2() -> auto (*)() -> auto (*)() -> void { return nullptr; } + +#line 71 "pure2-function-type-id.cpp2" +[[nodiscard]] auto f2() -> std::function) -> std::string>{ + return [](cpp2::in s) -> std::string{return s + " World!"; }; +} + +#line 76 "pure2-function-type-id.cpp2" + [[nodiscard]] auto f(cpp2::in x) -> auto (*)(cpp2::in) -> std::string { return +[](cpp2::in x) -> std::string { return ""; }; } +auto postfix_operators() -> void{ + cpp2::Default.expects(cpp2::is) -> auto (*)(cpp2::in) -> std::string>(f), ""); + // / | | + // / | | + cpp2::Default.expects(cpp2::is) -> std::string>(f(42)), ""); + // ________________/ | + // / | + cpp2::Default.expects(cpp2::is) -> std::string>(*cpp2::assert_not_null(f(42))), ""); + // _______________/ + // / + cpp2::Default.expects(cpp2::is((*cpp2::assert_not_null(f(42)))(1)), ""); +} // clang-format on + diff --git a/regression-tests/test-results/pure2-function-type-id.cpp2.output b/regression-tests/test-results/pure2-function-type-id.cpp2.output new file mode 100644 index 0000000000..01a4af059d --- /dev/null +++ b/regression-tests/test-results/pure2-function-type-id.cpp2.output @@ -0,0 +1,2 @@ +pure2-function-type-id.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/source/cppfront.cpp b/source/cppfront.cpp index 5b89964d3a..fdfdc5caf6 100644 --- a/source/cppfront.cpp +++ b/source/cppfront.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace cpp2 { @@ -197,7 +198,7 @@ class positional_printer std::vector const* pcomments = {}; // Cpp2 comments data source const* psource = {}; parser const* pparser = {}; - + source_position curr_pos = {}; // current (line,col) in output lineno_t generated_pos_line = {}; // current line in generated output int last_line_indentation = {}; @@ -975,6 +976,15 @@ class positional_printer }; +template +auto stack_value(T& var, std::type_identity_t const& value) + -> auto +{ + return finally([&var, old = std::exchange(var, value)]() { + var = old; + }); +}; + //----------------------------------------------------------------------- // // cppfront: a compiler instance @@ -1226,7 +1236,7 @@ class cppfront //--------------------------------------------------------------------- // Do lowered file prolog - // + // // Only emit extra lines if we actually have Cpp2, because // we want pure-Cpp1 files to pass through with zero changes if (source.has_cpp2()) @@ -1284,7 +1294,7 @@ class cppfront } } - + //--------------------------------------------------------------------- // Do phase1_type_defs_func_decls // @@ -1641,7 +1651,7 @@ class cppfront { add_move = false; } - + if ( emitting_move_that_function && *n.identifier == "that" @@ -1768,6 +1778,9 @@ class cppfront // auto emit( type_id_node const& n, + bool anonymous_object = false, + bool needs_wrapping = false, + std::string_view function_type_id_identifier = {}, source_position pos = {} ) -> void @@ -1776,18 +1789,50 @@ class cppfront pos = n.position(); } + auto is_function_type_id = n.id.index() == type_id_node::function; + auto wrap_type = + ( + anonymous_object + && !n.pc_qualifiers.empty() + ) + || ( + is_function_type_id + && needs_wrapping + ); + + // A type that has more than 1 non-nested token may need to be wrapped. + if (wrap_type) { + printer.print_cpp2("std::type_identity_t<", pos); + } if (n.is_wildcard()) { printer.print_cpp2("auto", pos); } else { try_emit(n.id, false, false); try_emit(n.id); + try_emit(n.id, nullptr, false, false, function_type_id_identifier, n.pc_qualifiers); try_emit(n.id); } - for (auto i = n.pc_qualifiers.rbegin(); i != n.pc_qualifiers.rend(); ++i) { - if ((**i) == "const") { printer.print_cpp2(" ", pos); } - emit(**i, false, pos); + // Emit the type qualifiers. + // A function type's are split between the object's identifier and before its parameter list. + if (!is_function_type_id) { + for (auto i = n.pc_qualifiers.rbegin(); i != n.pc_qualifiers.rend(); ++i) { + if ((**i) == "const") { printer.print_cpp2(" ", pos); } + emit(**i, false, pos); + } + } + if (wrap_type) { + printer.print_cpp2(">", pos); + } + // Emit the `const` pertaining to an object with function type right before its identifier. + if ( + is_function_type_id + && !n.pc_qualifiers.empty() + && *n.pc_qualifiers.front() == "const" + ) { + printer.print_cpp2(" ", pos); + emit(*n.pc_qualifiers.front(), false, pos); } } @@ -2253,7 +2298,7 @@ class cppfront return; } else if ( is_literal(tok->type()) || n.expression->expr->is_result_a_temporary_variable() - ) + ) { errors.emplace_back( n.position(), @@ -2497,7 +2542,7 @@ class cppfront printer.add_pad_in_this_line( -5 ); - emit(*type_id); + emit(*type_id, true); printer.print_cpp2("{", decl->position()); assert(decl->initializer); @@ -2545,7 +2590,7 @@ class cppfront ) -> bool { - if (!fun_node) { + if (!fun_node) { return false; } if (addr_cnt > deref_cnt) { @@ -2572,11 +2617,11 @@ class cppfront ) -> bool { - if (!type_id_node) { + if (!type_id_node) { return false; } if (addr_cnt > deref_cnt) { - return true; + return true; } if ( type_id_node->dereference_of ) { @@ -2753,7 +2798,7 @@ class cppfront { auto& unqual = std::get(id->id); assert(unqual); - // TODO: Generalize this: + // TODO: Generalize this: // - we don't recognize pointer types from Cpp1 // - we don't deduce pointer types from parameter_declaration_list_node if ( is_pointer_declaration(unqual->identifier) ) { @@ -3780,7 +3825,8 @@ class cppfront auto emit( parameter_declaration_node const& n, bool is_returns = false, - bool is_template_parameter = false + bool is_template_parameter = false, + bool in_function_type_id = false ) -> void { @@ -3868,11 +3914,16 @@ class cppfront assert( n.declaration->is_object() ); auto const& type_id = *std::get(n.declaration->type); + auto has_function_type_id = type_id.id.index() == type_id_node::function; + if (is_template_parameter) { - emit( type_id ); - printer.print_cpp2(" ", type_id.position()); assert (n.declaration->identifier); - emit(*n.declaration->identifier); + emit( type_id, false, false, print_to_string(*n.declaration->identifier) ); + // If it has a function type, the identifier has been emitted before its parameter list. + if (!has_function_type_id) { + printer.print_cpp2(" ", type_id.position()); + emit(*n.declaration->identifier); + } return; } @@ -3951,7 +4002,7 @@ class cppfront && n.pass == passing_style::in ) { - printer.print_cpp2( print_to_string(type_id), n.position() ); + printer.print_cpp2( print_to_string(type_id, false, false, identifier), n.position() ); } else if ( type_id.is_wildcard() @@ -4028,7 +4079,8 @@ class cppfront if (is_returns) { printer.print_extra( " " + identifier); } - else { + // If it has a function type, the identifier has been emitted before its parameter list. + else if (!in_function_type_id && !has_function_type_id) { printer.print_cpp2( " " + identifier, n.declaration->identifier->position()); } @@ -4048,11 +4100,12 @@ class cppfront auto emit( parameter_declaration_list_node const& n, bool is_returns = false, - bool is_template_parameter = false + bool is_template_parameter = false, + bool in_function_type_id = false ) -> void { - in_parameter_list = true; + auto stack = stack_value(in_parameter_list, true); if (is_returns) { printer.print_extra( "{ " ); @@ -4076,7 +4129,7 @@ class cppfront } prev_pos = x->position(); assert(x); - emit(*x, is_returns, is_template_parameter); + emit(*x, is_returns, is_template_parameter, in_function_type_id); if (!x->declaration->has_name("this")) { first = false; } @@ -4097,8 +4150,6 @@ class cppfront emit(*n.close_paren); printer.preempt_position_pop(); } - - in_parameter_list = false; } @@ -4180,16 +4231,20 @@ class cppfront //----------------------------------------------------------------------- // auto emit( - function_type_node const& n, - token const* ident, - bool is_main = false, - bool is_ctor_or_dtor = false, - std::string suffix1 = {} + function_type_node const& n, + token const* ident, + bool is_main = false, + bool is_ctor_or_dtor = false, + std::string_view function_type_id_identifier = {}, + std::span pc_qualifiers = {}, + std::string suffix1 = {} ) -> void { assert(n.parameters); + auto is_function_type_id = !n.my_decl; + if ( is_main && n.parameters->parameters.size() > 0 @@ -4204,17 +4259,52 @@ class cppfront ); } else { - emit(*n.parameters); + if (is_function_type_id) { + // Emit `auto` now for the later trailing return type. + auto code = std::string{"auto "}; + // Omit the `const` qualifier that will be applied to the object. + if ( + !pc_qualifiers.empty() + && *pc_qualifiers.front() == "const" + ) { + pc_qualifiers = pc_qualifiers.subspan(1); + } + // Emit the parenthesized qualifiers that appertain to the function type. + if (!pc_qualifiers.empty()) + { + code += "("; + for (auto i = pc_qualifiers.begin(); i != pc_qualifiers.end(); ++i) { + if ((**i) == "const") { code += " "; } + code += print_to_string(**i, false, n.position()); + } + if (!function_type_id_identifier.empty()) { + (code += " ") += function_type_id_identifier; + } + code += ")"; + } + printer.print_cpp2( code, n.position() ); + } + // Now emit the parameters. + emit(*n.parameters, false, false, is_function_type_id); } // For now, adding implicit noexcept only for move/swap/dtor functions if ( - n.is_move() - || n.is_swap() - || n.is_destructor() - || generating_move_from == n.my_decl + ( + !is_function_type_id + && ( + n.is_move() + || n.is_swap() + || n.is_destructor() + || generating_move_from == n.my_decl + ) ) - { + // And always for function types that aren't specified with `throws`. + || ( + is_function_type_id + && !n.throws + ) + ) { printer.print_cpp2( " noexcept", n.position() ); } @@ -4222,10 +4312,12 @@ class cppfront // Handle a special member function if ( - n.is_assignment() - || generating_assignment_from == n.my_decl - ) - { + !is_function_type_id + && ( + n.is_assignment() + || generating_assignment_from == n.my_decl + ) + ) { assert( n.returns.index() == function_type_node::empty && n.my_decl->parent_declaration->name() @@ -4253,7 +4345,8 @@ class cppfront else if (n.returns.index() == function_type_node::id) { auto is_type_scope_function_with_in_this = - n.my_decl->parent_is_type() + !is_function_type_id + && n.my_decl->parent_is_type() && n.parameters->ssize() > 0 && (*n.parameters)[0]->direction() == passing_style::in ; @@ -4815,7 +4908,7 @@ class cppfront } // If this is a generated declaration (negative source line number), - // add a line break before + // add a line break before if ( printer.get_phase() == printer.phase2_func_defs && n.position().lineno < 1 @@ -5450,7 +5543,7 @@ class cppfront // A2) This is '(out this, move that)' // and no '(inout this, move that)' was written by the user // (*) and no '(inout this, that)' was written by the user (*) - // + // // (*) This third test is to tie-break M2 and A2 in favor of M2. Both M2 and A2 // can generate a missing '(inout this, move that)', and if we have both // options then we should prefer to use M2 (generate move assignment from @@ -5536,7 +5629,7 @@ class cppfront printer.print_cpp2( prefix, n.position() ); printer.print_cpp2( "auto " + type_qualification_if_any_for(n), n.position() ); emit( *n.name() ); - emit( *func, n.name(), is_main, false, suffix1 ); + emit( *func, n.name(), is_main, false, {}, {}, suffix1 ); printer.print_cpp2( suffix2, n.position() ); } } @@ -5636,7 +5729,7 @@ class cppfront printer.preempt_position_push( n.equal_sign ); // *** NOTE ===================================================== - // + // // This branch to emit the requires-clause should maybe be // moved to location (A) above, so that it's also emitted // on the function declaration. But moving it to (A) triggers @@ -5644,14 +5737,14 @@ class cppfront // break using a 'forward' parameter of a concrete type and // also explicitly user-written requires-clauses that do // similar decltype tests. - // + // // I don't want to neednessly break compatibility with a // decently conforming C++20 compiler that works well for // everything else that Cpp2 needs from C++20. If the // 'requires' down here doesn't cause a problem, I'll keep // it here for now... if we do encounter a reason it needs to // also be on the declaration, move this code to (A). - // + // // Handle requires clause - an explicit one the user wrote, // and/or any conditions we generated while processing the // parameters (i.e., forwarding a concrete type) @@ -5765,7 +5858,7 @@ class cppfront // Emit "auto" for deduced types (of course) if (type->is_wildcard()) { assert(n.initializer); - emit( *type, n.position() ); + emit( *type, false, false, {}, n.position() ); } // Otherwise, emit the type else { @@ -5783,7 +5876,7 @@ class cppfront } } printer.preempt_position_push(n.position()); - emit( *type ); + emit( *type, false, true ); printer.preempt_position_pop(); // one pointer is enough for now, pointer-to-function fun can be later if ( diff --git a/source/parse.h b/source/parse.h index 2fbf60396a..d47bbd8e37 100644 --- a/source/parse.h +++ b/source/parse.h @@ -721,7 +721,7 @@ struct postfix_expression_node if (ops.empty()) { return false; } else { - return (ops.front().op->type() == lexeme::Ampersand + return (ops.front().op->type() == lexeme::Ampersand || ops.front().op->type() == lexeme::Tilde); } } @@ -1008,6 +1008,8 @@ struct qualified_id_node }; +struct function_type_node; + struct type_id_node { source_position pos; @@ -1018,11 +1020,12 @@ struct type_id_node int dereference_cnt = {}; token const* suspicious_initialization = {}; - enum active { empty=0, qualified, unqualified, keyword }; + enum active { empty=0, qualified, unqualified, function, keyword }; std::variant< std::monostate, std::unique_ptr, std::unique_ptr, + std::unique_ptr, token const* > id; @@ -1085,6 +1088,8 @@ struct type_id_node return {}; break;case unqualified: return get(id)->get_token(); + break;case function: + return {}; break;case keyword: return get(id); break;default: @@ -1109,6 +1114,7 @@ struct type_id_node } try_visit(id, v, depth); try_visit(id, v, depth); + try_visit(id, v, depth); try_visit(id, v, depth); v.end(*this, depth); } @@ -3965,7 +3971,7 @@ class parser // || curr().type() == lexeme::LeftBrace ) { - bool inside_initializer = ( + bool inside_initializer = ( peek(-1) && peek(-1)->type() == lexeme::Assignment ); auto open_paren = &curr(); @@ -3987,12 +3993,12 @@ class parser next(); if ( curr().type() != lexeme::Semicolon - && curr().type() != lexeme::RightParen - && curr().type() != lexeme::RightBracket + && curr().type() != lexeme::RightParen + && curr().type() != lexeme::RightBracket && curr().type() != lexeme::Comma ) { expr_list->inside_initializer = false; - } + } n->expr = std::move(expr_list); return n; } @@ -4350,7 +4356,7 @@ class parser //G shift-expression '<<' additive-expression //G shift-expression '>>' additive-expression //G - auto shift_expression(bool allow_angle_operators = true) + auto shift_expression(bool allow_angle_operators = true) -> auto { if (allow_angle_operators) { @@ -4385,7 +4391,7 @@ class parser //G shift-expression //G compare-expression '<=>' shift-expression //G - auto compare_expression(bool allow_angle_operators = true) + auto compare_expression(bool allow_angle_operators = true) -> auto { return binary_expression ( @@ -4401,7 +4407,7 @@ class parser //G relational-expression '<=' compare-expression //G relational-expression '>=' compare-expression //G - auto relational_expression(bool allow_angle_operators = true) + auto relational_expression(bool allow_angle_operators = true) -> auto { if (allow_angle_operators) { @@ -4433,7 +4439,7 @@ class parser //G equality-expression '==' relational-expression //G equality-expression '!=' relational-expression //G - auto equality_expression(bool allow_angle_operators = true) + auto equality_expression(bool allow_angle_operators = true) -> auto { return binary_expression ( @@ -4446,7 +4452,7 @@ class parser //G equality-expression //G bit-and-expression '&' equality-expression //G - auto bit_and_expression(bool allow_angle_operators = true) + auto bit_and_expression(bool allow_angle_operators = true) -> auto { return binary_expression ( @@ -4459,7 +4465,7 @@ class parser //G bit-and-expression //G bit-xor-expression '^' bit-and-expression //G - auto bit_xor_expression(bool allow_angle_operators = true) + auto bit_xor_expression(bool allow_angle_operators = true) -> auto { return binary_expression ( @@ -4472,7 +4478,7 @@ class parser //G bit-xor-expression //G bit-or-expression '|' bit-xor-expression //G - auto bit_or_expression(bool allow_angle_operators = true) + auto bit_or_expression(bool allow_angle_operators = true) -> auto { return binary_expression ( @@ -4485,7 +4491,7 @@ class parser //G bit-or-expression //G logical-and-expression '&&' bit-or-expression //G - auto logical_and_expression(bool allow_angle_operators = true) + auto logical_and_expression(bool allow_angle_operators = true) -> auto { return binary_expression ( @@ -4500,7 +4506,7 @@ class parser //G logical-and-expression //G logical-or-expression '||' logical-and-expression //G - auto logical_or_expression(bool allow_angle_operators = true) + auto logical_or_expression(bool allow_angle_operators = true) -> auto { return binary_expression ( @@ -4634,6 +4640,7 @@ class parser //G type-id: //G type-qualifier-seq? qualified-id //G type-qualifier-seq? unqualified-id + //G type-qualifier-seq? function-type //G //G type-qualifier-seq: //G type-qualifier @@ -4643,7 +4650,10 @@ class parser //G 'const' //G '*' //G - auto type_id() + auto type_id( + bool function_is_optional = false, + bool could_be_anonymous_returns = false + ) -> std::unique_ptr { auto n = std::make_unique(); @@ -4676,9 +4686,61 @@ class parser n->id = std::move(id); assert (n->id.index() == type_id_node::unqualified); } + else if ( + auto undo_function_parse = + [&, start_pos = pos]() { + errors.clear(); + pos = start_pos; + }; + auto f = function_type(nullptr, true, true) + ) { + if (!errors.empty() && (function_is_optional || could_be_anonymous_returns)) { + undo_function_parse(); + return {}; + } + if (f->parameters) { + for (auto& p: f->parameters->parameters) { + assert(p->has_name()); + if (p->name()->as_string_view() != "_") { + if (could_be_anonymous_returns && n->pc_qualifiers.empty()) { + undo_function_parse(); + return {}; + } + error( + "the parameter of a function type must be named '_'", + false, + p->position() + ); + return {}; + } + } + } + if (auto l = get_if(&f->returns)) { + error( + "a function type can't have an anonymous return type", + false, + (*l)->position() + ); + return {}; + } + if (!f->contracts.empty()) { + error( + "a function type can't have contracts", + false, + f->contracts.front()->position() + ); + return {}; + } + n->pos = f->position(); + n->id = std::move(f); + assert (n->id.index() == type_id_node::function); + } else { if (!n->pc_qualifiers.empty()) { - error("'*'/'const' type qualifiers must be followed by a type name or '_' wildcard"); + error("'*'/'const' type qualifiers must be followed by a type name or '_' wildcard"); + } + else if (function_is_optional) { + undo_function_parse(); } return {}; } @@ -4740,7 +4802,7 @@ class parser term.op = &curr(); next(); - if ((term.type = type_id()) != nullptr) { + if ((term.type = type_id(true)) != nullptr) { ; } else if ((term.expr = expression()) != nullptr) { @@ -4818,19 +4880,23 @@ class parser n->open_angle = curr().position(); next(); - + auto term = unqualified_id_node::term{}; do { // disallow unparenthesized relational comparisons in template args - if (auto e = expression(false)) { + if (auto start_pos = pos; auto e = expression(false)) { term.arg = std::move(e); } - else if (auto i = type_id()) { - term.arg = std::move(i); - } else { - break; + errors.clear(); + pos = start_pos; + if (auto i = type_id()) { + term.arg = std::move(i); + } + else { + break; + } } n->template_args.push_back( std::move(term) ); } @@ -5377,7 +5443,7 @@ class parser n->is_as_keyword = &curr(); next(); - if (auto id = type_id()) { + if (auto id = type_id(true)) { n->type_id = std::move(id); } else if (auto e = postfix_expression()) { @@ -5749,7 +5815,8 @@ class parser bool is_returns = false, bool is_named = true, bool is_template = true, - bool is_statement = false + bool is_statement = false, + bool is_type_id = false ) -> std::unique_ptr { @@ -5884,7 +5951,7 @@ class parser error("Cpp2 is currently exploring the path of not allowing default arguments - use overloading instead", false); return {}; } - if (is_named && is_returns) { + if (is_named && (is_returns || is_type_id)) { auto tok = n->name(); assert(tok); if (tok->type() != lexeme::Identifier) { @@ -5892,8 +5959,11 @@ class parser false, tok->position()); } else if (n->declaration->has_wildcard_type()) { - error("return parameter '" + tok->to_string(true) + "' must have a type", - false, tok->position()); + auto prefix = std::string{"function type parameter"}; + if (is_returns) { + prefix = "return parameter '" + tok->to_string(true) + "'"; + } + error(prefix + " must have a type", false, tok->position()); } } return n; @@ -5911,7 +5981,8 @@ class parser bool is_returns = false, bool is_named = true, bool is_template = false, - bool is_statement = false + bool is_statement = false, + bool is_type_id = false ) -> std::unique_ptr { @@ -5938,7 +6009,7 @@ class parser auto param = std::make_unique(); - while ((param = parameter_declaration(is_returns, is_named, is_template, is_statement)) != nullptr) + while ((param = parameter_declaration(is_returns, is_named, is_template, is_statement, is_type_id)) != nullptr) { if ( std::ssize(n->parameters) > 1 @@ -6084,14 +6155,17 @@ class parser //G auto function_type( declaration_node* my_decl, - bool is_named = true + bool is_named = true, + bool is_type_id = false ) -> std::unique_ptr { auto n = std::make_unique( my_decl ); + auto is_function_type_id = !my_decl; + // Parameters - auto parameters = parameter_declaration_list(false, is_named, false); + auto parameters = parameter_declaration_list(false, is_named, false, false, is_type_id); if (!parameters) { return {}; } @@ -6104,11 +6178,13 @@ class parser ) { if ( - n->is_move() - || n->is_swap() - || n->is_destructor() - ) - { + !is_function_type_id + && ( + n->is_move() + || n->is_swap() + || n->is_destructor() + ) + ) { error( "(experimental restriction) Cpp2 currently does not allow a move, swap, or destructor function to be designated 'throws'" ); return {}; } @@ -6144,7 +6220,7 @@ class parser error(msg + "' must be followed by a type-id"); } } - else if (auto t = type_id()) { + else if (auto t = type_id(false, true)) { if ( t->get_token() && t->get_token()->to_string(true) == "auto" @@ -6389,7 +6465,7 @@ class parser } assert (n->is_type()); } - + // Or a function type, declaring a function - and tell the function whether it's in a user-defined type else if (auto t = function_type(n.get(), named)) { @@ -6537,11 +6613,11 @@ class parser ) { auto& type = std::get(n->type); - // object initialized by the address of the curr() object + // object initialized by the address of the curr() object if (peek(1)->type() == lexeme::Ampersand) { type->address_of = &curr(); } - // object initialized by (potentially multiple) dereference of the curr() object + // object initialized by (potentially multiple) dereference of the curr() object else if (peek(1)->type() == lexeme::Multiply) { type->dereference_of = &curr(); for (int i = 1; peek(i)->type() == lexeme::Multiply; ++i) @@ -6746,7 +6822,7 @@ class parser return {}; } if ( - t->is_wildcard() + t->is_wildcard() || ( t->get_token() && t->get_token()->to_string(true) == "auto" ) ) { errors.emplace_back( diff --git a/source/sema.h b/source/sema.h index cfaf589582..2cffbc186c 100644 --- a/source/sema.h +++ b/source/sema.h @@ -477,6 +477,7 @@ class sema if (is_uninitialized_decl(*sym)) { if ( sym->declaration->is_object() + && !sym->declaration->parent_is_object() && !sym->declaration->parent_is_namespace() ) { @@ -1564,7 +1565,7 @@ class sema // Skip type scope (member) variables && !(n.parent_is_type() && n.is_object()) // Skip unnamed variables - && n.identifier + && n.identifier // Skip non-out parameters && ( !inside_parameter_list