On 4/7/21 12:10 PM, Patrick Palka wrote:
We currently substitute through a lambda's constraints whenever we
regenerate it via tsubst_lambda_expr.  This is the wrong approach
because it can lead to hard errors due to constraints being evaluated
out of order (as in the testcase concepts-lambda17.C below), and because
it doesn't mesh well with the recently added REQUIRES_EXPR_EXTRA_ARGS
mechanism for delaying substitution into requires-expressions, which is
the cause of this PR.

But in order to avoid substituting through a lambda's constraints during
regeneration, we we need to be able to get at all in-scope template
parameters and the corresponding template arguments during constraint
checking of a lambda's op().  And this information is not easily
available where we need it, it seems.

To that end, the approach that this patch takes is to add two new fields
to LAMBDA_EXPR (and remove one): LAMBDA_EXPR_REGENERATED_FROM
(replacing LAMBDA_EXPR_INSTANTIATED), and LAMBDA_EXPR_REGENERATING_TARGS.
The former allows us to obtain the complete set of template parameters
that are in-scope for a lambda's op(), and the latter gives us all outer
template arguments that were used to regenerate the lambda.

I'm a little surprised you didn't use a TEMPLATE_INFO for these, but this way is fine too.

LAMBDA_EXPR_REGENERATING_TARGS is not strictly necessary -- in an
earlier version of the patch, I walked LAMBDA_EXPR_EXTRA_SCOPE to build
up this set of outer template arguments on demand, but it's cleaner to
do it this way.  (We'd need to walk LAMBDA_EXPR_EXTRA_SCOPE and not
DECL/TYPE_CONTEXT because the latter skips over variable template
scopes.)

This patch also renames the predicate instantiated_lambda_fn_p to
regenerated_lambda_fn_p, for sake of consistency with the rest of the
patch which uses "regenerated" instead of "instantiated".

Bootstrapped and regtested on x86_64-pc-linux-gnu, and also tested on
cmcstl2 and range-v3.  Does this look OK for trunk?

OK.

gcc/cp/ChangeLog:

        PR c++/99874
        * constraint.cc (get_normalized_constraints_from_decl): Handle
        regenerated lambdas.
        (satisfy_declaration_constraints): Likewise.  Check for
        dependent args later.
        * cp-tree.h (LAMBDA_EXPR_INSTANTIATED): Replace with ...
        (LAMBDA_EXPR_REGENERATED_FROM): ... this.
        (LAMBDA_EXPR_REGENERATING_TARGS): New.
        (tree_lambda_expr::regenerated_from): New data member.
        (tree_lambda_expr::regenerating_targs): New data member.
        (add_to_template_args): Declare.
        (regenerated_lambda_fn_p): Likewise.
        (most_general_lambda): Likewise.
        * lambda.c (build_lambda_expr): Set LAMBDA_EXPR_REGENERATED_FROM
        and LAMBDA_EXPR_REGENERATING_TARGS.
        * pt.c (add_to_template_args): No longer static.
        (tsubst_function_decl): Unconditionally propagate constraints on
        the substituted function decl.
        (instantiated_lambda_fn_p): Rename to ...
        (regenerated_lambda_fn_p): ... this.  Check
        LAMBDA_EXPR_REGENERATED_FROM instead of
        LAMBDA_EXPR_INSTANTIATED.
        (most_general_lambda): Define.
        (enclosing_instantiation_of): Adjust after renaming
        instantiated_lambda_fn_p.
        (tsubst_lambda_expr): Don't substitute or set constraints on
        the regenerated lambda.

gcc/testsuite/ChangeLog:

        PR c++/99874
        * g++.dg/cpp2a/concepts-lambda16.C: New test.
        * g++.dg/cpp2a/concepts-lambda17.C: New test.
---
  gcc/cp/constraint.cc                          | 43 +++++++++++--
  gcc/cp/cp-tree.h                              | 20 ++++--
  gcc/cp/lambda.c                               |  2 +
  gcc/cp/pt.c                                   | 42 ++++++-------
  .../g++.dg/cpp2a/concepts-lambda16.C          | 61 +++++++++++++++++++
  .../g++.dg/cpp2a/concepts-lambda17.C          | 14 +++++
  6 files changed, 150 insertions(+), 32 deletions(-)
  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-lambda16.C
  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-lambda17.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 5cf43bd6c18..bd526f669ab 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -886,6 +886,16 @@ get_normalized_constraints_from_decl (tree d, bool diag = 
false)
       it has the correct template information attached. */
    d = strip_inheriting_ctors (d);
