Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/class_decl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2385,6 +2385,47 @@ lambdas.
} /* field_for_lambda_capture */


static void set_init_capture_visibility_if_needed(a_type_ptr closure_class,
a_boolean visible)
/*
P2579 makes init-captures visible throughout the lambda header. GCC before
version 16, Clang before version 17, and MSVC (except version 19.50 and later
in C++20 and later modes) make them visible only in the lambda body: In those
modes, make the fields for the init-captures of the given closure class
invisible to lookup if visible is FALSE (when starting to scan the lambda
header), and visible again if visible is TRUE (when done scanning it).
*/
{
if (gpp_version_is(<160000) || clang_version_is(<170000) ||
ms_version_is(<1950) || (microsoft_mode && !cpp20_mode)) {
a_field_ptr fp;
for (fp = fields_of(closure_class); fp != NULL; fp = fp->next) {
if (fp->is_init_capture) {
a_symbol_ptr sym = symbol_for(fp);
if (sym != NULL) sym->is_invisible = !visible;
} /* if */
} /* for */
} /* if */
} /* set_init_capture_visibility_if_needed */


void end_of_generic_lambda_params(a_type_ptr closure_class)
/*
Called right after the parameter list of the call operator of a generic lambda
has been scanned (when the lambda is first scanned, and when that call operator
is instantiated). closure_class is the closure class of that lambda. In
pre-C++20 modes, MSVC finds init-captures in the remainder of the header of a
generic lambda (its exception specification and trailing return type), though
not in its parameter types. Emulate that by making the init-captures visible
again.
*/
{
if (microsoft_mode && !cpp20_mode) {
set_init_capture_visibility_if_needed(closure_class, /*visible=*/TRUE);
} /* if */
} /* end_of_generic_lambda_params */


