Skip to content

Commit 5d44e44

Browse files
authored
Merge pull request #169 from filipsajdak/fsajdak-ufcs-chaining
Add support for ufcs chaining
2 parents eaf2551 + cc85f85 commit 5d44e44

8 files changed

+129
-100
lines changed

include/cpp2util.h

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -536,21 +536,23 @@ class out {
536536
} \
537537
}(PARAM1)
538538

539-
#define CPP2_UFCS_TEMPLATE(FUNCNAME,PARAM1,...) \
539+
#define CPP2_UFCS_REMPARENS(...) __VA_ARGS__
540+
541+
#define CPP2_UFCS_TEMPLATE(FUNCNAME,TEMPARGS,PARAM1,...) \
540542
[](auto&& obj, auto&& ...params) { \
541-
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME(std::forward<decltype(params)>(params)...); }) { \
542-
return std::forward<decltype(obj)>(obj).template FUNCNAME(std::forward<decltype(params)>(params)...); \
543+
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); }) { \
544+
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); \
543545
} else { \
544-
return FUNCNAME(std::forward<decltype(obj)>(obj), std::forward<decltype(params)>(params)...); \
546+
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj), std::forward<decltype(params)>(params)...); \
545547
} \
546548
}(PARAM1, __VA_ARGS__)
547549

548-
#define CPP2_UFCS_TEMPLATE_0(FUNCNAME,PARAM1) \
550+
#define CPP2_UFCS_TEMPLATE_0(FUNCNAME,TEMPARGS,PARAM1) \
549551
[](auto&& obj) { \
550-
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME(); }) { \
551-
return std::forward<decltype(obj)>(obj).template FUNCNAME(); \
552+
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
553+
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
552554
} else { \
553-
return FUNCNAME(std::forward<decltype(obj)>(obj)); \
555+
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj)); \
554556
} \
555557
}(PARAM1)
556558
//--------------------------------------------------------------------

regression-tests/test-results/mixed-lifetime-safety-and-null-contracts.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ auto call_my_framework(cpp2::in<const char *> msg) -> void;
2222
#line 5 "mixed-lifetime-safety-and-null-contracts.cpp2"
2323

2424
[[nodiscard]] auto main() -> int{
25-
cpp2::Null.set_handler(&call_my_framework);
25+
CPP2_UFCS(set_handler, cpp2::Null, &call_my_framework);
2626
try_pointer_stuff();
2727
std::cout << "done\n";
2828
}

