Skip to content

Commit b4117ae

Browse files
committed
Run postconditions before the 'return'
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"; }
1 parent 13f765b commit b4117ae

14 files changed

+155
-20
lines changed

include/cpp2util.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@
242242
#if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
243243
#include <format>
244244
#endif
245+
#include <functional>
245246
#include <iostream>
246247
#include <iterator>
247248
#include <limits>
@@ -1576,6 +1577,11 @@ constexpr auto as( X const& x ) -> decltype(auto)
15761577
// finally_success ensures something is run at the end of a scope
15771578
// if no exception is thrown
15781579
//
1580+
// finally_presuccess ensures a group of add'd operations are run
1581+
// immediately before (not after) the return if no exception is
1582+
// thrown - right now this is used only for postconditions, so
1583+
// they can inspect named return values before they're moved from
1584+
//
15791585
//-----------------------------------------------------------------------
15801586
//
15811587

@@ -1631,6 +1637,39 @@ class finally
16311637
};
16321638

16331639

1640+
class finally_presuccess
1641+
{
1642+
public:
1643+
finally_presuccess() = default;
1644+
1645+
auto add(const auto& f) { fs.push_back(f); }
1646+
1647+
// In compiled Cpp2 code, this function will be called
1648+
// immediately before 'return' (both explicit and implicit)
1649+
auto run() {
1650+
if (invoke && ecount == std::uncaught_exceptions()) {
1651+
for (auto const& f : fs) {
1652+
f();
1653+
}
1654+
}
1655+
invoke = false;
1656+
}
1657+
1658+
~finally_presuccess() noexcept {
1659+
run();
1660+
}
1661+
1662+
finally_presuccess(finally_presuccess const&) = delete;
1663+
void operator= (finally_presuccess const&) = delete;
1664+
void operator= (finally_presuccess &&) = delete;
1665+
1666+
private:
1667+
std::vector<std::function<void()>> fs;
1668+
int ecount = std::uncaught_exceptions();
1669+
bool invoke = true;
1670+
};
1671+
1672+
16341673
//-----------------------------------------------------------------------
16351674
//
16361675
// args: see main() arguments as vector<string_view>

regression-tests/mixed-postexpression-with-capture.cpp2

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

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

1214
vec: std::vector<int> = ();
@@ -17,3 +19,20 @@ insert_at: (where: int, val: int)
1719
= {
1820
vec.push_back(val);
1921
}
22+
23+
make_string: () -> (ret: std::string = "xyzzy")
24+
post (ret.length() == ret.length()$ + 5)
25+
= {
26+
ret += " and ";
27+
}
28+
29+
make_strings: ()
30+
-> (
31+
a: std::string = "xyzzy",
32+
b: std::string = "plugh"
33+
)
34+
post (a.length() == b.length() == 5)
35+
= {
36+
// 'return' is generated when omitted like this
37+
}
38+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
xyzzy and plugh
2+
xyzzyplugh
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
xyzzy and plugh
2+
xyzzyplugh

regression-tests/test-results/gcc-10/pure2-bugfix-for-requires-clause-in-forward-declaration.cpp.output

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:46: error: expect
66
In file included from pure2-bugfix-for-requires-clause-in-forward-declaration.cpp:7:
77
../../../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.
88
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:4:1: note: in expansion of macro ‘CPP2_REQUIRES_’
9-
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>’
9+
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>’
1010
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:5:11: note: candidates are: ‘element::element(const element&)’
11-
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:20: note: ‘template<class auto:88> element::element(auto:88&&)’
11+
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:20: note: ‘template<class auto:89> element::element(auto:89&&)’
1212
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:1:7: note: ‘class element’ defined here
1313
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:5:78: error: expected unqualified-id before ‘{’ token
14-
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>’
14+
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>’
1515
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:6:16: note: candidates are: ‘void element::operator=(const element&)’
16-
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:16: note: ‘template<class auto:89> element& element::operator=(auto:89&&)’
16+
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:16: note: ‘template<class auto:90> element& element::operator=(auto:90&&)’
1717
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:1:7: note: ‘class element’ defined here

regression-tests/test-results/gcc-10/pure2-print.cpp.output

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ pure2-print.cpp2:66:1: note: in expansion of macro ‘CPP2_REQUIRES_’
99
pure2-print.cpp2:94:1: note: in expansion of macro ‘CPP2_REQUIRES_’
1010
pure2-print.cpp2:7:41: error: ‘constexpr const T outer::object_alias’ is not a static data member of ‘class outer’
1111
pure2-print.cpp2:7:48: error: template definition of non-template ‘constexpr const T outer::object_alias’
12-
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> && ...)’
13-
pure2-print.cpp2:65:29: note: candidate is: ‘template<class ... auto:88> static void outer::mytype::variadic(const auto:88& ...)’
12+
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> && ...)’
13+
pure2-print.cpp2:65:29: note: candidate is: ‘template<class ... auto:89> static void outer::mytype::variadic(const auto:89& ...)’
1414
pure2-print.cpp2:8:19: note: ‘class outer::mytype’ defined here
1515
pure2-print.cpp2:93:37: error: no declaration matches ‘void outer::print(std::ostream&, const Args& ...) requires cpp2::cmp_greater_eq(sizeof (Args)..., 0)’
1616
pure2-print.cpp2:93:37: note: no functions named ‘void outer::print(std::ostream&, const Args& ...) requires cpp2::cmp_greater_eq(sizeof (Args)..., 0)’
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
xyzzy and plugh
2+
xyzzyplugh

regression-tests/test-results/mixed-captures-in-expressions-and-postconditions.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ auto insert_at(cpp2::in<int> where, cpp2::in<int> val) -> void
4646

4747
#line 22 "mixed-captures-in-expressions-and-postconditions.cpp2"
4848
{
49+
cpp2::finally_presuccess cpp2_finally_presuccess;
4950
cpp2::Default.expects(cpp2::cmp_less_eq(0,where) && cpp2::cmp_less_eq(where,CPP2_UFCS_0(ssize, vec)), "");
50-
auto post_21_5 = cpp2::finally_success([&, _1 = CPP2_UFCS_0(ssize, vec)]{cpp2::Default.expects(CPP2_UFCS_0(ssize, vec) == _1 + 1, "");} );
51+
cpp2_finally_presuccess.add([&, _1 = CPP2_UFCS_0(ssize, vec)]{cpp2::Default.expects(CPP2_UFCS_0(ssize, vec) == _1 + 1, "");} );
5152
#line 23 "mixed-captures-in-expressions-and-postconditions.cpp2"
5253
static_cast<void>(CPP2_UFCS(insert, vec, CPP2_UFCS_0(begin, vec) + where, val));
5354
}

regression-tests/test-results/mixed-postexpression-with-capture.cpp

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,24 @@
2121
#line 8 "mixed-postexpression-with-capture.cpp2"
2222
[[nodiscard]] auto main() -> int;
2323

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

2727
auto insert_at(cpp2::in<int> where, cpp2::in<int> val) -> void;
28+
using make_string_ret = std::string;
29+
30+
31+
#line 23 "mixed-postexpression-with-capture.cpp2"
32+
[[nodiscard]] auto make_string() -> make_string_ret;
33+
struct make_strings_ret { std::string a; std::string b; };
34+
35+
36+
37+
#line 29 "mixed-postexpression-with-capture.cpp2"
38+
[[nodiscard]] auto make_strings() -> make_strings_ret;
39+
#line 38 "mixed-postexpression-with-capture.cpp2"
40+
41+
#line 1 "mixed-postexpression-with-capture.cpp2"
2842

2943
//=== Cpp2 function definitions =================================================
3044

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

3854
std::vector<int> vec {};
3955

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

42-
#line 17 "mixed-postexpression-with-capture.cpp2"
58+
#line 19 "mixed-postexpression-with-capture.cpp2"
4359
{
60+
cpp2::finally_presuccess cpp2_finally_presuccess;
4461
cpp2::Default.expects(cpp2::cmp_less_eq(0,where) && cpp2::cmp_less_eq(where,CPP2_UFCS_0(ssize, vec)), "");
45-
auto post_16_5 = cpp2::finally_success([&, _1 = CPP2_UFCS_0(size, vec)]{cpp2::Default.expects(CPP2_UFCS_0(size, vec) == _1 + 1, "");} );
46-
#line 18 "mixed-postexpression-with-capture.cpp2"
62+
cpp2_finally_presuccess.add([&, _1 = CPP2_UFCS_0(size, vec)]{cpp2::Default.expects(CPP2_UFCS_0(size, vec) == _1 + 1, "");} );
63+
#line 20 "mixed-postexpression-with-capture.cpp2"
4764
CPP2_UFCS(push_back, vec, val);
4865
}
4966

67+
[[nodiscard]] auto make_string() -> make_string_ret
68+
69+
{
70+
cpp2::finally_presuccess cpp2_finally_presuccess;
71+
std::string ret {"xyzzy"};
72+
cpp2_finally_presuccess.add([&, _1 = CPP2_UFCS_0(length, ret)]{cpp2::Default.expects(CPP2_UFCS_0(length, ret) == _1 + 5, "");} );
73+
#line 26 "mixed-postexpression-with-capture.cpp2"
74+
ret += " and ";
75+
cpp2_finally_presuccess.run(); return std::move(ret); }
76+
77+
[[nodiscard]] auto make_strings() -> make_strings_ret
78+
79+
#line 35 "mixed-postexpression-with-capture.cpp2"
80+
{
81+
cpp2::finally_presuccess cpp2_finally_presuccess;
82+
std::string a {"xyzzy"};
83+
std::string b {"plugh"};
84+
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; }(), "");} );
85+
#line 30 "mixed-postexpression-with-capture.cpp2"
86+
cpp2_finally_presuccess.run(); return { std::move(a), std::move(b) };
87+
88+
#line 36 "mixed-postexpression-with-capture.cpp2"
89+
// 'return' is generated when omitted like this
90+
}
91+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
xyzzy and plugh
2+
xyzzyplugh

