diff --git a/include/cpp2util.h b/include/cpp2util.h index 783138b0b..a5f7e0aab 100644 --- a/include/cpp2util.h +++ b/include/cpp2util.h @@ -561,7 +561,8 @@ class contract_group { std::cerr << ": " << msg; } std::cerr << "\n"; - std::terminate(); + // Get outta here but don't raise a signal + std::exit(1); } auto inline cpp2_default = contract_group( diff --git a/regression-tests/mixed-bugfix-for-double-poundelse.cpp2 b/regression-tests/mixed-bugfix-for-double-poundelse.cpp2 new file mode 100644 index 000000000..5d825c172 --- /dev/null +++ b/regression-tests/mixed-bugfix-for-double-poundelse.cpp2 @@ -0,0 +1,2 @@ +#else +#else diff --git a/regression-tests/pure2-bugfix-for-assert-capture.cpp2 b/regression-tests/pure2-bugfix-for-assert-capture.cpp2 new file mode 100644 index 000000000..c35b2d24f --- /dev/null +++ b/regression-tests/pure2-bugfix-for-assert-capture.cpp2 @@ -0,0 +1,3 @@ +crash_10: (foo: i32) = { + assert( 10LL as i32 == foo$); +} diff --git a/regression-tests/pure2-bugfix-for-bad-capture.cpp2 b/regression-tests/pure2-bugfix-for-bad-capture.cpp2 new file mode 100644 index 000000000..c35b2d24f --- /dev/null +++ b/regression-tests/pure2-bugfix-for-bad-capture.cpp2 @@ -0,0 +1,3 @@ +crash_10: (foo: i32) = { + assert( 10LL as i32 == foo$); +} diff --git a/regression-tests/pure2-bugfix-for-bad-decltype.cpp2 b/regression-tests/pure2-bugfix-for-bad-decltype.cpp2 new file mode 100644 index 000000000..2b860c447 --- /dev/null +++ b/regression-tests/pure2-bugfix-for-bad-decltype.cpp2 @@ -0,0 +1,4 @@ +crash_89: () = { + f := new(0); + _ = f is decltype.f); +} diff --git a/regression-tests/pure2-bugfix-for-bad-parameter.cpp2 b/regression-tests/pure2-bugfix-for-bad-parameter.cpp2 new file mode 100644 index 000000000..68198c544 --- /dev/null +++ b/regression-tests/pure2-bugfix-for-bad-parameter.cpp2 @@ -0,0 +1 @@ +print: (inout out: std::ostream=args: T) requires true = {} diff --git a/regression-tests/pure2-bugfix-for-functions-before-superclasses.cpp2 b/regression-tests/pure2-bugfix-for-functions-before-superclasses.cpp2 new file mode 100644 index 000000000..e368600ba --- /dev/null +++ b/regression-tests/pure2-bugfix-for-functions-before-superclasses.cpp2 @@ -0,0 +1,8 @@ +crash_m0b: type = { +} + +crash_m0c: type = { + name: i32; + get_name: (this) -> i32 = { return name; } + this: crash_m0b; +} diff --git a/regression-tests/pure2-bugfix-for-invalid-alias.cpp2 b/regression-tests/pure2-bugfix-for-invalid-alias.cpp2 new file mode 100644 index 000000000..352efff49 --- /dev/null +++ b/regression-tests/pure2-bugfix-for-invalid-alias.cpp2 @@ -0,0 +1,3 @@ +outer: type = { + x: requires true == 42; +} diff --git a/regression-tests/pure2-bugfix-for-late-comments.cpp2 b/regression-tests/pure2-bugfix-for-late-comments.cpp2 new file mode 100644 index 000000000..97235edde --- /dev/null +++ b/regression-tests/pure2-bugfix-for-late-comments.cpp2 @@ -0,0 +1,11 @@ + +main: () -> int = { + x := crash_m0(); + _ = x; +} + +crash_m0: type = { + operator-: (this, _) -> int = 0;/* Comment starts here +And continues here +*/ +} diff --git a/regression-tests/pure2-bugfix-for-naked-unsigned-char.cpp2 b/regression-tests/pure2-bugfix-for-naked-unsigned-char.cpp2 new file mode 100644 index 000000000..1458076d2 --- /dev/null +++ b/regression-tests/pure2-bugfix-for-naked-unsigned-char.cpp2 @@ -0,0 +1,3 @@ +main: () = { + e: unsigned char +} diff --git a/regression-tests/pure2-bugfix-for-namespace.cpp2 b/regression-tests/pure2-bugfix-for-namespace.cpp2 new file mode 100644 index 000000000..ef4efc8f4 --- /dev/null +++ b/regression-tests/pure2-bugfix-for-namespace.cpp2 @@ -0,0 +1,9 @@ +crash_96: @print type = { + namespace_alias: namespace = type_alias: type == array; +} + +crash_96a: @print type = { + test: () = { + namespace_alias: namespace = type_alias: type == array; + } +} diff --git a/regression-tests/test-results/mixed-bugfix-for-double-poundelse.cpp2.output b/regression-tests/test-results/mixed-bugfix-for-double-poundelse.cpp2.output new file mode 100644 index 000000000..bebfaa7ce --- /dev/null +++ b/regression-tests/test-results/mixed-bugfix-for-double-poundelse.cpp2.output @@ -0,0 +1,5 @@ +mixed-bugfix-for-double-poundelse.cpp2... +mixed-bugfix-for-double-poundelse.cpp2(2,1): error: #else does not match a prior #if +mixed-bugfix-for-double-poundelse.cpp2(3,1): error: #else does not match a prior #if +mixed-bugfix-for-double-poundelse.cpp2(3,1): error: #else already encountered for this #if + diff --git a/regression-tests/test-results/pure2-bugfix-for-assert-capture.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-assert-capture.cpp2.output new file mode 100644 index 000000000..edf453fa8 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-assert-capture.cpp2.output @@ -0,0 +1,4 @@ +pure2-bugfix-for-assert-capture.cpp2... +pure2-bugfix-for-assert-capture.cpp2(2,29): error: $ (capture) cannot appear here - it must appear in an anonymous expression function, a postcondition, or an interpolated string literal (at '$') +pure2-bugfix-for-assert-capture.cpp2(2,23): error: expected ')' at the end of the contract (at '==') + diff --git a/regression-tests/test-results/pure2-bugfix-for-bad-capture.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-bad-capture.cpp2.output new file mode 100644 index 000000000..cc4c11f84 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-bad-capture.cpp2.output @@ -0,0 +1,4 @@ +pure2-bugfix-for-bad-capture.cpp2... +pure2-bugfix-for-bad-capture.cpp2(2,29): error: $ (capture) cannot appear here - it must appear in an anonymous expression function, a postcondition, or an interpolated string literal (at '$') +pure2-bugfix-for-bad-capture.cpp2(2,23): error: expected ')' at the end of the contract (at '==') + diff --git a/regression-tests/test-results/pure2-bugfix-for-bad-decltype.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-bad-decltype.cpp2.output new file mode 100644 index 000000000..0685b6d73 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-bad-decltype.cpp2.output @@ -0,0 +1,6 @@ +pure2-bugfix-for-bad-decltype.cpp2... +pure2-bugfix-for-bad-decltype.cpp2(3,14): error: 'decltype' must be followed by a single parenthesized expression +pure2-bugfix-for-bad-decltype.cpp2(3,24): error: 'is' must be followed by a type-id or an expression +pure2-bugfix-for-bad-decltype.cpp2(3,14): error: 'decltype' must be followed by a single parenthesized expression +pure2-bugfix-for-bad-decltype.cpp2(3,24): error: 'is' must be followed by a type-id or an expression + diff --git a/regression-tests/test-results/pure2-bugfix-for-bad-parameter.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-bad-parameter.cpp2.output new file mode 100644 index 000000000..d435bd7b6 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-bad-parameter.cpp2.output @@ -0,0 +1,3 @@ +pure2-bugfix-for-bad-parameter.cpp2... +pure2-bugfix-for-bad-parameter.cpp2(1,50): error: parameter must be initialized with an expression (at ')') + diff --git a/regression-tests/test-results/pure2-bugfix-for-functions-before-superclasses.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-functions-before-superclasses.cpp2.output new file mode 100644 index 000000000..833bce07e --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-functions-before-superclasses.cpp2.output @@ -0,0 +1,3 @@ +pure2-bugfix-for-functions-before-superclasses.cpp2... +pure2-bugfix-for-functions-before-superclasses.cpp2(7,3): error: a type cannot declare a parent after defining a function + diff --git a/regression-tests/test-results/pure2-bugfix-for-invalid-alias.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-invalid-alias.cpp2.output new file mode 100644 index 000000000..96f281bf0 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-invalid-alias.cpp2.output @@ -0,0 +1,3 @@ +pure2-bugfix-for-invalid-alias.cpp2... +pure2-bugfix-for-invalid-alias.cpp2(2,25): error: invalid alias declaration - expected 'type', 'namespace', or a type-id after ':' + diff --git a/regression-tests/test-results/pure2-bugfix-for-late-comments.cpp b/regression-tests/test-results/pure2-bugfix-for-late-comments.cpp new file mode 100644 index 000000000..2bc53bab1 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-late-comments.cpp @@ -0,0 +1,47 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-bugfix-for-late-comments.cpp2" + +#line 7 "pure2-bugfix-for-late-comments.cpp2" +class crash_m0; + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-bugfix-for-late-comments.cpp2" + +#line 2 "pure2-bugfix-for-late-comments.cpp2" +[[nodiscard]] auto main() -> int; + +#line 7 "pure2-bugfix-for-late-comments.cpp2" +class crash_m0 { + public: [[nodiscard]] auto operator-([[maybe_unused]] auto const& unnamed_param_2) const& -> int; + public: crash_m0() = default; + public: crash_m0(crash_m0 const&) = delete; /* No 'that' constructor, suppress copy */ + public: auto operator=(crash_m0 const&) -> void = delete; + + +#line 11 "pure2-bugfix-for-late-comments.cpp2" +}; + + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-bugfix-for-late-comments.cpp2" + +#line 2 "pure2-bugfix-for-late-comments.cpp2" +[[nodiscard]] auto main() -> int{ + auto x {crash_m0()}; + static_cast(cpp2::move(x)); +} + +#line 8 "pure2-bugfix-for-late-comments.cpp2" + [[nodiscard]] auto crash_m0::operator-([[maybe_unused]] auto const& unnamed_param_2) const& -> int { return 0; }/* Comment starts here +And continues here +*/ diff --git a/regression-tests/test-results/pure2-bugfix-for-late-comments.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-late-comments.cpp2.output new file mode 100644 index 000000000..a44b6fa85 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-late-comments.cpp2.output @@ -0,0 +1,2 @@ +pure2-bugfix-for-late-comments.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/regression-tests/test-results/pure2-bugfix-for-naked-unsigned-char.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-naked-unsigned-char.cpp2.output new file mode 100644 index 000000000..01b912d76 --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-naked-unsigned-char.cpp2.output @@ -0,0 +1,8 @@ +pure2-bugfix-for-naked-unsigned-char.cpp2... +pure2-bugfix-for-naked-unsigned-char.cpp2(2,8): error: 'unsigned char' - did you mean 'u8' (usually best) or 'cpp2::_uchar'? +pure2-bugfix-for-naked-unsigned-char.cpp2(2,8): error: 'unsigned char' is an old-style C/C++ multi-word keyword type + - most such types should be used only for interoperability with older code + - using those when you need them is fine, but name them with these short names instead: + short, ushort, int, uint, long, ulong, longlong, ulonglong, longdouble, _schar, _uchar + - see also cpp2util.h > "Convenience names for integer types" + diff --git a/regression-tests/test-results/pure2-bugfix-for-namespace.cpp2.output b/regression-tests/test-results/pure2-bugfix-for-namespace.cpp2.output new file mode 100644 index 000000000..8e6b13daa --- /dev/null +++ b/regression-tests/test-results/pure2-bugfix-for-namespace.cpp2.output @@ -0,0 +1,3 @@ +pure2-bugfix-for-namespace.cpp2... +pure2-bugfix-for-namespace.cpp2(2,22): error: types cannot contain namespaces (at 'namespace') + diff --git a/source/io.h b/source/io.h index f950c52a9..ba7106942 100644 --- a/source/io.h +++ b/source/io.h @@ -362,9 +362,11 @@ class braces_tracker else { --else_net_braces; } } - auto found_preprocessor_else() -> void { - assert (!found_else); + auto found_preprocessor_else_was_there_another() -> bool { + if (found_else) + return true; found_else = true; + return false; } // If the "if" and "else" branches opened/closed the same net number @@ -469,7 +471,14 @@ class braces_tracker ); } - preprocessor.back().found_preprocessor_else(); + if (preprocessor.back().found_preprocessor_else_was_there_another()) { + // If this is the second or subsequent #else, it doesn't match + // the prior #if, so report an error + errors.emplace_back( + lineno, + "#else already encountered for this #if" + ); + }; } // Exiting an #endif diff --git a/source/lex.h b/source/lex.h index 8dce93406..8bd5acffa 100644 --- a/source/lex.h +++ b/source/lex.h @@ -713,13 +713,18 @@ auto lex_line( tokens.pop_back(); ++num_merged_tokens; } - - tokens.push_back({ - &generated_text.back()[0], - std::ssize(generated_text.back()), - pos, - lexeme::Keyword - }); + + // It's an error to have more than one of these, but we require that + // the number of tokens has not gone down. So just push back as many + // tokens as we merged. This will ensure that the token count remains + // the same. + for (auto i = 0; i < num_merged_tokens; i++) + tokens.push_back({ + &generated_text.back()[0], + std::ssize(generated_text.back()), + pos, + lexeme::Keyword + }); if (num_merged_tokens > 1) { @@ -750,8 +755,6 @@ auto lex_line( " short, ushort, int, uint, long, ulong, longlong, ulonglong, longdouble, _schar, _uchar\n" " - see also cpp2util.h > \"Convenience names for integer types\"" ); - - return; } tokens.push_back(last_token); diff --git a/source/parse.h b/source/parse.h index 9c80447bf..7987af007 100644 --- a/source/parse.h +++ b/source/parse.h @@ -5956,7 +5956,7 @@ auto pretty_print_visualize( + initializer; } else if (n.is_namespace()) { - auto& t = std::get(n.type); + auto& t = std::get(n.type); assert(t); ret += "namespace = " + initializer; @@ -7253,6 +7253,7 @@ class parser if (auto id = postfix_expression(); id && id->ops.size() == 1 + && id->ops[0].expr_list && id->ops[0].expr_list->expressions.size() == 1 && id->ops[0].expr_list->open_paren->type() == lexeme::LeftParen ) @@ -8683,6 +8684,18 @@ class parser } } + if ( + !is_returns + && n->declaration->initializer + && !n->declaration->initializer->is_expression() + ) + { + // If the initializer is not an expression statement (like a function call), + // then it can't be used as a parameter. + error("parameter must be initialized with an expression"); + return {}; + } + return n; } @@ -8805,7 +8818,6 @@ class parser -> std::unique_ptr { auto n = std::make_unique(curr().position()); - auto guard = capture_groups_stack_guard(this, &n->captures); if ( curr() != "pre" @@ -8816,6 +8828,13 @@ class parser return {}; } n->kind = &curr(); + + auto guard = + curr() == "post" + ? std::make_unique(this, &n->captures) + : std::unique_ptr() + ; + next(); // Check if there's a @@ -9292,6 +9311,10 @@ class parser // Or a namespace else if (curr() == "namespace") { + if (n->parent_is_type()) { + error("types cannot contain namespaces"); + return {}; + } n->type = std::make_unique( &curr() ); assert (n->type.index() == declaration_node::a_namespace); next(); @@ -9824,10 +9847,13 @@ class parser a->initializer = std::move(e); } - // Anything else shouldn't be possible + // Anything else is illegal else { - assert(false && "ICE: should be unreachable - invalid alias declaration"); - return {}; + errors.emplace_back( + curr().position(), + "invalid alias declaration - expected 'type', 'namespace', or a type-id after ':'" + ); + return {}; } // And the final ceremonial semicolon diff --git a/source/sema.h b/source/sema.h index 424261a69..e615c0949 100644 --- a/source/sema.h +++ b/source/sema.h @@ -2037,6 +2037,7 @@ class sema if (n.is_type()) { auto compound_stmt = n.initializer->get_if(); assert (compound_stmt); + bool seen_function = false; for (auto& stmt : compound_stmt->statements) { if ( !stmt->is_declaration() @@ -2049,6 +2050,24 @@ class sema ); return false; } + auto stmt_decl = stmt->get_if(); + // If this is a declaration, check if it's a function + if (stmt_decl && stmt_decl->is_function()) + seen_function = true; + + // If this is called 'this', then make sure we haven't seen any functions + if ( + stmt_decl + && stmt_decl->has_name("this") + && seen_function + ) + { + handle_error( + stmt->position(), + "a type cannot declare a parent after defining a function" + ); + return false; + } } } diff --git a/source/to_cpp1.h b/source/to_cpp1.h index a6c08f60f..cff6a92dd 100644 --- a/source/to_cpp1.h +++ b/source/to_cpp1.h @@ -432,7 +432,7 @@ class positional_printer c.dbg_was_printed = true; } - auto flush_comments( source_position pos ) + auto flush_comments( source_position pos, bool print_remaining_comments = false ) -> void { if (!pcomments) { @@ -444,7 +444,7 @@ class positional_printer // Add unprinted comments and blank lines as needed to catch up vertically // - while (curr_pos.lineno < pos.lineno) + while (print_remaining_comments ? (next_comment < std::ssize(comments)) : (curr_pos.lineno < pos.lineno)) { // If a comment goes on this line, print it if ( @@ -468,7 +468,8 @@ class positional_printer ) { print_comment( comments[next_comment] ); - assert(curr_pos.lineno <= pos.lineno); // we shouldn't have overshot + if (!print_remaining_comments) + assert(curr_pos.lineno <= pos.lineno); // we shouldn't have overshot } ++next_comment; @@ -479,6 +480,9 @@ class positional_printer print("\n"); } } + // And catch up. + while (curr_pos.lineno < pos.lineno) + print("\n"); } auto print_unprinted_comments() @@ -589,7 +593,7 @@ class positional_printer && psource->has_cpp2() ) { - flush_comments( {curr_pos.lineno+1, 1} ); + flush_comments( {curr_pos.lineno+1, 1}, print_remaining_comments ); if (print_remaining_comments) { print_unprinted_comments();