summaryrefslogtreecommitdiffhomepage
path: root/CompilerDriver/cc2/source/to_cpp1.h
diff options
context:
space:
mode:
authorAmlal El Mahrouss <amlal.elmahrouss@icloud.com>2023-12-30 23:39:37 +0100
committerAmlal El Mahrouss <amlal.elmahrouss@icloud.com>2023-12-30 23:39:37 +0100
commit263915832993dd12beee10e204f9ebcc6c786ed2 (patch)
tree862e51208a99c35746e574a76564a4532b3a4a49 /CompilerDriver/cc2/source/to_cpp1.h
Meta: initial commit of WestCo optimized toolchain.
Signed-off-by: Amlal El Mahrouss <amlal.elmahrouss@icloud.com>
Diffstat (limited to 'CompilerDriver/cc2/source/to_cpp1.h')
-rw-r--r--CompilerDriver/cc2/source/to_cpp1.h6750
1 files changed, 6750 insertions, 0 deletions
diff --git a/CompilerDriver/cc2/source/to_cpp1.h b/CompilerDriver/cc2/source/to_cpp1.h
new file mode 100644
index 0000000..a7b6782
--- /dev/null
+++ b/CompilerDriver/cc2/source/to_cpp1.h
@@ -0,0 +1,6750 @@
+
+// Copyright (c) Herb Sutter
+// SPDX-License-Identifier: CC-BY-NC-ND-4.0
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+
+//===========================================================================
+// Lowering to Cpp1 syntax
+//===========================================================================
+
+#ifndef CPP2_TO_CPP1_H
+#define CPP2_TO_CPP1_H
+
+#include "sema.h"
+#include <iostream>
+#include <cstdio>
+#include <optional>
+
+namespace cpp2 {
+
+// Defined out of line here just to avoid bringing <iostream> in before this,
+// so that we can't accidentally start depending on iostreams in earlier phases
+auto cmdline_processor::print(std::string_view s, int width)
+ -> void
+{
+ if (width > 0) {
+ std::cout << std::setw(width) << std::left;
+ }
+ std::cout << s;
+}
+
+
+//-----------------------------------------------------------------------
+//
+// Stringingizing helpers
+//
+//-----------------------------------------------------------------------
+
+auto pad(int padding)
+ -> std::string_view
+{
+ if (padding < 1) {
+ return "";
+ }
+
+ return {
+ indent_str.c_str(),
+ _as<size_t>( std::min( padding, _as<int>(std::ssize(indent_str))) )
+ };
+}
+
+
+//-----------------------------------------------------------------------
+//
+// positional_printer: a Syntax 1 pretty printer
+//
+//-----------------------------------------------------------------------
+//
+static auto flag_emit_cppfront_info = false;
+static cmdline_processor::register_flag cmd_emit_cppfront_info(
+ 9,
+ "emit-cppfront-info",
+ "Emit cppfront version/build in output file",
+ []{ flag_emit_cppfront_info = true; }
+);
+
+static auto flag_clean_cpp1 = false;
+static cmdline_processor::register_flag cmd_clean_cpp1(
+ 9,
+ "clean-cpp1",
+ "Emit clean Cpp1 without #line directives",
+ []{ flag_clean_cpp1 = true; }
+);
+
+static auto flag_import_std = false;
+static cmdline_processor::register_flag cmd_import_std(
+ 0,
+ "import-std",
+ "import all std:: via 'import std;' - ignored if -include-std is set",
+ []{ flag_import_std = true; }
+);
+
+static auto flag_include_std = false;
+static cmdline_processor::register_flag cmd_include_std(
+ 0,
+ "include-std",
+ "#include all std:: headers",
+ []{ flag_include_std = true; }
+);
+
+static auto flag_cpp2_only = false;
+static cmdline_processor::register_flag cmd_cpp2_only(
+ 0,
+ "pure-cpp2",
+ "Allow Cpp2 syntax only - also sets -import-std",
+ []{ flag_cpp2_only = true; flag_import_std = true; }
+);
+
+static auto flag_safe_null_pointers = true;
+static cmdline_processor::register_flag cmd_safe_null_pointers(
+ 2,
+ "no-null-checks",
+ "Disable null safety checks",
+ []{ flag_safe_null_pointers = false; }
+);
+
+static auto flag_safe_subscripts = true;
+static cmdline_processor::register_flag cmd_safe_subscripts(
+ 2,
+ "no-subscript-checks",
+ "Disable subscript safety checks",
+ []{ flag_safe_subscripts = false; }
+);
+
+static auto flag_safe_comparisons = true;
+static cmdline_processor::register_flag cmd_safe_comparisons(
+ 2,
+ "no-comparison-checks",
+ "Disable mixed-sign comparison safety checks",
+ []{ flag_safe_comparisons = false; }
+);
+
+static auto flag_use_source_location = false;
+static cmdline_processor::register_flag cmd_enable_source_info(
+ 2,
+ "add-source-info",
+ "Enable source_location information for contract checks",
+ []{ flag_use_source_location = true; }
+);
+
+static auto flag_cpp1_filename = std::string{};
+static cmdline_processor::register_flag cmd_cpp1_filename(
+ 9,
+ "output filename",
+ "Output to 'filename' (can be 'stdout') - default is *.cpp/*.h",
+ nullptr,
+ [](std::string const& name) { flag_cpp1_filename = name; }
+);
+
+static auto flag_print_colon_errors = false;
+static cmdline_processor::register_flag cmd_print_colon_errors(
+ 9,
+ "format-colon-errors",
+ "Emit ':line:col:' format for messages - lights up some tools",
+ []{ flag_print_colon_errors = true; }
+);
+
+static auto flag_verbose = false;
+static cmdline_processor::register_flag cmd_verbose(
+ 9,
+ "verbose",
+ "Print verbose statistics and -debug output",
+ []{ flag_verbose = true; }
+);
+
+static auto flag_no_exceptions = false;
+static cmdline_processor::register_flag cmd_no_exceptions(
+ 4,
+ "fno-exceptions",
+ "Disable C++ EH - failed 'as' for 'variant' will assert",
+ []{ flag_no_exceptions = true; }
+);
+
+static auto flag_no_rtti = false;
+static cmdline_processor::register_flag cmd_no_rtti(
+ 4,
+ "fno-rtti",
+ "Disable C++ RTTI - using 'as' for '*'/'std::any' will assert",
+ []{ flag_no_rtti = true; }
+);
+
+struct text_with_pos{
+ std::string text;
+ source_position pos;
+ text_with_pos(std::string const& t, source_position p) : text{t}, pos{p} { }
+};
+
+// Defined out of line so we can use flag_print_colon_errors.
+auto error_entry::print(
+ auto& o,
+ std::string const& file
+) const
+ -> void
+{
+ o << file ;
+ if (where.lineno > 0) {
+ if (flag_print_colon_errors) {
+ o << ":" << (where.lineno);
+ if (where.colno >= 0) {
+ o << ":" << where.colno;
+ }
+ }
+ else {
+ o << "("<< (where.lineno);
+ if (where.colno >= 0) {
+ o << "," << where.colno;
+ }
+ o << ")";
+ }
+ }
+ o << ":";
+ if (internal) {
+ o << " internal compiler";
+ }
+ o << " error: " << msg << "\n";
+}
+
+class positional_printer
+{
+ // Core information
+ std::ofstream out_file = {}; // Cpp1 syntax output file
+ std::ostream* out = {}; // will point to out_file or cout
+ std::string cpp2_filename = {};
+ std::string cpp1_filename = {};
+ std::vector<comment> const* pcomments = {}; // Cpp2 comments data
+ source const* psource = {};
+ parser const* pparser = {};
+
+ source_position curr_pos = {}; // current (line,col) in output
+ lineno_t generated_pos_line = {}; // current line in generated output
+ int last_line_indentation = {};
+ int next_comment = 0; // index of the next comment not yet printed
+ bool last_was_empty = false;
+ int empty_lines_suppressed = 0;
+ bool just_printed_line_directive = false;
+ bool printed_extra = false;
+ char last_printed_char = {};
+
+ struct req_act_info {
+ colno_t requested;
+ colno_t offset;
+
+ req_act_info(colno_t r, colno_t o) : requested{r}, offset{o} { }
+ };
+ struct {
+ lineno_t line = {};
+ std::vector<req_act_info> requests = {};
+ } prev_line_info = {};
+
+ // Position override information
+ std::vector<source_position> preempt_pos = {}; // use this position instead of the next supplied one
+ int pad_for_this_line = 0; // extra padding to add/subtract for this line only
+ bool ignore_align = false;
+ int ignore_align_indent = 0;
+ lineno_t ignore_align_lineno = 0;
+ bool enable_indent_heuristic = true;
+
+public:
+ // Modal information
+ enum phases {
+ phase0_type_decls = 0,
+ phase1_type_defs_func_decls = 1,
+ phase2_func_defs = 2
+ };
+ auto get_phase() const { return phase; }
+
+private:
+ phases phase = phase0_type_decls;
+
+ auto inc_phase() -> void {
+ switch (phase) {
+ break;case phase0_type_decls : phase = phase1_type_defs_func_decls;
+ break;case phase1_type_defs_func_decls: phase = phase2_func_defs;
+ break;default : assert(!"ICE: invalid lowering phase");
+ }
+ curr_pos = {};
+ next_comment = 0; // start over with the comments
+ }
+
+ std::vector<std::string*> emit_string_targets; // option to emit to string instead of out file
+ std::vector<std::vector<text_with_pos>*> emit_text_chunks_targets; // similar for vector<text_pos>
+
+ enum class target_type { string, chunks };
+ std::vector<target_type> emit_target_stack; // to interleave them sensibly
+
+
+ //-----------------------------------------------------------------------
+ // Print text
+ //
+ auto print(
+ std::string_view s,
+ source_position pos = source_position{},
+ bool track_curr_pos = true,
+ bool is_known_empty = false
+ )
+ -> void
+ {
+ // Take ownership of (and reset) just_printed_line_directive value
+ auto line_directive_already_done = std::exchange(just_printed_line_directive, false);
+
+ // If the caller is capturing this output, emit to the
+ // current target instead and skip most positioning logic
+ if (!emit_target_stack.empty())
+ {
+ // If capturing to a string, emit to the specified string
+ if (emit_target_stack.back() == target_type::string) {
+ assert(!emit_string_targets.empty());
+ *emit_string_targets.back() += s;
+ }
+
+ // If capturing to a vector of chunks, emit to that
+ else {
+ assert(!emit_text_chunks_targets.empty());
+ emit_text_chunks_targets.back()->insert( emit_text_chunks_targets.back()->begin(), text_with_pos(std::string(s), pos) );
+ }
+
+ return;
+ }
+
+ // Otherwise, we'll actually print the string to the output file
+ // and update our curr_pos position
+
+ if (s.length() > 0) {
+ last_printed_char = s.back();
+ }
+
+ // Reject consecutive empty lines: If this line is empty
+ if (
+ ( s == "\n" || is_known_empty )
+ && curr_pos.colno <= 1
+ )
+ {
+ // And so was the last one, update logical position only
+ // and increment empty_lines_suppressed instead of printing
+ if (last_was_empty) {
+ if (track_curr_pos) {
+ ++curr_pos.lineno;
+ curr_pos.colno = 1;
+ }
+ ++empty_lines_suppressed;
+ return;
+ }
+ // If this is the first consecutive empty, remember and continue
+ last_was_empty = true;
+ }
+ // Otherwise, if this line is not empty
+ else {
+ // Remember that this line was not empty
+ last_was_empty = false;
+
+ // And if we did suppress any empties, emit a #line to resync
+ if (empty_lines_suppressed > 0) {
+ if (!line_directive_already_done) {
+ print_line_directive(curr_pos.lineno);
+ }
+ empty_lines_suppressed = 0;
+ }
+ }
+
+ // Output the string
+ assert (out);
+ *out << s;
+
+ // Update curr_pos by finding how many line breaks s contained,
+ // and where the last one was which determines our current colno
+ if (track_curr_pos)
+ {
+ auto last_newline = std::string::npos; // the last newline we found in the string
+ auto newline_pos = std::size_t(0); // the current newline we found in the string
+ while ((newline_pos = s.find('\n', newline_pos)) != std::string::npos)
+ {
+ // For each line break we find, reset pad and inc current lineno
+ pad_for_this_line = 0;
+ ++curr_pos.lineno;
+ last_newline = newline_pos;
+ ++newline_pos;
+ }
+
+ // Now also adjust the colno
+ if (last_newline != std::string::npos) {
+ // If we found a newline, it's the distance from the last newline to EOL
+ curr_pos.colno = s.length() - last_newline;
+ }
+ else {
+ // Else add the length of the string
+ curr_pos.colno += s.length();
+ }
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Internal helpers
+
+ // Start a new line if we're not in col 1 already
+ //
+ auto ensure_at_start_of_new_line()
+ -> void
+ {
+ if (curr_pos.colno > 1) {
+ auto old_pos = curr_pos;
+ print( "\n" );
+ assert(curr_pos.lineno == old_pos.lineno+1);
+ assert(curr_pos.colno == 1);
+ }
+ }
+
+ // Print a #line directive
+ //
+ auto print_line_directive( lineno_t line )
+ -> void
+ {
+ // Ignore requests from generated code (negative line numbers)
+ if (line < 1) {
+ return;
+ }
+
+ // Otherwise, implement the request
+ prev_line_info = { curr_pos.lineno, { } };
+ ensure_at_start_of_new_line();
+
+ // Not using print() here because this is transparent to the curr_pos
+ if (!flag_clean_cpp1) {
+ assert (out);
+ *out << "#line " << line << " " << std::quoted(cpp2_filename) << "\n";
+ }
+ just_printed_line_directive = true;
+ }
+
+ // Catch up with comment/blank lines
+ //
+ auto print_comment(comment const& c)
+ -> void
+ {
+ // For a line comment, start it at the right indentation and print it
+ // with a newline end
+ if (c.kind == comment::comment_kind::line_comment) {
+ print( pad( c.start.colno - curr_pos.colno + 1 ) );
+ print( c.text );
+ assert( c.text.find("\n") == std::string::npos ); // we shouldn't have newlines
+ print("\n");
+ }
+
+ // For a stream comment, pad out to its column (if we haven't passed it already)
+ // and emit it there
+ else {
+ print( pad( c.start.colno - curr_pos.colno ) );
+ print( c.text );
+ }
+
+ c.dbg_was_printed = true;
+ }
+
+ auto flush_comments( source_position pos )
+ -> void
+ {
+ if (!pcomments) {
+ return;
+ }
+
+ // For convenience
+ auto& comments = *pcomments;
+
+ // Add unprinted comments and blank lines as needed to catch up vertically
+ //
+ while (curr_pos.lineno < pos.lineno)
+ {
+ // If a comment goes on this line, print it
+ if (
+ next_comment < std::ssize(comments)
+ && comments[next_comment].start.lineno <= curr_pos.lineno
+ )
+ {
+ // Emit non-function body comments in phase1_type_defs_func_decls,
+ // and emit function body comments in phase2_func_defs
+ assert(pparser);
+ if (
+ (
+ phase == phase1_type_defs_func_decls
+ && !pparser->is_within_function_body( comments[next_comment].start.lineno )
+ )
+ ||
+ (
+ phase == phase2_func_defs
+ && pparser->is_within_function_body( comments[next_comment].start.lineno )
+ )
+ )
+ {
+ print_comment( comments[next_comment] );
+ assert(curr_pos.lineno <= pos.lineno); // we shouldn't have overshot
+ }
+
+ ++next_comment;
+ }
+
+ // Otherwise, just print a blank line
+ else {
+ print("\n");
+ }
+ }
+ }
+
+ auto print_unprinted_comments()
+ {
+ for (auto const& c : *pcomments) {
+ if (!c.dbg_was_printed) {
+ print_comment(c);
+ }
+ }
+ }
+
+ // Position ourselves as close to pos as possible,
+ // and catch up with displaying comments
+ //
+ auto align_to( source_position pos )
+ -> void
+ {
+ auto on_same_line = curr_pos.lineno == pos.lineno;
+
+ // Ignoring this logic is used when we're generating new code sections,
+ // such as return value structs, and emitting raw string literals
+ if (ignore_align) {
+ print( pad( ignore_align_indent - curr_pos.colno ) );
+ return;
+ }
+
+ // Otherwise, we need to apply our usual alignment logic
+
+ // Catch up with displaying comments
+ flush_comments( pos );
+
+ // If we're not on the right line
+ if (
+ printed_extra
+ && !on_same_line
+ )
+ {
+ print_line_directive(pos.lineno);
+ curr_pos.lineno = pos.lineno;
+ printed_extra = false;
+ }
+ else if (curr_pos.lineno < pos.lineno)
+ {
+ // In case we're just one away, try a blank line
+ // (this might get ignored and we'll get the line directive)
+ print( "\n" );
+ if (curr_pos.lineno != pos.lineno) {
+ print_line_directive(pos.lineno);
+ }
+ curr_pos.lineno = pos.lineno;
+ }
+
+ // Finally, align to the target column, if we're on the right line
+ // and not one-past-the-end on the extra line at section end)
+ assert(
+ psource
+ && 0 <= curr_pos.lineno
+ && curr_pos.lineno < std::ssize(psource->get_lines())+1
+ );
+ if (
+ curr_pos.lineno == pos.lineno
+ && curr_pos.lineno < std::ssize(psource->get_lines())
+ )
+ {
+ // Record this line's indentation as the 'last' line for next time
+ last_line_indentation = psource->get_lines()[curr_pos.lineno].indent();
+
+ // If this line was originally densely spaced (had <2 whitespace
+ // between all tokens), then the programmer likely wasn't doing a lot
+ // of special formatting...
+ if (psource->get_lines()[curr_pos.lineno].all_tokens_are_densely_spaced)
+ {
+ // For the first token in a line, use the line's original indentation
+ if (curr_pos.colno <= 1)
+ {
+ print( pad( psource->get_lines()[curr_pos.lineno].indent() ) );
+ }
+ // For later tokens, don't try to add padding
+ else {
+ if (
+ last_printed_char == ';'
+ && on_same_line
+ )
+ {
+ print( " " );
+ }
+ }
+ }
+ // Otherwise, make a best effort to adjust position with some padding
+ else
+ {
+ pos.colno = std::max( 1, pos.colno + pad_for_this_line );
+ print( pad( pos.colno - curr_pos.colno ) );
+ }
+ }
+ }
+
+
+public:
+ //-----------------------------------------------------------------------
+ // Finalize phase
+ //
+ auto finalize_phase(bool print_remaining_comments = false)
+ {
+ if (
+ is_open()
+ && psource
+ && psource->has_cpp2()
+ )
+ {
+ flush_comments( {curr_pos.lineno+1, 1} );
+
+ if (print_remaining_comments) {
+ print_unprinted_comments();
+ }
+
+ // Always make sure the very last line ends with a newline
+ // (not really necessary but makes some tools quieter)
+ // -- but only if there's any Cpp2, otherwise don't
+ // because passing through all-Cpp1 code should always
+ // remain diff-identical
+ if (phase == phase2_func_defs) {
+ print_extra("\n");
+ }
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Open
+ //
+ auto open(
+ std::string cpp2_filename_,
+ std::string cpp1_filename_,
+ std::vector<comment> const& comments,
+ cpp2::source const& source,
+ cpp2::parser const& parser
+ )
+ -> void
+ {
+ cpp2_filename = cpp2_filename_;
+ assert(
+ !is_open()
+ && !pcomments
+ && "ICE: tried to call .open twice"
+ );
+ cpp1_filename = cpp1_filename_;
+ if (cpp1_filename == "stdout") {
+ out = &std::cout;
+ }
+ else {
+ out_file.open(cpp1_filename);
+ out = &out_file;
+ }
+ pcomments = &comments;
+ psource = &source;
+ pparser = &parser;
+ }
+
+ auto reopen()
+ -> void
+ {
+ assert(
+ is_open()
+ && "ICE: tried to call .reopen without first calling .open"
+ );
+ assert(cpp1_filename.ends_with(".h"));
+ out_file.close();
+ out_file.open(cpp1_filename + "pp");
+ }
+
+ auto is_open()
+ -> bool
+ {
+ if (out) {
+ assert(
+ pcomments
+ && "ICE: if is_open, pcomments should also be set"
+ );
+ }
+ return out;
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Abandon: close and delete
+ //
+ auto abandon()
+ -> void
+ {
+ if (!is_open()) {
+ return;
+ }
+ if (out_file.is_open()) {
+ out_file.close();
+ std::remove(cpp1_filename.c_str());
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Print extra text and don't track positions
+ // Used for Cpp2 boundary comment and prelude and final newline
+ //
+ auto print_extra( std::string_view s )
+ -> void
+ {
+ assert(
+ is_open()
+ && "ICE: printer must be open before printing"
+ );
+ print( s, source_position{}, false );
+ printed_extra = true;
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Print a Cpp1 line, which should be at lineno
+ //
+ auto print_cpp1( std::string_view s, lineno_t line )
+ -> void
+ {
+ assert(
+ is_open()
+ && line >= 0
+ && "ICE: printer must be open before printing, and line number must not be negative (Cpp1 code is never generated)"
+ );
+
+ // Always start a Cpp1 line on its own new line
+ ensure_at_start_of_new_line();
+
+ // If we are out of sync with the current logical line number,
+ // emit a #line directive to re-sync
+ if (curr_pos.lineno != line) {
+ print_line_directive( line );
+ curr_pos.lineno = line;
+ }
+
+ // Print the line
+ assert (curr_pos.colno == 1);
+ print( s );
+ print( "\n" );
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Used when we start a new Cpp2 section, or when we emit the same item
+ // more than once (notably when we emit operator= more than once)
+ //
+ auto reset_line_to(lineno_t line, bool force = false)
+ -> void
+ {
+ // Always start a Cpp2 section on its own new line
+ ensure_at_start_of_new_line();
+
+ // If we are out of sync with the current logical line number,
+ // emit a #line directive to re-sync
+ if (
+ force
+ || curr_pos.lineno != line
+ )
+ {
+ print_line_directive( line );
+ curr_pos.lineno = line;
+ }
+
+ assert (curr_pos.colno == 1);
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Print a Cpp2 item, which should be at pos
+ //
+ auto print_cpp2(
+ std::string_view s,
+ source_position pos,
+ bool leave_newlines_alone = false,
+ bool is_known_empty = false
+
+ )
+ -> void
+ {
+ // If we're printing for real (not to a string target)
+ if (emit_target_stack.empty())
+ {
+ // If we're in a generated text region (signified by negative
+ // line numbers), then shunt this call to print_extra instead
+ if (pos.lineno < 1) {
+ if (generated_pos_line != pos.lineno) {
+ *out << "\n" + std::string(last_line_indentation, ' ');
+ generated_pos_line = pos.lineno;
+ }
+ print_extra(s);
+ return;
+ }
+
+ // Otherwise, we're no longer in generated code, so reset the
+ // generated code counter
+ generated_pos_line = {};
+ }
+
+ assert(
+ is_open()
+ && "ICE: printer must be open before printing"
+ );
+
+ // If there are any embedded newlines, split this string into
+ // separate print_cpp2 calls
+ if (auto newline_pos = s.find('\n');
+ !leave_newlines_alone
+ && s.length() > 1
+ && newline_pos != s.npos
+ )
+ {
+ while (newline_pos != std::string_view::npos)
+ {
+ // Print the text before the next newline
+ if (newline_pos > 0) {
+ print_cpp2( s.substr(0, newline_pos), pos );
+ }
+
+ // Emit the newline as a positioned empty string
+ assert (s[newline_pos] == '\n');
+ ++pos.lineno;
+ pos.colno = 1;
+ print_cpp2( "", pos, false, curr_pos.colno <= 1 );
+
+ s.remove_prefix( newline_pos+1 );
+ newline_pos = s.find('\n');
+ }
+ // Print any tail following the last newline
+ if (!s.empty()) {
+ print_cpp2( s, pos );
+ }
+ return;
+ }
+
+ // The rest of this call handles a single chunk that's either a
+ // standalone "\n" or a piece of text that doesn't have a newline
+
+ // Skip alignment work if we're capturing emitted text
+ if (emit_target_stack.empty())
+ {
+ // Remember where we are
+ auto last_pos = curr_pos;
+
+ // We may want to adjust the position based on (1) a position preemption request
+ // or else (2) to repeat a similar adjustment we discovered on the previous line
+ auto adjusted_pos = pos;
+
+ // (1) See if there's a position preemption request, if so use it up
+ // For now, the preempt position use cases are about overriding colno
+ // and only on the same line. In the future, we might have more use cases.
+ if (!preempt_pos.empty()) {
+ if (preempt_pos.back().lineno == pos.lineno) {
+ adjusted_pos.colno = preempt_pos.back().colno;
+ }
+ }
+
+ // (2) Otherwise, see if there's a previous line's offset to repeat
+ // If we moved to a new line, then this is the first
+ // non-comment non-whitespace text on the new line
+ else if (
+ last_pos.lineno == pos.lineno-1
+ && enable_indent_heuristic
+ )
+ {
+ // If the last line had a request for this colno, remember its actual offset
+ constexpr int sentinel = -100;
+ auto last_line_offset = sentinel;
+ for(auto i = 0;
+ i < std::ssize(prev_line_info.requests)
+ && prev_line_info.requests[i].requested <= pos.colno;
+ ++i
+ )
+ {
+ if (prev_line_info.requests[i].requested == pos.colno)
+ {
+ last_line_offset = prev_line_info.requests[i].offset;
+ break;
+ }
+ }
+
+ // If there was one, apply the actual column number offset
+ if (last_line_offset > sentinel) {
+ adjusted_pos.colno += last_line_offset;
+ }
+ }
+ enable_indent_heuristic = true;
+
+ // If we're changing lines, start accumulating this new line's request/actual adjustment info
+ if (last_pos.lineno < adjusted_pos.lineno) {
+ prev_line_info = { curr_pos.lineno, { } };
+ }
+
+ align_to(adjusted_pos);
+
+ // Remember the requested and actual offset columns for this item
+ prev_line_info.requests.push_back( req_act_info( pos.colno /*requested*/ , curr_pos.colno /*actual*/ - pos.colno ) );
+ }
+
+ print(s, pos, true, is_known_empty );
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Position override control functions
+ //
+
+ // Use this position instead of the next supplied one
+ // Useful when Cpp1 syntax is emitted in a different order/verbosity
+ // than Cpp2 such as with declarations
+ //
+ auto preempt_position_push(source_position pos)
+ -> void
+ {
+ preempt_pos.push_back( pos );
+ }
+
+ auto preempt_position_pop()
+ -> void
+ {
+ assert(!preempt_pos.empty());
+ preempt_pos.pop_back();
+ }
+
+ // Add (or, if negative, subtract) padding for the current line only
+ //
+ auto add_pad_in_this_line(colno_t extra)
+ -> void
+ {
+ pad_for_this_line += extra;
+ }
+
+ // Enable indent heuristic for just this line
+ //
+ auto disable_indent_heuristic_for_next_text()
+ -> void
+ {
+ enable_indent_heuristic = false;
+ }
+
+ // Ignore position information, usually when emitting generated code
+ // such as generated multi-return type structs
+ //
+ auto ignore_alignment(
+ bool ignore,
+ int indent = 0
+ )
+ -> void
+ {
+ // We'll only ever call this in local non-nested true/false pairs.
+ // If we ever want to generalize (support nesting, or make it non-brittle),
+ // wrap this in a push/pop stack.
+ if (ignore) {
+ ignore_align = true;
+ ignore_align_indent = indent;
+ ignore_align_lineno = curr_pos.lineno; // push state
+ }
+ else {
+ ignore_align = false;
+ ignore_align_indent = 0;
+ curr_pos.lineno = ignore_align_lineno; // pop state
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Modal state control functions
+ //
+
+ auto next_phase()
+ -> void
+ {
+ inc_phase();
+ }
+
+ // Provide an option to store to a given string instead, which is
+ // useful for capturing Cpp1-formatted output for generated code
+ //
+ auto emit_to_string( std::string* target = {} )
+ -> void
+ {
+ if (target) {
+ emit_string_targets.push_back( target );
+ emit_target_stack.push_back(target_type::string);
+ }
+ else {
+ emit_string_targets.pop_back();
+ emit_target_stack.pop_back();
+ }
+ }
+
+ // Provide an option to store to a vector<text_with_pos>, which is
+ // useful for postfix expression which have to mix unwrapping operators
+ // with emitting sub-elements such as expression lists
+ //
+ auto emit_to_text_chunks( std::vector<text_with_pos>* target = {} )
+ -> void
+ {
+ if (target) {
+ emit_text_chunks_targets.push_back( target );
+ emit_target_stack.push_back(target_type::chunks);
+ }
+ else {
+ emit_text_chunks_targets.pop_back();
+ emit_target_stack.pop_back();
+ }
+ }
+
+};
+
+
+//-----------------------------------------------------------------------
+//
+// cppfront: a compiler instance
+//
+//-----------------------------------------------------------------------
+//
+struct function_prolog {
+ std::vector<std::string> mem_inits = {};
+ std::vector<std::string> statements = {};
+};
+
+class cppfront
+{
+ std::string sourcefile;
+ std::vector<error_entry> errors;
+
+ // For building
+ //
+ cpp2::source source;
+ cpp2::tokens tokens;
+ cpp2::parser parser;
+ cpp2::sema sema;
+
+ bool source_loaded = true;
+ bool last_postfix_expr_was_pointer = false;
+ bool violates_bounds_safety = false;
+ bool violates_initialization_safety = false;
+ bool suppress_move_from_last_use = false;
+
+ declaration_node const* having_signature_emitted = {};
+
+ declaration_node const* generating_assignment_from = {};
+ declaration_node const* generating_move_from = {};
+ declaration_node const* generating_postfix_inc_dec_from = {};
+ bool emitting_that_function = false;
+ bool emitting_move_that_function = false;
+ std::vector<token const*> already_moved_that_members = {};
+
+ struct arg_info {
+ passing_style pass = passing_style::in;
+ token const* ptoken = {};
+ };
+ std::vector<arg_info> current_args = { {} };
+
+ struct active_using_declaration {
+ token const* identifier = {};
+
+ explicit active_using_declaration(using_statement_node const& n) {
+ if (auto id = get_if<id_expression_node::qualified>(&n.id->id)) {
+ identifier = (*id)->ids.back().id->identifier;
+ }
+ }
+ };
+
+ using source_order_name_lookup_res =
+ std::optional<std::variant<declaration_node const*, active_using_declaration>>;
+
+ // Stack of the currently active nested declarations we're inside
+ std::vector<declaration_node const*> current_declarations = { {} };
+
+ // Stack of the currently active names for source order name lookup:
+ // Like 'current_declarations' + also parameters and using declarations
+ std::vector<source_order_name_lookup_res::value_type> current_names = { {} };
+
+ // Maintain a stack of the functions we're currently processing, which can
+ // be up to MaxNestedFunctions in progress (if we run out, bump the Max).
+ // The main reason for this is to be able to pass function_info's, especially
+ // their .epilog, by reference for performance while still having lifetime safety
+ struct function_info
+ {
+ declaration_node const* decl = {};
+ function_type_node const* func = {};
+ declaration_node::declared_value_set_funcs declared_value_set_functions = {};
+ function_prolog prolog = {};
+ std::vector<std::string> epilog = {};
+
+ function_info(
+ declaration_node const* decl_,
+ function_type_node const* func_,
+ declaration_node::declared_value_set_funcs declared_value_set_functions_
+ )
+ : decl{decl_}
+ , func{func_}
+ , declared_value_set_functions{declared_value_set_functions_}
+ { }
+ };
+ class current_functions_
+ {
+ std::deque<function_info> list = { {} };
+ public:
+ auto push(
+ declaration_node const* decl,
+ function_type_node const* func,
+ declaration_node::declared_value_set_funcs thats
+ ) {
+ list.emplace_back(decl, func, thats);
+ }
+
+ auto pop() {
+ list.pop_back();
+ }
+
+ auto back() -> function_info& {
+ assert(!empty());
+ return list.back();
+ }
+
+ auto empty() -> bool {
+ return list.empty();
+ }
+ };
+ current_functions_ current_functions;
+
+ // For lowering
+ //
+ positional_printer printer;
+ bool in_definite_init = false;
+ bool in_parameter_list = false;
+
+ std::string function_return_name;
+ struct function_return {
+ parameter_declaration_list_node* param_list;
+ passing_style pass;
+ bool is_deduced;
+
+ function_return(
+ parameter_declaration_list_node* param_list_,
+ passing_style pass_ = passing_style::invalid,
+ bool is_deduced_ = false
+ )
+ : param_list{param_list_}
+ , pass{pass_}
+ , is_deduced{is_deduced_}
+ { }
+ };
+ std::vector<function_return> function_returns;
+ parameter_declaration_list_node single_anon;
+ // special value - hack for now to note single-anon-return type kind in this function_returns working list
+ std::vector<std::string> function_requires_conditions;
+
+ struct iter_info {
+ iteration_statement_node const* stmt;
+ bool used = false;
+ };
+ std::vector<iter_info> iteration_statements;
+
+ std::vector<bool> in_non_rvalue_context = { false };
+ std::vector<bool> need_expression_list_parens = { true };
+ auto push_need_expression_list_parens( bool b ) -> void { need_expression_list_parens.push_back(b); }
+ auto pop_need_expression_list_parens() -> void { assert(std::ssize(need_expression_list_parens) > 1);
+ need_expression_list_parens.pop_back(); }
+ auto should_add_expression_list_parens() -> bool { assert(!need_expression_list_parens.empty());
+ return need_expression_list_parens.back(); }
+ auto consumed_expression_list_parens() -> void { if( std::ssize(need_expression_list_parens) > 1 )
+ need_expression_list_parens.back() = false; }
+
+public:
+ //-----------------------------------------------------------------------
+ // Constructor
+ //
+ // filename the source file to be processed
+ //
+ cppfront(std::string const& filename)
+ : sourcefile{ filename }
+ , source { errors }
+ , tokens { errors }
+ , parser { errors }
+ , sema { errors }
+ {
+ // "Constraints enable creativity in the right directions"
+ // sort of applies here
+ //
+ if (
+ !sourcefile.ends_with(".cpp2")
+ && !sourcefile.ends_with(".h2")
+ )
+ {
+ errors.emplace_back(
+ source_position(-1, -1),
+ "source filename must end with .cpp2 or .h2: " + sourcefile
+ );
+ }
+
+ // Load the program file into memory
+ //
+ else if (!source.load(sourcefile))
+ {
+ if (errors.empty()) {
+ errors.emplace_back(
+ source_position(-1, -1),
+ "file not found: " + sourcefile
+ );
+ }
+ source_loaded = false;
+ }
+
+ else
+ {
+ // Tokenize
+ //
+ tokens.lex(source.get_lines());
+
+ // Parse
+ //
+ try
+ {
+ for (auto const& [line, entry] : tokens.get_map()) {
+ if (!parser.parse(entry, tokens.get_generated())) {
+ errors.emplace_back(
+ source_position(line, 0),
+ "parse failed for section starting here",
+ false,
+ true // a noisy fallback error message
+ );
+ }
+ }
+
+ // Sema
+ parser.visit(sema);
+ if (!sema.apply_local_rules()) {
+ violates_initialization_safety = true;
+ }
+ }
+ catch (std::runtime_error& e) {
+ errors.emplace_back(
+ source_position(-1, -1),
+ e.what()
+ );
+ }
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ // lower_to_cpp1
+ //
+ // Emits the target file with the last '2' stripped
+ //
+ struct lower_to_cpp1_ret {
+ lineno_t cpp1_lines = 0;
+ lineno_t cpp2_lines = 0;
+ };
+ auto lower_to_cpp1()
+ -> lower_to_cpp1_ret
+ {
+ auto ret = lower_to_cpp1_ret{};
+
+ // Only lower to Cpp1 if we haven't already encountered errors
+ if (!errors.empty()) {
+ return {};
+ }
+
+ // Now we'll open the Cpp1 file
+ auto cpp1_filename = sourcefile.substr(0, std::ssize(sourcefile) - 1);
+ if (!flag_cpp1_filename.empty()) {
+ cpp1_filename = flag_cpp1_filename; // use override if present
+ }
+
+ printer.open(
+ sourcefile,
+ cpp1_filename,
+ tokens.get_comments(),
+ source,
+ parser
+ );
+ if (!printer.is_open()) {
+ errors.emplace_back(
+ source_position{},
+ "could not open output file " + cpp1_filename
+ );
+ return {};
+ }
+
+ // Generate a reasonable macroized name
+ auto cpp1_FILENAME = to_upper_and_underbar(cpp1_filename);
+
+
+ //---------------------------------------------------------------------
+ // Do lowered file prolog
+ //
+ // Only emit extra lines if we actually have Cpp2, because
+ // we want Cpp1-only files to pass through with zero changes
+ // (unless the user requested import/include of std)
+ if (
+ source.has_cpp2()
+ || flag_import_std
+ || flag_include_std
+ )
+ {
+ if (flag_emit_cppfront_info) {
+ printer.print_extra(
+ "\n// Generated by cppfront "
+ #include "version.info"
+ " build "
+ #include "build.info"
+ );
+ }
+ printer.print_extra( "\n" );
+ if (cpp1_filename.back() == 'h') {
+ printer.print_extra( "#ifndef " + cpp1_FILENAME+"_CPP2\n");
+ printer.print_extra( "#define " + cpp1_FILENAME+"_CPP2" + "\n\n" );
+ }
+
+ if (flag_use_source_location) {
+ printer.print_extra( "#define CPP2_USE_SOURCE_LOCATION Yes\n" );
+ }
+
+ if (flag_include_std) {
+ printer.print_extra( "#define CPP2_INCLUDE_STD Yes\n" );
+ }
+ else if (flag_import_std) {
+ printer.print_extra( "#define CPP2_IMPORT_STD Yes\n" );
+ }
+
+ if (flag_no_exceptions) {
+ printer.print_extra( "#define CPP2_NO_EXCEPTIONS Yes\n" );
+ }
+
+ if (flag_no_rtti) {
+ printer.print_extra( "#define CPP2_NO_RTTI Yes\n" );
+ }
+ }
+
+ auto map_iter = tokens.get_map().cbegin();
+ auto hpp_includes = std::string{};
+
+
+ //---------------------------------------------------------------------
+ // Do phase0_type_decls
+ assert(printer.get_phase() == printer.phase0_type_decls);
+
+ if (
+ source.has_cpp2()
+ && !flag_clean_cpp1
+ )
+ {
+ printer.print_extra( "\n//=== Cpp2 type declarations ====================================================\n\n" );
+ }
+
+ if (
+ !tokens.get_map().empty()
+ || flag_import_std
+ || flag_include_std
+ )
+ {
+ printer.print_extra( "\n#include \"cpp2util.h\"\n\n" );
+ }
+
+ if (
+ source.has_cpp2()
+ && !flag_clean_cpp1
+ )
+ {
+ printer.reset_line_to(1, true);
+ }
+
+ for (auto& section : tokens.get_map())
+ {
+ assert (!section.second.empty());
+
+ // Get the parse tree for this section and emit each forward declaration
+ auto decls = parser.get_parse_tree_declarations_in_range(section.second);
+ for (auto& decl : decls) {
+ assert(decl);
+ emit(*decl);
+ }
+ }
+
+
+ //---------------------------------------------------------------------
+ // Do phase1_type_defs_func_decls
+ //
+ printer.finalize_phase();
+ printer.next_phase();
+
+ if (
+ source.has_cpp2()
+ && !flag_clean_cpp1
+ )
+ {
+ printer.print_extra( "\n//=== Cpp2 type definitions and function declarations ===========================\n\n" );
+ printer.reset_line_to(1, true);
+ }
+
+ assert (printer.get_phase() == positional_printer::phase1_type_defs_func_decls);
+ for (
+ lineno_t curr_lineno = 0;
+ auto const& line : source.get_lines()
+ )
+ {
+ // Skip dummy line we added to make 0-vs-1-based offsets readable
+ if (curr_lineno != 0)
+ {
+ // If it's a Cpp1 line, emit it
+ if (line.cat != source_line::category::cpp2)
+ {
+ if (
+ source.has_cpp2()
+ && line.cat != source_line::category::preprocessor
+ )
+ {
+ ++ret.cpp2_lines;
+ }
+ else
+ {
+ ++ret.cpp1_lines;
+ }
+
+ if (
+ flag_cpp2_only
+ && !line.text.empty()
+ && line.cat != source_line::category::comment
+ && line.cat != source_line::category::import
+ )
+ {
+ if (line.cat == source_line::category::preprocessor) {
+ if (!line.text.ends_with(".h2\"")) {
+ errors.emplace_back(
+ source_position(curr_lineno, 1),
+ "pure-cpp2 switch disables the preprocessor, including #include (except of .h2 files) - use import instead (note: 'import std;' is implicit in -pure-cpp2)"
+ );
+ return {};
+ }
+ }
+ else {
+ errors.emplace_back(
+ source_position(curr_lineno, 1),
+ "pure-cpp2 switch disables Cpp1 syntax"
+ );
+ return {};
+ }
+ }
+
+ if (
+ line.cat == source_line::category::preprocessor
+ && line.text.ends_with(".h2\"")
+ )
+ {
+ // Strip off the 2"
+ auto h_include = line.text.substr(0, line.text.size()-2);
+ printer.print_cpp1( h_include + "\"", curr_lineno );
+ hpp_includes += h_include + "pp\"\n";
+ }
+ else {
+ printer.print_cpp1( line.text, curr_lineno );
+ }
+ }
+
+ // If it's a Cpp2 line...
+ else {
+ ++ret.cpp2_lines;
+
+ // We should be in a position to emit a set of Cpp2 declarations
+ if (
+ map_iter != tokens.get_map().cend()
+ && map_iter->first /*line*/ <= curr_lineno
+ )
+ {
+ // We should be here only when we're at exactly the first line of a Cpp2 section
+ assert (map_iter->first == curr_lineno);
+ assert (!map_iter->second.empty());
+
+ // Get the parse tree for this section and emit each forward declaration
+ auto decls = parser.get_parse_tree_declarations_in_range(map_iter->second);
+ for (auto& decl : decls) {
+ assert(decl);
+ emit(*decl);
+ }
+ ++map_iter;
+ }
+ }
+ }
+ ++curr_lineno;
+ }
+
+ // We can stop here if there's no Cpp2 code -- a file with no Cpp2
+ // should have perfect passthrough verifiable with diff, including
+ // that we didn't misidentify anything as Cpp2 (even in the
+ // presence of nonstandard vendor extensions)
+ //
+ if (!source.has_cpp2()) {
+ assert(ret.cpp2_lines == 0);
+ return ret;
+ }
+
+ // If there is Cpp2 code, we have more to do...
+
+ // First, if this is a .h2 and in a -pure-cpp2 compilation,
+ // we need to switch filenames
+ if (
+ cpp1_filename.back() == 'h'
+ && flag_cpp2_only
+ )
+ {
+ printer.print_extra( "\n#endif\n" );
+
+ printer.reopen();
+ if (!printer.is_open()) {
+ errors.emplace_back(
+ source_position{},
+ "could not open second output file " + cpp1_filename
+ );
+ return {};
+ }
+
+ printer.print_extra( "\n#ifndef " + cpp1_FILENAME+"_CPP2" );
+ printer.print_extra( "\n#error This file is part of a '.h2' header compiled to be consumed from another -pure-cpp2 file. To use this file, write '#include \"" + cpp1_filename + "2\"' in a '.h2' or '.cpp2' file compiled with -pure-cpp2." );
+ printer.print_extra( "\n#endif\n" );
+
+ cpp1_FILENAME += "PP";
+ printer.print_extra( "\n#ifndef " + cpp1_FILENAME+"_CPP2" );
+ printer.print_extra( "\n#define " + cpp1_FILENAME+"_CPP2" + "\n\n" );
+
+ printer.print_extra( hpp_includes );
+ }
+
+
+ //---------------------------------------------------------------------
+ // Do phase2_func_defs
+ //
+ printer.finalize_phase();
+ printer.next_phase();
+
+ if (
+ source.has_cpp2()
+ && !flag_clean_cpp1
+ )
+ {
+ printer.print_extra( "\n//=== Cpp2 function definitions =================================================\n\n" );
+ printer.reset_line_to(1, true);
+ }
+
+ for (auto& section : tokens.get_map())
+ {
+ assert (!section.second.empty());
+
+ // Get the parse tree for this section and emit each forward declaration
+ auto decls = parser.get_parse_tree_declarations_in_range(section.second);
+ for (auto& decl : decls) {
+ assert(decl);
+ emit(*decl);
+ }
+ }
+
+ if (cpp1_filename.back() == 'h') {
+ printer.print_extra( "\n#endif" );
+ }
+
+ printer.finalize_phase( true );
+
+ // Finally, some debug checks
+ assert(
+ (!errors.empty() || tokens.num_unprinted_comments() == 0)
+ && "ICE: not all comments were printed"
+ );
+
+ return ret;
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ // emit() functions - each emits a kind of node
+ //
+ // The body often mirrors the node's visit() function, unless customization
+ // is needed where Cpp1 and Cpp2 have different grammar orders
+ //
+
+ void print_to_string(
+ std::string* str,
+ auto& i,
+ auto... more
+ )
+ {
+ // Quick special-purpose state preservation... this tactical hack
+ // is fine for now, but if needed more then generalize this
+ auto state1 = need_expression_list_parens;
+ auto state2 = already_moved_that_members;
+
+ printer.emit_to_string(str);
+ emit(i, more...);
+ printer.emit_to_string();
+
+ // Restore state
+ need_expression_list_parens.swap(state1);
+ already_moved_that_members .swap(state2);
+ };
+
+ auto print_to_string(
+ auto& i,
+ auto... more
+ )
+ -> std::string
+ {
+ auto print = std::string{};
+ print_to_string(&print, i, more...);
+ return print;
+ };
+
+ //-----------------------------------------------------------------------
+ // try_emit
+ //
+ // Helper to emit whatever is in a variant where each
+ // alternative is a smart pointer
+ //
+ template <int I>
+ auto try_emit(
+ auto& v,
+ auto&&... more
+ )
+ -> void
+ {
+ if (v.index() == I) {
+ auto const& alt = std::get<I>(v);
+ assert (alt);
+ emit (*alt, CPP2_FORWARD(more)...);
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ token const& n,
+ bool is_qualified = false,
+ source_position pos = {}
+ )
+ -> void
+ { STACKINSTR
+ if (pos == source_position{}) {
+ pos = n.position();
+ }
+
+ // Implicit "cpp2::" qualification of Cpp2 fixed-width type aliases
+ // and cpp2::finally
+ if (
+ !is_qualified
+ && (
+ n.type() == lexeme::Cpp2FixedType
+ || n == "finally"
+ )
+ )
+ {
+ printer.print_cpp2("cpp2::", pos);
+ }
+
+ // 'this' is not a pointer
+ if (n == "this") {
+ printer.print_cpp2("(*this)", pos);
+ }
+ // Reclaim the alternative names and some keywords for users
+ else if (
+ n == "and"
+ || n == "and_eq"
+ || n == "bitand"
+ || n == "bitor"
+ || n == "compl"
+ || n == "not"
+ || n == "not_eq"
+ || n == "or"
+ || n == "or_eq"
+ || n == "xor"
+ || n == "xor_eq"
+ || n == "new"
+ || n == "class"
+ || n == "struct"
+ || n == "enum"
+ || n == "union"
+ )
+ {
+ printer.print_cpp2("cpp2_"+n.to_string(), pos);
+ }
+ else {
+ printer.print_cpp2(n, pos, true);
+ }
+
+ in_definite_init = is_definite_initialization(&n);
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ literal_node const& n,
+ source_position pos = {}
+ )
+ -> void
+ { STACKINSTR
+ if (pos == source_position{}) {
+ pos = n.position();
+ }
+
+ assert(n.literal);
+ emit(*n.literal);
+ if (n.user_defined_suffix) {
+ emit(*n.user_defined_suffix);
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ unqualified_id_node const& n,
+ bool in_synthesized_multi_return = false,
+ bool is_local_name = true,
+ bool is_qualified = false
+ )
+ -> void
+ { STACKINSTR
+ auto last_use = is_definite_last_use(n.identifier);
+
+ bool add_forward =
+ last_use
+ && last_use->is_forward
+ && !in_non_rvalue_context.back();
+
+ bool add_move =
+ !add_forward
+ && (
+ in_synthesized_multi_return
+ || (last_use && !suppress_move_from_last_use)
+ )
+ && !in_non_rvalue_context.back();
+
+ if (
+ add_move
+ && *(n.identifier - 1) == "return"
+ && *(n.identifier + 1) == ";"
+ )
+ {
+ add_move = false;
+ }
+
+ if (
+ emitting_move_that_function
+ && *n.identifier == "that"
+ )
+ {
+ add_move = true;
+ }
+
+ // For an explicit 'forward' apply forwarding to correct identifier
+ assert (!current_args.empty());
+ if (current_args.back().pass == passing_style::forward) {
+ add_forward = current_args.back().ptoken == n.identifier;
+ }
+
+ if (add_move) {
+ printer.print_cpp2("std::move(", n.position());
+ }
+ if (add_forward) {
+ printer.print_cpp2("CPP2_FORWARD(", {n.position().lineno, n.position().colno - 8});
+ }
+
+ assert(n.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);
+ auto first = true;
+ for (auto& a : n.template_args) {
+ if (!first) {
+ printer.print_cpp2(",", a.comma);
+ }
+ first = false;
+ try_emit<template_argument::expression>(a.arg);
+ try_emit<template_argument::type_id >(a.arg);
+ }
+ printer.print_cpp2(">", n.close_angle);
+ }
+
+ in_definite_init = is_definite_initialization(n.identifier);
+ if (
+ !in_definite_init
+ && !in_parameter_list
+ )
+ {
+ if (auto decl = sema.get_declaration_of(*n.identifier);
+ is_local_name
+ && !(*n.identifier == "this")
+ && !(*n.identifier == "that")
+ && decl
+ && (
+ in_synthesized_multi_return
+ // note pointer equality: if we're not in the actual declaration of n.identifier
+ || decl->identifier != n.identifier
+ )
+ // and this variable was uninitialized
+ && !decl->initializer
+ // and it's either a non-parameter or an out parameter
+ && (
+ !decl->parameter
+ || (
+ decl->parameter
+ && decl->parameter->pass == passing_style::out
+ )
+ )
+ )
+ {
+ printer.print_cpp2(".value()", n.position());
+ }
+ }
+ else if (in_synthesized_multi_return) {
+ printer.print_cpp2(".value()", n.position());
+ }
+
+ if (
+ add_move
+ || add_forward
+ )
+ {
+ printer.print_cpp2(")", n.position());
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ qualified_id_node const& n,
+ bool include_unqualified_id = true
+ )
+ -> void
+ { STACKINSTR
+ if (!sema.check(n)) {
+ return;
+ }
+
+ // Implicit "cpp2::" qualification of "unique.new" and "shared.new"
+ if (
+ n.ids.size() == 2
+ && (
+ *n.ids[0].id->identifier == "unique"
+ || *n.ids[0].id->identifier == "shared"
+ )
+ && *n.ids[1].scope_op == "."
+ && *n.ids[1].id->identifier == "new"
+ )
+ {
+ printer.print_cpp2("cpp2::", n.position());
+ }
+
+ auto ident = std::string{};
+ printer.emit_to_string(&ident);
+
+ 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, false, true, true); // inform the unqualified-id that it's qualified
+ }
+
+ printer.emit_to_string();
+ printer.print_cpp2( ident, n.position() );
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ type_id_node const& n,
+ source_position pos = {}
+ )
+ -> void
+ { STACKINSTR
+ if (pos == source_position{}) {
+ pos = n.position();
+ }
+
+ if (n.is_wildcard()) {
+ printer.print_cpp2("auto", pos);
+ }
+ else {
+ try_emit<type_id_node::unqualified>(n.id, false, false);
+ try_emit<type_id_node::qualified >(n.id);
+ try_emit<type_id_node::keyword >(n.id);
+ }
+
+ for (auto i = n.pc_qualifiers.rbegin(); i != n.pc_qualifiers.rend(); ++i) {
+ if ((**i) == "const") { printer.print_cpp2(" ", pos); }
+ emit(**i, false, pos);
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ id_expression_node const& n,
+ bool is_local_name = true
+ )
+ -> void
+ { STACKINSTR
+ try_emit<id_expression_node::qualified >(n.id);
+ try_emit<id_expression_node::unqualified>(n.id, false, is_local_name);
+ }
+
+
+ auto emit_prolog_mem_inits(
+ function_prolog const& prolog,
+ colno_t indent
+ )
+ -> void
+ { STACKINSTR
+ for (auto& line : prolog.mem_inits) {
+ printer.print_extra("\n");
+ printer.print_extra(pad(indent-1));
+ printer.print_extra(line);
+ }
+ }
+
+ auto emit_prolog_statements(
+ function_prolog const& prolog,
+ colno_t indent
+ )
+ -> void
+ { STACKINSTR
+ for (auto& line : prolog.statements) {
+ printer.print_extra("\n");
+ printer.print_extra(pad(indent-1));
+ printer.print_extra(line);
+ }
+ }
+
+ auto emit_epilog_statements(
+ std::vector<std::string> const& epilog,
+ colno_t indent
+ )
+ -> void
+ { STACKINSTR
+ for (auto& line : epilog) {
+ printer.print_extra("\n");
+ printer.print_extra(pad(indent-1));
+ printer.print_extra(line);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ compound_statement_node const& n,
+ function_prolog const& function_prolog = {},
+ std::vector<std::string> const& function_epilog = {}
+ )
+ -> void
+ { STACKINSTR
+ emit_prolog_mem_inits(function_prolog, n.body_indent+1);
+
+ printer.print_cpp2( "{", n.open_brace );
+
+ emit_prolog_statements(function_prolog, n.body_indent+1);
+
+ for (auto const& x : n.statements) {
+ assert(x);
+ emit(*x);
+ }
+
+ emit_epilog_statements( function_epilog, n.body_indent+1);
+
+ printer.print_cpp2( "}", n.close_brace );
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ inspect_expression_node const& n,
+ bool is_expression
+ )
+ -> void
+ { STACKINSTR
+ auto constexpr_qualifier = std::string{};
+ if (n.is_constexpr) {
+ constexpr_qualifier = "constexpr ";
+ }
+
+ // If this is an expression, it will have an explicit result type,
+ // and we need to start the lambda that we'll immediately invoke
+ auto result_type = std::string{};
+ if (is_expression) {
+ assert(n.result_type);
+ printer.emit_to_string(&result_type);
+ emit(*n.result_type);
+ printer.emit_to_string();
+ printer.print_cpp2("[&] () -> " + result_type + " ", n.position());
+ }
+ printer.print_cpp2("{ " + constexpr_qualifier + "auto&& _expr = ", n.position());
+
+ assert(n.expression);
+ emit(*n.expression);
+ printer.print_cpp2(";", n.position());
+
+ assert(
+ n.identifier
+ && *n.identifier == "inspect"
+ );
+
+ assert(!n.alternatives.empty());
+ auto found_wildcard = false;
+
+ for (auto first = true; auto&& alt : n.alternatives)
+ {
+ assert(alt && alt->is_as_keyword);
+ if (!first) {
+ printer.print_cpp2("else ", alt->position());
+ }
+ first = false;
+
+ auto id = std::string{};
+ printer.emit_to_string(&id);
+
+ if (alt->type_id) {
+ emit(*alt->type_id);
+ }
+ else {
+ assert(alt->value);
+ emit(*alt->value);
+ }
+ printer.emit_to_string();
+
+ assert (
+ *alt->is_as_keyword == "is"
+ || *alt->is_as_keyword == "as"
+ );
+ // TODO: pick up 'as' next, for now just do 'is'
+
+ if (*alt->is_as_keyword == "is")
+ {
+ // Stringize the expression-statement now...
+ auto statement = std::string{};
+ printer.emit_to_string(&statement);
+ emit(*alt->statement);
+ printer.emit_to_string();
+ // ... and jettison the final ; for an expression-statement
+ while (
+ !statement.empty()
+ && (
+ statement.back() == ';'
+ || isspace(statement.back())
+ )
+ )
+ {
+ statement.pop_back();
+ }
+
+ replace_all( statement, "cpp2::as_<", "cpp2::as<" );
+
+ // If this is an inspect-expression, we'll have to wrap each alternative
+ // in an 'if constexpr' so that its type is ignored for mismatches with
+ // the inspect-expression's type
+ auto return_prefix = std::string{};
+ auto return_suffix = std::string{";"}; // use this to tack the ; back on in the alternative body
+ if (is_expression) {
+ return_prefix = "{ if constexpr( requires{" + statement + ";} ) if constexpr( std::is_convertible_v<CPP2_TYPEOF((" + statement + "))," + result_type + "> ) return ";
+ return_suffix += " }";
+ }
+
+ if (id == "auto") {
+ found_wildcard = true;
+ if (is_expression) {
+ printer.print_cpp2("return ", alt->position());
+ }
+ }
+ else {
+ printer.print_cpp2("if " + constexpr_qualifier, alt->position());
+ if (alt->type_id) {
+ printer.print_cpp2("(cpp2::is<" + id + ">(_expr)) ", alt->position());
+ }
+ else {
+ assert (alt->value);
+ printer.print_cpp2("(cpp2::is(_expr, " + id + ")) ", alt->position());
+ }
+ printer.print_cpp2(return_prefix, alt->position());
+ }
+
+ printer.print_cpp2(statement, alt->position());
+
+ if (
+ is_expression
+ && id != "auto"
+ )
+ {
+ assert(alt->statement->is_expression());
+ printer.print_cpp2("; else return " + result_type + "{}", alt->position());
+ printer.print_cpp2("; else return " + result_type + "{}", alt->position());
+ }
+
+ printer.print_cpp2(return_suffix, alt->position());
+ }
+ else {
+ errors.emplace_back(
+ alt->position(),
+ "(temporary alpha limitation) cppfront is still learning 'inspect' - only simple 'is' alternatives are currently supported"
+ );
+ return;
+ }
+ }
+
+ if (is_expression) {
+ if (!found_wildcard) {
+ errors.emplace_back(
+ n.position(),
+ "an inspect expression must have an `is _` match-anything wildcard alternative"
+ );
+ return;
+ }
+ }
+ else {
+ printer.print_cpp2("}", n.close_brace);
+ }
+
+ // If this is an expression, finally actually invoke the lambda
+ if (is_expression) {
+ printer.print_cpp2("()", n.close_brace);
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(selection_statement_node const& n)
+ -> void
+ { STACKINSTR
+ assert(n.identifier);
+ emit(*n.identifier);
+
+ if (n.is_constexpr) {
+ printer.print_cpp2(" constexpr", n.position());
+ }
+
+ printer.print_cpp2(" (", n.position());
+ printer.add_pad_in_this_line(1);
+
+ assert(n.expression);
+ emit(*n.expression);
+
+ printer.print_cpp2(") ", n.position());
+ printer.add_pad_in_this_line(1);
+
+ assert(n.true_branch);
+ emit(*n.true_branch);
+
+ if (n.has_source_false_branch) {
+ printer.print_cpp2("else ", n.else_pos);
+ emit(*n.false_branch);
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(iteration_statement_node const& n)
+ -> void
+ { STACKINSTR
+ assert(n.identifier);
+ in_non_rvalue_context.push_back(true);
+ auto guard = finally([&]{ in_non_rvalue_context.pop_back(); });
+
+ iteration_statements.push_back({ &n, false});
+ auto labelname = labelized_position(n.label);
+
+ // Handle while
+ //
+ if (*n.identifier == "while") {
+ assert(
+ n.condition
+ && n.statements
+ && !n.range
+ && !n.body
+ );
+
+ // We emit Cpp2 while loops as Cpp2 for loops if there's a "next" clause
+ if (!n.next_expression) {
+ printer.print_cpp2("while( ", n.position());
+ emit(*n.condition);
+ }
+ else {
+ printer.print_cpp2("for( ; ", n.position());
+ emit(*n.condition);
+ printer.print_cpp2("; ", n.position());
+ printer.add_pad_in_this_line(-10);
+ emit(*n.next_expression);
+ }
+ printer.print_cpp2(" ) ", n.position());
+ if (!labelname.empty()) {
+ printer.print_extra("{");
+ }
+ emit(*n.statements);
+ if (!labelname.empty()) {
+ printer.print_extra(" CPP2_CONTINUE_BREAK("+labelname+") }");
+ }
+ }
+
+ // Handle do
+ //
+ else if (*n.identifier == "do") {
+ assert(
+ n.condition
+ && n.statements
+ && !n.range
+ && !n.body
+ );
+
+ printer.print_cpp2("do ", n.position());
+ if (!labelname.empty()) {
+ printer.print_extra("{");
+ }
+ emit(*n.statements);
+ if (!labelname.empty()) {
+ printer.print_extra(" CPP2_CONTINUE_BREAK("+labelname+") }");
+ }
+ printer.print_cpp2(" while ( ", n.position());
+ if (n.next_expression) {
+ // Gotta say, this feels kind of nifty... short-circuit eval
+ // and smuggling work into a condition via a lambda, O my...
+ printer.print_cpp2("[&]{ ", n.position());
+ emit(*n.next_expression);
+ printer.print_cpp2(" ; return true; }() && ", n.position());
+ }
+ emit(*n.condition);
+ printer.print_cpp2(");", n.position());
+ }
+
+ // Handle for
+ //
+ else if (*n.identifier == "for") {
+ assert(
+ !n.condition
+ && !n.statements
+ && n.range
+ && n.parameter
+ && n.body
+ );
+
+ // Note: This used to emit cpp2_range as a range-for-loop scope variable,
+ // but some major compilers seem to have random troubles with that;
+ // the workaround to avoid their bugs for now is to emit a { } block
+ // around the Cpp1 range-for and make the scope variable a normal local
+
+ printer.print_cpp2("for ( ", n.position());
+
+ emit(*n.parameter);
+
+ printer.print_cpp2(" : ", n.position());
+
+ // If this expression is just a single expression-list, we can
+ // take over direct control of emitting it without needing to
+ // go through the whole grammar, and surround it with braces
+ if (n.range->is_expression_list()) {
+ printer.print_cpp2( "{ ", n.position() );
+ emit(*n.range->get_expression_list(), false);
+ printer.print_cpp2( " }", n.position() );
+ }
+ // Otherwise, just emit the general expression as usual
+ else {
+ emit(*n.range);
+ }
+
+ printer.print_cpp2(" ) ", n.position());
+ if (!labelname.empty()) {
+ printer.print_extra("{");
+ }
+
+ // If there's a next-expression, smuggle it in via a nested do/while(false) loop
+ // (nested "continue" will work, but "break" won't until we do extra work to implement
+ // that using a flag and implementing "break" as "_for_break = true; continue;")
+ if (n.next_expression) {
+ printer.print_cpp2(" { do ", n.position());
+ }
+
+ assert(n.body);
+ emit(*n.body);
+
+ if (n.next_expression) {
+ printer.print_cpp2(" while (false); ", n.position());
+ emit(*n.next_expression);
+ printer.print_cpp2("; }", n.position());
+ }
+
+ printer.print_cpp2("", n.position());
+ if (!labelname.empty()) {
+ printer.print_extra(" CPP2_CONTINUE_BREAK("+labelname+") }");
+ }
+ }
+
+ else {
+ assert(!"ICE: unexpected case");
+ }
+
+ assert (iteration_statements.back().stmt);
+ if (
+ iteration_statements.back().stmt->label
+ && !iteration_statements.back().used
+ )
+ {
+ auto name = iteration_statements.back().stmt->label->to_string();
+ errors.emplace_back(
+ iteration_statements.back().stmt->position(),
+ name + ": a named loop must have its name used (did you forget 'break " + name + ";' or 'continue " + name + "';?)"
+ );
+ }
+
+ iteration_statements.pop_back();
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(return_statement_node const& n)
+ -> void
+ { STACKINSTR
+ assert (!current_functions.empty());
+ if (current_functions.back().func->has_postconditions()) {
+ printer.print_cpp2( "cpp2_finally_presuccess.run(); ", n.position() );
+ }
+
+ assert(n.identifier);
+ assert(*n.identifier == "return");
+ printer.print_cpp2("return ", n.position());
+
+ // Return with expression == single anonymous return type
+ //
+ if (n.expression)
+ {
+ assert(!current_functions.empty());
+ auto is_forward_return =
+ !function_returns.empty()
+ && function_returns.back().pass == passing_style::forward;
+ auto is_deduced_return =
+ !function_returns.empty()
+ && function_returns.back().is_deduced;
+
+ // If we're doing a forward return of a single-token name
+ if (auto tok = n.expression->expr->get_postfix_expression_node()->expr->get_token();
+ tok
+ && is_forward_return
+ )
+ {
+ // Ensure we're not returning a local or an in/move parameter
+ auto is_parameter_name = current_functions.back().decl->has_parameter_named(*tok);
+ if (
+ is_parameter_name
+ && (
+ current_functions.back().decl->has_in_parameter_named(*tok)
+ || current_functions.back().decl->has_move_parameter_named(*tok)
+ )
+ )
+ {
+ errors.emplace_back(
+ n.position(),
+ "a 'forward' return type cannot return an 'in' or 'move' parameter"
+ );
+ return;
+ }
+ else if (
+ !is_parameter_name
+ && sema.get_declaration_of(*tok)
+ )
+ {
+ errors.emplace_back(
+ n.position(),
+ "a 'forward' return type cannot return a local variable"
+ );
+ return;
+ } else if (
+ is_literal(tok->type()) || n.expression->expr->is_result_a_temporary_variable()
+ )
+ {
+ errors.emplace_back(
+ n.position(),
+ "a 'forward' return type cannot return a temporary variable"
+ );
+ return;
+ }
+ }
+
+ // If this expression is just a single expression-list, we can
+ // take over direct control of emitting it without needing to
+ // go through the whole grammar, and surround it with braces
+ if (n.expression->is_expression_list()) {
+ if (!is_deduced_return) {
+ printer.print_cpp2( "{ ", n.position() );
+ }
+ emit(*n.expression->get_expression_list(), false);
+ if (!is_deduced_return) {
+ printer.print_cpp2( " }", n.position() );
+ }
+ }
+ // Otherwise, just emit the general expression as usual
+ else {
+ emit(*n.expression);
+ }
+
+ if (
+ function_returns.empty()
+ || function_returns.back().param_list != &single_anon
+ )
+ {
+ errors.emplace_back(
+ n.position(),
+ "return statement with expression must be in a function with a single anonymous return value"
+ );
+ return;
+ }
+ }
+
+ else if (
+ !function_returns.empty()
+ && function_returns.back().param_list == &single_anon
+ )
+ {
+ errors.emplace_back(
+ n.position(),
+ "return statement must have an expression in a function with a single anonymous return value"
+ );
+ }
+
+ // Return without expression, could be assignment operator
+ //
+ else if (generating_assignment_from == current_functions.back().decl)
+ {
+ printer.print_cpp2("*this", n.position());
+ }
+
+ // Otherwise, zero or named return values
+ //
+ else if (
+ !function_returns.empty()
+ && function_returns.back().param_list
+ )
+ {
+ auto& parameters = function_returns.back().param_list->parameters;
+
+ auto stmt = std::string{};
+
+ // Put braces only around multiple named returns, which are a struct
+ // - single named returns are emitted as ordinary returns, and extra
+ // { } would be legal but generate a noisy warning on some compilers
+ if (std::ssize(parameters) > 1) {
+ stmt += std::string(" { ");
+ }
+
+ for (bool first = true; auto& param : parameters) {
+ if (!first) {
+ stmt += ", ";
+ }
+ first = false;
+ assert(param->declaration->identifier);
+
+ printer.emit_to_string(&stmt);
+ emit(*param->declaration->identifier, true);
+ printer.emit_to_string();
+ }
+
+ if (std::ssize(parameters) > 1) {
+ stmt += std::string(" }");
+ }
+
+ printer.print_cpp2(stmt, n.position());
+ }
+
+ printer.print_cpp2("; ", n.position());
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(jump_statement_node const& n)
+ -> void
+ { STACKINSTR
+ assert(n.keyword);
+
+ if (n.label) {
+ auto iter_stmt =
+ std::find_if(
+ iteration_statements.begin(),
+ iteration_statements.end(),
+ [&](auto& s){
+ assert(s.stmt);
+ return
+ s.stmt->label
+ && std::string_view{*s.stmt->label} == std::string_view{*n.label}
+ ;
+ }
+ );
+ if (iter_stmt == iteration_statements.end())
+ {
+ errors.emplace_back(
+ n.position(),
+ "a named " + n.keyword->to_string() + " must use the name of an enclosing local loop label"
+ );
+ return;
+ }
+ iter_stmt->used = true;
+ assert((*iter_stmt).stmt->label);
+ printer.print_cpp2(
+ "goto " + to_upper_and_underbar(*n.keyword) + "_" + labelized_position((*iter_stmt).stmt->label) + ";",
+ n.position()
+ );
+ }
+ else {
+ emit(*n.keyword);
+ printer.print_cpp2(";", n.position());
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(using_statement_node const& n)
+ -> void
+ { STACKINSTR
+ assert(n.keyword);
+ emit(*n.keyword);
+
+ if (n.for_namespace) {
+ printer.print_cpp2(" namespace", n.position());
+ } else {
+ current_names.push_back(active_using_declaration{n});
+ }
+
+ printer.print_cpp2(" " + print_to_string(*n.id) + ";", n.position());
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto build_capture_lambda_intro_for(
+ capture_group& captures,
+ source_position pos,
+ bool include_default_reference_capture = false
+ )
+ -> std::string
+ {
+ // First calculate the stringized version of each capture expression
+ // This will let us compare and de-duplicate repeated capture expressions
+ for (auto& cap : captures.members)
+ {
+ assert(cap.capture_expr->cap_grp == &captures);
+ if (cap.str.empty()) {
+ print_to_string(&cap.str, *cap.capture_expr, true);
+ suppress_move_from_last_use = true;
+ print_to_string(&cap.str_suppressed_move, *cap.capture_expr, true);
+ suppress_move_from_last_use = false;
+ }
+ }
+
+ // If move from last use was used on the variable we need to rewrite the str to add std::move
+ // to earlier use of the variable. That will save us from capturing one variable two times
+ // (one with copy and one with std::move).
+ for (auto rit = captures.members.rbegin(); rit != captures.members.rend(); ++rit)
+ {
+ auto is_same_str_suppressed_move = [s=rit->str_suppressed_move](auto& cap){
+ return cap.str_suppressed_move == s;
+ };
+
+ auto rit2 = std::find_if(rit+1, captures.members.rend(), is_same_str_suppressed_move);
+ while (rit2 != captures.members.rend())
+ {
+ rit2->str = rit->str;
+ rit2 = std::find_if(rit2+1, captures.members.rend(), is_same_str_suppressed_move);
+ }
+ }
+
+ // Then build the capture list, ignoring duplicated expressions
+ auto lambda_intro = std::string("[");
+ auto num_captures = 0;
+
+ if (
+ (!current_functions.empty()
+ && current_functions.back().decl->is_function_with_this()
+ && !current_functions.back().decl->parent_is_namespace()
+ )
+ || include_default_reference_capture
+ )
+ {
+ // Note: & is needed (when allowed, not at namespace scope) because a
+ // nested UFCS might be viewed as trying to capture 'this'
+ lambda_intro += "&";
+ ++num_captures;
+ }
+
+ printer.emit_to_string(&lambda_intro);
+
+ auto handled = std::vector<std::string>{};
+ for (auto& cap : captures.members)
+ {
+ // If we haven't handled a capture that looks like this one
+ if (std::find(handled.begin(), handled.end(), cap.str) == handled.end())
+ {
+ // Remember it
+ handled.push_back(cap.str);
+
+ // And handle it
+ if (num_captures != 0) { // not first
+ lambda_intro += ", ";
+ }
+ cap.cap_sym = "_"+std::to_string(num_captures);
+ printer.print_cpp2(cap.cap_sym + " = " + cap.str, pos);
+ }
+ ++num_captures;
+ }
+ printer.emit_to_string();
+ lambda_intro += "]";
+
+ return lambda_intro;
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(primary_expression_node const& n)
+ -> void
+ { STACKINSTR
+ try_emit<primary_expression_node::identifier >(n.expr);
+ try_emit<primary_expression_node::expression_list>(n.expr);
+ try_emit<primary_expression_node::id_expression >(n.expr);
+ try_emit<primary_expression_node::inspect >(n.expr, true);
+ try_emit<primary_expression_node::literal >(n.expr);
+
+ if (n.expr.index() == primary_expression_node::declaration)
+ {
+ // This must be an anonymous declaration
+ auto& decl = std::get<primary_expression_node::declaration>(n.expr);
+ assert(
+ decl
+ && !decl->identifier
+ );
+
+ // Handle an anonymous function
+ if (decl->is_function()) {
+ auto lambda_intro = build_capture_lambda_intro_for(decl->captures, n.position());
+
+ // Handle an anonymous generic function with explicit type list
+ if (decl->template_parameters) {
+ print_to_string(&lambda_intro, *decl->template_parameters, false, true);
+ }
+
+ emit(*decl, lambda_intro);
+ }
+ // Else an anonymous object as 'typeid { initializer }'
+ else {
+ assert(decl->is_object());
+ auto& type_id = std::get<declaration_node::an_object>(decl->type);
+
+ printer.add_pad_in_this_line( -5 );
+
+ emit(*type_id);
+ printer.print_cpp2("{", decl->position());
+
+ if (!decl->initializer) {
+ errors.emplace_back(
+ decl->position(),
+ "an anonymous object declaration must have '=' and an initializer"
+ );
+ return;
+ }
+
+ emit(*decl->initializer, false);
+
+ printer.print_cpp2("}", decl->position());
+ }
+ }
+ }
+
+ // Not yet implemented. TODO: finalize deducing pointer types from parameter lists
+ auto is_pointer_declaration(
+ parameter_declaration_list_node const*,
+ int,
+ int
+ )
+ -> bool
+ {
+ return false;
+ }
+
+ auto is_pointer_declaration(
+ declaration_node const* decl_node,
+ int deref_cnt,
+ int addr_cnt
+ )
+ -> bool
+ {
+ if (!decl_node) {
+ return false;
+ }
+ if (addr_cnt > deref_cnt) {
+ return true;
+ }
+
+ return std::visit([&](auto const& type){
+ return is_pointer_declaration(type.get(), deref_cnt, addr_cnt);
+ }, decl_node->type);
+ }
+
+ auto is_pointer_declaration(
+ function_type_node const* fun_node,
+ int deref_cnt,
+ int addr_cnt
+ )
+ -> bool
+ {
+ if (!fun_node) {
+ return false;
+ }
+ if (addr_cnt > deref_cnt) {
+ return true;
+ }
+
+ return std::visit([&]<typename T>(T const& type){
+ if constexpr (std::is_same_v<T, std::monostate>) {
+ return false;
+ }
+ else if constexpr (std::is_same_v<T, function_type_node::single_type_id>) {
+ return is_pointer_declaration(type.type.get(), deref_cnt, addr_cnt);
+ }
+ else {
+ return is_pointer_declaration(type.get(), deref_cnt, addr_cnt);
+ }
+ }, fun_node->returns);
+ }
+
+ auto is_pointer_declaration(
+ type_id_node const* type_id_node,
+ int deref_cnt,
+ int addr_cnt
+ )
+ -> bool
+ {
+ if (!type_id_node) {
+ return false;
+ }
+ if (addr_cnt > deref_cnt) {
+ return true;
+ }
+
+ if ( type_id_node->dereference_of ) {
+ return is_pointer_declaration(type_id_node->dereference_of, deref_cnt + type_id_node->dereference_cnt, addr_cnt);
+ } else if ( type_id_node->address_of ) {
+ return is_pointer_declaration(type_id_node->address_of, deref_cnt, addr_cnt + 1);
+ }
+
+ int pointer_declarators_cnt = std::count_if(std::cbegin(type_id_node->pc_qualifiers), std::cend(type_id_node->pc_qualifiers), [](auto* q) {
+ return q->type() == lexeme::Multiply;
+ });
+
+ if (
+ pointer_declarators_cnt == 0
+ && type_id_node->suspicious_initialization
+ )
+ {
+ return is_pointer_declaration(type_id_node->suspicious_initialization, deref_cnt, addr_cnt);
+ }
+
+ return (pointer_declarators_cnt + addr_cnt - deref_cnt) > 0;
+ }
+
+ auto is_pointer_declaration(
+ type_node const*,
+ int,
+ int
+ )
+ -> bool
+ {
+ return false;
+ }
+
+ auto is_pointer_declaration(
+ namespace_node const*,
+ int,
+ int
+ )
+ -> bool
+ {
+ return false;
+ }
+
+ auto is_pointer_declaration(
+ alias_node const*,
+ int,
+ int
+ )
+ -> bool
+ {
+ return false;
+ }
+
+ auto is_pointer_declaration(
+ declaration_sym const* decl,
+ int deref_cnt,
+ int addr_cnt
+ )
+ -> bool
+ {
+ if (!decl) {
+ return false;
+ }
+ if (addr_cnt > deref_cnt) {
+ return true;
+ }
+ return is_pointer_declaration(decl->declaration, deref_cnt, addr_cnt);
+ }
+
+ auto is_pointer_declaration(
+ token const* t,
+ int deref_cnt = 0,
+ int addr_cnt = 0
+ )
+ -> bool
+ {
+ if (!t) {
+ return false;
+ }
+ if (addr_cnt > deref_cnt) {
+ return true;
+ }
+ auto decl = sema.get_declaration_of(*t, true);
+ return is_pointer_declaration(decl, deref_cnt, addr_cnt);
+ }
+
+
+ auto source_order_name_lookup(unqualified_id_node const& id)
+ -> source_order_name_lookup_res
+ {
+ for (
+ auto first = current_names.rbegin(), last = current_names.rend() - 1;
+ first != last;
+ ++first
+ )
+ {
+ if (
+ auto decl = get_if<declaration_node const*>(&*first);
+ decl
+ && *decl
+ && (*decl)->has_name(*id.identifier)
+ )
+ {
+ return *decl;
+ }
+ else if (
+ auto using_ = get_if<active_using_declaration>(&*first);
+ using_
+ && using_->identifier
+ && *using_->identifier == *id.identifier
+ )
+ {
+ return *using_;
+ }
+ }
+
+ return {};
+ }
+
+
+ auto lookup_finds_variable_with_placeholder_type_under_initialization(id_expression_node const& n)
+ -> bool
+ {
+ if (!n.is_unqualified())
+ {
+ return false;
+ }
+
+ auto const& id = *get<id_expression_node::unqualified>(n.id);
+ auto lookup = source_order_name_lookup(id);
+
+ if (
+ !lookup
+ || get_if<active_using_declaration>(&*lookup)
+ )
+ {
+ return false;
+ }
+
+ auto decl = get<declaration_node const*>(*lookup);
+ if (
+ decl
+ && decl->has_name(*id.identifier)
+ )
+ {
+ if (
+ !decl->is_object()
+ && !decl->is_object_alias()
+ )
+ {
+ return false;
+ }
+
+ if (decl->is_object()) {
+ auto type = &**get_if<declaration_node::an_object>(&decl->type);
+ return type->is_wildcard()
+ && contains(current_declarations, decl);
+ }
+ auto const& type = (**get_if<declaration_node::an_alias>(&decl->type)).type_id;
+ return (
+ !type
+ || type->is_wildcard()
+ )
+ && contains(current_declarations, decl);
+ }
+
+ return false;
+ }
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ // Note: parameter is not const as we'll fill in the capture .str info
+ postfix_expression_node& n,
+ bool for_lambda_capture = false
+ )
+ -> void
+ { STACKINSTR
+ if (!sema.check(n)) {
+ return;
+ }
+
+ assert(n.expr);
+ last_postfix_expr_was_pointer = false;
+
+ // For a 'move that' parameter, track the members we already moved from
+ // so we can diagnose attempts to move from the same member twice
+ if (
+ emitting_move_that_function
+ && n.expr->get_token()
+ && *n.expr->get_token() == "that"
+ )
+ {
+ if (n.ops.empty()) {
+ if (!already_moved_that_members.empty()) {
+ errors.emplace_back(
+ n.position(),
+ "attempting to move from whole 'that' object after a 'that.member' was already moved from"
+ );
+ return;
+ }
+ // push a sentinel for "all members"
+ already_moved_that_members.push_back(nullptr);
+ }
+ else {
+ auto member = n.ops[0].id_expr->get_token();
+ assert(member);
+
+ for (
+ auto i = already_moved_that_members.begin();
+ i != already_moved_that_members.end();
+ ++i
+ )
+ {
+ if (
+ !*i
+ || **i == *member
+ )
+ {
+ errors.emplace_back(
+ n.position(),
+ "attempting to move twice from 'that." + member->to_string() + "'"
+ );
+ return;
+ }
+ }
+
+ already_moved_that_members.push_back(member);
+ }
+ }
+
+ // Ensure that forwarding postfix-expressions start with a forwarded parameter name
+ //
+ assert (!current_args.empty());
+ if (current_args.back().pass == passing_style::forward)
+ {
+ assert (n.expr->get_token());
+ assert (!current_args.back().ptoken);
+ current_args.back().ptoken = n.expr->get_token();
+ auto decl = sema.get_declaration_of(*current_args.back().ptoken);
+ if (!(decl && decl->parameter && decl->parameter->pass == passing_style::forward))
+ {
+ errors.emplace_back(
+ n.position(),
+ n.expr->get_token()->to_string() + " is not a forwarding parameter name"
+ );
+ }
+ }
+
+ // Check that this isn't pointer arithmentic
+ // (initial partial implementation)
+ if (n.expr->expr.index() == primary_expression_node::id_expression)
+ {
+ auto& id = std::get<primary_expression_node::id_expression>(n.expr->expr);
+ assert(id);
+ if (id->id.index() == id_expression_node::unqualified)
+ {
+ auto& unqual = std::get<id_expression_node::unqualified>(id->id);
+ assert(unqual);
+ // TODO: Generalize this:
+ // - we don't recognize pointer types from Cpp1
+ // - we don't deduce pointer types from parameter_declaration_list_node
+ if ( is_pointer_declaration(unqual->identifier) ) {
+ if (n.ops.empty()) {
+ last_postfix_expr_was_pointer = true;
+ }
+ else
+ {
+ auto op = [&]{
+ if (
+ n.ops.size() >= 2
+ && n.ops[0].op->type() == lexeme::LeftParen
+ )
+ {
+ return n.ops[1].op;
+ }
+ else
+ {
+ return n.ops.front().op;
+ }
+ }();
+
+ if (
+ op->type() == lexeme::PlusPlus
+ || op->type() == lexeme::MinusMinus
+ || op->type() == lexeme::LeftBracket
+ )
+ {
+ errors.emplace_back(
+ op->position(),
+ op->to_string() + " - pointer arithmetic is illegal - use std::span or gsl::span instead"
+ );
+ violates_bounds_safety = true;
+ }
+ else if (
+ op->type() == lexeme::Tilde
+ )
+ {
+ errors.emplace_back(
+ op->position(),
+ op->to_string() + " - pointer bitwise manipulation is illegal - use std::bit_cast to convert to raw bytes first"
+ );
+ }
+ }
+ }
+ }
+ }
+
+ // Simple case: If there are no .ops, just emit the expression
+ if (n.ops.empty()) {
+ emit(*n.expr);
+ return;
+ }
+
+ // Check to see if it's a capture expression that contains $,
+ // and if we're not capturing the expression for the lambda
+ // introducer replace it with the capture name
+ auto captured_part = std::string{};
+ if (
+ n.cap_grp
+ && !for_lambda_capture
+ )
+ {
+ // First stringize ourselves so that we compare equal against
+ // the first *cap_grp .str_suppressed_move that matches us (which is what the
+ // lambda introducer generator used to create a lambda capture)
+ suppress_move_from_last_use = true;
+ auto my_sym = print_to_string(n, true);
+ suppress_move_from_last_use = false;
+
+ auto found = std::find_if(n.cap_grp->members.cbegin(), n.cap_grp->members.cend(), [my_sym](auto& cap) {
+ return cap.str_suppressed_move == my_sym;
+ });
+
+ assert(
+ found != n.cap_grp->members.cend()
+ && "ICE: could not find this postfix-expression in capture group"
+ );
+ // And then emit that capture symbol with number
+ assert (!found->cap_sym.empty());
+ captured_part += found->cap_sym;
+ }
+
+ // Otherwise, we're going to have to potentially do some work to change
+ // some Cpp2 postfix operators to Cpp1 prefix operators, so let's set up...
+ auto prefix = std::vector<text_with_pos>{};
+ auto suffix = std::vector<text_with_pos>{};
+
+ auto last_was_prefixed = false;
+ auto saw_dollar = false;
+
+ struct text_chunks_with_parens_position {
+ std::vector<text_with_pos> text_chunks;
+ source_position open_pos;
+ source_position close_pos;
+ };
+
+ auto args = std::optional<text_chunks_with_parens_position>{};
+
+ auto flush_args = [&] {
+ if (args) {
+ suffix.emplace_back(")", args.value().close_pos);
+ for (auto&& e: args.value().text_chunks) {
+ suffix.push_back(e);
+ }
+ suffix.emplace_back("(", args.value().open_pos);
+ args.reset();
+ }
+ };
+
+ auto print_to_text_chunks = [&](auto& i, auto... more) {
+ auto text = std::vector<text_with_pos>{};
+ printer.emit_to_text_chunks(&text);
+ push_need_expression_list_parens(false);
+ emit(i, more...);
+ pop_need_expression_list_parens();
+ printer.emit_to_text_chunks();
+ return text;
+ };
+
+ for (auto i = n.ops.rbegin(); i != n.ops.rend(); ++i)
+ {
+ assert(i->op);
+
+ // If we already captured a part as a _## lambda capture,
+ // skip the part of this expression before the $ symbol
+ //
+ if (!captured_part.empty()) {
+ if (i->op->type() == lexeme::Dollar) {
+ break;
+ }
+ }
+ // Else skip the part of this expression after the $ symbol
+ else if (for_lambda_capture) {
+ if (i->op->type() == lexeme::Dollar) {
+ saw_dollar = true;
+ continue;
+ }
+ if (!saw_dollar) {
+ continue;
+ }
+ }
+
+ // Going backwards if we found LeftParen it might be UFCS
+ // expr_list is emitted to 'args' for future use
+ if (i->op->type() == lexeme::LeftParen) {
+
+ assert(i->op);
+ assert(i->op_close);
+ 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()) {
+ local_args.text_chunks = print_to_text_chunks(*i->expr_list);
+ }
+
+ flush_args();
+ args.emplace(std::move(local_args));
+ }
+ // Going backwards if we found Dot and there is args variable
+ // it means that it should be handled by UFCS
+ else if(
+ i->op->type() == lexeme::Dot
+ && args
+ // Disable UFCS if name lookup would hard-error (#550).
+ // That happens when it finds that the function identifier being called is the name
+ // of a variable with deduced type and we are in its initializer (e.g., x := y.x();)
+ // So lower it to a member call instead, the only possible valid meaning.
+ && !lookup_finds_variable_with_placeholder_type_under_initialization(*i->id_expr)
+ )
+ {
+ // The function name is the argument to the macro
+ auto funcname = print_to_string(*i->id_expr);
+
+ // First, build the UFCS macro name
+
+ auto ufcs_string = std::string("CPP2_UFCS");
+
+ // If there are template arguments, use the _TEMPLATE version
+ if (std::ssize(i->id_expr->template_arguments()) > 0) {
+ // If it is qualified, use the _QUALIFIED version
+ if (i->id_expr->is_qualified()) {
+ ufcs_string += "_QUALIFIED";
+ // And split the unqualified id in the function name as two macro arguments
+ auto& id = *get<id_expression_node::qualified>(i->id_expr->id);
+ funcname =
+ "("
+ + print_to_string(id, false)
+ + "::),"
+ + print_to_string(*cpp2::assert_not_null(id.ids.back().id), false, true, true);
+ }
+ ufcs_string += "_TEMPLATE";
+ }
+
+ // If we're in an object declaration (i.e., initializer)
+ // at namespace scope, use the _NONLOCAL version
+ //
+ // Note: If there are other cases where code could execute
+ // in a non-local scope where a capture-default for the UFCS
+ // lambda would not be allowed, then add them here
+ if (
+ current_declarations.back()->is_namespace()
+ || (
+ current_declarations.back()->is_object()
+ && current_declarations.back()->parent_is_namespace()
+ )
+ || (
+ (
+ current_declarations.back()->is_alias()
+ || (
+ current_declarations.back()->is_function()
+ && current_declarations.back() == having_signature_emitted
+ )
+ )
+ && (
+ current_declarations.back()->parent_is_namespace()
+ || current_declarations.back()->parent_is_type()
+ )
+ )
+ )
+ {
+ ufcs_string += "_NONLOCAL";
+ }
+
+ // Second, emit the UFCS argument list
+
+ prefix.emplace_back(ufcs_string + "(" + funcname + ")(", args.value().open_pos );
+ suffix.emplace_back(")", args.value().close_pos );
+ if (!args.value().text_chunks.empty()) {
+ for (auto&& e: args.value().text_chunks) {
+ suffix.push_back(e);
+ }
+ suffix.emplace_back(", ", i->op->position());
+ }
+ args.reset();
+ }
+
+ // Handle the Cpp2 postfix operators that are prefix in Cpp1
+ //
+ else if (
+ i->op->type() == lexeme::MinusMinus
+ || i->op->type() == lexeme::PlusPlus
+ || i->op->type() == lexeme::Multiply
+ || i->op->type() == lexeme::Ampersand
+ || i->op->type() == lexeme::Tilde
+ )
+ {
+ // omit some needless parens
+ if (
+ !last_was_prefixed
+ && i != n.ops.rbegin()
+ )
+ {
+ prefix.emplace_back( "(", i->op->position() );
+ }
+ prefix.emplace_back( i->op->to_string(), i->op->position());
+
+ // Enable null dereference checks
+ if (
+ flag_safe_null_pointers
+ && i->op->type() == lexeme::Multiply
+ )
+ {
+ prefix.emplace_back( "cpp2::assert_not_null(", i->op->position() );
+ }
+ if (
+ flag_safe_null_pointers
+ && i->op->type() == lexeme::Multiply
+ )
+ {
+ suffix.emplace_back( ")", i->op->position() );
+ }
+
+ // omit some needless parens
+ if (
+ !last_was_prefixed
+ && i != n.ops.rbegin()
+ )
+ {
+ suffix.emplace_back( ")", i->op->position() );
+ }
+ last_was_prefixed = true;
+ }
+
+ // Handle the other Cpp2 postfix operators that stay postfix in Cpp1 (currently: '...')
+ else if (is_postfix_operator(i->op->type())) {
+ flush_args();
+ suffix.emplace_back( i->op->to_string(), i->op->position());
+ }
+
+ // Handle the suffix operators that remain suffix
+ //
+ else {
+ assert(i->op);
+ last_was_prefixed = false;
+
+ // Enable subscript bounds checks
+ if (
+ flag_safe_subscripts
+ && i->op->type() == lexeme::LeftBracket
+ && std::ssize(i->expr_list->expressions) == 1
+ )
+ {
+ suffix.emplace_back( ")", i->op->position() );
+ }
+ else if (i->op_close) {
+ suffix.emplace_back( i->op_close->to_string(), i->op_close->position() );
+ }
+
+ if (i->id_expr)
+ {
+ if (args) {
+ // If args are stored it means that this is function or method
+ // that is not handled by UFCS and args need to be printed
+ suffix.emplace_back(")", args.value().close_pos);
+ for (auto&& e: args.value().text_chunks) {
+ suffix.push_back(e);
+ }
+ suffix.emplace_back("(", args.value().open_pos);
+ args.reset();
+ }
+
+ auto print = print_to_string(*i->id_expr, false /*not a local name*/);
+ suffix.emplace_back( print, i->id_expr->position() );
+ }
+
+ if (i->expr_list) {
+ auto text = print_to_text_chunks(*i->expr_list);
+ for (auto&& e: text) {
+ suffix.push_back(e);
+ }
+ }
+
+ // Enable subscript bounds checks
+ if (
+ flag_safe_subscripts
+ && i->op->type() == lexeme::LeftBracket
+ && std::ssize(i->expr_list->expressions) == 1
+ )
+ {
+ prefix.emplace_back( "CPP2_ASSERT_IN_BOUNDS(", i->op->position() );
+ suffix.emplace_back( ", ", i->op->position() );
+ }
+ else {
+ suffix.emplace_back( i->op->to_string(), i->op->position() );
+ }
+ }
+ }
+
+ // Print the prefixes (in forward order)
+ for (auto& e : prefix) {
+ printer.print_cpp2(e.text, n.position());
+ }
+
+ // If this is an --, ++, or &, don't add std::move on the lhs
+ // even if this is a definite last use (only do that when an rvalue is okay)
+ if (
+ n.ops.front().op->type() == lexeme::MinusMinus
+ || n.ops.front().op->type() == lexeme::PlusPlus
+ || n.ops.front().op->type() == lexeme::Ampersand
+ )
+ {
+ suppress_move_from_last_use = true;
+ }
+
+ // Now print the core expression -- or the captured_part in its place
+ if (captured_part.empty()) {
+ emit(*n.expr);
+ }
+ else {
+ printer.print_cpp2(captured_part, n.position());
+ }
+ suppress_move_from_last_use = false;
+
+ flush_args();
+
+ // Print the suffixes (in reverse order)
+ while (!suffix.empty()) {
+ printer.print_cpp2(suffix.back().text, suffix.back().pos);
+ suffix.pop_back();
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(prefix_expression_node const& n)
+ -> void
+ { STACKINSTR
+ auto suffix = std::string{};
+ for (auto const& x : n.ops) {
+ assert(x);
+ if (x->type() == lexeme::Not) {
+ printer.print_cpp2("!(", n.position());
+ printer.add_pad_in_this_line(-3);
+ suffix += ")";
+ }
+ else {
+ printer.print_cpp2(*x, x->position());
+ }
+ }
+ assert(n.expr);
+ push_need_expression_list_parens(true);
+ emit(*n.expr);
+ pop_need_expression_list_parens();
+ printer.print_cpp2(suffix, n.position());
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(is_as_expression_node const& n)
+ -> void
+ { STACKINSTR
+ std::string prefix = {};
+ std::string suffix = {};
+
+ auto wildcard_found = false;
+ bool as_on_literal = false;
+
+ assert(
+ n.expr
+ && n.expr->get_postfix_expression_node()
+ && n.expr->get_postfix_expression_node()->expr
+ );
+ {
+ auto& p = n.expr->get_postfix_expression_node()->expr;
+ if (auto t = p->get_token();
+ t
+ && is_literal(t->type())
+ && t->type() != lexeme::StringLiteral
+ && t->type() != lexeme::FloatLiteral
+ && !std::get<primary_expression_node::literal>(p->expr)->user_defined_suffix
+ && std::ssize(n.ops) > 0
+ && *n.ops[0].op == "as"
+ )
+ {
+ as_on_literal = true;
+ }
+ }
+
+ for (
+ auto i = n.ops.rbegin();
+ i != n.ops.rend();
+ ++i
+ )
+ {
+ // If it's "ISORAS type", emit "cpp2::ISORAS<type>(expr)"
+ if (i->type)
+ {
+ if (i->type->is_wildcard()) {
+ wildcard_found = true;
+ if (*i->op != "is") {
+ errors.emplace_back(
+ n.position(),
+ "'as _' wildcard is not allowed, specify a concrete target type instead"
+ );
+ }
+ else if (std::ssize(n.ops) > 1) {
+ errors.emplace_back(
+ n.position(),
+ "an 'is _' wildcard may only be used on its own, not in a chain with other 'is'/'as' in the same subexpression"
+ );
+ }
+ }
+ else {
+ auto op_name = i->op->to_string();
+ if (op_name == "as") {
+ op_name = "as_"; // use the static_assert-checked 'as' by default...
+ } // we'll override this inside inspect-expressions
+ prefix += "cpp2::" + op_name + "<" + print_to_string(*i->type) + ">(";
+ suffix = ")" + suffix;
+ }
+ }
+ // Else it's "is value", emit "cpp2::is(expr, value)"
+ else
+ {
+ assert(i->expr);
+ prefix += "cpp2::" + i->op->to_string() + "(";
+ suffix = ", " + print_to_string(*i->expr) + ")" + suffix;
+ }
+ }
+
+ if (as_on_literal) {
+ auto last_pos = prefix.rfind('>'); assert(last_pos != prefix.npos);
+ prefix.insert(last_pos, ", " + print_to_string(*n.expr));
+ }
+
+ printer.print_cpp2(prefix, n.position());
+ if (wildcard_found) {
+ printer.print_cpp2("true", n.position());
+ }
+ else if(!as_on_literal) {
+ emit(*n.expr);
+ }
+ printer.print_cpp2(suffix, n.position());
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ template<
+ String Name,
+ typename Term
+ >
+ auto emit(binary_expression_node<Name,Term> const& n)
+ -> void
+ { STACKINSTR
+ assert(n.expr);
+ assert(
+ n.terms.empty()
+ || n.terms.front().op
+ );
+
+ // If this is relational comparison
+ if (
+ !n.terms.empty()
+ && (
+ n.terms.front().op->type() == lexeme::Less
+ || n.terms.front().op->type() == lexeme::LessEq
+ || n.terms.front().op->type() == lexeme::Greater
+ || n.terms.front().op->type() == lexeme::GreaterEq
+ || n.terms.front().op->type() == lexeme::EqualComparison
+ || n.terms.front().op->type() == lexeme::NotEqualComparison
+ )
+ )
+ {
+ auto const& op = *n.terms.front().op;
+
+ // If this is one (non-chained) comparison, just emit it directly
+ if (std::ssize(n.terms) < 2)
+ {
+ assert (std::ssize(n.terms) == 1);
+
+ // emit < <= >= > as cmp_*(a,b) calls (if selected)
+ if (flag_safe_comparisons) {
+ switch (op.type()) {
+ break;case lexeme::Less:
+ printer.print_cpp2( "cpp2::cmp_less(", n.position());
+ break;case lexeme::LessEq:
+ printer.print_cpp2( "cpp2::cmp_less_eq(", n.position());
+ break;case lexeme::Greater:
+ printer.print_cpp2( "cpp2::cmp_greater(", n.position());
+ break;case lexeme::GreaterEq:
+ printer.print_cpp2( "cpp2::cmp_greater_eq(", n.position());
+ break;default:
+ ;
+ }
+ }
+
+ emit(*n.expr);
+
+ // emit == and != as infix a ? b operators (since we don't have
+ // any checking/instrumentation we want to do for those)
+ if (flag_safe_comparisons) {
+ switch (op.type()) {
+ break;case lexeme::EqualComparison:
+ case lexeme::NotEqualComparison:
+ printer.print_cpp2( " ", n.position() );
+ emit(op);
+ printer.print_cpp2( " ", n.position() );
+ break;default:
+ printer.print_cpp2( ",", n.position() );
+ }
+ }
+ else {
+ emit(op);
+ }
+
+ emit(*n.terms.front().expr);
+
+ if (flag_safe_comparisons) {
+ switch (op.type()) {
+ break;case lexeme::Less:
+ case lexeme::LessEq:
+ case lexeme::Greater:
+ case lexeme::GreaterEq:
+ printer.print_cpp2( ")", n.position() );
+ break;default:
+ ;
+ }
+ }
+
+ return;
+ }
+
+ // Else if this is a chained comparison, emit it as a lambda,
+ // to get single evaluation via the lambda capture
+ else
+ {
+ // To check for the valid chains: all </<=, all >/>=, or all ==
+ auto found_lt = 0; // < and <=
+ auto found_gt = 0; // > and >=
+ auto found_eq = 0; // ==
+ auto count = 0;
+
+ auto const* lhs = n.expr.get();
+ auto lhs_name = "_" + std::to_string(count);
+
+ auto lambda_capture = lhs_name + " = " + print_to_string(*lhs);
+ auto lambda_body = std::string{};
+
+ for (auto const& term : n.terms)
+ {
+ assert(
+ term.op
+ && term.expr
+ );
+ ++count;
+ auto rhs_name = "_" + std::to_string(count);
+
+ // Not the first expression? Insert a "&&"
+ if (found_lt + found_gt + found_eq > 0) {
+ lambda_body += " && ";
+ }
+
+ // Remember what we've seen
+ switch (term.op->type()) {
+ break;case lexeme::Less:
+ case lexeme::LessEq:
+ found_lt = 1;
+ break;case lexeme::Greater:
+ case lexeme::GreaterEq:
+ found_gt = 1;
+ break;case lexeme::EqualComparison:
+ found_eq = 1;
+ break;default:
+ ;
+ }
+
+ // emit < <= >= > as cmp_*(a,b) calls (if selected)
+ if (flag_safe_comparisons) {
+ switch (term.op->type()) {
+ break;case lexeme::Less:
+ lambda_body += "cpp2::cmp_less(";
+ break;case lexeme::LessEq:
+ lambda_body += "cpp2::cmp_less_eq(";
+ break;case lexeme::Greater:
+ lambda_body += "cpp2::cmp_greater(";
+ break;case lexeme::GreaterEq:
+ lambda_body += "cpp2::cmp_greater_eq(";
+ break;default:
+ ;
+ }
+ }
+
+ auto rhs_expr = print_to_string(*term.expr);
+
+ lambda_body += lhs_name;
+
+ // emit == and != as infix a ? b operators (since we don't have
+ // any checking/instrumentation we want to do for those)
+ if (flag_safe_comparisons) {
+ switch (term.op->type()) {
+ break;case lexeme::EqualComparison:
+ lambda_body += *term.op;
+ break;case lexeme::NotEqualComparison:
+ errors.emplace_back(
+ n.position(),
+ "!= comparisons cannot appear in a comparison chain (see https://wg21.link/p0893)"
+ );
+ return;
+ break;default:
+ lambda_body += ",";
+ }
+ }
+ else {
+ lambda_body += *term.op;
+ }
+
+ lambda_capture += ", " + rhs_name + " = " + rhs_expr;
+ lambda_body += rhs_name;
+
+ lhs = term.expr.get();
+ lhs_name = rhs_name;
+
+ if (flag_safe_comparisons) {
+ switch (term.op->type()) {
+ break;case lexeme::Less:
+ case lexeme::LessEq:
+ case lexeme::Greater:
+ case lexeme::GreaterEq:
+ lambda_body += ")";
+ break;default:
+ ;
+ }
+ }
+ }
+
+ assert(found_lt + found_gt + found_eq > 0);
+ if (found_lt + found_gt + found_eq != 1) {
+ errors.emplace_back(
+ n.position(),
+ "a comparison chain must be all < and <=, all > and >=, or all == (see https://wg21.link/p0893)"
+ );
+ return;
+ }
+
+ printer.print_cpp2( "[" + lambda_capture + "]{ return " + lambda_body + "; }()", n.position());
+
+ return;
+ }
+ }
+
+ // Else if this is an assignment expression, don't add std::move on the lhs
+ // even if this is a definite last use (only do that when an rvalue is okay)
+ if (
+ !n.terms.empty()
+ && is_assignment_operator(n.terms.front().op->type())
+ )
+ {
+ suppress_move_from_last_use = true;
+ }
+ // If it's "_ =" then emit static_cast<void>()
+ bool emit_discard = false;
+ if (
+ !n.terms.empty()
+ && n.terms.front().op->type() == lexeme::Assignment
+ && n.expr->get_postfix_expression_node()
+ && n.expr->get_postfix_expression_node()->get_first_token_ignoring_this()
+ && *n.expr->get_postfix_expression_node()->get_first_token_ignoring_this() == "_"
+ )
+ {
+ printer.print_cpp2( "static_cast<void>(", n.position() );
+ emit_discard = true;
+ }
+ else
+ {
+ emit(*n.expr);
+ }
+ suppress_move_from_last_use = false;
+
+ // Check that this isn't an illegal pointer operation
+ // (initial partial implementation)
+ if (
+ !n.terms.empty()
+ && last_postfix_expr_was_pointer
+ )
+ {
+ auto rhs_post = n.get_second_postfix_expression_node();
+ assert(
+ rhs_post
+ && rhs_post->expr
+ );
+ auto rhs_tok = rhs_post->expr->get_token();
+ if (
+ is_assignment_operator(n.terms.front().op->type())
+ && rhs_tok
+ && (
+ *rhs_tok == "nullptr"
+ || is_digit((rhs_tok->as_string_view())[0])
+ )
+ )
+ {
+ errors.emplace_back(
+ n.terms.front().op->position(),
+ n.terms.front().op->to_string() + " - pointer assignment from null or integer is illegal"
+ );
+ violates_lifetime_safety = true;
+ }
+ else if (
+ *n.terms.front().op == "+"
+ || *n.terms.front().op == "+="
+ || *n.terms.front().op == "-"
+ || *n.terms.front().op == "-="
+ )
+ {
+ errors.emplace_back(
+ n.terms.front().op->position(),
+ n.terms.front().op->to_string() + " - pointer arithmetic is illegal - use std::span or gsl::span instead"
+ );
+ violates_bounds_safety = true;
+ }
+ }
+
+ auto first = true;
+ for (auto const& x : n.terms) {
+ assert(x.op);
+ assert(x.expr);
+
+ // Normally we'll just emit the operator, but if this is an
+ // assignment that's a definite initialization, change it to
+ // a .construct() call
+ if (
+ x.op->type() == lexeme::Assignment
+ && in_definite_init
+ )
+ {
+ printer.print_cpp2( ".construct(", n.position() );
+ emit(*x.expr);
+ printer.print_cpp2( ")", n.position() );
+ }
+ else
+ {
+ // For the first operator only, if we are emitting a "_ =" discard
+ // then we don't need the =
+ if (
+ !emit_discard
+ || !first
+ ) {
+ printer.print_cpp2(" ", n.position());
+ emit(*x.op);
+ printer.print_cpp2(" ", n.position());
+ }
+
+ // When assigning a single expression-list, we can
+ // take over direct control of emitting it without needing to
+ // go through the whole grammar, and surround it with braces
+ if (
+ x.op->type() == lexeme::Assignment
+ && x.expr->is_expression_list()
+ )
+ {
+ printer.print_cpp2( "{ ", n.position() );
+ emit(*x.expr->get_expression_list(), false);
+ printer.print_cpp2( " }", n.position() );
+ }
+ // Otherwise, just emit the general expression as usual
+ else {
+ emit(*x.expr);
+ }
+ }
+
+ first = false;
+ }
+ // Finish emitting the "_ =" discard.
+ if (emit_discard) {
+ printer.print_cpp2( ")", n.position() );
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(expression_node const& n)
+ -> void
+ { STACKINSTR
+ assert(n.expr);
+ push_need_expression_list_parens(true);
+ emit(*n.expr);
+ pop_need_expression_list_parens();
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ expression_list_node const& n,
+ bool parens_ok = true
+ )
+ -> void
+ { STACKINSTR
+ auto add_parens =
+ should_add_expression_list_parens()
+ && !n.inside_initializer
+ && parens_ok
+ ;
+ add_parens |=
+ n.is_fold_expression() &&
+ !(n.inside_initializer && current_declarations.back()->initializer->position() != n.open_paren->position())
+ ;
+ if (add_parens) {
+ printer.print_cpp2( *n.open_paren, n.position());
+ }
+
+ auto first = true;
+ for (auto const& x : n.expressions) {
+ if (!first) {
+ printer.print_cpp2(", ", n.position());
+ }
+ first = false;
+ 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::out(&", 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);
+ }
+
+ 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 (
+ x.pass == passing_style::move
+ || x.pass == passing_style::out
+ )
+ {
+ printer.print_cpp2(")", n.position());
+ }
+ }
+
+ if (add_parens) {
+ printer.print_cpp2( *n.close_paren, n.position());
+ }
+ // We want to consume only one of these
+ consumed_expression_list_parens();
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ expression_statement_node const& n,
+ bool can_have_semicolon,
+ source_position function_body_start = {},
+ bool function_void_ret = false,
+ function_prolog const& function_prolog = {},
+ std::vector<std::string> const& function_epilog = {},
+ bool emitted = false
+ )
+ -> void
+ { STACKINSTR
+ assert(n.expr);
+ auto generating_return = false;
+
+ if (function_body_start != source_position{}) {
+ emit_prolog_mem_inits(function_prolog, n.position().colno);
+ printer.print_cpp2(" { ", function_body_start);
+ emit_prolog_statements(function_prolog, n.position().colno);
+ if (!function_void_ret) {
+ printer.print_cpp2("return ", n.position());
+ generating_return = true;
+ }
+ }
+
+ if (!emitted) {
+ // When generating 'return' of a single expression-list, we can
+ // take over direct control of emitting it without needing to
+ // go through the whole grammar, and surround it with braces
+ if (
+ generating_return
+ && n.expr->is_expression_list()
+ && !n.expr->get_expression_list()->is_fold_expression()
+ )
+ {
+ auto is_deduced_return =
+ !function_returns.empty()
+ && function_returns.back().is_deduced;
+
+ if (!is_deduced_return) {
+ printer.print_cpp2( "{ ", n.position() );
+ }
+ emit(*n.expr->get_expression_list(), false);
+ if (!is_deduced_return) {
+ printer.print_cpp2( " }", n.position() );
+ }
+ }
+ // Otherwise, just emit the general expression as usual
+ else {
+ emit(*n.expr);
+ }
+ if (can_have_semicolon) {
+ printer.print_cpp2(";", n.position());
+ }
+ }
+
+ if (function_body_start != source_position{}) {
+ emit_epilog_statements( function_epilog, n.position().colno);
+ printer.print_cpp2(" }", n.position());
+ }
+ }
+
+
+ // Consider moving these `stack` functions to `common.h` to enable more general use.
+
+ template<typename T>
+ auto stack_value(
+ T& var,
+ std::type_identity_t<T> const& value
+ )
+ -> auto
+ {
+ return finally([&var, old = std::exchange(var, value)]() {
+ var = old;
+ });
+ }
+
+ template<typename T>
+ auto stack_element(
+ std::vector<T>& cont,
+ std::type_identity_t<T> const& value
+ )
+ -> auto
+ {
+ cont.push_back(value);
+ return finally([&]{ cont.pop_back(); });
+ }
+
+ template<typename T>
+ auto stack_size(std::vector<T>& cont)
+ -> auto
+ {
+ return finally([&, size = cont.size()]{ cont.resize(size); });
+ }
+
+ template<typename T>
+ auto stack_size_if(
+ std::vector<T>& cont,
+ bool cond
+ )
+ -> std::optional<decltype(stack_size(cont))>
+ {
+ if (cond) {
+ return stack_size(cont);
+ }
+ return {};
+ }
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ statement_node const& n,
+ bool can_have_semicolon = true,
+ source_position function_body_start = {},
+ bool function_void_ret = false,
+ function_prolog const& function_prolog = {},
+ std::vector<std::string> const& function_epilog = {}
+ )
+ -> void
+ { STACKINSTR
+ if (!sema.check(n)) {
+ return;
+ }
+
+ auto emit_parameters =
+ !n.emitted
+ && n.parameters
+ ;
+
+ auto guard = stack_size_if(current_names, emit_parameters);
+ if (emit_parameters) {
+ printer.print_extra( "\n");
+ printer.print_extra( "{");
+ for (auto& param : n.parameters->parameters) {
+ printer.print_extra( "\n");
+ printer.print_extra( print_to_string(*param) );
+ }
+ }
+
+ // Do expression statement case first... it's the most complex
+ // because it's used for single-statement function bodies
+ try_emit<statement_node::expression >(
+ n.statement,
+ can_have_semicolon,
+ function_body_start,
+ function_void_ret,
+ function_prolog,
+ function_epilog,
+ n.emitted
+ );
+
+ // Otherwise, skip this one if it was already handled earlier (i.e., a constructor member init)
+ if (n.emitted) {
+ return;
+ }
+
+ printer.disable_indent_heuristic_for_next_text();
+
+ try_emit<statement_node::compound >(n.statement, function_prolog, function_epilog);
+
+ // NOTE: Reset preemption here because
+ // - for compound statements written as "= { ... }", we want to keep the
+ // preempted position which moves the { to where the = was
+ // - but for other statement types, we want to get rid of any leftover
+ // preemption (ideally there wouldn't be any, but sometimes there is
+ // and it should not apply to what we're about to emit)
+ printer.preempt_position_push({});
+ // This only has a whitespace effect in the generated Cpp1 code, but it's
+ // aesthetic and aesthetics are important in this case -- we want to keep
+ // the original source's personal whitespace formatting style as much as we can
+
+ try_emit<statement_node::selection >(n.statement);
+ try_emit<statement_node::declaration>(n.statement);
+ try_emit<statement_node::return_ >(n.statement);
+ try_emit<statement_node::iteration >(n.statement);
+ try_emit<statement_node::using_ >(n.statement);
+ try_emit<statement_node::contract >(n.statement);
+ try_emit<statement_node::inspect >(n.statement, false);
+ try_emit<statement_node::jump >(n.statement);
+
+ printer.preempt_position_pop();
+
+ if (emit_parameters) {
+ printer.print_extra( "\n");
+ printer.print_extra( "}");
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Within a type scope implementation, disallow declaring a name that
+ // is the same as (i.e., shadows) a type scope name... this is a
+ // convenient place to check because we have the decls stack
+ //
+ auto check_shadowing_of_type_scope_names(
+ declaration_node const& decl
+ )
+ -> bool
+ {
+ if (
+ decl.has_name() // this is a named declaration
+ && !decl.has_name("this") // that's not 'this'
+ && !decl.parent_is_type() // and the type isn't the direct parent
+ && is_name_declared_in_current_type_scope(*decl.name())
+ ) // and it shadows a name
+ {
+ errors.emplace_back(
+ decl.position(),
+ "a type's implementation may not declare a name that is the same as (i.e., shadows) a type scope name - for example, a type scope function's local variable may not have the same as one of the type's members"
+ );
+ return false;
+ }
+
+ return true;
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ parameter_declaration_node const& n,
+ bool is_returns = false,
+ bool is_template_parameter = false
+ )
+ -> void
+ { STACKINSTR
+ if (!sema.check(n)) {
+ return;
+ }
+
+ // Can't declare functions as parameters -- only pointers to functions which are objects
+ assert( n.declaration );
+ assert( !n.declaration->is_function() );
+
+ if (!check_shadowing_of_type_scope_names(*n.declaration)) {
+ return;
+ }
+
+ assert( n.declaration->identifier );
+ auto identifier = print_to_string( *n.declaration->identifier );
+ auto identifier_pos = n.position();
+
+ if (n.mod == parameter_declaration_node::modifier::implicit)
+ {
+ assert(!current_functions.empty());
+ if (
+ n.pass != passing_style::out
+ || !current_functions.back().decl->has_name("operator=")
+ )
+ {
+ errors.emplace_back(
+ n.position(),
+ "only an 'out this' parameter of an 'operator=' function may be declared implicit"
+ );
+ }
+ }
+
+ current_names.push_back(&*n.declaration);
+
+ //-----------------------------------------------------------------------
+ // Skip 'this' parameters
+
+ if (n.declaration->has_name("this"))
+ {
+ // Since we're skipping "out this," plus possibly "implicit " and
+ // whitespace, any following parameters on the same line can shift left
+ printer.add_pad_in_this_line(-18);
+
+ return;
+ }
+
+ //-----------------------------------------------------------------------
+ // Handle 'that' parameters
+
+ if (n.declaration->has_name("that"))
+ {
+ emitting_that_function = true;
+ assert(
+ n.pass == passing_style::in
+ || n.pass == passing_style::move
+ );
+ auto pass = std::string{" const&"};
+ if (
+ n.pass == passing_style::move
+ || emitting_move_that_function
+ )
+ {
+ pass = "&&";
+ }
+
+ auto func_name = get_enclosing_function_name();
+ assert(func_name);
+
+ auto type_name = get_enclosing_type_name();
+ assert(type_name);
+
+ // If we're in an empty type that has no member object, mark 'that' as
+ // [[maybe_unused]] to silence Cpp1 compiler warnings
+ assert(!current_functions.empty());
+ auto maybe_unused = std::string{};
+ if (current_functions.back().decl->get_parent()->get_type_scope_declarations(declaration_node::objects).empty()) {
+ maybe_unused = "[[maybe_unused]] ";
+ }
+
+ printer.print_cpp2(
+ maybe_unused + print_to_string( *type_name ) + pass + " that",
+ n.position()
+ );
+ return;
+ }
+
+ //-----------------------------------------------------------------------
+ // Handle type parameters
+
+ if (n.declaration->is_type()) {
+ assert( is_template_parameter );
+ printer.print_cpp2("typename ", identifier_pos);
+ if (n.declaration->is_variadic) {
+ printer.print_cpp2(
+ "...",
+ identifier_pos
+ );
+ }
+
+ if (identifier == "_") {
+ printer.print_cpp2( "UnnamedTypeParam" + std::to_string(n.ordinal), identifier_pos );
+ }
+ else {
+ printer.print_cpp2( identifier, identifier_pos );
+ }
+
+ return;
+ }
+
+ //-----------------------------------------------------------------------
+ // Else handle template non-type parameters
+
+ assert( n.declaration->is_object() );
+ auto const& type_id = *std::get<declaration_node::an_object>(n.declaration->type);
+
+ if (is_template_parameter) {
+ emit( type_id );
+ printer.print_cpp2(" ", type_id.position());
+ printer.print_cpp2( identifier, identifier_pos );
+ return;
+ }
+
+ //-----------------------------------------------------------------------
+ // Else handle ordinary parameters
+
+ auto param_type = print_to_string(type_id);
+
+ // 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) != std::string_view::npos
+ || name.find(","+tparam_name) != std::string_view::npos
+ )
+ )
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ 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)
+ )
+ ;
+
+ // First any prefix
+
+ if (identifier == "_") {
+ printer.print_cpp2( "[[maybe_unused]] ", identifier_pos );
+ identifier = "unnamed_param_" + std::to_string(n.ordinal);
+ }
+
+ if (
+ !is_returns
+ && !n.declaration->is_variadic
+ && !type_id.is_wildcard()
+ && !is_dependent_parameter_type
+ && !type_id.is_pointer_qualified()
+ )
+ {
+ switch (n.pass) {
+ break;case passing_style::in : printer.print_cpp2( "cpp2::in<", n.position() );
+ break;case passing_style::out : printer.print_cpp2( "cpp2::out<", n.position() );
+ break;default: ;
+ }
+ }
+
+ printer.preempt_position_push( n.position() );
+
+ if (
+ type_id.is_pointer_qualified()
+ && n.pass == passing_style::in
+ )
+ {
+ printer.print_cpp2( param_type, n.position() );
+ }
+ else if (
+ type_id.is_wildcard()
+ || is_dependent_parameter_type
+ || n.declaration->is_variadic
+ )
+ {
+ auto name = std::string{"auto"};
+ if (is_dependent_parameter_type) {
+ name = param_type;
+ }
+ else if (
+ n.declaration->is_variadic
+ && !type_id.is_wildcard()
+ )
+ {
+ auto name = n.declaration->identifier->get_token();
+ assert(name);
+ auto req = std::string{"(std::is_convertible_v<CPP2_TYPEOF("};
+ req += *name;
+ req += "), ";
+ req += param_type;
+ req += "> && ...)";
+ function_requires_conditions.push_back(req);
+ }
+
+ switch (n.pass) {
+ break;case passing_style::in : printer.print_cpp2( name+" const&", n.position() );
+ break;case passing_style::copy : printer.print_cpp2( name, n.position() );
+ break;case passing_style::inout : printer.print_cpp2( name+"&", n.position() );
+
+ // For generic out parameters, we take a pointer to anything with paramater named "identifier_"
+ // and then generate the out<> as a stack local with the expected name "identifier"
+ break;case passing_style::out : printer.print_cpp2( name, n.position() );
+ current_functions.back().prolog.statements.push_back(
+ "auto " + identifier + " = cpp2::out(" + identifier + "_); "
+ );
+ identifier += "_";
+
+ break;case passing_style::move : printer.print_cpp2( name+"&&", n.position() );
+ break;case passing_style::forward: printer.print_cpp2( name+"&&", n.position() );
+ break;default: ;
+ }
+ }
+ else if (n.pass == passing_style::forward) {
+ printer.print_cpp2("auto", n.position());
+
+ auto name = n.declaration->identifier->get_token();
+ assert(name);
+ auto req = std::string{"std::is_same_v<CPP2_TYPEOF("};
+ req += *name;
+ req += "), ";
+ req += param_type;
+ req += ">";
+ function_requires_conditions.push_back(req);
+ }
+ else {
+ if (is_returns) {
+ printer.print_extra( param_type );
+ }
+ else {
+ printer.print_cpp2( param_type, type_id.position() );
+ }
+ }
+
+ printer.preempt_position_pop();
+
+ // Then any suffix
+
+ if (
+ !is_returns
+ && !type_id.is_wildcard()
+ && !is_dependent_parameter_type
+ && !type_id.is_pointer_qualified()
+ && !n.declaration->is_variadic
+ )
+ {
+ switch (n.pass) {
+ break;case passing_style::in : printer.print_cpp2( ">", n.position() );
+ break;case passing_style::copy : printer.print_cpp2( "", n.position() );
+ break;case passing_style::inout : printer.print_cpp2( "&", n.position() );
+ break;case passing_style::out : printer.print_cpp2( ">", n.position() );
+ break;case passing_style::move : printer.print_cpp2( "&&", n.position() );
+ break;case passing_style::forward: printer.print_cpp2( "&&", n.position() );
+ break;default: ;
+ }
+ }
+
+ if (is_returns) {
+ printer.print_extra( " " + identifier );
+ }
+ else {
+ printer.print_cpp2( " ", identifier_pos );
+ if (n.declaration->is_variadic)
+ {
+ if (n.direction() == passing_style::out) {
+ errors.emplace_back(
+ n.declaration->position(),
+ "a variadic parameter cannot be 'out'"
+ );
+ return;
+ }
+
+ printer.print_cpp2(
+ "...",
+ identifier_pos
+ );
+ }
+ printer.print_cpp2( identifier, identifier_pos );
+ }
+
+ if (
+ !is_returns
+ && n.declaration->initializer
+ )
+ {
+ auto guard = stack_element(current_declarations, &*n.declaration);
+ printer.print_cpp2( " = ", n.declaration->initializer->position() );
+ emit(*n.declaration->initializer);
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ parameter_declaration_list_node const& n,
+ bool is_returns = false,
+ bool is_template_parameter = false,
+ bool generating_postfix_inc_dec = false
+ )
+ -> void
+ { STACKINSTR
+ in_parameter_list = true;
+
+ if (is_returns) {
+ printer.print_extra( "{ " );
+ }
+ else {
+ assert(n.open_paren);
+ emit(*n.open_paren);
+ }
+
+ // So we don't get cute about text-aligning the first parameter when it's on a new line
+ printer.disable_indent_heuristic_for_next_text();
+
+ auto prev_pos = n.position();
+ auto first = true;
+ for (auto const& x : n.parameters) {
+ if (
+ !first
+ && !is_returns
+ )
+ {
+ printer.print_cpp2( ", ", prev_pos );
+ }
+ prev_pos = x->position();
+ assert(x);
+ emit(*x, is_returns, is_template_parameter);
+ if (!x->declaration->has_name("this")) {
+ first = false;
+ }
+ if (is_returns) {
+ printer.print_extra( "; " );
+ }
+ }
+
+ if (is_returns) {
+ printer.print_extra( "};\n" );
+ }
+ else {
+ // If we're generating Cpp1 postfix ++ or --, add the dummy int parameter
+ if (generating_postfix_inc_dec) {
+ if (!first) {
+ printer.print_cpp2( ",", n.position() );
+ }
+ printer.print_cpp2( "int", n.position() );
+ }
+
+ // Position heuristic (aka hack): Avoid emitting extra whitespace before )
+ // beyond column 10
+ assert(n.close_paren);
+ auto col = std::min( n.close_paren->position().colno, colno_t{10});
+ printer.preempt_position_push({ n.close_paren->position().lineno, col});
+ emit(*n.close_paren);
+ printer.preempt_position_pop();
+ }
+
+ in_parameter_list = false;
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ // note: parameter is deliberately not const because we will fill
+ // in the capture .str information
+ contract_node& n
+ )
+ -> void
+ { STACKINSTR
+ assert (n.kind);
+
+ // If this is one of Cpp2's predefined contract groups,
+ // make it convenient to use without cpp2:: qualification
+ auto name = std::string{"cpp2::Default"};
+ if (n.group)
+ {
+ auto group = print_to_string(*n.group);
+ if (group != "_") {
+ name = group;
+ }
+ if (
+ name == "Default"
+ || name == "Bounds"
+ || name == "Null"
+ || name == "Type"
+ || name == "Testing"
+ )
+ {
+ name.insert(0, "cpp2::");
+ }
+ }
+
+ // "Unevaluated" is for static analysis only, and are never evaluated, so just skip them
+ // (The only requirement for an Unevaluated condition is that it parses; and even that's
+ // easy to relax if we ever want to allow arbitrary tokens in an Unevaluated condition)
+ if (n.group && n.group->to_string() == "Unevaluated") {
+ return;
+ }
+
+ // For a postcondition, we'll wrap it in a lambda and register it
+ //
+ if (*n.kind == "post") {
+ auto lambda_intro = build_capture_lambda_intro_for(n.captures, n.position(), true);
+ printer.print_cpp2(
+ "cpp2_finally_presuccess.add(" +
+ lambda_intro + "{",
+ n.position()
+ );
+ }
+
+ // Emit the contract group name, and report any violation to that group
+ //
+ assert(n.condition);
+ auto message = std::string{"\"\""};
+ if (n.message) {
+ message = "CPP2_CONTRACT_MSG(" + print_to_string(*n.message) + ")";
+ }
+
+ printer.print_cpp2(
+ "if (" + name + ".has_handler()",
+ n.position()
+ );
+ for (auto const& flag : n.flags) {
+ printer.print_cpp2(
+ " && " + print_to_string(*flag),
+ n.position()
+ );
+ }
+ printer.print_cpp2(
+ " && !(" + print_to_string(*n.condition) + ") ) " +
+ "{ " + name + ".report_violation(" + message + "); }",
+ n.position()
+ );
+
+ // For a postcondition, close out the lambda
+ //
+ if (*n.kind == "post") {
+ printer.print_cpp2( "} );", n.position()
+ );
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ function_type_node const& n,
+ token const* ident,
+ bool is_main = false,
+ bool is_ctor_or_dtor = false,
+ std::string suffix1 = {},
+ bool generating_postfix_inc_dec = false
+ )
+ -> void
+ { STACKINSTR
+ if (!sema.check(n)) {
+ return;
+ }
+
+ if (
+ is_main
+ && n.parameters->parameters.size() > 0
+ )
+ {
+ printer.print_cpp2(
+ "(int const argc_, char** argv_)",
+ n.parameters->position()
+ );
+ current_functions.back().prolog.statements.push_back(
+ "auto const args = cpp2::make_args(argc_, argv_); "
+ );
+ }
+ else {
+ emit(*n.parameters, false, false, generating_postfix_inc_dec);
+ }
+
+ // For an anonymous function, the emitted lambda is 'constexpr' or 'mutable'
+ if (!n.my_decl->has_name())
+ {
+ if (n.my_decl->is_constexpr) {
+ // The current design path we're trying out is for all '==' functions to be
+ // emitted as Cpp1 'constexpr', including anonymous functions. For anonymous
+ // functions that have captures, the intent is that '==' implies "the result
+ // always the same (depends only on the arguments)." Specifically, the result
+ // doesn't depend on the captured state, so the captured state should be const.
+ // But until we want to take a dependency on post-C++20 constexpr relaxation
+ // to make more code work with 'constexpr' even when not invoked in constexpr
+ // contexts, we will emit it as const/whitespace instead for now.
+ //
+ // printer.print_cpp2( " constexpr", n.position() );
+ // // consider enabling when P2242, P2280, and similar papers are widely implemented
+ }
+ else {
+ printer.print_cpp2( " mutable", n.position() );
+ }
+ }
+
+ // For now, adding implicit noexcept only for move/swap/dtor functions
+ if (
+ n.is_move()
+ || n.is_swap()
+ || n.is_destructor()
+ || generating_move_from == n.my_decl
+ )
+ {
+ printer.print_cpp2( " noexcept", n.position() );
+ }
+
+ printer.print_cpp2( suffix1, n.position() );
+
+ // Handle a special member function
+ if (
+ n.is_assignment()
+ || generating_assignment_from == n.my_decl
+ )
+ {
+ assert(
+ n.returns.index() == function_type_node::empty
+ && n.my_decl->parent_declaration->name()
+ );
+ printer.print_cpp2(
+ " -> " + print_to_string( *n.my_decl->parent_declaration->name() ) + "& ",
+ n.position()
+ );
+ }
+
+ // Otherwise, handle a default return type
+ else if (n.returns.index() == function_type_node::empty)
+ {
+ if (is_main)
+ {
+ printer.print_cpp2( " -> int", n.position() );
+ }
+ else if(!is_ctor_or_dtor)
+ {
+ printer.print_cpp2( " -> void", n.position() );
+ }
+ }
+
+ // Otherwise, handle a single anonymous return type
+ else if (n.returns.index() == function_type_node::id)
+ {
+ auto is_type_scope_function_with_in_this =
+ n.my_decl->parent_is_type()
+ && n.parameters->ssize() > 0
+ && (*n.parameters)[0]->direction() == passing_style::in
+ ;
+
+ printer.print_cpp2( " -> ", n.position() );
+ auto& r = std::get<function_type_node::id>(n.returns);
+ assert(r.type);
+
+ auto return_type = print_to_string(*r.type);
+
+ if (r.pass == passing_style::forward) {
+ if (r.type->is_wildcard()) {
+ printer.print_cpp2( "auto&&", n.position() );
+ }
+ else {
+ printer.print_cpp2( return_type, n.position() );
+ if (is_type_scope_function_with_in_this) {
+ printer.print_cpp2( " const&", n.position() );
+ }
+ else if (!generating_postfix_inc_dec) {
+ printer.print_cpp2( "&", n.position() );
+ }
+ }
+ }
+ else {
+ printer.print_cpp2( return_type, n.position() );
+ }
+ }
+
+ // Otherwise, handle multiple/named returns
+ else {
+ printer.print_cpp2( " -> ", n.position() );
+ function_return_name = {};
+ printer.emit_to_string(&function_return_name);
+ assert(ident);
+ printer.print_cpp2( *ident, ident->position() );
+ printer.print_cpp2( "_ret", ident->position() );
+ printer.emit_to_string();
+ printer.print_cpp2( function_return_name, ident->position() );
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto is_name_declared_in_current_type_scope(std::string_view s)
+ -> bool
+ {
+ if (!s.empty())
+ {
+ // Navigate to the enclosing type, if there is one...
+ for (auto parent = current_declarations.rbegin();
+ parent != current_declarations.rend();
+ ++parent
+ )
+ {
+ if (
+ *parent
+ && (*parent)->is_namespace()
+ )
+ {
+ break;
+ }
+ // ... and here it is, so...
+ if (
+ *parent
+ && (*parent)->is_type()
+ )
+ {
+ // ... for each of its type scope decls...
+ for (auto const& decl : (*parent)->get_type_scope_declarations())
+ {
+ // ... check the name
+ if (decl->has_name(s))
+ {
+ return true;
+ }
+ }
+ break;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto get_enclosing_type_name()
+ -> token const*
+ {
+ // Navigate to the enclosing type, if there is one...
+ for (auto parent = current_declarations.rbegin();
+ parent != current_declarations.rend();
+ ++parent
+ )
+ {
+ if (
+ *parent
+ && (*parent)->is_namespace()
+ )
+ {
+ break;
+ }
+ // ... and here it is, so...
+ if (
+ *parent
+ && (*parent)->is_type()
+ )
+ {
+ return (*parent)->name();
+ }
+ }
+ return {};
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto get_enclosing_function_name()
+ -> token const*
+ {
+ // Navigate to the enclosing function, if there is one...
+ for (auto parent = current_declarations.rbegin();
+ parent != current_declarations.rend();
+ ++parent
+ )
+ {
+ if (
+ *parent
+ && (*parent)->is_namespace()
+ )
+ {
+ break;
+ }
+ // ... and here it is, so...
+ if (
+ *parent
+ && (*parent)->is_function()
+ )
+ {
+ return (*parent)->name();
+ }
+ }
+ return {};
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Helper to emit type-qualified names for member functions
+ //
+ auto type_qualification_if_any_for(
+ declaration_node const& n
+ )
+ -> std::string
+ {
+ auto ret = std::string{};
+
+ if (
+ printer.get_phase() == printer.phase2_func_defs
+ && n.parent_is_type()
+// && !n.name()->as_string_view().starts_with("operator")
+ )
+ {
+ // If this function is inside templated type(s),
+ // emit those outer template parameter lists too
+ auto parent = n.parent_declaration;
+ while (
+ parent
+ && parent->is_type()
+ )
+ {
+ auto list = std::string{""};
+ if (parent->template_parameters) {
+ auto separator = std::string{"<"};
+ for (auto& tparam : parent->template_parameters->parameters) {
+ assert (tparam->has_name());
+ list += separator + tparam->name()->to_string();
+ separator = ",";
+ }
+ list += ">";
+ }
+ ret = print_to_string(*parent->identifier) + list + "::" + ret;
+ parent = parent->parent_declaration;
+ }
+ }
+
+ return ret;
+ }
+
+ //-----------------------------------------------------------------------
+ // Constructors and assignment operators
+ //
+ auto emit_special_member_function(
+ declaration_node const& n,
+ std::string prefix
+ )
+ -> void
+ { STACKINSTR
+ assert(n.is_function());
+ auto& func = std::get<declaration_node::a_function>(n.type);
+ assert(func);
+
+ auto is_assignment =
+ generating_assignment_from == &n
+ || (*func->parameters)[0]->pass == passing_style::inout;
+
+ if (
+ func->parameters->ssize() > 1
+ && (*func->parameters)[1]->has_name("that")
+ )
+ {
+ emitting_that_function = true;
+ if (
+ (*func->parameters)[1]->pass == passing_style::move
+ || generating_move_from == &n
+ )
+ {
+ emitting_move_that_function = true;
+ }
+ }
+
+ // Do the 'out' param and member init work only in the definition phase
+ if (printer.get_phase() == printer.phase2_func_defs)
+ {
+ auto canonize_object_name = [&]( declaration_node const* obj )
+ -> std::string
+ {
+ assert(obj->has_name());
+ auto ret = obj->name()->to_string();
+ if (ret == "this") {
+ ret = print_to_string( *obj->get_object_type() );
+ }
+ return ret;
+ };
+
+ // We'll use this common guidance in several errors,
+ // so write it once to keep the guidance consistent
+ assert (n.parent_declaration && n.parent_declaration->name());
+ auto error_msg = "an operator= body must start with a series of 'member = value;' initialization statements for each of the type-scope objects in the same order they are declared, or the member must have a default initializer (in type '" + n.parent_declaration->name()->to_string() + "')";
+
+ // If this constructor's type has data members, handle their initialization
+ // - objects is the list of this type's declarations
+ // - statements is the list of this constructor's statements
+ auto objects = n.parent_declaration->get_type_scope_declarations(n.objects);
+ auto statements = n.get_initializer_statements();
+ auto out_inits = std::vector<std::string>{};
+
+ auto object = objects.begin();
+ auto statement = statements.begin();
+ auto separator = std::string{": "};
+
+ while (object != objects.end())
+ {
+ auto object_name = canonize_object_name(*object);
+
+ auto is_object_before_base =
+ n.get_decl_if_type_scope_object_name_before_a_base_type(*(*object)->name());
+
+ auto found_explicit_init = false;
+ auto found_default_init = false;
+ auto stmt_pos = n.position();
+
+ auto initializer = std::string{};
+
+ // If we're at an assignment statement, get the lhs and rhs
+ if (statement != statements.end())
+ {
+ assert (*statement);
+ stmt_pos = (*statement)->position();
+ if (stmt_pos.lineno < 0) {
+ stmt_pos = n.position();
+ }
+
+ auto lhs = std::string{};
+ auto rhs = std::string{};
+ {
+ auto exprs = (*statement)->get_lhs_rhs_if_simple_assignment();
+ if (exprs.lhs) {
+ if (auto tok = exprs.lhs->get_first_token_ignoring_this()) {
+ lhs = *tok;
+ }
+ else {
+ lhs = print_to_string( *exprs.lhs );
+ }
+ }
+ if (exprs.rhs) {
+ rhs = print_to_string( *exprs.rhs );
+ }
+ }
+
+ // If this is an initialization of an 'out' parameter, stash it
+ if (n.has_out_parameter_named(lhs)){
+ out_inits.push_back( print_to_string(**statement, false) );
+ (*statement)->emitted = true;
+ ++statement;
+ continue;
+ }
+
+ // Now we're ready to check whether this is an assignment to *object
+
+ if (!lhs.empty())
+ {
+ // First, see if it's an assignment 'name = something'
+ found_explicit_init = object_name == lhs;
+
+ // Otherwise, see if it's 'this.name = something'
+ if (!found_explicit_init)
+ {
+ // If it's of the form 'this.name', check 'name'
+ if (
+ starts_with( lhs, "(*this).")
+ && object_name == lhs.substr(8)
+ )
+ {
+ found_explicit_init = true;
+ }
+ }
+
+ if (found_explicit_init)
+ {
+ initializer = rhs;
+
+ // We've used this statement, so note it
+ // and move 'statement' forward
+ (*statement)->emitted = true;
+ ++statement;
+ }
+ }
+ }
+
+ // Otherwise, use a default... for a non-copy/move that's the member initializer
+ // (for which we don't need to emit anything special because it will get used),
+ // and for a copy/move function we default to "= that.same_member" (or, if this
+ // is a base type, to assigning from the lowered base subobject)
+ if (!found_explicit_init)
+ {
+ if (emitting_that_function && (*object)->has_name("this"))
+ {
+ auto pass = std::string{" const&"};
+ if (emitting_move_that_function) {
+ pass = "&&";
+ }
+ initializer =
+ "static_cast<"
+ + object_name
+ + pass
+ + ">(that)";
+ found_default_init = true;
+ }
+ else if (emitting_move_that_function)
+ {
+ initializer =
+ "std::move(that)."
+ + object_name;
+ found_default_init = true;
+ }
+ else if (emitting_that_function)
+ {
+ initializer =
+ "that."
+ + object_name;
+ found_default_init = true;
+ }
+ else if ((*object)->initializer)
+ {
+ initializer = print_to_string(*(*object)->initializer, false);
+ found_default_init = true;
+ }
+ }
+
+ // If this is not an assignment to *object,
+ // and there was no member initializer, complain
+ if (
+ !found_explicit_init
+ && !found_default_init
+ )
+ {
+ errors.emplace_back(
+ stmt_pos,
+ "in operator=, expected '" + object_name + " = ...' initialization statement (because type scope object '" + object_name + "' does not have a default initializer)"
+ );
+ errors.emplace_back(
+ (*object)->position(),
+ "see declaration for '" + object_name + "' here"
+ );
+ errors.emplace_back(
+ stmt_pos,
+ error_msg
+ );
+ return;
+ }
+
+ assert(
+ found_explicit_init
+ || found_default_init
+ );
+
+ // Emit the initializer if it it isn't '_' (don't care) and ...
+ if (initializer == "_") {
+ // And on to the next data member...
+ ++object;
+ continue;
+ }
+
+ if (initializer.empty()) {
+ initializer = "{}";
+ }
+
+ // (a) ... if this is assignment, emit it in all cases
+ if (is_assignment)
+ {
+ assert ((*object)->name());
+
+ // Flush any 'out' parameter initializations
+ for (auto& init : out_inits) {
+ current_functions.back().prolog.statements.push_back(init + ";");
+ }
+ out_inits = {};
+
+ // Then add this statement
+
+ // Use ::operator= for base classes
+ if ((*object)->has_name("this")) {
+ current_functions.back().prolog.statements.push_back(
+ print_to_string( *(*object)->get_object_type() ) +
+ "::operator= ( " +
+ initializer +
+ " );"
+ );
+ }
+ // Else just use infix assignment
+ else {
+ current_functions.back().prolog.statements.push_back(
+ object_name +
+ " = " +
+ initializer +
+ ";"
+ );
+ }
+ }
+ // (b) ... if this isn't assignment, only need to emit it if it was
+ // explicit, or is a base type or 'that' initializer
+ else if (
+ found_explicit_init
+ || is_object_before_base
+ || (
+ (*object)->has_name("this")
+ && !initializer.empty()
+ )
+ || emitting_that_function
+ )
+ {
+ if (is_object_before_base) {
+ assert (is_object_before_base->name());
+ object_name =
+ print_to_string( *is_object_before_base->parent_declaration->name() )
+ + "_"
+ + (*object)->name()->to_string()
+ + "_as_base";
+ }
+
+ // Flush any 'out' parameter initializations
+ auto out_inits_with_commas = [&]() -> std::string {
+ auto ret = std::string{};
+ for (auto& init : out_inits) {
+ ret += init + ", ";
+ }
+ out_inits = {};
+ return ret;
+ }();
+
+ // If there were any, wedge them into this initializer
+ // using (holds nose) the comma operator and extra parens
+ // as we add this statement
+ if (!out_inits_with_commas.empty()) {
+ current_functions.back().prolog.mem_inits.push_back(
+ separator +
+ object_name +
+ "{(" +
+ out_inits_with_commas +
+ initializer +
+ " )}"
+ );
+ }
+ else {
+ if (initializer == "{}") {
+ initializer = "";
+ }
+ current_functions.back().prolog.mem_inits.push_back(
+ separator +
+ object_name +
+ "{ " +
+ initializer +
+ " }"
+ );
+ }
+ separator = ", ";
+ }
+
+ // And on to the next data member...
+ ++object;
+ }
+
+ // Now no data members should be left over
+ if (object != objects.end())
+ {
+ errors.emplace_back(
+ (*object)->position(),
+ canonize_object_name(*object) + " was not initialized - did you forget to write a default initializer, or assign to it in the operator= body?"
+ );
+ errors.emplace_back(
+ (*object)->position(),
+ "see declaration for '" + canonize_object_name(*object) + "' here"
+ );
+ errors.emplace_back(
+ (*object)->position(),
+ error_msg
+ );
+ return;
+ }
+
+ // Flush any possible remaining 'out' parameters
+ for (auto& init : out_inits) {
+ current_functions.back().prolog.statements.push_back(init + ";");
+ }
+ }
+
+ // For a constructor, print the type name instead of the operator= function name
+ assert(n.parent_is_type());
+ if (!is_assignment)
+ {
+ printer.print_cpp2( prefix, n.position() );
+ printer.print_cpp2( type_qualification_if_any_for(n), n.position() );
+ printer.print_cpp2( print_to_string( *n.parent_declaration->name() ), n.position() );
+ emit( *func, n.name(), false, true );
+ }
+ // For an assignment operator, similar to emitting an ordinary function
+ else
+ {
+ assert (!current_functions.empty());
+ current_functions.back().epilog.push_back( "return *this;");
+ printer.print_cpp2( prefix, n.position() );
+ printer.print_cpp2( "auto " + type_qualification_if_any_for(n) + print_to_string( *n.name() ), n.position());
+ emit( *func, n.name() );
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ //
+ auto emit(
+ declaration_node const& n,
+ std::string const& capture_intro = {}
+ )
+ -> void
+ { STACKINSTR
+ // Helper for declarations with parent *template-head*s.
+ auto const emit_parent_template_parameters = [&]() {
+ auto parent_template_parameters = std::string{};
+ auto parent = n.parent_declaration;
+ while (
+ parent
+ && parent->is_type()
+ )
+ {
+ if (parent->requires_clause_expression) {
+ parent_template_parameters =
+ "requires( " + print_to_string(*parent->requires_clause_expression) + " )\n"
+ + parent_template_parameters;
+ }
+ if (parent->template_parameters) {
+ parent_template_parameters =
+ "template " + print_to_string( *parent->template_parameters, false, true )
+ + " " + parent_template_parameters;
+ }
+ parent = parent->parent_declaration;
+ }
+ printer.print_cpp2(parent_template_parameters, n.position());
+ };
+
+ // Helper for declarations that can have requires-clauses
+ auto const emit_requires_clause = [&]() {
+ if (
+ n.requires_clause_expression
+ || !function_requires_conditions.empty()
+ )
+ {
+ printer.print_extra("\n");
+ printer.ignore_alignment( true, n.position().colno + 4 );
+ if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
+ // Workaround GCC 10 not supporting requires in forward declarations in some cases.
+ // See commit 5a0d77f8e297902c0b9712c5aafb6208cfa4c139.
+ if (n.is_object() || n.parent_is_type()) {
+ printer.print_extra("CPP2_REQUIRES_ (");
+ }
+ else {
+ printer.print_extra("CPP2_REQUIRES (");
+ }
+ }
+ else {
+ printer.print_extra("requires (");
+ }
+
+ if (n.requires_clause_expression) {
+ emit(*n.requires_clause_expression);
+ if (!function_requires_conditions.empty()) {
+ printer.print_extra(" && ");
+ }
+ }
+
+ if (!function_requires_conditions.empty()) {
+ printer.print_extra(function_requires_conditions.front());
+ for (auto it = std::cbegin(function_requires_conditions)+1; it != std::cend(function_requires_conditions); ++it) {
+ printer.print_extra(" && " + *it);
+ }
+ }
+
+ printer.print_extra(") ");
+ function_requires_conditions = {};
+ printer.ignore_alignment( false );
+ }
+ };
+
+
+ // Declarations are handled in multiple passes,
+ // but we only want to do the sema checks once
+ if (
+ printer.get_phase() == printer.phase2_func_defs
+ && !sema.check(n)
+ )
+ {
+ return;
+ }
+
+ // In phase 0, only need to consider namespaces and types
+
+ if (
+ printer.get_phase() == printer.phase0_type_decls
+ && !n.is_namespace()
+ && !n.is_type()
+ )
+ {
+ return;
+ }
+
+ // If this is a generated declaration (negative source line number),
+ // add a line break before
+ if (
+ printer.get_phase() == printer.phase2_func_defs
+ && n.position().lineno < 1
+ )
+ {
+ printer.print_extra("\n");
+ }
+
+ auto guard0 = stack_value(having_signature_emitted, &n);
+ auto guard1 = stack_element(current_declarations, &n);
+ current_names.push_back(&n);
+ auto guard2 = stack_size_if(current_names, n.is_function());
+
+ // Handle aliases
+
+ if (n.is_alias())
+ {
+ auto& a = std::get<declaration_node::an_alias>(n.type);
+ assert(a);
+
+ // Namespace-scope aliases are emitted in phase 1,
+ // type-scope object aliases in both phases 1 and 2, and
+ // function-scope aliases in phase 2
+ if (
+ (
+ !n.parent_is_function()
+ && printer.get_phase() == printer.phase1_type_defs_func_decls
+ )
+ ||
+ (
+ n.parent_is_type()
+ && n.is_object_alias()
+ && printer.get_phase() == printer.phase2_func_defs
+ )
+ ||
+ (
+ n.parent_is_function()
+ && printer.get_phase() == printer.phase2_func_defs
+ )
+ )
+ {
+ assert(
+ a->is_type_alias()
+ || a->is_namespace_alias()
+ || a->is_object_alias()
+ );
+
+ // If we're in a type scope, handle the access specifier
+ if (
+ n.parent_is_type()
+ && printer.get_phase() == printer.phase1_type_defs_func_decls
+ )
+ {
+ if (!n.is_default_access()) {
+ printer.print_cpp2(to_string(n.access) + ": ", n.position());
+ }
+ else {
+ printer.print_cpp2("public: ", n.position());
+ }
+ }
+
+ // Emit template parameters if any
+ if (
+ a->is_object_alias()
+ && n.parent_is_type()
+ && printer.get_phase() == printer.phase2_func_defs
+ )
+ {
+ emit_parent_template_parameters();
+ }
+
+ if (n.template_parameters) {
+ printer.print_cpp2("template", n.position());
+ emit(*n.template_parameters, false, true);
+ printer.print_cpp2(" ", n.position());
+ }
+
+ // Emit requires clause if any
+ emit_requires_clause();
+
+ // Handle type aliases
+ if (a->is_type_alias()) {
+ printer.print_cpp2(
+ "using "
+ + print_to_string(*n.identifier)
+ + " = "
+ + print_to_string( *std::get<alias_node::a_type>(a->initializer) )
+ + ";\n",
+ n.position()
+ );
+ }
+
+ // Handle namespace aliases
+ else if (a->is_namespace_alias()) {
+ printer.print_cpp2(
+ "namespace "
+ + print_to_string(*n.identifier)
+ + " = "
+ + print_to_string( *std::get<alias_node::a_namespace>(a->initializer) )
+ + ";\n",
+ n.position()
+ );
+ }
+
+ // Handle object aliases:
+ // - at function scope, it's const&
+ // - at namespace scope, it's inline constexpr
+ // - at type scope, it's also inline constexpr but see note (*) below
+ else if (a->is_object_alias())
+ {
+ auto type = std::string{"auto"};
+ if (a->type_id) {
+ type = print_to_string(*a->type_id);
+ }
+
+ // (*) If this is at type scope, Cpp1 requires an out-of-line declaration dance
+ // for some cases to work - see https://stackoverflow.com/questions/11928089/
+ if (n.parent_is_type())
+ {
+ assert (n.parent_declaration->name());
+
+ if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
+ printer.print_cpp2(
+ "static const "
+ + type + " "
+ + print_to_string(*n.identifier)
+ + ";\n",
+ n.position()
+ );
+ }
+ else if (printer.get_phase() == printer.phase2_func_defs) {
+ // The following logic is not yet complete, so give a diagnostic for now
+ if (n.parent_declaration->parent_is_type()) {
+ errors.emplace_back(
+ n.position(),
+ "(temporary alpha limitation) an object alias cannot yet appear inside a nested type"
+ );
+ return;
+ }
+
+ printer.print_cpp2(
+ "inline CPP2_CONSTEXPR "
+ + type
+ + " "
+ + type_qualification_if_any_for(n)
+ + print_to_string(*n.identifier)
+ + " = "
+ + print_to_string( *std::get<alias_node::an_object>(a->initializer) )
+ + ";\n",
+ n.position()
+ );
+ }
+ }
+ // Otherwise, at function and namespace scope we can just define
+ else
+ {
+ auto intro = std::string{};
+ if (n.parent_is_function()) {
+ intro = "constexpr";
+ }
+ else if (n.parent_is_namespace()) {
+ intro = "inline constexpr";
+ }
+
+ printer.print_cpp2(
+ type + " "
+ + intro + " "
+ + print_to_string(*n.identifier)
+ + " = "
+ + print_to_string( *std::get<alias_node::an_object>(a->initializer) )
+ + ";\n",
+ n.position()
+ );
+ }
+ }
+
+ else {
+ assert(!"ICE: should be unreachable - invalid alias");
+ }
+
+ return;
+ }
+ }
+
+
+ // Handle other declarations
+
+ auto need_to_generate_assignment = false;
+ auto need_to_generate_move = false;
+ auto need_to_generate_postfix_inc_dec = false;
+
+ if (
+ n.is_function()
+ && n.has_name()
+ )
+ { // reset the 'that' flags
+ emitting_that_function = false;
+ emitting_move_that_function = false;
+ already_moved_that_members = {};
+ }
+
+ auto is_main =
+ !n.parent_declaration
+ && n.has_name("main")
+ ;
+ auto is_in_type = n.parent_is_type();
+
+ if (!check_shadowing_of_type_scope_names(n)) {
+ return;
+ }
+
+
+ // If this is a function that has multiple return values,
+ // first we need to emit the struct that contains the returns
+ if (
+ printer.get_phase() == printer.phase1_type_defs_func_decls
+ && n.is_function()
+ )
+ {
+ auto& func = std::get<declaration_node::a_function>(n.type);
+ assert(func);
+
+ if (func->returns.index() == function_type_node::list)
+ {
+ auto& r = std::get<function_type_node::list>(func->returns);
+ assert(r);
+ assert(std::ssize(r->parameters) > 0);
+
+ auto func_name = n.name()->to_string();
+
+ // If it's a single named value, emit it as an anonymous return value
+ if (std::ssize(r->parameters) == 1)
+ {
+ printer.print_extra(
+ "\nusing "
+ + func_name + "_ret = "
+ + r->parameters[0]->declaration->get_object_type()->to_string()
+ + ";"
+ );
+ }
+ // Else just emit it as an ordinary struct
+ else
+ {
+ printer.print_extra(
+ "\nstruct "
+ + n.name()->to_string()
+ + "_ret "
+ );
+ emit(*r, true);
+ }
+ printer.print_extra( "\n" );
+ }
+ }
+
+ // If this is a class definition that has data members before bases,
+ // first we need to emit the aggregate that contains the members
+ if (
+ n.is_type()
+ && printer.get_phase() == printer.phase1_type_defs_func_decls
+ )
+ {
+ assert(
+ n.initializer
+ && n.initializer->is_compound()
+ );
+ auto& compound_stmt = std::get<statement_node::compound>(n.initializer->statement);
+
+ assert(compound_stmt);
+ auto found = false;
+
+ for (auto& stmt : compound_stmt->statements)
+ {
+ if (stmt->is_declaration())
+ {
+ auto& decl = std::get<statement_node::declaration>(stmt->statement);
+ assert(decl);
+ assert(decl->name());
+
+ auto emit_as_base =
+ decl->get_decl_if_type_scope_object_name_before_a_base_type(*decl->name());
+
+ if (emit_as_base) {
+ printer.print_extra(
+ "\nstruct "
+ + print_to_string(*decl->parent_declaration->name())
+ + "_"
+ + decl->name()->to_string()
+ + "_as_base { "
+ + print_to_string( *decl->get_object_type() )
+ + " "
+ + decl->name()->to_string()
+ + "; };"
+ );
+ found = true;
+ }
+ }
+ }
+
+ if (found) {
+ printer.print_extra("\n");
+ }
+ }
+
+ // In class definitions, emit the explicit access specifier if there
+ // is one, or default to private for data and public for functions
+ if (printer.get_phase() == printer.phase1_type_defs_func_decls)
+ {
+ if (!n.is_default_access()) {
+ assert (is_in_type);
+ printer.print_cpp2(to_string(n.access) + ": ", n.position());
+ }
+ else if (is_in_type) {
+ if (n.is_object()) {
+ printer.print_cpp2("private: ", n.position());
+ }
+ else {
+ printer.print_cpp2("public: ", n.position());
+ }
+ }
+ }
+
+ // If this is a function definition and the function is inside
+ // type(s) that have template parameters and/or requires clauses,
+ // emit those outer template parameters and requires clauses too
+ if (
+ printer.get_phase() == printer.phase2_func_defs
+ && n.is_function()
+ && n.initializer // only if the function has a definition (is not abstract)
+ )
+ {
+ emit_parent_template_parameters();
+ }
+
+ // Now, emit our own template parameters
+ if (
+ n.template_parameters
+ && (
+ printer.get_phase() < printer.phase2_func_defs
+ || n.is_object()
+ || (
+ n.is_function()
+ && n.has_name() // only if it is not unnamed function aka lambda
+ && n.initializer // only if the function has a definition (is not abstract)
+ && printer.get_phase() == printer.phase2_func_defs
+ )
+ )
+ && (
+ !n.is_concept()
+ || printer.get_phase() == printer.phase1_type_defs_func_decls
+ )
+ )
+ {
+ printer.print_cpp2("template", n.position());
+ emit(*n.template_parameters, false, true);
+ printer.print_cpp2(" ", n.position());
+ }
+
+ // User-defined type
+ if (n.is_type())
+ {
+ assert(
+ n.initializer
+ && n.initializer->is_compound()
+ );
+ auto& compound_stmt = std::get<statement_node::compound>(n.initializer->statement);
+
+ if (printer.get_phase() != printer.phase2_func_defs)
+ {
+ if (n.requires_clause_expression) {
+ printer.print_cpp2("requires( ", n.requires_pos);
+ emit(*n.requires_clause_expression);
+ printer.print_cpp2(" )\n", n.requires_pos);
+ }
+
+ printer.print_cpp2("class ", n.position());
+ emit(*n.identifier);
+
+ // Type declaration
+ if (printer.get_phase() == printer.phase0_type_decls) {
+ printer.print_cpp2( ";\n", n.position() );
+ return;
+ }
+ }
+
+ if (
+ n.is_type_final()
+ && printer.get_phase() == printer.phase1_type_defs_func_decls
+ )
+ {
+ printer.print_cpp2( " final", n.position() );
+ }
+
+ // Type definition
+ auto separator = std::string{":"};
+ auto started_body = false;
+ auto saved_for_body = std::vector<std::pair<std::string, source_position>>{};
+ auto found_constructor = false;
+ auto found_that_constructor = false;
+ assert(compound_stmt);
+
+ auto start_body = [&]{
+ if (!started_body) {
+ printer.print_cpp2(" {", compound_stmt->position());
+ started_body = true;
+ for (auto& [line, pos] : saved_for_body) {
+ printer.print_cpp2(line + "\n", pos);
+ }
+ }
+ };
+
+ for (auto& stmt : compound_stmt->statements)
+ {
+ assert(stmt);
+ if (
+ !stmt->is_declaration()
+ && !stmt->is_using()
+ )
+ {
+ // We will already have emitted an error for this in sema.check
+ return;
+ }
+
+ // If it's a using statement, save it up if we haven't started the body yet
+
+ if (stmt->is_using()) {
+ auto& use = std::get<statement_node::using_>(stmt->statement);
+ assert(use);
+ if (started_body) {
+ emit(*use);
+ }
+ else {
+ saved_for_body.emplace_back( print_to_string(*use), use->position() );
+ }
+ continue;
+ }
+
+ // Else it's a declaration...
+
+ auto& decl = std::get<statement_node::declaration>(stmt->statement);
+ assert(decl);
+
+ if (
+ decl->is_alias()
+ && printer.get_phase() == printer.phase1_type_defs_func_decls
+ )
+ {
+ if (started_body) {
+ emit(*decl);
+ }
+ else {
+ saved_for_body.emplace_back( print_to_string(*decl), decl->position() );
+ }
+ continue;
+ }
+
+ if (decl->is_constructor()) {
+ found_constructor = true;
+ }
+ if (decl->is_constructor_with_that()) {
+ found_that_constructor = true;
+ }
+
+ // First we'll encounter the base types == subobjects named "this"
+ // and any data members declared before them that we push into private bases
+ assert(decl->name());
+ auto emit_as_base =
+ decl->get_decl_if_type_scope_object_name_before_a_base_type(*decl->name())
+ || decl->has_name("this")
+ ;
+ if (emit_as_base)
+ {
+ // Do the sema check for these declarations here, because we're
+ // handling them here instead of going through emit() for them
+ if (!sema.check(*decl)) {
+ return;
+ }
+
+ 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()),
+ compound_stmt->position()
+ );
+ separator = ",";
+ }
+ }
+ else
+ {
+ if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
+ printer.print_cpp2(
+ separator
+ + " public "
+ + print_to_string(*decl->parent_declaration->name())
+ + "_"
+ + decl->name()->to_string()
+ + "_as_base",
+ compound_stmt->position()
+ );
+ separator = ",";
+ }
+ }
+ }
+ // Then we'll switch to start the body == other members
+ else
+ {
+ if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
+ start_body();
+ }
+ emit(*decl);
+ }
+ }
+
+ if (printer.get_phase() == printer.phase1_type_defs_func_decls)
+ {
+ // Ensure we emit the { even if there are only bases in the type
+ start_body();
+
+ auto id = print_to_string(*n.identifier);
+ auto indent = static_cast<size_t>(
+ std::clamp(
+ compound_stmt->body_indent,
+ n.position().colno,
+ n.position().colno + 5 // sanity check
+ )
+ );
+ auto prefix = "\n" + std::string( indent, ' ' ) + "public: ";
+
+ if (n.member_function_generation)
+ {
+ // If no constructor was defined, there should only be
+ // a default constructor, so generate that
+ if (!found_constructor) {
+ printer.print_extra( prefix + id + "() = default;" );
+ }
+
+ // If no 'that' constructor was defined, disable copy/move
+ // so that Cpp1 doesn't silently generate it anyway
+ if (!found_that_constructor) {
+ printer.print_extra( prefix + id + "(" + id + " const&) = delete; /* No 'that' constructor, suppress copy */" );
+ printer.print_extra( prefix + "auto operator=(" + id + " const&) -> void = delete;" );
+ }
+
+ if (!found_constructor || !found_that_constructor) {
+ printer.print_extra( "\n" );
+ }
+ }
+
+ printer.print_cpp2("};\n", compound_stmt->close_brace);
+ }
+ }
+
+
+ // Namespace
+ if (n.is_namespace())
+ {
+ printer.print_cpp2("namespace ", n.position());
+
+ // "_" is the anonymous namespace, which is just whitespace in Cpp1
+ if (auto tok = n.identifier->get_token();
+ tok
+ && *tok != "_"
+ )
+ {
+ emit(*n.identifier);
+ }
+
+ assert(
+ n.initializer
+ && n.initializer->is_compound()
+ );
+ auto& compound_stmt = std::get<statement_node::compound>(n.initializer->statement);
+
+ printer.print_cpp2(" {", compound_stmt->position());
+
+ assert(compound_stmt);
+ for (auto& stmt : compound_stmt->statements) {
+ assert(stmt);
+ if (stmt->is_declaration()) {
+ auto& decl = std::get<statement_node::declaration>(stmt->statement);
+ assert(decl);
+ emit(*decl);
+ }
+ else if (stmt->is_using()) {
+ auto& use = std::get<statement_node::using_>(stmt->statement);
+ assert(use);
+ emit(*use);
+ }
+ else {
+ errors.emplace_back(
+ stmt->position(),
+ "a namespace scope must contain only declarations or 'using' statements, not other code"
+ );
+ return;
+ }
+ }
+
+ printer.print_cpp2("}\n", compound_stmt->close_brace);
+ }
+
+ // Function
+ else if (
+ n.is_function()
+ && (
+ printer.get_phase() < printer.phase2_func_defs
+ || n.initializer // only emit definition if the function has one (is not abstract)
+ || n.is_defaultable_function()
+ )
+ )
+ {
+ auto is_streaming_operator = [](std::string_view sv) {
+ return
+ sv == "operator<<"
+ || sv == "operator>>"
+ ;
+ };
+
+ auto is_binary_arithmetic_operator = [](std::string_view sv) {
+ return
+ sv == "operator+"
+ || sv == "operator-"
+ || sv == "operator*"
+ || sv == "operator/"
+ || sv == "operator%"
+ ;
+ };
+
+ auto emit_as_friend =
+ n.name()
+ && (
+ is_streaming_operator( n.name()->as_string_view() )
+ || (!n.is_function_with_this() && is_binary_arithmetic_operator( n.name()->as_string_view() ))
+ )
+ ;
+
+ // Start fresh (there may be one spurious leftover
+ // requires-condition created during the declarations pass)
+ function_requires_conditions = {};
+
+ auto& func = std::get<declaration_node::a_function>(n.type);
+ assert(func);
+
+ current_functions.push(
+ &n,
+ func.get(),
+ n.find_parent_declared_value_set_functions()
+ );
+ auto guard0 = finally([&]{ current_functions.pop(); });
+
+ auto guard1 = stack_size(current_names);
+
+ // If this is at expression scope, we can't emit "[[nodiscard]] auto name"
+ // so print the provided intro instead, which will be a Cpp1 lambda-introducer
+ if (capture_intro != "")
+ {
+ assert (!n.identifier);
+ printer.print_cpp2(capture_intro, n.position());
+ emit( *func, nullptr, is_main);
+ }
+
+ // Else start introducing a normal function
+ else
+ {
+ assert (n.identifier);
+
+ // Handle member functions
+ std::string prefix = {};
+ std::string suffix1 = {};
+ std::string suffix2 = {};
+
+ if (n.is_constexpr) {
+ prefix += "constexpr ";
+ }
+
+ if (
+ !n.has_initializer()
+ && n.is_defaultable_function()
+ )
+ {
+ suffix2 += " = default";
+ }
+
+ // If there's a 'this' parameter, handle it here (the parameter emission will skip it)
+ // because Cpp1 syntax requires its information to be spread around the declaration syntax
+ assert (func->parameters);
+ if (
+ !func->parameters->parameters.empty()
+ && func->parameters->parameters[0]->declaration->has_name("this")
+ )
+ {
+ assert (is_in_type);
+ auto& this_ = func->parameters->parameters[0];
+
+ switch (this_->pass) {
+ break;case passing_style::in:
+ suffix1 += " const";
+ // Cpp1 ref-qualifiers don't belong on virtual functions
+ if (!this_->is_polymorphic()) {
+ suffix1 += "&";
+ }
+ break;case passing_style::inout:
+ // Cpp1 ref-qualifiers don't belong on virtual functions
+ if (!this_->is_polymorphic()) {
+ suffix1 += " &";
+ }
+ break;case passing_style::out:
+ ; // constructor is handled below
+ break;case passing_style::move:
+ suffix1 += " &&";
+
+ // We shouldn't be able to get into a state where these values
+ // exist here, if we did it's our compiler bug
+ break;case passing_style::copy:
+ case passing_style::forward:
+ default:
+ errors.emplace_back( n.position(), "ICE: invalid parameter passing style, should have been rejected", true);
+ }
+
+ // Note: Include a phase check because Cpp1 does not allow
+ // these on out-of-line definitions
+ if (printer.get_phase() != printer.phase2_func_defs)
+ {
+ switch (this_->mod) {
+ break;case parameter_declaration_node::modifier::implicit:
+ ;
+ break;case parameter_declaration_node::modifier::virtual_:
+ prefix += "virtual ";
+ if (!n.initializer) {
+ suffix2 += " = 0";
+ }
+ break;case parameter_declaration_node::modifier::override_:
+ suffix2 += " override";
+ break;case parameter_declaration_node::modifier::final_:
+ suffix2 += " final";
+ break;default:
+ if (
+ func->is_constructor()
+ && !func->is_constructor_with_that()
+ && generating_assignment_from != &n
+ )
+ {
+ prefix += "explicit ";
+ }
+ }
+ }
+ }
+ // Else if there isn't a 'this' parameter, but this function is in a type scope,
+ // it's a Cpp1 non-member function so we need to say so (on the declaration only)
+ else if (
+ is_in_type
+ && printer.get_phase() != printer.phase2_func_defs
+ ) {
+ if (emit_as_friend) {
+ prefix += "friend ";
+ }
+ else {
+ prefix += "static ";
+ }
+ }
+
+ // If there's a return type, it's [[nodiscard]] implicitly and all the time
+ // -- for now there's no opt-out, wait and see whether we actually need one
+ if (
+ func->has_non_void_return_type()
+ && !func->is_assignment()
+ && !func->is_compound_assignment()
+ && !func->is_increment_or_decrement()
+ && (
+ printer.get_phase() == printer.phase1_type_defs_func_decls
+ || n.has_initializer() // so we're printing it in phase 2
+ )
+ && (
+ !emit_as_friend // can't have an attribute on a friend declaration-not-definition
+ || printer.get_phase() != printer.phase1_type_defs_func_decls
+ )
+ && !(
+ n.name()
+ && is_streaming_operator(n.name()->as_string_view())
+ )
+ )
+ {
+ printer.print_cpp2( "[[nodiscard]] ", n.position() );
+ }
+
+ // Now we have all the pieces we need for the Cpp1 function declaration
+
+ // For a special member function, we need to do more work to translate
+ // in-body initialization statements to the Cpp1 mem-init-list syntax
+ if (
+ n.is_constructor()
+ || n.is_assignment()
+ )
+ {
+ assert(
+ !is_main
+ && suffix2.empty()
+ && "ICE: an operator= shouldn't have been able to generate a suffix (or be main)"
+ );
+
+ emit_special_member_function(
+ n,
+ prefix
+ );
+
+ // If there's no inheritance and this operator= has two parameters,
+ // it's setting from a single value -- either from the same type
+ // (aka copy/move) or another type (a conversion) -- so recurse to
+ // emit related functions if the user didn't write them by hand
+ if (
+ !n.parent_is_polymorphic()
+ && func->parameters->ssize() == 2
+ && generating_assignment_from != &n
+ )
+ {
+ assert(!current_functions.empty());
+
+ // A) Generate (A)ssignment from a constructor,
+ // if the user didn't write the assignment function themselves
+ if (
+ // A1) This is '(out this, that)'
+ // and no '(inout this, that)' was written by the user
+ (
+ &n == current_functions.back().declared_value_set_functions.out_this_in_that
+ && !current_functions.back().declared_value_set_functions.inout_this_in_that
+ )
+ ||
+ // A2) This is '(out this, move that)'
+ // and no '(inout this, move that)' was written by the user
+ // (*) and no '(inout this, that)' was written by the user (*)
+ //
+ // (*) This third test is to tie-break M2 and A2 in favor of M2. Both M2 and A2
+ // can generate a missing '(inout this, move that)', and if we have both
+ // options then we should prefer to use M2 (generate move assignment from
+ // copy assignment) rather than A2 (generate move assignment from move
+ // construction) as M2 is a better fit (move assignment is more like copy
+ // assignment than like move construction, because assignments are designed
+ // structurally to set the value of an existing 'this' object)
+ (
+ &n == current_functions.back().declared_value_set_functions.out_this_move_that
+ && !current_functions.back().declared_value_set_functions.inout_this_move_that
+ && !current_functions.back().declared_value_set_functions.inout_this_in_that
+ )
+ ||
+ // A3) This is '(out this, something-other-than-that)'
+ (
+ n.is_constructor()
+ && !n.is_constructor_with_that()
+ && !contains( current_functions.back().declared_value_set_functions.assignments_from, n.nth_parameter_type_name(2) )
+ )
+ )
+ {
+ need_to_generate_assignment = true;
+ }
+
+ if (generating_move_from != &n) {
+
+ // M) Generate (M)ove from copy,
+ // if the user didn't write the move function themselves
+ if (
+ // M1) This is '(out this, that)'
+ // and no '(out this, move that)' was written by the user
+ (
+ &n == current_functions.back().declared_value_set_functions.out_this_in_that
+ && !current_functions.back().declared_value_set_functions.out_this_move_that
+ )
+ ||
+ // M2) This is '(inout this, that)'
+ // and no '(inout this, move that)' was written by the user
+ (
+ &n == current_functions.back().declared_value_set_functions.inout_this_in_that
+ && !current_functions.back().declared_value_set_functions.inout_this_move_that
+ )
+ )
+ {
+ need_to_generate_move = true;
+ }
+
+ }
+ }
+ }
+
+ // For a destructor, we need to translate
+ else if (n.is_destructor())
+ {
+ assert(
+ !is_main
+ // prefix can be "virtual"
+ // suffix1 will be " &&" though we'll ignore that
+ // suffix2 can be "= 0"
+ );
+
+ // Print the ~-prefixed type name instead of the operator= function name
+ assert(
+ n.parent_is_type()
+ && n.parent_declaration->name()
+ );
+ printer.print_cpp2(
+ prefix
+ + type_qualification_if_any_for(n)
+ + "~" + print_to_string(*n.parent_declaration->name()),
+ n.position()
+ );
+ emit( *func, n.name(), false, true);
+ printer.print_cpp2( suffix2, n.position() );
+ }
+
+ // Ordinary functions are easier, do all their declarations except
+ // don't emit abstract virtual functions in phase 2
+ else if (
+ n.initializer
+ || printer.get_phase() < printer.phase2_func_defs
+ )
+ {
+ printer.print_cpp2( prefix, n.position() );
+ printer.print_cpp2( "auto ", n.position() );
+ if (
+ !emit_as_friend
+ || printer.get_phase() != printer.phase2_func_defs
+ )
+ {
+ printer.print_cpp2( type_qualification_if_any_for(n), n.position() );
+ }
+
+ emit( *n.name() );
+ emit( *func, n.name(), is_main, false, suffix1, generating_postfix_inc_dec_from != nullptr );
+ printer.print_cpp2( suffix2, n.position() );
+
+ // If this is ++ or --, also generate a Cpp1 postfix version of the operator
+ if (func->is_increment_or_decrement())
+ {
+ if (generating_postfix_inc_dec_from) {
+ assert (generating_postfix_inc_dec_from == &n);
+ }
+ else {
+ need_to_generate_postfix_inc_dec = true;
+ }
+ }
+ }
+ }
+
+ // If we're only emitting declarations, end the function declaration
+ if (
+ printer.get_phase() == printer.phase1_type_defs_func_decls
+ && !n.is_function_expression()
+ )
+ {
+ emit_requires_clause();
+ if (n.position().lineno < 0) {
+ printer.print_cpp2( ";\n", n.position() );
+ }
+ else {
+ printer.print_cpp2( ";", n.position() );
+ }
+
+ // Note: Not just early "return;" here because we may need
+ // to recurse to emit generated operator declarations too,
+ // so all the definition work goes into a big 'else' branch
+ }
+
+ // Else emit the definition
+ else if (n.initializer)
+ {
+ if (func->returns.index() == function_type_node::list) {
+ auto& r = std::get<function_type_node::list>(func->returns);
+ function_returns.emplace_back(r.get());
+ }
+ else if (func->returns.index() == function_type_node::id) {
+ function_returns.emplace_back(
+ &single_anon, // use special value as a note
+ std::get<function_type_node::id>(func->returns).pass,
+ std::get<function_type_node::id>(func->returns).type->is_wildcard()
+ );
+ }
+ else {
+ function_returns.emplace_back(nullptr); // no return type at all
+ }
+
+ if (func->has_postconditions()) {
+ current_functions.back().prolog.statements.push_back("cpp2::finally_presuccess cpp2_finally_presuccess;");
+ }
+
+ if (func->returns.index() == function_type_node::list)
+ {
+ auto& r = std::get<function_type_node::list>(func->returns);
+ assert(r);
+ for (auto& param : r->parameters)
+ {
+ assert(param && param->declaration);
+ auto& decl = *param->declaration;
+
+ assert(decl.is_object());
+ auto& id_expr = std::get<declaration_node::an_object>(decl.type);
+ assert(id_expr);
+
+ auto loc = std::string{};
+ if (!decl.initializer) {
+ loc += (" cpp2::deferred_init<");
+ }
+
+ // For convenience, just capture the id-expression as a string
+ printer.emit_to_string(&loc);
+ emit(*id_expr);
+ printer.emit_to_string();
+
+ if (!decl.initializer) {
+ loc += (">");
+ }
+ loc += " ";
+ loc += decl.name()->as_string_view();
+ if (decl.initializer)
+ {
+ std::string init;
+ printer.emit_to_string(&init);
+ printer.print_cpp2 ( " {", decl.initializer->position() );
+ if (!decl.initializer->is_expression()) {
+ errors.emplace_back(
+ decl.initializer->position(),
+ "return value initializer must be an expression"
+ );
+ return;
+ }
+ auto& expr = std::get<statement_node::expression>(decl.initializer->statement);
+ assert(expr);
+
+ emit(*expr, false);
+ printer.print_cpp2 ( "}", decl.initializer->position() );
+ printer.emit_to_string();
+
+ loc += init;
+ }
+ loc += ";";
+ current_functions.back().prolog.statements.push_back(loc);
+ }
+ }
+
+ for (auto&& c : func->contracts)
+ {
+ auto print = std::string();
+ printer.emit_to_string(&print);
+ auto guard = stack_value(having_signature_emitted, nullptr);
+ emit(*c);
+ printer.emit_to_string();
+ current_functions.back().prolog.statements.push_back(print);
+ }
+
+ printer.preempt_position_push( n.equal_sign );
+
+ emit_requires_clause();
+
+ having_signature_emitted = nullptr;
+
+ // If this is ++ or --, also generate a Cpp1 postfix version of the operator
+ if (generating_postfix_inc_dec_from)
+ {
+ assert (generating_postfix_inc_dec_from == &n);
+
+ auto param1 = std::string{"*this"};
+ if (
+ !n.parent_declaration
+ || !n.parent_declaration->is_type()
+ )
+ {
+ param1 = n.first_parameter_name();
+ }
+
+ printer.print_cpp2(
+ " { auto ret = " + param1 + "; ++" + param1 + "; return ret; }",
+ n.position()
+ );
+ }
+ // Else just emit the normal function body
+ else {
+ emit(
+ *n.initializer,
+ true, func->position(), func->returns.index() == function_type_node::empty,
+ current_functions.back().prolog,
+ current_functions.back().epilog
+ );
+ }
+
+ printer.preempt_position_pop();
+
+ function_returns.pop_back();
+ }
+
+ // Finally, do the potential recursions...
+
+ // If this was a constructor and we want also want to emit
+ // it as an assignment operator, do it via a recursive call
+ if (need_to_generate_assignment)
+ {
+ // Reset the 'emitted' flags
+ for (auto& statement : n.get_initializer_statements()) {
+ statement->emitted = false;
+ }
+
+ // Then reposition and do the recursive call
+ printer.reset_line_to(n.position().lineno);
+ generating_assignment_from = &n;
+ emit( n, capture_intro );
+ generating_assignment_from = {};
+ }
+
+ // If this was a constructor and we want also want to emit
+ // it as an assignment operator, do it via a recursive call
+ if (need_to_generate_move)
+ {
+ // Reset the 'emitted' flags
+ for (auto& statement : n.get_initializer_statements()) {
+ statement->emitted = false;
+ }
+
+ // Then reposition and do the recursive call
+ printer.reset_line_to(n.position().lineno);
+ generating_move_from = &n;
+ emit( n, capture_intro );
+ generating_move_from = {};
+ }
+
+ // If this is ++ or --, emit the Cpp1 postfix version via a recursive call
+ if (need_to_generate_postfix_inc_dec)
+ {
+ // Reset the 'emitted' flags
+ for (auto& statement : n.get_initializer_statements()) {
+ statement->emitted = false;
+ }
+
+ // Then reposition and do the recursive call
+ printer.reset_line_to(n.position().lineno);
+ generating_postfix_inc_dec_from = &n;
+ emit( n, capture_intro );
+ generating_postfix_inc_dec_from = {};
+ }
+ }
+
+ // Object with optional initializer
+ else if (
+ n.is_object()
+ && (
+ (
+ n.parent_is_namespace()
+ && printer.get_phase() >= printer.phase1_type_defs_func_decls
+ )
+ ||
+ (
+ n.parent_is_type()
+ && printer.get_phase() == printer.phase1_type_defs_func_decls
+ )
+ ||
+ (
+ n.parent_is_function()
+ && printer.get_phase() == printer.phase2_func_defs
+ )
+ ||
+ (
+ n.is_inside_global_unnamed_function()
+ && printer.get_phase() == printer.phase1_type_defs_func_decls
+ )
+ )
+ )
+ {
+ auto& type = std::get<declaration_node::an_object>(n.type);
+ if (
+ printer.get_phase() == printer.phase2_func_defs
+ && type->is_concept()
+ )
+ {
+ return;
+ }
+
+ emit_requires_clause();
+
+ if (
+ printer.get_phase() != printer.phase2_func_defs
+ && n.parent_is_namespace()
+ && !type->is_concept()
+ )
+ {
+ printer.print_cpp2( "extern ", n.position() );
+ }
+
+ // Emit "auto" for deduced types (of course)
+ if (type->is_wildcard()) {
+ assert(n.initializer);
+ emit( *type, n.position() );
+ }
+ // Otherwise, emit the type
+ else {
+ // If there isn't an initializer, use cpp2::deferred_init<T>
+ if (!n.initializer) {
+ if (n.parent_is_function()) {
+ printer.print_cpp2( "cpp2::deferred_init<", n.position() );
+ }
+ else if (!n.parent_is_type()) {
+ errors.emplace_back(
+ n.position(),
+ "a namespace-scope object must have an initializer"
+ );
+ return;
+ }
+ }
+ printer.preempt_position_push(n.position());
+ emit( *type );
+ printer.preempt_position_pop();
+ // one pointer is enough for now, pointer-to-function fun can be later
+ if (
+ !n.initializer
+ && n.parent_is_function()
+ )
+ {
+ printer.print_cpp2( ">", n.position() );
+ }
+ }
+
+ printer.print_cpp2( " ", n.position());
+ assert(n.identifier);
+
+ // If this is anonymous object (named "_"), generate a unique name
+ if (n.has_name("_")) {
+ if (n.has_wildcard_type()) {
+ errors.emplace_back(
+ n.identifier->position(),
+ "an object can have an anonymous name or an anonymous type, but not both at the same type (rationale: if '_ := f();' were allowed to keep the returned object alive, that syntax would be dangerously close to '_ = f();' to discard the returned object, and such importantly opposite meanings deserve more than a one-character typo distance; and explicit discarding gets the nice syntax because it's likely more common)"
+ );
+ return;
+ }
+
+ printer.print_cpp2(
+ "auto_" + labelized_position(n.identifier->get_token()),
+ n.identifier->position()
+ );
+ }
+ else {
+ emit(*n.identifier);
+ }
+
+ if (
+ n.parent_is_namespace()
+ && printer.get_phase() != printer.phase2_func_defs
+ && !type->is_concept()
+ )
+ {
+ printer.print_cpp2( ";", n.position());
+ return;
+ }
+
+ // If there's an initializer, emit it
+ if (n.initializer)
+ {
+ printer.add_pad_in_this_line(-100);
+ if (type->is_concept()) {
+ printer.print_cpp2( " = ", n.position() );
+ } else {
+ printer.print_cpp2( " {", n.position() );
+ }
+
+ push_need_expression_list_parens(false);
+ assert( n.initializer );
+ emit( *n.initializer, false );
+ pop_need_expression_list_parens();
+
+ if (!type->is_concept()) {
+ printer.print_cpp2( "}", n.position() );
+ }
+ }
+
+ printer.print_cpp2( "; ", n.position() );
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ // print_errors
+ //
+ auto print_errors()
+ -> void
+ {
+ if (!errors.empty()) {
+ // Delete the output file
+ printer.abandon();
+ }
+
+ error_entry const* prev = {};
+ bool print_fallback_errors = true; // true until we find a non-fallback message
+
+ for (auto&& error : errors)
+ {
+ // Only print fallback error messages if we
+ // haven't found a better (non-fallback) one yet
+ if (!error.fallback) {
+ print_fallback_errors = false;
+ }
+ if (error.fallback && !print_fallback_errors) {
+ continue;
+ }
+
+ // Suppress adjacent duplicates (e.g., can arise when we
+ // reenter operator= to emit it as an assignment operator)
+ if (
+ !prev
+ || error != *prev
+ )
+ {
+ error.print(std::cerr, strip_path(sourcefile));
+ }
+ prev = &error;
+ }
+
+ if (violates_lifetime_safety) {
+ std::cerr << " ==> program violates lifetime safety guarantee - see previous errors\n";
+ }
+ if (violates_bounds_safety) {
+ std::cerr << " ==> program violates bounds safety guarantee - see previous errors\n";
+ }
+ if (violates_initialization_safety) {
+ std::cerr << " ==> program violates initialization safety guarantee - see previous errors\n";
+ }
+ }
+
+ auto had_no_errors()
+ -> bool
+ {
+ return errors.empty();
+ }
+
+
+ //-----------------------------------------------------------------------
+ // debug_print
+ //
+ auto debug_print()
+ -> void
+ {
+ // Only create debug output files if we managed to load the source file.
+ //
+ if (source_loaded)
+ {
+ auto out_source = std::ofstream{ sourcefile+"-source" };
+ source.debug_print( out_source );
+
+ auto out_tokens = std::ofstream{ sourcefile+"-tokens" };
+ tokens.debug_print( out_tokens );
+
+ auto out_parse = std::ofstream{ sourcefile+"-parse" };
+ parser.debug_print( out_parse );
+
+ auto out_symbols = std::ofstream{ sourcefile+"-symbols" };
+ sema.debug_print ( out_symbols );
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ // has_cpp1: pass through
+ //
+ auto has_cpp1() const
+ -> bool
+ {
+ return source.has_cpp1();
+ }
+
+
+ //-----------------------------------------------------------------------
+ // has_cpp2: pass through
+ //
+ auto has_cpp2() const
+ -> bool
+ {
+ return source.has_cpp2();
+ }
+};
+
+}
+
+
+#endif