// Copyright 2022-2024 Herb Sutter
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// Part of the Cppfront Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://github.com/hsutter/cppfront/blob/main/LICENSE for license information.
//===========================================================================
// Lowering to Cpp1 syntax
//===========================================================================
#ifndef CPP2_TO_CPP1_H
#define CPP2_TO_CPP1_H
#include "sema.h"
#include
namespace cpp2 {
//-----------------------------------------------------------------------
//
// Stringingizing helpers
//
//-----------------------------------------------------------------------
auto pad(int padding)
-> std::string_view
{
if (padding < 1) {
return "";
}
return {
indent_str.c_str(),
_as( std::min( padding, _as(std::ssize(indent_str))) )
};
}
auto multi_return_type_name(declaration_node const& n)
-> std::string
{
// When generating a multi-return struct, also enable multi-return
// from a (), [], or * operator. We can expand this in the future,
// but with care because most operators should 'do as the ints do'
// (e.g., it doesn't seem sensible for == to return multiple values)
constexpr std::pair canonized_names[] = {
{ "operator()", "operator_call" },
{ "operator[]", "operator_subscript" },
{ "operator*", "operator_dereference" }
};
assert (n.is_function() && n.name());
auto ret = n.name()->to_string();
for (auto [op, canon] : canonized_names) {
if (ret == op) {
ret = canon;
break;
}
}
ret += "_ret";
return ret;
}
//-----------------------------------------------------------------------
//
// positional_printer: a Syntax 1 pretty printer
//
//-----------------------------------------------------------------------
//
static auto flag_emit_cppfront_info = false;
static cmdline_processor::register_flag cmd_emit_cppfront_info(
8,
"emit-cppfront-info",
"Emit cppfront version/build in Cpp1 file",
[]{ flag_emit_cppfront_info = true; }
);
static auto flag_clean_cpp1 = false;
static cmdline_processor::register_flag cmd_clean_cpp1(
8,
"clean-cpp1",
"Emit clean Cpp1 without #line directives",
[]{ flag_clean_cpp1 = true; }
);
static auto flag_line_paths = false;
static cmdline_processor::register_flag cmd_line_paths(
8,
"line-paths",
"Emit absolute paths in #line directives",
[] { flag_line_paths = true; }
);
static auto flag_import_std = false;
static cmdline_processor::register_flag cmd_import_std(
0,
"import-std",
"import all std:: via 'import std;' - ignored if -include-std is set",
[]{ flag_import_std = true; }
);
static auto flag_include_std = false;
static cmdline_processor::register_flag cmd_include_std(
0,
"include-std",
"#include all std:: headers",
[]{ flag_include_std = true; }
);
static auto flag_cpp2_only = false;
static cmdline_processor::register_flag cmd_cpp2_only(
0,
"pure-cpp2",
"Allow Cpp2 syntax only - also sets -import-std",
[]{ flag_cpp2_only = true; flag_import_std = true; }
);
static auto flag_safe_null_pointers = true;
static cmdline_processor::register_flag cmd_safe_null_pointers(
2,
"no-null-checks",
"Disable null safety checks",
[]{ flag_safe_null_pointers = false; }
);
static auto flag_safe_zero_division = true;
static cmdline_processor::register_flag cmd_safe_zero_division(
2,
"no-div-zero-checks",
"Disable integer division by zero checks",
[]{ flag_safe_zero_division = false; }
);
static auto flag_safe_subscripts = true;
static cmdline_processor::register_flag cmd_safe_subscripts(
2,
"no-subscript-checks",
"Disable subscript safety checks",
[]{ flag_safe_subscripts = false; }
);
static auto flag_safe_comparisons = true;
static cmdline_processor::register_flag cmd_safe_comparisons(
2,
"no-comparison-checks",
"Disable mixed-sign comparison safety checks",
[]{ flag_safe_comparisons = false; }
);
static auto flag_cpp1_filename = std::string{};
static cmdline_processor::register_flag cmd_cpp1_filename(
9,
"output filename",
"Output to 'filename' (can be 'stdout') - default is *.cpp/*.h",
nullptr,
[](std::string const& name) { flag_cpp1_filename = name; }
);
static auto flag_cwd = std::filesystem::path{};
static cpp2::cmdline_processor::register_flag cmd_cwd(
9,
"cwd path",
"Change current working directory to path",
nullptr,
[](std::string const& path) { flag_cwd = { path }; }
);
static auto flag_no_exceptions = false;
static cmdline_processor::register_flag cmd_no_exceptions(
4,
"fno-exceptions",
"Disable C++ EH - failed 'as' for 'variant' will assert",
[]{ flag_no_exceptions = true; }
);
static auto flag_no_rtti = false;
static cmdline_processor::register_flag cmd_no_rtti(
4,
"fno-rtti",
"Disable C++ RTTI - using 'as' for '*'/'std::any' will assert",
[]{ flag_no_rtti = true; }
);
struct text_with_pos{
std::string text;
source_position pos;
text_with_pos(std::string const& t, source_position p) : text{t}, pos{p} { }
};
class positional_printer
{
public:
positional_printer() = default;
private:
positional_printer(positional_printer const&) = delete;
void operator=(positional_printer const&) = delete;
// Core information
std::ofstream out_file = {}; // Cpp1 syntax output file
std::ostream* out = {}; // will point to out_file or cout
std::string cpp2_filename = {};
std::string cpp1_filename = {};
std::vector const* pcomments = {}; // Cpp2 comments data
source const* psource = {};
parser const* pparser = {};
source_position curr_pos = {}; // current (line,col) in output
lineno_t generated_pos_line = {}; // current line in generated output
int last_line_indentation = {};
int next_comment = 0; // index of the next comment not yet printed
bool last_was_empty = false;
int empty_lines_suppressed = 0;
bool just_printed_line_directive = false;
bool printed_extra = false;
char last_printed_char = {};
struct req_act_info {
colno_t requested;
colno_t offset;
req_act_info(colno_t r, colno_t o) : requested{r}, offset{o} { }
};
struct {
lineno_t line = {};
std::vector requests = {};
} prev_line_info = {};
// Position override information
std::vector preempt_pos = {}; // use this position instead of the next supplied one
int pad_for_this_line = 0; // extra padding to add/subtract for this line only
bool ignore_align = false;
int ignore_align_indent = 0;
lineno_t ignore_align_lineno = 0;
bool enable_indent_heuristic = true;
public:
// Modal information
enum phases : u8 {
phase0_type_decls = 0,
phase1_type_defs_func_decls = 1,
phase2_func_defs = 2
};
auto get_phase() const { return phase; }
private:
phases phase = phase0_type_decls;
auto inc_phase() -> void {
switch (phase) {
break;case phase0_type_decls : phase = phase1_type_defs_func_decls;
break;case phase1_type_defs_func_decls: phase = phase2_func_defs;
break;default : assert(false && "ICE: invalid lowering phase");
}
curr_pos = {};
next_comment = 0; // start over with the comments
}
std::vector emit_string_targets; // option to emit to string instead of out file
std::vector emit_text_chunks_targets; // similar for vector
enum class target_type : u8 { string, chunks };
std::vector emit_target_stack; // to interleave them sensibly
//-----------------------------------------------------------------------
// Print text
//
auto print(
std::string_view s,
source_position pos = source_position{},
bool track_curr_pos = true,
bool is_known_empty = false
)
-> void
{
// Take ownership of (and reset) just_printed_line_directive value
auto line_directive_already_done = std::exchange(just_printed_line_directive, false);
// If the caller is capturing this output, emit to the
// current target instead and skip most positioning logic
if (!emit_target_stack.empty())
{
// If capturing to a string, emit to the specified string
if (emit_target_stack.back() == target_type::string) {
assert(!emit_string_targets.empty());
*emit_string_targets.back() += s;
}
// If capturing to a vector of chunks, emit to that
else {
assert(!emit_text_chunks_targets.empty());
emit_text_chunks_targets.back()->insert( emit_text_chunks_targets.back()->begin(), text_with_pos(std::string(s), pos) );
}
return;
}
// Otherwise, we'll actually print the string to the output file
// and update our curr_pos position
if (s.length() > 0) {
last_printed_char = s.back();
}
// Reject consecutive empty lines: If this line is empty
if (
( s == "\n" || is_known_empty )
&& curr_pos.colno 0) {
if (!line_directive_already_done) {
print_line_directive(curr_pos.lineno);
}
empty_lines_suppressed = 0;
}
}
// Output the string
assert (out);
*out void
{
if (curr_pos.colno > 1) {
auto old_pos = curr_pos;
print( "\n" );
assert(curr_pos.lineno == old_pos.lineno+1);
assert(curr_pos.colno == 1);
}
}
// Print a #line directive
//
auto print_line_directive( lineno_t line )
-> void
{
// Ignore requests from generated code (negative line numbers)
if (line < 1) {
return;
}
// Otherwise, implement the request
prev_line_info = { curr_pos.lineno, { } };
ensure_at_start_of_new_line();
// Not using print() here because this is transparent to the curr_pos
if (!flag_clean_cpp1) {
assert (out);
*out get_lines()[curr_pos.lineno].all_tokens_are_densely_spaced)
{
// For the first token in a line, use the line's original indentation
if (curr_pos.colno get_lines()[curr_pos.lineno].indent() ) );
}
// For later tokens, don't try to add padding
else {
if (
last_printed_char == ';'
&& on_same_line
)
{
print( " " );
}
}
}
// Otherwise, make a best effort to adjust position with some padding
else
{
pos.colno = std::max( 1, pos.colno + pad_for_this_line );
print( pad( pos.colno - curr_pos.colno ) );
}
}
}
public:
//-----------------------------------------------------------------------
// Finalize phase
//
auto finalize_phase(bool print_remaining_comments = false)
{
if (
is_open()
&& psource
&& psource->has_cpp2()
)
{
flush_comments( {curr_pos.lineno+1, 1} );
if (print_remaining_comments) {
print_unprinted_comments();
}
// Always make sure the very last line ends with a newline
// (not really necessary but makes some tools quieter)
// -- but only if there's any Cpp2, otherwise don't
// because passing through all-Cpp1 code should always
// remain diff-identical
if (phase == phase2_func_defs) {
print_extra("\n");
}
}
}
//-----------------------------------------------------------------------
// Open
//
auto open(
std::string const& cpp2_filename_,
std::string const& cpp1_filename_,
std::vector const& comments,
cpp2::source const& source,
cpp2::parser const& parser
)
-> void
{
cpp2_filename = (flag_line_paths) ?
std::filesystem::absolute(std::filesystem::path(cpp2_filename_)).string() :
cpp2_filename_;
assert(
!is_open()
&& !pcomments
&& "ICE: tried to call .open twice"
);
cpp1_filename = cpp1_filename_;
if (cpp1_filename == "stdout") {
out = &std::cout;
}
else {
out_file.open(cpp1_filename);
out = &out_file;
}
pcomments = &comments;
psource = &source;
pparser = &parser;
}
auto reopen()
-> void
{
assert(
is_open()
&& "ICE: tried to call .reopen without first calling .open"
);
assert(cpp1_filename.ends_with(".h"));
out_file.close();
out_file.open(cpp1_filename + "pp");
}
auto is_open()
-> bool
{
if (out) {
assert(
pcomments
&& "ICE: if is_open, pcomments should also be set"
);
}
return out;
}
//-----------------------------------------------------------------------
// Abandon: close and delete
//
auto abandon()
-> void
{
if (!is_open()) {
return;
}
if (out_file.is_open()) {
out_file.close();
std::remove(cpp1_filename.c_str());
}
}
//-----------------------------------------------------------------------
// Print extra text and don't track positions
// Used for Cpp2 boundary comment and prelude and final newline
//
auto print_extra( std::string_view s )
-> void
{
assert(
is_open()
&& "ICE: printer must be open before printing"
);
print( s, source_position{}, false );
printed_extra = true;
}
//-----------------------------------------------------------------------
// Print a Cpp1 line, which should be at lineno
//
auto print_cpp1( std::string_view s, lineno_t line )
-> void
{
assert(
is_open()
&& line >= 0
&& "ICE: printer must be open before printing, and line number must not be negative (Cpp1 code is never generated)"
);
// Always start a Cpp1 line on its own new line
ensure_at_start_of_new_line();
// If we are out of sync with the current logical line number,
// emit a #line directive to re-sync
if (curr_pos.lineno != line) {
print_line_directive( line );
curr_pos.lineno = line;
}
// Print the line
assert (curr_pos.colno == 1);
print( s );
print( "\n" );
}
//-----------------------------------------------------------------------
// Used when we start a new Cpp2 section, or when we emit the same item
// more than once (notably when we emit operator= more than once)
//
auto reset_line_to(lineno_t line, bool force = false)
-> void
{
// Always start a Cpp2 section on its own new line
ensure_at_start_of_new_line();
// If we are out of sync with the current logical line number,
// emit a #line directive to re-sync
if (
force
|| curr_pos.lineno != line
)
{
print_line_directive( line );
curr_pos.lineno = line;
}
assert (curr_pos.colno == 1);
}
//-----------------------------------------------------------------------
// Print a Cpp2 item, which should be at pos
//
auto print_cpp2(
std::string_view s,
source_position pos,
bool leave_newlines_alone = false,
bool is_known_empty = false
)
-> void
{
// If we're printing for real (not to a string target)
if (emit_target_stack.empty())
{
// If we're in a generated text region (signified by negative
// line numbers), then shunt this call to print_extra instead
if (pos.lineno < 1) {
if (generated_pos_line != pos.lineno) {
*out 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;
std::set includes;
// For building
//
cpp2::source source;
cpp2::tokens tokens;
cpp2::parser parser;
cpp2::sema sema;
bool source_loaded = true;
bool violates_bounds_safety = false;
bool violates_initialization_safety = false;
bool suppress_move_from_last_use = false;
declaration_node const* having_signature_emitted = {};
declaration_node const* generating_assignment_from = {};
declaration_node const* generating_move_from = {};
declaration_node const* generating_postfix_inc_dec_from = {};
bool emitting_that_function = false;
bool emitting_move_that_function = false;
std::vector already_moved_that_members = {};
struct arg_info {
passing_style pass = passing_style::in;
token const* ptoken = {};
};
std::vector current_args = { {} };
struct active_using_declaration {
token const* identifier = {};
explicit active_using_declaration(using_statement_node const& n) {
if (auto id = get_if(&n.id->id)) {
identifier = (*id)->ids.back().id->identifier;
}
}
};
using source_order_name_lookup_res =
std::optional;
struct postfix_expression_info {
bool is_pointer = false;
postfix_expression_info() = default;
postfix_expression_info(bool b) : is_pointer{b} { }
};
std::vector active_postfix_expressions = {};
bool last_postfix_expr_was_pointer = false;
// Stack of the currently active nested declarations we're inside
std::vector current_declarations = { {} };
// Stack of the currently active names for source order name lookup:
// Like 'current_declarations' + also parameters and using declarations
std::vector current_names = { {} };
// Maintain a stack of the functions we're currently processing, which can
// be up to MaxNestedFunctions in progress (if we run out, bump the Max).
// The main reason for this is to be able to pass function_info's, especially
// their .epilog, by reference for performance while still having lifetime safety
struct function_info
{
declaration_node const* decl = {};
function_type_node const* func = {};
declaration_node::declared_value_set_funcs declared_value_set_functions = {};
function_prolog prolog = {};
std::vector epilog = {};
int ordinal = {};
function_info(
declaration_node const* decl_,
function_type_node const* func_,
declaration_node::declared_value_set_funcs declared_value_set_functions_
)
: decl{decl_}
, func{func_}
, declared_value_set_functions{std::move(declared_value_set_functions_)}
{ }
};
class current_functions_
{
stable_vector list = { {} };
public:
auto push(
declaration_node const* decl,
function_type_node const* func,
declaration_node::declared_value_set_funcs const& thats
) {
list.emplace_back(decl, func, thats);
}
auto pop() {
list.pop_back();
}
auto back() -> function_info& {
assert(!empty());
return list.back();
}
auto empty() -> bool {
return list.empty();
}
};
current_functions_ current_functions;
// For lowering
//
positional_printer printer;
bool in_definite_init = false;
bool in_parameter_list = false;
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 in_single_unqualified_id_return = { 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, includes }
, sema { errors }
{
// "Constraints enable creativity in the right directions"
// sort of applies here
//
if (
!sourcefile.ends_with(".cpp2")
&& !sourcefile.ends_with(".h2")
&& sourcefile != "stdin"
)
{
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
// Default to stdout if input is stdin
auto cpp1_filename = std::string{"stdout"};
if (sourcefile != "stdin") {
assert(sourcefile.ends_with("2"));
cpp1_filename = sourcefile.substr(0, std::ssize(sourcefile) - 1);
}
// Use explicit filename override if present, otherwise strip leading path
if (!flag_cpp1_filename.empty()) {
cpp1_filename = flag_cpp1_filename;
}
else if (cpp1_filename != "stdout") {
cpp1_filename = std::filesystem::path(cpp1_filename).filename().string();
}
printer.open(
sourcefile,
cpp1_filename,
tokens.get_comments(),
source,
parser
);
if (!printer.is_open()) {
errors.emplace_back(
source_position{},
"could not open output file " + cpp1_filename
);
return {};
}
// Generate a reasonable macroized name
auto cpp1_FILENAME = to_upper_and_underbar(cpp1_filename);
//---------------------------------------------------------------------
// Do lowered file prolog
//
// Only emit extra lines if we actually have Cpp2, because
// we want Cpp1-only files to pass through with zero changes
// (unless the user requested import/include of std)
if (
source.has_cpp2()
|| flag_import_std
|| flag_include_std
)
{
if (flag_emit_cppfront_info) {
printer.print_extra(
"\n// Generated by cppfront "
#include "version.info"
" build "
#include "build.info"
);
}
printer.print_extra( "\n" );
if (cpp1_filename.back() == 'h') {
printer.print_extra( "#ifndef " + cpp1_FILENAME+"_CPP2\n");
printer.print_extra( "#define " + cpp1_FILENAME+"_CPP2" + "\n\n" );
}
if (flag_include_std) {
printer.print_extra( "#define CPP2_INCLUDE_STD Yes\n" );
}
else if (flag_import_std) {
printer.print_extra( "#define CPP2_IMPORT_STD Yes\n" );
}
if (flag_no_exceptions) {
printer.print_extra( "#define CPP2_NO_EXCEPTIONS Yes\n" );
}
if (flag_no_rtti) {
printer.print_extra( "#define CPP2_NO_RTTI Yes\n" );
}
for (auto& h: includes) {
printer.print_extra( "#include \"" + h + "\"\n" );
}
}
auto map_iter = tokens.get_map().cbegin();
auto hpp_includes = std::string{};
//---------------------------------------------------------------------
// Do phase0_type_decls
assert(printer.get_phase() == printer.phase0_type_decls);
if (
source.has_cpp2()
&& !flag_clean_cpp1
)
{
printer.print_extra( "\n//=== Cpp2 type declarations ====================================================\n\n" );
}
if (
!tokens.get_map().empty()
|| flag_import_std
|| flag_include_std
)
{
printer.print_extra( "\n#include \"cpp2util.h\"\n\n" );
}
if (
source.has_cpp2()
&& !flag_clean_cpp1
)
{
printer.reset_line_to(1, true);
}
for (auto& section : tokens.get_map())
{
assert (!section.second.empty());
// Get the parse tree for this section and emit each forward declaration
auto decls = parser.get_parse_tree_declarations_in_range(section.second);
for (auto& decl : decls) {
assert(decl);
emit(*decl);
}
}
//---------------------------------------------------------------------
// Do phase1_type_defs_func_decls
//
printer.finalize_phase();
printer.next_phase();
if (
source.has_cpp2()
&& !flag_clean_cpp1
)
{
printer.print_extra( "\n//=== Cpp2 type definitions and function declarations ===========================\n\n" );
printer.reset_line_to(1, true);
}
assert (printer.get_phase() == positional_printer::phase1_type_defs_func_decls);
for (
lineno_t curr_lineno = 0;
auto const& line : source.get_lines()
)
{
// Skip dummy line we added to make 0-vs-1-based offsets readable
if (curr_lineno != 0)
{
// If it's a Cpp1 line, emit it
if (line.cat != source_line::category::cpp2)
{
if (
source.has_cpp2()
&& line.cat != source_line::category::preprocessor
)
{
++ret.cpp2_lines;
}
else
{
++ret.cpp1_lines;
}
if (
flag_cpp2_only
&& !line.text.empty()
&& line.cat != source_line::category::comment
&& line.cat != source_line::category::import
)
{
if (line.cat == source_line::category::preprocessor) {
if (!line.text.ends_with(".h2\"")) {
errors.emplace_back(
source_position(curr_lineno, 1),
"pure-cpp2 switch disables the preprocessor, including #include (except of .h2 files) - use import instead (note: 'import std;' is implicit in -pure-cpp2)"
);
return {};
}
}
else {
errors.emplace_back(
source_position(curr_lineno, 1),
"pure-cpp2 switch disables Cpp1 syntax"
);
return {};
}
}
if (
line.cat == source_line::category::preprocessor
&& line.text.ends_with(".h2\"")
)
{
// Strip off the 2"
auto h_include = line.text.substr(0, line.text.size()-2);
printer.print_cpp1( h_include + "\"", curr_lineno );
hpp_includes += h_include + "pp\"\n";
}
else {
printer.print_cpp1( line.text, curr_lineno );
}
}
// If it's a Cpp2 line...
else {
++ret.cpp2_lines;
// We should be in a position to emit a set of Cpp2 declarations
if (
map_iter != tokens.get_map().cend()
&& map_iter->first /*line*/ 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
{ STACKINSTR
if (pos == source_position{}) {
pos = n.position();
}
// Implicit "cpp2::" qualification of utilities we want to
// make usable without cpp2:: qualification
if (
!is_qualified
&& (
n.type() == lexeme::Cpp2FixedType
|| n == "finally"
|| n == "cpp1_ref"
|| n == "cpp1_rvalue_ref"
|| n == "unchecked_narrow"
|| n == "unchecked_cast"
|| n == "unchecked_cmp_less"
|| n == "unchecked_cmp_less_eq"
|| n == "unchecked_cmp_greater"
|| n == "unchecked_cmp_greater_eq"
|| n == "unchecked_div"
|| n == "unchecked_dereference"
|| n == "unchecked_subscript"
)
)
{
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"
|| n == "default"
)
{
printer.print_cpp2("cpp2_"+n.to_string(), pos);
}
else if (
!is_qualified
&& n == "type_of"
)
{
printer.print_cpp2("CPP2_TYPEOF", pos);
}
else {
printer.print_cpp2(n, pos, true);
}
in_definite_init = is_definite_initialization(&n);
}
//-----------------------------------------------------------------------
//
auto emit(
literal_node const& n,
source_position pos = {}
)
-> void
{ STACKINSTR
if (pos == source_position{}) {
pos = n.position();
}
for (auto p : n.pieces) {
assert(p);
emit(*p);
}
}
//-----------------------------------------------------------------------
//
auto emit(
unqualified_id_node const& n,
int synthesized_multi_return_size = 0,
bool is_local_name = true,
bool is_qualified = false,
bool is_class_member_access = false
)
-> void
{ STACKINSTR
assert( n.identifier );
auto last_use = is_definite_last_use(n.identifier);
auto decl = sema.get_declaration_of(*n.identifier, false, true);
bool add_forward =
last_use
&& last_use->is_forward
&& last_use->safe_to_move
&& !in_non_rvalue_context.back()
&& !is_class_member_access;
// We always want to std::move from named return values,
// regardless of their types, so use std::move for that
const bool add_std_move =
synthesized_multi_return_size > 1
|| (
synthesized_multi_return_size == 1
&& decl
&& !decl->initializer
);
// Otherwise we'll use cpp2::move
bool add_move =
!add_forward
&& !n.identifier->as_string_view().starts_with("guard")
&& (
!last_use
|| last_use->safe_to_move
)
&& (
add_std_move
|| (last_use && !suppress_move_from_last_use)
)
&& !in_non_rvalue_context.back()
&& !is_class_member_access;
// Add `cpp2::move(*this).` when implicitly moving a member on last use
// This way, members of lvalue reference type won't be implicitly moved
const bool add_this =
add_move
&& decl
&& decl->identifier
&& *decl->identifier == "this"
&& *n.identifier != "this";
if (
add_move
&& in_single_unqualified_id_return.size() > 0
&& in_single_unqualified_id_return.back()
&& (
!decl
|| decl->initializer
)
)
{
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_std_move) {
printer.print_cpp2("std::move(", n.position());
}
else if (add_move) {
printer.print_cpp2("cpp2::move(", n.position());
}
if (add_forward) {
printer.print_cpp2("CPP2_FORWARD(", {n.position().lineno, n.position().colno - 8});
}
if (add_this) {
printer.print_cpp2("*this).", n.position());
}
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 (
is_local_name
&& !(*n.identifier == "this")
&& !(*n.identifier == "that")
&& decl
&& (
synthesized_multi_return_size != 0
// 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 (synthesized_multi_return_size != 0) {
printer.print_cpp2(".value()", n.position());
}
if (
!add_this
&& (
add_move
|| add_std_move
|| add_forward
)
)
{
printer.print_cpp2(")", n.position());
}
}
//-----------------------------------------------------------------------
//
auto emit(
qualified_id_node const& n,
bool include_unqualified_id = true
)
-> void
{ STACKINSTR
if (!sema.check(n)) {
return;
}
auto ident = std::string{};
printer.emit_to_string(&ident);
for (auto const& id : std::span{n.ids}.first(n.ids.size() - !include_unqualified_id))
{
if (id.scope_op) {
emit(*id.scope_op);
}
emit(*id.id, 0, 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 = {},
std::string identifier = {}
)
-> void
{ STACKINSTR
if (pos == source_position{}) {
pos = n.position();
}
// Handle function types
if (n.is_function_typeid()) {
// If identifier is nonempty, we're doing a local variable with a (pointer to)
// function typeid, so stick in the pointers here for inside-out Cpp1 declarations
if (!identifier.empty()) {
for (auto q: n.pc_qualifiers) {
if (*q == "const") { identifier = " " + identifier; }
identifier = q->as_string_view() + identifier;
}
identifier = "(" + identifier + ")";
}
emit( *std::get(n.id), false, false, {}, false, identifier );
return;
}
// Handle all other types
if (n.is_wildcard()) {
printer.print_cpp2("auto", pos);
}
else {
try_emit(n.id);
try_emit(n.id, 0, 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,
bool is_class_member_access = false
)
-> void
{ STACKINSTR
try_emit(n.id);
try_emit(n.id, 0, is_local_name, false, is_class_member_access);
}
auto emit_prolog_mem_inits(
function_prolog const& prolog,
colno_t indent
)
-> void
{ STACKINSTR
for (auto& line : prolog.mem_inits) {
printer.print_extra("\n");
printer.print_extra(pad(indent-1));
printer.print_extra(line);
}
}
auto emit_prolog_statements(
function_prolog const& prolog,
colno_t indent
)
-> void
{ STACKINSTR
for (auto& line : prolog.statements) {
printer.print_extra("\n");
printer.print_extra(pad(indent-1));
printer.print_extra(line);
}
}
auto emit_epilog_statements(
std::vector const& epilog,
colno_t indent
)
-> void
{ STACKINSTR
for (auto& line : epilog) {
printer.print_extra("\n");
printer.print_extra(pad(indent-1));
printer.print_extra(line);
}
}
//-----------------------------------------------------------------------
//
auto emit(
compound_statement_node const& n,
function_prolog const& function_prolog = {},
std::vector const& function_epilog = {}
)
-> void
{ STACKINSTR
emit_prolog_mem_inits(function_prolog, n.body_indent+1);
printer.print_cpp2( "{", n.open_brace );
emit_prolog_statements(function_prolog, n.body_indent+1);
for (auto const& x : n.statements) {
assert(x);
emit(*x);
}
emit_epilog_statements( function_epilog, n.body_indent+1);
printer.print_cpp2( "}", n.close_brace );
}
//-----------------------------------------------------------------------
//
auto emit(
inspect_expression_node const& n,
bool is_expression
)
-> void
{ STACKINSTR
auto constexpr_qualifier = std::string{};
if (n.is_constexpr) {
constexpr_qualifier = "constexpr ";
}
// If this is an expression, it will have an explicit result type,
// and we need to start the lambda that we'll immediately invoke
auto result_type = std::string{};
if (is_expression) {
assert(n.result_type);
printer.emit_to_string(&result_type);
emit(*n.result_type);
printer.emit_to_string();
printer.print_cpp2("[&] () -> " + result_type + " ", n.position());
}
printer.print_cpp2("{ " + constexpr_qualifier + "auto&& _expr = ", n.position());
assert(n.expression);
emit(*n.expression);
printer.print_cpp2(";", n.position());
assert(
n.identifier
&& *n.identifier == "inspect"
);
assert(!n.alternatives.empty());
auto found_wildcard = false;
for (auto first = true; auto&& alt : n.alternatives)
{
assert(alt && alt->is_as_keyword);
if (!first) {
printer.print_cpp2("else ", alt->position());
}
first = false;
auto id = std::string{};
printer.emit_to_string(&id);
if (alt->type_id) {
emit(*alt->type_id);
}
else {
assert(alt->value);
emit(*alt->value);
}
printer.emit_to_string();
assert (
*alt->is_as_keyword == "is"
|| *alt->is_as_keyword == "as"
);
// TODO: pick up 'as' next, for now just do 'is'
if (*alt->is_as_keyword == "is")
{
// Stringize the expression-statement now...
auto statement = std::string{};
printer.emit_to_string(&statement);
emit(*alt->statement);
printer.emit_to_string();
// ... and jettison the final ; for an expression-statement
while (
!statement.empty()
&& (
statement.back() == ';'
|| isspace(statement.back())
)
)
{
statement.pop_back();
}
replace_all( statement, "cpp2::impl::as_ void
{ STACKINSTR
try_emit(n.expr);
try_emit(n.expr);
try_emit(n.expr);
try_emit(n.expr, true);
try_emit(n.expr);
if (n.expr.index() == primary_expression_node::declaration)
{
// This must be an anonymous declaration
auto& decl = std::get(n.expr);
assert(
decl
&& !decl->identifier
);
// Handle an anonymous function
if (decl->is_function()) {
auto lambda_intro = build_capture_lambda_intro_for(decl->captures, n.position());
// Handle an anonymous generic function with explicit type list
if (decl->template_parameters) {
print_to_string(&lambda_intro, *decl->template_parameters, false, true);
}
emit(*decl, lambda_intro);
}
// Else an anonymous object as 'typeid { initializer }'
else {
assert(decl->is_object());
auto& type_id = std::get(decl->type);
printer.add_pad_in_this_line( -5 );
emit(*type_id);
printer.print_cpp2("{", decl->position());
if (!decl->initializer) {
errors.emplace_back(
decl->position(),
"an anonymous object declaration must have '=' and an initializer"
);
return;
}
emit(*decl->initializer, false);
printer.print_cpp2("}", decl->position());
}
}
}
// Not yet implemented. TODO: finalize deducing pointer types from parameter lists
auto is_pointer_declaration(
parameter_declaration_list_node const*,
int,
int
)
-> bool
{
return false;
}
auto is_pointer_declaration(
declaration_node const* decl_node,
int deref_cnt,
int addr_cnt
)
-> bool
{
if (!decl_node) {
return false;
}
if (addr_cnt > deref_cnt) {
return true;
}
return std::visit([&](auto const& type){
return is_pointer_declaration(type.get(), deref_cnt, addr_cnt);
}, decl_node->type);
}
auto is_pointer_declaration(
function_type_node const* fun_node,
int deref_cnt,
int addr_cnt
)
-> bool
{
if (!fun_node) {
return false;
}
if (addr_cnt > deref_cnt) {
return true;
}
return std::visit([&](T const& type){
if constexpr (std::is_same_v) {
return false;
}
else if constexpr (std::is_same_v) {
return is_pointer_declaration(type.type.get(), deref_cnt, addr_cnt);
}
else {
return is_pointer_declaration(type.get(), deref_cnt, addr_cnt);
}
}, fun_node->returns);
}
auto is_pointer_declaration(
type_id_node const* type_id_node,
int deref_cnt,
int addr_cnt
)
-> bool
{
if (!type_id_node) {
return false;
}
if (addr_cnt > deref_cnt) {
return true;
}
if ( type_id_node->dereference_of ) {
return is_pointer_declaration(type_id_node->dereference_of, deref_cnt + type_id_node->dereference_cnt, addr_cnt);
} else if ( type_id_node->address_of ) {
return is_pointer_declaration(type_id_node->address_of, deref_cnt, addr_cnt + 1);
}
auto pointer_declarators_cnt = std::count_if(std::cbegin(type_id_node->pc_qualifiers), std::cend(type_id_node->pc_qualifiers), [](auto* q) {
return q->type() == lexeme::Multiply;
});
if (
pointer_declarators_cnt == 0
&& type_id_node->suspicious_initialization
)
{
return is_pointer_declaration(type_id_node->suspicious_initialization, deref_cnt, addr_cnt);
}
return (unchecked_narrow(pointer_declarators_cnt) + addr_cnt - deref_cnt) > 0;
}
auto is_pointer_declaration(
type_node const*,
int,
int
)
-> bool
{
return false;
}
auto is_pointer_declaration(
namespace_node const*,
int,
int
)
-> bool
{
return false;
}
auto is_pointer_declaration(
alias_node const*,
int,
int
)
-> bool
{
return false;
}
auto is_pointer_declaration(
declaration_sym const* decl,
int deref_cnt,
int addr_cnt
)
-> bool
{
if (!decl) {
return false;
}
if (addr_cnt > deref_cnt) {
return true;
}
return is_pointer_declaration(decl->declaration, deref_cnt, addr_cnt);
}
auto is_pointer_declaration(
token const* t,
int deref_cnt = 0,
int addr_cnt = 0
)
-> bool
{
if (!t) {
return false;
}
if (addr_cnt > deref_cnt) {
return true;
}
auto decl = sema.get_declaration_of(*t, true);
return is_pointer_declaration(decl, deref_cnt, addr_cnt);
}
auto source_order_name_lookup(std::string_view identifier)
-> source_order_name_lookup_res
{
for (
auto first = current_names.rbegin(), last = current_names.rend() - 1;
first != last;
++first
)
{
if (
auto decl = get_if(&*first);
decl
&& *decl
&& (*decl)->has_name(identifier)
)
{
return *decl;
}
else if (
auto using_ = get_if(&*first);
using_
&& using_->identifier
&& *using_->identifier == identifier
)
{
return *using_;
}
}
return {};
}
auto lookup_finds_type_scope_function(id_expression_node const& n)
-> bool
{
if (!n.is_unqualified())
{
return false;
}
auto const& id = *get(n.id);
auto lookup = source_order_name_lookup(*id.identifier);
if (
!lookup
|| get_if(&*lookup)
)
{
return false;
}
auto decl = get(*lookup);
if (
!decl->is_function()
|| !decl->has_name()
|| !decl->parent_is_type()
)
{
return false;
}
return true;
}
auto lookup_finds_variable_with_placeholder_type_under_initialization(id_expression_node const& n)
-> bool
{
if (!n.is_unqualified())
{
return false;
}
auto const& id = *get(n.id);
auto lookup = source_order_name_lookup(*id.identifier);
if (
!lookup
|| get_if(&*lookup)
)
{
return false;
}
auto decl = get(*lookup);
if (
decl
&& decl->has_name(*id.identifier)
)
{
if (
!decl->is_object()
&& !decl->is_object_alias()
)
{
return false;
}
if (decl->is_object()) {
auto type = &**get_if(&decl->type);
return type->is_wildcard()
&& contains(current_declarations, decl);
}
auto const& type = (**get_if(&decl->type)).type_id;
return (
!type
|| type->is_wildcard()
)
&& contains(current_declarations, decl);
}
return false;
}
//-----------------------------------------------------------------------
//
auto emit(
// Note: parameter is not const as we'll fill in the capture .str info
postfix_expression_node& n,
bool for_lambda_capture = false
)
-> void
{ STACKINSTR
if (!sema.check(n)) {
return;
}
assert(n.expr);
active_postfix_expressions.emplace_back(false);
auto guard = finally([this]{
last_postfix_expr_was_pointer = active_postfix_expressions.back().is_pointer;
active_postfix_expressions.pop_back();
});
// For a 'move that' parameter, track the members we already moved from
// so we can diagnose attempts to move from the same member twice
if (
emitting_move_that_function
&& n.expr->get_token()
&& *n.expr->get_token() == "that"
)
{
if (n.ops.empty()) {
if (!already_moved_that_members.empty()) {
errors.emplace_back(
n.position(),
"attempting to move from whole 'that' object after a 'that.member' was already moved from"
);
return;
}
// push a sentinel for "all members"
already_moved_that_members.push_back(nullptr);
}
else {
auto member = n.ops[0].id_expr->get_token();
assert(member);
for (
auto i = already_moved_that_members.begin();
i != already_moved_that_members.end();
++i
)
{
if (
!*i
|| **i == *member
)
{
errors.emplace_back(
n.position(),
"attempting to move twice from 'that." + member->to_string() + "'"
);
return;
}
}
already_moved_that_members.push_back(member);
}
}
// Ensure that forwarding postfix-expressions start with a forwarded parameter name
//
assert (!current_args.empty());
if (current_args.back().pass == passing_style::forward)
{
assert (n.expr->get_token());
assert (!current_args.back().ptoken);
current_args.back().ptoken = n.expr->get_token();
auto decl = sema.get_declaration_of(*current_args.back().ptoken);
if (!(decl && decl->parameter && decl->parameter->pass == passing_style::forward))
{
errors.emplace_back(
n.position(),
n.expr->get_token()->to_string() + " is not a forwarding parameter name"
);
}
}
// Check that this isn't pointer arithmentic
// (initial partial implementation)
if (n.expr->expr.index() == primary_expression_node::id_expression)
{
auto& id = std::get(n.expr->expr);
assert(id);
if (id->id.index() == id_expression_node::unqualified)
{
auto& unqual = std::get(id->id);
assert(unqual);
// TODO: Generalize this:
// - we don't recognize pointer types from Cpp1
// - we don't deduce pointer types from parameter_declaration_list_node
if ( is_pointer_declaration(unqual->identifier) ) {
if (n.ops.empty()) {
active_postfix_expressions.back().is_pointer = true;
}
else
{
auto op = [&]{
if (
n.ops.size() >= 2
&& n.ops[0].op->type() == lexeme::LeftParen
)
{
return n.ops[1].op;
}
else
{
return n.ops.front().op;
}
}();
if (
op->type() == lexeme::PlusPlus
|| op->type() == lexeme::MinusMinus
|| op->type() == lexeme::LeftBracket
)
{
errors.emplace_back(
op->position(),
op->to_string() + " - pointer arithmetic is illegal - use std::span or gsl::span instead"
);
violates_bounds_safety = true;
}
else if (
op->type() == lexeme::Tilde
)
{
errors.emplace_back(
op->position(),
op->to_string() + " - pointer bitwise manipulation is illegal - use std::bit_cast to convert to raw bytes first"
);
}
}
}
}
}
// Simple case: If there are no .ops, just emit the expression
if (n.ops.empty()) {
emit(*n.expr);
return;
}
// Check to see if it's a capture expression that contains $,
// and if we're not capturing the expression for the lambda
// introducer replace it with the capture name
auto captured_part = std::string{};
if (
n.cap_grp
&& !for_lambda_capture
)
{
// First stringize ourselves so that we compare equal against
// the first *cap_grp .str_suppressed_move that matches us (which is what the
// lambda introducer generator used to create a lambda capture)
suppress_move_from_last_use = true;
auto my_sym = print_to_string(n, true);
suppress_move_from_last_use = false;
auto found = std::find_if(n.cap_grp->members.cbegin(), n.cap_grp->members.cend(), [my_sym](auto& cap) {
return cap.str_suppressed_move == my_sym;
});
assert(
found != n.cap_grp->members.cend()
&& "ICE: could not find this postfix-expression in capture group"
);
// And then emit that capture symbol with number
assert (!found->cap_sym.empty());
captured_part += found->cap_sym;
}
// Otherwise, we're going to have to potentially do some work to change
// some Cpp2 postfix operators to Cpp1 prefix operators, so let's set up...
auto prefix = std::vector{};
auto suffix = std::vector{};
auto last_was_prefixed = false;
auto saw_dollar = false;
struct text_chunks_with_parens_position {
std::vector text_chunks;
source_position open_pos;
source_position close_pos;
};
auto args = std::optional{};
auto flush_args = [&] {
if (args) {
suffix.emplace_back(")", args.value().close_pos);
for (auto&& e: args.value().text_chunks) {
suffix.push_back(e);
}
suffix.emplace_back("(", args.value().open_pos);
args.reset();
}
};
auto print_to_text_chunks = [&](auto& i, auto... more) {
auto text = std::vector{};
printer.emit_to_text_chunks(&text);
push_need_expression_list_parens(false);
emit(i, more...);
pop_need_expression_list_parens();
printer.emit_to_text_chunks();
return text;
};
for (auto i = n.ops.rbegin(); i != n.ops.rend(); ++i)
{
assert(i->op);
// If we already captured a part as a _## lambda capture,
// skip the part of this expression before the $ symbol
//
if (!captured_part.empty()) {
if (i->op->type() == lexeme::Dollar) {
break;
}
}
// Else skip the part of this expression after the $ symbol
else if (for_lambda_capture) {
if (i->op->type() == lexeme::Dollar) {
saw_dollar = true;
continue;
}
if (!saw_dollar) {
continue;
}
}
// Going backwards if we found LeftParen it might be UFCS
// expr_list is emitted to 'args' for future use
if (i->op->type() == lexeme::LeftParen) {
assert(i->op);
assert(i->op_close);
auto local_args = text_chunks_with_parens_position{{}, i->op->position(), i->op_close->position()};
assert (i->expr_list);
if (!i->expr_list->expressions.empty()) {
local_args.text_chunks = print_to_text_chunks(*i->expr_list);
}
flush_args();
args.emplace(std::move(local_args));
}
// Going backwards if we found Dot and there is args variable
// it means that it should be handled by UFCS
else if(
i->op->type() == lexeme::Dot
&& args
// Disable UFCS if name lookup would hard-error (#550).
// That happens when it finds that the function identifier being called is the name
// of a variable with deduced type and we are in its initializer (e.g., x := y.x();)
// So lower it to a member call instead, the only possible valid meaning.
&& !lookup_finds_variable_with_placeholder_type_under_initialization(*i->id_expr)
)
{
// The function name is the argument to the macro
auto funcname = print_to_string(*i->id_expr);
// First, build the UFCS macro name
auto ufcs_string = std::string("CPP2_UFCS");
// If there are template arguments, use the _TEMPLATE version
if (std::ssize(i->id_expr->template_arguments()) > 0) {
// If it is qualified, use the _QUALIFIED version
if (i->id_expr->is_qualified()) {
ufcs_string += "_QUALIFIED";
// And split the unqualified id in the function name as two macro arguments
auto& id = *get(i->id_expr->id);
funcname =
"("
+ print_to_string(id, false)
+ "::),"
+ print_to_string(*cpp2::impl::assert_not_null(id.ids.back().id), 0, true, true);
}
ufcs_string += "_TEMPLATE";
}
// If we're in a namespace-scope object declaration (i.e., initializer)
// or in a default function argument, use the _NONLOCAL version
//
// Note: If there are other cases where code could execute
// in a non-local scope where a capture-default for the UFCS
// lambda would not be allowed, then add them here
if (
current_declarations.back()->is_namespace()
|| (
current_declarations.back()->is_parameter()
&& !current_declarations.back()->is_statement_parameter()
)
|| (
current_declarations.back()->is_object()
&& current_declarations.back()->parent_is_namespace()
)
|| (
(
current_declarations.back()->is_alias()
|| (
current_declarations.back()->is_function()
&& current_declarations.back() == having_signature_emitted
)
)
&& (
current_declarations.back()->parent_is_namespace()
|| current_declarations.back()->parent_is_type()
)
)
)
{
ufcs_string += "_NONLOCAL";
}
// If it's a last use, emit as part of the macro name
//
// Note: This ensures a valid member call is still prioritized
// by leveraging the last use only in the non-member branch
// For example, `x.f()` won't emit as 'CPP2_UFCS(cpp2::move(f))(x)'
// to never take the branch that wants to call `x.cpp2::move(f)()`
if (auto last_use = is_definite_last_use(i->id_expr->get_token());
last_use
&& last_use->safe_to_move
&& !lookup_finds_type_scope_function(*i->id_expr)
)
{
if (last_use->is_forward) {
ufcs_string += "_FORWARD";
}
else {
ufcs_string += "_MOVE";
}
in_non_rvalue_context.push_back(true);
funcname = print_to_string(*i->id_expr);
in_non_rvalue_context.pop_back();
}
// Second, emit the UFCS argument list
// If the computed function name is an explicit member access
// we don't need to go through the UFCS macro
// Note: This also works around compiler bugs
if (funcname.starts_with("cpp2::move(*this).")) {
prefix.emplace_back(funcname + "(", args.value().open_pos );
}
else {
prefix.emplace_back(ufcs_string + "(" + funcname + ")(", args.value().open_pos );
}
suffix.emplace_back(")", args.value().close_pos );
if (!args.value().text_chunks.empty()) {
for (auto&& e: args.value().text_chunks) {
suffix.push_back(e);
}
suffix.emplace_back(", ", i->op->position());
}
args.reset();
}
// Handle the Cpp2 postfix operators that are prefix in Cpp1
//
else if (
i->op->type() == lexeme::MinusMinus
|| i->op->type() == lexeme::PlusPlus
|| i->op->type() == lexeme::Multiply
|| i->op->type() == lexeme::Ampersand
|| i->op->type() == lexeme::Tilde
)
{
// omit some needless parens
if (
!last_was_prefixed
&& i != n.ops.rbegin()
)
{
prefix.emplace_back( "(", i->op->position() );
}
prefix.emplace_back( i->op->to_string(), i->op->position());
// Enable null dereference checks
if (
flag_safe_null_pointers
&& i->op->type() == lexeme::Multiply
)
{
prefix.emplace_back( "cpp2::impl::assert_not_null(", i->op->position() );
}
if (
flag_safe_null_pointers
&& i->op->type() == lexeme::Multiply
)
{
suffix.emplace_back( ")", i->op->position() );
}
// omit some needless parens
if (
!last_was_prefixed
&& i != n.ops.rbegin()
)
{
suffix.emplace_back( ")", i->op->position() );
}
last_was_prefixed = true;
}
// Handle the other Cpp2 postfix operators that stay postfix in Cpp1
// (currently '...' for expansion, not when used as a range operator)
else if (
is_postfix_operator(i->op->type())
&& !i->last_expr // not being used as a range operator
)
{
flush_args();
suffix.emplace_back( i->op->to_string(), i->op->position());
}
// Handle the suffix operators that remain suffix
//
else {
flush_args();
assert(i->op);
last_was_prefixed = false;
// Enable subscript bounds checks
if (
flag_safe_subscripts
&& i->op->type() == lexeme::LeftBracket
&& std::ssize(i->expr_list->expressions) == 1
)
{
suffix.emplace_back( ")", i->op->position() );
}
else if (i->op_close) {
suffix.emplace_back( i->op_close->to_string(), i->op_close->position() );
}
if (i->id_expr)
{
if (args) {
// If args are stored it means that this is function or method
// that is not handled by UFCS and args need to be printed
suffix.emplace_back(")", args.value().close_pos);
for (auto&& e: args.value().text_chunks) {
suffix.push_back(e);
}
suffix.emplace_back("(", args.value().open_pos);
args.reset();
}
auto print = print_to_string(
*i->id_expr,
false, // not a local name
i->op->type() == lexeme::Dot || i->op->type() == lexeme::DotDot // member access
);
suffix.emplace_back( print, i->id_expr->position() );
}
if (i->expr_list) {
auto text = print_to_text_chunks(*i->expr_list);
for (auto&& e: text) {
suffix.push_back(e);
}
}
if (i->last_expr)
{
prefix.emplace_back( "cpp2::range(", i->op->position() );
auto print = print_to_string( *i->last_expr );
if (i->op->type() == lexeme::EllipsisEqual) {
print += ",true";
}
else {
assert(i->op->type() == lexeme::EllipsisLess);
}
suffix.emplace_back( "," + print + ")", i->last_expr->position());
}
// Enable subscript bounds checks
if (
flag_safe_subscripts
&& i->op->type() == lexeme::LeftBracket
&& std::ssize(i->expr_list->expressions) == 1
)
{
if (auto lit = i->expr_list->expressions.front().expr->get_literal();
lit
&& lit->get_token()->type() == lexeme::DecimalLiteral
)
{
prefix.emplace_back( "CPP2_ASSERT_IN_BOUNDS_LITERAL(", i->op->position() );
}
else
{
prefix.emplace_back( "CPP2_ASSERT_IN_BOUNDS(", i->op->position() );
}
suffix.emplace_back( ", ", i->op->position() );
}
// If this is a .., emit . instead
else if( i->op->type() == lexeme::DotDot) {
suffix.emplace_back(".", i->op->position());
}
// If this is a range expression, suppress emitting the .../..= operator
// Otherwise emit the postfix operator here
else if (!i->last_expr) {
suffix.emplace_back( i->op->to_string(), i->op->position() );
}
}
}
// Print the prefixes (in forward order)
for (auto& e : prefix) {
printer.print_cpp2(e.text, n.position());
}
// If this is an --, ++, or &, don't add cpp2::move on the lhs
// even if this is a definite last use (only do that when an rvalue is okay)
if (
n.ops.front().op->type() == lexeme::MinusMinus
|| n.ops.front().op->type() == lexeme::PlusPlus
|| n.ops.front().op->type() == lexeme::Ampersand
)
{
suppress_move_from_last_use = true;
}
// Now print the core expression -- or the captured_part in its place
if (captured_part.empty()) {
emit(*n.expr);
}
else {
printer.print_cpp2(captured_part, n.position());
}
suppress_move_from_last_use = false;
flush_args();
// Print the suffixes (in reverse order)
while (!suffix.empty()) {
printer.print_cpp2(suffix.back().text, suffix.back().pos);
suffix.pop_back();
}
}
//-----------------------------------------------------------------------
//
auto emit(prefix_expression_node const& n)
-> void
{ STACKINSTR
auto suffix = std::string{};
for (auto const& x : n.ops) {
assert(x);
if (x->type() == lexeme::Not) {
printer.print_cpp2("!(", n.position());
printer.add_pad_in_this_line(-3);
suffix += ")";
}
else {
printer.print_cpp2(*x, x->position());
}
}
assert(n.expr);
push_need_expression_list_parens(true);
emit(*n.expr);
pop_need_expression_list_parens();
printer.print_cpp2(suffix, n.position());
}
//-----------------------------------------------------------------------
//
auto emit(is_as_expression_node const& n)
-> void
{ STACKINSTR
std::string prefix = {};
std::string suffix = {};
auto wildcard_found = false;
bool as_on_literal = false;
assert(
n.expr
&& n.expr->get_postfix_expression_node()
&& n.expr->get_postfix_expression_node()->expr
);
{
auto& p = n.expr->get_postfix_expression_node()->expr;
if (auto t = p->get_token();
t
&& is_literal(t->type())
&& t->type() != lexeme::StringLiteral
&& t->type() != lexeme::FloatLiteral
&& !std::get(p->expr)->has_user_defined_suffix()
&& std::ssize(n.ops) > 0
&& *n.ops[0].op == "as"
)
{
as_on_literal = true;
}
}
for (
auto i = n.ops.rbegin();
i != n.ops.rend();
++i
)
{
// If it's "ISORAS type", emit "cpp2::impl::ISORAS(expr)"
if (i->type)
{
if (i->type->is_wildcard()) {
wildcard_found = true;
if (*i->op != "is") {
errors.emplace_back(
n.position(),
"'as _' wildcard is not allowed, specify a concrete target type instead"
);
}
else if (std::ssize(n.ops) > 1) {
errors.emplace_back(
n.position(),
"an 'is _' wildcard may only be used on its own, not in a chain with other 'is'/'as' in the same subexpression"
);
}
}
else {
auto op_name = i->op->to_string();
auto type_name = print_to_string(*i->type);
if (op_name == "as") {
if (type_name == "void") {
errors.emplace_back(
n.position(),
"'expression as void' is not allowed - use '_ = expression' instead to discard the result"
);
}
op_name = "as_"; // use the static_assert-checked 'as' by default...
} // we'll override this inside inspect-expressions
prefix += "cpp2::impl::" + op_name + "(";
suffix = ")" + suffix;
}
}
// Else it's "is value", emit "cpp2::impl::is(expr, value)"
else
{
assert(i->expr);
prefix += "cpp2::impl::" + 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
{ STACKINSTR
assert(n.expr);
assert(
n.terms.empty()
|| n.terms.front().op
);
// If this is relational comparison
if (
!n.terms.empty()
&& (
n.terms.front().op->type() == lexeme::Less
|| n.terms.front().op->type() == lexeme::LessEq
|| n.terms.front().op->type() == lexeme::Greater
|| n.terms.front().op->type() == lexeme::GreaterEq
|| n.terms.front().op->type() == lexeme::EqualComparison
|| n.terms.front().op->type() == lexeme::NotEqualComparison
)
)
{
auto const& op = *n.terms.front().op;
// If this is one (non-chained) comparison, just emit it directly
if (std::ssize(n.terms) < 2)
{
assert (std::ssize(n.terms) == 1);
// emit < = > as cmp_*(a,b) calls (if selected)
if (flag_safe_comparisons) {
switch (op.type()) {
break;case lexeme::Less:
printer.print_cpp2( "cpp2::impl::cmp_less(", n.position());
break;case lexeme::LessEq:
printer.print_cpp2( "cpp2::impl::cmp_less_eq(", n.position());
break;case lexeme::Greater:
printer.print_cpp2( "cpp2::impl::cmp_greater(", n.position());
break;case lexeme::GreaterEq:
printer.print_cpp2( "cpp2::impl::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::impl::cmp_less(";
break;case lexeme::LessEq:
lambda_body += "cpp2::impl::cmp_less_eq(";
break;case lexeme::Greater:
lambda_body += "cpp2::impl::cmp_greater(";
break;case lexeme::GreaterEq:
lambda_body += "cpp2::impl::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 cpp2::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;
auto last_expr = decltype(n.expr.get()){};
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
{
last_expr = n.expr.get();
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 {
// If this is a division, wrap the denominator in a not-zero check
auto suffix = std::string{};
if (
flag_safe_zero_division
&& (
x.op->type() == lexeme::Slash
|| x.op->type() == lexeme::SlashEq
)
)
{
assert(last_expr && "ICE: shouldn't get here without having captured a pointer to the numerator expression");
if (auto lit = x.expr->get_literal();
lit
&& lit->get_token()->type() == lexeme::DecimalLiteral
)
{
printer.print_cpp2( "CPP2_ASSERT_NOT_ZERO_LITERAL(CPP2_TYPEOF(" + print_to_string(*last_expr) + "),", x.op->position() );
}
else
{
printer.print_cpp2( "CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(" + print_to_string(*last_expr) + "),", x.op->position() );
}
suffix = ")";
}
last_expr = x.expr.get();
emit(*x.expr);
printer.print_cpp2( suffix, x.expr->position() );
}
}
first = false;
}
// Finish emitting the "_ =" discard.
if (emit_discard) {
printer.print_cpp2( ")", n.position() );
}
}
//-----------------------------------------------------------------------
//
auto emit(expression_node const& n)
-> void
{ STACKINSTR
assert(n.expr);
push_need_expression_list_parens(true);
emit(*n.expr);
pop_need_expression_list_parens();
}
//-----------------------------------------------------------------------
//
auto emit(
expression_list_node const& n,
bool parens_ok = true
)
-> void
{ STACKINSTR
auto add_parens =
should_add_expression_list_parens()
&& !n.inside_initializer
&& parens_ok
;
add_parens |=
n.is_fold_expression() &&
!(n.inside_initializer && current_declarations.back()->initializer->position() != n.open_paren->position())
;
if (n.default_initializer) {
if (add_parens) {
printer.print_cpp2("{}", n.position());
}
return;
}
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
&& x.pass != passing_style::in_ref
)
{
assert(
x.pass == passing_style::out
|| x.pass == passing_style::move
|| x.pass == passing_style::forward
);
if (x.pass == passing_style::out) {
is_out = true;
printer.print_cpp2("cpp2::impl::out(&", n.position());
}
else if (x.pass == passing_style::move) {
printer.print_cpp2("std::move(", n.position());
}
}
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
{ STACKINSTR
assert(n.expr);
auto generating_return = false;
if (function_body_start != source_position{})
{
emit_prolog_mem_inits(function_prolog, n.position().colno);
printer.print_cpp2(" { ", function_body_start);
emit_prolog_statements(function_prolog, n.position().colno);
if (!function_void_ret)
{
if (!check_return_expression_lifetime(*n.expr)) {
return;
}
printer.print_cpp2("return ", n.position());
generating_return = true;
}
}
if (!emitted) {
// When generating 'return' of a single expression-list, we can
// take over direct control of emitting it without needing to
// go through the whole grammar, and surround it with braces
if (
generating_return
&& n.expr->is_expression_list()
&& !n.expr->get_expression_list()->is_fold_expression()
)
{
auto is_deduced_return =
!function_returns.empty()
&& function_returns.back().is_deduced;
if (!is_deduced_return) {
printer.print_cpp2( "{ ", n.position() );
}
emit(*n.expr->get_expression_list(), false);
if (!is_deduced_return) {
printer.print_cpp2( " }", n.position() );
}
}
// Otherwise, just emit the general expression as usual
else {
emit(*n.expr);
}
if (can_have_semicolon) {
printer.print_cpp2(";", n.position());
}
}
if (function_body_start != source_position{}) {
emit_epilog_statements( function_epilog, n.position().colno);
printer.print_cpp2(" }", n.position());
}
}
// Consider moving these `stack` functions to `common.h` to enable more general use.
template
auto stack_value(
T& var,
std::type_identity_t const& value
)
-> auto
{
return finally([&var, old = std::exchange(var, value)]() {
var = old;
});
}
template
auto stack_element(
std::vector& cont,
std::type_identity_t const& value
)
-> auto
{
cont.push_back(value);
return finally([&]{ cont.pop_back(); });
}
template
auto stack_size(std::vector& cont)
-> auto
{
return finally([&, size = cont.size()]{ cont.resize(size); });
}
template
auto stack_size_if(
std::vector& cont,
bool cond
)
-> std::optional
{
if (cond) {
return stack_size(cont);
}
return {};
}
//-----------------------------------------------------------------------
//
auto emit(
statement_node const& n,
bool can_have_semicolon = true,
source_position function_body_start = {},
bool function_void_ret = false,
function_prolog const& function_prolog = {},
std::vector const& function_epilog = {}
)
-> void
{ STACKINSTR
if (!sema.check(n)) {
return;
}
auto emit_parameters =
!n.emitted
&& n.parameters
;
auto guard = stack_size_if(current_names, emit_parameters);
if (emit_parameters) {
printer.print_extra( "\n");
printer.print_extra( "{");
for (auto& param : n.parameters->parameters) {
printer.print_extra( "\n");
printer.print_extra( print_to_string(*param, false, false, true) );
}
}
// 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);
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,
bool is_statement = false
)
-> void
{ STACKINSTR
if (!sema.check(n)) {
return;
}
// Can't declare functions as parameters -- only pointers to functions which are objects
assert( n.declaration );
assert( !n.declaration->is_function() );
if (!check_shadowing_of_type_scope_names(*n.declaration)) {
return;
}
assert( n.declaration->identifier );
auto identifier = print_to_string( *n.declaration->identifier );
auto identifier_pos = n.position();
if (n.mod == parameter_declaration_node::modifier::implicit)
{
assert(!current_functions.empty());
if (
n.pass != passing_style::out
|| !current_functions.back().decl->has_name("operator=")
)
{
errors.emplace_back(
n.position(),
"only an 'out this' parameter of an 'operator=' function may be declared implicit"
);
}
}
current_names.push_back(&*n.declaration);
// Common initialization
//
auto emit_initializer = [&]
{
assert(n.declaration);
auto is_param_to_namespace_scope_type =
n.declaration->parent_is_type()
&& n.declaration->parent_declaration->parent_is_namespace()
;
auto emit_in_phase_0 =
is_param_to_namespace_scope_type
&& printer.get_phase() == printer.phase0_type_decls
;
auto emit_in_phase_1 =
!is_param_to_namespace_scope_type
&& printer.get_phase() == printer.phase1_type_defs_func_decls
;
if (
!is_returns
&& n.declaration->initializer
&& (
n.is_in_function_scope() // implies we're in phase 1 or 2
|| emit_in_phase_0
|| emit_in_phase_1
)
)
{
auto expr = n.declaration->initializer->get_if();
assert(expr->expr);
auto empty = expr && expr->is_empty_expression_list();
auto guard = stack_element(current_declarations, &*n.declaration);
if (is_statement) {
printer.print_cpp2( "{", n.declaration->initializer->position() );
}
else {
printer.print_cpp2( " = ", n.declaration->initializer->position() );
}
if (empty && !is_statement) {
printer.print_cpp2( "{}", n.declaration->initializer->position() );
}
else {
emit(*n.declaration->initializer, false);
}
if (is_statement) {
printer.print_cpp2( "};", n.declaration->initializer->position() );
}
}
};
//-----------------------------------------------------------------------
// 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
// Common template naming
auto emit_template_name = [&]() {
if (n.declaration->is_variadic) {
printer.print_cpp2(
"...",
identifier_pos
);
}
if (identifier == "_") {
printer.print_cpp2( unnamed_type_param_name(n.ordinal, n.declaration->identifier->get_token()),
identifier_pos );
}
else {
printer.print_cpp2( identifier, identifier_pos );
}
};
if (n.declaration->is_type()) {
assert( is_template_parameter );
printer.print_cpp2("typename ", identifier_pos);
emit_template_name();
emit_initializer();
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());
emit_template_name();
emit_initializer();
return;
}
//-----------------------------------------------------------------------
// Else handle ordinary parameters
assert(n.declaration->identifier);
auto param_type = print_to_string(type_id, source_position{}, print_to_string(*n.declaration->identifier));
// 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::in_ref : printer.print_cpp2( " const&", 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 if (!n.declaration->is_object_with_function_typeid()) {
printer.print_cpp2( " ", identifier_pos );
if (n.declaration->is_variadic)
{
if (n.direction() == passing_style::out) {
errors.emplace_back(
n.declaration->position(),
"a variadic parameter cannot be 'out'"
);
return;
}
printer.print_cpp2(
"...",
identifier_pos
);
}
printer.print_cpp2( identifier, identifier_pos );
}
emit_initializer();
}
//-----------------------------------------------------------------------
//
auto emit(
parameter_declaration_list_node const& n,
bool is_returns = false,
bool is_template_parameter = false,
bool generating_postfix_inc_dec = false
)
-> void
{ STACKINSTR
in_parameter_list = true;
if (is_returns) {
printer.print_extra( "{ " );
}
else {
assert(n.open_paren);
emit(*n.open_paren);
}
// So we don't get cute about text-aligning the first parameter when it's on a new line
printer.disable_indent_heuristic_for_next_text();
auto prev_pos = n.position();
auto first = true;
for (auto const& x : n.parameters) {
if (
!first
&& !is_returns
)
{
printer.print_cpp2( ", ", prev_pos );
}
prev_pos = x->position();
assert(x);
emit(*x, is_returns, is_template_parameter);
if (!x->declaration->has_name("this")) {
first = false;
}
if (is_returns) {
printer.print_extra( "; " );
}
}
if (is_returns) {
printer.print_extra( "};\n" );
}
else {
// If we're generating Cpp1 postfix ++ or --, add the dummy int parameter
if (generating_postfix_inc_dec) {
if (!first) {
printer.print_cpp2( ",", n.position() );
}
printer.print_cpp2( "int", n.position() );
}
// Position heuristic (aka hack): Avoid emitting extra whitespace before )
// beyond column 10
assert(n.close_paren);
auto col = std::min( n.close_paren->position().colno, colno_t{10});
printer.preempt_position_push({ n.close_paren->position().lineno, col});
emit(*n.close_paren);
printer.preempt_position_pop();
}
in_parameter_list = false;
}
//-----------------------------------------------------------------------
//
auto emit(
// note: parameter is deliberately not const because we will fill
// in the capture .str information
contract_node& n
)
-> void
{ STACKINSTR
assert (n.kind);
// If this is one of Cpp2's predefined contract groups,
// make it convenient to use without cpp2:: qualification
auto name = std::string{"cpp2::cpp2_default"};
if (n.group)
{
auto group = print_to_string(*n.group);
if (group != "_") {
name = group;
}
if (
name == "cpp2_default"
|| name == "bounds_safety"
|| name == "null_safety"
|| name == "type_safety"
|| name == "testing"
)
{
name.insert(0, "cpp2::");
}
}
// "Unevaluated" is for static analysis only, and are never evaluated, so just skip them
// (The only requirement for an Unevaluated condition is that it parses; and even that's
// easy to relax if we ever want to allow arbitrary tokens in an Unevaluated condition)
if (n.group && n.group->to_string() == "unevaluated") {
return;
}
// For a postcondition, we'll wrap it in a lambda and register it
//
if (*n.kind == "post") {
auto lambda_intro = build_capture_lambda_intro_for(n.captures, n.position(), true);
printer.print_cpp2(
"cpp2_finally_presuccess.add(" +
lambda_intro + "{",
n.position()
);
}
// Emit the contract group name, and report any violation to that group
//
assert(n.condition);
auto message = std::string{"\"\""};
if (n.message) {
message = "CPP2_CONTRACT_MSG(" + print_to_string(*n.message) + ")";
}
auto separator = std::string{""};
printer.print_cpp2(
"if (",
n.position()
);
for (auto const& flag : n.flags) {
printer.print_cpp2(
separator + print_to_string(*flag),
n.position()
);
separator = " && ";
}
printer.print_cpp2(
separator + name + ".is_active()",
n.position()
);
printer.print_cpp2(
" && !(" + print_to_string(*n.condition) + ") ) " +
"{ " + name + ".report_violation(" + message + "); }",
n.position()
);
// For a postcondition, close out the lambda
//
if (*n.kind == "post") {
printer.print_cpp2( "} );", n.position()
);
}
}
//-----------------------------------------------------------------------
//
auto emit(
function_type_node const& n,
bool is_main = false,
bool is_ctor_or_dtor = false,
std::string const& suffix1 = {},
bool generating_postfix_inc_dec = false,
std::string const& identifier = {}
)
-> void
{ STACKINSTR
if (!sema.check(n)) {
return;
}
// Common parts we may use in different orders
//
auto handle_default_return_type = [&]{
if (is_main)
{
printer.print_cpp2( " -> int", n.position() );
}
else if(!is_ctor_or_dtor)
{
printer.print_cpp2( " -> void", n.position() );
}
};
auto handle_single_anon_return_type = [&]{
auto is_type_scope_function_with_in_this =
n.my_decl
&& n.my_decl->parent_is_type()
&& n.parameters->ssize() > 0
&& (*n.parameters)[0]->direction() == passing_style::in
;
auto& r = std::get(n.returns);
assert(r.type);
auto return_type = print_to_string(*r.type);
if (
r.pass == passing_style::forward
|| r.pass == passing_style::forward_ref
)
{
if (r.type->is_wildcard()) {
if (r.pass == passing_style::forward) {
printer.print_cpp2( "decltype(auto)", n.position() );
}
else {
printer.print_cpp2( "auto&&", n.position() );
}
}
else {
printer.print_cpp2( return_type, n.position() );
if (is_type_scope_function_with_in_this) {
printer.print_cpp2( " const&", n.position() );
}
else if (!generating_postfix_inc_dec) {
printer.print_cpp2( "&", n.position() );
}
}
}
else {
printer.print_cpp2( return_type, n.position() );
}
};
// If this is not part of a declaration, just a function type-id,
// the return type comes first
if (!n.my_decl) {
if (n.returns.index() == function_type_node::empty) {
handle_default_return_type();
}
else if (n.returns.index() == function_type_node::id) {
handle_single_anon_return_type();
}
printer.print_cpp2( identifier, n.position() );
}
if (
is_main
&& n.parameters->parameters.size() > 0
)
{
printer.print_cpp2(
"(int const argc_, char** argv_)",
n.parameters->position()
);
current_functions.back().prolog.statements.push_back(
"auto const args = cpp2::make_args(argc_, argv_); "
);
}
else {
emit(*n.parameters, false, false, generating_postfix_inc_dec);
}
// For an anonymous function, the emitted lambda is 'constexpr' or 'mutable'
if (
n.my_decl
&& !n.my_decl->has_name()
)
{
if (n.my_decl->is_constexpr) {
// The current design path we're trying out is for all '==' functions to be
// emitted as Cpp1 'constexpr', including anonymous functions. For anonymous
// functions that have captures, the intent is that '==' implies "the result
// always the same (depends only on the arguments)." Specifically, the result
// doesn't depend on the captured state, so the captured state should be const.
// But until we want to take a dependency on post-C++20 constexpr relaxation
// to make more code work with 'constexpr' even when not invoked in constexpr
// contexts, we will emit it as const/whitespace instead for now.
//
// printer.print_cpp2( " constexpr", n.position() );
// // consider enabling when P2242, P2280, and similar papers are widely implemented
}
else if (!n.my_decl->captures.members.empty()) {
printer.print_cpp2( " mutable", n.position() );
}
}
// For now, adding implicit noexcept only for move/swap/dtor functions
if (
n.is_move()
|| n.is_swap()
|| n.is_destructor()
|| (
n.my_decl
&& 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()
|| (
n.my_decl
&& 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()
);
}
// If this is a declaration, emit the trailing return type
else if (n.my_decl)
{
// Otherwise, handle a default return type
if (n.returns.index() == function_type_node::empty) {
handle_default_return_type();
}
// Otherwise, handle a single anonymous return type
else if (n.returns.index() == function_type_node::id) {
printer.print_cpp2( " -> ", n.position() );
handle_single_anon_return_type();
}
// Otherwise, handle multiple/named returns
else {
printer.print_cpp2( " -> ", n.position() );
assert (n.my_decl);
printer.print_cpp2( multi_return_type_name(*n.my_decl), n.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 const& prefix
)
-> void
{ STACKINSTR
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 it it isn't '_' (don't care) and ...
if (initializer == "_") {
// And on to the next data member...
++object;
continue;
}
if (initializer.empty()) {
initializer = "{}";
}
// (a) ... if this is assignment, emit it in all cases
if (is_assignment)
{
assert ((*object)->name());
// Flush any 'out' parameter initializations
for (auto& init : out_inits) {
current_functions.back().prolog.statements.push_back(init + ";");
}
out_inits = {};
// Then add this statement
// Use ::operator= for base classes
if ((*object)->has_name("this")) {
current_functions.back().prolog.statements.push_back(
print_to_string( *(*object)->get_object_type() ) +
"::operator= ( " +
initializer +
" );"
);
}
// Else just use infix assignment
else {
current_functions.back().prolog.statements.push_back(
object_name +
" = " +
initializer +
";"
);
}
}
// (b) ... if this isn't assignment, only need to emit it if it was
// explicit, or is a base type or 'that' initializer
else if (
found_explicit_init
|| is_object_before_base
|| (
(*object)->has_name("this")
&& !initializer.empty()
)
|| emitting_that_function
)
{
if (is_object_before_base) {
assert (is_object_before_base->name());
object_name =
print_to_string( *is_object_before_base->parent_declaration->name() )
+ "_"
+ (*object)->name()->to_string()
+ "_as_base";
}
// Flush any 'out' parameter initializations
auto out_inits_with_commas = [&]() -> std::string {
auto ret = std::string{};
for (auto& init : out_inits) {
ret += init + ", ";
}
out_inits = {};
return ret;
}();
// If there were any, wedge them into this initializer
// using (holds nose) the comma operator and extra parens
// as we add this statement
if (!out_inits_with_commas.empty()) {
current_functions.back().prolog.mem_inits.push_back(
separator +
object_name +
"{(" +
out_inits_with_commas +
initializer +
" )}"
);
}
else {
if (initializer == "{}") {
initializer = "";
}
current_functions.back().prolog.mem_inits.push_back(
separator +
object_name +
"{ " +
initializer +
" }"
);
}
separator = ", ";
}
// And on to the next data member...
++object;
}
// Now no data members should be left over
if (object != objects.end())
{
errors.emplace_back(
(*object)->position(),
canonize_object_name(*object) + " was not initialized - did you forget to write a default initializer, or assign to it in the operator= body?"
);
errors.emplace_back(
(*object)->position(),
"see declaration for '" + canonize_object_name(*object) + "' here"
);
errors.emplace_back(
(*object)->position(),
error_msg
);
return;
}
// Flush any possible remaining 'out' parameters
for (auto& init : out_inits) {
current_functions.back().prolog.statements.push_back(init + ";");
}
}
// For a constructor, print the type name instead of the operator= function name
assert(n.parent_is_type());
if (!is_assignment)
{
printer.print_cpp2( prefix, n.position() );
printer.print_cpp2( type_qualification_if_any_for(n), n.position() );
printer.print_cpp2( print_to_string( *n.parent_declaration->name() ), n.position() );
emit( *func, 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 );
}
}
//-----------------------------------------------------------------------
//
auto emit(
declaration_node const& n,
std::string const& capture_intro = {}
)
-> void
{ STACKINSTR
// Helper for declarations with parent *template-head*s.
auto const emit_parent_template_parameters = [&]() {
auto parent_template_parameters = std::string{};
auto parent = n.parent_declaration;
while (
parent
&& parent->is_type()
)
{
if (parent->requires_clause_expression) {
parent_template_parameters =
"requires( " + print_to_string(*parent->requires_clause_expression) + " )\n"
+ parent_template_parameters;
}
if (parent->template_parameters) {
parent_template_parameters =
"template " + print_to_string( *parent->template_parameters, false, true )
+ " " + parent_template_parameters;
}
parent = parent->parent_declaration;
}
printer.print_cpp2(parent_template_parameters, n.position());
};
// Helper for declarations that can have requires-clauses
auto const emit_requires_clause = [&]() {
if (
n.requires_clause_expression
|| !function_requires_conditions.empty()
)
{
printer.print_extra("\n");
printer.ignore_alignment( true, n.position().colno + 4 );
if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
// Workaround GCC 10 not supporting requires in forward declarations in some cases.
// See commit 5a0d77f8e297902c0b9712c5aafb6208cfa4c139.
if (n.is_object() || n.parent_is_type()) {
printer.print_extra("CPP2_REQUIRES_ (");
}
else {
printer.print_extra("CPP2_REQUIRES (");
}
}
else {
printer.print_extra("requires (");
}
if (n.requires_clause_expression) {
emit(*n.requires_clause_expression);
if (!function_requires_conditions.empty()) {
printer.print_extra(" && ");
}
}
if (!function_requires_conditions.empty()) {
printer.print_extra(function_requires_conditions.front());
for (auto it = std::cbegin(function_requires_conditions)+1; it != std::cend(function_requires_conditions); ++it) {
printer.print_extra(" && " + *it);
}
}
printer.print_extra(") ");
function_requires_conditions = {};
printer.ignore_alignment( false );
}
};
// Declarations are handled in multiple passes,
// but we only want to emit the error messages once (in phase 2)
if (!sema.check(n, printer.get_phase() == printer.phase2_func_defs))
{
return;
}
// In phase 0, only need to consider namespaces and types
if (
printer.get_phase() == printer.phase0_type_decls
&& !n.is_namespace()
&& !n.is_type()
)
{
return;
}
// If this is a generated declaration (negative source line number),
// add a line break before
if (
printer.get_phase() == printer.phase2_func_defs
&& n.position().lineno < 1
)
{
printer.print_extra("\n");
}
auto guard0 = stack_value(having_signature_emitted, &n);
auto guard1 = stack_element(current_declarations, &n);
current_names.push_back(&n);
auto guard2 = stack_size_if(current_names, !n.is_namespace());
// 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 (
a->is_object_alias()
&& n.parent_is_type()
&& printer.get_phase() == printer.phase2_func_defs
)
{
emit_parent_template_parameters();
}
if (n.template_parameters) {
printer.print_cpp2("template", n.position());
emit(*n.template_parameters, false, true);
printer.print_cpp2(" ", n.position());
}
// Emit requires clause if any
emit_requires_clause();
// Handle type aliases
if (a->is_type_alias()) {
printer.print_cpp2(
"using "
+ print_to_string(*n.identifier)
+ " = "
+ print_to_string( *std::get(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);
}
auto print_initializer_to_string = [&](expression_node const& n) -> std::string {
// If this expression is just a single expression-list, we can
// take over direct control of emitting it without needing to
// go through the whole grammar, and surround it with braces
if (n.is_expression_list()) {
return "{ "
+ print_to_string(*n.get_expression_list(), false)
+ " }";
}
// Otherwise, just emit the general expression as usual
else {
return "{ "
+ print_to_string(n)
+ " }";
}
};
// (*) If this is at type scope, Cpp1 requires an out-of-line declaration dance
// for some cases to work - see https://stackoverflow.com/questions/11928089/
if (n.parent_is_type())
{
assert (n.parent_declaration->name());
if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
printer.print_cpp2(
"static const "
+ type + " "
+ print_to_string(*n.identifier)
+ ";\n",
n.position()
);
}
else if (printer.get_phase() == printer.phase2_func_defs) {
// The following logic is not yet complete, so give a diagnostic for now
if (n.parent_declaration->parent_is_type()) {
errors.emplace_back(
n.position(),
"(temporary alpha limitation) an object alias cannot yet appear inside a nested type"
);
return;
}
printer.print_cpp2(
"inline CPP2_CONSTEXPR "
+ type
+ " "
+ type_qualification_if_any_for(n)
+ print_to_string(*n.identifier)
+ print_initializer_to_string( *std::get(a->initializer) )
+ ";\n",
n.position()
);
}
}
// Otherwise, at function and namespace scope we can just define
else
{
auto intro = std::string{};
if (n.parent_is_function()) {
intro = "constexpr";
}
else if (n.parent_is_namespace()) {
intro = "inline constexpr";
}
printer.print_cpp2(
type + " "
+ intro + " "
+ print_to_string(*n.identifier)
+ print_initializer_to_string( *std::get(a->initializer) )
+ ";\n",
n.position()
);
}
}
else {
assert(false && "ICE: should be unreachable - invalid alias");
}
return;
}
}
// Handle other declarations
auto need_to_generate_assignment = false;
auto need_to_generate_move = false;
auto need_to_generate_postfix_inc_dec = false;
if (
n.is_function()
&& n.has_name()
)
{ // reset the 'that' flags
emitting_that_function = false;
emitting_move_that_function = false;
already_moved_that_members = {};
}
auto is_main =
!n.parent_declaration
&& n.has_name("main")
;
auto is_in_type = n.parent_is_type();
if (!check_shadowing_of_type_scope_names(n)) {
return;
}
// If this is a function that has multiple return values,
// first we need to emit the struct that contains the returns
if (
printer.get_phase() == printer.phase1_type_defs_func_decls
&& n.is_function()
)
{
auto& func = std::get(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);
auto func_name = n.name()->to_string();
// If it's a single named value, emit it as an anonymous return value
if (std::ssize(r->parameters) == 1)
{
printer.print_extra(
"\nusing "
+ func_name + "_ret = "
+ print_to_string( *r->parameters[0]->declaration->get_object_type() )
+ ";"
);
}
// Else just emit it as an ordinary struct
else
{
printer.print_extra( "\nstruct " + multi_return_type_name(n) + " ");
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())
{
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());
}
}
}
// Print a line directive before every function definition, excluding lambdas.
// This is needed to enable debugging with lldb.
if (
printer.get_phase() == printer.phase2_func_defs
&& n.is_function()
&& n.has_name()
&& n.initializer
)
{
printer.print_extra("");
}
// If this is a function definition and the function is inside
// type(s) that have template parameters and/or requires clauses,
// emit those outer template parameters and requires clauses too
if (
printer.get_phase() == printer.phase2_func_defs
&& n.is_function()
&& n.initializer // only if the function has a definition (is not abstract)
)
{
emit_parent_template_parameters();
}
// Now, emit our own template parameters
if (
n.template_parameters
&& (
printer.get_phase() < printer.phase2_func_defs
|| n.is_object()
|| (
n.is_function()
&& n.has_name() // only if it is not unnamed function aka lambda
&& n.initializer // only if the function has a definition (is not abstract)
&& printer.get_phase() == printer.phase2_func_defs
)
)
&& (
!n.is_concept()
|| printer.get_phase() == printer.phase1_type_defs_func_decls
)
)
{
printer.print_cpp2("template", n.position());
emit(*n.template_parameters, false, true);
printer.print_cpp2(" ", n.position());
}
// User-defined type
if (n.is_type())
{
assert(
n.initializer
&& n.initializer->is_compound()
);
auto& compound_stmt = std::get(n.initializer->statement);
if (printer.get_phase() != printer.phase2_func_defs)
{
if (n.requires_clause_expression) {
printer.print_cpp2("requires( ", n.requires_pos);
emit(*n.requires_clause_expression);
printer.print_cpp2(" )\n", n.requires_pos);
}
printer.print_cpp2("class ", n.position());
emit(*n.identifier);
// Type declaration
if (printer.get_phase() == printer.phase0_type_decls) {
printer.print_cpp2( ";\n", n.position() );
return;
}
}
if (
n.is_type_final()
&& printer.get_phase() == printer.phase1_type_defs_func_decls
)
{
printer.print_cpp2( " final", n.position() );
}
// Type definition
auto separator = std::string{":"};
auto started_body = false;
auto saved_for_body = std::vector{};
auto found_constructor = false;
auto found_that_constructor = false;
assert(compound_stmt);
auto start_body = [&]{
if (!started_body) {
printer.print_cpp2(" {", compound_stmt->position());
started_body = true;
for (auto& [line, pos] : saved_for_body) {
printer.print_cpp2(line + "\n", pos);
}
}
};
for (auto& stmt : compound_stmt->statements)
{
assert(stmt);
if (
!stmt->is_declaration()
&& !stmt->is_using()
)
{
// We will already have emitted an error for this in sema.check
return;
}
// If it's a using statement, save it up if we haven't started the body yet
if (stmt->is_using()) {
auto& use = std::get(stmt->statement);
assert(use);
if (started_body) {
emit(*use);
}
else {
saved_for_body.emplace_back( print_to_string(*use), use->position() );
}
continue;
}
// Else it's a declaration...
auto& decl = std::get(stmt->statement);
assert(decl);
if (
decl->is_alias()
&& printer.get_phase() == printer.phase1_type_defs_func_decls
)
{
if (started_body) {
emit(*decl);
}
else {
saved_for_body.emplace_back( print_to_string(*decl), decl->position() );
}
continue;
}
if (decl->is_constructor()) {
found_constructor = true;
}
if (decl->is_constructor_with_that()) {
found_that_constructor = true;
}
// First we'll encounter the base types == subobjects named "this"
// and any data members declared before them that we push into private bases
assert(decl->name());
auto emit_as_base =
decl->get_decl_if_type_scope_object_name_before_a_base_type(*decl->name())
|| decl->has_name("this")
;
if (emit_as_base)
{
// Do the sema check for these declarations here, because we're
// handling them here instead of going through emit() for them
if (!sema.check(*decl)) {
return;
}
if (decl->has_name("this")) {
if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
printer.print_cpp2(
separator + " public " + print_to_string(*decl->get_object_type()),
compound_stmt->position()
);
separator = ",";
}
}
else
{
if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
printer.print_cpp2(
separator
+ " public "
+ print_to_string(*decl->parent_declaration->name())
+ "_"
+ decl->name()->to_string()
+ "_as_base",
compound_stmt->position()
);
separator = ",";
}
}
current_names.push_back(decl.get());
}
// Then we'll switch to start the body == other members
else
{
if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
start_body();
}
emit(*decl);
}
}
if (printer.get_phase() == printer.phase1_type_defs_func_decls)
{
// Ensure we emit the { even if there are only bases in the type
start_body();
auto id = print_to_string(*n.identifier);
auto indent = static_cast(
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()) {
auto& decl = std::get(stmt->statement);
assert(decl);
emit(*decl);
}
else if (stmt->is_using()) {
auto& use = std::get(stmt->statement);
assert(use);
emit(*use);
}
else {
errors.emplace_back(
stmt->position(),
"a namespace scope must contain only declarations or 'using' statements, not other code"
);
return;
}
}
printer.print_cpp2("}\n", compound_stmt->close_brace);
}
// Function
else if (
n.is_function()
&& (
printer.get_phase() < printer.phase2_func_defs
|| n.initializer // only emit definition if the function has one (is not abstract)
|| n.is_defaultable_function()
)
)
{
auto is_streaming_operator = [](std::string_view sv) {
return
sv == "operator"
;
};
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,
func.get(),
n.find_parent_declared_value_set_functions()
);
auto guard0 = finally([&]{ current_functions.pop(); });
auto guard1 = stack_size(current_names);
// If this is at expression scope, we can't emit "[[nodiscard]] auto name"
// so print the provided intro instead, which will be a Cpp1 lambda-introducer
if (capture_intro != "")
{
assert (!n.identifier && !is_main);
printer.print_cpp2(capture_intro, n.position());
emit( *func );
}
// 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::in_ref:
case passing_style::forward:
case passing_style::forward_ref:
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
&& func->parameter_count() < 2
)
{
prefix += "explicit ";
}
}
}
}
// Else if there isn't a 'this' parameter, but this function is in a type scope,
// it's a Cpp1 non-member function so we need to say so (on the declaration only)
else if (
is_in_type
&& printer.get_phase() != printer.phase2_func_defs
) {
if (emit_as_friend) {
prefix += "friend ";
}
else {
prefix += "static ";
}
}
// If there's a return type, it's [[nodiscard]] implicitly and all the time
// -- for now there's no opt-out, wait and see whether we actually need one
if (
func->has_non_void_return_type()
&& !func->is_assignment()
&& !func->is_compound_assignment()
&& !func->is_increment_or_decrement()
&& (
printer.get_phase() == printer.phase1_type_defs_func_decls
|| n.has_initializer() // so we're printing it in phase 2
)
&& (
!emit_as_friend // can't have an attribute on a friend declaration-not-definition
|| printer.get_phase() != printer.phase1_type_defs_func_decls
)
&& !(
n.name()
&& is_streaming_operator(n.name()->as_string_view())
)
)
{
printer.print_cpp2( "[[nodiscard]] ", n.position() );
}
// Now we have all the pieces we need for the Cpp1 function declaration
// For a special member function, we need to do more work to translate
// in-body initialization statements to the Cpp1 mem-init-list syntax
if (
n.is_constructor()
|| n.is_assignment()
)
{
assert(
!is_main
&& suffix2.empty()
&& "ICE: an operator= shouldn't have been able to generate a suffix (or be main)"
);
emit_special_member_function(
n,
prefix
);
// If there's no inheritance and this operator= has two parameters,
// it's setting from a single value -- either from the same type
// (aka copy/move) or another type (a conversion) -- so recurse to
// emit related functions if the user didn't write them by hand
if (
!n.parent_is_polymorphic()
&& func->parameters->ssize() == 2
&& generating_assignment_from != &n
)
{
assert(!current_functions.empty());
// A) Generate (A)ssignment from a constructor,
// if the user didn't write the assignment function themselves
if (
// A1) This is '(out this, that)'
// and no '(inout this, that)' was written by the user
(
&n == current_functions.back().declared_value_set_functions.out_this_in_that
&& !current_functions.back().declared_value_set_functions.inout_this_in_that
)
||
// A2) This is '(out this, move that)'
// and no '(inout this, move that)' was written by the user
// (*) and no '(inout this, that)' was written by the user (*)
//
// (*) This third test is to tie-break M2 and A2 in favor of M2. Both M2 and A2
// can generate a missing '(inout this, move that)', and if we have both
// options then we should prefer to use M2 (generate move assignment from
// copy assignment) rather than A2 (generate move assignment from move
// construction) as M2 is a better fit (move assignment is more like copy
// assignment than like move construction, because assignments are designed
// structurally to set the value of an existing 'this' object)
(
&n == current_functions.back().declared_value_set_functions.out_this_move_that
&& !current_functions.back().declared_value_set_functions.inout_this_move_that
&& !current_functions.back().declared_value_set_functions.inout_this_in_that
)
||
// A3) This is '(out this, something-other-than-that)'
(
n.is_constructor()
&& !n.is_constructor_with_that()
&& !contains( current_functions.back().declared_value_set_functions.assignments_from, n.nth_parameter_type_name(2) )
)
)
{
need_to_generate_assignment = true;
}
if (generating_move_from != &n) {
// M) Generate (M)ove from copy,
// if the user didn't write the move function themselves
if (
// M1) This is '(out this, that)'
// and no '(out this, move that)' was written by the user
(
&n == current_functions.back().declared_value_set_functions.out_this_in_that
&& !current_functions.back().declared_value_set_functions.out_this_move_that
)
||
// M2) This is '(inout this, that)'
// and no '(inout this, move that)' was written by the user
(
&n == current_functions.back().declared_value_set_functions.inout_this_in_that
&& !current_functions.back().declared_value_set_functions.inout_this_move_that
)
)
{
need_to_generate_move = true;
}
}
}
}
// For a destructor, we need to translate
else if (n.is_destructor())
{
assert(
!is_main
// prefix can be "virtual"
// suffix1 will be " &&" though we'll ignore that
// suffix2 can be "= 0"
);
// Print the ~-prefixed type name instead of the operator= function name
assert(
n.parent_is_type()
&& n.parent_declaration->name()
);
printer.print_cpp2(
prefix
+ type_qualification_if_any_for(n)
+ "~" + print_to_string(*n.parent_declaration->name()),
n.position()
);
emit( *func, 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, is_main, false, suffix1, generating_postfix_inc_dec_from != nullptr );
printer.print_cpp2( suffix2, n.position() );
// If this is ++ or --, also generate a Cpp1 postfix version of the operator
// (as long as we don't know for sure this isn't a copyable type)
if (func->is_increment_or_decrement())
{
if (generating_postfix_inc_dec_from) {
assert (generating_postfix_inc_dec_from == &n);
}
else if (
!n.parent_declaration
|| !n.parent_declaration->is_type()
|| !n.parent_declaration->cannot_be_a_copy_constructible_type()
)
{
need_to_generate_postfix_inc_dec = true;
}
}
}
}
// If we're only emitting declarations, end the function declaration
if (
printer.get_phase() == printer.phase1_type_defs_func_decls
&& !n.is_function_expression()
)
{
emit_requires_clause();
if (n.position().lineno < 0) {
printer.print_cpp2( ";\n", n.position() );
}
else {
printer.print_cpp2( ";", n.position() );
}
// Note: Not just early "return;" here because we may need
// to recurse to emit generated operator declarations too,
// so all the definition work goes into a big 'else' branch
}
// Else emit the definition
else if (n.initializer)
{
if (func->returns.index() == function_type_node::list) {
auto& r = std::get(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
}
if (func->has_postconditions()) {
current_functions.back().prolog.statements.push_back("cpp2::finally_presuccess cpp2_finally_presuccess;");
}
if (func->returns.index() == function_type_node::list)
{
auto& r = std::get(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::impl::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);
}
}
for (auto&& c : func->contracts)
{
auto print = std::string();
printer.emit_to_string(&print);
auto guard = stack_value(having_signature_emitted, nullptr);
emit(*c);
printer.emit_to_string();
current_functions.back().prolog.statements.push_back(print);
}
printer.preempt_position_push( n.equal_sign );
emit_requires_clause();
having_signature_emitted = nullptr;
// If this is ++ or --, also generate a Cpp1 postfix version of the operator
if (generating_postfix_inc_dec_from)
{
assert (generating_postfix_inc_dec_from == &n);
auto param1 = std::string{"*this"};
if (
!n.parent_declaration
|| !n.parent_declaration->is_type()
)
{
param1 = n.first_parameter_name();
}
printer.print_cpp2(
" { auto ret = " + param1 + "; ++" + param1 + "; return ret; }",
n.position()
);
}
// Else just emit the normal function body
else {
emit(
*n.initializer,
true, func->position(), func->returns.index() == function_type_node::empty,
current_functions.back().prolog,
current_functions.back().epilog
);
}
printer.preempt_position_pop();
function_returns.pop_back();
}
// Finally, do the potential recursions...
// If this was a constructor and we want also want to emit
// it as an assignment operator, do it via a recursive call
if (need_to_generate_assignment)
{
// Reset the 'emitted' flags
for (auto& statement : n.get_initializer_statements()) {
statement->emitted = false;
}
// Then reposition and do the recursive call
printer.reset_line_to(n.position().lineno);
generating_assignment_from = &n;
emit( n, capture_intro );
generating_assignment_from = {};
}
// If this was a constructor and we want also want to emit
// it as an assignment operator, do it via a recursive call
if (need_to_generate_move)
{
// Reset the 'emitted' flags
for (auto& statement : n.get_initializer_statements()) {
statement->emitted = false;
}
// Then reposition and do the recursive call
printer.reset_line_to(n.position().lineno);
generating_move_from = &n;
emit( n, capture_intro );
generating_move_from = {};
}
// If this is ++ or --, emit the Cpp1 postfix version via a recursive call
if (need_to_generate_postfix_inc_dec)
{
// Reset the 'emitted' flags
for (auto& statement : n.get_initializer_statements()) {
statement->emitted = false;
}
// Then reposition and do the recursive call
printer.reset_line_to(n.position().lineno);
generating_postfix_inc_dec_from = &n;
emit( n, capture_intro );
generating_postfix_inc_dec_from = {};
}
}
// Object with optional initializer
else if (
n.is_object()
&& (
(
n.parent_is_namespace()
&& printer.get_phase() >= printer.phase1_type_defs_func_decls
)
||
(
n.parent_is_type()
&& printer.get_phase() == printer.phase1_type_defs_func_decls
)
||
(
n.parent_is_function()
&& printer.get_phase() == printer.phase2_func_defs
)
||
(
n.is_inside_global_unnamed_function()
&& printer.get_phase() == printer.phase1_type_defs_func_decls
)
)
)
{
auto& type = std::get(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() );
}
assert(n.identifier);
// Emit "auto" for deduced types (of course)
if (type->is_wildcard()) {
assert(n.initializer);
// And the constraint if there is one
if (type->constraint) {
emit( *type->constraint, n.position() );
printer.print_cpp2(" ", n.position());
}
emit( *type, n.position() );
}
// Otherwise, emit the type
else {
// If there isn't an initializer, use deferred_init
if (!n.initializer) {
if (n.parent_is_function()) {
printer.print_cpp2( "cpp2::impl::deferred_init", n.position() );
}
}
printer.print_cpp2( " ", n.position());
// 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 time (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;
}
// Minimize diff noise by using ordinals for generated names, not line/col info
auto next_unique_id = [&]() -> std::string
{
if (current_functions.empty()) {
// Generate a globally unique label for non-function-locals,
// so the declarations and definitions match
return labelized_position( n.identifier->get_token() );
}
// Else just use a per-function ordinal
return std::to_string( ++current_functions.back().ordinal );
};
printer.print_cpp2(
"auto_" + next_unique_id(),
n.identifier->position()
);
}
else if (!n.is_object_with_function_typeid()) {
emit(*n.identifier);
}
if (
n.parent_is_namespace()
&& printer.get_phase() != printer.phase2_func_defs
&& !type->is_concept()
)
{
printer.print_cpp2( ";", n.position());
return;
}
// If there's an initializer, emit it
if (n.initializer)
{
printer.add_pad_in_this_line(-100);
if (
type->is_concept()
|| type->is_function_typeid()
)
{
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()
&& !type->is_function_typeid()
)
{
printer.print_cpp2( "}", n.position() );
}
}
printer.print_cpp2( "; ", n.position() );
}
}
//-----------------------------------------------------------------------
// print_errors
//
auto print_errors()
-> void
{
if (!errors.empty()) {
// Delete the output file
printer.abandon();
}
error_entry const* prev = {};
bool print_fallback_errors = true; // true until we find a non-fallback message
for (auto&& error : errors)
{
// Only print fallback error messages if we
// haven't found a better (non-fallback) one yet
if (!error.fallback) {
print_fallback_errors = false;
}
if (error.fallback && !print_fallback_errors) {
continue;
}
// Suppress adjacent duplicates (e.g., can arise when we
// reenter operator= to emit it as an assignment operator)
if (
!prev
|| error != *prev
)
{
error.print(std::cerr, strip_path(sourcefile));
}
prev = &error;
}
if (violates_lifetime_safety) {
std::cerr bool
{
return errors.empty();
}
//-----------------------------------------------------------------------
// debug_print
//
auto debug_print() const
-> void
{
// Only create debug output files if we managed to load the source file.
//
if (source_loaded)
{
auto out_source = std::ofstream{ sourcefile+"-source" };
source.debug_print( out_source );
auto out_tokens = std::ofstream{ sourcefile+"-tokens" };
tokens.debug_print( out_tokens );
auto out_parse = std::ofstream{ sourcefile+"-parse" };
parser.debug_print( out_parse );
auto out_symbols = std::ofstream{ sourcefile+"-symbols" };
sema.debug_print ( out_symbols );
}
}
//-----------------------------------------------------------------------
// has_cpp1: pass through
//
auto has_cpp1() const
-> bool
{
return source.has_cpp1();
}
//-----------------------------------------------------------------------
// has_cpp2: pass through
//
auto has_cpp2() const
-> bool
{
return source.has_cpp2();
}
};
}
#endif