From 19adcd12cfc89da3df158a5d965a6f3db6852729 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 22 Jul 2024 16:31:07 -1000 Subject: [PATCH 1/2] Attempt to rebase #526 with current main Hand-merged #526 into current main code Some basic examples compile, but `reflect.h2` doesn't yet, needs investigation I've commented out some error message emission on what appeared to be trial parses(?), because the presence of the error message stopped further processing --- include/cpp2util.h | 11 ++ source/parse.h | 256 ++++++++++++++++++++++++++++++++++++++------- source/sema.h | 1 + source/to_cpp1.h | 103 ++++++++++++++---- 4 files changed, 310 insertions(+), 61 deletions(-) diff --git a/include/cpp2util.h b/include/cpp2util.h index a6afa09646..b824f87898 100644 --- a/include/cpp2util.h +++ b/include/cpp2util.h @@ -368,6 +368,17 @@ using _schar = signed char; // normally use i8 instead using _uchar = unsigned char; // normally use u8 instead +//----------------------------------------------------------------------- +// +// fn_t For emitted Cpp2 function types +// +//----------------------------------------------------------------------- +// +template + requires std::is_function_v +using fn_t = T; + + //----------------------------------------------------------------------- // // String utilities diff --git a/source/parse.h b/source/parse.h index d01a7e0957..91a52a8003 100644 --- a/source/parse.h +++ b/source/parse.h @@ -1325,6 +1325,8 @@ struct qualified_id_node }; +struct function_type_id_node; + struct type_id_node { source_position pos; @@ -1335,16 +1337,21 @@ 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; std::unique_ptr constraint = {}; + // 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(); + auto is_wildcard() const -> bool { @@ -1416,6 +1423,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: @@ -1440,6 +1449,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); if (constraint) { constraint->visit(v, depth + 1); @@ -2657,6 +2667,23 @@ struct function_type_node }; +struct function_type_id_node { + std::unique_ptr type; + + function_type_id_node(decltype(type) v) + : type{ std::move(v) } + { } + + auto visit(auto& v, int depth) + -> void + { + v.start(*this, depth); + type->visit(v, depth + 1); + v.end(*this, depth); + } +}; + + struct type_node { token const* type; @@ -4467,7 +4494,11 @@ struct translation_unit_node } }; + // Definitions of out-of-line dtors for nodes with unique_ptr members of forward-declared types + +type_id_node::~type_id_node() = default; + primary_expression_node::~primary_expression_node() = default; prefix_expression_node::~prefix_expression_node() = default; @@ -5455,6 +5486,16 @@ auto pretty_print_visualize(translation_unit_node const& n) } +template +auto stack_value(T& var, std::type_identity_t const& value) + -> auto +{ + return finally([&var, old = std::exchange(var, value)]() { + var = old; + }); +}; + + //----------------------------------------------------------------------- // // parser: parses a section of Cpp2 code @@ -6609,6 +6650,7 @@ class parser //G type-id: //G type-qualifier-seq? qualified-id is-type-constraint? //G type-qualifier-seq? unqualified-id is-type-constraint? + //G type-qualifier-seq? function-type //G //G type-qualifier-seq: //G type-qualifier @@ -6619,8 +6661,10 @@ class parser //G '*' //G auto type_id( - bool allow_omitting_type_name = false, - bool allow_constraint = false + bool allow_omitting_type_name = false, + bool allow_constraint = false, + bool function_is_optional = false, + bool could_be_anonymous_returns = false ) -> std::unique_ptr { @@ -6657,9 +6701,63 @@ 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_id(nullptr, true, true) + ) + { + if (!errors.empty() && (function_is_optional || could_be_anonymous_returns)) { + undo_function_parse(); + return {}; + } + if (f->type->parameters) { + for (auto& p : f->type->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->type->returns)) { + //error( + // "a function type can't have an anonymous return type", + // false, + // (*l)->position() + //); + return {}; + } + if (!f->type->contracts.empty()) { + //error( + // "a function type can't have contracts", + // false, + // f->type->contracts.front()->position() + //); + return {}; + } + n->pos = f->type->position(); + n->id = std::move(f); + assert(n->id.index() == type_id_node::function); + } else if (!allow_omitting_type_name) { + if (function_is_optional) { + undo_function_parse(); + } return {}; } + if (curr().type() == lexeme::Multiply) { error("'T*' is not a valid Cpp2 type; use '*T' for a pointer instead", false); return {}; @@ -6735,7 +6833,7 @@ class parser term.op = &curr(); next(); - if ((term.type = type_id()) != nullptr) { + if ((term.type = type_id(false, false, true)) != nullptr) { ; } else if ((term.expr = expression()) != nullptr) { @@ -6826,6 +6924,8 @@ class parser auto term = template_argument{}; do { + auto stack = stack_value(start_pos, pos); + // If it doesn't start with * or const (which can only be a type id), // try parsing it as an expression if (auto e = [&]{ @@ -6837,27 +6937,40 @@ class parser return decltype(expression()){}; } return expression(false); // false == disallow unparenthesized relational comparisons in template args - }() + }(); + e + && ( + !e->is_expression_list() + // an empty expression-list is actually a function type + // (Cpp1 `auto () -> void`) (so far, see CWG2450) + || !e->get_expression_list()->expressions.empty() + ) ) { term.arg = std::move(e); } - // Else try parsing it as a type id - else if (auto i = type_id()) { - term.arg = std::move(i); - } + else + { + errors.clear(); + pos = start_pos; - // Else if we already got at least one template-argument, this is a - // ',' followed by something that isn't a valid template-arg - else if (std::ssize(n->template_args) > 0) { - error( "expected a template argument after ','", false); - return {}; - } + // Else try parsing it as a type id + if (auto i = type_id()) { + term.arg = std::move(i); + } - // Else this is an empty '<>' list which is okay - else { - break; + // Else if we already got at least one template-argument, this is a + // ',' followed by something that isn't a valid template-arg + else if (std::ssize(n->template_args) > 0) { + error( "expected a template argument after ','", false); + return {}; + } + + // Else this is an empty '<>' list which is okay + else { + break; + } } n->template_args.push_back( std::move(term) ); @@ -7418,7 +7531,7 @@ class parser return {}; } - // Now we should be as "is" or "as" + // Now we should be at "is" or "as" // (initial partial implementation, just "is/as id-expression") if ( curr() != "is" @@ -7431,7 +7544,7 @@ class parser n->is_as_keyword = &curr(); next(); - if (auto id = type_id()) { + if (auto id = type_id(false, false, true)) { n->type_id = std::move(id); } else if (auto e = postfix_expression()) { @@ -7869,7 +7982,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 { @@ -8025,7 +8139,9 @@ 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) { @@ -8034,8 +8150,14 @@ class parser return {}; } else if (n->declaration->has_wildcard_type()) { - error("return parameter '" + tok->to_string() + "' must have a type", - false, tok->position()); + if (is_returns) { + error("return parameter '" + tok->to_string() + "' must have a type", + false, tok->position()); + } + else { + //error("function type parameter '" + tok->to_string(true) + "' must have a type", + // false, tok->position()); + } return {}; } } @@ -8054,7 +8176,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 { @@ -8083,7 +8206,7 @@ class parser auto count = 1; - 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) { param->ordinal = count; ++count; @@ -8246,6 +8369,7 @@ class parser //G //G throws-specifier: //G 'throws' + //G '!' 'throws' //G //G return-list: //G expression-statement @@ -8258,25 +8382,41 @@ 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 ); // 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 {}; } n->parameters = std::move(parameters); // Optional "throws" + if ( + curr().type() == lexeme::Not + && !is_type_id + ) + { + error("'!throws' is only allowed on a function type-id, not on a function declaration"); + return {}; + } + if ( curr().type() == lexeme::Keyword && curr() == "throws" ) { + if (is_type_id) + { + error("expected '!' before 'throws', or no 'throws' (the default for function type-id)"); + return {}; + } + if ( n->is_move() || n->is_swap() @@ -8290,12 +8430,25 @@ class parser n->throws = true; next(); } - + else if ( + curr().type() == lexeme::Not + && peek(1) + && *peek(1) == "throws" + ) + { + n->throws = false; + next(2); + } + else if (is_type_id) + { + n->throws = true; + } // If we're not at a '->' or 'requires' or contract and what follows is // an expression, this is a ":(params) expr" shorthand function syntax if ( - curr().type() != lexeme::Arrow + !is_type_id + && curr().type() != lexeme::Arrow && curr() != "requires" && (curr() != "pre" && curr() != "post") ) @@ -8343,7 +8496,7 @@ class parser } } - else if (auto t = type_id()) + else if (auto t = type_id(false, false, false, true)) { if ( t->get_token() @@ -8399,6 +8552,20 @@ class parser } + auto function_type_id( + declaration_node* my_decl, + bool is_named = true, + bool is_type_id = false + ) + -> std::unique_ptr + { + if (auto type = function_type(my_decl, is_named, is_type_id)) { + return std::make_unique(std::move(type)); + } + return {}; + } + + auto apply_type_metafunctions( declaration_node& decl ) -> bool; @@ -8607,17 +8774,26 @@ class parser } // 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)) + else if (auto t = function_type_id(n.get(), named, is_parameter)) { - n->type = std::move(t); - assert (n->is_function()); + if (is_parameter) { + t->type->my_decl = nullptr; + auto type = std::make_unique(); + type->pos = t->type->position(); + type->id = std::move(t); + n->type = std::move(type); + } + else { + n->type = std::move(t->type); + assert(n->is_function()); - if (!n->metafunctions.empty()) { - errors.emplace_back( - n->metafunctions.front()->position(), - "(temporary alpha limitation) metafunctions are currently not supported on functions, only on types" - ); - return {}; + if (!n->metafunctions.empty()) { + errors.emplace_back( + n->metafunctions.front()->position(), + "(temporary alpha limitation) metafunctions are currently not supported on functions, only on types" + ); + return {}; + } } } diff --git a/source/sema.h b/source/sema.h index 27169fee00..0cd26554da 100644 --- a/source/sema.h +++ b/source/sema.h @@ -688,6 +688,7 @@ class sema if (is_uninitialized_decl(*sym)) { if ( sym->declaration->is_object() + && !sym->declaration->parent_is_object() && !sym->declaration->parent_is_namespace() ) { diff --git a/source/to_cpp1.h b/source/to_cpp1.h index 345283cc91..7861e25b68 100644 --- a/source/to_cpp1.h +++ b/source/to_cpp1.h @@ -1892,7 +1892,8 @@ class cppfront // auto emit( type_id_node const& n, - source_position pos = {} + bool anonymous_object = false, + source_position pos = {} ) -> void { STACKINSTR @@ -1900,12 +1901,19 @@ class cppfront pos = n.position(); } + // A type that has more than 1 non-nested token may need to be wrapped + auto wrap_type = anonymous_object && !n.pc_qualifiers.empty(); + 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, 0, false); try_emit(n.id); + try_emit(n.id); try_emit(n.id); } @@ -1913,6 +1921,10 @@ class cppfront if ((**i) == "const") { printer.print_cpp2(" ", pos); } emit(**i, false, pos); } + + if (wrap_type) { + printer.print_cpp2(">", pos); + } } @@ -2688,7 +2700,7 @@ class cppfront printer.add_pad_in_this_line( -5 ); - emit(*type_id); + emit(*type_id, true); printer.print_cpp2("{", decl->position()); if (!decl->initializer) { @@ -4283,7 +4295,8 @@ class cppfront parameter_declaration_node const& n, bool is_returns = false, bool is_template_parameter = false, - bool is_statement = false + bool is_statement = false, + bool in_function_type_id = false ) -> void { STACKINSTR @@ -4293,7 +4306,16 @@ class cppfront // Can't declare functions as parameters -- only pointers to functions which are objects assert( n.declaration ); - assert( !n.declaration->is_function() ); + + // Can't declare function values as parameters -- only pointers to functions which are objects + assert( + !n.declaration->is_function() + // or lowered references to function + || ( + n.pass != passing_style::out + && n.pass != passing_style::copy + ) + ); if (!check_shadowing_of_type_scope_names(*n.declaration)) { return; @@ -4466,7 +4488,11 @@ class cppfront // First any prefix - if (identifier == "_") { + if ( + identifier == "_" + && !in_function_type_id + ) + { printer.print_cpp2( "[[maybe_unused]] ", identifier_pos ); identifier = "unnamed_param_" + std::to_string(n.ordinal); } @@ -4595,7 +4621,7 @@ class cppfront if (is_returns) { printer.print_extra( " " + identifier ); } - else { + else if (!in_function_type_id) { printer.print_cpp2( " ", identifier_pos ); if (n.declaration->is_variadic) { @@ -4641,11 +4667,12 @@ class cppfront parameter_declaration_list_node const& n, bool is_returns = false, bool is_template_parameter = false, - bool generating_postfix_inc_dec = false + bool generating_postfix_inc_dec = false, + bool in_function_type_id = false ) -> void { STACKINSTR - in_parameter_list = true; + auto stack = stack_value(in_parameter_list, true); if (is_returns) { printer.print_extra( "{ " ); @@ -4670,7 +4697,7 @@ class cppfront } prev_pos = x->position(); assert(x); - emit(*x, is_returns, is_template_parameter); + emit(*x, is_returns, is_template_parameter, false, in_function_type_id); if (!x->declaration->has_name("this")) { first = false; } @@ -4699,8 +4726,6 @@ class cppfront emit(*n.close_paren); printer.preempt_position_pop(); } - - in_parameter_list = false; } @@ -4859,10 +4884,31 @@ class cppfront printer.print_cpp2( suffix1, n.position() ); + emit(n, function_returns_tag{}, is_main, is_ctor_or_dtor, false, generating_postfix_inc_dec); + } + + auto emit( + function_type_node const& n, + function_returns_tag, + bool is_main, + bool is_ctor_or_dtor, + bool is_function_type_id, + bool generating_postfix_inc_dec = false + ) + -> void + { + auto return_type_introducer = std::string{ " -> " }; + if (is_function_type_id) { + return_type_introducer.clear(); + } + // 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( @@ -4870,7 +4916,7 @@ class cppfront && n.my_decl->parent_declaration->name() ); printer.print_cpp2( - " -> " + print_to_string( *n.my_decl->parent_declaration->name() ) + "& ", + return_type_introducer + print_to_string( *n.my_decl->parent_declaration->name() ) + "& ", n.position() ); } @@ -4880,11 +4926,11 @@ class cppfront { if (is_main) { - printer.print_cpp2( " -> int", n.position() ); + printer.print_cpp2( return_type_introducer + "int", n.position() ); } else if(!is_ctor_or_dtor) { - printer.print_cpp2( " -> void", n.position() ); + printer.print_cpp2( return_type_introducer + "void", n.position() ); } } @@ -4892,12 +4938,13 @@ 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 ; - printer.print_cpp2( " -> ", n.position() ); + printer.print_cpp2( return_type_introducer, n.position() ); auto& r = std::get(n.returns); assert(r.type); @@ -4924,13 +4971,27 @@ class cppfront // Otherwise, handle multiple/named returns else { - printer.print_cpp2( " -> ", n.position() ); + printer.print_cpp2( return_type_introducer, n.position() ); assert (n.my_decl); printer.print_cpp2( multi_return_type_name(*n.my_decl), n.position()); } } + auto emit(function_type_id_node const& n) + -> void + { + assert(n.type); + printer.print_cpp2("cpp2::fn_t<", n.type->position()); + emit(*n.type, function_returns_tag{}, false, false, true); + emit(*n.type->parameters, false, false, true); + if (!n.type->throws) { + printer.print_cpp2(" noexcept", n.type->position()); + } + printer.print_cpp2(">", n.type->position()); + } + + //----------------------------------------------------------------------- // auto is_name_declared_in_current_type_scope(std::string_view s) @@ -6752,10 +6813,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 { From 1bbf56df84cf546f414084dc49038da42aae2978 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 22 Jul 2024 17:23:28 -1000 Subject: [PATCH 2/2] Fix warnings for clean build (unused var, extra semicolon) --- source/parse.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/parse.h b/source/parse.h index 91a52a8003..10a9130f52 100644 --- a/source/parse.h +++ b/source/parse.h @@ -5493,7 +5493,7 @@ auto stack_value(T& var, std::type_identity_t const& value) return finally([&var, old = std::exchange(var, value)]() { var = old; }); -}; +} //----------------------------------------------------------------------- @@ -6731,7 +6731,7 @@ class parser } } } - if (auto l = get_if(&f->type->returns)) { + if ([[maybe_unused]] auto l = get_if(&f->type->returns)) { //error( // "a function type can't have an anonymous return type", // false,