diff --git a/src/class_decl.c b/src/class_decl.c index 4e16b53c9f..dd6990113f 100644 --- a/src/class_decl.c +++ b/src/class_decl.c @@ -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. @@ -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 && @@ -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, diff --git a/src/class_decl.h b/src/class_decl.h index efa2e18c96..aaf33e1133 100644 --- a/src/class_decl.h +++ b/src/class_decl.h @@ -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, diff --git a/src/declarator.c b/src/declarator.c index 8054f3a87a..01d1f4d56c 100644 --- a/src/declarator.c +++ b/src/declarator.c @@ -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 && diff --git a/src/pending_changes/gh-470-init-capture-lambda-header.txt b/src/pending_changes/gh-470-init-capture-lambda-header.txt new file mode 100644 index 0000000000..a87a307fae --- /dev/null +++ b/src/pending_changes/gh-470-init-capture-lambda-header.txt @@ -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. + } diff --git a/tests/tests/regressions/.gh-470.rto/default.1.1.txt b/tests/tests/regressions/.gh-470.rto/default.1.1.txt new file mode 100644 index 0000000000..b12a665463 --- /dev/null +++ b/tests/tests/regressions/.gh-470.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --c++20 -DHIDDEN=0 Test_name.c diff --git a/tests/tests/regressions/.gh-470.rto/default.2.1.txt b/tests/tests/regressions/.gh-470.rto/default.2.1.txt new file mode 100644 index 0000000000..7b4e359620 --- /dev/null +++ b/tests/tests/regressions/.gh-470.rto/default.2.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=2 --c++20 --g++ --gnu_version=130300 -DHIDDEN=1 Test_name.c diff --git a/tests/tests/regressions/.gh-470.rto/default.3.1.txt b/tests/tests/regressions/.gh-470.rto/default.3.1.txt new file mode 100644 index 0000000000..837840b8ed --- /dev/null +++ b/tests/tests/regressions/.gh-470.rto/default.3.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=3 --c++20 --g++ --gnu_version=150200 -DHIDDEN=1 Test_name.c diff --git a/tests/tests/regressions/.gh-470.rto/default.4.1.txt b/tests/tests/regressions/.gh-470.rto/default.4.1.txt new file mode 100644 index 0000000000..ef822f2e3c --- /dev/null +++ b/tests/tests/regressions/.gh-470.rto/default.4.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=4 --c++20 --g++ --gnu_version=160100 -DHIDDEN=0 Test_name.c diff --git a/tests/tests/regressions/.gh-470.rto/default.5.1.txt b/tests/tests/regressions/.gh-470.rto/default.5.1.txt new file mode 100644 index 0000000000..90a159a51b --- /dev/null +++ b/tests/tests/regressions/.gh-470.rto/default.5.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=5 --c++20 --clang --clang_version=160000 -DHIDDEN=1 Test_name.c diff --git a/tests/tests/regressions/.gh-470.rto/default.6.1.txt b/tests/tests/regressions/.gh-470.rto/default.6.1.txt new file mode 100644 index 0000000000..9b0028aa6c --- /dev/null +++ b/tests/tests/regressions/.gh-470.rto/default.6.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=6 --c++20 --clang --clang_version=170000 -DHIDDEN=0 Test_name.c diff --git a/tests/tests/regressions/.gh-470.rto/default.7.1.txt b/tests/tests/regressions/.gh-470.rto/default.7.1.txt new file mode 100644 index 0000000000..fd3ac5108e --- /dev/null +++ b/tests/tests/regressions/.gh-470.rto/default.7.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=7 --ms_c++20 --microsoft_version=1944 -DHIDDEN=1 Test_name.c diff --git a/tests/tests/regressions/.gh-470.rto/default.8.1.txt b/tests/tests/regressions/.gh-470.rto/default.8.1.txt new file mode 100644 index 0000000000..6b1caa0cf8 --- /dev/null +++ b/tests/tests/regressions/.gh-470.rto/default.8.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=8 --ms_c++20 --microsoft_version=1950 -DHIDDEN=0 Test_name.c diff --git a/tests/tests/regressions/.gh-470.rto/default.9.1.txt b/tests/tests/regressions/.gh-470.rto/default.9.1.txt new file mode 100644 index 0000000000..24859b66ee --- /dev/null +++ b/tests/tests/regressions/.gh-470.rto/default.9.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=9 --ms_c++17 --microsoft_version=1950 -DHIDDEN=1 -DGHIDDEN=0 -DCPP17 Test_name.c diff --git a/tests/tests/regressions/gh-470.sft.cpp b/tests/tests/regressions/gh-470.sft.cpp new file mode 100644 index 0000000000..12032a8562 --- /dev/null +++ b/tests/tests/regressions/gh-470.sft.cpp @@ -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 constexpr bool is_same = false; +template constexpr bool is_same = 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) { + return x; + }; + static_assert(noexcept(l1()) == HIDDEN); + UNUSED auto l2 = [x = 10]() -> decltype(x) { return {}; }; + static_assert(is_same); + UNUSED auto l3 = [x = 10](decltype(x) p) { return p; }; + static_assert(is_same); +#if HIDDEN + UNUSED auto l4 = [x = 10]() -> decltype((x)) { static X r{}; return r; }; + static_assert(is_same); +#endif + // The init-capture is visible in the lambda body in all modes. + UNUSED auto l5 = [x = 10]() { return x; }; + static_assert(is_same); + // 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); + UNUSED auto l8 = [x = 10](auto) -> decltype(x) { return {}; }; + static_assert(is_same); +#ifndef CPP17 + UNUSED auto l9 = [x = 10](T) -> decltype(x) { return {}; }; + static_assert(is_same); +#endif +} + +void no_enclosing_variable() { +#if !HIDDEN + UNUSED auto l = [y = 10]() -> decltype(y) { return y; }; + static_assert(is_same); +#endif +} + +template void in_template() { + UNUSED X x = {2, 3}; + UNUSED auto l1 = [x = T(10)]() noexcept(is_same) { + return x; + }; + static_assert(noexcept(l1()) == HIDDEN); + UNUSED auto l2 = [x = T(10)](auto) -> decltype(x) { return {}; }; + static_assert(is_same); +} +template void in_template(); + +#if HIDDEN && !defined(CPP17) +template void init_capture_pack(T... t) { + UNUSED X x = {2, 3}; + UNUSED auto l = [...x = t]() noexcept(is_same) { + return sizeof...(x); + }; + static_assert(noexcept(l())); +} +template void init_capture_pack(int, int); +#endif