diff --git a/regression-tests/pure2-bugfix-for-deducible-parameters.cpp2 b/regression-tests/pure2-bugfix-for-deducible-parameters.cpp2 new file mode 100644 index 000000000..b528beee3 --- /dev/null +++ b/regression-tests/pure2-bugfix-for-deducible-parameters.cpp2 @@ -0,0 +1,32 @@ +// Dependent, non-deducible parameters are wrapped like non-dependent parameters. +init: (out x: std::integral_constant) = { x = (); } +init: (out x: std::integral_constant, _: T) = { x = (); } +id: (in_ref x: std::integral_constant) = x; +id: (x: std::integral_constant, y: T) = { assert(x& == y&); } + +main: () = { + zero: type == std::integral_constant; + + z: zero; + init(out z); + assert(id(z)& == z&); + + // Deducible parameters. + _ = :v = 0; + : (_: std::vector) = {}(:std::vector = ()); + : (_: std::vector>) = {}(:std::vector> = ()); + // _ = : (x: std::pair, y: U) = {}(:std::pair = (0, 0), z); // Blocked on #727. + : (_: std::array, _: U) = {}(:std::array = (), z); + init(out z, z); + id(z, z); + + // Test that these are emitted unwrapped in case they are deducible. + (copy f := : (_: std::vector>) = {}) + static_assert(!std::is_invocable_v>, "`T` is non-deducible."); + (copy f := : (_: std::vector>) = {}) + static_assert(std::is_invocable_v>>, "`T` is deducible."); +} + +v: type = { + operator=: (out this, _: T) = { } +} diff --git a/regression-tests/pure2-bugfix-for-dependent-types-recursion.cpp2 b/regression-tests/pure2-bugfix-for-dependent-types-recursion.cpp2 new file mode 100644 index 000000000..d449b8d55 --- /dev/null +++ b/regression-tests/pure2-bugfix-for-dependent-types-recursion.cpp2 @@ -0,0 +1,6 @@ +main: () = { + a: type == b; + b: type == a; + _ = a::t; + _ = b::t; +} diff --git a/regression-tests/pure2-bugfix-for-dependent-types.cpp2 b/regression-tests/pure2-bugfix-for-dependent-types.cpp2 new file mode 100644 index 000000000..dadea6012 --- /dev/null +++ b/regression-tests/pure2-bugfix-for-dependent-types.cpp2 @@ -0,0 +1,96 @@ +identity: type == T; + +f: (inout _: T::value_type) = { } +f: (move _: T::value_type) = { } +f: (x: T::value_type) -> T::value_type = { + assert(x is T::value_type); + y: T::value_type; + y = x; + z: type == T::value_type; + return (:T::value_type = x); + + // Dependent *template-id*s. + _ = :identity::value_type = (); // First identifier. + _ = :std::optional::value_type = (); // Non-first identifier. + _ = :std::array::value_type = (); + _ = :std::array::value_type = (); + + // Emitted `template`. + ptr: type == * T; // Also test lookup through type aliases. + nptr: type == * i32; + _ = :std::pointer_traits::rebind = (); // Type-only context. + _ = :std::pointer_traits::rebind = (); // Non-dependent. + _ = :std::pointer_traits::rebind = (); // Dependent on the nested template. + _ = :std::pointer_traits::rebind = (); // Dependent on the outer template. + // _ = :identity::rebind> = (); // Non type-only context. Blocked on #727. + + // Aliases. + w: type == T; + _ = :w::value_type = x; + v: type == w; + _ = :v::value_type = x; + a: type == T::type; + _ = :a::value_type = x; + + { + // Test that there's no prefixed `typename` to.... + _ = std::integral_constant(); // `T::value`. + _ = :std::type_identity_t = (); // `std::type_identity_t`. + + // Test that non-dependent names aren't emitted with `typename`. + a: type == std::integral_constant; + b: type == a; + c: type == b; + _ = :b::value_type = x; + _ = :c::value_type = x; + } +} + +t: @cpp1_rule_of_zero type = { + u: type = { + x: T::value_type = (); + this: T::type = (); + // Test that there's no `typename` in the member initializer list. + operator=: (out this, that) = { } + } + x: T::value_type = 0; +} + +main: () = { + zero: type == std::integral_constant; + _ = f(0); + + _ = : ::t = (); + + // Emitted `template` (noop, taken care of by the UFCS macro). + _ = :(move f) = _ = f.operator()();(: () = {}); + + // Nesting is not relevant to lookup. + _ = : () = { _ = :T::value_type = (); }; + _ = :() = { _ = : () = { _ = :T::value_type = (); }; }; + _ = :() = { _ = :() = { _ = : () = { _ = :T::value_type = (); }; }; }; + _ = :() = { _ = :() = { _ = :() = { _ = : () = { _ = :T::value_type = (); }; }; }; }; + _ = :() = { _ = :() = { _ = : () = { _ = :() = { _ = :T::value_type = (); }; }; }; }; + _ = :() = { _ = : () = { _ = :() = { _ = :() = { _ = :T::value_type = (); }; }; }; }; + _ = : () = { _ = :() = { _ = :() = { _ = :() = { _ = :T::value_type = (); }; }; }; }; + _ = : () = { _ = :() = { _ = :() = { _ = :(_: T::value_type) = {}; }; }; }; + _ = : () = { _ = :() = { _ = :(_: T::value_type) = { _ = :() = {}; }; }; }; + _ = : () = { _ = :(_: T::value_type) = { _ = :() = { _ = :() = {}; }; }; }; + _ = : (_: T::value_type) = { _ = :() = { _ = :() = { _ = :() = {}; }; }; }; + + // Lookup. + { + alias: type == std::integral_constant; + _ = :alias::value_type = 0; // Non-dependent. + } + _ = : (_: T) = { + alias: type == std::integral_constant; + _ = :alias::value_type = 0; // Dependent. + { + alias: type == std::integral_constant; + _ = :alias::value_type = 0; // Non-dependent. + } + }(0); + + _ = :(r) -> std::type_identity_t = r[0];(std::vector(1)); +} diff --git a/regression-tests/test-results/pure2-bugfix-for-deducible-parameters.cpp b/regression-tests/test-results/pure2-bugfix-for-deducible-parameters.cpp new file mode 100644 index 000000000..d35801b81 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-deducible-parameters.cpp @@ -0,0 +1,90 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-bugfix-for-deducible-parameters.cpp2" + +#line 30 "pure2-bugfix-for-deducible-parameters.cpp2" +template class v; + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-bugfix-for-deducible-parameters.cpp2" +// Dependent, non-deducible parameters are wrapped like non-dependent parameters. +#line 2 "pure2-bugfix-for-deducible-parameters.cpp2" +template auto init(cpp2::impl::out> x) -> void; +template auto init(cpp2::impl::out> x, [[maybe_unused]] T const& unnamed_param_2) -> void; +template [[nodiscard]] auto id(std::integral_constant const& x) -> decltype(auto); +template auto id(cpp2::impl::in> x, T const& y) -> void; + +auto main() -> int; + +#line 30 "pure2-bugfix-for-deducible-parameters.cpp2" +template class v { + public: explicit v([[maybe_unused]] T const& unnamed_param_2); +#line 31 "pure2-bugfix-for-deducible-parameters.cpp2" + public: auto operator=([[maybe_unused]] T const& unnamed_param_2) -> v& ; + public: v(v const&) = delete; /* No 'that' constructor, suppress copy */ + public: auto operator=(v const&) -> void = delete; + +#line 32 "pure2-bugfix-for-deducible-parameters.cpp2" +}; + + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-bugfix-for-deducible-parameters.cpp2" + +#line 2 "pure2-bugfix-for-deducible-parameters.cpp2" +template auto init(cpp2::impl::out> x) -> void{x.construct(); } +#line 3 "pure2-bugfix-for-deducible-parameters.cpp2" +template auto init(cpp2::impl::out> x, [[maybe_unused]] T const& unnamed_param_2) -> void{x.construct(); } +#line 4 "pure2-bugfix-for-deducible-parameters.cpp2" +template [[nodiscard]] auto id(std::integral_constant const& x) -> decltype(auto) { return x; } +#line 5 "pure2-bugfix-for-deducible-parameters.cpp2" +template auto id(cpp2::impl::in> x, T const& y) -> void{if (cpp2::cpp2_default.is_active() && !(&x == &y) ) { cpp2::cpp2_default.report_violation(""); }} + +#line 7 "pure2-bugfix-for-deducible-parameters.cpp2" +auto main() -> int{ + using zero = std::integral_constant; + + cpp2::impl::deferred_init z; + init(cpp2::impl::out(&z)); + if (cpp2::cpp2_default.is_active() && !(&id(z.value()) == &z.value()) ) { cpp2::cpp2_default.report_violation(""); } + + // Deducible parameters. + static_cast(v{ 0}); + []([[maybe_unused]] std::vector const& unnamed_param_1) -> void{}(std::vector{}); + []([[maybe_unused]] std::vector> const& unnamed_param_1) -> void{}(std::vector>{}); + // _ = : (x: std::pair, y: U) = {}(:std::pair = (0, 0), z); // Blocked on #727. + []([[maybe_unused]] std::array const& unnamed_param_1, [[maybe_unused]] U const& unnamed_param_2) -> void{}(std::array{}, z.value()); + init(cpp2::impl::out(&z.value()), z.value()); + id(z.value(), cpp2::move(z.value())); +{ +auto f{[]([[maybe_unused]] std::vector> const& unnamed_param_1) -> void{}}; + + // Test that these are emitted unwrapped in case they are deducible. + +#line 25 "pure2-bugfix-for-deducible-parameters.cpp2" + static_assert(!(std::is_invocable_v>), "`T` is non-deducible."); +} +{ +auto f{[]([[maybe_unused]] std::vector> const& unnamed_param_1) -> void{}}; + +#line 27 "pure2-bugfix-for-deducible-parameters.cpp2" + static_assert(std::is_invocable_v>>, "`T` is deducible."); +} +#line 28 "pure2-bugfix-for-deducible-parameters.cpp2" +} + +#line 31 "pure2-bugfix-for-deducible-parameters.cpp2" + template v::v([[maybe_unused]] T const& unnamed_param_2){} +#line 31 "pure2-bugfix-for-deducible-parameters.cpp2" + template auto v::operator=([[maybe_unused]] T const& unnamed_param_2) -> v& { + return *this; } + diff --git a/regression-tests/test-results/pure2-bugfix-for-deducible-parameters.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-deducible-parameters.cpp2.output new file mode 100644 index 000000000..7e84d47be --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-deducible-parameters.cpp2.output @@ -0,0 +1,2 @@ +pure2-bugfix-for-deducible-parameters.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/regression-tests/test-results/pure2-bugfix-for-dependent-types-recursion.cpp b/regression-tests/test-results/pure2-bugfix-for-dependent-types-recursion.cpp new file mode 100644 index 000000000..d04857dc5 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-dependent-types-recursion.cpp @@ -0,0 +1,27 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-bugfix-for-dependent-types-recursion.cpp2" + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-bugfix-for-dependent-types-recursion.cpp2" +auto main() -> int; + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-bugfix-for-dependent-types-recursion.cpp2" +auto main() -> int{ +#line 2 "pure2-bugfix-for-dependent-types-recursion.cpp2" + using a = b; + using b = a; + static_cast(a::t); + static_cast(b::t); +} + diff --git a/regression-tests/test-results/pure2-bugfix-for-dependent-types-recursion.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-dependent-types-recursion.cpp2.output new file mode 100644 index 000000000..759bf8d53 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-dependent-types-recursion.cpp2.output @@ -0,0 +1,2 @@ +pure2-bugfix-for-dependent-types-recursion.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/regression-tests/test-results/pure2-bugfix-for-dependent-types.cpp b/regression-tests/test-results/pure2-bugfix-for-dependent-types.cpp new file mode 100644 index 000000000..87c12843f --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-dependent-types.cpp @@ -0,0 +1,138 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-bugfix-for-dependent-types.cpp2" + +#line 49 "pure2-bugfix-for-dependent-types.cpp2" +template class t; + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-bugfix-for-dependent-types.cpp2" +template using identity = T; +#line 2 "pure2-bugfix-for-dependent-types.cpp2" + +template auto f([[maybe_unused]] typename T::value_type& unnamed_param_1) -> void; +template auto f([[maybe_unused]] typename T::value_type&& unnamed_param_1) -> void; +template [[nodiscard]] auto f(cpp2::impl::in x) -> T::value_type; + +#line 49 "pure2-bugfix-for-dependent-types.cpp2" +template class t { +struct u_x_as_base { typename T::value_type x; }; + +#line 50 "pure2-bugfix-for-dependent-types.cpp2" + public: class u: public u_x_as_base, public T::type { + +#line 53 "pure2-bugfix-for-dependent-types.cpp2" + // Test that there's no `typename` in the member initializer list. + public: u(u const& that); + }; + private: T::value_type x {0}; +}; + +auto main() -> int; + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-bugfix-for-dependent-types.cpp2" + +#line 3 "pure2-bugfix-for-dependent-types.cpp2" +template auto f([[maybe_unused]] typename T::value_type& unnamed_param_1) -> void{} +#line 4 "pure2-bugfix-for-dependent-types.cpp2" +template auto f([[maybe_unused]] typename T::value_type&& unnamed_param_1) -> void{} +#line 5 "pure2-bugfix-for-dependent-types.cpp2" +template [[nodiscard]] auto f(cpp2::impl::in x) -> T::value_type{ + if (cpp2::cpp2_default.is_active() && !(cpp2::impl::is(x)) ) { cpp2::cpp2_default.report_violation(""); } + cpp2::impl::deferred_init y; + y.construct(x); + using z = T::value_type; + return { typename T::value_type{x} }; + + // Dependent *template-id*s. + static_cast(typename identity::value_type{});// First identifier. + static_cast(typename std::optional::value_type{});// Non-first identifier. + static_cast(typename std::array::value_type{}); + static_cast(typename std::array::value_type{}); + + // Emitted `template`. + using ptr = T*; // Also test lookup through type aliases. + using nptr = cpp2::i32*; + static_cast(typename std::pointer_traits::template rebind{});// Type-only context. + static_cast(std::pointer_traits::rebind{});// Non-dependent. + static_cast(std::pointer_traits::rebind{});// Dependent on the nested template. + static_cast(typename std::pointer_traits::template rebind{});// Dependent on the outer template. + // _ = :identity::rebind> = (); // Non type-only context. Blocked on #727. + + // Aliases. + using w = T; + static_cast(typename w::value_type{x}); + using v = w; + static_cast(typename v::value_type{x}); + using a = T::type; + static_cast(typename a::value_type{x}); + + { + // Test that there's no prefixed `typename` to.... + static_cast(std::integral_constant());// `T::value`. + static_cast(std::type_identity_t{});// `std::type_identity_t`. + + // Test that non-dependent names aren't emitted with `typename`. + using a = std::integral_constant; + using b = a; + using c = b; + static_cast(b::value_type{x}); + static_cast(c::value_type{x}); + } +} + +#line 54 "pure2-bugfix-for-dependent-types.cpp2" + template t::u::u(u const& that) + : u_x_as_base{ that.x } + , T::type{ static_cast(that) }{} + +#line 59 "pure2-bugfix-for-dependent-types.cpp2" +auto main() -> int{ + using zero = std::integral_constant; + static_cast(f(0)); + + static_cast(::t{}); + + // Emitted `template` (noop, taken care of by the UFCS macro). + static_cast([](auto&& f) -> decltype(auto) { return static_cast(CPP2_UFCS_TEMPLATE(operator())(cpp2::move(f))); }([]() -> void{})); + + // Nesting is not relevant to lookup. + static_cast([]() -> void{static_cast(typename T::value_type{}); }); + static_cast([]() -> void{static_cast([]() -> void{static_cast(typename T::value_type{}); }); }); + static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast(typename T::value_type{}); }); }); }); + static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast(typename T::value_type{}); }); }); }); }); + static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast(typename T::value_type{}); }); }); }); }); + static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast(typename T::value_type{}); }); }); }); }); + static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast(typename T::value_type{}); }); }); }); }); + static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast([]([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void{}); }); }); }); + static_cast([]() -> void{static_cast([]() -> void{static_cast([]([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void{static_cast([]() -> void{}); }); }); }); + static_cast([]() -> void{static_cast([]([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void{static_cast([]() -> void{static_cast([]() -> void{}); }); }); }); + static_cast([]([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void{static_cast([]() -> void{static_cast([]() -> void{static_cast([]() -> void{}); }); }); }); + + // Lookup. + { + using alias = std::integral_constant; + static_cast(alias::value_type{0});// Non-dependent. + } + static_cast([]([[maybe_unused]] T const& unnamed_param_1) -> void{ + using alias = std::integral_constant; + static_cast(typename alias::value_type{0});// Dependent. + { + using alias = std::integral_constant; + static_cast(alias::value_type{0});// Non-dependent. + } + }(0)); + + static_cast([](auto const& r) -> std::type_identity_t { return CPP2_ASSERT_IN_BOUNDS_LITERAL(r, 0); }(std::vector(1))); +} + diff --git a/regression-tests/test-results/pure2-bugfix-for-dependent-types.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-dependent-types.cpp2.output new file mode 100644 index 000000000..6896db60e --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-dependent-types.cpp2.output @@ -0,0 +1,2 @@ +pure2-bugfix-for-dependent-types.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/source/parse.h b/source/parse.h index a8ad4bd9e..036ec5b06 100644 --- a/source/parse.h +++ b/source/parse.h @@ -217,7 +217,7 @@ struct literal_node { if ( !std::exchange(first, false) && p->as_string_view().starts_with("\"") - ) + ) { ret += " "; } @@ -1401,6 +1401,8 @@ struct type_id_node std::unique_ptr constraint = {}; + bool type_only_context = false; + // Out-of-line definition of the dtor is necessary due to the forward-declared // type(s) used in a std::unique_ptr as a member ~type_id_node(); @@ -2426,7 +2428,7 @@ struct parameter_declaration_list_node std::vector> parameters; - parameter_declaration_list_node(bool f = false, bool t = false, bool s = false) + parameter_declaration_list_node(bool f = false, bool t = false, bool s = false) : in_function_typeid{f} , in_template_param_list{t} , in_statement_param_list{s} @@ -2542,7 +2544,7 @@ struct function_type_node assert (parameters); auto ret = parameters->to_string(); - + if (throws) { ret += " throws"; } @@ -5907,7 +5909,7 @@ class parser // // errors error list // - parser( + parser( std::vector& errors_, std::set& includes_ ) @@ -6370,9 +6372,9 @@ class parser // Next should be an expression-list followed by a ')' // If not, then this wasn't a call expression so backtrack to // the '(' which will be part of the next grammar production - is_inside_call_expr = true; + is_inside_call_expr = true; term.expr_list = expression_list(term.op, lexeme::RightParen); - is_inside_call_expr = false; + is_inside_call_expr = false; if ( term.expr_list @@ -6401,7 +6403,7 @@ class parser } } else if ( - ( + ( term.op->type() == lexeme::EllipsisLess || term.op->type() == lexeme::EllipsisEqual ) @@ -6958,11 +6960,13 @@ class parser auto type_id( bool allow_omitting_type_name = false, bool allow_constraint = false, - bool allow_function_type = false + bool allow_function_type = false, + bool type_only_context = true ) -> std::unique_ptr { auto n = std::make_unique(); + n->type_only_context = type_only_context; // Remember current position, because we need to look ahead auto start_pos = pos; @@ -7226,7 +7230,7 @@ class parser } // Else try parsing it as a type id - else if (auto i = type_id(false, false, true)) { + else if (auto i = type_id(false, false, true, false)) { term.arg = std::move(i); } @@ -7321,7 +7325,7 @@ class parser n->ids.push_back( std::move(term) ); - for ( + for ( auto first_time_through_loop = true; curr().type() == lexeme::Scope; first_time_through_loop = false @@ -7340,7 +7344,7 @@ class parser && first_uid_was_std && term.scope_op->type() == lexeme::Scope && *term.id->identifier == "forward" - ) + ) { error("std::forward is not needed in Cpp2 - use 'forward' parameters/arguments instead", false); return {}; @@ -7993,7 +7997,7 @@ class parser } if ( - peek(1) + peek(1) && *peek(1) == "namespace" ) { diff --git a/source/to_cpp1.h b/source/to_cpp1.h index 4a891ad1d..57f69d9c3 100644 --- a/source/to_cpp1.h +++ b/source/to_cpp1.h @@ -1255,7 +1255,7 @@ class cppfront // Now we'll open the Cpp1 file auto cpp1_filename = sourcefile.substr(0, std::ssize(sourcefile) - 1); - + // Use explicit filename override if present, // otherwise strip leading path if (!flag_cpp1_filename.empty()) { @@ -1721,7 +1721,8 @@ class cppfront int synthesized_multi_return_size = 0, bool is_local_name = true, bool is_qualified = false, - bool is_class_member_access = false + bool is_class_member_access = false, + bool is_dependent = false ) -> void { STACKINSTR @@ -1812,6 +1813,10 @@ class cppfront printer.print_cpp2("*this).", n.position()); } + if (is_dependent && n.open_angle != source_position{}) { + printer.print_cpp2("template ", n.position()); + } + assert(n.identifier); emit(*n.identifier, is_qualified); // inform the identifier if we know this is qualified @@ -1878,11 +1883,327 @@ class cppfront } + auto has_template_parameter_named(declaration_node const& decl, token const& id) + -> bool + { + if (auto& params = decl.template_parameters) { + return std::any_of(params->parameters.begin(), params->parameters.end(), [&](auto& param) { + assert(param->has_name()); + return *param->name() == id; + }); + } + return false; + } + + auto is_template_parameter(token const& lookup_id) + -> bool + { + // If any parent declaration + return std::any_of(current_declarations.begin() + 1, current_declarations.end(), [&](auto& decl) { + return has_template_parameter_named(*decl, lookup_id); + }); + } + + + // Consider moving these `stack` functions to `common.h` to enable more general use. + + template + auto stack_value( + T& var, + std::type_identity_t const& value + ) + -> auto + { + return finally([&var, old = std::exchange(var, value)]() { + var = old; + }); + } + + template + auto stack_element( + std::vector& cont, + std::type_identity_t const& value + ) + -> auto + { + cont.push_back(value); + return finally([&]{ cont.pop_back(); }); + } + + template + auto stack_size(std::vector& cont) + -> auto + { + return finally([&, size = cont.size()]{ cont.resize(size); }); + } + + template + auto stack_size_if( + std::vector& cont, + bool cond + ) + -> std::optional + { + if (cond) { + return stack_size(cont); + } + return {}; + } + + + std::vector looking_up = {}; + + auto is_dependent_type_alias(token const& lookup_id) + -> bool + { + // Prevent recursion. + if (contains(looking_up, &lookup_id)) { + return false; + } + auto guard = stack_element(looking_up, &lookup_id); + + auto lookup = source_order_name_lookup(lookup_id); + + if ( + !lookup + || get_if(&*lookup) + ) + { + return false; + } + + auto decl = get(*lookup); + if (!decl->is_alias()) { + return false; + } + + auto& alias = get(decl->type); + + if ( + !alias->is_type_alias() + || !decl->identifier + || *decl->identifier->identifier != lookup_id + ) + { + return false; + } + + auto& type_id = get(alias->initializer); + return is_dependent(*type_id); + } + + auto is_dependent(const type_id_node& n) + -> bool + { + if (auto qual = get_if(&n.id)) { + return is_dependent(**qual); + } + else if (auto unqual = get_if(&n.id)) { + return is_dependent(**unqual); + } + return false; + } + + auto is_dependent(const id_expression_node& n) + -> bool + { + if (auto qual = get_if(&n.id)) { + return is_dependent(**qual); + } + else if (auto unqual = get_if(&n.id)) { + return is_dependent(**unqual); + } + return false; + } + + struct is_dependent_expression_visitor { + cppfront* self; + + auto operator()(expression_node const& expr) const + -> bool + { + return (*this)(*expr.expr); + } + + template + auto operator()(binary_expression_node const& expr) const + -> bool + { + return (*this)(*expr.expr) || std::any_of(expr.terms.begin(), expr.terms.end(), *this); + } + + template + auto operator()(BinaryExpressionTerm const& term) const + -> bool + requires requires { term.op; term.expr; } + { + return (*this)(*term.expr); + } + + auto operator()(is_as_expression_node const& expr) const + -> bool + { + return (*this)(*expr.expr) || std::any_of(expr.ops.begin(), expr.ops.end(), *this); + } + + auto operator()(is_as_expression_node::term const& expr) const + -> bool + { + if (expr.expr) { + return (*this)(*expr.expr); + } + return self->is_dependent(*expr.type); + } + + auto operator()(prefix_expression_node const& expr) const + -> bool + { + return (*this)(*expr.expr); + } + + auto operator()(postfix_expression_node const& expr) const + -> bool + { + return (*this)(*expr.expr) || std::any_of(expr.ops.begin(), expr.ops.end(), *this); + } + + auto operator()(postfix_expression_node::term const& expr) const + -> bool + { + if (expr.id_expr) { + return (*this)(*expr.id_expr); + } else if (expr.expr_list) { + return (*this)(*expr.expr_list); + } + return false; + } + + auto operator()(primary_expression_node const& expr) const + -> bool + { + return std::visit([&](T const& expr) { + if constexpr (std::is_same_v + || std::is_same_v + || std::is_same_v>) { + return false; + } else { + return (*this)(*expr); + } + }, expr.expr); + } + + auto operator()(expression_list_node const& expr) const + -> bool + { + return std::any_of(expr.expressions.begin(), expr.expressions.end(), *this); + } + + auto operator()(expression_list_node::term const& term) const + -> bool + { + return (*this)(*term.expr); + } + + auto operator()(id_expression_node const& expr) const + -> bool + { + return std::visit([&](T const& expr) { + if constexpr (std::is_same_v) { + return false; + } else { + return self->is_dependent(*expr); + } + }, expr.id); + } + + auto operator()(declaration_node const& n) const + -> bool + { + auto type_id = std::get_if(&n.type); + return + ( + ( + !type_id + || (*type_id)->is_wildcard() + ) + && (*this)(*n.initializer) + ) + || ( + type_id + && self->is_dependent(**type_id) + ); + } + + auto operator()(statement_node const&) const + -> bool + { + // TODO: Implement checking whether an initializer is dependent. + return false; + } + + auto operator()(inspect_expression_node const& expr) const + -> bool + { + return self->is_dependent(*expr.result_type); + } + }; + + auto is_dependent( + const unqualified_id_node& n, + bool is_first = true) + -> bool + { + if (is_first && n.open_angle == source_position{}) { + assert(n.identifier); + return is_template_parameter(*n.identifier) + || is_dependent_type_alias(*n.identifier); + } + // If it's a _template-id_ + if (n.open_angle != source_position{}) { + assert(n.identifier); + // and any of its template arguments is either + return std::any_of(n.template_args.begin(), n.template_args.end(), [&](template_argument const& arg) { + if (auto expr = get_if(&arg.arg)) { + // a dependent _expression_ + return is_dependent_expression_visitor{this}(**expr); + } + else if (auto type_id = get_if(&arg.arg)) { + // or a dependent _type-id_. + return is_dependent(**type_id); + } + return false; + }); + } + return false; + } + + auto is_dependent(const qualified_id_node& n, bool exclude_last = false) + -> bool + { + if (is_dependent(*n.ids[0].id, true)) { + return true; + } + return std::any_of( + n.ids.begin() + 1, + n.ids.end() - int{exclude_last && n.ids.size() > 1}, + [&](auto& id) { + return is_dependent(*id.id, false); + } + ); + } + + auto should_be_prefixed_with_typename(const qualified_id_node& n) + -> bool + { + return is_dependent(n, true); + } + //----------------------------------------------------------------------- // auto emit( qualified_id_node const& n, - bool include_unqualified_id = true + bool include_unqualified_id = true, + bool requires_typename_keyword = false ) -> void { STACKINSTR @@ -1893,12 +2214,20 @@ class cppfront auto ident = std::string{}; printer.emit_to_string(&ident); + if (requires_typename_keyword && should_be_prefixed_with_typename(n)) { + printer.print_cpp2("typename ", n.position()); + } + + auto is_dependent = false; for (auto const& id : std::span{n.ids}.first(n.ids.size() - !include_unqualified_id)) { if (id.scope_op) { emit(*id.scope_op); } - emit(*id.id, 0, true, true); // inform the unqualified-id that it's qualified + emit(*id.id, 0, true, true, false, is_dependent); // inform the unqualified-id that it's qualified + if (!is_dependent && this->is_dependent(*id.id)) { + is_dependent = true; + } } printer.emit_to_string(); @@ -1910,8 +2239,9 @@ class cppfront // auto emit( type_id_node const& n, - source_position pos = {}, - std::string identifier = {} + bool in_cpp1_type_only_context = false, + source_position pos = {}, + std::string identifier = {} ) -> void { STACKINSTR @@ -1941,7 +2271,7 @@ class cppfront else { try_emit(n.id); try_emit(n.id, 0, false); - try_emit(n.id); + try_emit(n.id, true, !in_cpp1_type_only_context && n.type_only_context); try_emit(n.id); } @@ -3458,12 +3788,12 @@ class cppfront last_was_prefixed = true; } - // Handle the other Cpp2 postfix operators that stay postfix in Cpp1 + // Handle the other Cpp2 postfix operators that stay postfix in Cpp1 // (currently '...' for expansion, not when used as a range operator) else if ( is_postfix_operator(i->op->type()) && !i->last_expr // not being used as a range operator - ) + ) { flush_args(); suffix.emplace_back( i->op->to_string(), i->op->position()); @@ -3504,7 +3834,7 @@ class cppfront } auto print = print_to_string( - *i->id_expr, + *i->id_expr, false, // not a local name i->op->type() == lexeme::Dot || i->op->type() == lexeme::DotDot // member access ); @@ -4249,51 +4579,6 @@ class cppfront } - // Consider moving these `stack` functions to `common.h` to enable more general use. - - template - auto stack_value( - T& var, - std::type_identity_t const& value - ) - -> auto - { - return finally([&var, old = std::exchange(var, value)]() { - var = old; - }); - } - - template - auto stack_element( - std::vector& cont, - std::type_identity_t const& value - ) - -> auto - { - cont.push_back(value); - return finally([&]{ cont.pop_back(); }); - } - - template - auto stack_size(std::vector& cont) - -> auto - { - return finally([&, size = cont.size()]{ cont.resize(size); }); - } - - template - auto stack_size_if( - std::vector& cont, - bool cond - ) - -> std::optional - { - if (cond) { - return stack_size(cont); - } - return {}; - } - //----------------------------------------------------------------------- // auto emit( @@ -4403,6 +4688,84 @@ class cppfront } + auto is_deducible_template_parameter(token const& lookup_id) + -> bool + { + assert( current_declarations.back() ); + return has_template_parameter_named(*current_declarations.back(), lookup_id) + || ( + current_declarations.back()->parent_is_type() + && current_declarations.back()->has_name("operator=") + && has_template_parameter_named(*current_declarations.back()->get_parent(), lookup_id) + ); + } + + auto is_deducible(const type_id_node& n) + -> bool + { + if (auto qual = get_if(&n.id)) { + return is_deducible(**qual); + } + else if (auto unqual = get_if(&n.id)) { + return is_deducible(**unqual); + } + return false; + } + + auto is_deducible(const id_expression_node& n) + -> bool + { + if (auto qual = get_if(&n.id)) { + return is_deducible(**qual); + } + else if (auto unqual = get_if(&n.id)) { + return is_deducible(**unqual); + } + return false; + } + + auto is_deducible( + const unqualified_id_node& n, + bool is_first = true) + -> bool + { + // If it's not a _template-id_ + if (is_first && n.open_angle == source_position{}) { + assert(n.identifier); + // and it names a deducible template parameter. + return is_deducible_template_parameter(*n.identifier); + } + // If it's a _template-id_ + if (n.open_angle != source_position{}) { + assert(n.identifier); + // and any of its template arguments is either + return std::any_of(n.template_args.begin(), n.template_args.end(), [&](template_argument const& arg) { + if (auto expr = get_if(&arg.arg); + expr && (*expr)->is_id_expression()) { + // a deducible _id-expression_ + auto& pid = (*expr)->expr->expr->expr->expr->expr->expr->expr->expr->expr->expr->expr->expr->expr->expr->expr->expr->expr; + if (auto id = get_if(&pid)) { + return is_deducible(**id); + } + } + else if (auto type_id = get_if(&arg.arg)) { + // or a deducible _type-id_. + return is_deducible(**type_id); + } + return false; + }); + } + return false; + } + + auto is_deducible(const qualified_id_node& n) + -> bool + { + assert(n.ids.size() > 0); + // Its non-terminal components are non-dependent and + // its terminal component is deducible. + return !is_dependent(n, true) && is_deducible(*n.ids.back().id); + } //----------------------------------------------------------------------- // auto emit( @@ -4453,8 +4816,8 @@ class cppfront { assert(n.declaration); auto is_param_to_namespace_scope_type = - n.declaration->parent_is_type() - && n.declaration->parent_declaration->parent_is_namespace() + n.declaration->parent_is_type() + && n.declaration->parent_declaration->parent_is_namespace() ; auto emit_in_phase_0 = @@ -4591,7 +4954,7 @@ class cppfront auto const& type_id = *std::get(n.declaration->type); if (is_template_parameter) { - emit( type_id ); + emit( type_id, true ); printer.print_cpp2(" ", type_id.position()); emit_template_name(); @@ -4603,52 +4966,9 @@ class cppfront // Else handle ordinary parameters assert(n.declaration->identifier); - auto param_type = print_to_string(type_id, source_position{}, print_to_string(*n.declaration->identifier)); - - // If there are template parameters on this function or its enclosing - // type, see if this parameter's name is an unqualified-id with a - // template parameter name, or mentions a template parameter as a - // template argument - auto has_template_parameter_type_named = []( - declaration_node const& decl, - std::string_view name - ) - -> bool - { - if (decl.template_parameters) { - for (auto& tparam : decl.template_parameters->parameters) - { - assert( - tparam - && tparam->name() - ); - // For now just do a quick string match - auto tparam_name = tparam->name()->to_string(); - if ( - tparam->declaration->is_type() - && ( - name == tparam_name - || name.find("<"+tparam_name) != name.npos - || name.find(","+tparam_name) != name.npos - ) - ) - { - return true; - } - } - } - return false; - }; + auto param_type = print_to_string(type_id, false, source_position{}, print_to_string(*n.declaration->identifier)); - assert( current_declarations.back() ); - auto is_dependent_parameter_type = - has_template_parameter_type_named( *current_declarations.back(), param_type ) - || ( - current_declarations.back()->parent_is_type() - && current_declarations.back()->has_name("operator=") - && has_template_parameter_type_named( *current_declarations.back()->get_parent(), param_type) - ) - ; + auto is_deducible_parameter_type = is_deducible(type_id); // First any prefix @@ -4661,7 +4981,7 @@ class cppfront !is_returns && !n.declaration->is_variadic && !type_id.is_wildcard() - && !is_dependent_parameter_type + && !is_deducible_parameter_type && !type_id.is_pointer_qualified() ) { @@ -4683,12 +5003,12 @@ class cppfront } else if ( type_id.is_wildcard() - || is_dependent_parameter_type + || is_deducible_parameter_type || n.declaration->is_variadic ) { auto name = std::string{"auto"}; - if (is_dependent_parameter_type) { + if (is_deducible_parameter_type) { name = param_type; } else if ( @@ -4739,6 +5059,11 @@ class cppfront auto req = std::string{"std::is_convertible_vis_variadic ) @@ -5001,7 +5326,7 @@ class cppfront auto& r = std::get(n.returns); assert(r.type); - auto return_type = print_to_string(*r.type); + auto return_type = print_to_string(*r.type, true); if ( r.pass == passing_style::forward @@ -5091,7 +5416,7 @@ class cppfront || n.is_swap() || n.is_destructor() || ( - n.my_decl + n.my_decl && generating_move_from == n.my_decl ) ) @@ -5105,7 +5430,7 @@ class cppfront if ( n.is_assignment() || ( - n.my_decl + n.my_decl && generating_assignment_from == n.my_decl ) ) @@ -5572,6 +5897,9 @@ class cppfront + (*object)->name()->to_string() + "_as_base"; } + else if (object_name.starts_with("typename ")) { + object_name = std::string_view{object_name.begin() + 9, object_name.end()}; + } // Flush any 'out' parameter initializations auto out_inits_with_commas = [&]() -> std::string { @@ -5842,7 +6170,7 @@ class cppfront "using " + print_to_string(*n.identifier) + " = " - + print_to_string( *std::get(a->initializer) ) + + print_to_string( *std::get(a->initializer), true ) + ";\n", n.position() ); @@ -6259,7 +6587,7 @@ class cppfront if (decl->has_name("this")) { if (printer.get_phase() == printer.phase1_type_defs_func_decls) { printer.print_cpp2( - separator + " public " + print_to_string(*decl->get_object_type()), + separator + " public " + print_to_string(*decl->get_object_type(), true), compound_stmt->position() ); separator = ","; @@ -6975,10 +7303,10 @@ class cppfront assert(n.initializer); // And the constraint if there is one if (type->constraint) { - emit( *type->constraint, n.position() ); + emit( *type->constraint, false, n.position() ); printer.print_cpp2(" ", n.position()); } - emit( *type, n.position() ); + emit( *type, false, n.position() ); } // Otherwise, emit the type else { @@ -6995,8 +7323,8 @@ class cppfront return; } } - printer.preempt_position_push(n.position()); - emit( *type, {}, print_to_string(*n.identifier) ); + printer.preempt_position_push(n.position()); + emit( *type, n.parent_is_type(), {}, print_to_string(*n.identifier) ); printer.preempt_position_pop(); if (