hsutter / cppfront

A personal experimental C++ Syntax 2 -> Syntax 1 compiler
Other
5.39k stars 232 forks source link

[BUG] Move/forward on last use doesn't apply to generated code #869

Closed JohelEGP closed 6 months ago

JohelEGP commented 8 months ago

Title: Move/forward on last use doesn't apply to generated code.

Description:

@union generates set functions with a forward parameter pack. But the lowered code doesn't forward the pack on its last use.

Minimal reproducer (https://cpp2.godbolt.org/z/E9Eq91nEo):

v: @union @print type = {
  i: int;
}
main: () = { }

Commands: ```bash cppfront main.cpp2 clang++18 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -Werror=unused-result -Werror=unused-value -Werror=unused-parameter -I . main.cpp ```

Expected result:

Same as when you copy the @printed code to the Cpp2 source (https://cpp2.godbolt.org/z/fa4T55hGE):

    auto v::set_i(

        auto&& ..._args
    ) & -> void
    {
        if (!(is_i()))
        {
            _destroy();
            std::construct_at(reinterpret_cast<int*>(&_storage), CPP2_FORWARD(_args)...);
        }
        else
        {
            *cpp2::assert_not_null(reinterpret_cast<int*>(&_storage)) = int{CPP2_FORWARD(_args)...};
        }
        _discriminator = 0;
    }

Actual result and error:

No implicit forward on last use in the Cpp1 code lowered from generated Cpp2 code.

auto v::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;}

Cpp2 lowered to Cpp1: ```C++ //=== Cpp2 type declarations ==================================================== #include "cpp2util.h" #line 1 "/app/example.cpp2" class v; #line 2 "/app/example.cpp2" //=== Cpp2 type definitions and function declarations =========================== #line 1 "/app/example.cpp2" class v { private: std::aligned_storage_t _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 _value) & -> void; public: auto set_i(auto&& ..._args) & -> void; private: auto _destroy() & -> void; public: ~v() noexcept; public: explicit v(); public: v(v const& that); public: v(v&& that) noexcept; public: auto operator=(v const& that) -> v& ; public: auto operator=(v&& that) noexcept -> v& ; #line 3 "/app/example.cpp2" }; auto main() -> int; //=== Cpp2 function definitions ================================================= #line 1 "/app/example.cpp2" #line 1 "/app/example.cpp2" [[nodiscard]] auto v::is_i() const& -> bool { return _discriminator == 0; } [[nodiscard]] auto v::i() const& -> int const& { cpp2::Default.expects(is_i(), "");return *cpp2::assert_not_null(reinterpret_cast(&_storage)); } [[nodiscard]] auto v::i() & -> int& { cpp2::Default.expects(is_i(), "");return *cpp2::assert_not_null(reinterpret_cast(&_storage)); } auto v::set_i(cpp2::in _value) & -> void{if (!(is_i())) {_destroy();std::construct_at(reinterpret_cast(&_storage), _value);}else {*cpp2::assert_not_null(reinterpret_cast(&_storage)) = _value;}_discriminator = 0;} auto v::set_i(auto&& ..._args) & -> void{if (!(is_i())) {_destroy();std::construct_at(reinterpret_cast(&_storage), _args...);}else {*cpp2::assert_not_null(reinterpret_cast(&_storage)) = int{_args...};}_discriminator = 0;} auto v::_destroy() & -> void{ if (_discriminator == 0) {std::destroy_at(reinterpret_cast(&_storage));} _discriminator = -1; } v::~v() noexcept{_destroy();} v::v(){} v::v(v const& that) : _storage{ } , _discriminator{ -1 }{ if (CPP2_UFCS(is_i)(that)) {set_i(CPP2_UFCS(i)(that));} } v::v(v&& that) noexcept : _storage{ } , _discriminator{ -1 }{ if (CPP2_UFCS(is_i)(std::move(that))) {set_i(CPP2_UFCS(i)(std::move(that)));} } auto v::operator=(v const& that) -> v& { if (CPP2_UFCS(is_i)(that)) {set_i(CPP2_UFCS(i)(that));} return *this; } auto v::operator=(v&& that) noexcept -> v& { if (CPP2_UFCS(is_i)(std::move(that))) {set_i(CPP2_UFCS(i)(std::move(that)));} return *this; } #line 4 "/app/example.cpp2" auto main() -> int{} ```

Output: ```output Step build returned: 0 [1/3] Generating main.cpp main.cpp2... v: type = { _storage: std::aligned_storage_t = (); _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; } private _destroy:(inout this) = { if _discriminator == 0 { std::destroy_at(reinterpret_cast<* int>(_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()); } } operator=:( inout this, in that ) = { _storage = _; _discriminator = _; if that.is_i() { set_i(that.i()); } } } ok (all Cpp2, passes safety checks) [2/3] Building CXX object CMakeFiles/main.dir/main.cpp.o [3/3] Linking CXX executable main Program returned: 0 ```

See also:

Other bugs in this area: