Skip to content

Commit f9ca050

Browse files
committed
Fix generation when operator= nests a lambda, and work around Clang and MSVC bug, addresses hsutter#281
Also fix the logic in the `is_constructor_*` and `is_assignment_*` functions
1 parent de25e0e commit f9ca050

File tree

3 files changed

+189
-98
lines changed

3 files changed

+189
-98
lines changed

include/cpp2util.h

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -630,8 +630,25 @@ class out {
630630
#define CPP2_FORCE_INLINE_LAMBDA __attribute__((always_inline))
631631
#endif
632632

633+
634+
// === NOTE: (see also corresponding note in cppfront.cpp)
635+
//
636+
// This "&" capture default is to work around 'if constexpr' bugs in Clang and MSVC...
637+
// If we don't emit a not-actually-used "[&]" here, then both compilers get tripped up
638+
// if we attempt UFCS for a member function name... the reason is that the UFCS macro
639+
// generates a lambda that does an 'if constexpr' to call the member function if
640+
// available, but Clang and MSVC look into the NOT-taken 'if constexpr' branch (which
641+
// they shouldn't) and see the nonmember function call syntax and think it's trying to
642+
// use the member function with implicit 'this->' and choke... (see issue #281, and
643+
// https://godbolt.org/z/M47zzMsoT for a distilled repro)
644+
//
645+
// The workaround that seems to be effective is to add TWO actually-unused "&" captures:
646+
// - here, and
647+
// - and also in the cppfront.cpp build_capture_lambda_intro_for() code
648+
//
649+
633650
#define CPP2_UFCS(FUNCNAME,PARAM1,...) \
634-
[](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
651+
[&](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
635652
if constexpr (requires{ std::forward<decltype(obj)>(obj).FUNCNAME(std::forward<decltype(params)>(params)...); }) { \
636653
return std::forward<decltype(obj)>(obj).FUNCNAME(std::forward<decltype(params)>(params)...); \
637654
} else { \
@@ -640,7 +657,7 @@ class out {
640657
}(PARAM1, __VA_ARGS__)
641658

642659
#define CPP2_UFCS_0(FUNCNAME,PARAM1) \
643-
[](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
660+
[&](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
644661
if constexpr (requires{ std::forward<decltype(obj)>(obj).FUNCNAME(); }) { \
645662
return std::forward<decltype(obj)>(obj).FUNCNAME(); \
646663
} else { \
@@ -651,7 +668,7 @@ class out {
651668
#define CPP2_UFCS_REMPARENS(...) __VA_ARGS__
652669

653670
#define CPP2_UFCS_TEMPLATE(FUNCNAME,TEMPARGS,PARAM1,...) \
654-
[](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
671+
[&](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
655672
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); }) { \
656673
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); \
657674
} else { \
@@ -660,14 +677,13 @@ class out {
660677
}(PARAM1, __VA_ARGS__)
661678

662679
#define CPP2_UFCS_TEMPLATE_0(FUNCNAME,TEMPARGS,PARAM1) \
663-
[](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
680+
[&](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
664681
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
665682
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
666683
} else { \
667684
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj)); \
668685
} \
669686
}(PARAM1)
670-
//--------------------------------------------------------------------
671687

672688

673689
//-----------------------------------------------------------------------

source/cppfront.cpp

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -835,11 +835,11 @@ class cppfront
835835
bool violates_initialization_safety = false;
836836
bool suppress_move_from_last_use = false;
837837

838-
bool generating_assignment_function = false;
839-
bool generating_move_function = false;
840-
bool emitting_that_function = false;
841-
bool emitting_move_that_function = false;
842-
std::vector<token const*> already_moved_that_members = {};
838+
declaration_node const* generating_assignment_from = {};
839+
declaration_node const* generating_move_from = {};
840+
bool emitting_that_function = false;
841+
bool emitting_move_that_function = false;
842+
std::vector<token const*> already_moved_that_members = {};
843843

844844
struct arg_info {
845845
passing_style pass = passing_style::in;
@@ -848,10 +848,18 @@ class cppfront
848848
std::vector<arg_info> current_args = { {} };
849849

850850
struct function_info {
851-
token const* pname = {};
851+
declaration_node const* decl = {};
852852
declaration_node::declared_that_funcs declared_that_functions = {};
853853
function_prolog prolog = {};
854854
std::vector<std::string> epilog = {};
855+
856+
function_info(
857+
declaration_node const* decl_,
858+
declaration_node::declared_that_funcs declared_that_functions_
859+
)
860+
: decl{decl_}
861+
, declared_that_functions{declared_that_functions_}
862+
{ }
855863
};
856864
std::vector<function_info> current_function_info = { {} };
857865

@@ -1965,7 +1973,7 @@ class cppfront
19651973

19661974
// Return without expression, could be assignment operator
19671975
//
1968-
else if (generating_assignment_function)
1976+
else if (generating_assignment_from == current_function_info.back().decl)
19691977
{
19701978
printer.print_cpp2("*this", n.position());
19711979
}
@@ -2079,9 +2087,35 @@ class cppfront
20792087

20802088
// Then build the capture list, ignoring duplicated expressions
20812089
auto lambda_intro = std::string("[");
2090+
auto num_captures = 0;
2091+
2092+
// === NOTE: (see also corresponding note in cpp2util.h)
2093+
//
2094+
// This "&" capture default is to work around 'if constexpr' bugs in Clang and MSVC...
2095+
// If we don't emit a not-actually-used "[&]" here, then both compilers get tripped up
2096+
// if we attempt UFCS for a member function name... the reason is that the UFCS macro
2097+
// generates a lambda that does an 'if constexpr' to call the member function if
2098+
// available, but Clang and MSVC look into the NOT-taken 'if constexpr' branch (which
2099+
// they shouldn't) and see the nonmember function call syntax and think it's trying to
2100+
// use the member function with implicit 'this->' and choke... (see issue #281, and
2101+
// https://godbolt.org/z/M47zzMsoT for a distilled repro)
2102+
//
2103+
// The workaround that seems to be effective is to add TWO actually-unused "&" captures:
2104+
// - here, and
2105+
// - and also in the cpp2util.h UFCS code
2106+
//
2107+
if (
2108+
!current_function_info.empty()
2109+
&& current_function_info.back().decl->is_function_with_this()
2110+
)
2111+
{
2112+
lambda_intro += "&";
2113+
++num_captures;
2114+
}
2115+
// === END NOTE
2116+
20822117
printer.emit_to_string(&lambda_intro);
20832118

2084-
auto num = 0;
20852119
auto handled = std::vector<std::string>{};
20862120
for (auto& cap : captures.members)
20872121
{
@@ -2092,13 +2126,13 @@ class cppfront
20922126
handled.push_back(cap.str);
20932127

20942128
// And handle it
2095-
if (num != 0) { // not first
2129+
if (num_captures != 0) { // not first
20962130
lambda_intro += ", ";
20972131
}
2098-
cap.cap_sym = "_"+std::to_string(num);
2132+
cap.cap_sym = "_"+std::to_string(num_captures);
20992133
printer.print_cpp2(cap.cap_sym + " = " + cap.str, pos);
21002134
}
2101-
++num;
2135+
++num_captures;
21022136
}
21032137
printer.emit_to_string();
21042138
lambda_intro += "]";
@@ -3320,8 +3354,7 @@ class cppfront
33203354
{
33213355
if (
33223356
n.pass != passing_style::out
3323-
|| !current_function_info.back().pname
3324-
|| *current_function_info.back().pname != "operator="
3357+
|| !current_function_info.back().decl->has_name("operator=")
33253358
)
33263359
{
33273360
errors.emplace_back(
@@ -3725,7 +3758,7 @@ class cppfront
37253758
// Handle a special member function
37263759
if (
37273760
n.is_assignment()
3728-
|| generating_assignment_function
3761+
|| generating_assignment_from == n.my_decl
37293762
)
37303763
{
37313764
assert(
@@ -3914,7 +3947,7 @@ class cppfront
39143947
assert(func);
39153948

39163949
auto is_assignment =
3917-
generating_assignment_function
3950+
generating_assignment_from == &n
39183951
|| (*func->parameters)[0]->pass == passing_style::inout;
39193952

39203953
if (
@@ -3925,7 +3958,7 @@ class cppfront
39253958
emitting_that_function = true;
39263959
if (
39273960
(*func->parameters)[1]->pass == passing_style::move
3928-
|| generating_move_function
3961+
|| generating_move_from == &n
39293962
)
39303963
{
39313964
emitting_move_that_function = true;
@@ -4315,7 +4348,7 @@ class cppfront
43154348
assert(func);
43164349

43174350
current_function_info.emplace_back(
4318-
n.identifier ? n.name() : nullptr,
4351+
&n,
43194352
n.find_parent_declared_that_functions()
43204353
);
43214354
auto guard = finally([&]{ current_function_info.pop_back(); });
@@ -4381,7 +4414,7 @@ class cppfront
43814414
if (
43824415
func->is_constructor()
43834416
&& func->parameters->ssize() == 2
4384-
&& !generating_assignment_function
4417+
&& generating_assignment_from != &n
43854418
)
43864419
{
43874420
prefix += "explicit ";
@@ -4427,7 +4460,7 @@ class cppfront
44274460
// so recurse to emit related functions if the user didn't write them by hand
44284461
if (
44294462
func->parameters->ssize() == 2
4430-
&& !generating_assignment_function
4463+
&& generating_assignment_from != &n
44314464
)
44324465
{
44334466
assert(!current_function_info.empty());
@@ -4469,7 +4502,7 @@ class cppfront
44694502
need_to_generate_assignment = true;
44704503
}
44714504

4472-
if (!generating_move_function) {
4505+
if (generating_move_from != &n) {
44734506

44744507
// M) Generate (M)ove from copy,
44754508
// if the user didn't write the move function themselves
@@ -4721,9 +4754,9 @@ class cppfront
47214754

47224755
// Then reposition and do the recursive call
47234756
printer.reset_cpp2_line_to(n.position().lineno);
4724-
generating_assignment_function = true;
4757+
generating_assignment_from = &n;
47254758
emit( n, capture_intro );
4726-
generating_assignment_function = false;
4759+
generating_assignment_from = {};
47274760
}
47284761

47294762
// If this was a constructor and we want also want to emit
@@ -4737,9 +4770,9 @@ class cppfront
47374770

47384771
// Then reposition and do the recursive call
47394772
printer.reset_cpp2_line_to(n.position().lineno);
4740-
generating_move_function = true;
4773+
generating_move_from = &n;
47414774
emit( n, capture_intro );
4742-
generating_move_function = false;
4775+
generating_move_from = {};
47434776
}
47444777
}
47454778

0 commit comments

Comments
 (0)