regression-tests/test-results/mixed-postfix-expression-custom-formatting.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ auto call(auto const& v, auto const& w, auto const& x, auto const& y, auto const
1717

1818
[[nodiscard]] auto test(auto const& a) -> std::string{
1919
return call( a,
20-
++*cpp2::assert_not_null(a.b(a.c)), "hello", /* polite
20+
++*cpp2::assert_not_null(CPP2_UFCS(b, a, a.c)), "hello", /* polite
2121
greeting
2222
goes here */ " there",
23-
a.d.e( ++(*cpp2::assert_not_null(a.f)).g(), // because f is foobar
24-
a.h.i(),
23+
CPP2_UFCS(e, a.d, ++CPP2_UFCS_0(g, *cpp2::assert_not_null(a.f)), // because f is foobar
24+
CPP2_UFCS_0(i, a.h),
2525
CPP2_UFCS(j, a, a.k, a.l))
2626
);
2727
}

regression-tests/test-results/mixed-ufcs-multiple-template-arguments.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct X {
2828
std::cout << substr<4,8>(test_string) << "\n";
2929

3030
X x { test_string };
31-
std::cout << std::move(x).substr<4,8>() << "\n";
31+
std::cout << CPP2_UFCS_TEMPLATE_0(substr, (<4,8>), std::move(x)) << "\n";
3232
// for now this should not be UFCS-ized because of the multiple template arguments
3333
}
3434

regression-tests/test-results/pure2-inspect-expression-in-generic-function-multiple-types.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ auto test_generic(auto const& x, auto const& msg) -> void;
2121
test_generic(a, "any");
2222
test_generic(o, "optional<int>");
2323

24-
CPP2_UFCS_TEMPLATE(emplace<0>, v, 1);
24+
CPP2_UFCS_TEMPLATE(emplace, (<0>), v, 1);
2525
a = 2;
2626
o = 3;
2727
test_generic(42, "int");

regression-tests/test-results/pure2-type-safety-1.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ auto print(cpp2::in<std::string> msg, cpp2::in<bool> b) -> void;
2929

3030
std::cout << "\n";
3131

32-
CPP2_UFCS_TEMPLATE(emplace<1>, v, 1);
32+
CPP2_UFCS_TEMPLATE(emplace, (<1>), v, 1);
3333
a = 2;
3434
o = 3;
3535
test_generic(42, "int");

regression-tests/test-results/pure2-type-safety-2-with-inspect-expression.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ auto test_generic(auto const& x, auto const& msg) -> void;
2121
test_generic(a, "any");
2222
test_generic(o, "optional<int>");
2323

24-
CPP2_UFCS_TEMPLATE(emplace<2>, v, 1);
24+
CPP2_UFCS_TEMPLATE(emplace, (<2>), v, 1);
2525
a = 2;
2626
o = 3;
2727
test_generic(42, "int");

source/cppfront.cpp

Lines changed: 111 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include "sema.h"
1919
#include <iostream>
2020
#include <cstdio>
21-
21+
#include <optional>
2222

2323
namespace cpp2 {
2424

@@ -1725,77 +1725,6 @@ class cppfront
17251725
captured_part += "_" + std::to_string(mynum);
17261726
}
17271727

1728-
// Check to see if it's just a function call with "." syntax,
1729-
// and if so use this path to convert it to UFCS
1730-
if (// there's a single-token expression followed by . and (
1731-
n.expr->get_token() && // if the base expression is a single token
1732-
std::ssize(n.ops) >= 2 && // and we're of the form:
1733-
n.ops[0].op->type() == lexeme::Dot && // token . id-expr ( expr-list )
1734-
n.ops[1].op->type() == lexeme::LeftParen &&
1735-
// alpha limitation: if it's a function call with more than one template argument (e.g., x.f<1,2>())
1736-
// the UFCS* macros can't handle that right now, so don't UFCS-size it
1737-
n.ops[0].id_expr->template_args_count() < 2 &&
1738-
// and either there's nothing after that, or there's just a $ after that
1739-
(
1740-
std::ssize(n.ops) == 2 ||
1741-
(std::ssize(n.ops) == 3 && n.ops[2].op->type() == lexeme::Dollar)
1742-
)
1743-
)
1744-
{
1745-
// If we already replaced this with a capture (which contains the UFCS
1746-
// work already done when the capture was computed), emit the capture
1747-
if (!captured_part.empty()) {
1748-
printer.print_cpp2(captured_part, n.position());
1749-
return;
1750-
}
1751-
1752-
// Otherwise, do the UFCS work...
1753-
1754-
// The . has its id_expr
1755-
assert (n.ops[0].id_expr);
1756-
1757-
// The ( has its expr_list and op_close
1758-
assert (n.ops[1].expr_list && n.ops[1].op_close);
1759-
1760-
//--------------------------------------------------------------------
1761-
// TODO: When MSVC supports __VA_OPT__ in standard mode without the
1762-
// experimental /Zc:preprocessor switch, use this single line
1763-
// instead of the dual lines below that special-case _0 args
1764-
// AND: Make the similarly noted change in cpp2util.h
1765-
//
1766-
//printer.print_cpp2("CPP2_UFCS(", n.position());
1767-
1768-
auto ufcs_string = std::string("CPP2_UFCS");
1769-
if (n.ops[0].id_expr->template_args_count() > 0) {
1770-
ufcs_string += "_TEMPLATE";
1771-
}
1772-
// If there are no additional arguments, use the _0 version
1773-
if (n.ops[1].expr_list->expressions.empty()) {
1774-
ufcs_string += "_0";
1775-
}
1776-
printer.print_cpp2(ufcs_string+"(", n.position());
1777-
//--------------------------------------------------------------------
1778-
1779-
// Make the "funcname" the first argument to CPP2_UFCS
1780-
emit(*n.ops[0].id_expr);
1781-
printer.print_cpp2(", ", n.position());
1782-
1783-
// Then make the base expression the second argument
1784-
emit(*n.expr);
1785-
1786-
// Then tack on any additional arguments
1787-
if (!n.ops[1].expr_list->expressions.empty()) {
1788-
printer.print_cpp2(", ", n.position());
1789-
push_need_expression_list_parens(false);
1790-
emit(*n.ops[1].expr_list);
1791-
pop_need_expression_list_parens();
1792-
}
1793-
printer.print_cpp2(")", n.position());
1794-
1795-
// And we're done. This path has handled this node, so return...
1796-
return;
1797-
}
1798-
17991728
// Otherwise, we're going to have to potentially do some work to change
18001729
// some Cpp2 postfix operators to Cpp1 prefix operators, so let's set up...
18011730
auto prefix = std::vector<text_with_pos>{};
@@ -1804,6 +1733,31 @@ class cppfront
18041733
auto last_was_prefixed = false;
18051734
auto saw_dollar = false;
18061735

1736+
struct text_chunks_with_parens_position {
1737+
std::vector<text_with_pos> text_chunks;
1738+
cpp2::source_position open_pos;
1739+
cpp2::source_position close_pos;
1740+
};
1741+
1742+
auto args = std::optional<text_chunks_with_parens_position>{};
1743+
1744+
auto print_to_string = [&](auto& i, auto... args) {
1745+
auto print = std::string{};
1746+
printer.emit_to_string(&print);
1747+
emit(i, args...);
1748+
printer.emit_to_string();
1749+
return print;
1750+
};
1751+
auto print_to_text_chunks = [&](auto& i, auto... args) {
1752+
auto text = std::vector<text_with_pos>{};
1753+
printer.emit_to_text_chunks(&text);
1754+
push_need_expression_list_parens(false);
1755+
emit(i, args...);
1756+
pop_need_expression_list_parens();
1757+
printer.emit_to_text_chunks();
1758+
return text;
1759+
};
1760+
18071761
for (auto i = n.ops.rbegin(); i != n.ops.rend(); ++i)
18081762
{
18091763
assert(i->op);
@@ -1827,9 +1781,64 @@ class cppfront
18271781
}
18281782
}
18291783

1784+
// Going backwards if we found LeftParen it might be UFCS
1785+
// expr_list is emited to args variable for future use
1786+
if (i->op->type() == lexeme::LeftParen) {
1787+
1788+
assert(i->op);
1789+
assert(i->op_close);
1790+
auto local_args = text_chunks_with_parens_position{{}, i->op->position(), i->op_close->position()};
1791+
1792+
if (!i->expr_list->expressions.empty()) {
1793+
local_args.text_chunks = print_to_text_chunks(*i->expr_list);
1794+
}
1795+
1796+
args.emplace(std::move(local_args));
1797+
}
1798+
// Going backwards if we found Dot and there is args variable
1799+
// it means that it should be handled by UFCS
1800+
else if( i->op->type() == lexeme::Dot && args )
1801+
{
1802+
auto funcname = print_to_string(*i->id_expr);
1803+
1804+
//--------------------------------------------------------------------
1805+
// TODO: When MSVC supports __VA_OPT__ in standard mode without the
1806+
// experimental /Zc:preprocessor switch, use this single line
1807+
// instead of the dual lines below that special-case _0 args
1808+
// AND: Make the similarly noted change in cpp2util.h
1809+
//
1810+
//printer.print_cpp2("CPP2_UFCS(", n.position());
1811+
1812+
auto ufcs_string = std::string("CPP2_UFCS");
1813+
1814+
if (i->id_expr->template_args_count() > 0) {
1815+
ufcs_string += "_TEMPLATE";
1816+
// we need to replace "fun<int,long,double>" to "fun, (<int,long,double>)" to be able to generate
1817+
// from obj.fun<int, long, double>(1,2) this CPP2_UFCS_TEMPLATE(fun, (<int,long, double>), obj, 1, 2)
1818+
auto split = funcname.find('<'); assert(split != std::string::npos);
1819+
funcname.insert(split, ", (");
1820+
assert(funcname.back() == '>');
1821+
funcname += ')';
1822+
}
1823+
// If there are no additional arguments, use the _0 version
1824+
if (args.value().text_chunks.empty()) {
1825+
ufcs_string += "_0";
1826+
}
1827+
1828+
prefix.emplace_back(ufcs_string + "(" + funcname + ", ", i->op->position() );
1829+
suffix.emplace_back(")", args.value().close_pos );
1830+
if (!args.value().text_chunks.empty()) {
1831+
for (auto&& e: args.value().text_chunks) {
1832+
suffix.push_back(e);
1833+
}
1834+
suffix.emplace_back(", ", i->op->position());
1835+
}
1836+
args.reset();
1837+
}
1838+
18301839
// Handle the Cpp2 postfix operators that are prefix in Cpp1
18311840
//
1832-
if (i->op->type() == lexeme::MinusMinus ||
1841+
else if (i->op->type() == lexeme::MinusMinus ||
18331842
i->op->type() == lexeme::PlusPlus ||
18341843
i->op->type() == lexeme::Multiply ||
18351844
i->op->type() == lexeme::Ampersand ||
@@ -1873,21 +1882,25 @@ class cppfront
18731882
}
18741883

18751884
if (i->id_expr) {
1876-
auto print = std::string{};
1877-
printer.emit_to_string(&print);
1878-
emit(*i->id_expr, false /*not a local name*/);
1879-
printer.emit_to_string();
1885+
1886+
if (args) {
1887+
// if args are stored it means that this is function or method
1888+
// that is not handled by UFCS and args need to be printed
1889+
suffix.emplace_back(")", args.value().close_pos);
1890+
for (auto&& e: args.value().text_chunks) {
1891+
suffix.push_back(e);
1892+
}
1893+
suffix.emplace_back("(", args.value().open_pos);
1894+
args.reset();
1895+
}
1896+
1897+
auto print = print_to_string(*i->id_expr, false /*not a local name*/);
18801898
suffix.emplace_back( print, i->id_expr->position() );
18811899
}
18821900

18831901
if (i->expr_list) {
1884-
auto text = std::vector<text_with_pos>{};
1885-
printer.emit_to_text_chunks(&text);
1886-
push_need_expression_list_parens(false);
1887-
emit(*i->expr_list);
1888-
pop_need_expression_list_parens();
1889-
printer.emit_to_text_chunks();
1890-
for (auto&& e: text) {
1902+
auto text = print_to_text_chunks(*i->expr_list);
1903+
for (auto&& e: text) {
18911904
suffix.push_back(e);
18921905
}
18931906
}
@@ -1903,6 +1916,8 @@ class cppfront
19031916
}
19041917
}
19051918

1919+
1920+
19061921
// Print the prefixes (in forward order)
19071922
for (auto& e : prefix) {
19081923
printer.print_cpp2(e.text, n.position());
@@ -1927,6 +1942,18 @@ class cppfront
19271942
}
19281943
suppress_move_from_last_use = false;
19291944

1945+
if (args) {
1946+
// if after printing core expression args is defined
1947+
// it means that the chaining started by function call
1948+
// we need to print its arguments
1949+
suffix.emplace_back(")", args.value().close_pos);
1950+
for (auto&& e: args.value().text_chunks) {
1951+
suffix.push_back(e);
1952+
}
1953+
suffix.emplace_back("(", args.value().open_pos);
1954+
args.reset();
1955+
}
1956+
19301957
// Print the suffixes (in reverse order)
19311958
while (!suffix.empty()) {
19321959
printer.print_cpp2(suffix.back().text, suffix.back().pos);

0 commit comments

Comments
 (0)