From: Patrick Palka <ppalka@redhat.com>
To: Patrick Palka <ppalka@redhat.com>
Cc: gcc-patches@gcc.gnu.org, jason@redhat.com
Subject: Re: [PATCH] c++: templated substitution into lambda-expr [PR114393]
Date: Wed, 27 Mar 2024 10:01:37 -0400 (EDT) [thread overview]
Message-ID: <080968c1-af41-3597-f980-667f41ac98a2@idea> (raw)
In-Reply-To: <5d1bdce4-91e7-7a80-8459-228bb0b65809@idea>
On Mon, 25 Mar 2024, Patrick Palka wrote:
> On Mon, 25 Mar 2024, Patrick Palka wrote:
>
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
> > for trunk?
> >
> > -- >8 --
> >
> > The below testcases use a lambda-expr as a template argument and they
> > all trip over the below added tsubst_lambda_expr sanity check ultimately
> > because current_template_parms is empty, which causes push_template_decl
> > to return error_mark_node from the call to begin_lambda_type. Were it
> > not for the sanity check this silent error_mark_node result leads to
> > nonsensical errors down the line, or silent breakage.
> >
> > In the first testcase, we hit this assert during instantiation of the
> > dependent alias template-id c1_t<_Data> from instantiate_template, which
> > clears current_template_parms via push_to_top_level. Similar story for
> > the second testcase. For the third testcase we hit the assert during
> > partial instantiation of the member template from instantiate_class_template
> > which similarly calls push_to_top_level.
> >
> > These testcases illustrate that templated substitution into a lambda-expr
> > is not always possible, in particular when we lost the relevant template
> > context. I experimented with recovering the template context by making
> > tsubst_lambda_expr fall back to using scope_chain->prev->template_parms if
> > current_template_parms is empty which worked but seemed like a hack. I
> > also experimented with preserving the template context by keeping
> > current_template_parms set during instantiate_template for a dependent
> > specialization which also worked but it's at odds with the fact that we
> > cache dependent specializations (and so they should be independent of
> > the template context).
> >
> > So instead of trying to make such substitution work, this patch uses the
> > extra-args mechanism to defer templated substitution into a lambda-expr
> > when we lost the relevant template context.
> >
> > PR c++/114393
> > PR c++/107457
> > PR c++/93595
> >
> > gcc/cp/ChangeLog:
> >
> > * cp-tree.h (LAMBDA_EXPR_EXTRA_ARGS):
> > (struct GTY):
> > * module.cc (trees_out::core_vals) <case LAMBDA_EXPR>: Stream
> > LAMBDA_EXPR_EXTRA_ARGS.
> > (trees_in::core_vals) <case LAMBDA_EXPR>: Likewise.
> > * pt.cc (has_extra_args_mechanism_p):
> > (tsubst_lambda_expr):
>
> Whoops, this version of the patch has an incomplete ChangeLog entry.
>
> >
> > gcc/testsuite/ChangeLog:
> >
> > * g++.dg/cpp2a/lambda-targ2.C: New test.
> > * g++.dg/cpp2a/lambda-targ3.C: New test.
> > * g++.dg/cpp2a/lambda-targ4.C: New test.
> > ---
> > gcc/cp/cp-tree.h | 5 +++++
> > gcc/cp/module.cc | 2 ++
> > gcc/cp/pt.cc | 20 ++++++++++++++++++--
> > gcc/testsuite/g++.dg/cpp2a/lambda-targ2.C | 19 +++++++++++++++++++
> > gcc/testsuite/g++.dg/cpp2a/lambda-targ3.C | 12 ++++++++++++
> > gcc/testsuite/g++.dg/cpp2a/lambda-targ4.C | 12 ++++++++++++
> > 6 files changed, 68 insertions(+), 2 deletions(-)
> > create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ2.C
> > create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ3.C
> > create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ4.C
> >
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index c29a5434492..27100537038 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -1538,6 +1538,10 @@ enum cp_lambda_default_capture_mode_type {
> > #define LAMBDA_EXPR_REGEN_INFO(NODE) \
> > (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->regen_info)
> >
> > +/* Like PACK_EXPANSION_EXTRA_ARGS, for lambda-expressions. */
> > +#define LAMBDA_EXPR_EXTRA_ARGS(NODE) \
> > + (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->extra_args)
> > +
> > /* The closure type of the lambda, which is also the type of the
> > LAMBDA_EXPR. */
> > #define LAMBDA_EXPR_CLOSURE(NODE) \
> > @@ -1550,6 +1554,7 @@ struct GTY (()) tree_lambda_expr
> > tree this_capture;
> > tree extra_scope;
> > tree regen_info;
> > + tree extra_args;
> > vec<tree, va_gc> *pending_proxies;
> > location_t locus;
> > enum cp_lambda_default_capture_mode_type default_capture_mode : 2;
> > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > index 52c60cf370c..1cd890909e3 100644
> > --- a/gcc/cp/module.cc
> > +++ b/gcc/cp/module.cc
> > @@ -6312,6 +6312,7 @@ trees_out::core_vals (tree t)
> > WT (((lang_tree_node *)t)->lambda_expression.this_capture);
> > WT (((lang_tree_node *)t)->lambda_expression.extra_scope);
> > WT (((lang_tree_node *)t)->lambda_expression.regen_info);
> > + WT (((lang_tree_node *)t)->lambda_expression.extra_args);
> > /* pending_proxies is a parse-time thing. */
> > gcc_assert (!((lang_tree_node *)t)->lambda_expression.pending_proxies);
> > if (state)
> > @@ -6814,6 +6815,7 @@ trees_in::core_vals (tree t)
> > RT (((lang_tree_node *)t)->lambda_expression.this_capture);
> > RT (((lang_tree_node *)t)->lambda_expression.extra_scope);
> > RT (((lang_tree_node *)t)->lambda_expression.regen_info);
> > + RT (((lang_tree_node *)t)->lambda_expression.extra_args);
> > /* lambda_expression.pending_proxies is NULL */
> > ((lang_tree_node *)t)->lambda_expression.locus
> > = state->read_location (*this);
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 8cf0d5b7a8d..b1a9ee2b385 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -3855,7 +3855,8 @@ has_extra_args_mechanism_p (const_tree t)
> > return (PACK_EXPANSION_P (t) /* PACK_EXPANSION_EXTRA_ARGS */
> > || TREE_CODE (t) == REQUIRES_EXPR /* REQUIRES_EXPR_EXTRA_ARGS */
> > || (TREE_CODE (t) == IF_STMT
> > - && IF_STMT_CONSTEXPR_P (t))); /* IF_STMT_EXTRA_ARGS */
> > + && IF_STMT_CONSTEXPR_P (t)) /* IF_STMT_EXTRA_ARGS */
> > + || TREE_CODE (t) == LAMBDA_EXPR) /* LAMBDA_EXPR_EXTRA_ARGS */;
> > }
> >
> > /* Structure used to track the progress of find_parameter_packs_r. */
> > @@ -19571,6 +19572,18 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
> > tree oldfn = lambda_function (t);
> > in_decl = oldfn;
> >
> > + args = add_extra_args (LAMBDA_EXPR_EXTRA_ARGS (t), args, complain, in_decl);
> > + if (processing_template_decl && !in_template_context)
> > + {
> > + /* Defer templated substitution into a lambda-expr when arguments
> > + are dependent or when we lost the necessary template context,
> > + which may happen for a lambda-expr used as a template argument. */
>
> And this comment is stale (an earlier version of the patch also deferred
> for dependent arguments even when current_template_parms is non-empty,
> which I backed out to make the fix as narrow as possible).
FWIW I also experimented with unconditionally deferring templated
substitution into a lambda-expr (i.e. iff processing_template_decl)
which passed bootstrap+regtest, and turns out to also fix the
(non-regression) PR114167. I didn't analyze the underlying issue
very closely though, there might very well be a better way to fix
that particular non-regression PR.
One downside of unconditionally deferring is that it'd mean less
ahead-of-time checking of uninvoked deeply-nested generic lambdas,
e.g.:
int main() {
[](auto x) {
[](auto) {
[](auto) { decltype(x)::fail; }; // not diagnosed anymore
};
}(0);
}
>
> Better version:
>
> -- >8 --
>
> Subject: [PATCH] c++: templated substitution into lambda-expr [PR114393]
>
> PR c++/114393
> PR c++/107457
> PR c++/93595
>
> gcc/cp/ChangeLog:
>
> * cp-tree.h (LAMBDA_EXPR_EXTRA_ARGS): Define.
> (tree_lambda_expr::extra_args): New field.
> * module.cc (trees_out::core_vals) <case LAMBDA_EXPR>: Stream
> LAMBDA_EXPR_EXTRA_ARGS.
> (trees_in::core_vals) <case LAMBDA_EXPR>: Likewise.
> * pt.cc (has_extra_args_mechanism_p): Return true for LAMBDA_EXPR.
> (tsubst_lambda_expr): Use LAMBDA_EXPR_EXTRA_ARGS to defer templated
> substitution into a lambda-expr if we lost the template context.
> Add sanity check for error_mark_node result from begin_lambda_type.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/cpp2a/lambda-targ2.C: New test.
> * g++.dg/cpp2a/lambda-targ3.C: New test.
> * g++.dg/cpp2a/lambda-targ4.C: New test.
> ---
> gcc/cp/cp-tree.h | 5 +++++
> gcc/cp/module.cc | 2 ++
> gcc/cp/pt.cc | 20 ++++++++++++++++++--
> gcc/testsuite/g++.dg/cpp2a/lambda-targ2.C | 19 +++++++++++++++++++
> gcc/testsuite/g++.dg/cpp2a/lambda-targ3.C | 12 ++++++++++++
> gcc/testsuite/g++.dg/cpp2a/lambda-targ4.C | 12 ++++++++++++
> 6 files changed, 68 insertions(+), 2 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ2.C
> create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ3.C
> create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ4.C
>
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index c29a5434492..27100537038 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -1538,6 +1538,10 @@ enum cp_lambda_default_capture_mode_type {
> #define LAMBDA_EXPR_REGEN_INFO(NODE) \
> (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->regen_info)
>
> +/* Like PACK_EXPANSION_EXTRA_ARGS, for lambda-expressions. */
> +#define LAMBDA_EXPR_EXTRA_ARGS(NODE) \
> + (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->extra_args)
> +
> /* The closure type of the lambda, which is also the type of the
> LAMBDA_EXPR. */
> #define LAMBDA_EXPR_CLOSURE(NODE) \
> @@ -1550,6 +1554,7 @@ struct GTY (()) tree_lambda_expr
> tree this_capture;
> tree extra_scope;
> tree regen_info;
> + tree extra_args;
> vec<tree, va_gc> *pending_proxies;
> location_t locus;
> enum cp_lambda_default_capture_mode_type default_capture_mode : 2;
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index 52c60cf370c..1cd890909e3 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -6312,6 +6312,7 @@ trees_out::core_vals (tree t)
> WT (((lang_tree_node *)t)->lambda_expression.this_capture);
> WT (((lang_tree_node *)t)->lambda_expression.extra_scope);
> WT (((lang_tree_node *)t)->lambda_expression.regen_info);
> + WT (((lang_tree_node *)t)->lambda_expression.extra_args);
> /* pending_proxies is a parse-time thing. */
> gcc_assert (!((lang_tree_node *)t)->lambda_expression.pending_proxies);
> if (state)
> @@ -6814,6 +6815,7 @@ trees_in::core_vals (tree t)
> RT (((lang_tree_node *)t)->lambda_expression.this_capture);
> RT (((lang_tree_node *)t)->lambda_expression.extra_scope);
> RT (((lang_tree_node *)t)->lambda_expression.regen_info);
> + RT (((lang_tree_node *)t)->lambda_expression.extra_args);
> /* lambda_expression.pending_proxies is NULL */
> ((lang_tree_node *)t)->lambda_expression.locus
> = state->read_location (*this);
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 8cf0d5b7a8d..c25bdd283f1 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -3855,7 +3855,8 @@ has_extra_args_mechanism_p (const_tree t)
> return (PACK_EXPANSION_P (t) /* PACK_EXPANSION_EXTRA_ARGS */
> || TREE_CODE (t) == REQUIRES_EXPR /* REQUIRES_EXPR_EXTRA_ARGS */
> || (TREE_CODE (t) == IF_STMT
> - && IF_STMT_CONSTEXPR_P (t))); /* IF_STMT_EXTRA_ARGS */
> + && IF_STMT_CONSTEXPR_P (t)) /* IF_STMT_EXTRA_ARGS */
> + || TREE_CODE (t) == LAMBDA_EXPR) /* LAMBDA_EXPR_EXTRA_ARGS */;
> }
>
> /* Structure used to track the progress of find_parameter_packs_r. */
> @@ -19571,6 +19572,18 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
> tree oldfn = lambda_function (t);
> in_decl = oldfn;
>
> + args = add_extra_args (LAMBDA_EXPR_EXTRA_ARGS (t), args, complain, in_decl);
> + if (processing_template_decl && !in_template_context)
> + {
> + /* Defer templated substitution into a lambda-expr when we lost the
> + necessary template context, which may happen for a lambda-expr
> + used as a template argument. */
> + t = copy_node (t);
> + LAMBDA_EXPR_EXTRA_ARGS (t) = NULL_TREE;
> + LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, complain);
> + return t;
> + }
> +
> tree r = build_lambda_expr ();
>
> LAMBDA_EXPR_LOCATION (r)
> @@ -19662,7 +19675,10 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>
> tree type = begin_lambda_type (r);
> if (type == error_mark_node)
> - return error_mark_node;
> + {
> + gcc_checking_assert (!(complain & tf_error) || seen_error ());
> + return error_mark_node;
> + }
>
> if (LAMBDA_EXPR_EXTRA_SCOPE (t))
> record_lambda_scope (r);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-targ2.C b/gcc/testsuite/g++.dg/cpp2a/lambda-targ2.C
> new file mode 100644
> index 00000000000..41b8d8749f2
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/lambda-targ2.C
> @@ -0,0 +1,19 @@
> +// PR c++/114393
> +// { dg-do compile { target c++20 } }
> +
> +template <auto _DescriptorFn> struct c1 {};
> +
> +template <class _Descriptor, auto t = [] { return _Descriptor(); }>
> +inline constexpr auto b_v = t;
> +
> +template <class _Tag>
> +using c1_t = c1<b_v<int>>;
> +
> +template <class _Data>
> +constexpr auto g(_Data __data) {
> + return c1_t<_Data>{};
> +}
> +
> +void f() {
> + auto &&b = g(0);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-targ3.C b/gcc/testsuite/g++.dg/cpp2a/lambda-targ3.C
> new file mode 100644
> index 00000000000..31d08add277
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/lambda-targ3.C
> @@ -0,0 +1,12 @@
> +// PR c++/107457
> +// { dg-do compile { target c++20 } }
> +
> +template<class T>
> +using lambda = decltype([]() {});
> +
> +template<class R, class F = lambda<R>>
> +void test() { }
> +
> +int main() {
> + test<int>();
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-targ4.C b/gcc/testsuite/g++.dg/cpp2a/lambda-targ4.C
> new file mode 100644
> index 00000000000..341a1aa5bb1
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/lambda-targ4.C
> @@ -0,0 +1,12 @@
> +// PR c++/93595
> +// { dg-do compile { target c++20 } }
> +
> +template<int>
> +struct bad {
> + template<class T, auto = []{ return T(); }>
> + static void f(T) { }
> +};
> +
> +int main() {
> + bad<0>::f(0);
> +}
> --
> 2.44.0.325.g11c821f2f2
>
>
next prev parent reply other threads:[~2024-03-27 14:01 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-03-25 20:44 Patrick Palka
2024-03-25 20:51 ` Patrick Palka
2024-03-27 14:01 ` Patrick Palka [this message]
2024-04-09 20:26 ` Patrick Palka
2024-04-10 16:00 ` Jason Merrill
2024-04-12 13:52 ` Patrick Palka
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=080968c1-af41-3597-f980-667f41ac98a2@idea \
--to=ppalka@redhat.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=jason@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).