+ if (regenerated_lambda_fn_p (d))
+    {
+      /* If this lambda was regenerated, DECL_TEMPLATE_PARMS doesn't contain
+        all in-scope template parameters, but the lambda from which it was
+        ultimately regenerated does, so use that instead.  */
+      tree lambda = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (d));
+      lambda = most_general_lambda (lambda);
+      d = lambda_function (lambda);
+    }
+
    if (TREE_CODE (d) == TEMPLATE_DECL)
      {
        tmpl = d;
@@ -3174,13 +3184,27 @@ satisfy_declaration_constraints (tree t, sat_info info)
        args = TI_ARGS (ti);
        if (inh_ctor_targs)
        args = add_outermost_template_args (args, inh_ctor_targs);
+    }
- /* If any arguments depend on template parameters, we can't
-        check constraints. Pretend they're satisfied for now.  */
-      if (uses_template_parms (args))
-       return boolean_true_node;
+  if (regenerated_lambda_fn_p (t))
+    {
+      /* The DECL_TI_ARGS of a regenerated lambda contains only the innermost
+        set of template arguments.  Augment this with the outer template
+        arguments that were used to regenerate the lambda.  */
+      gcc_assert (!args || TMPL_ARGS_DEPTH (args) == 1);
+      tree lambda = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (t));
+      tree outer_args = LAMBDA_EXPR_REGENERATING_TARGS (lambda);
+      if (args)
+       args = add_to_template_args (outer_args, args);
+      else
+       args = outer_args;
      }
+ /* If any arguments depend on template parameters, we can't
+     check constraints. Pretend they're satisfied for now.  */
+  if (uses_template_parms (args))
+    return boolean_true_node;
+
    /* Get the normalized constraints.  */
    tree norm = get_normalized_constraints_from_decl (t, info.noisy ());
@@ -3227,7 +3251,16 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info) gcc_assert (TREE_CODE (t) == TEMPLATE_DECL); - args = add_outermost_template_args (t, args);
+  if (regenerated_lambda_fn_p (t))
+    {
+      /* As in the two-parameter version of this function.  */
+      gcc_assert (!args || TMPL_ARGS_DEPTH (args) == 1);
+      tree lambda = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (t));
+      tree outer_args = LAMBDA_EXPR_REGENERATING_TARGS (lambda);
+      args = add_to_template_args (outer_args, args);
+    }
+  else
+    args = add_outermost_template_args (t, args);
/* If any arguments depend on template parameters, we can't
       check constraints. Pretend they're satisfied for now.  */
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index a5d9d7ac625..bf9d5add0cf 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -490,7 +490,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
        DECLTYPE_FOR_REF_CAPTURE (in DECLTYPE_TYPE)
        CONSTRUCTOR_C99_COMPOUND_LITERAL (in CONSTRUCTOR)
        OVL_NESTED_P (in OVERLOAD)
