Open
Description
Title: Poor debugging experience of generated code.
Description:
A lot of Cpp2 can be generated, not visible in the Cpp2 source code itself.
For example, generated constructors and assignments,
as well as member functions generated by metafunctions.
When debugging the reproducer,
until you step into a standard function,
attempting to step into generated code
- keeps the cursor at the EOF, and
- you can see some movement in various panes.
This results in a poor user experience.
I think we can and should do better.
Minimal reproducer (https://cpp2.godbolt.org/z/P7ssbdoYe):
number: @union @print type = {
i: int;
d: double;
}
main: () = {
n: number = ();
n.set_i(42);
if n.is_i() { n.set_d(17.29); }
_ = n;
}
Commands:
cppfront main.cpp2
clang++18 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -Werror=unused-result -I . main.cpp
Expected result:
- Stepping into generated code jumps to the generated code.
cppfront
to output a Cpp2 source file with the generated code.- The lowered Cpp1
#line
directives of generated code to point to the new file.
Actual result and error: Stepping into generated code jumps to the EOF.
Cpp2 lowered to Cpp1:
//=== Cpp2 type declarations ====================================================
#include "cpp2util.h"
class number;
//=== Cpp2 type definitions and function declarations ===========================
class number {
private: std::aligned_storage_t<cpp2::max(sizeof(int), sizeof(double))> _storage {}; private: cpp2::i8 _discriminator {-1}; public: [[nodiscard]] auto is_i() const& -> bool;
public: [[nodiscard]] auto i() const& -> int const&;
public: [[nodiscard]] auto i() & -> int&;
public: auto set_i(cpp2::in<int> _value) & -> void;
public: auto set_i(auto&& ..._args) & -> void;
public: [[nodiscard]] auto is_d() const& -> bool;
public: [[nodiscard]] auto d() const& -> double const&;
public: [[nodiscard]] auto d() & -> double&;
public: auto set_d(cpp2::in<double> _value) & -> void;
public: auto set_d(auto&& ..._args) & -> void;
private: auto _destroy() & -> void;
public: ~number() noexcept;
public: explicit number();
public: number(number const& that);
public: number(number&& that) noexcept;
public: auto operator=(number const& that) -> number& ;
public: auto operator=(number&& that) noexcept -> number& ;
};
auto main() -> int;
//=== Cpp2 function definitions =================================================
[[nodiscard]] auto number::is_i() const& -> bool { return _discriminator == 0; }
[[nodiscard]] auto number::i() const& -> int const& {
cpp2::Default.expects(is_i(), "");return *cpp2::assert_not_null(reinterpret_cast<int const*>(&_storage)); }
[[nodiscard]] auto number::i() & -> int& {
cpp2::Default.expects(is_i(), "");return *cpp2::assert_not_null(reinterpret_cast<int*>(&_storage)); }
auto number::set_i(cpp2::in<int> _value) & -> void{if (!(is_i())) {_destroy();std::construct_at(reinterpret_cast<int*>(&_storage), _value);}else {*cpp2::assert_not_null(reinterpret_cast<int*>(&_storage)) = _value;}_discriminator = 0;}
auto number::set_i(auto&& ..._args) & -> void{if (!(is_i())) {_destroy();std::construct_at(reinterpret_cast<int*>(&_storage), _args...);}else {*cpp2::assert_not_null(reinterpret_cast<int*>(&_storage)) = int{_args...};}_discriminator = 0;}
[[nodiscard]] auto number::is_d() const& -> bool { return _discriminator == 1; }
[[nodiscard]] auto number::d() const& -> double const& {
cpp2::Default.expects(is_d(), "");return *cpp2::assert_not_null(reinterpret_cast<double const*>(&_storage)); }
[[nodiscard]] auto number::d() & -> double& {
cpp2::Default.expects(is_d(), "");return *cpp2::assert_not_null(reinterpret_cast<double*>(&_storage)); }
auto number::set_d(cpp2::in<double> _value) & -> void{if (!(is_d())) {_destroy();std::construct_at(reinterpret_cast<double*>(&_storage), _value);}else {*cpp2::assert_not_null(reinterpret_cast<double*>(&_storage)) = _value;}_discriminator = 1;}
auto number::set_d(auto&& ..._args) & -> void{if (!(is_d())) {_destroy();std::construct_at(reinterpret_cast<double*>(&_storage), _args...);}else {*cpp2::assert_not_null(reinterpret_cast<double*>(&_storage)) = double{_args...};}_discriminator = 1;}
auto number::_destroy() & -> void{
if (_discriminator == 0) {std::destroy_at(reinterpret_cast<int*>(&_storage));}
if (_discriminator == 1) {std::destroy_at(reinterpret_cast<double*>(&_storage));}
_discriminator = -1;
}
number::~number() noexcept{_destroy();}
number::number(){}
number::number(number const& that)
: _storage{ }
, _discriminator{ -1 }{
if (CPP2_UFCS_0(is_i, that)) {set_i(CPP2_UFCS_0(i, that));}
if (CPP2_UFCS_0(is_d, that)) {set_d(CPP2_UFCS_0(d, that));}
}
number::number(number&& that) noexcept
: _storage{ }
, _discriminator{ -1 }{
if (CPP2_UFCS_0(is_i, std::move(that))) {set_i(CPP2_UFCS_0(i, std::move(that)));}
if (CPP2_UFCS_0(is_d, std::move(that))) {set_d(CPP2_UFCS_0(d, std::move(that)));}
}
auto number::operator=(number const& that) -> number& {
if (CPP2_UFCS_0(is_i, that)) {set_i(CPP2_UFCS_0(i, that));}
if (CPP2_UFCS_0(is_d, that)) {set_d(CPP2_UFCS_0(d, that));}
return *this;
}
auto number::operator=(number&& that) noexcept -> number& {
if (CPP2_UFCS_0(is_i, std::move(that))) {set_i(CPP2_UFCS_0(i, std::move(that)));}
if (CPP2_UFCS_0(is_d, std::move(that))) {set_d(CPP2_UFCS_0(d, std::move(that)));}
return *this;
}
auto main() -> int{
number n {};
CPP2_UFCS(set_i, n, 42);
if (CPP2_UFCS_0(is_i, n)) {CPP2_UFCS(set_d, n, 17.29); }
static_cast<void>(std::move(n));
}
Output:
Step build returned: 0
[1/5] Generating main.cpp
main.cpp2...
number: type =
{
_storage: std::aligned_storage_t<cpp2::max(sizeof(int), sizeof(double))> = ();
_discriminator: i8 = -1;
is_i:(in this) -> move bool = _discriminator == 0;
i:(in this) -> forward int
pre( is_i() ) = reinterpret_cast<* const int>(_storage&)*;
i:(inout this) -> forward int
pre( is_i() ) = reinterpret_cast<* int>(_storage&)*;
set_i:(
inout this,
in _value: int
) =
{
if !is_i()
{
_destroy();
std::construct_at(reinterpret_cast<* int>(_storage&), _value);
}
else
{
reinterpret_cast<* int>(_storage&)* = _value;
}
_discriminator = 0;
}
set_i:(
inout this,
forward _args...:
) =
{
if !is_i()
{
_destroy();
std::construct_at(reinterpret_cast<* int>(_storage&), _args...);
}
else
{
reinterpret_cast<* int>(_storage&)* = : int = (_args...);
}
_discriminator = 0;
}
is_d:(in this) -> move bool = _discriminator == 1;
d:(in this) -> forward double
pre( is_d() ) = reinterpret_cast<* const double>(_storage&)*;
d:(inout this) -> forward double
pre( is_d() ) = reinterpret_cast<* double>(_storage&)*;
set_d:(
inout this,
in _value: double
) =
{
if !is_d()
{
_destroy();
std::construct_at(reinterpret_cast<* double>(_storage&), _value);
}
else
{
reinterpret_cast<* double>(_storage&)* = _value;
}
_discriminator = 1;
}
set_d:(
inout this,
forward _args...:
) =
{
if !is_d()
{
_destroy();
std::construct_at(reinterpret_cast<* double>(_storage&), _args...);
}
else
{
reinterpret_cast<* double>(_storage&)* = : double = (_args...);
}
_discriminator = 1;
}
private _destroy:(inout this) =
{
if _discriminator == 0
{
std::destroy_at(reinterpret_cast<* int>(_storage&));
}
if _discriminator == 1
{
std::destroy_at(reinterpret_cast<* double>(_storage&));
}
_discriminator = -1;
}
operator=:(move this) =
{
_destroy();
}
operator=:(out this) =
{
}
operator=:(
out this,
in that
) =
{
_storage = ();
_discriminator = -1;
if that.is_i()
{
set_i(that.i());
}
if that.is_d()
{
set_d(that.d());
}
}
operator=:(
inout this,
in that
) =
{
_storage = _;
_discriminator = _;
if that.is_i()
{
set_i(that.i());
}
if that.is_d()
{
set_d(that.d());
}
}
}
ok (all Cpp2, passes safety checks)
[2/5] Scanning /app/build/main.cpp for CXX dependencies
[3/5] Generating CXX dyndep file CMakeFiles/main.dir/CXX.dd
[4/5] Building CXX object CMakeFiles/main.dir/main.cpp.o
main.cpp2:2:83: warning: 'using std::aligned_storage_t = union std::aligned_storage<8, 16>::type' is deprecated [-Wdeprecated-declarations]
In file included from /opt/compiler-explorer/gcc-trunk-20231120/include/c++/14.0.0/bits/stl_pair.h:60,
from /opt/compiler-explorer/gcc-trunk-20231120/include/c++/14.0.0/bits/stl_algobase.h:64,
from /opt/compiler-explorer/gcc-trunk-20231120/include/c++/14.0.0/algorithm:60,
from /app/raw.githubusercontent.com/hsutter/cppfront/main/include/cpp2util.h:232,
from /app/cpp2util.h:1,
from /app/build/main.cpp:6:
/opt/compiler-explorer/gcc-trunk-20231120/include/c++/14.0.0/type_traits:2611:11: note: declared here
2611 | using aligned_storage_t _GLIBCXX23_DEPRECATED = typename aligned_storage<_Len, _Align>::type;
| ^~~~~~~~~~~~~~~~~
[5/5] Linking CXX executable main
Program returned: 0