regression-tests/test-results/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

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

55
SPDX-License-Identifier: CC-BY-NC-ND-4.0

source/build.info

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"8B16:1224"
1+
"8B19:0904"

source/parse.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,6 +2190,9 @@ struct function_type_node
21902190

21912191
// API
21922192
//
2193+
auto has_postconditions() const
2194+
-> bool;
2195+
21932196
auto is_function_with_this() const
21942197
-> bool;
21952198

@@ -3480,6 +3483,16 @@ auto function_type_node::nth_parameter_type_name(int n) const
34803483
}
34813484

34823485

3486+
auto function_type_node::has_postconditions() const
3487+
-> bool
3488+
{
3489+
return
3490+
std::ranges::find_if(
3491+
contracts,
3492+
[](auto const& e){ return *e->kind == "post"; }
3493+
) != contracts.end();
3494+
}
3495+
34833496
auto function_type_node::is_function_with_this() const
34843497
-> bool
34853498
{

source/to_cpp1.h

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,15 +1050,18 @@ class cppfront
10501050
struct function_info
10511051
{
10521052
declaration_node const* decl = {};
1053+
function_type_node const* func = {};
10531054
declaration_node::declared_value_set_funcs declared_value_set_functions = {};
10541055
function_prolog prolog = {};
10551056
std::vector<std::string> epilog = {};
10561057

10571058
function_info(
1058-
declaration_node const* decl_,
1059+
declaration_node const* decl_,
1060+
function_type_node const* func_,
10591061
declaration_node::declared_value_set_funcs declared_value_set_functions_
10601062
)
10611063
: decl{decl_}
1064+
, func{func_}
10621065
, declared_value_set_functions{declared_value_set_functions_}
10631066
{ }
10641067
};
@@ -1067,10 +1070,11 @@ class cppfront
10671070
std::deque<function_info> list = { {} };
10681071
public:
10691072
auto push(
1070-
declaration_node const* decl,
1073+
declaration_node const* decl,
1074+
function_type_node const* func,
10711075
declaration_node::declared_value_set_funcs thats
10721076
) {
1073-
list.emplace_back(decl, thats);
1077+
list.emplace_back(decl, func, thats);
10741078
}
10751079

10761080
auto pop() {
@@ -2261,6 +2265,11 @@ class cppfront
22612265
auto emit(return_statement_node const& n)
22622266
-> void
22632267
{
2268+
assert (!current_functions.empty());
2269+
if (current_functions.back().func->has_postconditions()) {
2270+
printer.print_cpp2( "cpp2_finally_presuccess.run(); ", n.position() );
2271+
}
2272+
22642273
assert(n.identifier);
22652274
assert(*n.identifier == "return");
22662275
printer.print_cpp2("return ", n.position());
@@ -4319,13 +4328,12 @@ class cppfront
43194328
{
43204329
assert (n.kind);
43214330

4322-
// For a postcondition, we'll wrap it in a final_action_success lambda
4331+
// For a postcondition, we'll wrap it in a lambda and register it
43234332
//
43244333
if (*n.kind == "post") {
43254334
auto lambda_intro = build_capture_lambda_intro_for(n.captures, n.position(), true);
43264335
printer.print_cpp2(
4327-
"auto post_" + std::to_string(n.position().lineno) + "_" +
4328-
std::to_string(n.position().colno) + " = cpp2::finally_success(" +
4336+
"cpp2_finally_presuccess.add(" +
43294337
lambda_intro + "{",
43304338
n.position()
43314339
);
@@ -4374,7 +4382,7 @@ class cppfront
43744382
}
43754383
printer.print_cpp2(");", n.position());
43764384

4377-
// For a postcondition, close out the final_action_success lambda
4385+
// For a postcondition, close out the lambda
43784386
//
43794387
if (*n.kind == "post") {
43804388
printer.print_cpp2( "} );", n.position()
@@ -5747,6 +5755,7 @@ class cppfront
57475755

57485756
current_functions.push(
57495757
&n,
5758+
func.get(),
57505759
n.find_parent_declared_value_set_functions()
57515760
);
57525761
auto guard = finally([&]{ current_functions.pop(); });
@@ -6065,6 +6074,10 @@ class cppfront
60656074
function_returns.emplace_back(nullptr); // no return type at all
60666075
}
60676076

6077+
if (func->has_postconditions()) {
6078+
current_functions.back().prolog.statements.push_back("cpp2::finally_presuccess cpp2_finally_presuccess;");
6079+
}
6080+
60686081
if (func->returns.index() == function_type_node::list)
60696082
{
60706083
auto& r = std::get<function_type_node::list>(func->returns);

0 commit comments

Comments
 (0)