On Thu, 5 Sep 2024, Jason Merrill wrote:

> On 9/5/24 2:28 PM, Patrick Palka wrote:
> > On Thu, 5 Sep 2024, Jason Merrill wrote:
> > 
> > > On 9/5/24 1:26 PM, Patrick Palka wrote:
> > > > On Thu, 5 Sep 2024, Jason Merrill wrote:
> > > > 
> > > > > On 9/5/24 10:54 AM, Patrick Palka wrote:
> > > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
> > > > > > for trunk/14?
> > > > > > 
> > > > > > -- >8 --
> > > > > > 
> > > > > > A lambda within a default template argument used in some template-id
> > > > > > may have a smaller template depth than the context of the
> > > > > > template-id.
> > > > > > For example, the lambda in v1's default template argument has
> > > > > > template
> > > > > > depth 1, and in v2's has template depth 2, but the template-ids
> > > > > > v1<0>
> > > > > > and v2<0> which uses these default arguments appear in a depth 3
> > > > > > template
> > > > > > context.  So add_extra_args will ultimately return args with depth 3
> > > > > > --
> > > > > > too many args for the lambda, leading to a bogus substitution.
> > > > > > 
> > > > > > This patch fixes this by trimming the result of add_extra_args to
> > > > > > match
> > > > > > the template depth of the lambda.  A new LAMBDA_EXPR_TEMPLATE_DEPTH
> > > > > > field
> > > > > > is added that tracks the template-ness of a lambda;
> > > > > > 
> > > > > >     PR c++/116567
> > > > > > 
> > > > > > gcc/cp/ChangeLog:
> > > > > > 
> > > > > >     * pt.cc (tsubst_lambda_expr): For a deferred-substitution
> > > > > > lambda,
> > > > > >     trim the augmented template arguments to match the template
> > > > > > depth
> > > > > >     of the lambda.
> > > > > > 
> > > > > > gcc/testsuite/ChangeLog:
> > > > > > 
> > > > > >     * g++.dg/cpp2a/lambda-targ7.C: New test.
> > > > > > ---
> > > > > >     gcc/cp/pt.cc                              | 11 +++++++++
> > > > > >     gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 30
> > > > > > +++++++++++++++++++++++
> > > > > >     2 files changed, 41 insertions(+)
> > > > > >     create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
> > > > > > 
> > > > > > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > > > > > index 747e627f547..c49a26b4f5e 100644
> > > > > > --- a/gcc/cp/pt.cc
> > > > > > +++ b/gcc/cp/pt.cc
> > > > > > @@ -19699,6 +19699,17 @@ tsubst_lambda_expr (tree t, tree args,
> > > > > > tsubst_flags_t complain, tree in_decl)
> > > > > >           LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args,
> > > > > > complain);
> > > > > >           return t;
> > > > > >         }
> > > > > > +  if (LAMBDA_EXPR_EXTRA_ARGS (t))
> > > > > > +    {
> > > > > > +      /* If we deferred substitution into this lambda, then it's
> > > > > > probably
> > > > > > from
> > > > > 
> > > > > "probably" seems wrong, given that it wasn't implemented for this
> > > > > case.
> > > > 
> > > > I said "probably" because in e.g.
> > > > 
> > > >       template<int N, auto F = []{}>
> > > >       bool b = true;
> > > > 
> > > >       template<class T>
> > > >       void f() {
> > > >         b<0>;
> > > >       }
> > > > 
> > > > the lambda context has the same depth as the template-id context.  But
> > > > as you point out, the issue is ultimately related vs unrelated
> > > > parameters rather than depth.
> > > > 
> > > > > 
> > > > > > +    a context (e.g. default template argument context) which may
> > > > > > have
> > > > > > fewer
> > > > > > +    levels than the current context it's embedded in.  Adjust the
> > > > > > result
> > > > > > of
> > > > > > +    add_extra_args accordingly.  */
> > > > > 
> > > > > Hmm, this looks like a situation of not just fewer levels, but
> > > > > potentially
> > > > > unrelated levels.  "args" here is for f, which shares no template
> > > > > context
> > > > > with
> > > > > v1.  What happens if your templates have non-type template parameters?
> > > > 
> > > > Indeed before add_extra_args 'args' will be unrelated, but after doing
> > > > add_extra_args the innermost levels of 'args' will correspond to the
> > > > lambda's template context, and so using get_innermost_template_args
> > > > ought to get rid of the unrelated arguments, keeping only the ones
> > > > relevant to the original lambda context.
> > > 
> > > Will they?  The original function of add_extra_args was to reintroduce
> > > outer
> > > args that we weren't able to substitute the last time through
> > > tsubst_lambda_expr.  I expect the innermost levels of 'args' to be the
> > > same
> > > before and after.
> > > 
> > > Hmm, looking at add_extra_args again, I see that whether the EXTRA_ARGS go
> > > on
> > > the outside or the inside depends on whether they're dependent. How does
> > > this
> > > work other than by accident? >.>
> > 
> > It's kind of a happy accident indeed :P  In the cases this patch is
> > concerned with, i.e. a template-id using a default argument containing
> > a lambda, the extra args will always be considered dependent because
> > during default template argument coercion we substitute an incomplete
> > set of targs into the default targ (namely the element corresponding to
> > the default targ is NULL_TREE), which any_dependent_template_arguments_p
> > considers dependent.  So add_extra_args will reliably put these captured
> > extra args as the innermost!
> 
> I see that the testcases you enabled this code to handle in
> r12-2222-g2c699fd29829cd were also about lambda/requires in default template
> arguments.  Can we detect this case some other way than uses_template_parms?

I think during default template argument coercion we need to set
tf_partial when in a template context.  Then build_extra_args should
remember if this flag was set.  Then add_extra_args should merge the
extra arguments according to whether the flag was set during deferring.

> 
> Can we do the pruning added by this patch in add_extra_args instead of its
> caller?

If we're only concerned with the default template argument situation, then
it seems 'extra' will always be a full set of template arguments (even when
the template-id names a template from the current instantiation) so I think
add_extra_args just needs to substitute into 'extra' and not do any additional
pruning.

> 
> Incidentally, why is having extra outer levels causing trouble?  I thought we
> were generally able to safely ignore them.

I thought extra _inner_ levels are safe to ignore generally?  If we have
extra outer levels then it causes the inner args to no longer be at the
right level.

For

    template<auto N, auto = [] {}>
    bool flag = true;

    template<typename>
    struct core {
      template<typename>
      auto func() -> bool {
        return flag<0>;
      }
    };

    auto main() -> int {
      core<int> _;
      [[maybe_unused]] bool flag = _.func<int>();
    }

we crash during substitution into lambda's 'auto' return type which has
level 2, and 'args' after add_extra_args is {{int},{0, <empty>}}, so
we see non-type argument at level 2 index 0 where a type is expected.
(And if the lambda used N, that substitution would be similarly wrong
as well.)

How does the following look?  Bootstrapped and regtested on
x86_64-pc-linux-gnu.  This would be too risky to backport, so for 14 I
think we should go with the original patch.

-- >8 --

Subject: [PATCH] c++: deferring partial substitution into lambda [PR116567]

        PR c++/116567

gcc/cp/ChangeLog:

        * pt.cc (coerce_template_parms): Pass tf_partial when substituting
        into a default template argument in a template context.
        (build_extra_args): Set TREE_STATIC on the extra args if this is
        a partial substitution.
        (add_extra_args): Handle deferred partial substitution.
        (tsubst_lammda_expr): Check for tf_partial instead of dependence
        of args when determining whether to defer substitution.
        (tsubst_expr) <case LAMBDA_EXPR>: Remove tf_partial early exit.

gcc/testsuite/ChangeLog:

        * g++.dg/cpp2a/lambda-targ7.C: New test.
---
 gcc/cp/pt.cc                              | 33 +++++++++---------
 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 42 +++++++++++++++++++++++
 2 files changed, 58 insertions(+), 17 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 9195a5274e1..9a9c16a47c2 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -9326,7 +9326,9 @@ coerce_template_parms (tree parms,
        {
          /* There must be a default arg in this case.  */
          arg = tsubst_template_arg (TREE_PURPOSE (parm), new_args,
-                                    complain, in_decl);
+                                    complain | (processing_template_decl
+                                                ? tf_partial : tf_none),
+                                    in_decl);
          /* The position of the first default template argument,
             is also the number of non-defaulted arguments in NEW_INNER_ARGS.
             Record that.  */
@@ -13572,6 +13574,9 @@ build_extra_args (tree pattern, tree args, 
tsubst_flags_t complain)
   if (local_specializations)
     if (tree locals = extract_local_specs (pattern, complain))
       extra = tree_cons (NULL_TREE, extra, locals);
+  if (complain & tf_partial)
+    /* Remember whether this is a partial substitution.  */
+    TREE_STATIC (extra) = true;
   return extra;
 }
 
@@ -13581,7 +13586,10 @@ build_extra_args (tree pattern, tree args, 
tsubst_flags_t complain)
 tree
 add_extra_args (tree extra, tree args, tsubst_flags_t complain, tree in_decl)
 {
-  if (extra && TREE_CODE (extra) == TREE_LIST)
+  if (!extra)
+    return args;
+
+  if (TREE_CODE (extra) == TREE_LIST)
     {
       for (tree elt = TREE_CHAIN (extra); elt; elt = TREE_CHAIN (elt))
        {
@@ -13599,13 +13607,11 @@ add_extra_args (tree extra, tree args, tsubst_flags_t 
complain, tree in_decl)
       gcc_assert (!TREE_PURPOSE (extra));
       extra = TREE_VALUE (extra);
     }
-  if (uses_template_parms (extra))
-    {
-      /* This can happen after dependent substitution into a
-        requires-expr or a lambda that uses constexpr if.  */
-      extra = tsubst_template_args (extra, args, complain, in_decl);
-      args = add_outermost_template_args (args, extra);
-    }
+  if (TREE_STATIC (extra))
+    /* This is a partial substitution into e.g. a requires-expr or lambda
+       inside a default template argument; we expect 'extra' to be a full
+       set of template arguments for the template context.  */
+    args = tsubst_template_args (extra, args, complain, in_decl);
   else
     args = add_to_template_args (extra, args);
   return args;
@@ -19706,7 +19712,7 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
 
   args = add_extra_args (LAMBDA_EXPR_EXTRA_ARGS (t), args, complain, in_decl);
   if (processing_template_decl
-      && (!in_template_context || any_dependent_template_arguments_p (args)))
+      && (!in_template_context || (complain & tf_partial)))
     {
       /* Defer templated substitution into a lambda-expr if we lost the
         necessary template context.  This may happen for a lambda-expr
@@ -21827,13 +21833,6 @@ tsubst_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
 
     case LAMBDA_EXPR:
       {
-       if (complain & tf_partial)
-         {
-           /* We don't have a full set of template arguments yet; don't touch
-              the lambda at all.  */
-           gcc_assert (processing_template_decl);
-           return t;
-         }
        tree r = tsubst_lambda_expr (t, args, complain, in_decl);
 
        RETURN (build_lambda_object (r));
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C 
b/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
new file mode 100644
index 00000000000..97b42d6ed0c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
@@ -0,0 +1,42 @@
+// PR c++/116567
+// { dg-do compile { target c++20 } }
+
+struct X { int n; };
+
+template<auto N, auto F = []{ return N; }>
+auto v1 = F;
+
+template<auto N, auto F = [](auto y){ return decltype(N){N.n + y}; }>
+auto v1g = F;
+
+template<class T>
+struct A {
+  template<auto N, auto F = []{ return N; }>
+  static inline auto v2 = F;
+
+  template<auto N, auto F = [](auto y){ return decltype(N){N.n + y}; }>
+  static inline auto v2g = F;
+
+  template<class U>
+  struct B {
+    template<auto N, auto F = []{ return N; }>
+    static inline auto v3 = F;
+
+    template<auto N, auto F = [](auto y){ return decltype(N){N.n + y}; }>
+    static inline auto v3g = F;
+
+    template<class V>
+    static void f() {
+      static_assert(v1<X{1}>().n == 1);
+      static_assert(v1g<X{1}>(42).n == 1 + 42);
+      static_assert(v2<X{2}>().n == 2);
+      static_assert(v2g<X{2}>(42).n == 2 + 42);
+      static_assert(v3<X{3}>().n == 3);
+      static_assert(v3g<X{3}>(42).n == 3 + 42);
+    }
+  };
+};
+
+int main() {
+  A<int>::B<int>::f<int>();
+}
-- 
2.46.0.519.g2e7b89e038

Reply via email to