Skip to content

Commit

Permalink
Run postconditions before the 'return'
Browse files Browse the repository at this point in the history
So they correctly see the pre-moved named return value(s)

The following example now works correctly - before this commit, the postconditions would fail because the moved-from strings were zero length because of the automatic move-from-definite-last-use of the named return values

    make_string: () -> (ret: std::string = "xyzzy")
        post (ret.length() == ret.length()$ + 5)
    = {
        ret += " and ";
    }

    make_strings: ()
    -> (
        a: std::string = "xyzzy",
        b: std::string = "plugh"
        )
        post (a.length() == b.length() == 5)
    = { }

    main: () = {
        std::cout << make_string() + "plugh\n";
        std::cout << make_strings().a + make_strings().b + "\n";
    }
  • Loading branch information
hsutter committed Nov 19, 2023
1 parent 13f765b commit b4117ae
Show file tree
Hide file tree
Showing 14 changed files with 155 additions and 20 deletions.
39 changes: 39 additions & 0 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@
#if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
#include <format>
#endif
#include <functional>
#include <iostream>
#include <iterator>
#include <limits>
Expand Down Expand Up @@ -1576,6 +1577,11 @@ constexpr auto as( X const& x ) -> decltype(auto)
// finally_success ensures something is run at the end of a scope
// if no exception is thrown
//
// finally_presuccess ensures a group of add'd operations are run
// immediately before (not after) the return if no exception is
// thrown - right now this is used only for postconditions, so
// they can inspect named return values before they're moved from
//
//-----------------------------------------------------------------------
//

Expand Down Expand Up @@ -1631,6 +1637,39 @@ class finally
};


class finally_presuccess
{
public:
finally_presuccess() = default;

auto add(const auto& f) { fs.push_back(f); }

// In compiled Cpp2 code, this function will be called
// immediately before 'return' (both explicit and implicit)
auto run() {
if (invoke && ecount == std::uncaught_exceptions()) {
for (auto const& f : fs) {
f();
}
}
invoke = false;
}

~finally_presuccess() noexcept {
run();
}

finally_presuccess(finally_presuccess const&) = delete;
void operator= (finally_presuccess const&) = delete;
void operator= (finally_presuccess &&) = delete;

private:
std::vector<std::function<void()>> fs;
int ecount = std::uncaught_exceptions();
bool invoke = true;
};


//-----------------------------------------------------------------------
//
// args: see main() arguments as vector<string_view>
Expand Down
19 changes: 19 additions & 0 deletions regression-tests/mixed-postexpression-with-capture.cpp2
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

main: () -> int = {
insert_at( 0, 42 );
std::cout << make_string() + "plugh\n";
std::cout << make_strings().a + make_strings().b + "\n";
}

vec: std::vector<int> = ();
Expand All @@ -17,3 +19,20 @@ insert_at: (where: int, val: int)
= {
vec.push_back(val);
}

make_string: () -> (ret: std::string = "xyzzy")
post (ret.length() == ret.length()$ + 5)
= {
ret += " and ";
}

