[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/Popsalot/cppfront/main/source/cppfront.cpp [Back]  [Original]

//  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.


//===========================================================================
//  cppfront
//===========================================================================

#include "sema.h"
#include 
#include 
#include 

namespace cpp2 {

//  Defined out of line here just to avoid bringing  into the headers,
//  so that we can't accidentally start depending on iostreams in the compiler body
auto cmdline_processor::print(std::string_view s, int width)
    -> void
{
    if (width > 0) {
        std::cout  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) {
            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 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  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)
        -> 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 (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  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, 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* 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 mem_inits  = {};
    std::vector statements = {};
};

class cppfront
{
    std::string              sourcefile;
    std::vector 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*   generating_assignment_from  = {};
    declaration_node const*   generating_move_from        = {};
    bool                      emitting_that_function      = false;
    bool                      emitting_move_that_function = false;
    std::vector already_moved_that_members  = {};

    struct arg_info {
        passing_style pass   = passing_style::in;
        token const*  ptoken = {};
    };
    std::vector current_args  = { {} };

    std::vector current_declarations = { {} };

    //  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                         = {};
        declaration_node::declared_value_set_funcs declared_value_set_functions = {};
        function_prolog                            prolog                       = {};
        std::vector                   epilog                       = {};

        function_info(
            declaration_node const* decl_,
            declaration_node::declared_value_set_funcs declared_value_set_functions_
        )
            : decl{decl_}
            , declared_value_set_functions{declared_value_set_functions_}
        { }
    };
    class current_functions_
    {
        std::deque list = { {} };
    public:
        auto push(
            declaration_node const*               decl,
            declaration_node::declared_value_set_funcs thats
        ) {
            list.emplace_back(decl, 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_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                      function_requires_conditions;

    struct iter_info {
        iteration_statement_node const* stmt;
        bool used = false;
    };
    std::vector iteration_statements;

    std::vector                             in_non_rvalue_context   = { false };
    std::vector                             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
            )
        {
            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_import_std) {
                printer.print_extra( "#define CPP2_IMPORT_STD          Yes\n" );
            }
            if (flag_include_std) {
                printer.print_extra( "#define CPP2_INCLUDE_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" );
        }

        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" );
        }

        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::empty
                        )
                    {
                        ++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*/  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 
    auto try_emit(
        auto&     v,
        auto&&... more
    )
        -> void
    {
        if (v.index() == I) {
            auto const& alt = std::get(v);
            assert (alt);
            emit (*alt, CPP2_FORWARD(more)...);
        }
    }


    //-----------------------------------------------------------------------
    //
    auto emit(
        token const&    n,
        bool            is_qualified = false,
        source_position pos          = {}
    )
        -> void
    {
        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
    {
        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
    {
        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.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)
        -> void
    {
        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 : n.ids)
        {
            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
    {
        if (pos == source_position{}) {
            pos = n.position();
        }

        if (n.is_wildcard()) {
            printer.print_cpp2("auto", pos);
        }
        else {
            try_emit(n.id, false, false);
            try_emit(n.id);
            try_emit(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
    {
        try_emit(n.id);
        try_emit(n.id, false, is_local_name);
    }


    auto emit_prolog_mem_inits(
        function_prolog const& prolog,
        colno_t                indent
    )
        -> void
    {
        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
    {
        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 const& epilog,
        colno_t                         indent
    )
        -> void
    {
        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 const& function_epilog = {}
    )
        -> void
    {
        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
    {
        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_(";
                    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 const& n)
        -> void
    {
        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 =, or all ==
                auto found_lt = 0;  // < and  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  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()
        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(", 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
    {
        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
    {
        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 const&  function_epilog     = {},
        bool                             emitted             = false
    )
        -> void
    {
        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());
        }
    }


    //-----------------------------------------------------------------------
    //
    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 const& function_epilog     = {}
    )
        -> void
    {
        if (!sema.check(n)) {
            return;
        }

        auto emit_parameters =
            !n.emitted
            && n.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(
            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(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(n.statement);
        try_emit(n.statement);
        try_emit(n.statement);
        try_emit(n.statement);
        try_emit(n.statement);
        try_emit(n.statement, false);
        try_emit(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
    {
        //  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;
        }

        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"
                );
            }
        }

        //-----------------------------------------------------------------------
        //  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()) {
            printer.print_cpp2("typename ", n.declaration->identifier->position());
            if (n.declaration->is_variadic) {
                printer.print_cpp2(
                    "...",
                    n.declaration->identifier->position()
                );
            }
            assert (n.declaration->identifier);
            emit(*n.declaration->identifier);
            return;
        }

        //-----------------------------------------------------------------------
        //  Else handle template non-type parameters

        assert( n.declaration->is_object() );
        auto const& type_id = *std::get(n.declaration->type);

        if (is_template_parameter) {
            emit( type_id );
            printer.print_cpp2(" ", type_id.position());
            assert (n.declaration->identifier);
            emit(*n.declaration->identifier);
            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("",  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( " ", n.declaration->identifier->position());
            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(
                    "...",
                    n.declaration->identifier->position()
                );
            }
            printer.print_cpp2( identifier, n.declaration->identifier->position());
        }

        if (
            !is_returns
            && n.declaration->initializer
            )
        {
            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
    )
        -> void
    {
        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();
        for (auto first = true; 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 {
            //  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
    {
        assert (n.kind);

        //  For a postcondition, we'll wrap it in a final_action_success lambda
        //
        if (*n.kind == "post") {
            auto lambda_intro = build_capture_lambda_intro_for(n.captures, n.position());
            printer.print_cpp2(
                "auto post_" + std::to_string(n.position().lineno) + "_" +
                    std::to_string(n.position().colno) + " = cpp2::finally_success(" +
                    lambda_intro + "{",
                n.position()
            );
        }

        //  Emit the contract group name (defaults to cpp2::Default)
        //
        if (n.group) {
            //  If this is one of Cpp2's predefined contract groups,
            //  make it convenient to use without cpp2:: qualification
            if (auto uid = std::get_if(&n.group->id)) {
                assert (*uid && (**uid).identifier);
                if (
                    *(**uid).identifier == "Default"
                    || *(**uid).identifier == "Bounds"
                    || *(**uid).identifier == "Null"
                    || *(**uid).identifier == "Type"
                    || *(**uid).identifier == "Testing"
                    )
                {
                    printer.print_cpp2("cpp2::", n.position());
                }
            }

            printer.preempt_position_push(n.position());
            printer.add_pad_in_this_line(-20);
            emit(*n.group);
            printer.preempt_position_pop();
        }
        else {
            printer.print_cpp2("cpp2::Default", n.position());
            printer.add_pad_in_this_line(-8);
        }

        //  And invoke .expects on that contract group
        //
        printer.print_cpp2(".expects(", n.position());
        assert(n.condition);
        emit (*n.condition);
        printer.print_cpp2(", ", n.position());
        if (n.message) {
            emit (*n.message);
        }
        else {
            printer.print_cpp2("\"\"", n.position());
        }
        printer.print_cpp2(");", n.position());

        //  For a postcondition, close out the final_action_success 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         = {}
    )
        -> void
    {
        assert(n.parameters);

        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 args = cpp2::make_args(argc_, argv_); "
            );
        }
        else {
            emit(*n.parameters);
        }

        //  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(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 {
                        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{"";
                }
                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
    {
        assert(n.is_function());
        auto& func = std::get(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{};

            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(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 (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
    {
        //  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");
        }

        //  Handle aliases

        if (n.is_alias())
        {
            auto& a = std::get(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 (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(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(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;
                            }

                            auto parent_template_params = std::string{};
                            auto parent_template_args   = std::string{};
                            auto parent_qualifier       = std::string{};

                            auto parent = n.parent_declaration;
                            while (parent && parent->is_type())
                            {
                                if (!parent_qualifier.empty()) {
                                    parent_qualifier.insert(0, "::");
                                }
                                parent_qualifier.insert(0, parent->name()->to_string());

                                if (parent->template_parameters) {
                                    parent_template_params.insert(0, "template" + print_to_string(*parent->template_parameters));
                                    for (auto const& param : parent->template_parameters->parameters) {
                                        assert(param->name());
                                        if (parent_template_args.empty()) {
                                            parent_template_args = "";
                                        }
                                        else {
                                            parent_template_args.insert(parent_template_args.size()-1, ",");
                                        }
                                        parent_template_args.insert(parent_template_args.size()-1, param->name()->as_string_view());
                                    }
                                }

                                parent = parent->parent_declaration;
                            }

                            if (!parent_template_params.empty()) {
                                parent_template_params += " ";
                            }

                            printer.print_cpp2(
                                parent_template_params
                                    + "inline CPP2_CONSTEXPR "
                                    + type
                                    + parent_template_args
                                    + " " + parent_qualifier
                                    + parent_template_args + "::"
                                    + print_to_string(*n.identifier)
                                    + " = "
                                    + print_to_string( *std::get(a->initializer) )
                                    + ";\n",
                                n.position()
                            );
                        }
                    }
                    //  Otherwise, at function and namespace scope we can just declare
                    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(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;

        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;
        }

        current_declarations.push_back(&n);
        auto guard = finally([&]{ current_declarations.pop_back(); });

        //  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(n.type);
            assert(func);

            if (func->returns.index() == function_type_node::list) {
                auto& r = std::get(func->returns);
                assert(r);
                assert(std::ssize(r->parameters) > 0);
                printer.print_extra( "struct " );
                printer.print_extra( *n.name() );
                printer.print_extra( "_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(n.initializer->statement);

            assert(compound_stmt);
            auto found = false;

            for (auto& stmt : compound_stmt->statements)
            {
                if (!stmt->is_declaration()) {
                    //  We will already have emitted an error for this in sema.check
                    return;
                }
                auto& decl = std::get(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)
            )
        {
            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());
        }

        //  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(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 found_constructor      = false;
            auto found_that_constructor = false;
            assert(compound_stmt);

            for (auto& stmt : compound_stmt->statements)
            {
                assert(stmt);
                if (!stmt->is_declaration()) {
                    //  We will already have emitted an error for this in sema.check
                    return;
                }

                auto& decl = std::get(stmt->statement);
                assert(decl);

                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) {
                        if (!started_body) {
                            printer.print_cpp2(" {", compound_stmt->position());
                            started_body = true;
                        }
                    }
                    emit(*decl);
                }
            }

            //  Ensure we emit the { even if there are only bases in the type
            if (printer.get_phase() == printer.phase1_type_defs_func_decls)
            {
                if (!started_body) {
                    printer.print_cpp2(" {", compound_stmt->position());
                }

                auto id     = print_to_string(*n.identifier);
                auto indent = static_cast(
                                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(n.initializer->statement);

            printer.print_cpp2(" {", compound_stmt->position());

            assert(compound_stmt);
            for (auto& stmt : compound_stmt->statements) {
                assert(stmt);
                if (!stmt->is_declaration()) {
                    errors.emplace_back(
                        stmt->position(),
                        "a namespace scope must contain only declarations, not other code"
                    );
                    return;
                }
                auto& decl = std::get(stmt->statement);
                assert(decl);
                emit(*decl);
            }

            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"
                    ;
                };

            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(n.type);
            assert(func);

            current_functions.push(
                &n,
                n.find_parent_declared_value_set_functions()
                );
            auto guard = finally([&]{ current_functions.pop(); });

            //  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()
                    && (
                        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 );
                    printer.print_cpp2( suffix2, n.position() );
                }
            }

            //  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();
                printer.print_cpp2( ";\n", n.position() );
                //  Note: Not just early "return;" here because we may need to
                //  recurse to emit the 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(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(func->returns).pass,
                        std::get(func->returns).type->is_wildcard()
                    );
                }
                else {
                    function_returns.emplace_back(nullptr);        // no return type at all
                }

                for (auto&& c : func->contracts)
                {
                    auto print = std::string();
                    printer.emit_to_string(&print);
                    emit(*c);
                    printer.emit_to_string();
                    current_functions.back().prolog.statements.push_back(print);
                }

                if (func->returns.index() == function_type_node::list)
                {
                    auto& r = std::get(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(decl.type);
                        assert(id_expr);

                        auto loc = std::string{};
                        if (!decl.initializer) {
                            loc += ("    cpp2::deferred_init");
                        }
                        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(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);
                    }
                }

                printer.preempt_position_push( n.equal_sign );

                emit_requires_clause();

                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 = {};
            }
        }

        //  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
                    )
                )
            )
        {
            auto& type = std::get(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
                if (!n.initializer) {
                    if (n.parent_is_function()) {
                        printer.print_cpp2( "cpp2::deferred_init", 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)
            {
                in_non_rvalue_context.push_back(true);
                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() );
                }
                in_non_rvalue_context.pop_back();
            }

            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  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();
    }
};

}


//===========================================================================
//  main - driver
//===========================================================================

using namespace std;
using namespace cpp2;

static auto enable_debug_output_files = false;
static cmdline_processor::register_flag cmd_debug(
    9,
    "debug",
    "Emit compiler debug output files",
    []{ enable_debug_output_files = true; }
);

auto main(
    int   argc,
    char* argv[]
)
    -> int
{
    cmdline.set_args(argc, argv);
    cmdline.process_flags();

    if (cmdline.help_was_requested()) {
        return EXIT_SUCCESS;
    }

    if (cmdline.arguments().empty()) {
        std::cerr 

Web Proxy Viewer  |  New URL  |  Original Page