static a_lambda_capture_ptr alloc_capture_for_lambda(a_lambda_ptr lambda)
/*
Allocate and initialize a lambda capture entry and return a pointer to it.
Expand Down Expand Up @@ -30343,8 +30384,10 @@ flag if error recovery should be performed as if the specifier didn't occur.
} else if (dps->is_lambda) {
/* A generic lambda declarator rescan. */
check_assertion(is_member_template_rescan);
set_init_capture_visibility_if_needed(class_type, /*visible=*/FALSE);
scan_lambda_declarator(dps, func_info, (a_tmpl_decl_state*)NULL,
&decl_info->decl_pos_block);
set_init_capture_visibility_if_needed(class_type, /*visible=*/TRUE);
*is_function = dps->type->kind == (a_type_kind)tk_routine;
} else if (no_decl_specifiers && !decl_info->is_constructor &&
!decl_info->is_destructor &&
Expand Down Expand Up @@ -35952,8 +35995,10 @@ For example:
push_closure_class(lambda, &class_state);
decl_lambda_capture_fields(lambda, /*init_captures=*/TRUE);
/* Parse the "declarator" part of the lambda (the parameter list, etc.). */
set_init_capture_visibility_if_needed(closure_class, /*visible=*/FALSE);
scan_optional_lambda_declarator(lambda, &func_info, &decl_info,
&templ_state);
set_init_capture_visibility_if_needed(closure_class, /*visible=*/TRUE);
record_end_of_lambda_header(lambda);
/* Declare the call operator for the closure class. */
decl_call_operator_for_lambda(lambda, &class_state, &decl_info, &func_info,
Expand Down
2 changes: 2 additions & 0 deletions src/class_decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ extern a_lambda_capture_ptr find_lambda_capture(a_lambda_ptr lambda,
extern a_field_ptr field_for_lambda_capture(a_lambda_ptr lambda,
a_lambda_capture_ptr lcp);

extern void end_of_generic_lambda_params(a_type_ptr closure_class);

extern a_lambda_capture_ptr lambda_capture_for_variable(
a_variable_ptr vp,
a_source_position_ptr pos,
Expand Down
6 changes: 6 additions & 0 deletions src/declarator.c
Original file line number Diff line number Diff line change
Expand Up @@ -4473,6 +4473,12 @@ an error if a default argument expression is encountered.
}
remove_stop_token(tok_rparen);
scope_stack_top().outside_parameter_list = TRUE;
if (state->is_lambda && parent_type != NULL && func_info != NULL &&
(state->is_abbr_func_template || func_info->lambda == NULL)) {
/* The parameter list of a generic lambda's call operator was scanned
(func_info->lambda is NULL when rescanning that call operator). */
end_of_generic_lambda_params(parent_type);
} /* if */
#if MICROSOFT_EXTENSIONS_ALLOWED
if (ms_extensions) {
a_boolean managed_member = cli_or_cx_enabled && parent_type != NULL &&
Expand Down
20 changes: 20 additions & 0 deletions src/pending_changes/gh-470-init-capture-lambda-header.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[GH #470]
GNU, Clang, and Microsoft compatibility: Init-captures in lambda headers

P2579 (adopted as a defect report) makes init-captures visible throughout a
lambda header, and the front end implemented that in all modes. However, GCC
before version 16, Clang before version 17, and MSVC before version 19.50 (or
in pre-C++20 modes) make init-captures visible only in the lambda body: In
the lambda header (e.g., in a parameter type, an exception specification, or
a trailing return type), the name of an init-capture refers to an entity of
the enclosing scope instead. (In pre-C++20 modes, MSVC does make
init-captures visible in the exception specification and trailing return type
of a generic lambda.) The front end now emulates that in the corresponding
modes. For example, with --g++ --gnu_version=130300 --c++14:

struct X { int value; };
void f() {
X x = {2};
auto lm = [x = 10]() -> decltype(x) { return {}; };
// Previously the return type was int. Now it is X.
}
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-470.rto/default.1.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=1 --c++20 -DHIDDEN=0 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-470.rto/default.2.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=2 --c++20 --g++ --gnu_version=130300 -DHIDDEN=1 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-470.rto/default.3.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=3 --c++20 --g++ --gnu_version=150200 -DHIDDEN=1 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-470.rto/default.4.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=4 --c++20 --g++ --gnu_version=160100 -DHIDDEN=0 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-470.rto/default.5.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=5 --c++20 --clang --clang_version=160000 -DHIDDEN=1 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-470.rto/default.6.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=6 --c++20 --clang --clang_version=170000 -DHIDDEN=0 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-470.rto/default.7.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=7 --ms_c++20 --microsoft_version=1944 -DHIDDEN=1 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-470.rto/default.8.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=8 --ms_c++20 --microsoft_version=1950 -DHIDDEN=0 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-470.rto/default.9.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=9 --ms_c++17 --microsoft_version=1950 -DHIDDEN=1 -DGHIDDEN=0 -DCPP17 Test_name.c
100 changes: 100 additions & 0 deletions tests/tests/regressions/gh-470.sft.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//type:fp
//options:--c++20 -DHIDDEN=0:--c++20 --g++ --gnu_version=130300 -DHIDDEN=1:--c++20 --g++ --gnu_version=150200 -DHIDDEN=1:--c++20 --g++ --gnu_version=160100 -DHIDDEN=0:--c++20 --clang --clang_version=160000 -DHIDDEN=1:--c++20 --clang --clang_version=170000 -DHIDDEN=0:--ms_c++20 --microsoft_version=1944 -DHIDDEN=1:--ms_c++20 --microsoft_version=1950 -DHIDDEN=0:--ms_c++17 --microsoft_version=1950 -DHIDDEN=1 -DGHIDDEN=0 -DCPP17
//remark:visibility of init-captures in lambda headers in emulation modes

// P2579 makes init-captures visible throughout the lambda header (and not
// just in the lambda body). GCC before version 16, Clang before version 17,
// and MSVC before version 19.50 (or in pre-C++20 modes) do not implement
// that: In the lambda header, a name of an init-capture refers to an entity of
// the enclosing scope instead. The front end previously made init-captures
// visible in lambda headers in all modes. HIDDEN is 1 for the modes in which
// init-captures are not visible in lambda headers. GHIDDEN is the same for
// the exception specification and trailing return type of generic lambdas,
// except that in pre-C++20 modes, MSVC makes init-captures visible there.

#ifndef GHIDDEN
#define GHIDDEN HIDDEN
#endif

template <typename T, typename U> constexpr bool is_same = false;
template <typename T> constexpr bool is_same<T, T> = true;

struct X {
int value, other;
};

// The type of "x" in the header of a lambda with the init-capture "x = 10"
// when there is also a variable "X x" in the enclosing scope.
#if HIDDEN
using HdrT = X;
#else
using HdrT = int;
#endif
#if GHIDDEN
using GenHdrT = X;
#else
using GenHdrT = int;
#endif

#define UNUSED [[maybe_unused]]

void f() {
UNUSED X x = {2, 3};
UNUSED auto l1 = [x = 10]() noexcept(is_same<decltype(x), X>) {
return x;
};
static_assert(noexcept(l1()) == HIDDEN);
UNUSED auto l2 = [x = 10]() -> decltype(x) { return {}; };
static_assert(is_same<decltype(l2()), HdrT>);
UNUSED auto l3 = [x = 10](decltype(x) p) { return p; };
static_assert(is_same<decltype(l3(HdrT{})), HdrT>);
#if HIDDEN
UNUSED auto l4 = [x = 10]() -> decltype((x)) { static X r{}; return r; };
static_assert(is_same<decltype(l4()), X&>);
#endif
// The init-capture is visible in the lambda body in all modes.
UNUSED auto l5 = [x = 10]() { return x; };
static_assert(is_same<decltype(l5()), int>);
// Generic lambdas.
UNUSED auto l6 = [x = 10](auto) noexcept(sizeof(x) == sizeof(X)) {
return x;
};
static_assert(noexcept(l6(1)) == GHIDDEN);
UNUSED auto l7 = [x = 10](auto, decltype(x) p) { return p; };
static_assert(is_same<decltype(l7(1, HdrT{})), HdrT>);
UNUSED auto l8 = [x = 10](auto) -> decltype(x) { return {}; };
static_assert(is_same<decltype(l8(1)), GenHdrT>);
#ifndef CPP17
UNUSED auto l9 = [x = 10]<class T>(T) -> decltype(x) { return {}; };
static_assert(is_same<decltype(l9(1)), HdrT>);
#endif
}

void no_enclosing_variable() {
#if !HIDDEN
UNUSED auto l = [y = 10]() -> decltype(y) { return y; };
static_assert(is_same<decltype(l()), int>);
#endif
}

template <class T> void in_template() {
UNUSED X x = {2, 3};
UNUSED auto l1 = [x = T(10)]() noexcept(is_same<decltype(x), X>) {
return x;
};
static_assert(noexcept(l1()) == HIDDEN);
UNUSED auto l2 = [x = T(10)](auto) -> decltype(x) { return {}; };
static_assert(is_same<decltype(l2(1)), GenHdrT>);
}
template void in_template<int>();

#if HIDDEN && !defined(CPP17)
template <class... T> void init_capture_pack(T... t) {
UNUSED X x = {2, 3};
UNUSED auto l = [...x = t]() noexcept(is_same<decltype(x), X>) {
return sizeof...(x);
};
static_assert(noexcept(l()));
}
template void init_capture_pack(int, int);
#endif
Loading