From 6c32f2ea8e774b5f9ddf2f7d3a56e6cee4fd8c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johel=20Ernesto=20Guerrero=20Pe=C3=B1a?= Date: Mon, 8 Jan 2024 12:18:23 -0400 Subject: [PATCH 1/4] refactor: rename `expression_list_node::expressions` to `arguments` --- source/parse.h | 22 +++++++++++----------- source/to_cpp1.h | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/source/parse.h b/source/parse.h index 44d45c9d6..016a1dbd8 100644 --- a/source/parse.h +++ b/source/parse.h @@ -713,7 +713,7 @@ struct expression_list_node v.end(*this, depth); } }; - std::vector< term > expressions; + std::vector< term > arguments; // API @@ -721,7 +721,7 @@ struct expression_list_node auto is_empty() const -> bool { - return expressions.empty(); + return arguments.empty(); } auto is_fold_expression() const @@ -730,7 +730,7 @@ struct expression_list_node // This is a fold-expression if any subexpression // has an identifier named "..." auto ret = false; - for (auto& x : expressions) { + for (auto& x : arguments) { ret |= x.expr->is_fold_expression(); } return ret; @@ -745,7 +745,7 @@ struct expression_list_node ret += *open_paren; } - for (auto& term : expressions) { + for (auto& term : arguments) { ret += term.expr->to_string(); } @@ -771,7 +771,7 @@ struct expression_list_node -> void { v.start(*this, depth); - for (auto& x : expressions) { + for (auto& x : arguments) { x.visit(v, depth+1); } v.end(*this, depth); @@ -969,7 +969,7 @@ struct postfix_expression_node std::ssize(ops) >= 1 && ops.front().op->type() == lexeme::LeftParen && ops.front().expr_list - && std::ssize(ops.front().expr_list->expressions) == n + && std::ssize(ops.front().expr_list->arguments) == n ; } @@ -4965,7 +4965,7 @@ auto pretty_print_visualize(expression_list_node const& n, int indent) auto ret = n.open_paren->to_string(); - for (auto i = 0; auto& expr : n.expressions) { + for (auto i = 0; auto& expr : n.arguments) { assert(expr.expr); if ( expr.pass == passing_style::out @@ -4976,7 +4976,7 @@ auto pretty_print_visualize(expression_list_node const& n, int indent) ret += to_string_view(expr.pass) + std::string{" "}; } ret += pretty_print_visualize(*expr.expr, indent); - if (++i < std::ssize(n.expressions)) { + if (++i < std::ssize(n.arguments)) { ret += ", "; } } @@ -6140,7 +6140,7 @@ class parser } n->expression_list_is_fold_expression = expr_list->is_fold_expression(); expr_list->default_initializer = - is_inside_call_expr && std::empty(expr_list->expressions); + is_inside_call_expr && std::empty(expr_list->arguments); n->expr = std::move(expr_list); return n; @@ -6843,7 +6843,7 @@ class parser } // Otherwise remember the first expression - n->expressions.push_back( { pass, std::move(x) } ); + n->arguments.push_back( { pass, std::move(x) } ); // and see if there are more... while (curr().type() == lexeme::Comma) { next(); @@ -6859,7 +6859,7 @@ class parser error("invalid text in expression list", true, {}, true); return {}; } - n->expressions.push_back( { pass, std::move(expr) } ); + n->arguments.push_back( { pass, std::move(expr) } ); } return n; diff --git a/source/to_cpp1.h b/source/to_cpp1.h index 00ac1cdac..7bc9206b5 100644 --- a/source/to_cpp1.h +++ b/source/to_cpp1.h @@ -3260,7 +3260,7 @@ class cppfront auto local_args = text_chunks_with_parens_position{{}, i->op->position(), i->op_close->position()}; assert (i->expr_list); - if (!i->expr_list->expressions.empty()) { + if (!i->expr_list->arguments.empty()) { local_args.text_chunks = print_to_text_chunks(*i->expr_list); } @@ -3451,7 +3451,7 @@ class cppfront if ( flag_safe_subscripts && i->op->type() == lexeme::LeftBracket - && std::ssize(i->expr_list->expressions) == 1 + && std::ssize(i->expr_list->arguments) == 1 ) { suffix.emplace_back( ")", i->op->position() ); @@ -3505,10 +3505,10 @@ class cppfront if ( flag_safe_subscripts && i->op->type() == lexeme::LeftBracket - && std::ssize(i->expr_list->expressions) == 1 + && std::ssize(i->expr_list->arguments) == 1 ) { - if (auto lit = i->expr_list->expressions.front().expr->get_literal(); + if (auto lit = i->expr_list->arguments.front().expr->get_literal(); lit && lit->get_token()->type() == lexeme::DecimalLiteral ) @@ -4086,7 +4086,7 @@ class cppfront } auto first = true; - for (auto const& x : n.expressions) { + for (auto const& x : n.arguments) { if (!first) { printer.print_cpp2(", ", n.position()); } From 73f8b3d7bbc9cb964e17f9d4f9cd0e2d93eea559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johel=20Ernesto=20Guerrero=20Pe=C3=B1a?= Date: Mon, 8 Jan 2024 12:19:41 -0400 Subject: [PATCH 2/4] refactor: rename `expression_list_node::term` to `expression_term` --- source/parse.h | 6 +++--- source/sema.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/parse.h b/source/parse.h index 016a1dbd8..84b026cca 100644 --- a/source/parse.h +++ b/source/parse.h @@ -701,7 +701,7 @@ struct expression_list_node bool inside_initializer = false; bool default_initializer = false; - struct term { + struct expression_term { passing_style pass = {}; std::unique_ptr expr; @@ -713,7 +713,7 @@ struct expression_list_node v.end(*this, depth); } }; - std::vector< term > arguments; + std::vector< expression_term > arguments; // API @@ -9738,7 +9738,7 @@ class parse_tree_printer : printing_visitor << static_cast(n.my_statement) << "]\n"; } - auto start(expression_list_node::term const&n, int indent) -> void + auto start(expression_list_node::expression_term const&n, int indent) -> void { o << pre(indent) << "expression-list term\n"; if (n.pass == passing_style::out) { diff --git a/source/sema.h b/source/sema.h index d1b14e41b..695a343a1 100644 --- a/source/sema.h +++ b/source/sema.h @@ -2355,7 +2355,7 @@ class sema inside_out_parameter = {}; } - auto start(expression_list_node::term const&n, int) -> void + auto start(expression_list_node::expression_term const&n, int) -> void { is_out_expression = (n.pass == passing_style::out); } From 8f13c29117f0de36d435b82a63b658223a6cfdf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johel=20Ernesto=20Guerrero=20Pe=C3=B1a?= Date: Mon, 8 Jan 2024 12:22:19 -0400 Subject: [PATCH 3/4] refactor: add `expression_list_node::type_id_term` --- source/parse.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/source/parse.h b/source/parse.h index 84b026cca..3d1de2d6c 100644 --- a/source/parse.h +++ b/source/parse.h @@ -694,6 +694,8 @@ auto to_string_view(passing_style pass) -> std::string_view { } +struct type_id_node; + struct expression_list_node { token const* open_paren = {}; @@ -713,6 +715,12 @@ struct expression_list_node v.end(*this, depth); } }; + struct type_id_term { + bool has_disambiguating_type = {}; + std::unique_ptr type; + + auto visit(auto& v, int depth) -> void; + }; std::vector< expression_term > arguments; @@ -1174,7 +1182,6 @@ auto prefix_expression_node::visit(auto& v, int depth) } -struct type_id_node; struct template_args_tag { }; struct template_argument @@ -1513,6 +1520,14 @@ auto template_argument::to_string() const return {}; } +auto expression_list_node::type_id_term::visit(auto& v, int depth) -> void +{ + v.start(*this, depth); + assert(type); + type->visit(v, depth+1); + v.end(*this, depth); +} + struct is_as_expression_node { From f6c399d0adf42961be2df19dfb51681c61d4a5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johel=20Ernesto=20Guerrero=20Pe=C3=B1a?= Date: Mon, 8 Jan 2024 15:01:34 -0400 Subject: [PATCH 4/4] feat: accept type arguments in function calls --- .../mixed-function-type-argument.cpp2 | 21 ++ regression-tests/pure2-print.cpp2 | 2 + .../mixed-function-type-argument.cpp | 63 ++++++ .../mixed-function-type-argument.cpp2.output | 2 + regression-tests/test-results/pure2-print.cpp | 10 +- .../test-results/pure2-print.cpp2.output | 2 + source/parse.h | 199 ++++++++++++++---- source/to_cpp1.h | 77 ++++--- 8 files changed, 299 insertions(+), 77 deletions(-) create mode 100644 regression-tests/mixed-function-type-argument.cpp2 create mode 100644 regression-tests/test-results/mixed-function-type-argument.cpp create mode 100644 regression-tests/test-results/mixed-function-type-argument.cpp2.output diff --git a/regression-tests/mixed-function-type-argument.cpp2 b/regression-tests/mixed-function-type-argument.cpp2 new file mode 100644 index 000000000..dc27e5494 --- /dev/null +++ b/regression-tests/mixed-function-type-argument.cpp2 @@ -0,0 +1,21 @@ +#include +main: () = { + _ = alignof(int); + _ = sizeof(int); + _ = typeid(int); + _ = offsetof(t, a); + _ = : (_: T) _ = sizeof(T::value);(std::true_type()); + _ = : (_: T) _ = sizeof(type T::value_type);(std::true_type()); + _ = alignof(const int); + _ = sizeof(const int); + _ = typeid(const int); + _ = alignof(*int); + _ = sizeof(*int); + _ = typeid(*int); +} +#if defined(_MSC_VER) || defined(__GNUC__) +_: int == __builtin_bit_cast(const int, 0); +#endif +t: @struct type = { + a: int; +} diff --git a/regression-tests/pure2-print.cpp2 b/regression-tests/pure2-print.cpp2 index c757e87ef..1d6a48dde 100644 --- a/regression-tests/pure2-print.cpp2 +++ b/regression-tests/pure2-print.cpp2 @@ -100,6 +100,8 @@ outer: @print type = { all: (args...: Args) -> bool = (... && args); + sizeof_dependent_type: () = sizeof(type T::value_type); + } main: () = { diff --git a/regression-tests/test-results/mixed-function-type-argument.cpp b/regression-tests/test-results/mixed-function-type-argument.cpp new file mode 100644 index 000000000..2ae601d66 --- /dev/null +++ b/regression-tests/test-results/mixed-function-type-argument.cpp @@ -0,0 +1,63 @@ + + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "mixed-function-type-argument.cpp2" + +#line 19 "mixed-function-type-argument.cpp2" +class t; + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "mixed-function-type-argument.cpp2" +#include +auto main() -> int; +#line 16 "mixed-function-type-argument.cpp2" +#if defined(_MSC_VER) || defined(__GNUC__) +int inline constexpr _{ __builtin_bit_cast(int const, 0) }; +#line 18 "mixed-function-type-argument.cpp2" +#endif +class t { + public: int a; + public: t(auto&& a_) +CPP2_REQUIRES_ (std::is_convertible_v&>) ; + +public: auto operator=(auto&& a_) -> t& +CPP2_REQUIRES_ (std::is_convertible_v&>) ; + +#line 21 "mixed-function-type-argument.cpp2" +}; + + +//=== Cpp2 function definitions ================================================= + +#line 1 "mixed-function-type-argument.cpp2" + +#line 2 "mixed-function-type-argument.cpp2" +auto main() -> int{ + static_cast(alignof(int)); + static_cast(sizeof(int)); + static_cast(typeid(int)); + static_cast(offsetof(t, a)); + static_cast([]([[maybe_unused]] T const& unnamed_param_1) -> auto { return static_cast(sizeof(T::value)); }(std::true_type())); + static_cast([]([[maybe_unused]] T const& unnamed_param_1) -> auto { return static_cast(sizeof(typename T::value_type)); }(std::true_type())); + static_cast(alignof(int const)); + static_cast(sizeof(int const)); + static_cast(typeid(int const)); + static_cast(alignof(int*)); + static_cast(sizeof(int*)); + static_cast(typeid(int*)); +} + +t::t(auto&& a_) +requires (std::is_convertible_v&>) + : a{ CPP2_FORWARD(a_) }{} + +auto t::operator=(auto&& a_) -> t& +requires (std::is_convertible_v&>) { + a = CPP2_FORWARD(a_); + return *this;} diff --git a/regression-tests/test-results/mixed-function-type-argument.cpp2.output b/regression-tests/test-results/mixed-function-type-argument.cpp2.output new file mode 100644 index 000000000..50af9f9bc --- /dev/null +++ b/regression-tests/test-results/mixed-function-type-argument.cpp2.output @@ -0,0 +1,2 @@ +mixed-function-type-argument.cpp2... ok (mixed Cpp1/Cpp2, Cpp2 code passes safety checks) + diff --git a/regression-tests/test-results/pure2-print.cpp b/regression-tests/test-results/pure2-print.cpp index 74103903e..9dab6be65 100644 --- a/regression-tests/test-results/pure2-print.cpp +++ b/regression-tests/test-results/pure2-print.cpp @@ -70,12 +70,15 @@ CPP2_REQUIRES_ (cpp2::impl::cmp_greater_eq(sizeof...(Args),0u)) ; #line 100 "pure2-print.cpp2" public: template [[nodiscard]] static auto all(Args const& ...args) -> bool; + +#line 103 "pure2-print.cpp2" + public: template static auto sizeof_dependent_type() -> void; public: outer() = default; public: outer(outer const&) = delete; /* No 'that' constructor, suppress copy */ public: auto operator=(outer const&) -> void = delete; -#line 103 "pure2-print.cpp2" +#line 105 "pure2-print.cpp2" }; auto main() -> int; @@ -199,7 +202,10 @@ requires (cpp2::impl::cmp_greater_eq(sizeof...(Args),0u)) { template [[nodiscard]] auto outer::all(Args const& ...args) -> bool { return (... && args); } -#line 105 "pure2-print.cpp2" +#line 103 "pure2-print.cpp2" + template auto outer::sizeof_dependent_type() -> void { sizeof(typename T::value_type); } + +#line 107 "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..70c723bea 100644 --- a/regression-tests/test-results/pure2-print.cpp2.output +++ b/regression-tests/test-results/pure2-print.cpp2.output @@ -146,6 +146,8 @@ outer:/* @print */ type = } all: (in args...: Args, ) -> move bool = (... && args); + + sizeof_dependent_type: () = sizeof(type T::value_type); } ok (all Cpp2, passes safety checks) diff --git a/source/parse.h b/source/parse.h index 3d1de2d6c..c21878705 100644 --- a/source/parse.h +++ b/source/parse.h @@ -721,7 +721,23 @@ struct expression_list_node auto visit(auto& v, int depth) -> void; }; - std::vector< expression_term > arguments; + struct term { + enum active { expression=0, type }; + std::variant< + std::unique_ptr, + std::unique_ptr + > argument; + + auto visit(auto& v, int depth) + -> void + { + v.start(*this, depth); + try_visit(argument, v, depth); + try_visit(argument, v, depth); + v.end(*this, depth); + } + }; + std::vector< term > arguments; // API @@ -739,30 +755,15 @@ struct expression_list_node // has an identifier named "..." auto ret = false; for (auto& x : arguments) { - ret |= x.expr->is_fold_expression(); + if (auto expr = std::get_if(&x.argument)) { + ret |= (*expr)->expr->is_fold_expression(); + } } return ret; } auto to_string() const - -> std::string - { - auto ret = std::string{}; - - if (open_paren) { - ret += *open_paren; - } - - for (auto& term : arguments) { - ret += term.expr->to_string(); - } - - if (close_paren) { - ret += *close_paren; - } - - return ret; - } + -> std::string; // Internals @@ -2805,6 +2806,32 @@ auto type_id_node::to_string() const } +auto expression_list_node::to_string() const + -> std::string +{ + auto ret = std::string{}; + + if (open_paren) { + ret += *open_paren; + } + + for (auto& term : arguments) { + if (auto expr = std::get_if(&term.argument)) { + ret += (*expr)->expr->to_string(); + } + else if (auto type = std::get_if(&term.argument)) { + ret += (*type)->type->to_string(); + } + } + + if (close_paren) { + ret += *close_paren; + } + + return ret; +} + + struct type_node { token const* type; @@ -4980,17 +5007,29 @@ auto pretty_print_visualize(expression_list_node const& n, int indent) auto ret = n.open_paren->to_string(); - for (auto i = 0; auto& expr : n.arguments) { - assert(expr.expr); - if ( - expr.pass == passing_style::out - || expr.pass == passing_style::move - || expr.pass == passing_style::forward - ) + for (auto i = 0; auto& arg : n.arguments) { + if (auto expr = std::get_if(&arg.argument)) { - ret += to_string_view(expr.pass) + std::string{" "}; + assert((*expr)->expr); + if ( + (*expr)->pass == passing_style::out + || (*expr)->pass == passing_style::move + || (*expr)->pass == passing_style::forward + ) + { + ret += to_string_view((*expr)->pass) + std::string{" "}; + } + ret += pretty_print_visualize(*(*expr)->expr, indent); + } + else if (auto type = std::get_if(&arg.argument)) + { + assert((*type)->type); + if ((*type)->has_disambiguating_type) + { + ret += "type "; + } + ret += pretty_print_visualize(*(*type)->type, indent); } - ret += pretty_print_visualize(*expr.expr, indent); if (++i < std::ssize(n.arguments)) { ret += ", "; } @@ -6381,10 +6420,7 @@ class parser //G postfix-expression //G prefix-operator prefix-expression //G 'sizeof' '...' ( identifier ')' - //GTODO 'sizeof' '(' type-id ')' - //GTODO 'alignof' '(' type-id ')' //GTODO await-expression - //GTODO throws-expression //G auto prefix_expression() -> std::unique_ptr @@ -6817,7 +6853,10 @@ class parser } //G expression-list: + //G 'type' type-id + //G 'const' type-id //G parameter-direction? expression + //G type-id //G expression-list ',' parameter-direction? expression //G auto expression_list( @@ -6827,7 +6866,7 @@ class parser ) -> std::unique_ptr { - auto pass = passing_style::in; + auto pass = std::optional(); auto n = std::make_unique(); n->open_paren = open_paren; n->inside_initializer = inside_initializer; @@ -6849,16 +6888,60 @@ class parser } }; - consume_optional_passing_style(); - auto x = expression(); + auto add_expr = [&](std::unique_ptr&& x) { + n->arguments.push_back( + {std::make_unique( + expression_list_node::expression_term{ + pass.value_or(passing_style::in), + std::move(x) + } + )} + ); + pass.reset(); + }; + auto parses_type_id = [&]() -> bool { + if (pass.has_value()) { + return false; + } + auto res = std::make_unique(); + next((res->has_disambiguating_type = curr() == "type")); + if ((res->type = type_id())) + { + n->arguments.push_back( {std::move(res)} ); + return true; + } + if (curr().type() == lexeme::Multiply) { + error("prefix '*ptr' dereference is not valid Cpp2; use postfix 'ptr*' instead", false); + } + return false; + }; - // If this is an empty expression_list, we're done - if (!x) { - return n; + decltype(expression()) x = {}; + + // If it doesn't start with * or const (which can only be a type id), + // try parsing it as an expression + if ( + curr().type() != lexeme::Multiply // '*' + && curr() != "const" // 'const' + && curr() != "type" // 'type' + ) + { + consume_optional_passing_style(); + x = expression(); } + // If this wasn't a valid expression... + if (!x) + { + // and it wasn't a valid type id, we're done + if (!parses_type_id()) { + return n; + } + } // Otherwise remember the first expression - n->arguments.push_back( { pass, std::move(x) } ); + else { + add_expr( std::move(x) ); + } // and see if there are more... while (curr().type() == lexeme::Comma) { next(); @@ -6868,13 +6951,30 @@ class parser break; } - consume_optional_passing_style(); - auto expr = expression(); - if (!expr) { + decltype(expression()) expr = {}; + // If it doesn't start with * or const (which can only be a type id), + // try parsing it as an expression + if ( + curr().type() != lexeme::Multiply // '*' + && curr() != "const" // 'const' + && curr() != "type" // 'type' + ) + { + consume_optional_passing_style(); + expr = expression(); + } + if ( + !expr + && !parses_type_id() + ) + { error("invalid text in expression list", true, {}, true); return {}; } - n->arguments.push_back( { pass, std::move(expr) } ); + else + { + add_expr( std::move(expr) ); + } } return n; @@ -9753,14 +9853,23 @@ class parse_tree_printer : printing_visitor << static_cast(n.my_statement) << "]\n"; } - auto start(expression_list_node::expression_term const&n, int indent) -> void + auto start(expression_list_node::term const&, int indent) -> void { o << pre(indent) << "expression-list term\n"; + } + + auto start(expression_list_node::expression_term const&n, int indent) -> void + { if (n.pass == passing_style::out) { - o << pre(indent+1) << "out\n"; + o << pre(indent) << "out\n"; } } + auto start(expression_list_node::type_id_term const&, int indent) -> void + { + o << pre(indent) << "type\n"; + } + auto start(expression_list_node const&, int indent) -> void { o << pre(indent) << "expression-list\n"; diff --git a/source/to_cpp1.h b/source/to_cpp1.h index 7bc9206b5..1ae536a4a 100644 --- a/source/to_cpp1.h +++ b/source/to_cpp1.h @@ -3506,9 +3506,13 @@ class cppfront flag_safe_subscripts && i->op->type() == lexeme::LeftBracket && std::ssize(i->expr_list->arguments) == 1 + && i->expr_list->arguments.front().argument.index() == expression_list_node::term::expression ) { - if (auto lit = i->expr_list->arguments.front().expr->get_literal(); + auto& expr = std::get( + i->expr_list->arguments.front().argument + )->expr; + if (auto lit = expr->get_literal(); lit && lit->get_token()->type() == lexeme::DecimalLiteral ) @@ -4086,47 +4090,60 @@ class cppfront } auto first = true; - for (auto const& x : n.arguments) { + for (auto const& arg : n.arguments) { if (!first) { printer.print_cpp2(", ", n.position()); } first = false; - auto is_out = false; + if (auto x_ = std::get_if(&arg.argument)) + { + auto& x = **x_; + auto is_out = false; - if (x.pass != passing_style::in) { - assert( - x.pass == passing_style::out - || x.pass == passing_style::move - || x.pass == passing_style::forward - ); - if (x.pass == passing_style::out) { - is_out = true; - printer.print_cpp2("cpp2::impl::out(&", n.position()); + if (x.pass != passing_style::in) { + assert( + x.pass == passing_style::out + || x.pass == passing_style::move + || x.pass == passing_style::forward + ); + if (x.pass == passing_style::out) { + is_out = true; + printer.print_cpp2("cpp2::impl::out(&", n.position()); + } + else if (x.pass == passing_style::move) { + printer.print_cpp2("std::move(", n.position()); + } } - else if (x.pass == passing_style::move) { - printer.print_cpp2("std::move(", n.position()); + + if (is_out) { + in_non_rvalue_context.push_back(true); } - } - if (is_out) { - in_non_rvalue_context.push_back(true); - } + assert(x.expr); + current_args.push_back( {x.pass} ); + emit(*x.expr); + current_args.pop_back(); - assert(x.expr); - current_args.push_back( {x.pass} ); - emit(*x.expr); - current_args.pop_back(); + if (is_out) { + in_non_rvalue_context.pop_back(); + } - if (is_out) { - in_non_rvalue_context.pop_back(); + if ( + x.pass == passing_style::move + || x.pass == passing_style::out + ) + { + printer.print_cpp2(")", n.position()); + } } - - if ( - x.pass == passing_style::move - || x.pass == passing_style::out - ) + else if (auto x_ = std::get_if(&arg.argument)) { - printer.print_cpp2(")", n.position()); + auto& x = **x_; + if (x.has_disambiguating_type) { + printer.print_cpp2("typename ", n.position()); + } + assert(x.type); + emit(*x.type); } }