diff --git a/regression-tests/mixed-ufcs-method-chaining.cpp2 b/regression-tests/mixed-ufcs-method-chaining.cpp2 new file mode 100644 index 000000000..cced86715 --- /dev/null +++ b/regression-tests/mixed-ufcs-method-chaining.cpp2 @@ -0,0 +1,28 @@ +#include + +add: (in v : _, in a : int) -> auto = { + cv := v; + for cv do :(inout e : _) = { + e += a; + } + return cv; +} + +dbl: (in v : _) -> auto = { + cv := v; + for cv do :(inout e : _) = { + e *= 2; + } + return cv; +} + +main: () -> int = { + + v : std::vector = (1,2,3,4,5,6,7,8,9,10); + + for v.dbl().add(1).dbl() do :(in e : _) = { + std::cout << e << ", "; + } + + std::cout << std::endl; +} diff --git a/source/cppfront.cpp b/source/cppfront.cpp index 523613a50..720a8585a 100644 --- a/source/cppfront.cpp +++ b/source/cppfront.cpp @@ -1667,7 +1667,7 @@ class cppfront captured_part += "_" + std::to_string(mynum); } - // Check to see if it's just a function call with "." syntax, + // Check to see if it's just a function call with "." syntax (potentially chained with other methods), // and if so use this path to convert it to UFCS if (// there's a single-token expression followed by . and ( n.expr->get_token() && // if the base expression is a single token @@ -1675,9 +1675,11 @@ class cppfront n.ops[0].op->type() == lexeme::Dot && // token . id-expr ( expr-list ) n.ops[1].op->type() == lexeme::LeftParen && // and either there's nothing after that, or there's just a $ after that + // or there is mathod chaining started ( std::ssize(n.ops) == 2 || - (std::ssize(n.ops) == 3 && n.ops[2].op->type() == lexeme::Dollar) + (std::ssize(n.ops) == 3 && n.ops[2].op->type() == lexeme::Dollar) || + (std::ssize(n.ops) > 3 && n.ops[2].op->type() == lexeme::Dot) // fsajdak: chaining identification ) ) { @@ -1690,44 +1692,60 @@ class cppfront // Otherwise, do the UFCS work... - // The . has its id_expr - assert (n.ops[0].id_expr); - - // The ( has its expr_list and op_close - assert (n.ops[1].expr_list && n.ops[1].op_close); - - //-------------------------------------------------------------------- - // TODO: When MSVC supports __VA_OPT__ in standard mode without the - // experimental /Zc:preprocessor switch, use this single line - // instead of the dual lines below that special-case _0 args - // AND: Make the similarly noted change in cpp2util.h - // - //printer.print_cpp2("CPP2_UFCS(", n.position()); + auto find_id_of_a_last = [](auto& ops, auto op){ + for (int i = std::ssize(ops)-1; i>0; --i) { + if (ops[i].op->type() == op) + return i; + } + return 0; + }; - // If there are no additional arguments, use the CPP2_UFCS_0 version - if (!n.ops[1].expr_list->expressions.empty()) { - printer.print_cpp2("CPP2_UFCS(", n.position()); - } - else { - printer.print_cpp2("CPP2_UFCS_0(", n.position()); + // If method are chained we need to go from the last to the first + // token.a(a-expr-list).b(b-expr-list).c(c-expr-list) will be tranformed to: + // CPP2_UFCS(c, CPP2_UFCS(b, CPP2_UFCS(a,token, a-expr-list), b-expr-list), c-expr-list ) + for (auto i = find_id_of_a_last(n.ops, lexeme::LeftParen); i > 0; i -= 2) + { + // The . has its id_expr + assert (n.ops[i-1].id_expr); + + // The ( has its expr_list and op_close + assert (n.ops[i].expr_list && n.ops[i].op_close); + + //-------------------------------------------------------------------- + // TODO: When MSVC supports __VA_OPT__ in standard mode without the + // experimental /Zc:preprocessor switch, use this single line + // instead of the dual lines below that special-case _0 args + // AND: Make the similarly noted change in cpp2util.h + // + //printer.print_cpp2("CPP2_UFCS(", n.position()); + + // If there are no additional arguments, use the CPP2_UFCS_0 version + if (!n.ops[i].expr_list->expressions.empty()) { + printer.print_cpp2("CPP2_UFCS(", n.position()); + } + else { + printer.print_cpp2("CPP2_UFCS_0(", n.position()); + } + emit(*n.ops[i-1].id_expr); + printer.print_cpp2(", ", n.position()); } - //-------------------------------------------------------------------- - // Make the "funcname" the first argument to CPP2_UFCS - emit(*n.ops[0].id_expr); - printer.print_cpp2(", ", n.position()); - - // Then make the base expression the second argument - emit(*n.expr); + // expr-list need to be added in reversed order then CPP2_UFCS macros + for (auto i = 0; i < find_id_of_a_last(n.ops, lexeme::LeftParen); i += 2) { + // Then make the base expression the second argument - only needed on the most nested call + if (i == 0) { + emit(*n.expr); + } - // Then tack on any additional arguments - if (!n.ops[1].expr_list->expressions.empty()) { - printer.print_cpp2(", ", n.position()); - push_need_expression_list_parens(false); - emit(*n.ops[1].expr_list); - pop_need_expression_list_parens(); + // Then tack on any additional arguments + if (!n.ops[(i+1)].expr_list->expressions.empty()) { + printer.print_cpp2(", ", n.position()); + push_need_expression_list_parens(false); + emit(*n.ops[(i+1)].expr_list); + pop_need_expression_list_parens(); + } + printer.print_cpp2(")", n.position()); } - printer.print_cpp2(")", n.position()); // And we're done. This path has handled this node, so return... return;