-      LAMBDA_EXPR_INSTANTIATED (in LAMBDA_EXPR)
        DECL_MODULE_EXPORT_P (in _DECL)
     4: IDENTIFIER_MARKED (IDENTIFIER_NODEs)
        TREE_HAS_CONSTRUCTOR (in INDIRECT_REF, SAVE_EXPR, CONSTRUCTOR,
@@ -1434,10 +1433,6 @@ enum cp_lambda_default_capture_mode_type {
  #define LAMBDA_EXPR_CAPTURE_OPTIMIZED(NODE) \
    TREE_LANG_FLAG_2 (LAMBDA_EXPR_CHECK (NODE))
-/* True iff this LAMBDA_EXPR was generated in tsubst_lambda_expr. */
-#define LAMBDA_EXPR_INSTANTIATED(NODE) \
-  TREE_LANG_FLAG_3 (LAMBDA_EXPR_CHECK (NODE))
-
  /* True if this TREE_LIST in LAMBDA_EXPR_CAPTURE_LIST is for an explicit
     capture.  */
  #define LAMBDA_CAPTURE_EXPLICIT_P(NODE) \
@@ -1461,6 +1456,16 @@ enum cp_lambda_default_capture_mode_type {
  #define LAMBDA_EXPR_PENDING_PROXIES(NODE) \
    (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->pending_proxies)
+/* The immediate LAMBDA_EXPR from which NODE was regenerated, or NULL_TREE
+   (if NODE was not regenerated via tsubst_lambda_expr).  */
+#define LAMBDA_EXPR_REGENERATED_FROM(NODE) \
+  (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->regenerated_from)
+
+/* The full set of template arguments used to regenerate NODE, or NULL_TREE
+   (if NODE was not regenerated via tsubst_lambda_expr).  */
+#define LAMBDA_EXPR_REGENERATING_TARGS(NODE) \
+  (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->regenerating_targs)
+
  /* The closure type of the lambda, which is also the type of the
     LAMBDA_EXPR.  */
  #define LAMBDA_EXPR_CLOSURE(NODE) \
@@ -1472,6 +1477,8 @@ struct GTY (()) tree_lambda_expr
    tree capture_list;
    tree this_capture;
    tree extra_scope;
+  tree regenerated_from;
+  tree regenerating_targs;
    vec<tree, va_gc> *pending_proxies;
    location_t locus;
    enum cp_lambda_default_capture_mode_type default_capture_mode : 8;
@@ -7247,6 +7254,7 @@ extern unsigned get_mergeable_specialization_flags (tree 
tmpl, tree spec);
  extern void add_mergeable_specialization        (bool is_decl, bool is_alias,
                                                 spec_entry *,
                                                 tree outer, unsigned);
+extern tree add_to_template_args               (tree, tree);
  extern tree add_outermost_template_args               (tree, tree);
  extern tree add_extra_args                    (tree, tree);
  extern tree build_extra_args                  (tree, tree, tsubst_flags_t);
@@ -7557,6 +7565,8 @@ extern void record_null_lambda_scope              (tree);
  extern void finish_lambda_scope                       (void);
  extern tree start_lambda_function             (tree fn, tree lambda_expr);
  extern void finish_lambda_function            (tree body);
+extern bool regenerated_lambda_fn_p            (tree);
+extern tree most_general_lambda                        (tree);
/* in tree.c */
  extern int cp_tree_operand_length             (const_tree);
diff --git a/gcc/cp/lambda.c b/gcc/cp/lambda.c
index b0fd6ecc57e..c0a5ffb427e 100644
--- a/gcc/cp/lambda.c
+++ b/gcc/cp/lambda.c
@@ -41,6 +41,8 @@ build_lambda_expr (void)
    LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda) = CPLD_NONE;
    LAMBDA_EXPR_CAPTURE_LIST         (lambda) = NULL_TREE;
    LAMBDA_EXPR_THIS_CAPTURE         (lambda) = NULL_TREE;
+  LAMBDA_EXPR_REGENERATED_FROM     (lambda) = NULL_TREE;
+  LAMBDA_EXPR_REGENERATING_TARGS   (lambda) = NULL_TREE;
    LAMBDA_EXPR_PENDING_PROXIES      (lambda) = NULL;
    LAMBDA_EXPR_MUTABLE_P            (lambda) = false;
    return lambda;
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index a08d08d2834..7917a280804 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -151,7 +151,6 @@ static tree coerce_template_parms (tree, tree, tree, 
tsubst_flags_t,
  static tree coerce_innermost_template_parms (tree, tree, tree, tsubst_flags_t,
                                              bool, bool);
  static void tsubst_enum       (tree, tree, tree);
-static tree add_to_template_args (tree, tree);
  static bool check_instantiated_args (tree, tree, tsubst_flags_t);
  static int check_non_deducible_conversion (tree, tree, int, int,
                                           struct conversion **, bool);
@@ -553,7 +552,7 @@ maybe_end_member_template_processing (void)
  /* Return a new template argument vector which contains all of ARGS,
     but has as its innermost set of arguments the EXTRA_ARGS.  */
-static tree
+tree
  add_to_template_args (tree args, tree extra_args)
  {
    tree new_args;
@@ -14058,10 +14057,7 @@ tsubst_function_decl (tree t, tree args, 
tsubst_flags_t complain,
       don't substitute through the constraints; that's only done when
       they are checked.  */
    if (tree ci = get_constraints (t))
-    /* Unless we're regenerating a lambda, in which case we'll set the
-       lambda's constraints in tsubst_lambda_expr.  */
-    if (!lambda_fntype)
-      set_constraints (r, ci);
+    set_constraints (r, ci);
if (DECL_FRIEND_CONTEXT (t))
      SET_DECL_FRIEND_CONTEXT (r,
@@ -14347,13 +14343,24 @@ lambda_fn_in_template_p (tree fn)
     which the above is true.  */
bool
-instantiated_lambda_fn_p (tree fn)
+regenerated_lambda_fn_p (tree fn)
  {
    if (!fn || !LAMBDA_FUNCTION_P (fn))
      return false;
    tree closure = DECL_CONTEXT (fn);
    tree lam = CLASSTYPE_LAMBDA_EXPR (closure);
-  return LAMBDA_EXPR_INSTANTIATED (lam);
+  return LAMBDA_EXPR_REGENERATED_FROM (lam) != NULL_TREE;
+}
+
+/* Return the LAMBDA_EXPR from which T was ultimately regenerated.
+   If T is not a regenerated LAMBDA_EXPR, return T.  */
+
+tree
+most_general_lambda (tree t)
+{
+  while (LAMBDA_EXPR_REGENERATED_FROM (t))
+    t = LAMBDA_EXPR_REGENERATED_FROM (t);
+  return t;
  }
/* We're instantiating a variable from template function TCTX. Return the
@@ -14369,7 +14376,7 @@ enclosing_instantiation_of (tree otctx)
    int lambda_count = 0;
for (; tctx && (lambda_fn_in_template_p (tctx)
-                 || instantiated_lambda_fn_p (tctx));
+                 || regenerated_lambda_fn_p (tctx));
         tctx = decl_function_context (tctx))
      ++lambda_count;
@@ -14389,7 +14396,7 @@ enclosing_instantiation_of (tree otctx)
      {
        tree ofn = fn;
        int flambda_count = 0;
-      for (; fn && instantiated_lambda_fn_p (fn);
+      for (; fn && regenerated_lambda_fn_p (fn);
           fn = decl_function_context (fn))
        ++flambda_count;
        if ((fn && DECL_TEMPLATE_INFO (fn))
@@ -19264,7 +19271,9 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
    LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (r)
      = LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (t);
    LAMBDA_EXPR_MUTABLE_P (r) = LAMBDA_EXPR_MUTABLE_P (t);
-  LAMBDA_EXPR_INSTANTIATED (r) = true;
+  LAMBDA_EXPR_REGENERATED_FROM (r) = t;
+  LAMBDA_EXPR_REGENERATING_TARGS (r)
+    = add_to_template_args (LAMBDA_EXPR_REGENERATING_TARGS (t), args);
gcc_assert (LAMBDA_EXPR_THIS_CAPTURE (t) == NULL_TREE
              && LAMBDA_EXPR_PENDING_PROXIES (t) == NULL);
@@ -19406,17 +19415,6 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
          finish_member_declaration (fn);
        }
- if (tree ci = get_constraints (oldfn))
-       {
-         /* Substitute into the lambda's constraints.  */
-         if (oldtmpl)
-           ++processing_template_decl;
-         ci = tsubst_constraint_info (ci, args, complain, in_decl);
-         if (oldtmpl)
-           --processing_template_decl;
-         set_constraints (fn, ci);
-       }
-
        /* Let finish_function set this.  */
        DECL_DECLARED_CONSTEXPR_P (fn) = false;
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-lambda16.C b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda16.C
new file mode 100644
index 00000000000..01fda6efb38
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda16.C
@@ -0,0 +1,61 @@
+// PR c++/99874
+// { dg-do compile { target c++20 } }
+
+template <class T>
+struct A {
+  static inline auto a = [] <class U> (U) {
+    return [] <class V> (V) requires requires (T t, U u, V v) { t + u + v; } { 
};
+  };
+
+  template <class W>
+  static inline auto b = [] <class U> (U) {
+    return [] <class V> (V) requires requires (T t, U u, V v, W w) { t + u + v 
+ w; } { };
+  };
+
+  static auto f() {
+    return [] <class U> (U) {
+      return [] <class V> (V) requires requires (T t, U u, V v) { t + u + v; } 
{ };
+    };
+  }
+
+  template <class W>
+  static auto g() {
+    return [] <class U> (U) {
+      return [] <class V> (V) requires requires (T t, U u, V v, W w) { t + u + 
v + w; } { };
+    };
+  }
+};
+
+template <class T>
+auto a = [] <class U> (U) {
+  return [] <class V> (V) requires requires (T t, U u, V v) { t + u + v; } { };
+};
+
+template <class T>
+auto b = [] <class U> (U) {
+  return [] <class V> (V) {
+    return [] {
+      return [] () requires requires (T t, U u, V v) { t + u + v; } { };
+    };
+  };
+};
+
+int main() {
+  A<int>::a(0)(0);
+  A<int>::a(0)(nullptr); // { dg-error "no match" }
+
+  A<int>::b<int>(0)(0);
+  A<int>::b<int>(0)(nullptr); // { dg-error "no match" }
+
+  A<int>::f()(0)(0);
+  A<int>::f()(0)(nullptr); // { dg-error "no match" }
+
+  A<int>::g<int>()(0)(0);
+  A<int>::g<int>()(0)(nullptr); // { dg-error "no match" }
+
+  a<int>(0)(0);
+  a<int>(0)(nullptr); // { dg-error "no match" }
+
+  b<int>(0)(0)();
+  b<int>(0)(nullptr)()(); // { dg-error "no match" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-lambda17.C 
b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda17.C
new file mode 100644
index 00000000000..32ae1e12174
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda17.C
@@ -0,0 +1,14 @@
+// { dg-do compile { target c++20 } }
+
+template<class T>
+struct A { static const bool value = T::value; };
+
+template<class T>
+void f() {
+  // Verify we don't substitute into a lambda's constraints when
+  // regenerating it, which would lead to a hard error here.
+  [] () requires (T::value && A<T>::value) || true { }();
+  [] <class U> (U) requires (U::value && A<T>::value) || true { }(0);
+}
+
+template void f<int>();


Reply via email to