make_strings: ()
-> (
a: std::string = "xyzzy",
b: std::string = "plugh"
)
post (a.length() == b.length() == 5)
= {
// 'return' is generated when omitted like this
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
xyzzy and plugh
xyzzyplugh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
xyzzy and plugh
xyzzyplugh
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:46: error: expect
In file included from pure2-bugfix-for-requires-clause-in-forward-declaration.cpp:7:
../../../include/cpp2util.h:10005:47: error: static assertion failed: GCC 11 or higher is required to support variables and type-scope functions that have a 'requires' clause. This includes a type-scope 'forward' parameter of non-wildcard type, such as 'func: (this, forward s: std::string)', which relies on being able to add a 'requires' clause - in that case, use 'forward s: _' instead if you need the result to compile with GCC 10.
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:4:1: note: in expansion of macro ‘CPP2_REQUIRES_’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:3: error: no declaration matches ‘element::element(auto:90&&) requires is_same_v<typename std::remove_cv<typename std::remove_reference<decltype(element::__ct ::n)>::type>::type, std::__cxx11::string>’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:3: error: no declaration matches ‘element::element(auto:91&&) requires is_same_v<typename std::remove_cv<typename std::remove_reference<decltype(element::__ct ::n)>::type>::type, std::__cxx11::string>’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:5:11: note: candidates are: ‘element::element(const element&)’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:20: note: ‘template<class auto:88> element::element(auto:88&&)’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:20: note: ‘template<class auto:89> element::element(auto:89&&)’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:1:7: note: ‘class element’ defined here
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:5:78: error: expected unqualified-id before ‘{’ token
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:8: error: no declaration matches ‘element& element::operator=(auto:91&&) requires is_same_v<typename std::remove_cv<typename std::remove_reference<decltype(element::operator=::n)>::type>::type, std::__cxx11::string>’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:8: error: no declaration matches ‘element& element::operator=(auto:92&&) requires is_same_v<typename std::remove_cv<typename std::remove_reference<decltype(element::operator=::n)>::type>::type, std::__cxx11::string>’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:6:16: note: candidates are: ‘void element::operator=(const element&)’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:16: note: ‘template<class auto:89> element& element::operator=(auto:89&&)’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:16: note: ‘template<class auto:90> element& element::operator=(auto:90&&)’
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:1:7: note: ‘class element’ defined here
4 changes: 2 additions & 2 deletions regression-tests/test-results/gcc-10/pure2-print.cpp.output
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ pure2-print.cpp2:66:1: note: in expansion of macro ‘CPP2_REQUIRES_’
pure2-print.cpp2:94:1: note: in expansion of macro ‘CPP2_REQUIRES_’
pure2-print.cpp2:7:41: error: ‘constexpr const T outer::object_alias’ is not a static data member of ‘class outer’
pure2-print.cpp2:7:48: error: template definition of non-template ‘constexpr const T outer::object_alias’
pure2-print.cpp2:65:14: error: no declaration matches ‘void outer::mytype::variadic(const auto:97& ...) requires (is_convertible_v<typename std::remove_cv<typename std::remove_reference<decltype(outer::mytype::variadic::x)>::type>::type, int> && ...)’
pure2-print.cpp2:65:29: note: candidate is: ‘template<class ... auto:88> static void outer::mytype::variadic(const auto:88& ...)’
pure2-print.cpp2:65:14: error: no declaration matches ‘void outer::mytype::variadic(const auto:98& ...) requires (is_convertible_v<typename std::remove_cv<typename std::remove_reference<decltype(outer::mytype::variadic::x)>::type>::type, int> && ...)’
pure2-print.cpp2:65:29: note: candidate is: ‘template<class ... auto:89> static void outer::mytype::variadic(const auto:89& ...)’
pure2-print.cpp2:8:19: note: ‘class outer::mytype’ defined here
pure2-print.cpp2:93:37: error: no declaration matches ‘void outer::print(std::ostream&, const Args& ...) requires cpp2::cmp_greater_eq(sizeof (Args)..., 0)’
pure2-print.cpp2:93:37: note: no functions named ‘void outer::print(std::ostream&, const Args& ...) requires cpp2::cmp_greater_eq(sizeof (Args)..., 0)’
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
xyzzy and plugh
xyzzyplugh
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ auto insert_at(cpp2::in<int> where, cpp2::in<int> val) -> void

#line 22 "mixed-captures-in-expressions-and-postconditions.cpp2"
{
cpp2::finally_presuccess cpp2_finally_presuccess;
cpp2::Default.expects(cpp2::cmp_less_eq(0,where) && cpp2::cmp_less_eq(where,CPP2_UFCS_0(ssize, vec)), "");
auto post_21_5 = cpp2::finally_success([&, _1 = CPP2_UFCS_0(ssize, vec)]{cpp2::Default.expects(CPP2_UFCS_0(ssize, vec) == _1 + 1, "");} );
cpp2_finally_presuccess.add([&, _1 = CPP2_UFCS_0(ssize, vec)]{cpp2::Default.expects(CPP2_UFCS_0(ssize, vec) == _1 + 1, "");} );
#line 23 "mixed-captures-in-expressions-and-postconditions.cpp2"
static_cast<void>(CPP2_UFCS(insert, vec, CPP2_UFCS_0(begin, vec) + where, val));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,24 @@
#line 8 "mixed-postexpression-with-capture.cpp2"
[[nodiscard]] auto main() -> int;

#line 12 "mixed-postexpression-with-capture.cpp2"
#line 14 "mixed-postexpression-with-capture.cpp2"
extern std::vector<int> vec;

auto insert_at(cpp2::in<int> where, cpp2::in<int> val) -> void;
using make_string_ret = std::string;


#line 23 "mixed-postexpression-with-capture.cpp2"
[[nodiscard]] auto make_string() -> make_string_ret;
struct make_strings_ret { std::string a; std::string b; };



#line 29 "mixed-postexpression-with-capture.cpp2"
[[nodiscard]] auto make_strings() -> make_strings_ret;
#line 38 "mixed-postexpression-with-capture.cpp2"

#line 1 "mixed-postexpression-with-capture.cpp2"

//=== Cpp2 function definitions =================================================

Expand All @@ -33,17 +47,45 @@ auto insert_at(cpp2::in<int> where, cpp2::in<int> val) -> void;
#line 8 "mixed-postexpression-with-capture.cpp2"
[[nodiscard]] auto main() -> int{
insert_at(0, 42);
std::cout << make_string() + "plugh\n";
std::cout << make_strings().a + make_strings().b + "\n";
}

std::vector<int> vec {};

auto insert_at(cpp2::in<int> where, cpp2::in<int> val) -> void

#line 17 "mixed-postexpression-with-capture.cpp2"
#line 19 "mixed-postexpression-with-capture.cpp2"
{
cpp2::finally_presuccess cpp2_finally_presuccess;
cpp2::Default.expects(cpp2::cmp_less_eq(0,where) && cpp2::cmp_less_eq(where,CPP2_UFCS_0(ssize, vec)), "");
auto post_16_5 = cpp2::finally_success([&, _1 = CPP2_UFCS_0(size, vec)]{cpp2::Default.expects(CPP2_UFCS_0(size, vec) == _1 + 1, "");} );
#line 18 "mixed-postexpression-with-capture.cpp2"
cpp2_finally_presuccess.add([&, _1 = CPP2_UFCS_0(size, vec)]{cpp2::Default.expects(CPP2_UFCS_0(size, vec) == _1 + 1, "");} );
#line 20 "mixed-postexpression-with-capture.cpp2"
CPP2_UFCS(push_back, vec, val);
}

[[nodiscard]] auto make_string() -> make_string_ret

{
cpp2::finally_presuccess cpp2_finally_presuccess;
std::string ret {"xyzzy"};
cpp2_finally_presuccess.add([&, _1 = CPP2_UFCS_0(length, ret)]{cpp2::Default.expects(CPP2_UFCS_0(length, ret) == _1 + 5, "");} );
#line 26 "mixed-postexpression-with-capture.cpp2"
ret += " and ";
cpp2_finally_presuccess.run(); return std::move(ret); }

[[nodiscard]] auto make_strings() -> make_strings_ret

#line 35 "mixed-postexpression-with-capture.cpp2"
{
cpp2::finally_presuccess cpp2_finally_presuccess;
std::string a {"xyzzy"};
std::string b {"plugh"};
cpp2_finally_presuccess.add([&]{cpp2::Default.expects([_0 = CPP2_UFCS_0(length, a), _1 = CPP2_UFCS_0(length, b), _2 = 5]{ return _0==_1 && _1==_2; }(), "");} );
#line 30 "mixed-postexpression-with-capture.cpp2"
cpp2_finally_presuccess.run(); return { std::move(a), std::move(b) };

#line 36 "mixed-postexpression-with-capture.cpp2"
// 'return' is generated when omitted like this
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
xyzzy and plugh
xyzzyplugh
2 changes: 1 addition & 1 deletion regression-tests/test-results/version
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

cppfront compiler v0.3.0 Build 8B16:1224
cppfront compiler v0.3.0 Build 8B19:0904
Copyright(c) Herb Sutter All rights reserved

SPDX-License-Identifier: CC-BY-NC-ND-4.0
Expand Down
2 changes: 1 addition & 1 deletion source/build.info
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"8B16:1224"
"8B19:0904"
13 changes: 13 additions & 0 deletions source/parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -2190,6 +2190,9 @@ struct function_type_node

// API
//
auto has_postconditions() const
-> bool;

auto is_function_with_this() const
-> bool;

Expand Down Expand Up @@ -3480,6 +3483,16 @@ auto function_type_node::nth_parameter_type_name(int n) const
}


auto function_type_node::has_postconditions() const
-> bool
{
return
std::ranges::find_if(
contracts,
[](auto const& e){ return *e->kind == "post"; }
) != contracts.end();
}

auto function_type_node::is_function_with_this() const
-> bool
{
Expand Down
27 changes: 20 additions & 7 deletions source/to_cpp1.h
Original file line number Diff line number Diff line change
Expand Up @@ -1050,15 +1050,18 @@ class cppfront
struct function_info
{
declaration_node const* decl = {};
function_type_node const* func = {};
declaration_node::declared_value_set_funcs declared_value_set_functions = {};
function_prolog prolog = {};
std::vector<std::string> epilog = {};

function_info(
declaration_node const* decl_,
declaration_node const* decl_,
function_type_node const* func_,
declaration_node::declared_value_set_funcs declared_value_set_functions_
)
: decl{decl_}
, func{func_}
, declared_value_set_functions{declared_value_set_functions_}
{ }
};
Expand All @@ -1067,10 +1070,11 @@ class cppfront
std::deque<function_info> list = { {} };
public:
auto push(
declaration_node const* decl,
declaration_node const* decl,
function_type_node const* func,
declaration_node::declared_value_set_funcs thats
) {
list.emplace_back(decl, thats);
list.emplace_back(decl, func, thats);
}

auto pop() {
Expand Down Expand Up @@ -2261,6 +2265,11 @@ class cppfront
auto emit(return_statement_node const& n)
-> void
{
assert (!current_functions.empty());
if (current_functions.back().func->has_postconditions()) {
printer.print_cpp2( "cpp2_finally_presuccess.run(); ", n.position() );
}

assert(n.identifier);
assert(*n.identifier == "return");
printer.print_cpp2("return ", n.position());
Expand Down Expand Up @@ -4319,13 +4328,12 @@ class cppfront
{
assert (n.kind);

// For a postcondition, we'll wrap it in a final_action_success lambda
// 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(
"auto post_" + std::to_string(n.position().lineno) + "_" +
std::to_string(n.position().colno) + " = cpp2::finally_success(" +
"cpp2_finally_presuccess.add(" +
lambda_intro + "{",
n.position()
);
Expand Down Expand Up @@ -4374,7 +4382,7 @@ class cppfront
}
printer.print_cpp2(");", n.position());

// For a postcondition, close out the final_action_success lambda
// For a postcondition, close out the lambda
//
if (*n.kind == "post") {
printer.print_cpp2( "} );", n.position()
Expand Down Expand Up @@ -5747,6 +5755,7 @@ class cppfront

current_functions.push(
&n,
func.get(),
n.find_parent_declared_value_set_functions()
);
auto guard = finally([&]{ current_functions.pop(); });
Expand Down Expand Up @@ -6065,6 +6074,10 @@ class cppfront
function_returns.emplace_back(nullptr); // no return type at all
}

if (func->has_postconditions()) {
current_functions.back().prolog.statements.push_back("cpp2::finally_presuccess cpp2_finally_presuccess;");
}

if (func->returns.index() == function_type_node::list)
{
auto& r = std::get<function_type_node::list>(func->returns);
Expand Down

0 comments on commit b4117ae

Please sign in to comment.