From 12ad062b135adc38efe0c528e9c6218bc1f810e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johel=20Ernesto=20Guerrero=20Pe=C3=B1a?= Date: Tue, 1 Oct 2024 19:03:22 -0400 Subject: [PATCH] feat: allow template specialization The syntax is `specialize` after the optional template parameter list. Some examples from the tests: ```Cpp2 std: namespace = { common_type: @struct @print specialize type = { type: type == outer; } } v: const i32 = 1; v: <> specialize const i32 = 2; v: specialize const i32 = 3; v: specialize<* T> std::optional == 6; ``` --- regression-tests/pure2-print.cpp2 | 9 ++ .../pure2-template-specialization.cpp2 | 38 ++++++++ regression-tests/test-results/pure2-print.cpp | 27 ++++++ .../test-results/pure2-print.cpp2.output | 6 ++ .../pure2-template-specialization.cpp | 76 ++++++++++++++++ .../pure2-template-specialization.cpp2.output | 2 + source/parse.h | 87 ++++++++++++++++--- source/to_cpp1.h | 77 ++++++++++++---- 8 files changed, 293 insertions(+), 29 deletions(-) create mode 100644 regression-tests/pure2-template-specialization.cpp2 create mode 100644 regression-tests/test-results/pure2-template-specialization.cpp create mode 100644 regression-tests/test-results/pure2-template-specialization.cpp2.output diff --git a/regression-tests/pure2-print.cpp2 b/regression-tests/pure2-print.cpp2 index c757e87ef..fda653d99 100644 --- a/regression-tests/pure2-print.cpp2 +++ b/regression-tests/pure2-print.cpp2 @@ -102,6 +102,15 @@ outer: @print type = { } +std: namespace = { + common_type: @struct @print specialize type = { + type: type == outer; + } + numbers: namespace = { + pi_v: /*@print*/ specialize const double = pi_v; + } +} + main: () = { outer::test(); } diff --git a/regression-tests/pure2-template-specialization.cpp2 b/regression-tests/pure2-template-specialization.cpp2 new file mode 100644 index 000000000..1803ae398 --- /dev/null +++ b/regression-tests/pure2-template-specialization.cpp2 @@ -0,0 +1,38 @@ +t: @cpp1_rule_of_zero type = { + public a: i32 = 1; +} +t: @cpp1_rule_of_zero specialize type requires std::is_void_v = { + public b: i32 = 2; +} +t: @cpp1_rule_of_zero specialize type = { + public c: i32 = 3; +} +t: @cpp1_rule_of_zero specialize<* i8> type = { + public f: () 17; + public v: int == 29; +} +t: @cpp1_rule_of_zero specialize<* T> type = { + public v: int == 17; + public f: () 29; +} +v: const i32 = 1; +v: <> specialize const i32 = 2; +v: specialize const i32 = 3; +v: specialize std::optional == 4; +v: <> specialize std::optional == 5; +v: specialize<* T> std::optional == 6; +main: () = { + assert(t().a == 1); + assert(t().b == 2); + assert(t().c == 3); + assert(t<* i8>::f() == 17); + assert(t<* i8>::v == 29); + assert(t<* i16>::v == 17); + assert(t<* i16>::f() == 29); + assert(v == 1); + assert(v == 2); + assert(v == 3); + static_assert(v == 4); + static_assert(v == 5); + static_assert(v<* int> == 6); +} diff --git a/regression-tests/test-results/pure2-print.cpp b/regression-tests/test-results/pure2-print.cpp index 74103903e..9fa74bce2 100644 --- a/regression-tests/test-results/pure2-print.cpp +++ b/regression-tests/test-results/pure2-print.cpp @@ -11,6 +11,15 @@ #line 6 "pure2-print.cpp2" class outer; +#line 105 "pure2-print.cpp2" +namespace std { + +#line 109 "pure2-print.cpp2" + namespace numbers { + + } +} + //=== Cpp2 type definitions and function declarations =========================== @@ -78,6 +87,15 @@ CPP2_REQUIRES_ (cpp2::impl::cmp_greater_eq(sizeof...(Args),0u)) ; #line 103 "pure2-print.cpp2" }; +namespace std { + template<> class common_type { + public: using type = outer; + }; + namespace numbers { + /*@print*/ + } +} + auto main() -> int; //=== Cpp2 function definitions ================================================= @@ -200,6 +218,15 @@ requires (cpp2::impl::cmp_greater_eq(sizeof...(Args),0u)) { return (... && args); } #line 105 "pure2-print.cpp2" +namespace std { + +#line 109 "pure2-print.cpp2" + namespace numbers { + template<> double const pi_v {pi_v}; + } +} + +#line 114 "pure2-print.cpp2" auto main() -> int{ outer::test(); } diff --git a/regression-tests/test-results/pure2-print.cpp2.output b/regression-tests/test-results/pure2-print.cpp2.output index d523e2f54..25c4374de 100644 --- a/regression-tests/test-results/pure2-print.cpp2.output +++ b/regression-tests/test-results/pure2-print.cpp2.output @@ -147,5 +147,11 @@ outer:/* @print */ type = all: (in args...: Args, ) -> move bool = (... && args); } + + +common_type:/* @struct @print */ specialize type = +{ + public type: type == outer; +} ok (all Cpp2, passes safety checks) diff --git a/regression-tests/test-results/pure2-template-specialization.cpp b/regression-tests/test-results/pure2-template-specialization.cpp new file mode 100644 index 000000000..af06a6c6a --- /dev/null +++ b/regression-tests/test-results/pure2-template-specialization.cpp @@ -0,0 +1,76 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-template-specialization.cpp2" +template class t; +#line 2 "pure2-template-specialization.cpp2" + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-template-specialization.cpp2" +template class t { +#line 2 "pure2-template-specialization.cpp2" + public: cpp2::i32 a {1}; +}; +template requires( std::is_void_v ) + class t {public: cpp2::i32 b {2}; +}; +template<> class t { + public: cpp2::i32 c {3}; +}; +template<> class t { + public: [[nodiscard]] static auto f() -> auto; + public: static const int v; +}; +template class t { + public: static const int v; + public: [[nodiscard]] static auto f() -> auto; +}; +template extern cpp2::i32 const v; + +#line 21 "pure2-template-specialization.cpp2" +template<> std::optional inline constexpr v{ 4 }; +template<> std::optional inline constexpr v{ 5 }; +template std::optional inline constexpr v{ 6 }; +auto main() -> int; + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-template-specialization.cpp2" + +#line 11 "pure2-template-specialization.cpp2" + [[nodiscard]] auto t::f() -> auto { return 17; } + inline CPP2_CONSTEXPR int t::v{ 29 }; + +#line 15 "pure2-template-specialization.cpp2" + template inline CPP2_CONSTEXPR int t::v{ 17 }; + template [[nodiscard]] auto t::f() -> auto { return 29; } + +#line 18 "pure2-template-specialization.cpp2" +template cpp2::i32 const v {1}; +template<> cpp2::i32 const v {2}; +template<> cpp2::i32 const v {3}; + +#line 24 "pure2-template-specialization.cpp2" +auto main() -> int{ + if (cpp2::cpp2_default.is_active() && !(t().a == 1) ) { cpp2::cpp2_default.report_violation(""); } + if (cpp2::cpp2_default.is_active() && !(t().b == 2) ) { cpp2::cpp2_default.report_violation(""); } + if (cpp2::cpp2_default.is_active() && !(t().c == 3) ) { cpp2::cpp2_default.report_violation(""); } + if (cpp2::cpp2_default.is_active() && !(t::f() == 17) ) { cpp2::cpp2_default.report_violation(""); } + if (cpp2::cpp2_default.is_active() && !(t::v == 29) ) { cpp2::cpp2_default.report_violation(""); } + if (cpp2::cpp2_default.is_active() && !(t::v == 17) ) { cpp2::cpp2_default.report_violation(""); } + if (cpp2::cpp2_default.is_active() && !(t::f() == 29) ) { cpp2::cpp2_default.report_violation(""); } + if (cpp2::cpp2_default.is_active() && !(v == 1) ) { cpp2::cpp2_default.report_violation(""); } + if (cpp2::cpp2_default.is_active() && !(v == 2) ) { cpp2::cpp2_default.report_violation(""); } + if (cpp2::cpp2_default.is_active() && !(v == 3) ) { cpp2::cpp2_default.report_violation(""); } + static_assert(v == 4); + static_assert(v == 5); + static_assert(v == 6); +} + diff --git a/regression-tests/test-results/pure2-template-specialization.cpp2.output b/regression-tests/test-results/pure2-template-specialization.cpp2.output new file mode 100644 index 000000000..550585ef6 --- /dev/null +++ b/regression-tests/test-results/pure2-template-specialization.cpp2.output @@ -0,0 +1,2 @@ +pure2-template-specialization.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/source/parse.h b/source/parse.h index 44d45c9d6..b3ea4b5d3 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 += " "; } @@ -2386,7 +2386,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} @@ -2502,7 +2502,7 @@ struct function_type_node assert (parameters); auto ret = parameters->to_string(); - + if (throws) { ret += " throws"; } @@ -2943,6 +2943,7 @@ struct declaration_node std::vector> metafunctions; std::unique_ptr template_parameters; + std::unique_ptr specialization_template_arguments; source_position requires_pos = {}; std::unique_ptr requires_clause_expression; @@ -3282,6 +3283,8 @@ struct declaration_node auto is_function_expression () const -> bool { return is_function() && !identifier; } + auto is_specialization() const -> bool + { return specialization_template_arguments != nullptr; } auto is_polymorphic() const // has base types or virtual functions -> bool @@ -5533,6 +5536,11 @@ auto pretty_print_visualize(declaration_node const& n, int indent, bool include_ template_params += " " + pretty_print_visualize(*n.template_parameters, indent + 1, true); } + auto specialization_args = std::string{}; + if (n.specialization_template_arguments) { + specialization_args += " " + pretty_print_visualize(*n.specialization_template_arguments, indent + 1); + } + auto requires_clause = std::string{}; if (n.requires_clause_expression) { requires_clause += " requires (" + pretty_print_visualize(*n.requires_clause_expression, indent) + ")"; @@ -5621,6 +5629,7 @@ auto pretty_print_visualize(declaration_node const& n, int indent, bool include_ assert(type_id); ret += metafunctions + template_params + + specialization_args + " " + pretty_print_visualize(*type_id, indent) + requires_clause + initializer; @@ -5630,6 +5639,7 @@ auto pretty_print_visualize(declaration_node const& n, int indent, bool include_ assert(t); ret += metafunctions + template_params + + specialization_args + " " + pretty_print_visualize(*t) + initializer; } @@ -5648,7 +5658,8 @@ auto pretty_print_visualize(declaration_node const& n, int indent, bool include_ object_type_id += " " + pretty_print_visualize(*a->type_id, indent); } - ret += template_params; + ret += template_params + + specialization_args; if (a->is_type_alias()) { auto& t = std::get(a->initializer); ret += " type" @@ -5833,7 +5844,7 @@ class parser // // errors error list // - parser( + parser( std::vector& errors_, std::set& includes_ ) @@ -6296,9 +6307,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 @@ -6327,7 +6338,7 @@ class parser } } else if ( - ( + ( term.op->type() == lexeme::EllipsisLess || term.op->type() == lexeme::EllipsisEqual ) @@ -7050,6 +7061,7 @@ class parser //G //G template-arguments: //G template-arguments ',' template-argument + //G template-argument //G //G template-argument: //G # note: < > << >> are not allowed in expressions until new ( is opened @@ -7211,7 +7223,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 @@ -7230,7 +7242,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 {}; @@ -7887,7 +7899,7 @@ class parser } if ( - peek(1) + peek(1) && *peek(1) == "namespace" ) { @@ -8691,9 +8703,9 @@ class parser //G unnamed-declaration: //G ':' meta-functions? template-parameters? function-type requires-clause? '=' statement //G ':' meta-functions? template-parameters? function-type statement - //G ':' meta-functions? template-parameters? type-id? requires-clause? '=' statement + //G ':' meta-functions? template-parameters? specialization-arguments? type-id? requires-clause? '=' statement //G ':' meta-functions? template-parameters? type-id - //G ':' meta-functions? template-parameters? 'final'? 'type' requires-clause? '=' statement + //G ':' meta-functions? template-parameters? specialization-arguments? 'final'? 'type' requires-clause? '=' statement //G ':' 'namespace' '=' statement //G //G meta-functions: @@ -8707,6 +8719,9 @@ class parser //G template-parameters: //G '<' parameter-declaration-seq '>' //G + //G specialization-arguments: + //G 'specialize' '<' template-arguments '>' + //G auto unnamed_declaration( source_position start, bool semicolon_required = true, @@ -8860,6 +8875,21 @@ class parser n->template_parameters = std::move(template_parameters); } + // Next is an optional template specialization argument list + if ( + curr() == "specialize" + && peek(1) + && peek(1)->type() == lexeme::Less + ) + { + auto specialization_template_arguments = unqualified_id(); + if (!specialization_template_arguments) { + error("invalid template specialization argument list"); + return {}; + } + n->specialization_template_arguments = std::move(specialization_template_arguments); + } + // Next is an an optional type auto deduced_type = false; @@ -9273,7 +9303,7 @@ class parser //G alias: //G ':' template-parameters? 'type' requires-clause? '==' type-id ';' //G ':' 'namespace' '==' id-expression ';' - //G ':' template-parameters? type-id? requires-clause? '==' expression ';' + //G ':' template-parameters? specialization-arguments? type-id? requires-clause? '==' expression ';' //G //GT ':' function-type '==' expression ';' //GT # See commit 63efa6ed21c4d4f4f136a7a73e9f6b2c110c81d7 comment @@ -9302,12 +9332,34 @@ class parser n->template_parameters = std::move(template_parameters); } + // Next is an optional template specialization argument list + if ( + curr() == "specialize" + && peek(1) + && peek(1)->type() == lexeme::Less + ) + { + auto specialization_template_arguments = unqualified_id(); + if (!specialization_template_arguments) { + pos = start_pos; // backtrack + return {}; + } + n->specialization_template_arguments = std::move(specialization_template_arguments); + } + auto a = std::make_unique( &curr() ); // Next must be 'type', 'namespace', a type-id, or we're at the 'requires' or '==' if (curr() == "type") { next(); + if (n->specialization_template_arguments) { + errors.emplace_back( + curr().position(), + "a type alias cannot be specialized" + ); + return {}; + } } else if (curr() == "namespace") { @@ -9319,6 +9371,13 @@ class parser ); return {}; } + if (n->specialization_template_arguments) { + errors.emplace_back( + curr().position(), + "a namespace alias cannot be specialized" + ); + return {}; + } } else if (curr().type() != lexeme::EqualComparison && curr() != "requires") { diff --git a/source/to_cpp1.h b/source/to_cpp1.h index 00ac1cdac..dd22927ce 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 emit_identifier = true ) -> void { STACKINSTR @@ -1813,7 +1814,9 @@ class cppfront } assert(n.identifier); - emit(*n.identifier, is_qualified); // inform the identifier if we know this is qualified + if (emit_identifier) { + emit(*n.identifier, is_qualified); // inform the identifier if we know this is qualified + } if (n.open_angle != source_position{}) { printer.print_cpp2("<", n.open_angle); @@ -3428,12 +3431,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()); @@ -3474,7 +3477,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 ); @@ -4406,8 +4409,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 = @@ -5033,7 +5036,7 @@ class cppfront || n.is_swap() || n.is_destructor() || ( - n.my_decl + n.my_decl && generating_move_from == n.my_decl ) ) @@ -5047,7 +5050,7 @@ class cppfront if ( n.is_assignment() || ( - n.my_decl + n.my_decl && generating_assignment_from == n.my_decl ) ) @@ -5216,7 +5219,10 @@ class cppfront ) { auto list = std::string{""}; - if (parent->template_parameters) { + if (parent->specialization_template_arguments) { + list = print_to_string(*parent->specialization_template_arguments, 0, false, false, false, false); + } + else if (parent->template_parameters) { auto separator = std::string{"<"}; for (auto& tparam : parent->template_parameters->parameters) { assert (tparam->has_name()); @@ -5696,6 +5702,18 @@ class cppfront return; } + // Do not forward declare specializations. + if ( + n.is_specialization() + && ( + (n.is_type() && printer.get_phase() == printer.phase0_type_decls) + || (n.is_object() && printer.get_phase() == printer.phase1_type_defs_func_decls) + ) + ) + { + return; + } + // If this is a generated declaration (negative source line number), // add a line break before if ( @@ -5773,6 +5791,8 @@ class cppfront printer.print_cpp2("template", n.position()); emit(*n.template_parameters, false, true); printer.print_cpp2(" ", n.position()); + } else if (n.is_specialization()) { + printer.print_cpp2("template<> ", n.position()); } // Emit requires clause if any @@ -5835,6 +5855,13 @@ class cppfront if (n.parent_is_type()) { assert (n.parent_declaration->name()); + if (n.specialization_template_arguments) { + errors.emplace_back( + n.position(), + "(temporary alpha limitation) an object alias in type scope cannot be specialized" + ); + return; + } if (printer.get_phase() == printer.phase1_type_defs_func_decls) { printer.print_cpp2( @@ -5878,10 +5905,16 @@ class cppfront intro = "inline constexpr"; } + auto specialization_template_arguments = std::string{}; + if (n.specialization_template_arguments) { + specialization_template_arguments = print_to_string(*n.specialization_template_arguments, 0, false, false, false, false); + } + printer.print_cpp2( type + " " + intro + " " + print_to_string(*n.identifier) + + specialization_template_arguments + print_initializer_to_string( *std::get(a->initializer) ) + ";\n", n.position() @@ -6056,7 +6089,10 @@ class cppfront // Now, emit our own template parameters if ( - n.template_parameters + ( + n.template_parameters + || n.is_specialization() + ) && ( printer.get_phase() < printer.phase2_func_defs || n.is_object() @@ -6074,7 +6110,12 @@ class cppfront ) { printer.print_cpp2("template", n.position()); - emit(*n.template_parameters, false, true); + if (n.template_parameters) { + emit(*n.template_parameters, false, true); + } else { + assert(n.is_specialization()); + printer.print_cpp2("<>", n.position()); + } printer.print_cpp2(" ", n.position()); } @@ -6097,6 +6138,9 @@ class cppfront printer.print_cpp2("class ", n.position()); emit(*n.identifier); + if (n.specialization_template_arguments) { + emit(*n.specialization_template_arguments, 0, false, false, false, false); + } // Type declaration if (printer.get_phase() == printer.phase0_type_decls) { @@ -6935,8 +6979,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, {}, print_to_string(*n.identifier) ); printer.preempt_position_pop(); if ( @@ -6985,6 +7029,9 @@ class cppfront } else if (!n.is_object_with_function_typeid()) { emit(*n.identifier); + if (n.specialization_template_arguments) { + emit(*n.specialization_template_arguments, 0, false, false, false, false); + } } if (